diff --git a/Gemfile.lock b/Gemfile.lock index 35d6b177b7..cfddd7f1e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,7 @@ GEM PLATFORMS x86_64-darwin-19 + x86_64-linux DEPENDENCIES activerecord (~> 6.1) @@ -106,4 +107,4 @@ DEPENDENCIES thin (~> 1.8) BUNDLED WITH - 2.2.23 + 2.3.4 diff --git a/README.md b/README.md index 2fbb296827..2c1a1ce2bf 100644 --- a/README.md +++ b/README.md @@ -20,28 +20,13 @@ by a separate **React frontend** that interacts with the database via the API. For this project, you must: - Use Active Record to interact with a database. -- Have at least two models with a one-to-many relationship. -- At a minimum, set up the following API routes in Sinatra: - - create and read actions for both models - - full CRUD capability for one of the models: - The update action should be implemented using a form that is - pre-filled with existing values for the object. On submission of - the form, the object should update. Note: Using a like button or - similar will not meet the update requirement. +- Have a minimum of two models with a one-to-many relationship. +- Create API routes in Sinatra that handles at least three different CRUD + actions for at least one of your Active Record models. - Build a separate React frontend application that interacts with the API to perform CRUD actions. -- Implement proper front end state management. You should be updating state using a - setState function after receiving your response from a POST, PATCH, or DELETE - request. You should NOT be relying on a GET request to update state. - Use good OO design patterns. You should have separate classes for each of your - models, and create instance and class methods as necessary. -- Routes in your application (both client side and back end) should follow RESTful - conventions. -- Use your back end optimally. Pass JSON for related associations to the front - end from the back end. You should use active record methods in your controller to grab - the needed data from your database and provide as JSON to the front end. You - should NOT be relying on filtering front end state or a separate fetch request to - retrieve related data. + models, and create instance and class methods as necessary. For example, build a todo list application with a React frontend interface and a Sinatra backend API, where a user can: @@ -62,11 +47,6 @@ This repository has all the starter code needed to get a Sinatra backend up and running. [**Fork and clone**][fork link] this repository to get started. Then, run `bundle install` to install the gems. -**Important**: Be sure you fork a copy of the repo into your GitHub account -before cloning it. You can do this by using the link above or by clicking the -"Octocat" button at the top of this page, then clicking "Fork" in the upper -right corner of the repo page. - [fork link]: https://github.com/learn-co-curriculum/phase-3-sinatra-react-project/fork The `app/controllers/application_controller.rb` file has an example GET route @@ -86,7 +66,7 @@ This will run your server on port Your backend and your frontend should be in **two different repositories**. Create a new repository in a **separate folder** with a React app for your -frontend. To do this, `cd` out of the backend project directory, and use +frontend. `cd` out of the backend project directory, and use [create-react-app][] to generate the necessary code for your React frontend: ```console @@ -136,3 +116,4 @@ fetch("http://localhost:9292/test") [dbdiagram.io]: https://dbdiagram.io/ [postman download]: https://www.postman.com/downloads/ [network tab]: https://developer.chrome.com/docs/devtools/network/ +# phase-3-sinatra-react-project diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ed3d3c96ae..9ede46240e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,8 +2,49 @@ class ApplicationController < Sinatra::Base set :default_content_type, 'application/json' # Add your routes here - get "/" do - { message: "Good luck with your project!" }.to_json + + get "/books" do + books = Book.all + books.to_json(include: :reviews) + end + + get "/books/" do + book = Book.all(title: params[:title], author: params[:author], likes: params[:likes]) + book.to_json(include: :reviews) + end + + + get 'reviews/:id' do + review = Review.find(params[:id]) + review.to_json + end + + #create a new book + post "/books" do + new_book = Book.create(title: params[:title], author: params[:author], likes: params[:likes]) + new_book.to_json + end + + #create a new review + post '/books/:book_id/reviews' do + book = Book.find_by(id: params[:book_id]) + new_review = book.reviews.create(text: params[:text]) + new_review.to_json(include: :book) + + end + + #update book likes + patch '/books/:id' do + book = Book.find_by(id: params[:id]) + book.update(likes: params[:likes]) + book.to_json(include: :reviews) + end + + #delete book review + delete '/reviews/:id' do + review = Review.find_by(id: params[:id]) + review.destroy + review.to_json end end diff --git a/app/models/book.rb b/app/models/book.rb new file mode 100644 index 0000000000..8d29a864fa --- /dev/null +++ b/app/models/book.rb @@ -0,0 +1,3 @@ +class Book < ActiveRecord::Base + has_many :reviews +end \ No newline at end of file diff --git a/app/models/.gitkeep b/app/models/gitkeep.txt similarity index 100% rename from app/models/.gitkeep rename to app/models/gitkeep.txt diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..092c152715 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,3 @@ +class Review < ActiveRecord::Base + belongs_to :book +end \ No newline at end of file diff --git a/db/migrate/20220209162053_create_books.rb b/db/migrate/20220209162053_create_books.rb new file mode 100644 index 0000000000..bdb5b13798 --- /dev/null +++ b/db/migrate/20220209162053_create_books.rb @@ -0,0 +1,9 @@ +class CreateBooks < ActiveRecord::Migration[6.1] + def change + create_table :books do |t| + t.string :title + t.string :author + t.integer :likes + end + end +end diff --git a/db/migrate/20220209162102_create_reviews.rb b/db/migrate/20220209162102_create_reviews.rb new file mode 100644 index 0000000000..58f7422fab --- /dev/null +++ b/db/migrate/20220209162102_create_reviews.rb @@ -0,0 +1,8 @@ +class CreateReviews < ActiveRecord::Migration[6.1] + def change + create_table :reviews do |t| + t.string :text + t.integer :book_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..74fb504939 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,26 @@ +# 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.define(version: 2022_02_09_162102) do + + create_table "books", force: :cascade do |t| + t.string "title" + t.string "author" + t.integer "likes" + end + + create_table "reviews", force: :cascade do |t| + t.string "text" + t.integer "book_id" + end + +end \ No newline at end of file diff --git a/db/seeds.rb b/db/seeds.rb index c79fec5b1b..2df13c1810 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,5 +1,27 @@ puts "🌱 Seeding spices..." # Seed your database here +all_reviews = ["This play is as relevant today as it was in 1959, and more relevant to American students than Shakespeare may ever be.", "I did not like this book at all. All the drug, booze, swearing was just too much for me to overlook. Perhaps there is an element of society that lives this kind of life, but I really didn't enjoy reading about it.", +"French girls farther is a museum custodian and the jewel now resides behind 13 locked doors like Russian dolls. Guess the girl and soldier meet up … could not take any more.", "Every story in this collection is a gem. ", +"Boring... ", "I dont need to feel ashamed for how Im not “woke enough” these days. I get enough of that on Twitter.", "I bought this because it was required reading for my English class.", +"I enjoyed this novel very much because it rang true about life in general during this period and because it was so well written.", "This is a fantastic edition to own, and I'm glad to have it. 5 stars for the book.", +" I came to a conclusion that there's simply nothing else like it, and it tops my list."] -puts "✅ Done seeding!" +Book.create(title: "A Raisin in the Sun", author: "Lorraine Hansberry", likes: 0) +Book.create(title: "The Goldfinch", author: "Donna Tartt", likes: 0) +Book.create(title: "All the Light We Can Not See", author: "Anthony Doerr", likes: 0) +Book.create(title: "Welcome to the Monkey House", author: "Kurt Vonnegut", likes: 0) +Book.create(title: "Paradise Lost", author: "John Milton", likes: 0) +Book.create(title: "Bad Feminist", author: "Roxane Gay", likes: 0) +Book.create(title: "Tracks", author: "Louise Erdrich", likes: 0) +Book.create(title: "The Outsiders", author: "SE Hinton", likes: 0) +Book.create(title: "The Lord of the Rings", author: "JRR Tolkien", likes: 0) +Book.create(title: "And Then There Were None", author: "Agatha Christie", likes: 0) + +Book.all.each do |book| + Review.create(text: all_reviews.sample, book_id: book.id) +end + + + +puts "✅ Done seeding!" \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8c3256b0cb..0c459d4f84 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -45,4 +45,4 @@ # Rack::Test::Methods needs this to run our controller def app Rack::Builder.parse_file('config.ru').first -end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/bin/build b/vendor/bundle/ruby/3.0.0/bin/build new file mode 100755 index 0000000000..c1d6a2330a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/build @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'rspec-json_expectations' 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-json_expectations', 'build', version) +else +gem "rspec-json_expectations", version +load Gem.bin_path("rspec-json_expectations", "build", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/bundle b/vendor/bundle/ruby/3.0.0/bin/bundle new file mode 100755 index 0000000000..052ee126a3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/bundle @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'bundler' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ENV['BUNDLER_VERSION'] = str + + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('bundler', 'bundle', version) +else +gem "bundler", version +load Gem.bin_path("bundler", "bundle", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/bundler b/vendor/bundle/ruby/3.0.0/bin/bundler new file mode 100755 index 0000000000..0dac315507 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/bundler @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'bundler' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ENV['BUNDLER_VERSION'] = str + + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('bundler', 'bundler', version) +else +gem "bundler", version +load Gem.bin_path("bundler", "bundler", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/coderay b/vendor/bundle/ruby/3.0.0/bin/coderay new file mode 100755 index 0000000000..23f1cd8f6f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/coderay @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'coderay' 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('coderay', 'coderay', version) +else +gem "coderay", version +load Gem.bin_path("coderay", "coderay", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/htmldiff b/vendor/bundle/ruby/3.0.0/bin/htmldiff new file mode 100755 index 0000000000..e625295032 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/htmldiff @@ -0,0 +1,31 @@ +#!/bin/sh +'exec' "ruby3.0" '-x' "$0" "$@" +#!/usr/bin/ruby3.0 +# +# 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.0.0/bin/ldiff b/vendor/bundle/ruby/3.0.0/bin/ldiff new file mode 100755 index 0000000000..204ba0188f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/ldiff @@ -0,0 +1,31 @@ +#!/bin/sh +'exec' "ruby3.0" '-x' "$0" "$@" +#!/usr/bin/ruby3.0 +# +# 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.0.0/bin/listen b/vendor/bundle/ruby/3.0.0/bin/listen new file mode 100755 index 0000000000..934c469a30 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/listen @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'listen' 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('listen', 'listen', version) +else +gem "listen", version +load Gem.bin_path("listen", "listen", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/pry b/vendor/bundle/ruby/3.0.0/bin/pry new file mode 100755 index 0000000000..19b67d338b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/pry @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'pry' 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('pry', 'pry', version) +else +gem "pry", version +load Gem.bin_path("pry", "pry", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/rackup b/vendor/bundle/ruby/3.0.0/bin/rackup new file mode 100755 index 0000000000..2888305579 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/rackup @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'rack' 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('rack', 'rackup', version) +else +gem "rack", version +load Gem.bin_path("rack", "rackup", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/rake b/vendor/bundle/ruby/3.0.0/bin/rake new file mode 100755 index 0000000000..19817d9f63 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/rake @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# 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.0.0/bin/rerun b/vendor/bundle/ruby/3.0.0/bin/rerun new file mode 100755 index 0000000000..5fe1f050fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/rerun @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'rerun' 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('rerun', 'rerun', version) +else +gem "rerun", version +load Gem.bin_path("rerun", "rerun", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/rspec b/vendor/bundle/ruby/3.0.0/bin/rspec new file mode 100755 index 0000000000..0f3ab644de --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/rspec @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# 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.0.0/bin/thin b/vendor/bundle/ruby/3.0.0/bin/thin new file mode 100755 index 0000000000..6d0e88c98f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/thin @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'thin' 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('thin', 'thin', version) +else +gem "thin", version +load Gem.bin_path("thin", "thin", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/tilt b/vendor/bundle/ruby/3.0.0/bin/tilt new file mode 100755 index 0000000000..548aa220a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/tilt @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'tilt' 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('tilt', 'tilt', version) +else +gem "tilt", version +load Gem.bin_path("tilt", "tilt", version) +end diff --git a/vendor/bundle/ruby/3.0.0/bin/with-rspec-3 b/vendor/bundle/ruby/3.0.0/bin/with-rspec-3 new file mode 100755 index 0000000000..648b847ade --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/bin/with-rspec-3 @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.0 +# +# This file was generated by RubyGems. +# +# The application 'rspec-json_expectations' 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-json_expectations', 'with-rspec-3', version) +else +gem "rspec-json_expectations", version +load Gem.bin_path("rspec-json_expectations", "with-rspec-3", version) +end diff --git a/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.4.gem b/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.4.gem new file mode 100644 index 0000000000..a71999db16 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.7.2.gem b/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.7.2.gem new file mode 100644 index 0000000000..91b485bcc2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activemodel-6.1.7.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.4.gem b/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.4.gem new file mode 100644 index 0000000000..d545f74df3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.7.2.gem b/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.7.2.gem new file mode 100644 index 0000000000..8e334b7b49 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activerecord-6.1.7.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.4.gem b/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.4.gem new file mode 100644 index 0000000000..b8631a7b44 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.7.2.gem b/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.7.2.gem new file mode 100644 index 0000000000..a46497612d Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/activesupport-6.1.7.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/bundler-2.2.23.gem b/vendor/bundle/ruby/3.0.0/cache/bundler-2.2.23.gem new file mode 100644 index 0000000000..07d7e30526 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/bundler-2.2.23.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/coderay-1.1.3.gem b/vendor/bundle/ruby/3.0.0/cache/coderay-1.1.3.gem new file mode 100644 index 0000000000..3475820d26 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/coderay-1.1.3.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.1.9.gem b/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.1.9.gem new file mode 100644 index 0000000000..9ed64f26c7 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.1.9.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.2.2.gem b/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.2.2.gem new file mode 100644 index 0000000000..8caccaff92 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/concurrent-ruby-1.2.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.0.gem b/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.0.gem new file mode 100644 index 0000000000..398ab619f2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.1.gem b/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.1.gem new file mode 100644 index 0000000000..b705e4dbbe Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/daemons-1.4.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/database_cleaner-2.0.1.gem b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-2.0.1.gem new file mode 100644 index 0000000000..42c06671df Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-2.0.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/database_cleaner-active_record-2.0.1.gem b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-active_record-2.0.1.gem new file mode 100644 index 0000000000..c1ed4b4026 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-active_record-2.0.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/database_cleaner-core-2.0.1.gem b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-core-2.0.1.gem new file mode 100644 index 0000000000..d49376e02b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/database_cleaner-core-2.0.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.4.4.gem b/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.4.4.gem new file mode 100644 index 0000000000..3be4edc12b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.4.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.5.0.gem b/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.5.0.gem new file mode 100644 index 0000000000..3a2585203b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/diff-lcs-1.5.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/eventmachine-1.2.7.gem b/vendor/bundle/ruby/3.0.0/cache/eventmachine-1.2.7.gem new file mode 100644 index 0000000000..708d366b8f Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/eventmachine-1.2.7.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.3.gem b/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.3.gem new file mode 100644 index 0000000000..78aec617cd Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.3.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.5.gem b/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.5.gem new file mode 100644 index 0000000000..a632047fd7 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/ffi-1.15.5.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/i18n-1.12.0.gem b/vendor/bundle/ruby/3.0.0/cache/i18n-1.12.0.gem new file mode 100644 index 0000000000..c64c068e63 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/i18n-1.12.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/i18n-1.8.10.gem b/vendor/bundle/ruby/3.0.0/cache/i18n-1.8.10.gem new file mode 100644 index 0000000000..7dd1339024 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/i18n-1.8.10.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/listen-3.5.1.gem b/vendor/bundle/ruby/3.0.0/cache/listen-3.5.1.gem new file mode 100644 index 0000000000..66dd605a28 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/listen-3.5.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/listen-3.8.0.gem b/vendor/bundle/ruby/3.0.0/cache/listen-3.8.0.gem new file mode 100644 index 0000000000..8837e95c49 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/listen-3.8.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/method_source-1.0.0.gem b/vendor/bundle/ruby/3.0.0/cache/method_source-1.0.0.gem new file mode 100644 index 0000000000..2e035c34ea Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/method_source-1.0.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/minitest-5.14.4.gem b/vendor/bundle/ruby/3.0.0/cache/minitest-5.14.4.gem new file mode 100644 index 0000000000..6a73694804 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/minitest-5.14.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/minitest-5.18.0.gem b/vendor/bundle/ruby/3.0.0/cache/minitest-5.18.0.gem new file mode 100644 index 0000000000..bd1720eec7 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/minitest-5.18.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/mustermann-1.1.1.gem b/vendor/bundle/ruby/3.0.0/cache/mustermann-1.1.1.gem new file mode 100644 index 0000000000..55018660d6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/mustermann-1.1.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/mustermann-2.0.2.gem b/vendor/bundle/ruby/3.0.0/cache/mustermann-2.0.2.gem new file mode 100644 index 0000000000..ff06f2a8d0 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/mustermann-2.0.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/pry-0.14.1.gem b/vendor/bundle/ruby/3.0.0/cache/pry-0.14.1.gem new file mode 100644 index 0000000000..46b1b669f3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/pry-0.14.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/pry-0.14.2.gem b/vendor/bundle/ruby/3.0.0/cache/pry-0.14.2.gem new file mode 100644 index 0000000000..7f06f5ffc7 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/pry-0.14.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-2.2.3.gem b/vendor/bundle/ruby/3.0.0/cache/rack-2.2.3.gem new file mode 100644 index 0000000000..19fa2e9ffc Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-2.2.3.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-2.2.6.3.gem b/vendor/bundle/ruby/3.0.0/cache/rack-2.2.6.3.gem new file mode 100644 index 0000000000..8d7ff884e6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-2.2.6.3.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-contrib-2.3.0.gem b/vendor/bundle/ruby/3.0.0/cache/rack-contrib-2.3.0.gem new file mode 100644 index 0000000000..a52e92b0c1 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-contrib-2.3.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-cors-1.1.1.gem b/vendor/bundle/ruby/3.0.0/cache/rack-cors-1.1.1.gem new file mode 100644 index 0000000000..d0b77079e4 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-cors-1.1.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.1.0.gem b/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.1.0.gem new file mode 100644 index 0000000000..2a4296e3a9 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.1.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.2.4.gem b/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.2.4.gem new file mode 100644 index 0000000000..0ebd74f534 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-protection-2.2.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rack-test-1.1.0.gem b/vendor/bundle/ruby/3.0.0/cache/rack-test-1.1.0.gem new file mode 100644 index 0000000000..3fb2d3a8f1 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rack-test-1.1.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rake-13.0.6.gem b/vendor/bundle/ruby/3.0.0/cache/rake-13.0.6.gem new file mode 100644 index 0000000000..19ae802836 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rake-13.0.6.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.0.gem b/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.0.gem new file mode 100644 index 0000000000..5369a7e3e6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.2.gem b/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.2.gem new file mode 100644 index 0000000000..e645266335 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rb-fsevent-0.11.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rb-inotify-0.10.1.gem b/vendor/bundle/ruby/3.0.0/cache/rb-inotify-0.10.1.gem new file mode 100644 index 0000000000..276590b7e9 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rb-inotify-0.10.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/require_all-3.0.0.gem b/vendor/bundle/ruby/3.0.0/cache/require_all-3.0.0.gem new file mode 100644 index 0000000000..53a2731b47 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/require_all-3.0.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rerun-0.13.1.gem b/vendor/bundle/ruby/3.0.0/cache/rerun-0.13.1.gem new file mode 100644 index 0000000000..26a0e3ca7c Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rerun-0.13.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rerun-0.14.0.gem b/vendor/bundle/ruby/3.0.0/cache/rerun-0.14.0.gem new file mode 100644 index 0000000000..55d756db55 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rerun-0.14.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-3.10.0.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-3.10.0.gem new file mode 100644 index 0000000000..8932c9c4b4 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-3.10.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-3.12.0.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-3.12.0.gem new file mode 100644 index 0000000000..a28e4c33bf Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-3.12.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.10.1.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.10.1.gem new file mode 100644 index 0000000000..7c8a2058fc Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.10.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.12.1.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.12.1.gem new file mode 100644 index 0000000000..b566a2917d Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-core-3.12.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.10.1.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.10.1.gem new file mode 100644 index 0000000000..de1935a9ce Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.10.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.12.2.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.12.2.gem new file mode 100644 index 0000000000..f1ce31871e Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-expectations-3.12.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-json_expectations-2.2.0.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-json_expectations-2.2.0.gem new file mode 100644 index 0000000000..fc4d62023b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-json_expectations-2.2.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.10.2.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.10.2.gem new file mode 100644 index 0000000000..b4aa313db2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.10.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.12.3.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.12.3.gem new file mode 100644 index 0000000000..9fd1a2378c Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-mocks-3.12.3.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.10.2.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.10.2.gem new file mode 100644 index 0000000000..8457ee70d6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.10.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.12.0.gem b/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.12.0.gem new file mode 100644 index 0000000000..1902af69d2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/rspec-support-3.12.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.4.gem b/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.4.gem new file mode 100644 index 0000000000..8f14b3368c Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.5.gem b/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.5.gem new file mode 100644 index 0000000000..d311c5d0f9 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/ruby2_keywords-0.0.5.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sinatra-2.1.0.gem b/vendor/bundle/ruby/3.0.0/cache/sinatra-2.1.0.gem new file mode 100644 index 0000000000..6be5a16e5b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sinatra-2.1.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sinatra-2.2.4.gem b/vendor/bundle/ruby/3.0.0/cache/sinatra-2.2.4.gem new file mode 100644 index 0000000000..4e604efea9 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sinatra-2.2.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.23.gem b/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.23.gem new file mode 100644 index 0000000000..4bde48034f Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.23.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.26.gem b/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.26.gem new file mode 100644 index 0000000000..f0733f0694 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sinatra-activerecord-2.0.26.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.4.2.gem b/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.4.2.gem new file mode 100644 index 0000000000..e5df998d5f Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.4.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.6.1-x86_64-linux.gem b/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.6.1-x86_64-linux.gem new file mode 100644 index 0000000000..a18cc2c3b3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/sqlite3-1.6.1-x86_64-linux.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/thin-1.8.1.gem b/vendor/bundle/ruby/3.0.0/cache/thin-1.8.1.gem new file mode 100644 index 0000000000..54450bcb35 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/thin-1.8.1.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/tilt-2.0.10.gem b/vendor/bundle/ruby/3.0.0/cache/tilt-2.0.10.gem new file mode 100644 index 0000000000..b432e1590e Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/tilt-2.0.10.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/tilt-2.1.0.gem b/vendor/bundle/ruby/3.0.0/cache/tilt-2.1.0.gem new file mode 100644 index 0000000000..bf209fa800 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/tilt-2.1.0.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.4.gem b/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.4.gem new file mode 100644 index 0000000000..a3f982055a Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.4.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.6.gem b/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.6.gem new file mode 100644 index 0000000000..2c16da8abd Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/tzinfo-2.0.6.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.4.2.gem b/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.4.2.gem new file mode 100644 index 0000000000..8054e9466d Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.4.2.gem differ diff --git a/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.6.7.gem b/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.6.7.gem new file mode 100644 index 0000000000..611bd261f4 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/cache/zeitwerk-2.6.7.gem differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/fastfilereaderext.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/fastfilereaderext.so new file mode 100755 index 0000000000..cb20df8798 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/fastfilereaderext.so differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/gem.build_complete b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/gem.build_complete new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/gem_make.out b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/gem_make.out new file mode 100644 index 0000000000..5ce0ecebb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/gem_make.out @@ -0,0 +1,16 @@ +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/fastfilereader +/usr/bin/ruby3.0 -I /usr/lib/ruby/vendor_ruby -r ./siteconf20230605-4951-qx3mzx.rb extconf.rb +creating Makefile + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/fastfilereader +make DESTDIR\= clean + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/fastfilereader +make DESTDIR\= +compiling mapper.cpp +compiling rubymain.cpp +linking shared-object fastfilereaderext.so + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/fastfilereader +make DESTDIR\= install +/usr/bin/install -c -m 0755 fastfilereaderext.so ./.gem.20230605-4951-7a6oy8 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/mkmf.log b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/mkmf.log new file mode 100644 index 0000000000..bec6d90384 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/mkmf.log @@ -0,0 +1,647 @@ +"pkg-config --exists openssl" +| pkg-config --libs openssl +=> "-lssl -lcrypto\n" +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lssl -lcrypto -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +| pkg-config --cflags-only-I openssl +=> "\n" +| pkg-config --cflags-only-other openssl +=> "\n" +| pkg-config --libs-only-l openssl +=> "-lssl -lcrypto\n" +package configuration for openssl +incflags: +cflags: +ldflags: +libs: -lssl -lcrypto + +have_library: checking for -lcrypto... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lruby-3.0 -lssl -lcrypto -lcrypto -lm -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) { ; return 0; } +/* end */ + +-------------------- + +have_library: checking for -lssl... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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) { ; return 0; } +/* end */ + +-------------------- + +have_header: checking for openssl/ssl.h... -------------------- yes + +"x86_64-linux-gnu-gcc -E -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for openssl/err.h... -------------------- yes + +"x86_64-linux-gnu-gcc -E -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_var: checking for rb_trap_immediate in ruby.h,rubysig.h... -------------------- no + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -c conftest.c" +conftest.c:4:10: fatal error: rubysig.h: No such file or directory + 4 | #include + | ^~~~~~~~~~~ +compilation terminated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: #include + 5: + 6: /*top*/ + 7: extern int t(void); + 8: int main(int argc, char **argv) + 9: { +10: if (argc > 1000000) { +11: int (* volatile tp)(void)=(int (*)(void))&t; +12: printf("%d", (*tp)()); +13: } +14: +15: return !!argv[argc]; +16: } +17: int t(void) { const volatile void *volatile p; p = &(&rb_trap_immediate)[0]; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_thread_blocking_region()... -------------------- no + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_thread_blocking_region’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_thread_blocking_region; 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_thread_blocking_region; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -lc" +/bin/ld: /tmp/cc4DR5Vb.o: in function `t': +/home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/conftest.c:15: undefined reference to `rb_thread_blocking_region' +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_thread_blocking_region(); +15: int t(void) { rb_thread_blocking_region(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_thread_call_without_gvl() in ruby/thread.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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_thread_call_without_gvl; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_thread_fd_select()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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_thread_fd_select; return !p; } +/* end */ + +-------------------- + +have_type: checking for rb_fdset_t in ruby/intern.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef rb_fdset_t conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + +have_func: checking for rb_wait_for_single_fd()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_wait_for_single_fd’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_wait_for_single_fd; 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_wait_for_single_fd; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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_wait_for_single_fd(); +15: int t(void) { rb_wait_for_single_fd(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_enable_interrupt()... -------------------- no + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_enable_interrupt’ undeclared (first use in this function); did you mean ‘rb_eInterrupt’? + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_enable_interrupt; return !p; } + | ^~~~~~~~~~~~~~~~~~~ + | rb_eInterrupt +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_enable_interrupt; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -lc" +/bin/ld: /tmp/ccAQXkih.o: in function `t': +/home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/eventmachine-1.2.7/ext/conftest.c:15: undefined reference to `rb_enable_interrupt' +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_enable_interrupt(); +15: int t(void) { rb_enable_interrupt(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_time_new()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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_time_new; return !p; } +/* end */ + +-------------------- + +have_func: checking for inotify_init() in sys/inotify.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))inotify_init; return !p; } +/* end */ + +-------------------- + +have_func: checking for writev() in sys/uio.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))writev; return !p; } +/* end */ + +-------------------- + +have_func: checking for pipe2() in unistd.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))pipe2; return !p; } +/* end */ + +-------------------- + +have_func: checking for accept4() in sys/socket.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))accept4; return !p; } +/* end */ + +-------------------- + +have_const: checking for SOCK_CLOEXEC in sys/socket.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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)SOCK_CLOEXEC; +/* end */ + +-------------------- + +have_header: checking for sys/event.h... -------------------- no + +"x86_64-linux-gnu-gcc -E -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC conftest.c -o conftest.i" +conftest.c:3:10: fatal error: sys/event.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 epoll_create() in sys/epoll.h... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))epoll_create; return !p; } +/* end */ + +-------------------- + +have_func: checking for clock_gettime()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lssl -lcrypto -lcrypto -lssl -lm -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 ((*)()))clock_gettime; return !p; } +/* end */ + +-------------------- + +have_const: checking for CLOCK_MONOTONIC_RAW in time.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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)CLOCK_MONOTONIC_RAW; +/* end */ + +-------------------- + +have_const: checking for CLOCK_MONOTONIC in time.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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)CLOCK_MONOTONIC; +/* end */ + +-------------------- + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wall -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wextra -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wno-deprecated-declarations -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wno-ignored-qualifiers -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wno-unused-result -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -Wno-address -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"x86_64-linux-gnu-g++ -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lssl -lcrypto -lcrypto -lssl -lruby-3.0 -lstdc++ -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: using namespace std; +5: int main(){ pair tuple = make_pair(1,2); } +/* end */ + diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/rubyeventmachine.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/rubyeventmachine.so new file mode 100755 index 0000000000..7da1f1fde3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/eventmachine-1.2.7/rubyeventmachine.so differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/ffi_c.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/ffi_c.so new file mode 100755 index 0000000000..30dd55cdff Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/ffi_c.so differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem.build_complete b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem.build_complete new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem_make.out b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem_make.out new file mode 100644 index 0000000000..be990cfe08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/gem_make.out @@ -0,0 +1,43 @@ +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.3/ext/ffi_c +/usr/bin/ruby3.0 -I /usr/lib/ruby/vendor_ruby -r ./siteconf20230605-4951-k7m927.rb extconf.rb +checking for ffi_prep_closure_loc() in -lffi... yes +checking for ffi_prep_cif_var()... yes +checking for ffi_raw_call()... yes +checking for ffi_prep_raw_closure()... yes +checking for whether -pthread is accepted as LDFLAGS... yes +creating extconf.h +creating Makefile + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.3/ext/ffi_c +make DESTDIR\= clean + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.3/ext/ffi_c +make DESTDIR\= +compiling AbstractMemory.c +compiling ArrayType.c +compiling Buffer.c +compiling Call.c +compiling ClosurePool.c +compiling DynamicLibrary.c +compiling Function.c +compiling FunctionInfo.c +compiling LastError.c +compiling LongDouble.c +compiling MappedType.c +compiling MemoryPointer.c +compiling MethodHandle.c +compiling Platform.c +compiling Pointer.c +compiling Struct.c +compiling StructByValue.c +compiling StructLayout.c +compiling Thread.c +compiling Type.c +compiling Types.c +compiling Variadic.c +compiling ffi.c +linking shared-object ffi_c.so + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.3/ext/ffi_c +make DESTDIR\= install +/usr/bin/install -c -m 0755 ffi_c.so ./.gem.20230605-4951-pxc8lx diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/mkmf.log b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/mkmf.log new file mode 100644 index 0000000000..a1c2096440 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.3/mkmf.log @@ -0,0 +1,232 @@ +"pkg-config --exists libffi" +| pkg-config --libs libffi +=> "-lffi\n" +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lffi -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +| pkg-config --cflags-only-I libffi +=> "\n" +| pkg-config --cflags-only-other libffi +=> "\n" +| pkg-config --libs-only-l libffi +=> "-lffi\n" +package configuration for libffi +incflags: +cflags: +ldflags: +libs: -lffi + +have_library: checking for ffi_prep_closure_loc() in -lffi... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lruby-3.0 -lffi -lffi -lm -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 ((*)()))ffi_prep_closure_loc; return !p; } +/* end */ + +-------------------- + +have_func: checking for ffi_prep_cif_var()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_prep_cif_var’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_cif_var; 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 ((*)()))ffi_prep_cif_var; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_prep_cif_var(); +15: int t(void) { ffi_prep_cif_var(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ffi_raw_call()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_raw_call’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_raw_call; 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 ((*)()))ffi_raw_call; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_raw_call(); +15: int t(void) { ffi_raw_call(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ffi_prep_raw_closure()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_prep_raw_closure’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_raw_closure; 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 ((*)()))ffi_prep_raw_closure; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_prep_raw_closure(); +15: int t(void) { ffi_prep_raw_closure(); return 0; } +/* end */ + +-------------------- + +block in append_ldflags: checking for whether -pthread is accepted as LDFLAGS... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -pthread -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +-------------------- + +extconf.h is: +/* begin */ +1: #ifndef EXTCONF_H +2: #define EXTCONF_H +3: #define HAVE_FFI_PREP_CIF_VAR 1 +4: #define HAVE_FFI_RAW_CALL 1 +5: #define HAVE_FFI_PREP_RAW_CLOSURE 1 +6: #define HAVE_RAW_API 1 +7: #endif +/* end */ + diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/ffi_c.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/ffi_c.so new file mode 100755 index 0000000000..14c56fd360 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/ffi_c.so differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/gem.build_complete b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/gem.build_complete new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/gem_make.out b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/gem_make.out new file mode 100644 index 0000000000..0b59407eb2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/gem_make.out @@ -0,0 +1,43 @@ +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.5/ext/ffi_c +/usr/bin/ruby3.0 -I /usr/lib/ruby/vendor_ruby -r ./siteconf20230605-31593-oj1tgf.rb extconf.rb +checking for ffi_prep_closure_loc() in -lffi... yes +checking for ffi_prep_cif_var()... yes +checking for ffi_raw_call()... yes +checking for ffi_prep_raw_closure()... yes +checking for whether -pthread is accepted as LDFLAGS... yes +creating extconf.h +creating Makefile + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.5/ext/ffi_c +make DESTDIR\= clean + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.5/ext/ffi_c +make DESTDIR\= +compiling AbstractMemory.c +compiling ArrayType.c +compiling Buffer.c +compiling Call.c +compiling ClosurePool.c +compiling DynamicLibrary.c +compiling Function.c +compiling FunctionInfo.c +compiling LastError.c +compiling LongDouble.c +compiling MappedType.c +compiling MemoryPointer.c +compiling MethodHandle.c +compiling Platform.c +compiling Pointer.c +compiling Struct.c +compiling StructByValue.c +compiling StructLayout.c +compiling Thread.c +compiling Type.c +compiling Types.c +compiling Variadic.c +compiling ffi.c +linking shared-object ffi_c.so + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.5/ext/ffi_c +make DESTDIR\= install +/usr/bin/install -c -m 0755 ffi_c.so ./.gem.20230605-31593-n11q4f diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/mkmf.log b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/mkmf.log new file mode 100644 index 0000000000..a1c2096440 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/ffi-1.15.5/mkmf.log @@ -0,0 +1,232 @@ +"pkg-config --exists libffi" +| pkg-config --libs libffi +=> "-lffi\n" +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lffi -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +| pkg-config --cflags-only-I libffi +=> "\n" +| pkg-config --cflags-only-other libffi +=> "\n" +| pkg-config --libs-only-l libffi +=> "-lffi\n" +package configuration for libffi +incflags: +cflags: +ldflags: +libs: -lffi + +have_library: checking for ffi_prep_closure_loc() in -lffi... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lruby-3.0 -lffi -lffi -lm -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 ((*)()))ffi_prep_closure_loc; return !p; } +/* end */ + +-------------------- + +have_func: checking for ffi_prep_cif_var()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_prep_cif_var’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_cif_var; 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 ((*)()))ffi_prep_cif_var; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_prep_cif_var(); +15: int t(void) { ffi_prep_cif_var(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ffi_raw_call()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_raw_call’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_raw_call; 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 ((*)()))ffi_raw_call; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_raw_call(); +15: int t(void) { ffi_raw_call(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ffi_prep_raw_closure()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘ffi_prep_raw_closure’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_raw_closure; 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 ((*)()))ffi_prep_raw_closure; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -lffi -lffi -lm -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 ffi_prep_raw_closure(); +15: int t(void) { ffi_prep_raw_closure(); return 0; } +/* end */ + +-------------------- + +block in append_ldflags: checking for whether -pthread is accepted as LDFLAGS... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lffi -lffi -lruby-3.0 -pthread -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +-------------------- + +extconf.h is: +/* begin */ +1: #ifndef EXTCONF_H +2: #define EXTCONF_H +3: #define HAVE_FFI_PREP_CIF_VAR 1 +4: #define HAVE_FFI_RAW_CALL 1 +5: #define HAVE_FFI_PREP_RAW_CLOSURE 1 +6: #define HAVE_RAW_API 1 +7: #endif +/* end */ + diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/gem.build_complete b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/gem.build_complete new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/gem_make.out b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/gem_make.out new file mode 100644 index 0000000000..fad6abfbdd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/gem_make.out @@ -0,0 +1,76 @@ +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3 +/usr/bin/ruby3.0 -I /usr/lib/ruby/vendor_ruby -r ./siteconf20230605-4951-w9ov0.rb extconf.rb +checking for sqlite3.h... yes +checking for pthread_create() in -lpthread... yes +checking for -ldl... yes +checking for sqlite3_libversion_number() in -lsqlite3... yes +checking for rb_proc_arity()... yes +checking for rb_integer_pack()... yes +checking for sqlite3_initialize()... yes +checking for sqlite3_backup_init()... yes +checking for sqlite3_column_database_name()... yes +checking for sqlite3_enable_load_extension()... yes +checking for sqlite3_load_extension()... yes +checking for sqlite3_open_v2()... yes +checking for sqlite3_prepare_v2()... yes +checking for sqlite3_int64 in sqlite3.h... yes +checking for sqlite3_uint64 in sqlite3.h... yes +creating Makefile + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3 +make DESTDIR\= clean + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3 +make DESTDIR\= +compiling aggregator.c +compiling backup.c +compiling database.c +database.c: In function ‘exec_batch’: +database.c:726:57: warning: passing argument 3 of ‘sqlite3_exec’ from incompatible pointer type [-Wincompatible-pointer-types] + 726 | status = sqlite3_exec(ctx->db, StringValuePtr(sql), hash_callback_function, callback_ary, &errMsg); + | ^~~~~~~~~~~~~~~~~~~~~~ + | | + | int (*)(VALUE, int, char **, char **) {aka int (*)(long unsigned int, int, char **, char **)} +In file included from ./sqlite3_ruby.h:25, + from database.c:1: +/usr/include/sqlite3.h:428:9: note: expected ‘int (*)(void *, int, char **, char **)’ but argument is of type ‘int (*)(VALUE, int, char **, char **)’ {aka ‘int (*)(long unsigned int, int, char **, char **)’} + 428 | int (*callback)(void*,int,char**,char**), /* Callback function */ + | ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +database.c:726:81: warning: passing argument 4 of ‘sqlite3_exec’ makes pointer from integer without a cast [-Wint-conversion] + 726 | status = sqlite3_exec(ctx->db, StringValuePtr(sql), hash_callback_function, callback_ary, &errMsg); + | ^~~~~~~~~~~~ + | | + | VALUE {aka long unsigned int} +In file included from ./sqlite3_ruby.h:25, + from database.c:1: +/usr/include/sqlite3.h:429:3: note: expected ‘void *’ but argument is of type ‘VALUE’ {aka ‘long unsigned int’} + 429 | void *, /* 1st argument to callback */ + | ^~~~~~ +database.c:728:57: warning: passing argument 3 of ‘sqlite3_exec’ from incompatible pointer type [-Wincompatible-pointer-types] + 728 | status = sqlite3_exec(ctx->db, StringValuePtr(sql), regular_callback_function, callback_ary, &errMsg); + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + | | + | int (*)(VALUE, int, char **, char **) {aka int (*)(long unsigned int, int, char **, char **)} +In file included from ./sqlite3_ruby.h:25, + from database.c:1: +/usr/include/sqlite3.h:428:9: note: expected ‘int (*)(void *, int, char **, char **)’ but argument is of type ‘int (*)(VALUE, int, char **, char **)’ {aka ‘int (*)(long unsigned int, int, char **, char **)’} + 428 | int (*callback)(void*,int,char**,char**), /* Callback function */ + | ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +database.c:728:84: warning: passing argument 4 of ‘sqlite3_exec’ makes pointer from integer without a cast [-Wint-conversion] + 728 | status = sqlite3_exec(ctx->db, StringValuePtr(sql), regular_callback_function, callback_ary, &errMsg); + | ^~~~~~~~~~~~ + | | + | VALUE {aka long unsigned int} +In file included from ./sqlite3_ruby.h:25, + from database.c:1: +/usr/include/sqlite3.h:429:3: note: expected ‘void *’ but argument is of type ‘VALUE’ {aka ‘long unsigned int’} + 429 | void *, /* 1st argument to callback */ + | ^~~~~~ +compiling exception.c +compiling sqlite3.c +compiling statement.c +linking shared-object sqlite3/sqlite3_native.so + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3 +make DESTDIR\= install +/usr/bin/install -c -m 0755 sqlite3_native.so ./.gem.20230605-4951-mj6ow/sqlite3 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/mkmf.log b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/mkmf.log new file mode 100644 index 0000000000..ec59350c97 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/mkmf.log @@ -0,0 +1,584 @@ +"pkg-config --exists sqlite3" +| pkg-config --libs sqlite3 +=> "-lsqlite3\n" +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lsqlite3 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +| pkg-config --cflags-only-I sqlite3 +=> "\n" +| pkg-config --cflags-only-other sqlite3 +=> "\n" +| pkg-config --libs-only-l sqlite3 +=> "-lsqlite3\n" +package configuration for sqlite3 +incflags: +cflags: +ldflags: +libs: -lsqlite3 + +find_header: checking for sqlite3.h... -------------------- yes + +"x86_64-linux-gnu-gcc -E -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +find_library: checking for pthread_create() in -lpthread... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -lruby-3.0 -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘pthread_create’ undeclared (first use in this function); did you mean ‘rb_thread_create’? + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))pthread_create; return !p; } + | ^~~~~~~~~~~~~~ + | rb_thread_create +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 ((*)()))pthread_create; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -lruby-3.0 -lpthread -lsqlite3 -lm -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 pthread_create(); +15: int t(void) { pthread_create(); return 0; } +/* end */ + +-------------------- + +have_library: checking for -ldl... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lpthread -lsqlite3 -lruby-3.0 -ldl -lpthread -lsqlite3 -lm -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) { ; return 0; } +/* end */ + +-------------------- + +find_library: checking for sqlite3_libversion_number() in -lsqlite3... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_libversion_number’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_libversion_number; 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 ((*)()))sqlite3_libversion_number; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_libversion_number(); +15: int t(void) { sqlite3_libversion_number(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_proc_arity()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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_proc_arity; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_integer_pack()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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_integer_pack; return !p; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_initialize()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_initialize’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_initialize; 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 ((*)()))sqlite3_initialize; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_initialize(); +15: int t(void) { sqlite3_initialize(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_backup_init()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_backup_init’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_backup_init; 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 ((*)()))sqlite3_backup_init; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_backup_init(); +15: int t(void) { sqlite3_backup_init(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_column_database_name()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_column_database_name’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_column_database_name; 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 ((*)()))sqlite3_column_database_name; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_column_database_name(); +15: int t(void) { sqlite3_column_database_name(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_enable_load_extension()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_enable_load_extension’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_enable_load_extension; 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 ((*)()))sqlite3_enable_load_extension; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_enable_load_extension(); +15: int t(void) { sqlite3_enable_load_extension(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_load_extension()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_load_extension’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_load_extension; 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 ((*)()))sqlite3_load_extension; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_load_extension(); +15: int t(void) { sqlite3_load_extension(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_open_v2()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_open_v2’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_open_v2; 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 ((*)()))sqlite3_open_v2; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_open_v2(); +15: int t(void) { sqlite3_open_v2(); return 0; } +/* end */ + +-------------------- + +have_func: checking for sqlite3_prepare_v2()... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘sqlite3_prepare_v2’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))sqlite3_prepare_v2; 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 ((*)()))sqlite3_prepare_v2; return !p; } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lsqlite3 -ldl -lpthread -lsqlite3 -lruby-3.0 -lsqlite3 -ldl -lpthread -lsqlite3 -lm -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 sqlite3_prepare_v2(); +15: int t(void) { sqlite3_prepare_v2(); return 0; } +/* end */ + +-------------------- + +have_type: checking for sqlite3_int64 in sqlite3.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef sqlite3_int64 conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + +have_type: checking for sqlite3_uint64 in sqlite3.h... -------------------- yes + +"x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef sqlite3_uint64 conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/sqlite3/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/sqlite3/sqlite3_native.so new file mode 100755 index 0000000000..569fb67ef2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/sqlite3-1.4.2/sqlite3/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/gem.build_complete b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/gem.build_complete new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/gem_make.out b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/gem_make.out new file mode 100644 index 0000000000..8eecbac763 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/gem_make.out @@ -0,0 +1,17 @@ +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser +/usr/bin/ruby3.0 -I /usr/lib/ruby/vendor_ruby -r ./siteconf20230605-4951-x3xaid.rb extconf.rb +checking for main() in -lc... yes +creating Makefile + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser +make DESTDIR\= clean + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser +make DESTDIR\= +compiling parser.c +compiling thin.c +linking shared-object thin_parser.so + +current directory: /home/chweya/phase-3-sinatra-react-project/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser +make DESTDIR\= install +/usr/bin/install -c -m 0755 thin_parser.so ./.gem.20230605-4951-y08t1w diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/mkmf.log b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/mkmf.log new file mode 100644 index 0000000000..199c7eae79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/mkmf.log @@ -0,0 +1,34 @@ +have_library: checking for main() in -lc... -------------------- yes + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lm -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +"x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.0.0 -I/usr/include/ruby-3.0.0/ruby/backward -I/usr/include/ruby-3.0.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2 -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -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 -lruby-3.0 -lc -lm -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 ((*)()))main; return !p; } +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/thin_parser.so b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/thin_parser.so new file mode 100755 index 0000000000..74ca3142ee Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/extensions/x86_64-linux/3.0.0/thin-1.8.1/thin_parser.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/CHANGELOG.md new file mode 100644 index 0000000000..2a64a91cf3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/CHANGELOG.md @@ -0,0 +1,112 @@ +## Rails 6.1.4 (June 24, 2021) ## + +* Fix `to_json` for `ActiveModel::Dirty` object. + + Exclude +mutations_from_database+ attribute from json as it lead to recursion. + + *Anil Maurya* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* No changes. + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* No changes. + + +## Rails 6.1.2 (February 09, 2021) ## + +* No changes. + + +## Rails 6.1.1 (January 07, 2021) ## + +* No changes. + + +## Rails 6.1.0 (December 09, 2020) ## + +* Pass in `base` instead of `base_class` to Error.human_attribute_name + + This is useful in cases where the `human_attribute_name` method depends + on other attributes' values of the class under validation to derive what the + attribute name should be. + + *Filipe Sabella* + +* Deprecate marshalling load from legacy attributes format. + + *Ryuta Kamizono* + +* `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`. + + topic.update!(status: :archived) + topic.status_previously_changed?(from: "active", to: "archived") + # => true + + *George Claghorn* + +* Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen: + + class Animal + include ActiveModel::Attributes + attribute :age + end + + animal = Animal.new + animal.freeze + animal.age = 25 # => FrozenError, "can't modify a frozen Animal" + + *Josh Brody* + +* Add `*_previously_was` attribute methods when dirty tracking. Example: + + pirate.update(catchphrase: "Ahoy!") + pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"] + pirate.catchphrase_previously_was # => "Thar She Blows!" + + *DHH* + +* Encapsulate each validation error as an Error object. + + The `ActiveModel`’s `errors` collection is now an array of these Error + objects, instead of messages/details hash. + + For each of these `Error` object, its `message` and `full_message` methods + are for generating error messages. Its `details` method would return error’s + extra parameters, found in the original `details` hash. + + The change tries its best at maintaining backward compatibility, however + some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating + `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward, + please convert those direct manipulations to use provided API methods instead. + + The list of deprecated methods and their planned future behavioral changes at the next major release are: + + * `errors#slice!` will be removed. + * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object. + * `errors#values` will be removed. + * `errors#keys` will be removed. + * `errors#to_xml` will be removed. + * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`. + * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`). + * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect. + * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect. + + *lulalala* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/MIT-LICENSE new file mode 100644 index 0000000000..0e2be7cd7f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2020 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.0.0/gems/activemodel-6.1.4/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/README.rdoc new file mode 100644 index 0000000000..f68c5bfcd0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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://edgeguides.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::Model. + + class Person + include ActiveModel::Model + + 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::Model 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}[link: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}[link: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}[link: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}[link: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}[link: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}[link:classes/ActiveModel/Serialization.html] + +* Internationalization (i18n) support + + class Person + extend ActiveModel::Translation + end + + Person.human_attribute_name('my_attribute') + # => "My attribute" + + {Learn more}[link: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}[link: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}[link: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.0.0/gems/activemodel-6.1.4/lib/active_model.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model.rb new file mode 100644 index 0000000000..2338bf168c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2020 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" + +module ActiveModel + extend ActiveSupport::Autoload + + autoload :Attribute + autoload :Attributes + autoload :AttributeAssignment + autoload :AttributeMethods + 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" + 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.0.0/gems/activemodel-6.1.4/lib/active_model/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute.rb new file mode 100644 index 0000000000..5bb5e747ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute.rb @@ -0,0 +1,248 @@ +# 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 + type.serialize(value) + 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 defined?(@value) && @value.duplicable? + @value = @value.dup + end + end + + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) + end + + def _original_value_for_database + type.serialize(original_value) + end + + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + 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 + 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.0.0/gems/activemodel-6.1.4/lib/active_model/attribute/user_provided_default.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute/user_provided_default.rb new file mode 100644 index 0000000000..9dc16e882d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute/user_provided_default.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class Attribute # :nodoc: + 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.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb new file mode 100644 index 0000000000..ea4dae101c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_assignment.rb @@ -0,0 +1,55 @@ +# 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 + + private + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + setter = :"#{k}=" + if respond_to?(setter) + public_send(setter, v) + else + raise UnknownAttributeError.new(self, k.to_s) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_methods.rb new file mode 100644 index 0000000000..f584ed99a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_methods.rb @@ -0,0 +1,555 @@ +# 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 + 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_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.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) + self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix } + 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) + self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix } + 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_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] } + 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) + self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s) + CodeGenerator.batch(self, __FILE__, __LINE__) do |owner| + attribute_method_matchers.each do |matcher| + matcher_new = matcher.method_name(new_name).to_s + matcher_old = matcher.method_name(old_name).to_s + define_proxy_call false, owner, matcher_new, matcher_old + end + end + 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) + CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| + attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) } + 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) + CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner| + attribute_method_matchers.each do |matcher| + method_name = matcher.method_name(attr_name) + + unless instance_method_already_implemented?(method_name) + generate_method = "define_method_#{matcher.target}" + + if respond_to?(generate_method, true) + send(generate_method, attr_name.to_s, owner: owner) + else + define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s + end + end + end + attribute_method_matchers_cache.clear + end + end + + # Removes all the previously dynamically defined methods from the class. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_method :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name_short? # => true + # + # Person.undefine_attribute_methods + # + # person.name_short? # => NoMethodError + def undefine_attribute_methods + generated_attribute_methods.module_eval do + undef_method(*instance_methods) + end + attribute_method_matchers_cache.clear + end + + private + class CodeGenerator + 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 + @sources = ["# frozen_string_literal: true\n"] + @renames = {} + end + + def <<(source_line) + @sources << source_line + end + + def rename_method(old_name, new_name) + @renames[old_name] = new_name + end + + def execute + @owner.module_eval(@sources.join(";"), @path, @line - 1) + @renames.each do |old_name, new_name| + @owner.alias_method new_name, old_name + @owner.undef_method old_name + end + end + end + private_constant :CodeGenerator + + 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_matchers_cache + @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4) + end + + def attribute_method_matchers_matching(method_name) + attribute_method_matchers_cache.compute_if_absent(method_name) do + attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact + end + end + + # Define a method `name` in `mod` that dispatches to `send` + # using the given `extra` args. This falls back on `define_method` + # and `send` if the given names cannot be compiled. + def define_proxy_call(include_private, code_generator, name, target, *extra) + defn = if NAME_COMPILABLE_REGEXP.match?(name) + "def #{name}(*args)" + else + "define_method(:'#{name}') do |*args|" + end + + extra = (extra.map!(&:inspect) << "*args").join(", ") + + body = if CALL_COMPILABLE_REGEXP.match?(target) + "#{"self." unless include_private}#{target}(#{extra})" + else + "send(:'#{target}', #{extra})" + end + + code_generator << + defn << + body << + "end" << + "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)" + end + + class AttributeMethodMatcher #:nodoc: + attr_reader :prefix, :suffix, :target + + AttributeMethodMatch = Struct.new(:target, :attr_name) + + def initialize(options = {}) + @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "") + @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ + @target = "#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" + end + + def match(method_name) + if @regex =~ method_name + AttributeMethodMatch.new(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, *args, &block) + if respond_to_without_attributes?(method, true) + super + else + match = matched_attribute_method(method.to_s) + match ? attribute_missing(match, *args, &block) : super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + # +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, *args, &block) + __send__(match.target, match.attr_name, *args, &block) + 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_matchers_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}", 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. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # 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 + owner.rename_method(temp_method_name, method_name) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_mutation_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_mutation_tracker.rb new file mode 100644 index 0000000000..e46d2e76da --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_mutation_tracker.rb @@ -0,0 +1,181 @@ +# 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) == from) && + (OPTION_NOT_GIVEN == to || fetch_value(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 + 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 + 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.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set.rb new file mode 100644 index 0000000000..e612916976 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set.rb @@ -0,0 +1,106 @@ +# 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 values_before_type_cast + attributes.transform_values(&:value_before_type_cast) + 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 + + 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) + value + end + + def freeze + attributes.freeze + super + end + + def deep_dup + AttributeSet.new(attributes.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 ==(other) + attributes == other.attributes + end + + protected + attr_reader :attributes + + private + def default_attribute(name) + Attribute.null(name) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set/builder.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set/builder.rb new file mode 100644 index 0000000000..a6559ac02d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set/builder.rb @@ -0,0 +1,191 @@ +# 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) + if values.is_a?(Hash) + ActiveSupport::Deprecation.warn(<<~MSG) + Marshalling load from legacy attributes format is deprecated and will be removed in Rails 6.2. + MSG + empty_hash = {}.freeze + initialize(empty_hash, empty_hash, empty_hash, empty_hash, values) + @materialized = true + else + initialize(*values) + end + 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.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set/yaml_encoder.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attribute_set/yaml_encoder.rb new file mode 100644 index 0000000000..ea1efc160e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attributes.rb new file mode 100644 index 0000000000..de94b1cf3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/attributes.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_model/attribute_set" +require "active_model/attribute/user_provided_default" + +module ActiveModel + module Attributes #:nodoc: + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "=" + class_attribute :attribute_types, :_default_attributes, instance_accessor: false + self.attribute_types = Hash.new(Type.default_value) + self._default_attributes = AttributeSet.new({}) + end + + module ClassMethods + def attribute(name, type = Type::Value.new, **options) + name = name.to_s + if type.is_a?(Symbol) + type = ActiveModel::Type.lookup(type, **options.except(:default)) + end + self.attribute_types = attribute_types.merge(name => type) + define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type) + 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 + + private + def define_method_attribute=(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED + + def define_default_attribute(name, value, type) + self._default_attributes = _default_attributes.deep_dup + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + else + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + end + _default_attributes[name] = default_attribute + end + end + + def initialize(*) + @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(name: 'Francesco', 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 + @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.0.0/gems/activemodel-6.1.4/lib/active_model/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/callbacks.rb new file mode 100644 index 0000000000..dc8f798f6a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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: Calling 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 :initializer, 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.0.0/gems/activemodel-6.1.4/lib/active_model/conversion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/conversion.rb new file mode 100644 index 0000000000..82713ddc81 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/conversion.rb @@ -0,0 +1,111 @@ +# 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 + + # 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 ? [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.join("-") : 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 ||= begin + 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.0.0/gems/activemodel-6.1.4/lib/active_model/dirty.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/dirty.rb new file mode 100644 index 0000000000..118b64c585 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/dirty.rb @@ -0,0 +1,293 @@ +# 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 [attr_name]_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 + # [attribute_name]_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 [attribute_name]_will_change! on Active Record models. + # + # person.name_will_change! + # person.name_change # => ["Bill", "Bill"] + # person.name << 'y' + # person.name_change # => ["Bill", "Billy"] + module Dirty + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "_changed?", "_change", "_will_change!", "_was" + attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was" + attribute_method_affix prefix: "restore_", suffix: "!" + attribute_method_affix prefix: "clear_", suffix: "_change" + end + + def initialize_dup(other) # :nodoc: + super + if self.class.respond_to?(:_default_attributes) + @attributes = self.class._default_attributes.map do |attr| + attr.with_value_from_user(@attributes.fetch_value(attr.name)) + end + end + @mutations_from_database = nil + end + + def as_json(options = {}) # :nodoc: + options[:except] = [options[:except], "mutations_from_database"].flatten + 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? attribute methods. + def attribute_changed?(attr_name, **options) # :nodoc: + mutations_from_database.changed?(attr_name.to_s, **options) + end + + # Dispatch target for *_was attribute methods. + def attribute_was(attr_name) # :nodoc: + mutations_from_database.original_value(attr_name.to_s) + end + + # Dispatch target for *_previously_changed? attribute methods. + def attribute_previously_changed?(attr_name, **options) # :nodoc: + mutations_before_last_save.changed?(attr_name.to_s, **options) + end + + # Dispatch target for *_previously_was attribute methods. + def attribute_previously_was(attr_name) # :nodoc: + 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 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.0.0/gems/activemodel-6.1.4/lib/active_model/error.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/error.rb new file mode 100644 index 0000000000..103d7d6e71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/error.rb @@ -0,0 +1,207 @@ +# 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.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.0.0/gems/activemodel-6.1.4/lib/active_model/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/errors.rb new file mode 100644 index 0000000000..79e048d34a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/errors.rb @@ -0,0 +1,709 @@ +# 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_support/core_ext/string/filters" +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 + def_delegators :@errors, :size, :clear, :blank?, :empty?, :uniq!, :any? + # TODO: forward all enumerable methods after `each` deprecation is removed. + def_delegators :@errors, :count + + LEGACY_ATTRIBUTES = [:messages, :details].freeze + private_constant :LEGACY_ATTRIBUTES + + # 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. + # + # 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+. + # + # override_options - Hash + # @option override_options [Symbol] :attribute Override the attribute the error belongs to + # @option override_options [Symbol] :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. + # + # other - The ActiveModel::Errors instance. + # + # Examples + # + # person.errors.merge!(other) + def merge!(other) + other.errors.each { |error| + import(error) + } + end + + # Removes all errors except the given keys. Returns a hash containing the removed errors. + # + # person.errors.keys # => [:name, :age, :gender, :city] + # person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] } + # person.errors.keys # => [:age, :gender] + def slice!(*keys) + deprecation_removal_warning(:slice!) + + keys = keys.map(&:to_sym) + + results = messages.dup.slice!(*keys) + + @errors.keep_if do |error| + keys.include?(error.attribute) + end + + results + 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) + DeprecationHandlingMessageArray.new(messages_for(attribute), self, attribute) + end + + # 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 + # + # To be backward compatible with past deprecated hash-like behavior, + # when block accepts two parameters instead of one, it + # iterates through each error key, value pair in the error messages hash. + # Yields the attribute and the error for that attribute. If the attribute + # has more than one error message, yields once for each error message. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.each do |attribute, message| + # # Will yield :name and "can't be blank" + # end + # + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.each do |attribute, message| + # # Will yield :name and "can't be blank" + # # then yield :name and "must be specified" + # end + def each(&block) + if block.arity <= 1 + @errors.each(&block) + else + ActiveSupport::Deprecation.warn(<<~MSG) + Enumerating ActiveModel::Errors as a hash has been deprecated. + In Rails 6.1, `errors` is an array of Error objects, + therefore it should be accessed by a block with a single block + parameter like this: + + person.errors.each do |error| + attribute = error.attribute + message = error.message + end + + You are passing a block expecting two parameters, + so the old hash behavior is simulated. As this is deprecated, + this will result in an ArgumentError in Rails 6.2. + MSG + @errors. + sort { |a, b| a.attribute <=> b.attribute }. + each { |error| yield error.attribute, error.message } + end + end + + # Returns all message values. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.values # => [["cannot be nil", "must be specified"]] + def values + deprecation_removal_warning(:values, "errors.map { |error| error.message }") + @errors.map(&:message).freeze + end + + # Returns all message keys. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.keys # => [:name] + def keys + deprecation_removal_warning(:keys, "errors.attribute_names") + keys = @errors.map(&:attribute) + keys.uniq! + keys.freeze + 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 an xml formatted representation of the Errors hash. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.to_xml + # # => + # # + # # + # # name can't be blank + # # name must be specified + # # + def to_xml(options = {}) + deprecation_removal_warning(:to_xml) + to_a.to_xml({ root: "errors", skip_types: true }.merge!(options)) + 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 + + def to_h + ActiveSupport::Deprecation.warn(<<~EOM) + ActiveModel::Errors#to_h is deprecated and will be removed in Rails 6.2. + Please use `ActiveModel::Errors.to_hash` instead. The values in the hash + returned by `ActiveModel::Errors.to_hash` is an array of error messages. + EOM + + to_hash.transform_values { |values| values.last } + end + + # Returns a Hash of attributes with an array of their error messages. + # + # Updating this hash would still update errors state for backward + # compatibility, but this behavior is deprecated. + def messages + DeprecationHandlingMessageHash.new(self) + end + + # Returns a Hash of attributes with an array of their error details. + # + # Updating this hash would still update errors state for backward + # compatibility, but this behavior is deprecated. + def details + hash = group_by_attribute.transform_values do |errors| + errors.map(&:details) + end + DeprecationHandlingDetailsHash.new(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+). + # + # 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 marshal_load(array) # :nodoc: + # Rails 5 + @errors = [] + @base = array[0] + add_from_legacy_details_hash(array[2]) + end + + def init_with(coder) # :nodoc: + data = coder.map + + data.each { |k, v| + next if LEGACY_ATTRIBUTES.include?(k.to_sym) + instance_variable_set(:"@#{k}", v) + } + + @errors ||= [] + + # Legacy support Rails 5.x details hash + add_from_legacy_details_hash(data["details"]) if data.key?("details") + 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 + + def add_from_legacy_details_hash(details) + details.each { |attribute, errors| + errors.each { |error| + type = error.delete(:error) + add(attribute, type, **error) + } + } + end + + def deprecation_removal_warning(method_name, alternative_message = nil) + message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 6.2." + if alternative_message + message << "\n\nTo achieve the same use:\n\n " + message << alternative_message + end + ActiveSupport::Deprecation.warn(message) + end + + def deprecation_rename_warning(old_method_name, new_method_name) + ActiveSupport::Deprecation.warn("ActiveModel::Errors##{old_method_name} is deprecated. Please call ##{new_method_name} instead.") + end + end + + class DeprecationHandlingMessageHash < SimpleDelegator + def initialize(errors) + @errors = errors + super(prepare_content) + end + + def []=(attribute, value) + ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.") + + @errors.delete(attribute) + Array(value).each do |message| + @errors.add(attribute, message) + end + + __setobj__ prepare_content + end + + def delete(attribute) + ActiveSupport::Deprecation.warn("Calling `delete` to an ActiveModel::Errors messages hash is deprecated. Please call `ActiveModel::Errors#delete` instead.") + + @errors.delete(attribute) + end + + private + def prepare_content + content = @errors.to_hash + content.each do |attribute, value| + content[attribute] = DeprecationHandlingMessageArray.new(value, @errors, attribute) + end + content.default_proc = proc do |hash, attribute| + hash = hash.dup + hash[attribute] = DeprecationHandlingMessageArray.new([], @errors, attribute) + __setobj__ hash.freeze + hash[attribute] + end + content.freeze + end + end + + class DeprecationHandlingMessageArray < SimpleDelegator + def initialize(content, errors, attribute) + @errors = errors + @attribute = attribute + super(content.freeze) + end + + def <<(message) + ActiveSupport::Deprecation.warn("Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.") + + @errors.add(@attribute, message) + __setobj__ @errors.messages_for(@attribute) + self + end + + def clear + ActiveSupport::Deprecation.warn("Calling `clear` to an ActiveModel::Errors message array in order to delete all errors is deprecated. Please call `ActiveModel::Errors#delete` instead.") + + @errors.delete(@attribute) + end + end + + class DeprecationHandlingDetailsHash < SimpleDelegator + def initialize(details) + details.default = [] + details.freeze + super(details) + end + end + + # 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 + + # Raised when attribute values are out of range. + class RangeError < ::RangeError + end + + # 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.0.0/gems/activemodel-6.1.4/lib/active_model/forbidden_attributes_protection.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/forbidden_attributes_protection.rb new file mode 100644 index 0000000000..4b37f80c52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/forbidden_attributes_protection.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveModel + # 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.0.0/gems/activemodel-6.1.4/lib/active_model/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/gem_version.rb new file mode 100644 index 0000000000..29cbb11e67 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 4 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/lint.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/lint.rb new file mode 100644 index 0000000000..f9bfed95f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/locale/en.yml new file mode 100644 index 0000000000..061e35dd1e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/locale/en.yml @@ -0,0 +1,36 @@ +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)" + 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}" + odd: "must be odd" + even: "must be even" diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/model.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/model.rb new file mode 100644 index 0000000000..fc52cd4fdf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/model.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ActiveModel + # == Active \Model \Basic \Model + # + # 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::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + # + # Note that, by default, ActiveModel::Model 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::Model + # attr_accessor :id, :name + # + # def persisted? + # self.id == 1 + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => true + # + # Also, 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::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::Model + # 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::Model + # 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.0.0/gems/activemodel-6.1.4/lib/active_model/naming.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/naming.rb new file mode 100644 index 0000000000..b6fa3aa1f4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/naming.rb @@ -0,0 +1,333 @@ +# 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" + +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. + # + # module Foo + # class Bar + # end + # end + # + # ActiveModel::Name.new(Foo::Bar).to_s + # # => "Foo::Bar" + def initialize(klass, namespace = nil, name = nil) + @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) + @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) : @plural.dup) + @singular_route_key = ActiveSupport::Inflector.singularize(@route_key) + @route_key << "_index" if @plural == @singular + 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 unless @klass.respond_to?(:lookup_ancestors) && + @klass.respond_to?(:i18n_scope) + + defaults = @klass.lookup_ancestors.map do |klass| + klass.model_name.i18n_key + end + + defaults << options[:default] if options[:default] + defaults << @human + + options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default)) + I18n.translate(defaults.shift, **options) + end + + private + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr("/", "_") + 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) + plural(record_or_class) == singular(record_or_class) + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/nested_error.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/nested_error.rb new file mode 100644 index 0000000000..60d4093093 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/railtie.rb new file mode 100644 index 0000000000..65e20b9791 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/railtie.rb @@ -0,0 +1,20 @@ +# 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.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.0.0/gems/activemodel-6.1.4/lib/active_model/secure_password.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/secure_password.rb new file mode 100644 index 0000000000..6af2809b24 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/secure_password.rb @@ -0,0 +1,128 @@ +# 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. + # + # For further customizability, it is possible to suppress the default + # validations by passing validations: false as an argument. + # + # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password: + # + # gem 'bcrypt', '~> 3.1.7' + # + # Example 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 = 'mUc3m00RsqyRe' + # user.save # => false, confirmation doesn't match + # user.password_confirmation = 'mUc3m00RsqyRe' + # user.save # => true + # user.recovery_password = "42password" + # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC" + # user.save # => true + # user.authenticate('notright') # => false + # user.authenticate('mUc3m00RsqyRe') # => user + # user.authenticate_recovery_password('42password') # => user + # User.find_by(name: 'david')&.authenticate('notright') # => false + # User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user + def has_secure_password(attribute = :password, validations: 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 + $stderr.puts "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) + + 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 + + validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED + validates_confirmation_of attribute, allow_blank: true + end + end + end + + class InstanceMethodsOnActivation < Module + def initialize(attribute) + attr_reader attribute + + define_method("#{attribute}=") do |unencrypted_password| + if unencrypted_password.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 + + define_method("#{attribute}_confirmation=") do |unencrypted_password| + instance_variable_set("@#{attribute}_confirmation", unencrypted_password) + end + + # 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") + BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self + end + + alias_method :authenticate, :authenticate_password if attribute == :password + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/serialization.rb new file mode 100644 index 0000000000..cc17254674 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/serialization.rb @@ -0,0 +1,194 @@ +# 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, take a look + # at the private method +read_attribute_for_serialization+. + # + # 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 = attributes.keys + + return serializable_attributes(attribute_names) if options.blank? + + if only = options[:only] + attribute_names &= Array(only).map(&:to_s) + 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 + + private + # 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 + + 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.0.0/gems/activemodel-6.1.4/lib/active_model/serializers/json.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/serializers/json.rb new file mode 100644 index 0000000000..09dcae889b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/translation.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/translation.rb new file mode 100644 index 0000000000..44c0bf0604 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/translation.rb @@ -0,0 +1,70 @@ +# 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 + + # Returns the +i18n_scope+ for the class. Overwrite 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 + + # 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 = {}) + options = { count: 1 }.merge!(options) + parts = attribute.to_s.split(".") + attribute = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{i18n_scope}.attributes" + + if namespace + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" + end + defaults << :"#{attributes_scope}.#{namespace}.#{attribute}" + else + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}" + end + end + + defaults << :"attributes.#{attribute}" + defaults << options.delete(:default) if options[:default] + defaults << attribute.humanize + + options[:default] = defaults + I18n.translate(defaults.shift, **options) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type.rb new file mode 100644 index 0000000000..1d7a26fff5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "active_model/type/helpers" +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 gotten through ActiveModel::Type#lookup + def register(type_name, klass = nil, **options, &block) + registry.register(type_name, klass, **options, &block) + end + + def lookup(*args, **kwargs) # :nodoc: + registry.lookup(*args, **kwargs) + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/big_integer.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/big_integer.rb new file mode 100644 index 0000000000..b2c3ee50aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/big_integer.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_model/type/integer" + +module ActiveModel + module Type + class BigInteger < Integer # :nodoc: + private + def max_value + ::Float::INFINITY + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/binary.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/binary.rb new file mode 100644 index 0000000000..76203c5a88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/binary.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Binary < Value # :nodoc: + def type + :binary + end + + def binary? + true + end + + def cast(value) + if value.is_a?(Data) + value.to_s + else + super + 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 + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/boolean.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/boolean.rb new file mode 100644 index 0000000000..1214e9319b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/boolean.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # == Active \Model \Type \Boolean + # + # A class that behaves like a boolean type, including rules for coercion of user input. + # + # === Coercion + # Values set from user input will first be coerced into the appropriate ruby type. + # Coercion behavior is roughly mapped to Ruby's boolean semantics. + # + # - "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 + + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/date.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/date.rb new file mode 100644 index 0000000000..0e96d2c8a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/date.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Date < Value # :nodoc: + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new + + def type + :date + end + + def type_cast_for_schema(value) + value.to_s(: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) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/date_time.rb new file mode 100644 index 0000000000..532f0f0e86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/date_time.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class DateTime < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 4 => 0, 5 => 0 } + ) + + 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 = ::Date._parse(string) + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/decimal.rb new file mode 100644 index 0000000000..6aa51ff2ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/decimal.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Type + class Decimal < Value # :nodoc: + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/float.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/float.rb new file mode 100644 index 0000000000..435e39b2c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/float.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module ActiveModel + module Type + class Float < Value # :nodoc: + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers.rb new file mode 100644 index 0000000000..20145d5f0d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/accepts_multiparameter_time.rb new file mode 100644 index 0000000000..1011c5891a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module + module InstanceMethods + def serialize(value) + super(cast(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.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/mutable.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/mutable.rb new file mode 100644 index 0000000000..1cbea644c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/mutable.rb @@ -0,0 +1,20 @@ +# 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/numeric.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/numeric.rb new file mode 100644 index 0000000000..5b1db1f36b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/numeric.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Numeric + def serialize(value) + cast(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) + end + + private + def number_to_non_number?(old_value, new_value_before_type_cast) + old_value != nil && 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/time_value.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/time_value.rb new file mode 100644 index 0000000000..927f27b50c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/time_value.rb @@ -0,0 +1,90 @@ +# 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(value) + value = apply_seconds_precision(value) + + if value.acts_like?(:time) + if is_utc? + value = value.getutc if value.respond_to?(:getutc) && !value.utc? + else + value = value.getlocal if value.respond_to?(: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_s(: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 + + def fast_string_to_time(string) + return unless ISO_DATETIME =~ string + + usec = $7.to_i + usec_len = $7&.length + if usec_len&.< 6 + usec *= 10**(6 - usec_len) + end + + if $8 + offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60 + end + + new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/timezone.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/timezone.rb new file mode 100644 index 0000000000..b0477aec32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/helpers/timezone.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Timezone + def is_utc? + ::Time.zone_default.nil? || ::Time.zone_default.match?("UTC") + end + + def default_timezone + is_utc? ? :utc : :local + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/immutable_string.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/immutable_string.rb new file mode 100644 index 0000000000..5cb24a3928 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/immutable_string.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class ImmutableString < Value # :nodoc: + 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 + + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/integer.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/integer.rb new file mode 100644 index 0000000000..de7875192d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/integer.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Integer < Value # :nodoc: + 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 serializable?(value) + cast_value = cast(value) + in_range?(cast_value) && super + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/registry.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/registry.rb new file mode 100644 index 0000000000..a391250ad5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/registry.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveModel + # :stopdoc: + module Type + class Registry + def initialize + @registrations = [] + end + + def initialize_dup(other) + @registrations = @registrations.dup + super + 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_klass.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 registration_klass + Registration + end + + def find_registration(symbol, *args, **kwargs) + registrations.find { |r| r.matches?(symbol, *args, **kwargs) } + end + end + + class Registration + # Options must be taken because of https://bugs.ruby-lang.org/issues/10856 + def initialize(name, block, **) + @name = name + @block = block + end + + def call(_registry, *args, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + end + + def matches?(type_name, *args, **kwargs) + type_name == name + end + + private + attr_reader :name, :block + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/string.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/string.rb new file mode 100644 index 0000000000..631f29613a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/string.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_model/type/immutable_string" + +module ActiveModel + module Type + class String < ImmutableString # :nodoc: + 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/time.rb new file mode 100644 index 0000000000..f230bd4257 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/time.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Time < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } + ) + + 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 = ::Date._parse(value) + return if 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.empty? + + dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ") + + fast_string_to_time(dummy_time_value) || begin + time_hash = ::Date._parse(dummy_time_value) + return if 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.0.0/gems/activemodel-6.1.4/lib/active_model/type/value.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/value.rb new file mode 100644 index 0000000000..1bcebe1b6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/type/value.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Value + attr_reader :precision, :scale, :limit + + def initialize(precision: nil, limit: nil, scale: nil) + @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 + + def type # :nodoc: + 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: + yield 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 + + 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.0.0/gems/activemodel-6.1.4/lib/active_model/validations.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations.rb new file mode 100644 index 0000000000..6e99d7e874 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations.rb @@ -0,0 +1,436 @@ +# 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 + + attr_accessor :validation_context + private :validation_context= + 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]) + # * :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].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]) + # * :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? { |arg| arg.is_a?(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.dup + options[:on] = Array(options[:on]) + options[:if] = [ + ->(o) { !(options[:on] & Array(o.validation_context)).empty? }, + *options[:if] + ] + 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 + 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, self.validation_context = validation_context, context + errors.clear + run_validations! + ensure + self.validation_context = current_context + end + + alias_method :validate, :valid? + + # 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 + + private + 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 +end + +Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file } diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/absence.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/absence.rb new file mode 100644 index 0000000000..cb1d1ba57d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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?). Happens by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/acceptance.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/acceptance.rb new file mode 100644 index 0000000000..5ea03cb124 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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 + 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+ and by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/callbacks.rb new file mode 100644 index 0000000000..38642272f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/callbacks.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # == Active \Model \Validation \Callbacks + # + # Provides an interface for any class to have +before_validation+ and + # +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] & Array(o.validation_context)).empty? + }, + *options[:if] + ] + end + end + end + + private + # Overwrite run validations to include callbacks. + def run_validations! + _run_validation_callbacks { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/clusivity.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/clusivity.rb new file mode 100644 index 0000000000..9f4f4e7923 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/clusivity.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range" + +module ActiveModel + module Validations + module Clusivity #:nodoc: + 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 = if delimiter.respond_to?(:call) + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end + + 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.first + when Numeric, Time, DateTime, Date + :cover? + else + :include? + end + else + :include? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/confirmation.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/confirmation.rb new file mode 100644 index 0000000000..9ef2031be5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") + end.compact) + + klass.attr_writer(*attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end.compact) + 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/exclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/exclusion.rb new file mode 100644 index 0000000000..a2e4dd8652 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/format.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/format.rb new file mode 100644 index 0000000000..92493a2658 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/format.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class FormatValidator < EachValidator # :nodoc: + def validate_each(record, attribute, value) + if options[:with] + regexp = option_call(record, :with) + record_error(record, attribute, :with, value) unless regexp.match?(value.to_s) + elsif options[:without] + regexp = option_call(record, :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 option_call(record, name) + option = options[name] + option.respond_to?(:call) ? option.call(record) : option + end + + 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/helper_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/helper_methods.rb new file mode 100644 index 0000000000..730173f2f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/inclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/inclusion.rb new file mode 100644 index 0000000000..bbd99676c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/length.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/length.rb new file mode 100644 index 0000000000..57db6c6816 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/length.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class LengthValidator < EachValidator # :nodoc: + 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], options[:maximum] = range.min, range.max + 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.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) + case check_value + when Proc + check_value = check_value.call(record) + when Symbol + check_value = record.send(check_value) + end + 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/numericality.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/numericality.rb new file mode 100644 index 0000000000..4f97156f51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/numericality.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Validations + class NumericalityValidator < EachValidator # :nodoc: + CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, + equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, + odd: :odd?, even: :even?, other_than: :!= }.freeze + + RESERVED_OPTIONS = CHECKS.keys + [:only_integer] + + INTEGER_REGEX = /\A[+-]?\d+\z/ + + HEXADECIMAL_REGEX = /\A[+-]?0[xX]/ + + def check_validity! + keys = CHECKS.keys - [:odd, :even] + options.slice(*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 + 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(*CHECKS.keys).each do |option, option_value| + case option + when :odd, :even + unless value.to_i.public_send(CHECKS[option]) + record.errors.add(attr_name, option, **filtered_options(value)) + end + else + case option_value + when Proc + option_value = option_value.call(record) + when Symbol + option_value = record.send(option_value) + end + + option_value = parse_as_number(option_value, precision, scale) + + unless value.public_send(CHECKS[option], option_value) + record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value)) + end + end + end + end + + private + 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) + !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) + case options[:only_integer] + when Symbol + record.send(options[:only_integer]) + when Proc + options[:only_integer].call(record) + else + options[:only_integer] + end + 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, e.g. an integral value (default is +false+). + # * :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. + # * :greater_than_or_equal_to - Specifies the value must be + # greater than or equal the supplied value. + # * :equal_to - Specifies the value must be equal to the supplied + # value. + # * :less_than - Specifies the value must be less than the + # supplied value. + # * :less_than_or_equal_to - Specifies the value must be less + # than or equal the supplied value. + # * :other_than - Specifies the value must be other than the + # supplied value. + # * :odd - Specifies the value must be an odd number. + # * :even - Specifies the value must be an even number. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . + # See ActiveModel::Validations#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/presence.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/presence.rb new file mode 100644 index 0000000000..9ac376c528 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/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?). Happens by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/validates.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/validates.rb new file mode 100644 index 0000000000..20a34c56cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/validates.rb @@ -0,0 +1,175 @@ +# 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]) + # * :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: 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 = key.include?("::") ? key.constantize : 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] + 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.0.0/gems/activemodel-6.1.4/lib/active_model/validations/with.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/with.rb new file mode 100644 index 0000000000..d777ac836e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validations/with.rb @@ -0,0 +1,147 @@ +# 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 + # + # 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, &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, &block) + validator.validate(self) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validator.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validator.rb new file mode 100644 index 0000000000..a521d79d7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/validator.rb @@ -0,0 +1,188 @@ +# 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 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 + + # +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 #:nodoc: + 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.0.0/gems/activemodel-6.1.4/lib/active_model/version.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/version.rb new file mode 100644 index 0000000000..dd817f5639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.4/lib/active_model/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/CHANGELOG.md new file mode 100644 index 0000000000..7b2473442e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/CHANGELOG.md @@ -0,0 +1,212 @@ +## Rails 6.1.7.2 (January 24, 2023) ## + +* No changes. + + +## Rails 6.1.7.1 (January 17, 2023) ## + +* No changes. + + +## Rails 6.1.7 (September 09, 2022) ## + +* No changes. + + +## Rails 6.1.6.1 (July 12, 2022) ## + +* No changes. + + +## Rails 6.1.6 (May 09, 2022) ## + +* No changes. + + +## Rails 6.1.5.1 (April 26, 2022) ## + +* No changes. + + +## Rails 6.1.5 (March 09, 2022) ## + +* Clear secure password cache if password is set to `nil` + + Before: + + user.password = 'something' + user.password = nil + + user.password # => 'something' + + Now: + + user.password = 'something' + user.password = nil + + user.password # => nil + + *Markus Doits* + +* Fix delegation in `ActiveModel::Type::Registry#lookup` and `ActiveModel::Type.lookup` + + Passing a last positional argument `{}` would be incorrectly considered as keyword argument. + + *Benoit Daloze* + +* Fix `to_json` after `changes_applied` for `ActiveModel::Dirty` object. + + *Ryuta Kamizono* + + +## Rails 6.1.4.7 (March 08, 2022) ## + +* No changes. + + +## Rails 6.1.4.6 (February 11, 2022) ## + +* No changes. + + +## Rails 6.1.4.5 (February 11, 2022) ## + +* No changes. + + +## Rails 6.1.4.4 (December 15, 2021) ## + +* No changes. + + +## Rails 6.1.4.3 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.2 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.1 (August 19, 2021) ## + +* No changes. + + +## Rails 6.1.4 (June 24, 2021) ## + +* Fix `to_json` for `ActiveModel::Dirty` object. + + Exclude +mutations_from_database+ attribute from json as it lead to recursion. + + *Anil Maurya* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* No changes. + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* No changes. + + +## Rails 6.1.2 (February 09, 2021) ## + +* No changes. + + +## Rails 6.1.1 (January 07, 2021) ## + +* No changes. + + +## Rails 6.1.0 (December 09, 2020) ## + +* Pass in `base` instead of `base_class` to Error.human_attribute_name + + This is useful in cases where the `human_attribute_name` method depends + on other attributes' values of the class under validation to derive what the + attribute name should be. + + *Filipe Sabella* + +* Deprecate marshalling load from legacy attributes format. + + *Ryuta Kamizono* + +* `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`. + + topic.update!(status: :archived) + topic.status_previously_changed?(from: "active", to: "archived") + # => true + + *George Claghorn* + +* Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen: + + class Animal + include ActiveModel::Attributes + attribute :age + end + + animal = Animal.new + animal.freeze + animal.age = 25 # => FrozenError, "can't modify a frozen Animal" + + *Josh Brody* + +* Add `*_previously_was` attribute methods when dirty tracking. Example: + + pirate.update(catchphrase: "Ahoy!") + pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"] + pirate.catchphrase_previously_was # => "Thar She Blows!" + + *DHH* + +* Encapsulate each validation error as an Error object. + + The `ActiveModel`’s `errors` collection is now an array of these Error + objects, instead of messages/details hash. + + For each of these `Error` object, its `message` and `full_message` methods + are for generating error messages. Its `details` method would return error’s + extra parameters, found in the original `details` hash. + + The change tries its best at maintaining backward compatibility, however + some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating + `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward, + please convert those direct manipulations to use provided API methods instead. + Please note that `errors#add` now accepts `options` as keyword arguments instead of `Hash` which + introduced a change in Ruby 3 to [keyword arguments][kwargs-ann]. + + [kwargs-ann]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ + + The list of deprecated methods and their planned future behavioral changes at the next major release are: + + * `errors#slice!` will be removed. + * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object. + * `errors#values` will be removed. + * `errors#keys` will be removed. + * `errors#to_xml` will be removed. + * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`. + * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`). + * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect. + * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect. + + *lulalala* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/MIT-LICENSE new file mode 100644 index 0000000000..5637054003 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2004-2022 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.0.0/gems/activemodel-6.1.7.2/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/README.rdoc new file mode 100644 index 0000000000..f68c5bfcd0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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://edgeguides.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::Model. + + class Person + include ActiveModel::Model + + 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::Model 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}[link: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}[link: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}[link: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}[link: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}[link: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}[link:classes/ActiveModel/Serialization.html] + +* Internationalization (i18n) support + + class Person + extend ActiveModel::Translation + end + + Person.human_attribute_name('my_attribute') + # => "My attribute" + + {Learn more}[link: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}[link: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}[link: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.0.0/gems/activemodel-6.1.7.2/lib/active_model.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model.rb new file mode 100644 index 0000000000..bb6e785d19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2022 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" + +module ActiveModel + extend ActiveSupport::Autoload + + autoload :Attribute + autoload :Attributes + autoload :AttributeAssignment + autoload :AttributeMethods + 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" + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute.rb new file mode 100644 index 0000000000..5bb5e747ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute.rb @@ -0,0 +1,248 @@ +# 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 + type.serialize(value) + 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 defined?(@value) && @value.duplicable? + @value = @value.dup + end + end + + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) + end + + def _original_value_for_database + type.serialize(original_value) + end + + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + 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 + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute/user_provided_default.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute/user_provided_default.rb new file mode 100644 index 0000000000..9dc16e882d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute/user_provided_default.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class Attribute # :nodoc: + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_assignment.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_assignment.rb new file mode 100644 index 0000000000..ea4dae101c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_assignment.rb @@ -0,0 +1,55 @@ +# 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 + + private + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + setter = :"#{k}=" + if respond_to?(setter) + public_send(setter, v) + else + raise UnknownAttributeError.new(self, k.to_s) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_methods.rb new file mode 100644 index 0000000000..f584ed99a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_methods.rb @@ -0,0 +1,555 @@ +# 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 + 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_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.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) + self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix } + 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) + self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix } + 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_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] } + 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) + self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s) + CodeGenerator.batch(self, __FILE__, __LINE__) do |owner| + attribute_method_matchers.each do |matcher| + matcher_new = matcher.method_name(new_name).to_s + matcher_old = matcher.method_name(old_name).to_s + define_proxy_call false, owner, matcher_new, matcher_old + end + end + 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) + CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| + attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) } + 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) + CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner| + attribute_method_matchers.each do |matcher| + method_name = matcher.method_name(attr_name) + + unless instance_method_already_implemented?(method_name) + generate_method = "define_method_#{matcher.target}" + + if respond_to?(generate_method, true) + send(generate_method, attr_name.to_s, owner: owner) + else + define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s + end + end + end + attribute_method_matchers_cache.clear + end + end + + # Removes all the previously dynamically defined methods from the class. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_method :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name_short? # => true + # + # Person.undefine_attribute_methods + # + # person.name_short? # => NoMethodError + def undefine_attribute_methods + generated_attribute_methods.module_eval do + undef_method(*instance_methods) + end + attribute_method_matchers_cache.clear + end + + private + class CodeGenerator + 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 + @sources = ["# frozen_string_literal: true\n"] + @renames = {} + end + + def <<(source_line) + @sources << source_line + end + + def rename_method(old_name, new_name) + @renames[old_name] = new_name + end + + def execute + @owner.module_eval(@sources.join(";"), @path, @line - 1) + @renames.each do |old_name, new_name| + @owner.alias_method new_name, old_name + @owner.undef_method old_name + end + end + end + private_constant :CodeGenerator + + 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_matchers_cache + @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4) + end + + def attribute_method_matchers_matching(method_name) + attribute_method_matchers_cache.compute_if_absent(method_name) do + attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact + end + end + + # Define a method `name` in `mod` that dispatches to `send` + # using the given `extra` args. This falls back on `define_method` + # and `send` if the given names cannot be compiled. + def define_proxy_call(include_private, code_generator, name, target, *extra) + defn = if NAME_COMPILABLE_REGEXP.match?(name) + "def #{name}(*args)" + else + "define_method(:'#{name}') do |*args|" + end + + extra = (extra.map!(&:inspect) << "*args").join(", ") + + body = if CALL_COMPILABLE_REGEXP.match?(target) + "#{"self." unless include_private}#{target}(#{extra})" + else + "send(:'#{target}', #{extra})" + end + + code_generator << + defn << + body << + "end" << + "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)" + end + + class AttributeMethodMatcher #:nodoc: + attr_reader :prefix, :suffix, :target + + AttributeMethodMatch = Struct.new(:target, :attr_name) + + def initialize(options = {}) + @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "") + @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ + @target = "#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" + end + + def match(method_name) + if @regex =~ method_name + AttributeMethodMatch.new(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, *args, &block) + if respond_to_without_attributes?(method, true) + super + else + match = matched_attribute_method(method.to_s) + match ? attribute_missing(match, *args, &block) : super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + # +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, *args, &block) + __send__(match.target, match.attr_name, *args, &block) + 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_matchers_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}", 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. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # 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 + owner.rename_method(temp_method_name, method_name) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_mutation_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_mutation_tracker.rb new file mode 100644 index 0000000000..e46d2e76da --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_mutation_tracker.rb @@ -0,0 +1,181 @@ +# 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) == from) && + (OPTION_NOT_GIVEN == to || fetch_value(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 + 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 + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set.rb new file mode 100644 index 0000000000..e612916976 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set.rb @@ -0,0 +1,106 @@ +# 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 values_before_type_cast + attributes.transform_values(&:value_before_type_cast) + 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 + + 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) + value + end + + def freeze + attributes.freeze + super + end + + def deep_dup + AttributeSet.new(attributes.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 ==(other) + attributes == other.attributes + end + + protected + attr_reader :attributes + + private + def default_attribute(name) + Attribute.null(name) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set/builder.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set/builder.rb new file mode 100644 index 0000000000..6381aa6593 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set/builder.rb @@ -0,0 +1,191 @@ +# 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) + if values.is_a?(Hash) + ActiveSupport::Deprecation.warn(<<~MSG) + Marshalling load from legacy attributes format is deprecated and will be removed in Rails 7.0. + MSG + empty_hash = {}.freeze + initialize(empty_hash, empty_hash, empty_hash, empty_hash, values) + @materialized = true + else + initialize(*values) + end + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set/yaml_encoder.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attribute_set/yaml_encoder.rb new file mode 100644 index 0000000000..ea1efc160e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attributes.rb new file mode 100644 index 0000000000..de94b1cf3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/attributes.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_model/attribute_set" +require "active_model/attribute/user_provided_default" + +module ActiveModel + module Attributes #:nodoc: + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "=" + class_attribute :attribute_types, :_default_attributes, instance_accessor: false + self.attribute_types = Hash.new(Type.default_value) + self._default_attributes = AttributeSet.new({}) + end + + module ClassMethods + def attribute(name, type = Type::Value.new, **options) + name = name.to_s + if type.is_a?(Symbol) + type = ActiveModel::Type.lookup(type, **options.except(:default)) + end + self.attribute_types = attribute_types.merge(name => type) + define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type) + 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 + + private + def define_method_attribute=(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED + + def define_default_attribute(name, value, type) + self._default_attributes = _default_attributes.deep_dup + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + else + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + end + _default_attributes[name] = default_attribute + end + end + + def initialize(*) + @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(name: 'Francesco', 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 + @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.0.0/gems/activemodel-6.1.7.2/lib/active_model/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/callbacks.rb new file mode 100644 index 0000000000..dc8f798f6a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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: Calling 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 :initializer, 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/conversion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/conversion.rb new file mode 100644 index 0000000000..82713ddc81 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/conversion.rb @@ -0,0 +1,111 @@ +# 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 + + # 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 ? [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.join("-") : 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 ||= begin + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/dirty.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/dirty.rb new file mode 100644 index 0000000000..50e18b7044 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/dirty.rb @@ -0,0 +1,293 @@ +# 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 [attr_name]_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 + # [attribute_name]_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 [attribute_name]_will_change! on Active Record models. + # + # person.name_will_change! + # person.name_change # => ["Bill", "Bill"] + # person.name << 'y' + # person.name_change # => ["Bill", "Billy"] + module Dirty + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "_changed?", "_change", "_will_change!", "_was" + attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was" + attribute_method_affix prefix: "restore_", suffix: "!" + attribute_method_affix prefix: "clear_", suffix: "_change" + end + + def initialize_dup(other) # :nodoc: + super + if self.class.respond_to?(:_default_attributes) + @attributes = self.class._default_attributes.map do |attr| + attr.with_value_from_user(@attributes.fetch_value(attr.name)) + end + end + @mutations_from_database = nil + end + + def as_json(options = {}) # :nodoc: + options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"] + 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? attribute methods. + def attribute_changed?(attr_name, **options) # :nodoc: + mutations_from_database.changed?(attr_name.to_s, **options) + end + + # Dispatch target for *_was attribute methods. + def attribute_was(attr_name) # :nodoc: + mutations_from_database.original_value(attr_name.to_s) + end + + # Dispatch target for *_previously_changed? attribute methods. + def attribute_previously_changed?(attr_name, **options) # :nodoc: + mutations_before_last_save.changed?(attr_name.to_s, **options) + end + + # Dispatch target for *_previously_was attribute methods. + def attribute_previously_was(attr_name) # :nodoc: + 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 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/error.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/error.rb new file mode 100644 index 0000000000..103d7d6e71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/error.rb @@ -0,0 +1,207 @@ +# 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.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/errors.rb new file mode 100644 index 0000000000..b0355a1ccb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/errors.rb @@ -0,0 +1,709 @@ +# 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_support/core_ext/string/filters" +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 + def_delegators :@errors, :size, :clear, :blank?, :empty?, :uniq!, :any? + # TODO: forward all enumerable methods after `each` deprecation is removed. + def_delegators :@errors, :count + + LEGACY_ATTRIBUTES = [:messages, :details].freeze + private_constant :LEGACY_ATTRIBUTES + + # 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. + # + # 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+. + # + # override_options - Hash + # @option override_options [Symbol] :attribute Override the attribute the error belongs to + # @option override_options [Symbol] :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. + # + # other - The ActiveModel::Errors instance. + # + # Examples + # + # person.errors.merge!(other) + def merge!(other) + other.errors.each { |error| + import(error) + } + end + + # Removes all errors except the given keys. Returns a hash containing the removed errors. + # + # person.errors.keys # => [:name, :age, :gender, :city] + # person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] } + # person.errors.keys # => [:age, :gender] + def slice!(*keys) + deprecation_removal_warning(:slice!) + + keys = keys.map(&:to_sym) + + results = messages.dup.slice!(*keys) + + @errors.keep_if do |error| + keys.include?(error.attribute) + end + + results + 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) + DeprecationHandlingMessageArray.new(messages_for(attribute), self, attribute) + end + + # 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 + # + # To be backward compatible with past deprecated hash-like behavior, + # when block accepts two parameters instead of one, it + # iterates through each error key, value pair in the error messages hash. + # Yields the attribute and the error for that attribute. If the attribute + # has more than one error message, yields once for each error message. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.each do |attribute, message| + # # Will yield :name and "can't be blank" + # end + # + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.each do |attribute, message| + # # Will yield :name and "can't be blank" + # # then yield :name and "must be specified" + # end + def each(&block) + if block.arity <= 1 + @errors.each(&block) + else + ActiveSupport::Deprecation.warn(<<~MSG) + Enumerating ActiveModel::Errors as a hash has been deprecated. + In Rails 6.1, `errors` is an array of Error objects, + therefore it should be accessed by a block with a single block + parameter like this: + + person.errors.each do |error| + attribute = error.attribute + message = error.message + end + + You are passing a block expecting two parameters, + so the old hash behavior is simulated. As this is deprecated, + this will result in an ArgumentError in Rails 7.0. + MSG + @errors. + sort { |a, b| a.attribute <=> b.attribute }. + each { |error| yield error.attribute, error.message } + end + end + + # Returns all message values. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.values # => [["cannot be nil", "must be specified"]] + def values + deprecation_removal_warning(:values, "errors.map { |error| error.message }") + @errors.map(&:message).freeze + end + + # Returns all message keys. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.keys # => [:name] + def keys + deprecation_removal_warning(:keys, "errors.attribute_names") + keys = @errors.map(&:attribute) + keys.uniq! + keys.freeze + 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 an xml formatted representation of the Errors hash. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.to_xml + # # => + # # + # # + # # name can't be blank + # # name must be specified + # # + def to_xml(options = {}) + deprecation_removal_warning(:to_xml) + to_a.to_xml({ root: "errors", skip_types: true }.merge!(options)) + 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 + + def to_h + ActiveSupport::Deprecation.warn(<<~EOM) + ActiveModel::Errors#to_h is deprecated and will be removed in Rails 7.0. + Please use `ActiveModel::Errors.to_hash` instead. The values in the hash + returned by `ActiveModel::Errors.to_hash` is an array of error messages. + EOM + + to_hash.transform_values { |values| values.last } + end + + # Returns a Hash of attributes with an array of their error messages. + # + # Updating this hash would still update errors state for backward + # compatibility, but this behavior is deprecated. + def messages + DeprecationHandlingMessageHash.new(self) + end + + # Returns a Hash of attributes with an array of their error details. + # + # Updating this hash would still update errors state for backward + # compatibility, but this behavior is deprecated. + def details + hash = group_by_attribute.transform_values do |errors| + errors.map(&:details) + end + DeprecationHandlingDetailsHash.new(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+). + # + # 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 marshal_load(array) # :nodoc: + # Rails 5 + @errors = [] + @base = array[0] + add_from_legacy_details_hash(array[2]) + end + + def init_with(coder) # :nodoc: + data = coder.map + + data.each { |k, v| + next if LEGACY_ATTRIBUTES.include?(k.to_sym) + instance_variable_set(:"@#{k}", v) + } + + @errors ||= [] + + # Legacy support Rails 5.x details hash + add_from_legacy_details_hash(data["details"]) if data.key?("details") + 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 + + def add_from_legacy_details_hash(details) + details.each { |attribute, errors| + errors.each { |error| + type = error.delete(:error) + add(attribute, type, **error) + } + } + end + + def deprecation_removal_warning(method_name, alternative_message = nil) + message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 7.0." + if alternative_message + message << "\n\nTo achieve the same use:\n\n " + message << alternative_message + end + ActiveSupport::Deprecation.warn(message) + end + + def deprecation_rename_warning(old_method_name, new_method_name) + ActiveSupport::Deprecation.warn("ActiveModel::Errors##{old_method_name} is deprecated. Please call ##{new_method_name} instead.") + end + end + + class DeprecationHandlingMessageHash < SimpleDelegator + def initialize(errors) + @errors = errors + super(prepare_content) + end + + def []=(attribute, value) + ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.") + + @errors.delete(attribute) + Array(value).each do |message| + @errors.add(attribute, message) + end + + __setobj__ prepare_content + end + + def delete(attribute) + ActiveSupport::Deprecation.warn("Calling `delete` to an ActiveModel::Errors messages hash is deprecated. Please call `ActiveModel::Errors#delete` instead.") + + @errors.delete(attribute) + end + + private + def prepare_content + content = @errors.to_hash + content.each do |attribute, value| + content[attribute] = DeprecationHandlingMessageArray.new(value, @errors, attribute) + end + content.default_proc = proc do |hash, attribute| + hash = hash.dup + hash[attribute] = DeprecationHandlingMessageArray.new([], @errors, attribute) + __setobj__ hash.freeze + hash[attribute] + end + content.freeze + end + end + + class DeprecationHandlingMessageArray < SimpleDelegator + def initialize(content, errors, attribute) + @errors = errors + @attribute = attribute + super(content.freeze) + end + + def <<(message) + ActiveSupport::Deprecation.warn("Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.") + + @errors.add(@attribute, message) + __setobj__ @errors.messages_for(@attribute) + self + end + + def clear + ActiveSupport::Deprecation.warn("Calling `clear` to an ActiveModel::Errors message array in order to delete all errors is deprecated. Please call `ActiveModel::Errors#delete` instead.") + + @errors.delete(@attribute) + end + end + + class DeprecationHandlingDetailsHash < SimpleDelegator + def initialize(details) + details.default = [] + details.freeze + super(details) + end + end + + # 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 + + # Raised when attribute values are out of range. + class RangeError < ::RangeError + end + + # 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/forbidden_attributes_protection.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/forbidden_attributes_protection.rb new file mode 100644 index 0000000000..4b37f80c52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/forbidden_attributes_protection.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveModel + # 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/gem_version.rb new file mode 100644 index 0000000000..0508a24168 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 7 + PRE = "2" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/lint.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/lint.rb new file mode 100644 index 0000000000..f9bfed95f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/locale/en.yml new file mode 100644 index 0000000000..061e35dd1e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/locale/en.yml @@ -0,0 +1,36 @@ +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)" + 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}" + odd: "must be odd" + even: "must be even" diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/model.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/model.rb new file mode 100644 index 0000000000..fc52cd4fdf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/model.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ActiveModel + # == Active \Model \Basic \Model + # + # 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::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + # + # Note that, by default, ActiveModel::Model 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::Model + # attr_accessor :id, :name + # + # def persisted? + # self.id == 1 + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => true + # + # Also, 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::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::Model + # 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::Model + # 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/naming.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/naming.rb new file mode 100644 index 0000000000..b6fa3aa1f4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/naming.rb @@ -0,0 +1,333 @@ +# 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" + +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. + # + # module Foo + # class Bar + # end + # end + # + # ActiveModel::Name.new(Foo::Bar).to_s + # # => "Foo::Bar" + def initialize(klass, namespace = nil, name = nil) + @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) + @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) : @plural.dup) + @singular_route_key = ActiveSupport::Inflector.singularize(@route_key) + @route_key << "_index" if @plural == @singular + 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 unless @klass.respond_to?(:lookup_ancestors) && + @klass.respond_to?(:i18n_scope) + + defaults = @klass.lookup_ancestors.map do |klass| + klass.model_name.i18n_key + end + + defaults << options[:default] if options[:default] + defaults << @human + + options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default)) + I18n.translate(defaults.shift, **options) + end + + private + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr("/", "_") + 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) + plural(record_or_class) == singular(record_or_class) + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/nested_error.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/nested_error.rb new file mode 100644 index 0000000000..60d4093093 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/railtie.rb new file mode 100644 index 0000000000..65e20b9791 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/railtie.rb @@ -0,0 +1,20 @@ +# 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.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/secure_password.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/secure_password.rb new file mode 100644 index 0000000000..742e4a63eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/secure_password.rb @@ -0,0 +1,129 @@ +# 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. + # + # For further customizability, it is possible to suppress the default + # validations by passing validations: false as an argument. + # + # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password: + # + # gem 'bcrypt', '~> 3.1.7' + # + # Example 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 = 'mUc3m00RsqyRe' + # user.save # => false, confirmation doesn't match + # user.password_confirmation = 'mUc3m00RsqyRe' + # user.save # => true + # user.recovery_password = "42password" + # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC" + # user.save # => true + # user.authenticate('notright') # => false + # user.authenticate('mUc3m00RsqyRe') # => user + # user.authenticate_recovery_password('42password') # => user + # User.find_by(name: 'david')&.authenticate('notright') # => false + # User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user + def has_secure_password(attribute = :password, validations: 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 + $stderr.puts "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) + + 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 + + validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED + validates_confirmation_of attribute, allow_blank: true + end + end + end + + class InstanceMethodsOnActivation < Module + def initialize(attribute) + 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 + + define_method("#{attribute}_confirmation=") do |unencrypted_password| + instance_variable_set("@#{attribute}_confirmation", unencrypted_password) + end + + # 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") + BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self + end + + alias_method :authenticate, :authenticate_password if attribute == :password + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/serialization.rb new file mode 100644 index 0000000000..cc17254674 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/serialization.rb @@ -0,0 +1,194 @@ +# 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, take a look + # at the private method +read_attribute_for_serialization+. + # + # 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 = attributes.keys + + return serializable_attributes(attribute_names) if options.blank? + + if only = options[:only] + attribute_names &= Array(only).map(&:to_s) + 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 + + private + # 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 + + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/serializers/json.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/serializers/json.rb new file mode 100644 index 0000000000..09dcae889b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/translation.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/translation.rb new file mode 100644 index 0000000000..44c0bf0604 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/translation.rb @@ -0,0 +1,70 @@ +# 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 + + # Returns the +i18n_scope+ for the class. Overwrite 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 + + # 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 = {}) + options = { count: 1 }.merge!(options) + parts = attribute.to_s.split(".") + attribute = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{i18n_scope}.attributes" + + if namespace + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" + end + defaults << :"#{attributes_scope}.#{namespace}.#{attribute}" + else + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}" + end + end + + defaults << :"attributes.#{attribute}" + defaults << options.delete(:default) if options[:default] + defaults << attribute.humanize + + options[:default] = defaults + I18n.translate(defaults.shift, **options) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type.rb new file mode 100644 index 0000000000..dbc05cd3f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "active_model/type/helpers" +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 gotten through ActiveModel::Type#lookup + def register(type_name, klass = nil, **options, &block) + registry.register(type_name, klass, **options, &block) + end + + def lookup(*args) # :nodoc: + registry.lookup(*args) + end + ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true) + + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/big_integer.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/big_integer.rb new file mode 100644 index 0000000000..b2c3ee50aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/big_integer.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_model/type/integer" + +module ActiveModel + module Type + class BigInteger < Integer # :nodoc: + private + def max_value + ::Float::INFINITY + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/binary.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/binary.rb new file mode 100644 index 0000000000..76203c5a88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/binary.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Binary < Value # :nodoc: + def type + :binary + end + + def binary? + true + end + + def cast(value) + if value.is_a?(Data) + value.to_s + else + super + 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 + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/boolean.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/boolean.rb new file mode 100644 index 0000000000..1214e9319b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/boolean.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # == Active \Model \Type \Boolean + # + # A class that behaves like a boolean type, including rules for coercion of user input. + # + # === Coercion + # Values set from user input will first be coerced into the appropriate ruby type. + # Coercion behavior is roughly mapped to Ruby's boolean semantics. + # + # - "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 + + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date.rb new file mode 100644 index 0000000000..0e96d2c8a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Date < Value # :nodoc: + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new + + def type + :date + end + + def type_cast_for_schema(value) + value.to_s(: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) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date_time.rb new file mode 100644 index 0000000000..532f0f0e86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/date_time.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class DateTime < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 4 => 0, 5 => 0 } + ) + + 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 = ::Date._parse(string) + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/decimal.rb new file mode 100644 index 0000000000..6aa51ff2ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/decimal.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Type + class Decimal < Value # :nodoc: + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/float.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/float.rb new file mode 100644 index 0000000000..435e39b2c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/float.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module ActiveModel + module Type + class Float < Value # :nodoc: + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers.rb new file mode 100644 index 0000000000..20145d5f0d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb new file mode 100644 index 0000000000..1011c5891a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module + module InstanceMethods + def serialize(value) + super(cast(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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/mutable.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/mutable.rb new file mode 100644 index 0000000000..1cbea644c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/mutable.rb @@ -0,0 +1,20 @@ +# 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/numeric.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/numeric.rb new file mode 100644 index 0000000000..5b1db1f36b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/numeric.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Numeric + def serialize(value) + cast(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) + end + + private + def number_to_non_number?(old_value, new_value_before_type_cast) + old_value != nil && 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/time_value.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/time_value.rb new file mode 100644 index 0000000000..927f27b50c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/time_value.rb @@ -0,0 +1,90 @@ +# 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(value) + value = apply_seconds_precision(value) + + if value.acts_like?(:time) + if is_utc? + value = value.getutc if value.respond_to?(:getutc) && !value.utc? + else + value = value.getlocal if value.respond_to?(: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_s(: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 + + def fast_string_to_time(string) + return unless ISO_DATETIME =~ string + + usec = $7.to_i + usec_len = $7&.length + if usec_len&.< 6 + usec *= 10**(6 - usec_len) + end + + if $8 + offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60 + end + + new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/timezone.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/timezone.rb new file mode 100644 index 0000000000..b0477aec32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/helpers/timezone.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Timezone + def is_utc? + ::Time.zone_default.nil? || ::Time.zone_default.match?("UTC") + end + + def default_timezone + is_utc? ? :utc : :local + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/immutable_string.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/immutable_string.rb new file mode 100644 index 0000000000..5cb24a3928 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/immutable_string.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class ImmutableString < Value # :nodoc: + 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 + + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/integer.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/integer.rb new file mode 100644 index 0000000000..de7875192d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/integer.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Integer < Value # :nodoc: + 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 serializable?(value) + cast_value = cast(value) + in_range?(cast_value) && super + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/registry.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/registry.rb new file mode 100644 index 0000000000..3e87507b32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/registry.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ActiveModel + # :stopdoc: + module Type + class Registry + def initialize + @registrations = [] + end + + def initialize_dup(other) + @registrations = @registrations.dup + super + 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_klass.new(type_name, block, **options) + end + + def lookup(symbol, *args) + registration = find_registration(symbol, *args) + + if registration + registration.call(self, symbol, *args) + else + raise ArgumentError, "Unknown type #{symbol.inspect}" + end + end + ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true) + + private + attr_reader :registrations + + def registration_klass + Registration + end + + def find_registration(symbol, *args, **kwargs) + registrations.find { |r| r.matches?(symbol, *args, **kwargs) } + end + end + + class Registration + # Options must be taken because of https://bugs.ruby-lang.org/issues/10856 + def initialize(name, block, **) + @name = name + @block = block + end + + def call(_registry, *args) + block.call(*args) + end + ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true) + + def matches?(type_name, *args, **kwargs) + type_name == name + end + + private + attr_reader :name, :block + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/string.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/string.rb new file mode 100644 index 0000000000..631f29613a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/string.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_model/type/immutable_string" + +module ActiveModel + module Type + class String < ImmutableString # :nodoc: + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/time.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/time.rb new file mode 100644 index 0000000000..f230bd4257 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/time.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Time < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } + ) + + 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 = ::Date._parse(value) + return if 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.empty? + + dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ") + + fast_string_to_time(dummy_time_value) || begin + time_hash = ::Date._parse(dummy_time_value) + return if 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/value.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/value.rb new file mode 100644 index 0000000000..1bcebe1b6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/type/value.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Value + attr_reader :precision, :scale, :limit + + def initialize(precision: nil, limit: nil, scale: nil) + @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 + + def type # :nodoc: + 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: + yield 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 + + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations.rb new file mode 100644 index 0000000000..6e99d7e874 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations.rb @@ -0,0 +1,436 @@ +# 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 + + attr_accessor :validation_context + private :validation_context= + 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]) + # * :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].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]) + # * :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? { |arg| arg.is_a?(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.dup + options[:on] = Array(options[:on]) + options[:if] = [ + ->(o) { !(options[:on] & Array(o.validation_context)).empty? }, + *options[:if] + ] + 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 + 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, self.validation_context = validation_context, context + errors.clear + run_validations! + ensure + self.validation_context = current_context + end + + alias_method :validate, :valid? + + # 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 + + private + 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 +end + +Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file } diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/absence.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/absence.rb new file mode 100644 index 0000000000..cb1d1ba57d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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?). Happens by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/acceptance.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/acceptance.rb new file mode 100644 index 0000000000..5ea03cb124 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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 + 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+ and by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/callbacks.rb new file mode 100644 index 0000000000..38642272f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/callbacks.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # == Active \Model \Validation \Callbacks + # + # Provides an interface for any class to have +before_validation+ and + # +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] & Array(o.validation_context)).empty? + }, + *options[:if] + ] + end + end + end + + private + # Overwrite run validations to include callbacks. + def run_validations! + _run_validation_callbacks { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/clusivity.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/clusivity.rb new file mode 100644 index 0000000000..9f4f4e7923 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/clusivity.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range" + +module ActiveModel + module Validations + module Clusivity #:nodoc: + 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 = if delimiter.respond_to?(:call) + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end + + 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.first + when Numeric, Time, DateTime, Date + :cover? + else + :include? + end + else + :include? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/confirmation.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/confirmation.rb new file mode 100644 index 0000000000..9ef2031be5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") + end.compact) + + klass.attr_writer(*attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end.compact) + 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/exclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/exclusion.rb new file mode 100644 index 0000000000..a2e4dd8652 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/format.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/format.rb new file mode 100644 index 0000000000..92493a2658 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/format.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class FormatValidator < EachValidator # :nodoc: + def validate_each(record, attribute, value) + if options[:with] + regexp = option_call(record, :with) + record_error(record, attribute, :with, value) unless regexp.match?(value.to_s) + elsif options[:without] + regexp = option_call(record, :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 option_call(record, name) + option = options[name] + option.respond_to?(:call) ? option.call(record) : option + end + + 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/helper_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/helper_methods.rb new file mode 100644 index 0000000000..730173f2f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/inclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/inclusion.rb new file mode 100644 index 0000000000..bbd99676c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/length.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/length.rb new file mode 100644 index 0000000000..57db6c6816 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/length.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class LengthValidator < EachValidator # :nodoc: + 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], options[:maximum] = range.min, range.max + 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.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) + case check_value + when Proc + check_value = check_value.call(record) + when Symbol + check_value = record.send(check_value) + end + 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/numericality.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/numericality.rb new file mode 100644 index 0000000000..4f97156f51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/numericality.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Validations + class NumericalityValidator < EachValidator # :nodoc: + CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, + equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, + odd: :odd?, even: :even?, other_than: :!= }.freeze + + RESERVED_OPTIONS = CHECKS.keys + [:only_integer] + + INTEGER_REGEX = /\A[+-]?\d+\z/ + + HEXADECIMAL_REGEX = /\A[+-]?0[xX]/ + + def check_validity! + keys = CHECKS.keys - [:odd, :even] + options.slice(*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 + 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(*CHECKS.keys).each do |option, option_value| + case option + when :odd, :even + unless value.to_i.public_send(CHECKS[option]) + record.errors.add(attr_name, option, **filtered_options(value)) + end + else + case option_value + when Proc + option_value = option_value.call(record) + when Symbol + option_value = record.send(option_value) + end + + option_value = parse_as_number(option_value, precision, scale) + + unless value.public_send(CHECKS[option], option_value) + record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value)) + end + end + end + end + + private + 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) + !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) + case options[:only_integer] + when Symbol + record.send(options[:only_integer]) + when Proc + options[:only_integer].call(record) + else + options[:only_integer] + end + 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, e.g. an integral value (default is +false+). + # * :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. + # * :greater_than_or_equal_to - Specifies the value must be + # greater than or equal the supplied value. + # * :equal_to - Specifies the value must be equal to the supplied + # value. + # * :less_than - Specifies the value must be less than the + # supplied value. + # * :less_than_or_equal_to - Specifies the value must be less + # than or equal the supplied value. + # * :other_than - Specifies the value must be other than the + # supplied value. + # * :odd - Specifies the value must be an odd number. + # * :even - Specifies the value must be an even number. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . + # See ActiveModel::Validations#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/presence.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/presence.rb new file mode 100644 index 0000000000..9ac376c528 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.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?). Happens by default on save. + # + # 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#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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/validates.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/validates.rb new file mode 100644 index 0000000000..20a34c56cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/validates.rb @@ -0,0 +1,175 @@ +# 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]) + # * :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: 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 = key.include?("::") ? key.constantize : 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] + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/with.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/with.rb new file mode 100644 index 0000000000..d777ac836e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validations/with.rb @@ -0,0 +1,147 @@ +# 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 + # + # 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, &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, &block) + validator.validate(self) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validator.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validator.rb new file mode 100644 index 0000000000..a521d79d7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/validator.rb @@ -0,0 +1,188 @@ +# 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 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 + + # +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 #:nodoc: + 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.0.0/gems/activemodel-6.1.7.2/lib/active_model/version.rb b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/version.rb new file mode 100644 index 0000000000..dd817f5639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activemodel-6.1.7.2/lib/active_model/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/CHANGELOG.md new file mode 100644 index 0000000000..6b97b050f7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/CHANGELOG.md @@ -0,0 +1,1591 @@ +## Rails 6.1.4 (June 24, 2021) ## + +* Do not try to rollback transactions that failed due to a `ActiveRecord::TransactionRollbackError`. + + *Jamie McCarthy* + +* Raise an error if `pool_config` is `nil` in `set_pool_config`. + + *Eileen M. Uchitelle* + +* Fix compatibility with `psych >= 4`. + + Starting in Psych 4.0.0 `YAML.load` behaves like `YAML.safe_load`. To preserve compatibility + Active Record's schema cache loader and `YAMLColumn` now uses `YAML.unsafe_load` if available. + + *Jean Boussier* + +* Support using replicas when using `rails dbconsole`. + + *Christopher Thornton* + +* Restore connection pools after transactional tests. + + *Eugene Kenny* + +* Change `upsert_all` to fails cleanly for MySQL when `:unique_by` is used. + + *Bastian Bartmann* + +* Fix user-defined `self.default_scope` to respect table alias. + + *Ryuta Kamizono* + +* Clear `@cache_keys` cache after `update_all`, `delete_all`, `destroy_all`. + + *Ryuta Kamizono* + +* Changed Arel predications `contains` and `overlaps` to use + `quoted_node` so that PostgreSQL arrays are quoted properly. + + *Bradley Priest* + +* Fix `merge` when the `where` clauses have string contents. + + *Ryuta Kamizono* + +* Fix rollback of parent destruction with nested `dependent: :destroy`. + + *Jacopo Beschi* + +* Fix binds logging for `"WHERE ... IN ..."` statements. + + *Ricardo Díaz* + +* Handle `false` in relation strict loading checks. + + Previously when a model had strict loading set to true and then had a + relation set `strict_loading` to false the false wasn't considered when + deciding whether to raise/warn about strict loading. + + ``` + class Dog < ActiveRecord::Base + self.strict_loading_by_default = true + + has_many :treats, strict_loading: false + end + ``` + + In the example, `dog.treats` would still raise even though + `strict_loading` was set to false. This is a bug effecting more than + Active Storage which is why I made this PR superceeding #41461. We need + to fix this for all applications since the behavior is a little + surprising. I took the test from ##41461 and the code suggestion from #41453 + with some additions. + + *Eileen M. Uchitelle*, *Radamés Roriz* + +* Fix numericality validator without precision. + + *Ryuta Kamizono* + +* Fix aggregate attribute on Enum types. + + *Ryuta Kamizono* + +* Fix `CREATE INDEX` statement generation for PostgreSQL. + + *eltongo* + +* Fix where clause on enum attribute when providing array of strings. + + *Ryuta Kamizono* + +* Fix `unprepared_statement` to work it when nesting. + + *Ryuta Kamizono* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* Fix the MySQL adapter to always set the right collation and charset + to the connection session. + + *Rafael Mendonça França* + +* Fix MySQL adapter handling of time objects when prepared statements + are enabled. + + *Rafael Mendonça França* + +* Fix scoping in enum fields using conditions that would generate + an `IN` clause. + + *Ryuta Kamizono* + +* Skip optimised #exist? query when #include? is called on a relation + with a having clause + + Relations that have aliased select values AND a having clause that + references an aliased select value would generate an error when + #include? was called, due to an optimisation that would generate + call #exists? on the relation instead, which effectively alters + the select values of the query (and thus removes the aliased select + values), but leaves the having clause intact. Because the having + clause is then referencing an aliased column that is no longer + present in the simplified query, an ActiveRecord::InvalidStatement + error was raised. + + An sample query affected by this problem: + + ```ruby + Author.select('COUNT(*) as total_posts', 'authors.*') + .joins(:posts) + .group(:id) + .having('total_posts > 2') + .include?(Author.first) + ``` + + This change adds an addition check to the condition that skips the + simplified #exists? query, which simply checks for the presence of + a having clause. + + Fixes #41417 + + *Michael Smart* + +* Increment postgres prepared statement counter before making a prepared statement, so if the statement is aborted + without Rails knowledge (e.g., if app gets kill -9d during long-running query or due to Rack::Timeout), app won't end + up in perpetual crash state for being inconsistent with Postgres. + + *wbharding*, *Martin Tepper* + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* Fix possible DoS vector in PostgreSQL money type + + Carefully crafted input can cause a DoS via the regular expressions used + for validating the money format in the PostgreSQL adapter. This patch + fixes the regexp. + + Thanks to @dee-see from Hackerone for this patch! + + [CVE-2021-22880] + + *Aaron Patterson* + + +## Rails 6.1.2 (February 09, 2021) ## + +* Fix timestamp type for sqlite3. + + *Eileen M. Uchitelle* + +* Make destroy async transactional. + + An active record rollback could occur while enqueuing a job. In this + case the job would enqueue even though the database deletion + rolledback putting things in a funky state. + + Now the jobs are only enqueued until after the db transaction has been committed. + + *Cory Gwin* + +* Fix malformed packet error in MySQL statement for connection configuration. + + *robinroestenburg* + +* Connection specification now passes the "url" key as a configuration for the + adapter if the "url" protocol is "jdbc", "http", or "https". Previously only + urls with the "jdbc" prefix were passed to the Active Record Adapter, others + are assumed to be adapter specification urls. + + Fixes #41137. + + *Jonathan Bracy* + +* Fix granular connection swapping when there are multiple abstract classes. + + *Eileen M. Uchitelle* + +* Fix `find_by` with custom primary key for belongs_to association. + + *Ryuta Kamizono* + +* Add support for `rails console --sandbox` for multiple database applications. + + *alpaca-tc* + +* Fix `where` on polymorphic association with empty array. + + *Ryuta Kamizono* + +* Fix preventing writes for `ApplicationRecord`. + + *Eileen M. Uchitelle* + + +## Rails 6.1.1 (January 07, 2021) ## + +* Fix fixtures loading when strict loading is enabled for the association. + + *Alex Ghiculescu* + +* Fix `where` with custom primary key for belongs_to association. + + *Ryuta Kamizono* + +* Fix `where` with aliased associations. + + *Ryuta Kamizono* + +* Fix `composed_of` with symbol mapping. + + *Ryuta Kamizono* + +* Don't skip money's type cast for pluck and calculations. + + *Ryuta Kamizono* + +* Fix `where` on polymorphic association with non Active Record object. + + *Ryuta Kamizono* + +* Make sure `db:prepare` works even the schema file doesn't exist. + + *Rafael Mendonça França* + +* Fix complicated `has_many :through` with nested where condition. + + *Ryuta Kamizono* + +* Handle STI models for `has_many dependent: :destroy_async`. + + *Muhammad Usman* + +* Restore possibility of passing `false` to :polymorphic option of `belongs_to`. + + Previously, passing `false` would trigger the option validation logic + to throw an error saying :polymorphic would not be a valid option. + + *glaszig* + +* Allow adding nonnamed expression indexes to be revertible. + + Fixes #40732. + + Previously, the following code would raise an error, when executed while rolling back, + and the index name should be specified explicitly. Now, the index name is inferred + automatically. + + ```ruby + add_index(:items, "to_tsvector('english', description)") + ``` + + *fatkodima* + + +## Rails 6.1.0 (December 09, 2020) ## + +* Only warn about negative enums if a positive form that would cause conflicts exists. + + Fixes #39065. + + *Alex Ghiculescu* + +* Change `attribute_for_inspect` to take `filter_attributes` in consideration. + + *Rafael Mendonça França* + +* Fix odd behavior of inverse_of with multiple belongs_to to same class. + + Fixes #35204. + + *Tomoyuki Kai* + +* Build predicate conditions with objects that delegate `#id` and primary key: + + ```ruby + class AdminAuthor + delegate_missing_to :@author + + def initialize(author) + @author = author + end + end + + Post.where(author: AdminAuthor.new(author)) + ``` + + *Sean Doyle* + +* Add `connected_to_many` API. + + This API allows applications to connect to multiple databases at once without switching all of them or implementing a deeply nested stack. + + Before: + + AnimalsRecord.connected_to(role: :reading) do + MealsRecord.connected_to(role: :reading) do + Dog.first # read from animals replica + Dinner.first # read from meals replica + Person.first # read from primary writer + end + end + + After: + + 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 + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add option to raise or log for `ActiveRecord::StrictLoadingViolationError`. + + Some applications may not want to raise an error in production if using `strict_loading`. This would allow an application to set strict loading to log for the production environment while still raising in development and test environments. + + Set `config.active_record.action_on_strict_loading_violation` to `:log` errors instead of raising. + + *Eileen M. Uchitelle* + +* Allow the inverse of a `has_one` association that was previously autosaved to be loaded. + + Fixes #34255. + + *Steven Weber* + +* Optimise the length of index names for polymorphic references by using the reference name rather than the type and id column names. + + Because the default behaviour when adding an index with multiple columns is to use all column names in the index name, this could frequently lead to overly long index names for polymorphic references which would fail the migration if it exceeded the database limit. + + This change reduces the chance of that happening by using the reference name, e.g. `index_my_table_on_my_reference`. + + Fixes #38655. + + *Luke Redpath* + +* MySQL: Uniqueness validator now respects default database collation, + no longer enforce case sensitive comparison by default. + + *Ryuta Kamizono* + +* Remove deprecated methods from `ActiveRecord::ConnectionAdapters::DatabaseLimits`. + + `column_name_length` + `table_name_length` + `columns_per_table` + `indexes_per_table` + `columns_per_multicolumn_index` + `sql_query_length` + `joins_per_query` + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_multi_insert?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_foreign_keys_in_create?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#supports_ranges?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::Base#update_attributes` and `ActiveRecord::Base#update_attributes!`. + + *Rafael Mendonça França* + +* Remove deprecated `migrations_path` argument in `ActiveRecord::ConnectionAdapter::SchemaStatements#assume_migrated_upto_version`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.sqlite3.represent_boolean_as_integer`. + + *Rafael Mendonça França* + +* `relation.create` does no longer leak scope to class level querying methods + in initialization block and callbacks. + + Before: + + User.where(name: "John").create do |john| + User.find_by(name: "David") # => nil + end + + After: + + User.where(name: "John").create do |john| + User.find_by(name: "David") # => # + end + + *Ryuta Kamizono* + +* Named scope chain does no longer leak scope to class level querying methods. + + class User < ActiveRecord::Base + scope :david, -> { User.where(name: "David") } + end + + Before: + + User.where(name: "John").david + # SELECT * FROM users WHERE name = 'John' AND name = 'David' + + After: + + User.where(name: "John").david + # SELECT * FROM users WHERE name = 'David' + + *Ryuta Kamizono* + +* Remove deprecated methods from `ActiveRecord::DatabaseConfigurations`. + + `fetch` + `each` + `first` + `values` + `[]=` + + *Rafael Mendonça França* + +* `where.not` now generates NAND predicates instead of NOR. + + Before: + + User.where.not(name: "Jon", role: "admin") + # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' + + After: + + User.where.not(name: "Jon", role: "admin") + # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin') + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::Result#to_hash` method. + + *Rafael Mendonça França* + +* Deprecate `ActiveRecord::Base.allow_unsafe_raw_sql`. + + *Rafael Mendonça França* + +* Remove deprecated support for using unsafe raw SQL in `ActiveRecord::Relation` methods. + + *Rafael Mendonça França* + +* Allow users to silence the "Rails couldn't infer whether you are using multiple databases..." + message using `config.active_record.suppress_multiple_database_warning`. + + *Omri Gabay* + +* Connections can be granularly switched for abstract classes when `connected_to` is called. + + This change allows `connected_to` to switch a `role` and/or `shard` for a single abstract class instead of all classes globally. Applications that want to use the new feature need to set `config.active_record.legacy_connection_handling` to `false` in their application configuration. + + Example usage: + + Given an application we have a `User` model that inherits from `ApplicationRecord` and a `Dog` model that inherits from `AnimalsRecord`. `AnimalsRecord` and `ApplicationRecord` have writing and reading connections as well as shard `default`, `one`, and `two`. + + ```ruby + ActiveRecord::Base.connected_to(role: :reading) do + User.first # reads from default replica + Dog.first # reads from default replica + + AnimalsRecord.connected_to(role: :writing, shard: :one) do + User.first # reads from default replica + Dog.first # reads from shard one primary + end + + User.first # reads from default replica + Dog.first # reads from default replica + + ApplicationRecord.connected_to(role: :writing, shard: :two) do + User.first # reads from shard two primary + Dog.first # reads from default replica + end + end + ``` + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow double-dash comment syntax when querying read-only databases + + *James Adam* + +* Add `values_at` method. + + Returns an array containing the values associated with the given methods. + + ```ruby + topic = Topic.first + topic.values_at(:title, :author_name) + # => ["Budget", "Jason"] + ``` + + Similar to `Hash#values_at` but on an Active Record instance. + + *Guillaume Briday* + +* Fix `read_attribute_before_type_cast` to consider attribute aliases. + + *Marcelo Lauxen* + +* Support passing record to uniqueness validator `:conditions` callable: + + ```ruby + class Article < ApplicationRecord + 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 + ``` + + *Eliot Sykes* + +* `BatchEnumerator#update_all` and `BatchEnumerator#delete_all` now return the + total number of rows affected, just like their non-batched counterparts. + + ```ruby + Person.in_batches.update_all("first_name = 'Eugene'") # => 42 + Person.in_batches.delete_all # => 42 + ``` + + Fixes #40287. + + *Eugene Kenny* + +* Add support for PostgreSQL `interval` data type with conversion to + `ActiveSupport::Duration` when loading records from database and + serialization to ISO 8601 formatted duration string on save. + Add support to define a column in migrations and get it in a schema dump. + Optional column precision is supported. + + To use this in 6.1, you need to place the next string to your model file: + + attribute :duration, :interval + + To keep old behavior until 6.2 is released: + + attribute :duration, :string + + Example: + + create_table :events do |t| + t.string :name + t.interval :duration + end + + class Event < ApplicationRecord + attribute :duration, :interval + end + + Event.create!(name: 'Rock Fest', duration: 2.days) + Event.last.duration # => 2 days + Event.last.duration.iso8601 # => "P2D" + Event.new(duration: 'P1DT12H3S').duration # => 1 day, 12 hours, and 3 seconds + Event.new(duration: '1 day') # Unknown value will be ignored and NULL will be written to database + + *Andrey Novikov* + +* Allow associations supporting the `dependent:` key to take `dependent: :destroy_async`. + + ```ruby + class Account < ActiveRecord::Base + belongs_to :supplier, dependent: :destroy_async + end + ``` + + `:destroy_async` will enqueue a job to destroy associated records in the background. + + *DHH*, *George Claghorn*, *Cory Gwin*, *Rafael Mendonça França*, *Adrianna Chang* + +* Add `SKIP_TEST_DATABASE` environment variable to disable modifying the test database when `rails db:create` and `rails db:drop` are called. + + *Jason Schweier* + +* `connects_to` can only be called on `ActiveRecord::Base` or abstract classes. + + Ensure that `connects_to` can only be called from `ActiveRecord::Base` or abstract classes. This protects the application from opening duplicate or too many connections. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* All connection adapters `execute` now raises `ActiveRecord::ConnectionNotEstablished` rather than + `ActiveRecord::StatementInvalid` when they encounter a connection error. + + *Jean Boussier* + +* `Mysql2Adapter#quote_string` now raises `ActiveRecord::ConnectionNotEstablished` rather than + `ActiveRecord::StatementInvalid` when it can't connect to the MySQL server. + + *Jean Boussier* + +* Add support for check constraints that are `NOT VALID` via `validate: false` (PostgreSQL-only). + + *Alex Robbin* + +* Ensure the default configuration is considered primary or first for an environment + + If a multiple database application provides a configuration named primary, that will be treated as default. In applications that do not have a primary entry, the default database configuration will be the first configuration for an environment. + + *Eileen M. Uchitelle* + +* Allow `where` references association names as joined table name aliases. + + ```ruby + class Comment < ActiveRecord::Base + enum label: [:default, :child] + has_many :children, class_name: "Comment", foreign_key: :parent_id + end + + # ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1 + Comment.includes(:children).where("children.label": "child") + ``` + + *Ryuta Kamizono* + +* Support storing demodulized class name for polymorphic type. + + Before Rails 6.1, storing demodulized class name is supported only for STI type + by `store_full_sti_class` class attribute. + + Now `store_full_class_name` class attribute can handle both STI and polymorphic types. + + *Ryuta Kamizono* + +* Deprecate `rails db:structure:{load, dump}` tasks and extend + `rails db:schema:{load, dump}` tasks to work with either `:ruby` or `:sql` format, + depending on `config.active_record.schema_format` configuration value. + + *fatkodima* + +* Respect the `select` values for eager loading. + + ```ruby + post = Post.select("UPPER(title) AS title").first + post.title # => "WELCOME TO THE WEBLOG" + post.body # => ActiveModel::MissingAttributeError + + # Rails 6.0 (ignore the `select` values) + post = Post.select("UPPER(title) AS title").eager_load(:comments).first + post.title # => "Welcome to the weblog" + post.body # => "Such a lovely day" + + # Rails 6.1 (respect the `select` values) + post = Post.select("UPPER(title) AS title").eager_load(:comments).first + post.title # => "WELCOME TO THE WEBLOG" + post.body # => ActiveModel::MissingAttributeError + ``` + + *Ryuta Kamizono* + +* Allow attribute's default to be configured but keeping its own type. + + ```ruby + class Post < ActiveRecord::Base + attribute :written_at, default: -> { Time.now.utc } + end + + # Rails 6.0 + Post.type_for_attribute(:written_at) # => # + + # Rails 6.1 + Post.type_for_attribute(:written_at) # => # + ``` + + *Ryuta Kamizono* + +* Allow default to be configured for Enum. + + ```ruby + class Book < ActiveRecord::Base + enum status: [:proposed, :written, :published], _default: :published + end + + Book.new.status # => "published" + ``` + + *Ryuta Kamizono* + +* Deprecate YAML loading from legacy format older than Rails 5.0. + + *Ryuta Kamizono* + +* Added the setting `ActiveRecord::Base.immutable_strings_by_default`, which + allows you to specify that all string columns should be frozen unless + otherwise specified. This will reduce memory pressure for applications which + do not generally mutate string properties of Active Record objects. + + *Sean Griffin*, *Ryuta Kamizono* + +* Deprecate `map!` and `collect!` on `ActiveRecord::Result`. + + *Ryuta Kamizono* + +* Support `relation.and` for intersection as Set theory. + + ```ruby + david_and_mary = Author.where(id: [david, mary]) + mary_and_bob = Author.where(id: [mary, bob]) + + david_and_mary.merge(mary_and_bob) # => [mary, bob] + + david_and_mary.and(mary_and_bob) # => [mary] + david_and_mary.or(mary_and_bob) # => [david, mary, bob] + ``` + + *Ryuta Kamizono* + +* Merging conditions on the same column no longer maintain both conditions, + and will be consistently replaced by the latter condition in Rails 6.2. + To migrate to Rails 6.2's behavior, use `relation.merge(other, rewhere: true)`. + + ```ruby + # Rails 6.1 (IN clause is replaced by merger side equality condition) + Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob] + + # Rails 6.1 (both conflict conditions exists, deprecated) + Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [] + + # Rails 6.1 with rewhere to migrate to Rails 6.2's behavior + Author.where(id: david.id..mary.id).merge(Author.where(id: bob), rewhere: true) # => [bob] + + # Rails 6.2 (same behavior with IN clause, mergee side condition is consistently replaced) + Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob] + Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [bob] + ``` + + *Ryuta Kamizono* + +* Do not mark Postgresql MAC address and UUID attributes as changed when the assigned value only varies by case. + + *Peter Fry* + +* Resolve issue with insert_all unique_by option when used with expression index. + + When the `:unique_by` option of `ActiveRecord::Persistence.insert_all` and + `ActiveRecord::Persistence.upsert_all` was used with the name of an expression index, an error + was raised. Adding a guard around the formatting behavior for the `:unique_by` corrects this. + + Usage: + + ```ruby + create_table :books, id: :integer, force: true do |t| + t.column :name, :string + t.index "lower(name)", unique: true + end + + Book.insert_all [{ name: "MyTest" }], unique_by: :index_books_on_lower_name + ``` + + Fixes #39516. + + *Austen Madden* + +* Add basic support for CHECK constraints to database migrations. + + Usage: + + ```ruby + add_check_constraint :products, "price > 0", name: "price_check" + remove_check_constraint :products, name: "price_check" + ``` + + *fatkodima* + +* Add `ActiveRecord::Base.strict_loading_by_default` and `ActiveRecord::Base.strict_loading_by_default=` + to enable/disable strict_loading mode by default for a model. The configuration's value is + inheritable by subclasses, but they can override that value and it will not impact parent class. + + Usage: + + ```ruby + class Developer < ApplicationRecord + self.strict_loading_by_default = true + + has_many :projects + end + + dev = Developer.first + dev.projects.first + # => ActiveRecord::StrictLoadingViolationError Exception: Developer is marked as strict_loading and Project cannot be lazily loaded. + ``` + + *bogdanvlviv* + +* Deprecate passing an Active Record object to `quote`/`type_cast` directly. + + *Ryuta Kamizono* + +* Default engine `ENGINE=InnoDB` is no longer dumped to make schema more agnostic. + + Before: + + ```ruby + create_table "accounts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| + end + ``` + + After: + + ```ruby + create_table "accounts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + end + ``` + + *Ryuta Kamizono* + +* Added delegated type as an alternative to single-table inheritance for representing class hierarchies. + See ActiveRecord::DelegatedType for the full description. + + *DHH* + +* Deprecate aggregations with group by duplicated fields. + + To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields. + + ```ruby + accounts = Account.group(:firm_id) + + # duplicated group fields, deprecated. + accounts.merge(accounts.where.not(credit_limit: nil)).sum(:credit_limit) + # => { + # [1, 1] => 50, + # [2, 2] => 60 + # } + + # use `uniq!(:group)` to deduplicate group fields. + accounts.merge(accounts.where.not(credit_limit: nil)).uniq!(:group).sum(:credit_limit) + # => { + # 1 => 50, + # 2 => 60 + # } + ``` + + *Ryuta Kamizono* + +* Deprecate duplicated query annotations. + + To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations. + + ```ruby + accounts = Account.where(id: [1, 2]).annotate("david and mary") + + # duplicated annotations, deprecated. + accounts.merge(accounts.rewhere(id: 3)) + # SELECT accounts.* FROM accounts WHERE accounts.id = 3 /* david and mary */ /* david and mary */ + + # use `uniq!(:annotate)` to deduplicate annotations. + accounts.merge(accounts.rewhere(id: 3)).uniq!(:annotate) + # SELECT accounts.* FROM accounts WHERE accounts.id = 3 /* david and mary */ + ``` + + *Ryuta Kamizono* + +* Resolve conflict between counter cache and optimistic locking. + + Bump an Active Record instance's lock version after updating its counter + cache. This avoids raising an unnecessary `ActiveRecord::StaleObjectError` + upon subsequent transactions by maintaining parity with the corresponding + database record's `lock_version` column. + + Fixes #16449. + + *Aaron Lipman* + +* Support merging option `:rewhere` to allow mergee side condition to be replaced exactly. + + ```ruby + david_and_mary = Author.where(id: david.id..mary.id) + + # both conflict conditions exists + david_and_mary.merge(Author.where(id: bob)) # => [] + + # mergee side condition is replaced by rewhere + david_and_mary.merge(Author.rewhere(id: bob)) # => [bob] + + # mergee side condition is replaced by rewhere option + david_and_mary.merge(Author.where(id: bob), rewhere: true) # => [bob] + ``` + + *Ryuta Kamizono* + +* Add support for finding records based on signed ids, which are tamper-proof, verified ids that can be + set to expire and scoped with a purpose. 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. + + ```ruby + 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 + + User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature + ``` + + *DHH* + +* Support `ALGORITHM = INSTANT` DDL option for index operations on MySQL. + + *Ryuta Kamizono* + +* Fix index creation to preserve index comment in bulk change table on MySQL. + + *Ryuta Kamizono* + +* Allow `unscope` to be aware of table name qualified values. + + It is possible to unscope only the column in the specified table. + + ```ruby + posts = Post.joins(:comments).group(:"posts.hidden") + posts = posts.where("posts.hidden": false, "comments.hidden": false) + + posts.count + # => { false => 10 } + + # unscope both hidden columns + posts.unscope(where: :hidden).count + # => { false => 11, true => 1 } + + # unscope only comments.hidden column + posts.unscope(where: :"comments.hidden").count + # => { false => 11 } + ``` + + *Ryuta Kamizono*, *Slava Korolev* + +* Fix `rewhere` to truly overwrite collided where clause by new where clause. + + ```ruby + steve = Person.find_by(name: "Steve") + david = Author.find_by(name: "David") + + relation = Essay.where(writer: steve) + + # Before + relation.rewhere(writer: david).to_a # => [] + + # After + relation.rewhere(writer: david).to_a # => [david] + ``` + + *Ryuta Kamizono* + +* Inspect time attributes with subsec and time zone offset. + + ```ruby + p Knot.create + => # + ``` + + *akinomaeni*, *Jonathan Hefner* + +* Deprecate passing a column to `type_cast`. + + *Ryuta Kamizono* + +* Deprecate `in_clause_length` and `allowed_index_name_length` in `DatabaseLimits`. + + *Ryuta Kamizono* + +* Support bulk insert/upsert on relation to preserve scope values. + + *Josef Šimánek*, *Ryuta Kamizono* + +* Preserve column comment value on changing column name on MySQL. + + *Islam Taha* + +* Add support for `if_exists` option for removing an index. + + The `remove_index` method can take an `if_exists` option. If this is set to true an error won't be raised if the index doesn't exist. + + *Eileen M. Uchitelle* + +* Remove ibm_db, informix, mssql, oracle, and oracle12 Arel visitors which are not used in the code base. + + *Ryuta Kamizono* + +* Prevent `build_association` from `touching` a parent record if the record isn't persisted for `has_one` associations. + + Fixes #38219. + + *Josh Brody* + +* Add support for `if_not_exists` option for adding index. + + The `add_index` method respects `if_not_exists` option. If it is set to true + index won't be added. + + Usage: + + ```ruby + add_index :users, :account_id, if_not_exists: true + ``` + + The `if_not_exists` option passed to `create_table` also gets propagated to indexes + created within that migration so that if table and its indexes exist then there is no + attempt to create them again. + + *Prathamesh Sonpatki* + +* Add `ActiveRecord::Base#previously_new_record?` to show if a record was new before the last save. + + *Tom Ward* + +* Support descending order for `find_each`, `find_in_batches`, and `in_batches`. + + Batch processing methods allow you to work with the records in batches, greatly reducing memory consumption, but records are always batched from oldest id to newest. + + This change allows reversing the order, batching from newest to oldest. This is useful when you need to process newer batches of records first. + + Pass `order: :desc` to yield batches in descending order. The default remains `order: :asc`. + + ```ruby + Person.find_each(order: :desc) do |person| + person.party_all_night! + end + ``` + + *Alexey Vasiliev* + +* Fix `insert_all` with enum values. + + Fixes #38716. + + *Joel Blum* + +* Add support for `db:rollback:name` for multiple database applications. + + Multiple database applications will now raise if `db:rollback` is call and recommend using the `db:rollback:[NAME]` to rollback migrations. + + *Eileen M. Uchitelle* + +* `Relation#pick` now uses already loaded results instead of making another query. + + *Eugene Kenny* + +* Deprecate using `return`, `break` or `throw` to exit a transaction block after writes. + + *Dylan Thacker-Smith* + +* Dump the schema or structure of a database when calling `db:migrate:name`. + + In previous versions of Rails, `rails db:migrate` would dump the schema of the database. In Rails 6, that holds true (`rails db:migrate` dumps all databases' schemas), but `rails db:migrate:name` does not share that behavior. + + Going forward, calls to `rails db:migrate:name` will dump the schema (or structure) of the database being migrated. + + *Kyle Thompson* + +* Reset the `ActiveRecord::Base` connection after `rails db:migrate:name`. + + When `rails db:migrate` has finished, it ensures the `ActiveRecord::Base` connection is reset to its original configuration. Going forward, `rails db:migrate:name` will have the same behavior. + + *Kyle Thompson* + +* Disallow calling `connected_to` on subclasses of `ActiveRecord::Base`. + + Behavior has not changed here but the previous API could be misleading to people who thought it would switch connections for only that class. `connected_to` switches the context from which we are getting connections, not the connections themselves. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add support for horizontal sharding to `connects_to` and `connected_to`. + + Applications can now connect to multiple shards and switch between their shards in an application. Note that the shard swapping is still a manual process as this change does not include an API for automatic shard swapping. + + Usage: + + Given the following configuration: + + ```yaml + # config/database.yml + production: + primary: + database: my_database + primary_shard_one: + database: my_database_shard_one + ``` + + Connect to multiple shards: + + ```ruby + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + + connects_to shards: { + default: { writing: :primary }, + shard_one: { writing: :primary_shard_one } + } + ``` + + Swap between shards in your controller / model code: + + ```ruby + ActiveRecord::Base.connected_to(shard: :shard_one) do + # Read from shard one + end + ``` + + The horizontal sharding API also supports read replicas. See guides for more details. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Deprecate `spec_name` in favor of `name` on database configurations. + + The accessors for `spec_name` on `configs_for` and `DatabaseConfig` are deprecated. Please use `name` instead. + + Deprecated behavior: + + ```ruby + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary") + db_config.spec_name + ``` + + New behavior: + + ```ruby + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", name: "primary") + db_config.name + ``` + + *Eileen M. Uchitelle* + +* Add additional database-specific rake tasks for multi-database users. + + Previously, `rails db:create`, `rails db:drop`, and `rails db:migrate` were the only rails tasks that could operate on a single + database. For example: + + ``` + rails db:create + rails db:create:primary + rails db:create:animals + rails db:drop + rails db:drop:primary + rails db:drop:animals + rails db:migrate + rails db:migrate:primary + rails db:migrate:animals + ``` + + With these changes, `rails db:schema:dump`, `rails db:schema:load`, `rails db:structure:dump`, `rails db:structure:load` and + `rails db:test:prepare` can additionally operate on a single database. For example: + + ``` + rails db:schema:dump + rails db:schema:dump:primary + rails db:schema:dump:animals + rails db:schema:load + rails db:schema:load:primary + rails db:schema:load:animals + rails db:structure:dump + rails db:structure:dump:primary + rails db:structure:dump:animals + rails db:structure:load + rails db:structure:load:primary + rails db:structure:load:animals + rails db:test:prepare + rails db:test:prepare:primary + rails db:test:prepare:animals + ``` + + *Kyle Thompson* + +* Add support for `strict_loading` mode on association declarations. + + Raise an error if attempting to load a record from an association that has been marked as `strict_loading` unless it was explicitly eager loaded. + + Usage: + + ```ruby + class Developer < ApplicationRecord + has_many :projects, strict_loading: true + end + + dev = Developer.first + dev.projects.first + # => ActiveRecord::StrictLoadingViolationError: The projects association is marked as strict_loading and cannot be lazily loaded. + ``` + + *Kevin Deisz* + +* Add support for `strict_loading` mode to prevent lazy loading of records. + + Raise an error if a parent record is marked as `strict_loading` and attempts to lazily load its associations. This is useful for finding places you may want to preload an association and avoid additional queries. + + Usage: + + ```ruby + dev = Developer.strict_loading.first + dev.audit_logs.to_a + # => ActiveRecord::StrictLoadingViolationError: Developer is marked as strict_loading and AuditLog cannot be lazily loaded. + ``` + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Add support for PostgreSQL 11+ partitioned indexes when using `upsert_all`. + + *Sebastián Palma* + +* Adds support for `if_not_exists` to `add_column` and `if_exists` to `remove_column`. + + Applications can set their migrations to ignore exceptions raised when adding a column that already exists or when removing a column that does not exist. + + Example Usage: + + ```ruby + class AddColumnTitle < ActiveRecord::Migration[6.1] + def change + add_column :posts, :title, :string, if_not_exists: true + end + end + ``` + + ```ruby + class RemoveColumnTitle < ActiveRecord::Migration[6.1] + def change + remove_column :posts, :title, if_exists: true + end + end + ``` + + *Eileen M. Uchitelle* + +* Regexp-escape table name for MS SQL Server. + + Add `Regexp.escape` to one method in ActiveRecord, so that table names with regular expression characters in them work as expected. Since MS SQL Server uses "[" and "]" to quote table and column names, and those characters are regular expression characters, methods like `pluck` and `select` fail in certain cases when used with the MS SQL Server adapter. + + *Larry Reid* + +* Store advisory locks on their own named connection. + + Previously advisory locks were taken out against a connection when a migration started. This works fine in single database applications but doesn't work well when migrations need to open new connections which results in the lock getting dropped. + + In order to fix this we are storing the advisory lock on a new connection with the connection specification name `AdvisoryLockBase`. The caveat is that we need to maintain at least 2 connections to a database while migrations are running in order to do this. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow schema cache path to be defined in the database configuration file. + + For example: + + ```yaml + development: + adapter: postgresql + database: blog_development + pool: 5 + schema_cache_path: tmp/schema/main.yml + ``` + + *Katrina Owen* + +* Deprecate `#remove_connection` in favor of `#remove_connection_pool` when called on the handler. + + `#remove_connection` is deprecated in order to support returning a `DatabaseConfig` object instead of a `Hash`. Use `#remove_connection_pool`, `#remove_connection` will be removed in 6.2. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Deprecate `#default_hash` and it's alias `#[]` on database configurations. + + Applications should use `configs_for`. `#default_hash` and `#[]` will be removed in 6.2. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add scale support to `ActiveRecord::Validations::NumericalityValidator`. + + *Gannon McGibbon* + +* Find orphans by looking for missing relations through chaining `where.missing`: + + Before: + + ```ruby + Post.left_joins(:author).where(authors: { id: nil }) + ``` + + After: + + ```ruby + Post.where.missing(:author) + ``` + + *Tom Rossi* + +* Ensure `:reading` connections always raise if a write is attempted. + + Now Rails will raise an `ActiveRecord::ReadOnlyError` if any connection on the reading handler attempts to make a write. If your reading role needs to write you should name the role something other than `:reading`. + + *Eileen M. Uchitelle* + +* Deprecate `"primary"` as the `connection_specification_name` for `ActiveRecord::Base`. + + `"primary"` has been deprecated as the `connection_specification_name` for `ActiveRecord::Base` in favor of using `"ActiveRecord::Base"`. This change affects calls to `ActiveRecord::Base.connection_handler.retrieve_connection` and `ActiveRecord::Base.connection_handler.remove_connection`. If you're calling these methods with `"primary"`, please switch to `"ActiveRecord::Base"`. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add `ActiveRecord::Validations::NumericalityValidator` with + support for casting floats using a database columns' precision value. + + *Gannon McGibbon* + +* Enforce fresh ETag header after a collection's contents change by adding + ActiveRecord::Relation#cache_key_with_version. This method will be used by + ActionController::ConditionalGet to ensure that when collection cache versioning + is enabled, requests using ConditionalGet don't return the same ETag header + after a collection is modified. + + Fixes #38078. + + *Aaron Lipman* + +* Skip test database when running `db:create` or `db:drop` in development + with `DATABASE_URL` set. + + *Brian Buchalter* + +* Don't allow mutations on the database configurations hash. + + Freeze the configurations hash to disallow directly changing it. If applications need to change the hash, for example to create databases for parallelization, they should use the `DatabaseConfig` object directly. + + Before: + + ```ruby + @db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: "primary") + @db_config.configuration_hash.merge!(idle_timeout: "0.02") + ``` + + After: + + ```ruby + @db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: "primary") + config = @db_config.configuration_hash.merge(idle_timeout: "0.02") + db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(@db_config.env_name, @db_config.spec_name, config) + ``` + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Remove `:connection_id` from the `sql.active_record` notification. + + *Aaron Patterson*, *Rafael Mendonça França* + +* The `:name` key will no longer be returned as part of `DatabaseConfig#configuration_hash`. Please use `DatabaseConfig#owner_name` instead. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* ActiveRecord's `belongs_to_required_by_default` flag can now be set per model. + + You can now opt-out/opt-in specific models from having their associations required + by default. + + This change is meant to ease the process of migrating all your models to have + their association required. + + *Edouard Chin* + +* The `connection_config` method has been deprecated, please use `connection_db_config` instead which will return a `DatabaseConfigurations::DatabaseConfig` instead of a `Hash`. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Retain explicit selections on the base model after applying `includes` and `joins`. + + Resolves #34889. + + *Patrick Rebsch* + +* The `database` kwarg is deprecated without replacement because it can't be used for sharding and creates an issue if it's used during a request. Applications that need to create new connections should use `connects_to` instead. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow attributes to be fetched from Arel node groupings. + + *Jeff Emminger*, *Gannon McGibbon* + +* A database URL can now contain a querystring value that contains an equal sign. This is needed to support passing PostgreSQL `options`. + + *Joshua Flanagan* + +* Calling methods like `establish_connection` with a `Hash` which is invalid (eg: no `adapter`) will now raise an error the same way as connections defined in `config/database.yml`. + + *John Crepezzi* + +* Specifying `implicit_order_column` now subsorts the records by primary key if available to ensure deterministic results. + + *Paweł Urbanek* + +* `where(attr => [])` now loads an empty result without making a query. + + *John Hawthorn* + +* Fixed the performance regression for `primary_keys` introduced MySQL 8.0. + + *Hiroyuki Ishii* + +* Add support for `belongs_to` to `has_many` inversing. + + *Gannon McGibbon* + +* Allow length configuration for `has_secure_token` method. The minimum length + is set at 24 characters. + + Before: + + ```ruby + has_secure_token :auth_token + ``` + + After: + + ```ruby + has_secure_token :default_token # 24 characters + has_secure_token :auth_token, length: 36 # 36 characters + has_secure_token :invalid_token, length: 12 # => ActiveRecord::SecureToken::MinimumLengthError + ``` + + *Bernardo de Araujo* + +* Deprecate `DatabaseConfigurations#to_h`. These connection hashes are still available via `ActiveRecord::Base.configurations.configs_for`. + + *Eileen Uchitelle*, *John Crepezzi* + +* Add `DatabaseConfig#configuration_hash` to return database configuration hashes with symbol keys, and use all symbol-key configuration hashes internally. Deprecate `DatabaseConfig#config` which returns a String-keyed `Hash` with the same values. + + *John Crepezzi*, *Eileen Uchitelle* + +* Allow column names to be passed to `remove_index` positionally along with other options. + + Passing other options can be necessary to make `remove_index` correctly reversible. + + Before: + + add_index :reports, :report_id # => works + add_index :reports, :report_id, unique: true # => works + remove_index :reports, :report_id # => works + remove_index :reports, :report_id, unique: true # => ArgumentError + + After: + + remove_index :reports, :report_id, unique: true # => works + + *Eugene Kenny* + +* Allow bulk `ALTER` statements to drop and recreate indexes with the same name. + + *Eugene Kenny* + +* `insert`, `insert_all`, `upsert`, and `upsert_all` now clear the query cache. + + *Eugene Kenny* + +* Call `while_preventing_writes` directly from `connected_to`. + + In some cases application authors want to use the database switching middleware and make explicit calls with `connected_to`. It's possible for an app to turn off writes and not turn them back on by the time we call `connected_to(role: :writing)`. + + This change allows apps to fix this by assuming if a role is writing we want to allow writes, except in the case it's explicitly turned off. + + *Eileen M. Uchitelle* + +* Improve detection of ActiveRecord::StatementTimeout with mysql2 adapter in the edge case when the query is terminated during filesort. + + *Kir Shatrov* + +* Stop trying to read yaml file fixtures when loading Active Record fixtures. + + *Gannon McGibbon* + +* Deprecate `.reorder(nil)` with `.first` / `.first!` taking non-deterministic result. + + To continue taking non-deterministic result, use `.take` / `.take!` instead. + + *Ryuta Kamizono* + +* Preserve user supplied joins order as much as possible. + + Fixes #36761, #34328, #24281, #12953. + + *Ryuta Kamizono* + +* Allow `matches_regex` and `does_not_match_regexp` on the MySQL Arel visitor. + + *James Pearson* + +* Allow specifying fixtures to be ignored by setting `ignore` in YAML file's '_fixture' section. + + *Tongfei Gao* + +* Make the DATABASE_URL env variable only affect the primary connection. Add new env variables for multiple databases. + + *John Crepezzi*, *Eileen Uchitelle* + +* Add a warning for enum elements with 'not_' prefix. + + class Foo + enum status: [:sent, :not_sent] + end + + *Edu Depetris* + +* Make currency symbols optional for money column type in PostgreSQL. + + *Joel Schneider* + +* Add support for beginless ranges, introduced in Ruby 2.7. + + *Josh Goodall* + +* Add `database_exists?` method to connection adapters to check if a database exists. + + *Guilherme Mansur* + +* Loading the schema for a model that has no `table_name` raises a `TableNotSpecified` error. + + *Guilherme Mansur*, *Eugene Kenny* + +* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute. + + Fixes #36022. + + *Ryuta Kamizono* + +* Make ActiveRecord `ConnectionPool.connections` method thread-safe. + + Fixes #36465. + + *Jeff Doering* + +* Add support for multiple databases to `rails db:abort_if_pending_migrations`. + + *Mark Lee* + +* Fix sqlite3 collation parsing when using decimal columns. + + *Martin R. Schuster* + +* Fix invalid schema when primary key column has a comment. + + Fixes #29966. + + *Guilherme Goettems Schneider* + +* Fix table comment also being applied to the primary key column. + + *Guilherme Goettems Schneider* + +* Allow generated `create_table` migrations to include or skip timestamps. + + *Michael Duchemin* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/MIT-LICENSE new file mode 100644 index 0000000000..d2cc61097d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/MIT-LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2004-2020 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.0.0/gems/activerecord-6.1.4/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/README.rdoc new file mode 100644 index 0000000000..306982d17b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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://edgeguides.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 + + {Learn more}[link:classes/ActiveRecord/Base.html] + +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). + + +* Associations between objects defined by simple class methods. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomerate + end + + {Learn more}[link: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}[link: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}[link: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}[link: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}[link:classes/ActiveRecord/Base.html] + + +* Transactions. + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link: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}[link: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}[link:classes/ActiveRecord/Base.html] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html], + PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and + SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. + + +* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-doc.org/stdlib/libdoc/logger/rdoc/]. + + 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[6.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}[link: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.0.0/gems/activerecord-6.1.4/examples/performance.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/examples/performance.rb new file mode 100644 index 0000000000..024e503ec7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.connection.send(:log, "hello", "world") { } + end + + x.report "AR.execute(query)" do + ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/examples/simple.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/examples/simple.rb new file mode 100644 index 0000000000..280b786d73 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record.rb new file mode 100644 index 0000000000..3ab2394595 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2020 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" +require "arel" +require "yaml" + +require "active_record/version" +require "active_model/attribute_set" +require "active_record/errors" + +module ActiveRecord + extend ActiveSupport::Autoload + + autoload :Base + autoload :Callbacks + autoload :Core + autoload :ConnectionHandling + autoload :CounterCache + autoload :DynamicMatchers + autoload :DelegatedType + autoload :Enum + autoload :InternalMetadata + autoload :Explain + autoload :Inheritance + autoload :Integration + autoload :Migration + autoload :Migrator, "active_record/migration" + autoload :ModelSchema + autoload :NestedAttributes + autoload :NoTouching + autoload :TouchLater + autoload :Persistence + autoload :QueryCache + autoload :Querying + autoload :ReadonlyAttributes + autoload :RecordInvalid, "active_record/validations" + autoload :Reflection + autoload :RuntimeRegistry + autoload :Sanitization + autoload :Schema + autoload :SchemaDumper + autoload :SchemaMigration + autoload :Scoping + autoload :Serialization + autoload :StatementCache + autoload :Store + autoload :SignedId + autoload :Suppressor + autoload :Timestamp + autoload :Transactions + autoload :Translation + autoload :Validations + autoload :SecureToken + autoload :DestroyAssociationAsyncJob + + eager_autoload do + autoload :ConnectionAdapters + + autoload :Aggregations + autoload :Associations + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :AutosaveAssociation + + autoload :LegacyYamlAdapter + + autoload :Relation + autoload :AssociationRelation + autoload :NullRelation + + autoload_under "relation" do + autoload :QueryMethods + autoload :FinderMethods + autoload :Calculations + autoload :PredicateBuilder + autoload :SpawnMethods + autoload :Batches + autoload :Delegation + end + + autoload :Result + autoload :TableMetadata + autoload :Type + end + + module Coders + autoload :YAMLColumn, "active_record/coders/yaml_column" + autoload :JSON, "active_record/coders/json" + end + + module AttributeMethods + extend ActiveSupport::Autoload + + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write + autoload :Serialization + 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 :Named + autoload :Default + end + end + + module Middleware + extend ActiveSupport::Autoload + + autoload :DatabaseSelector, "active_record/middleware/database_selector" + end + + module Tasks + extend ActiveSupport::Autoload + + autoload :DatabaseTasks + autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks" + autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks" + autoload :PostgreSQLDatabaseTasks, + "active_record/tasks/postgresql_database_tasks" + end + + autoload :TestDatabases, "active_record/test_databases" + autoload :TestFixtures, "active_record/fixtures" + + 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! + 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.0.0/gems/activerecord-6.1.4/lib/active_record/aggregations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/aggregations.rb new file mode 100644 index 0000000000..0379b8d6dc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/aggregations.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Aggregations::ClassMethods for documentation + module Aggregations + def initialize_dup(*) # :nodoc: + @aggregation_cache = {} + super + end + + def reload(*) # :nodoc: + clear_aggregation_cache + super + end + + private + def clear_aggregation_cache + @aggregation_cache.clear if persisted? + end + + def init_internals + @aggregation_cache = {} + super + end + + # 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: %w(balance amount) + # composed_of :address, mapping: [ %w(address_street street), %w(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: [ %w(network_address network), %w(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 an array where the first item is the name of the + # entity attribute and the second item 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. + # * :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: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) + # 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: %w(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 + 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.each_key.all? { |k| k.is_a?(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.freeze + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/association_relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/association_relation.rb new file mode 100644 index 0000000000..41571857b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/association_relation.rb @@ -0,0 +1,60 @@ +# 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 + 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 + + scoping { klass.#{method}(attributes, **kwargs) } + end + RUBY + end + + def build(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, &block) } + else + block = current_scope_restoring_block(&block) + scoping { _new(attributes, &block) } + end + end + alias new build + + 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) + yield record if block_given? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations.rb new file mode 100644 index 0000000000..b7545b6e20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations.rb @@ -0,0 +1,1972 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/conversions" + +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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.association_name + maybe_these = @error.record.class.reflections.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.association_name.to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.reflection && @error.associated_class + maybe_these = @error.associated_class.reflections.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.reflection && @error.owner_class + maybe_these = @error.owner_class.reflections.keys + maybe_these -= [@error.reflection.name.to_s] # remove failing reflection + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 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 + + # 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 :AliasTracker + end + + def self.eager_load! + super + Preloader.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 + + def reload(*) # :nodoc: + clear_association_cache + super + end + + private + # Clears out the association cache. + def clear_association_cache + @association_cache.clear if persisted? + end + + def init_internals + @association_cache = {} + super + 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 + + # \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#portfolio, Project#portfolio=(portfolio), Project#reload_portfolio + # * Project#project_manager, Project#project_manager=(project_manager), Project#reload_project_manager + # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), + # Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id), + # Project#milestones.build, Project#milestones.create + # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), + # 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 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 + # + # === 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, eager loading and preloading of these associations is not possible. + # These operations happen before instance creation and the scope will be called with a +nil+ argument. + # + # == 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 Project + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity + # + # def evaluate_velocity(developer) + # ... + # end + # end + # + # It's possible to stack callbacks by passing them as an array. Example: + # + # class Project + # has_and_belongs_to_many :developers, + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # 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. + # + # == 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, + # or a custom scope, will also prevent the association's inverse + # from being found automatically. + # + # 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 fallback + # 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. + # + # == 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 + # + # A Firm class declares has_many :clients, which will add: + # * Firm#clients (similar to Client.where(firm_id: id)) + # * Firm#clients<< + # * Firm#clients.delete + # * Firm#clients.destroy + # * Firm#clients= + # * Firm#client_ids + # * Firm#client_ids= + # * Firm#clients.clear + # * Firm#clients.empty? (similar to firm.clients.size == 0) + # * Firm#clients.size (similar to Client.count "firm_id = #{id}") + # * Firm#clients.find (similar to Client.where(firm_id: id).find(id)) + # * Firm#clients.exists?(name: 'ACME') (similar to Client.exists?(name: 'ACME', firm_id: firm.id)) + # * Firm#clients.build (similar to Client.new(firm_id: id)) + # * Firm#clients.create (similar to c = Client.new(firm_id: id); c.save; c) + # * Firm#clients.create! (similar to c = Client.new(firm_id: id); c.save!) + # * 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. + # + # 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. + # [: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 the 'Association Join Models' + # section above.) + # [: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 ActiveRecord::Associations::ClassMethods's overview on 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] + # 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. + # + # 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 :comments, strict_loading: true + 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 also ActiveRecord::Associations::ClassMethods's overview + # 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. + # + # === Example + # + # An Account class declares has_one :beneficiary, which will add: + # * Account#beneficiary (similar to Beneficiary.where(account_id: id).first) + # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) + # * Account#build_beneficiary (similar to Beneficiary.new(account_id: id)) + # * Account#create_beneficiary (similar to b = Beneficiary.new(account_id: id); b.save; b) + # * Account#create_beneficiary! (similar to b = Beneficiary.new(account_id: id); b.save!; b) + # * Account#reload_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. + # + # 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. + # [: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 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. + # [: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 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. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. + # See ActiveRecord::Associations::ClassMethods's overview on 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. + # + # 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 :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + # has_one :credit_card, strict_loading: true + 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 also ActiveRecord::Associations::ClassMethods's overview + # 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. + # + # === Example + # + # A Post class declares belongs_to :author, which will add: + # * Post#author (similar to Author.find(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 + # 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 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 + # + # [: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". + # + # 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. + # [: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, see note below). 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.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. + # [:polymorphic] + # Specify this association is a polymorphic association by passing +true+. + # 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). + # [: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/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/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are 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 ActiveRecord::Associations::ClassMethods's overview on 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. + # [: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. + # + # 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 + 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[6.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 + # + # A Developer class declares has_and_belongs_to_many :projects, which will add: + # * Developer#projects + # * Developer#projects<< + # * Developer#projects.delete + # * Developer#projects.destroy + # * Developer#projects= + # * Developer#project_ids + # * Developer#project_ids= + # * Developer#projects.clear + # * Developer#projects.empty? + # * Developer#projects.size + # * Developer#projects.find(id) + # * Developer#projects.exists?(...) + # * Developer#projects.build (similar to Project.new(developer_id: id)) + # * Developer#projects.create (similar to c = Project.new(developer_id: id); c.save; c) + # * 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. + # + # 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. + # [: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.to_s].parent_reflection = habtm_reflection + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/alias_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/alias_tracker.rb new file mode 100644 index 0000000000..27cb2f62f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/alias_tracker.rb @@ -0,0 +1,84 @@ +# 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(connection, initial_table, joins, aliases = nil) + 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, aliases) + end + + def self.initial_count_for(connection, name, table_joins) + quoted_name = nil + + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name ||= connection.quote_table_name(name) + + # Table names + table aliases + join.left.scan( + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\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(connection, aliases) + @aliases = aliases + @connection = connection + 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 = @connection.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 truncate(name) + name.slice(0, @connection.table_alias_length - 2) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association.rb new file mode 100644 index 0000000000..0b731e5d29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association.rb @@ -0,0 +1,358 @@ +# 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_reader :owner, :target, :reflection + + delegate :options, to: :reflection + + def initialize(owner, reflection) + reflection.check_validity! + + @owner, @reflection = owner, reflection + + reset + reset_scope + end + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + @loaded = false + @target = nil + @stale_state = nil + @inversed = false + 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.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 + @inversed = false + 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? + !@inversed && 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 (scope = klass.current_scope) && scope.try(:proxy_association) == self + scope.spawn + else + target_scope.merge!(association_scope) + end + end + + def reset_scope + @association_scope = nil + 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 + @inversed = !!record + end + + def inversed_from_queries(record) + if inversable?(record) + self.target = record + @inversed = true + else + @inversed = false + 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 if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + target + rescue ActiveRecord::RecordNotFound + reset + 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 + + private + def find_target + if strict_loading? && owner.validation_context.nil? + Base.strict_loading_violation!(owner: owner.class, reflection: reflection) + end + + scope = self.scope + return scope.to_a if skip_statement_cache?(scope) + + 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) + sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) } + end + + def strict_loading? + return reflection.strict_loading? if reflection.options.key?(:strict_loading) + + owner.strict_loading? + 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 ||= AssociationScope.scope(self) + 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 sanity 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) + record._has_attribute?(reflection.foreign_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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association_scope.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association_scope.rb new file mode 100644 index 0000000000..c5e394d7af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/association_scope.rb @@ -0,0 +1,170 @@ +# 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 << 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 = reflection.join_primary_key + foreign_key = reflection.join_foreign_key + + table = reflection.aliased_table + value = transform_value(owner[foreign_key]) + scope = apply_scope(scope, table, primary_key, value) + + 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 = reflection.join_primary_key + foreign_key = reflection.join_foreign_key + + table = reflection.aliased_table + foreign_table = next_reflection.aliased_table + constraint = table[primary_key].eq(foreign_table[foreign_key]) + + 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, constraint)) + 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| + # Exclude the scope of the association itself, because that + # was already merged in the #scope method. + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_association.rb new file mode 100644 index 0000000000..2ed6a700b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_association.rb @@ -0,0 +1,137 @@ +# 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 + id = owner.public_send(reflection.foreign_key.to_sym) + primary_key_column = reflection.active_record_primary_key.to_sym + + 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) + ) + 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_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize + 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.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 + 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) + target_key = record ? record._read_attribute(primary_key(record.class)) : nil + + if force || owner[reflection.foreign_key] != target_key + owner[reflection.foreign_key] = target_key + end + end + + def primary_key(klass) + reflection.association_primary_key(klass) + end + + def foreign_key_present? + owner._read_attribute(reflection.foreign_key) + end + + def invertible_for?(record) + inverse = inverse_reflection_for(record) + inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing) + end + + def stale_state + result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) } + result && result.to_s + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_polymorphic_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_polymorphic_association.rb new file mode 100644 index 0000000000..a0d5522fa0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -0,0 +1,41 @@ +# 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.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[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 + foreign_key = super + foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/association.rb new file mode 100644 index 0000000000..b7f45ed5ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/association.rb @@ -0,0 +1,163 @@ +# 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 + ].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 + 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) + 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.valid_dependent_options + raise NotImplementedError + end + + def self.check_dependent_options(dependent, model) + if dependent == :destroy_async && !model.destroy_association_async_job + err_message = "ActiveJob is required to use destroy_async on associations" + raise ActiveRecord::ActiveJobRequiredError, 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, + :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/belongs_to.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/belongs_to.rb new file mode 100644 index 0000000000..584af2c3f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/belongs_to.rb @@ -0,0 +1,133 @@ +# 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.target_changed? + association.increment_counters + association.decrement_counters_before_last_save + end + } + + klass = reflection.class_name.safe_constantize + klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) + end + + def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :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 = klass.constantize + 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.public_send(touch_method, touch) + else + old_record.public_send(touch_method) + end + end + end + + record = o.public_send name + if record && record.persisted? + if touch != true + record.public_send(touch_method, touch) + else + record.public_send(touch_method) + 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, belongs_to_touch_method) + }} + + if reflection.counter_cache_column + touch_callback = callback.(:saved_changes) + update_callback = lambda { |record| + instance_exec(record, &touch_callback) unless association(reflection.name).target_changed? + } + 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 + model.validates_presence_of reflection.name, message: :required + end + end + + private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations, + :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/collection_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/collection_association.rb new file mode 100644 index 0000000000..0c0613d95f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/collection_association.rb @@ -0,0 +1,73 @@ +# 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}" + + unless model.method_defined?(full_callback_name) + model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false) + end + + callbacks = Array(options[callback_name.to_sym]).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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_and_belongs_to_many.rb new file mode 100644 index 0000000000..170bdd7907 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -0,0 +1,111 @@ +# 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 + + 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.retrieve_connection + left_model.retrieve_connection + end + + private + def self.suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_many.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_many.rb new file mode 100644 index 0000000000..b21dd943aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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, :ensuring_owner_was] + valid += [:as, :foreign_type] if options[:as] + valid += [:through, :source, :source_type] 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_one.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/has_one.rb new file mode 100644 index 0000000000..1773faa01b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + valid += [:as, :foreign_type] if options[:as] + valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async + valid += [:through, :source, :source_type] 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/singular_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/singular_association.rb new file mode 100644 index 0000000000..7537aa468f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/builder/singular_association.rb @@ -0,0 +1,44 @@ +# 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) if reflection.constructable? + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def reload_#{name} + association(:#{name}).force_reload_reader + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_association.rb new file mode 100644 index 0000000000..599d663f97 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_association.rb @@ -0,0 +1,515 @@ +# frozen_string_literal: true + +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: + # Implements the reader method, e.g. foo.items for Foo.has_many :items + def reader + 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! { |i| pk_type.cast(i) } + + records = klass.where(primary_key => ids).index_by do |r| + r.public_send(primary_key) + end.values_at(*ids).compact + + if records.size != ids.size + found_ids = records.map { |record| record.public_send(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 = [] + @association_ids = nil + end + + def find(*args) + if options[:inverse_of] && loaded? + args_flatten = args.flatten + model = scope.klass + + 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? + load_target + concat_records(records) + else + transaction { concat_records(records) } + end + end + + # Starts a transaction in the association class's database connection. + # + # class Author < ActiveRecord::Base + # has_many :books + # end + # + # Author.first.books.transaction do + # # same effect as calling Book.transaction + # end + def transaction(*args) + reflection.klass.transaction(*args) do + yield + 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_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 = 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) + if record.is_a?(reflection.klass) + if record.new_record? + include_in_memory?(record) + else + loaded? ? target.include?(record) : scope.exists?(record.id) + end + else + false + 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) + if replace || association_scope.distinct_value + index = @target.index(record) + end + replace_on_target(record, index, skip_callbacks, &block) + end + + def target=(record) + return super unless ActiveRecord::Base.has_many_inversing + + case record + when Array + super + else + add_to_target(record, skip_callbacks: true, replace: 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? || + reflection.strict_loading? || + owner.new_record? || + target.any? { |record| record.new_record? || record.changed? } + end + + private + # 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? + return memory if persisted.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).each do |name| + mem_record[name] = record[name] + end + + mem_record + else + record + end + end + + persisted + memory + end + + def _create_record(attributes, raise = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + 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, @target.index(record), skip_callbacks) + 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, index, skip_callbacks) + 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 + 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}" + owner.class.send(full_callback_name) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_proxy.rb new file mode 100644 index 0000000000..323a95d8cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/collection_proxy.rb @@ -0,0 +1,1135 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # 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 + # 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 + # + # *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::Base.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 #first except returns only the third-to-last record. + + ## + # :method: second_to_last + # + # :call-seq: + # second_to_last() + # + # Same as #first 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::Base.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.length.zero?. + # + # 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 + # + # 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 + + def proxy_association # :nodoc: + @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 + # + # other = [Pet.new(id: 1), Pet.new(id: 2)] + # + # person.pets == other + # # => false + 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 + + 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 + ] + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/foreign_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/foreign_association.rb new file mode 100644 index 0000000000..6c9d28cfed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/foreign_association.rb @@ -0,0 +1,33 @@ +# 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| + attrs[reflection.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] + + key = owner._read_attribute(reflection.join_foreign_key) + record._write_attribute(reflection.join_primary_key, key) + + if reflection.type + record._write_attribute(reflection.type, owner.class.polymorphic_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_association.rb new file mode 100644 index 0000000000..2553fd0ef1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_association.rb @@ -0,0 +1,157 @@ +# 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 + primary_key_column = association_class.primary_key.to_sym + + ids = target.collect do |assoc| + assoc.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: ids, + association_primary_key_column: primary_key_column, + ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil) + ) + 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_cached_counter? + owner.read_attribute(reflection.counter_cache_column).to_i + else + scope.count(:all) + end + + # If there's nothing in the database and @target has no new records + # we are certain the current target is an empty array. This is a + # documented side-effect of the method that may avoid an extra SELECT. + loaded! if count == 0 + + [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 + scope = self.scope.where(reflection.klass.primary_key => records) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_through_association.rb new file mode 100644 index 0000000000..0b9fcb00d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_many_through_association.rb @@ -0,0 +1,226 @@ +# 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 + attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type] + + through_association.build(attributes) + end + end + + attr_reader :through_scope + + def through_scope_attributes + scope = through_scope || self.scope + scope.where_values_hash(through_association.reflection.name.to_s). + except!(through_association.reflection.foreign_key, + through_association.reflection.klass.inheritance_column) + 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 = source_reflection.inverse_of + 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? && owner[through_reflection.foreign_key].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.klass.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 + return [] unless target_reflection_has_associated_record? + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_one_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_one_association.rb new file mode 100644 index 0000000000..d25f0fa55a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_one_association.rb @@ -0,0 +1,132 @@ +# 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 + primary_key_column = target.class.primary_key.to_sym + id = target.public_send(primary_key_column) + + 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, "Failed to save the new associated #{reflection.name}." + 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, "Failed to remove the existing associated #{reflection.name}. " \ + "The record failed to save after its foreign key was set to nil." + end + end + end + + def nullify_owner_attributes(record) + record[reflection.foreign_key] = nil + end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end + + def _create_record(attributes, raise_error = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_one_through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/has_one_through_association.rb new file mode 100644 index 0000000000..10978b2d93 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency.rb new file mode 100644 index 0000000000..325a803c98 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency.rb @@ -0,0 +1,293 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + autoload :JoinBase, "active_record/associations/join_dependency/join_base" + autoload :JoinAssociation, "active_record/associations/join_dependency/join_association" + + 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 + 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| + table, terminated = @joined_tables[reflection] + root = reflection == child.reflection + + if table && (!root || !terminated) + @joined_tables[reflection] = [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[reflection] ||= [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 + + key = aliases.column_alias(node, node.primary_key) + id = row[key] + if id.nil? + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + model = seen[ar_parent][node][id] + + if model + construct(model, node, row, seen, model_cache, strict_loading_value) + else + model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value) + + seen[ar_parent][node][id] = model + construct(model, node, row, seen, model_cache, strict_loading_value) + end + end + end + + def construct_model(record, node, row, model_cache, id, strict_loading_value) + other = record.association(node.reflection.name) + + model = model_cache[node][id] ||= + node.instantiate(row, aliases.column_aliases(node)) do |m| + m.strict_loading! if strict_loading_value + other.set_inverse_instance(m) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_association.rb new file mode 100644 index 0000000000..88f21b80ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_association.rb @@ -0,0 +1,103 @@ +# 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.each do |reflection| + table, terminated = yield reflection + @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 = Arel.sql(base_klass.connection.visitor.compile(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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_base.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_base.rb new file mode 100644 index 0000000000..988b4e8fa2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_part.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/join_dependency/join_part.rb new file mode 100644 index 0000000000..3ffa079c61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader.rb new file mode 100644 index 0000000000..de7288983a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Associations + # 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 :ThroughAssociation, "active_record/associations/preloader/through_association" + end + + # 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 his + # 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 +:include+ option for + # ActiveRecord::Base.find. So +associations+ could look like this: + # + # :books + # [ :books, :author ] + # { author: :avatar } + # [ :books, { author: :avatar } ] + def preload(records, associations, preload_scope = nil) + records = Array.wrap(records).compact + + if records.empty? + [] + else + Array.wrap(associations).flat_map { |association| + preloaders_on association, records, preload_scope + } + end + end + + def initialize(associate_by_default: true) + @associate_by_default = associate_by_default + end + + private + # Loads all the given data into +records+ for the +association+. + def preloaders_on(association, records, scope, polymorphic_parent = false) + case association + when Hash + preloaders_for_hash(association, records, scope, polymorphic_parent) + when Symbol, String + preloaders_for_one(association, records, scope, polymorphic_parent) + else + raise ArgumentError, "#{association.inspect} was not recognized for preload" + end + end + + def preloaders_for_hash(association, records, scope, polymorphic_parent) + association.flat_map { |parent, child| + grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records| + loaders = preloaders_for_reflection(reflection, reflection_records, scope) + recs = loaders.flat_map(&:preloaded_records).uniq + child_polymorphic_parent = reflection && reflection.options[:polymorphic] + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope, child_polymorphic_parent + } + loaders + end + } + end + + # Loads all the given data into +records+ for a singular +association+. + # + # Functions by instantiating a preloader class such as Preloader::Association and + # call the +run+ method for each passed in class in the +records+ argument. + # + # Not all records have the same class, so group then preload group on the reflection + # itself so that if various subclass share the same association then we do not split + # them unnecessarily + # + # Additionally, polymorphic belongs_to associations can have multiple associated + # classes, depending on the polymorphic_type field. So we group by the classes as + # well. + def preloaders_for_one(association, records, scope, polymorphic_parent) + grouped_records(association, records, polymorphic_parent) + .flat_map do |reflection, reflection_records| + preloaders_for_reflection reflection, reflection_records, scope + end + end + + def preloaders_for_reflection(reflection, records, scope) + records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs| + preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run + end + end + + def grouped_records(association, records, polymorphic_parent) + h = {} + 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 + + class AlreadyLoaded # :nodoc: + def initialize(klass, owners, reflection, preload_scope, associate_by_default = true) + @owners = owners + @reflection = reflection + end + + def run + self + end + + def preloaded_records + @preloaded_records ||= records_by_owner.flat_map(&:last) + end + + def records_by_owner + @records_by_owner ||= owners.index_with do |owner| + Array(owner.association(reflection.name).target) + end + end + + private + attr_reader :owners, :reflection + 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, owners) + if owners.all? { |o| o.association(reflection.name).loaded? } + return AlreadyLoaded + end + reflection.check_preloadable! + + if reflection.options[:through] + ThroughAssociation + else + Association + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/association.rb new file mode 100644 index 0000000000..8ce6a8eecf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/association.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class Association #:nodoc: + def initialize(klass, owners, reflection, preload_scope, associate_by_default = true) + @klass = klass + @owners = owners.uniq(&:__id__) + @reflection = reflection + @preload_scope = preload_scope + @associate = associate_by_default || !preload_scope || preload_scope.empty_scope? + @model = owners.first && owners.first.class + end + + def run + 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 + + private + attr_reader :owners, :reflection, :preload_scope, :model, :klass + + def load_records + # 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 = owner_keys.empty? ? [] : records_for(owner_keys) + + @preloaded_records = raw_records.select do |record| + assignments = false + + owners_by_key[convert_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 + + # The name of the key on the associated records + def association_key_name + reflection.join_primary_key(klass) + end + + # 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) + association = owner.association(reflection.name) + if reflection.collection? + association.target = records + else + association.target = records.first + end + end + + def owner_keys + @owner_keys ||= owners_by_key.keys + end + + def owners_by_key + @owners_by_key ||= owners.each_with_object({}) do |owner, result| + key = convert_key(owner[owner_key_name]) + (result[key] ||= []) << owner if key + 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 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 records_for(ids) + scope.where(association_key_name => ids).load do |record| + # Processing only the first owner + # because the record is modified but not an owner + owner = owners_by_key[convert_key(record[association_key_name])].first + association = owner.association(reflection.name) + association.set_inverse_instance(record) + end + end + + def scope + @scope ||= build_scope + end + + def reflection_scope + @reflection_scope ||= begin + reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped + end + 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 + + if preload_scope && preload_scope.strict_loading_value + scope.strict_loading + else + scope + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/through_association.rb new file mode 100644 index 0000000000..b39a235a19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/preloader/through_association.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class ThroughAssociation < Association # :nodoc: + PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false) + + def initialize(*) + super + @already_loaded = owners.first.association(through_reflection.name).loaded? + end + + def preloaded_records + @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records) + end + + def records_by_owner + return @records_by_owner if defined?(@records_by_owner) + source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge) + through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge) + + @records_by_owner = owners.each_with_object({}) do |owner, result| + through_records = through_records_by_owner[owner] || [] + + if @already_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 + + private + def source_preloaders + @source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope) + end + + def middle_records + through_preloaders.flat_map(&:preloaded_records) + end + + def through_preloaders + @through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope) + end + + def through_reflection + reflection.through_reflection + end + + def source_reflection + reflection.source_reflection + 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 + + 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 + + scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/singular_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/singular_association.rb new file mode 100644 index 0000000000..b7e5987c4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/singular_association.rb @@ -0,0 +1,59 @@ +# 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 + if !loaded? || stale_target? + reload + end + + target + 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!(klass.primary_key) + end + + def find_target + super.first + 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.0.0/gems/activerecord-6.1.4/lib/active_record/associations/through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/through_association.rb new file mode 100644 index 0000000000..3f5e9066eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/associations/through_association.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Through Association + module ThroughAssociation #:nodoc: + delegate :source_reflection, to: :reflection + + private + 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 association_primary_key == reflection.klass.primary_key && !options[:source_type] + join_attributes = { source_reflection.name => records } + else + join_attributes = { + source_reflection.foreign_key => records.map(&association_primary_key.to_sym) + } + 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 crazy to try to + # properly support stale-checking for nested associations. + def stale_state + if through_reflection.belongs_to? + owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s + end + end + + def foreign_key_present? + through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil? + 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) + inverse = source_reflection.inverse_of + target = through_association.target + + if inverse && target && !target.is_a?(Array) + attributes[inverse.foreign_key] = target.id + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb new file mode 100644 index 0000000000..553484883c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_assignment.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require "active_model/forbidden_attributes_protection" + +module ActiveRecord + module AttributeAssignment + include ActiveModel::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?(&:nil?) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods.rb new file mode 100644 index 0000000000..3b58472184 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods.rb @@ -0,0 +1,430 @@ +# frozen_string_literal: true + +require "mutex_m" +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 parent superclass) + + class GeneratedAttributeMethods < Module #:nodoc: + include Mutex_m + 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 + ).map { |m| -m.to_s }.to_set.freeze + end + end + + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules # :nodoc: + @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new) + private_constant :GeneratedAttributeMethods + @attribute_methods_generated = false + include @generated_attribute_methods + + super + 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. + generated_attribute_methods.synchronize do + return false if @attribute_methods_generated + superclass.define_attribute_methods unless base_class? + super(attribute_names) + @attribute_methods_generated = true + end + end + + def undefine_attribute_methods # :nodoc: + generated_attribute_methods.synchronize do + super if defined?(@attribute_methods_generated) && @attribute_methods_generated + @attribute_methods_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 overwrite 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 + 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. + # We check defined?(@attributes) not to issue warnings if called on objects that + # have been allocated but not yet initialized. + if defined?(@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, Date and Time attributes are returned in the + # :db format. 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\"" + # + # 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 typecast (for example, + # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises + # ActiveModel::MissingAttributeError if the identified attribute is missing. + # + # Note: +:id+ is always present. + # + # class Person < ActiveRecord::Base + # belongs_to :organization + # end + # + # person = Person.new(name: 'Francesco', age: '22') + # person[:name] # => "Francesco" + # person[:age] # => 22 + # + # person = Person.select('id').first + # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name + # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id + def [](attr_name) + read_attribute(attr_name) { |n| missing_attribute(n, caller) } + end + + # Updates the attribute identified by attr_name with the specified +value+. + # (Alias for the protected #write_attribute method). + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person[:age] = '22' + # person[:age] # => 22 + # person[:age].class # => Integer + 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 attribute_method?(attr_name) + # We check defined? because Syck calls respond_to? before actually calling initialize. + defined?(@attributes) && @attributes.key?(attr_name) + end + + def attributes_with_values(attribute_names) + attribute_names.index_with do |name| + _read_attribute(name) + end + end + + # Filters the primary keys and readonly attributes 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) + end + end + + # Filters out the 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? + 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_s(: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.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/before_type_cast.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/before_type_cast.rb new file mode 100644 index 0000000000..33ca3d38b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/before_type_cast.rb @@ -0,0 +1,84 @@ +# 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" + attribute_method_suffix "_came_from_user?" + 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 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 + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/dirty.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/dirty.rb new file mode 100644 index 0000000000..bf5cc82f7a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/dirty.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActiveRecord + module AttributeMethods + 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_writes, instance_writer: false, default: true + + # Attribute methods for "changed in last call to save?" + attribute_method_affix(prefix: "saved_change_to_", suffix: "?") + attribute_method_prefix("saved_change_to_") + attribute_method_suffix("_before_last_save") + + # Attribute methods for "will change if I call save?" + attribute_method_affix(prefix: "will_save_change_to_", suffix: "?") + attribute_method_suffix("_change_to_be_saved", "_in_database") + 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 passed, this method will return false unless the original + # value is equal to the given option + # + # +to+ When passed, this method will return false unless the value was + # 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 passed, this method will return false unless the original + # value is equal to the given option + # + # +to+ When passed, 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 write_attribute_without_type_cast(attr_name, value) + result = super + clear_attribute_change(attr_name) + result + 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_writes) + affected_rows = super + changes_applied + affected_rows + end + + def _create_record(attribute_names = attribute_names_for_partial_writes) + id = super + changes_applied + id + end + + def attribute_names_for_partial_writes + partial_writes? ? changed_attribute_names_to_save : attribute_names + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/primary_key.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/primary_key.rb new file mode 100644 index 0000000000..977409ff4c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/primary_key.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "set" + +module ActiveRecord + module AttributeMethods + 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 + [key] if key + end + + # Returns the primary key column's value. + def id + _read_attribute(@primary_key) + end + + # Sets the primary key column's value. + def id=(value) + _write_attribute(@primary_key, value) + end + + # Queries the primary key column's value. + def id? + query_attribute(@primary_key) + end + + # Returns the primary key column's value before type cast. + def id_before_type_cast + attribute_before_type_cast(@primary_key) + end + + # Returns the primary key column's previous value. + def id_was + attribute_was(@primary_key) + end + + # Returns the primary key column's value from the 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 + + 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 + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key + end + + # Returns a quoted version of the primary key name, used to construct + # SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.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 + else + if ActiveRecord::Base != self && table_exists? + pk = connection.schema_cache.primary_keys(table_name) + suppress_composite_primary_key(pk) + else + "id" + end + 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 = value && -value.to_s + @quoted_primary_key = nil + @attributes_builder = nil + end + + private + def suppress_composite_primary_key(pk) + return pk unless pk.is_a?(Array) + + warn <<~WARNING + WARNING: Active Record does not support composite primary key. + + #{table_name} has composite primary key. Composite primary key is ignored. + WARNING + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/query.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/query.rb new file mode 100644 index 0000000000..d17e8d8513 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/query.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Query + extend ActiveSupport::Concern + + included do + attribute_method_suffix "?" + end + + def query_attribute(attr_name) + value = self[attr_name] + + 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 + + alias :attribute? :query_attribute + private :attribute? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/read.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/read.rb new file mode 100644 index 0000000000..12cbfc867f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/read.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Read + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + private + def define_method_attribute(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}" << + " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" << + "end" + end + end + end + + # Returns the value of the attribute identified by attr_name after + # it has been typecast (for example, "2004-12-12" in a date column is cast + # to a date object, like Date.new(2004, 12, 12)). + def read_attribute(attr_name, &block) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + name = @primary_key if name == "id" && @primary_key + @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.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/serialization.rb new file mode 100644 index 0000000000..624a70b425 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/serialization.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + 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 + + module ClassMethods + # If you have an attribute that needs to be saved to the database as an + # object, and retrieved as the same object, then specify the name of that + # attribute using this method and it will be handled automatically. The + # serialization is done through YAML. If +class_name+ is specified, the + # serialized object must be of that class on assignment and retrieval. + # Otherwise SerializationTypeMismatch will be raised. + # + # Empty objects as {}, in the case of +Hash+, or [], in the case of + # +Array+, will always be persisted as null. + # + # 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 field name that should be serialized. + # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+ + # or a class name that the object type should be equal to. + # + # ==== 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+. + # + # ==== Example + # + # # Serialize a preferences attribute. + # class User < ActiveRecord::Base + # serialize :preferences + # end + # + # # Serialize preferences using JSON as coder. + # class User < ActiveRecord::Base + # serialize :preferences, JSON + # end + # + # # Serialize preferences as Hash using YAML coder. + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + def serialize(attr_name, class_name_or_coder = Object, **options) + # 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 = if class_name_or_coder == ::JSON + Coders::JSON + elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder + else + Coders::YAMLColumn.new(attr_name, class_name_or_coder) + end + + decorate_attribute_type(attr_name.to_s, **options) do |cast_type| + if type_incompatible_with_serialize?(cast_type, class_name_or_coder) + raise ColumnNotSerializableError.new(attr_name, cast_type) + end + + Type::Serialized.new(cast_type, coder) + end + end + + private + def type_incompatible_with_serialize?(type, class_name) + type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON || + type.respond_to?(:type_cast_array, true) && class_name == ::Array + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/time_zone_conversion.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/time_zone_conversion.rb new file mode 100644 index 0000000000..f5eb10c079 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -0,0 +1,88 @@ +# 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 + else + map_avoiding_infinite_recursion(super) { |v| cast(v) } + end + end + + private + def convert_time_to_time_zone(value) + return if value.nil? + + if value.acts_like?(:time) + value.in_time_zone + elsif value.is_a?(::Float) + value + else + map_avoiding_infinite_recursion(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 + + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end + end + end + end + + extend ActiveSupport::Concern + + included do + mattr_accessor :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: + def define_attribute(name, cast_type, **) + if create_time_zone_conversion_attribute?(name, cast_type) + cast_type = TimeZoneConverter.new(cast_type) + end + super + end + + private + 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.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/write.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/write.rb new file mode 100644 index 0000000000..b0eb37c080 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attribute_methods/write.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Write + extend ActiveSupport::Concern + + included do + attribute_method_suffix "=" + end + + module ClassMethods # :nodoc: + private + def define_method_attribute=(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + end + + # Updates the attribute identified by attr_name with the + # specified +value+. Empty strings for Integer and Float columns are + # turned into +nil+. + 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= + + private + def write_attribute_without_type_cast(attr_name, value) + @attributes.write_cast_value(attr_name, value) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attributes.rb new file mode 100644 index 0000000000..5f1514d878 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/attributes.rb @@ -0,0 +1,303 @@ +# frozen_string_literal: true + +require "active_model/attribute/user_provided_default" + +module ActiveRecord + # See ActiveRecord::Attributes::ClassMethods for documentation + module Attributes + extend ActiveSupport::Concern + + included do + class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal: + end + + module ClassMethods + ## + # :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. 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 previous default value (if any) 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 MoneyType < 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(:money, MoneyType) + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :money + # 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 MoneyType < 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(:money, MoneyType) + # + # # app/models/product.rb + # class Product < ActiveRecord::Base + # currency_converter = ConversionRatesFromTheInternet.new + # attribute :price_in_bitcoins, :money, 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. + def attribute(name, cast_type = nil, **options, &block) + name = name.to_s + reload_schema_from_cache + + self.attributes_to_define_after_schema_loads = + attributes_to_define_after_schema_loads.merge( + name => [cast_type || block, options] + ) + end + + # This is the low level API which sits beneath +attribute+. It only + # accepts type objects, and will do its work immediately instead of + # waiting for the schema to load. Automatic schema detection and + # ClassMethods#attribute both call this under the hood. 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 load_schema! # :nodoc: + super + attributes_to_define_after_schema_loads.each do |name, (type, options)| + define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default)) + end + 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 decorate_attribute_type(attr_name, **default) + type, options = attributes_to_define_after_schema_loads[attr_name] + + default.with_defaults!(default: options[:default]) if options&.key?(:default) + + attribute(attr_name, **default) do |cast_type| + if type && !type.is_a?(Proc) + cast_type = _lookup_cast_type(attr_name, type, options) + end + + yield cast_type + end + end + + def _lookup_cast_type(name, type, options) + case type + when Symbol + adapter_name = ActiveRecord::Type.adapter_name_from(self) + ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name) + when Proc + type[type_for_attribute(name)] + else + type || type_for_attribute(name) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/autosave_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/autosave_association.rb new file mode 100644 index 0000000000..7b550dae5f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/autosave_association.rb @@ -0,0 +1,527 @@ +# frozen_string_literal: true + +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 + mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false + end + + module ClassMethods # :nodoc: + private + if Module.method(:method_defined?).arity == 1 # MRI 2.5 and older + using Module.new { + refine Module do + def method_defined?(method, inherit = true) + if inherit + super(method) + else + instance_methods(false).include?(method.to_sym) + end + end + end + } + end + + 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_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) + # 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 + else + method = :validate_single_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 + + private + # 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 association. + def validate_single_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.reader + association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?) + 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_with_index { |record, index| association_valid?(reflection, record, index) } + 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?(reflection, record, index = nil) + return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) + + context = validation_context if custom_validation_context? + + unless valid = record.valid?(context) + if reflection.options[:autosave] + indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) + + record.errors.group_by_attribute.each { |attribute, errors| + attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + + errors.each { |error| + self.errors.import( + error, + attribute: attribute + ) + } + } + else + errors.add(reflection.name) + end + end + valid + end + + def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + if indexed_attribute + "#{reflection.name}[#{index}].#{attribute}" + else + "#{reflection.name}.#{attribute}" + end + 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?) + 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) + record = association && association.load_target + + if record && !record.destroyed? + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + record.destroy + elsif autosave != false + key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id + + if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key) + unless reflection.through_reflection + record[reflection.foreign_key] = key + if inverse_reflection = reflection.inverse_of + record.association(inverse_reflection.name).inversed_from(self) + end + end + + saved = record.save(validate: !autosave) + raise ActiveRecord::Rollback if !saved && autosave + saved + end + end + 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) || + record.will_save_change_to_attribute?(reflection.foreign_key) + end + + def association_foreign_key_changed?(reflection, record, key) + return false if reflection.through_reflection? + + record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key + 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? + self[reflection.foreign_key] = nil + record.destroy + elsif autosave != false + saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) + + if association.updated? + association_id = record.public_send(reflection.options[:primary_key] || :id) + self[reflection.foreign_key] = association_id + association.loaded! + end + + saved if autosave + end + end + end + + def custom_validation_context? + validation_context && [:create, :update].exclude?(validation_context) + end + + def _ensure_no_duplicate_errors + errors.uniq! + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/base.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/base.rb new file mode 100644 index 0000000000..a79ce54fbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/base.rb @@ -0,0 +1,316 @@ +# 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 + # + # == 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.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 + extend ActiveModel::Naming + + 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 ActiveModel::Conversion + include Integration + include Validations + include CounterCache + include Attributes + include Locking::Optimistic + include Locking::Pessimistic + include AttributeMethods + include Callbacks + include Timestamp + include Associations + include ActiveModel::SecurePassword + include AutosaveAssociation + include NestedAttributes + include Transactions + include TouchLater + include NoTouching + include Reflection + include Serialization + include Store + include SecureToken + include SignedId + include Suppressor + end + + ActiveSupport.run_load_hooks(:active_record, Base) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/callbacks.rb new file mode 100644 index 0000000000..ee61d063bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/callbacks.rb @@ -0,0 +1,468 @@ +# 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+. + # The same applies to all non-transactional callbacks. + # + # As seen below, in case there are multiple transactional callbacks the order + # is reversed. + # + # For example: + # + # class Topic < ActiveRecord::Base + # has_many :children + # + # after_commit :log_children + # after_commit :do_something_else + # + # private + # + # def log_children + # # Child processing + # end + # + # def do_something_else + # # Something else + # end + # end + # + # In this case the +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 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 { super } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/coders/json.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/coders/json.rb new file mode 100644 index 0000000000..a69b38487e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/coders/json.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Coders # :nodoc: + class 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.0.0/gems/activerecord-6.1.4/lib/active_record/coders/yaml_column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/coders/yaml_column.rb new file mode 100644 index 0000000000..ada0a16719 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/coders/yaml_column.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "yaml" + +module ActiveRecord + module Coders # :nodoc: + class YAMLColumn # :nodoc: + attr_accessor :object_class + + def initialize(attr_name, object_class = Object) + @attr_name = attr_name + @object_class = object_class + check_arity_of_constructor + end + + def dump(obj) + return if obj.nil? + + assert_valid_value(obj, action: "dump") + YAML.dump obj + end + + def load(yaml) + return object_class.new if object_class != Object && yaml.nil? + return yaml unless yaml.is_a?(String) && yaml.start_with?("---") + obj = yaml_load(yaml) + + assert_valid_value(obj, action: "load") + obj ||= object_class.new if object_class != Object + + obj + end + + def assert_valid_value(obj, action:) + unless obj.nil? || obj.is_a?(object_class) + raise SerializationTypeMismatch, + "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.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 + + if YAML.respond_to?(:unsafe_load) + def yaml_load(payload) + YAML.unsafe_load(payload) + end + else + def yaml_load(payload) + YAML.load(payload) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters.rb new file mode 100644 index 0000000000..da07d5d295 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + extend ActiveSupport::Autoload + + eager_autoload do + autoload :AbstractAdapter + end + + autoload :Column + autoload :PoolConfig + autoload :PoolManager + autoload :LegacyPoolManager + autoload :SchemaCache + autoload :Deduplicable + + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do + autoload :IndexDefinition + autoload :ColumnDefinition + autoload :ChangeColumnDefinition + autoload :ForeignKeyDefinition + autoload :CheckConstraintDefinition + autoload :TableDefinition + autoload :Table + autoload :AlterTable + autoload :ReferenceDefinition + end + + autoload_at "active_record/connection_adapters/abstract/connection_pool" do + autoload :ConnectionHandler + end + + autoload_under "abstract" do + autoload :SchemaStatements + autoload :DatabaseStatements + autoload :DatabaseLimits + autoload :Quoting + autoload :ConnectionPool + autoload :QueryCache + autoload :Savepoints + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/connection_pool.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/connection_pool.rb new file mode 100644 index 0000000000..4f774bb7c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -0,0 +1,1229 @@ +# frozen_string_literal: true + +require "thread" +require "concurrent/map" +require "monitor" +require "weakref" + +module ActiveRecord + module ConnectionAdapters + module AbstractPool # :nodoc: + def get_schema_cache(connection) + self.schema_cache ||= SchemaCache.new(connection) + schema_cache.connection = connection + schema_cache + end + + def set_schema_cache(cache) + self.schema_cache = cache + end + end + + class NullPool # :nodoc: + include ConnectionAdapters::AbstractPool + + attr_accessor :schema_cache + + def connection_klass + nil + end + end + + # 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. + # + # == Obtaining (checking out) a connection + # + # Connections can be obtained and used from a connection pool in several + # ways: + # + # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection] + # as with Active Record 2.1 and + # earlier (pre-connection-pooling). Eventually, when you're done with + # the connection(s) and wish it to be returned to the pool, you call + # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!]. + # This will be 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). + # + # == 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 + # 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 = Concurrent.monotonic_time + elapsed = 0 + loop do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @cond.wait(timeout - elapsed) + end + + return remove if any? + + elapsed = Concurrent.monotonic_time - 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 + + # 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) + 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 + + include MonitorMixin + include QueryCache::ConnectionPoolConfiguration + include ConnectionAdapters::AbstractPool + + attr_accessor :automatic_reconnect, :checkout_timeout + attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass + + delegate :schema_cache, :schema_cache=, 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 + @connection_klass = pool_config.connection_klass + + @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 @thread_cached_conns does not require + # synchronization. + @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) + + @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 + + @lock_thread = false + + @reaper = Reaper.new(self, db_config.reaping_frequency) + @reaper.run + end + + def lock_thread=(lock_thread) + if lock_thread + @lock_thread = Thread.current + else + @lock_thread = nil + end + end + + # Retrieve the connection associated with the current thread, or call + # #checkout to obtain one if necessary. + # + # #connection can be called any number of times; the connection is + # held in a cache keyed by a thread. + def connection + @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout + 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 + # #connection or #with_connection methods. Connections obtained through + # #checkout will not be detected by #active_connection? + def active_connection? + @thread_cached_conns[connection_cache_key(current_thread)] + end + + # 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 + # #connection or #with_connection methods, connections obtained through + # #checkout will not be automatically released. + def release_connection(owner_thread = Thread.current) + if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)) + checkin conn + end + end + + # If a connection obtained through #connection or #with_connection methods + # already exists yield it to the block. If no such connection + # exists checkout a connection, yield it to the block, and checkin the + # connection when finished. + def with_connection + unless conn = @thread_cached_conns[connection_cache_key(Thread.current)] + conn = connection + fresh_connection = true + end + yield conn + ensure + release_connection if fresh_connection + end + + # Returns true if a connection has already been opened. + def connected? + synchronize { @connections.any? } + 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 = [] + @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 = @thread_cached_conns = 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) + checkout_and_verify(acquire_connection(checkout_timeout)) + 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) + conn.lock.synchronize do + synchronize do + remove_connection_from_thread_cache 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 + + # Return connection pool's usage statistic + # Example: + # + # 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 + + private + #-- + # 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 + + #-- + # From the discussion on GitHub: + # https://github.com/rails/rails/pull/14938#commitcomment-6601951 + # This hook-in method allows for easier monkey-patching fixes needed by + # JRuby users that use Fibers. + def connection_cache_key(thread) + thread + end + + def current_thread + @lock_thread || Thread.current + 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 + # account for our own connections + @connections.select { |conn| conn.owner == Thread.current } + end + + newly_checked_out = [] + timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2) + + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Concurrent.monotonic_time + 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 == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end + end + + msg << " (#{thread_report.join(', ')})" if thread_report.any? + + raise ExclusiveConnectionTimeoutError, msg + 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 + @available.poll(checkout_timeout) + end + 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) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache + + def new_connection + Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn| + conn.check_version + end + end + + # 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 + end + + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end + + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise + end + end + + # 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 + FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! } + private_constant :FINALIZER + + def initialize + # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name). + @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2) + + # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred + ObjectSpace.define_finalizer self, FINALIZER + end + + def prevent_writes # :nodoc: + Thread.current[:prevent_writes] + end + + def prevent_writes=(prevent_writes) # :nodoc: + Thread.current[:prevent_writes] = prevent_writes + 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) + unless ActiveRecord::Base.legacy_connection_handling + raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling" + end + + original, self.prevent_writes = self.prevent_writes, enabled + yield + ensure + self.prevent_writes = original + end + + def connection_pool_names # :nodoc: + owner_to_pool_manager.keys + end + + def all_connection_pools + owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) } + end + + def connection_pool_list(role = ActiveRecord::Base.current_role) + owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) } + end + alias :connection_pools :connection_pool_list + + def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard) + owner_name = config.to_s if config.is_a?(Symbol) + + pool_config = resolve_pool_config(config, owner_name) + db_config = pool_config.db_config + + # Protects the connection named `ActiveRecord::Base` from being removed + # if the user calls `establish_connection :primary`. + if owner_to_pool_manager.key?(pool_config.connection_specification_name) + remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard) + end + + message_bus = ActiveSupport::Notifications.instrumenter + payload = {} + if pool_config + payload[:spec_name] = pool_config.connection_specification_name + payload[:shard] = shard + payload[:config] = db_config.configuration_hash + end + + if ActiveRecord::Base.legacy_connection_handling + owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new + else + owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new + end + pool_manager = get_pool_manager(pool_config.connection_specification_name) + pool_manager.set_pool_config(role, shard, pool_config) + + message_bus.instrument("!connection.active_record", payload) do + pool_config.pool + end + end + + # Returns true if there are any active connections among the connection + # pools that the ConnectionHandler is managing. + def active_connections?(role = ActiveRecord::Base.current_role) + connection_pool_list(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 = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:release_connection) + end + + # Clears the cache which maps classes. + # + # See ConnectionPool#clear_reloadable_connections! for details. + def clear_reloadable_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:clear_reloadable_connections!) + end + + def clear_all_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:disconnect!) + end + + # Disconnects all currently idle connections. + # + # See ConnectionPool#flush! for details. + def flush_idle_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(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(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc: + pool = retrieve_connection_pool(spec_name, role: role, shard: shard) + + unless pool + if shard != ActiveRecord::Base.default_shard + message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard." + elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler + message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role." + elsif role != ActiveRecord::Base.default_role + message = "No connection pool for '#{spec_name}' found for the '#{role}' role." + else + message = "No connection pool for '#{spec_name}' found." + end + + raise ConnectionNotEstablished, message + end + + pool.connection + end + + # Returns true if a connection that's accessible to this class has + # already been opened. + def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + pool = retrieve_connection_pool(spec_name, role: role, shard: shard) + pool && pool.connected? + end + + # Remove the connection for this class. This will close the active + # connection and the defined connection (if they exist). The result + # can be used as an argument for #establish_connection, for easily + # re-establishing the connection. + def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash + end + deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash" + + def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + if pool_manager = get_pool_manager(owner) + pool_config = pool_manager.remove_pool_config(role, shard) + + if pool_config + pool_config.disconnect! + pool_config.db_config + end + end + end + + # Retrieving the connection pool happens a lot, so we cache it in @owner_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(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + pool_config = get_pool_manager(owner)&.get_pool_config(role, shard) + pool_config&.pool + end + + private + attr_reader :owner_to_pool_manager + + # Returns the pool manager for an owner. + # + # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is + # deprecated in favor of looking it up by `"ActiveRecord::Base"`. + # + # During the deprecation period, if `"primary"` is passed, the pool manager + # for `ActiveRecord::Base` will still be returned. + def get_pool_manager(owner) + return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner) + + if owner == "primary" + ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.") + owner_to_pool_manager[Base.name] + 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, owner_name) + db_config = Base.configurations.resolve(config) + + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. + path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter" + begin + require path_to_adapter + rescue LoadError => e + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end + + unless ActiveRecord::Base.respond_to?(db_config.adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter" + end + + ConnectionAdapters::PoolConfig.new(owner_name, db_config) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_limits.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_limits.rb new file mode 100644 index 0000000000..b1ff8eec74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -0,0 +1,43 @@ +# 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 alias. + def table_alias_length + max_identifier_length + end + + # Returns the maximum allowed length for an index name. This + # limit is enforced by \Rails and is less than or equal to + # #index_name_length. The gap between + # #index_name_length is to allow internal \Rails + # operations to use prefixes in temporary operations. + def allowed_index_name_length + index_name_length + end + deprecate :allowed_index_name_length + + # Returns the maximum length of an index name. + def index_name_length + max_identifier_length + end + + # Returns the maximum number of elements in an IN (x,y,z) clause. + # +nil+ means no limit. + def in_clause_length + nil + end + deprecate :in_clause_length + + private + def bind_params_length + 65535 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 0000000000..f1db1513ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -0,0 +1,561 @@ +# 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) # :nodoc: + if arel_or_sql_string.respond_to?(:ast) + 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() + + if prepared_statements + collector.preparable = true + sql, binds = visitor.compile(arel_or_sql_string.ast, 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.ast, collector) + end + [sql.freeze, binds, preparable] + else + arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen? + [arel_or_sql_string, binds, preparable] + 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) + arel = arel_from_relation(arel) + sql, binds, preparable = to_sql_and_binds(arel, binds, preparable) + + if prepared_statements && preparable + select_prepared(sql, name, binds) + else + select(sql, name, binds) + end + rescue ::RangeError + ActiveRecord::Result.new([], []) + end + + # Returns a record hash with the column names as keys and column values + # as values. + def select_one(arel, name = nil, binds = []) + select_all(arel, name, binds).first + end + + # Returns a single value from a record + def select_value(arel, name = nil, binds = []) + single_value_from_rows(select_rows(arel, name, binds)) + 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 = []) + select_all(arel, name, binds).rows + end + + def query_value(sql, name = nil) # :nodoc: + single_value_from_rows(query(sql, name)) + end + + def query_values(sql, name = nil) # :nodoc: + query(sql, name).map(&:first) + end + + def query(sql, name = nil) # :nodoc: + exec_query(sql, name).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. + # Note: depending on your database connector, the result returned by this + # method may be manually memory managed. Consider using the exec_query + # wrapper instead. + def execute(sql, name = nil) + raise NotImplementedError + 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. + def exec_query(sql, name = "SQL", binds = [], prepare: false) + raise NotImplementedError + 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. + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + sql, binds = sql_for_insert(sql, pk, binds) + 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 = []) + exec_query(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 = []) + exec_query(sql, name, binds) + end + + def exec_insert_all(sql, name) # :nodoc: + exec_query(sql, name) + end + + def explain(arel, binds = []) # :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+. + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + value = exec_insert(sql, name, binds, pk, sequence_name) + 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 -= [schema_migration.table_name, InternalMetadata.table_name] + + return if table_names.empty? + + with_multi_statements do + disable_referential_integrity do + statements = build_truncate_statements(table_names) + execute_batch(statements, "Truncate Tables") + end + end + end + + # Runs the given block in a database transaction, and returns the result + # of the block. + # + # == 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.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.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 and postgresql adapters support setting the transaction + # isolation level. + def transaction(requires_new: nil, isolation: nil, joinable: true) + if !requires_new && current_transaction.joinable? + if isolation + raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" + end + yield + else + transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield } + 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!, 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 #:nodoc: + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) + 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 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 + + # 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 + end + + def exec_rollback_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 + + with_multi_statements do + disable_referential_integrity do + transaction(requires_new: true) do + execute_batch(statements, "Fixtures Load") + end + 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 + + private + def execute_batch(statements, name = nil) + statements.each do |statement| + execute(statement, name) + 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) + + 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 + manager.into(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.map do |table_name, fixtures| + next if fixtures.empty? + build_fixture_sql(fixtures, table_name) + end.compact + 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 with_multi_statements + yield + end + + def combine_multi_statements(total_sql) + total_sql.join(";\n") + end + + # Returns an ActiveRecord::Result instance. + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: false) + end + + def select_prepared(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: true) + end + + def sql_for_insert(sql, pk, binds) + [sql, binds] + end + + def last_inserted_id(result) + single_value_from_rows(result.rows) + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/query_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/query_cache.rb new file mode 100644 index 0000000000..6223e37698 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module QueryCache + class << self + def included(base) #:nodoc: + dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables, + :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all + + base.set_callback :checkout, :after, :configure_query_cache! + base.set_callback :checkin, :after, :disable_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}(*) + ActiveRecord::Base.clear_query_caches_for_current_thread + super + end + end_code + end + end + end + + module ConnectionPoolConfiguration + def initialize(*) + super + @query_cache_enabled = Concurrent::Map.new { false } + end + + def enable_query_cache! + @query_cache_enabled[connection_cache_key(current_thread)] = true + connection.enable_query_cache! if active_connection? + end + + def disable_query_cache! + @query_cache_enabled.delete connection_cache_key(current_thread) + connection.disable_query_cache! if active_connection? + end + + def query_cache_enabled + @query_cache_enabled[connection_cache_key(current_thread)] + end + end + + attr_reader :query_cache, :query_cache_enabled + + def initialize(*) + super + @query_cache = Hash.new { |h, sql| h[sql] = {} } + @query_cache_enabled = false + end + + # Enable the query cache within the block. + def cache + old, @query_cache_enabled = @query_cache_enabled, true + yield + ensure + @query_cache_enabled = old + clear_query_cache unless @query_cache_enabled + end + + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + clear_query_cache + end + + # Disable the query cache within the block. + def uncached + old, @query_cache_enabled = @query_cache_enabled, false + yield + ensure + @query_cache_enabled = old + 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 + @lock.synchronize do + @query_cache.clear + end + end + + def select_all(arel, name = nil, binds = [], preparable: nil) + if @query_cache_enabled && !locked?(arel) + arel = arel_from_relation(arel) + sql, binds, preparable = to_sql_and_binds(arel, binds, preparable) + + cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } + else + super + end + end + + private + def cache_sql(sql, name, binds) + @lock.synchronize do + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument( + "sql.active_record", + cache_notification_info(sql, name, binds) + ) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end + 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, + cached: true + } + end + + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. + def locked?(arel) + arel = arel.arel if arel.is_a?(Relation) + arel.respond_to?(:locked) && arel.locked + end + + def configure_query_cache! + enable_query_cache! if pool.query_cache_enabled + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 0000000000..d70f517342 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/quoting.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/multibyte/chars" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module Quoting + # Quotes the column value to help prevent + # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection]. + def quote(value) + if value.is_a?(Base) + ActiveSupport::Deprecation.warn(<<~MSG) + Passing an Active Record object to `quote` directly is deprecated + and will be no longer quoted as id value in Rails 6.2. + MSG + value = value.id_for_database + end + + _quote(value) + 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, column = nil) + if value.is_a?(Base) + ActiveSupport::Deprecation.warn(<<~MSG) + Passing an Active Record object to `type_cast` directly is deprecated + and will be no longer type casted as id value in Rails 6.2. + MSG + value = value.id_for_database + end + + if column + ActiveSupport::Deprecation.warn(<<~MSG) + Passing a column to `type_cast` is deprecated and will be removed in Rails 6.2. + MSG + type = lookup_cast_type_from_column(column) + value = type.serialize(value) + end + + _type_cast(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. Defaults to no quoting. + def quote_column_name(column_name) + column_name.to_s + end + + # Quotes the table name. Defaults to column name quoting. + def quote_table_name(table_name) + quote_column_name(table_name) + end + + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for mysql2 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 ActiveRecord::Base.default_timezone == :utc + value = value.getutc if value.respond_to?(:getutc) && !value.utc? + else + value = value.getlocal if value.respond_to?(:getlocal) + end + end + + result = value.to_s(: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: + value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "") + end + + def column_name_matcher # :nodoc: + COLUMN_NAME + end + + def column_name_with_order_matcher # :nodoc: + COLUMN_NAME_WITH_ORDER + end + + # Regexp for column names (with or without a table name prefix). + # Matches the following: + # + # "#{table_name}.#{column_name}" + # "#{column_name}" + COLUMN_NAME = / + \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 + + # 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" + COLUMN_NAME_WITH_ORDER = / + \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 + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def type_casted_binds(binds) + case binds.first + when Array + binds.map { |column, value| type_cast(value, column) } + else + binds.map do |value| + if ActiveModel::Attribute === value + type_cast(value.value_for_database) + else + type_cast(value) + end + end + end + end + + def lookup_cast_type(sql_type) + type_map.lookup(sql_type) + end + + 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, ActiveSupport::Duration 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 + + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/savepoints.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/savepoints.rb new file mode 100644 index 0000000000..d6dbef3fc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module Savepoints + def current_savepoint_name + current_transaction.savepoint_name + end + + def create_savepoint(name = current_savepoint_name) + execute("SAVEPOINT #{name}", "TRANSACTION") + end + + def exec_rollback_to_savepoint(name = current_savepoint_name) + execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION") + end + + def release_savepoint(name = current_savepoint_name) + execute("RELEASE SAVEPOINT #{name}", "TRANSACTION") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_creation.rb new file mode 100644 index 0000000000..55cce5e501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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?, :supports_foreign_keys?, :foreign_key_options, + :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?, :check_constraint_options, + 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(" ") + 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 supports_foreign_keys? + statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) }) + end + + if supports_check_constraints? + statements.concat(o.check_constraints.map { |expression, options| check_constraint_in_create(o.name, expression, options) }) + 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) + sql = +<<~SQL + CONSTRAINT #{quote_column_name(o.name)} + FOREIGN KEY (#{quote_column_name(o.column)}) + REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) + 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_DropForeignKey(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + end + + 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 << "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 visit_DropCheckConstraint(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + 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 foreign_key_in_create(from_table, to_table, options) + prefix = ActiveRecord::Base.table_name_prefix + suffix = ActiveRecord::Base.table_name_suffix + to_table = "#{prefix}#{to_table}#{suffix}" + options = foreign_key_options(from_table, to_table, options) + accept ForeignKeyDefinition.new(from_table, to_table, options) + end + + def check_constraint_in_create(table_name, expression, options) + options = check_constraint_options(table_name, expression, options) + accept CheckConstraintDefinition.new(table_name, expression, options) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 0000000000..2d763aa063 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -0,0 +1,802 @@ +# 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, :comment + + def initialize( + table, name, + unique = false, + columns = [], + lengths: {}, + orders: {}, + opclasses: {}, + where: nil, + type: nil, + using: nil, + comment: nil + ) + @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 + @comment = comment + end + + def column_options + { + length: lengths, + order: orders, + opclass: opclasses, + } + 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: + def primary_key? + options[:primary_key] + end + + [:limit, :precision, :scale, :default, :null, :collation, :comment].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: + + 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 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) + (to_table.nil? || to_table.to_s == self.to_table) && + (validate.nil? || validate == options.fetch(:validate, validate)) && + options.all? { |k, v| self.options[k].to_s == v.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 + 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_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 polymorphic_options + as_options(polymorphic).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) + 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) + 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 :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 + + # 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[6.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 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 + + if @columns_hash[name] + if @columns_hash[name].primary_key? + raise ArgumentError, "you can't redefine the primary key column '#{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}'." + end + end + + @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(table_name, **options) # :nodoc: + foreign_keys << [table_name, options] + end + + def check_constraint(expression, **options) + check_constraints << [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) + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) + end + + private + def create_column_definition(name, type, options) + 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 + end + + class AlterTable # :nodoc: + attr_reader :adds + attr_reader :foreign_key_adds, :foreign_key_drops + attr_reader :check_constraint_adds, :check_constraint_drops + + def initialize(td) + @td = td + @adds = [] + @foreign_key_adds = [] + @foreign_key_drops = [] + @check_constraint_adds = [] + @check_constraint_drops = [] + end + + def name; @td.name; end + + def add_foreign_key(to_table, options) + @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options) + end + + def drop_foreign_key(name) + @foreign_key_drops << name + end + + def add_check_constraint(expression, options) + @check_constraint_adds << CheckConstraintDefinition.new(name, expression, options) + end + + def drop_check_constraint(constraint_name) + @check_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 + + # 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.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) + @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) + @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) + @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) + @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) + @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) + @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) + 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) + 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) + @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) + @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) + @base.add_check_constraint(name, *args) + 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) + @base.remove_check_constraint(name, *args) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_dumper.rb new file mode 100644 index 0000000000..3817c8829e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + class SchemaDumper < SchemaDumper # :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) + column.precision.inspect if column.precision + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 0000000000..0c5cdf62e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -0,0 +1,1637 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/access" +require "digest/sha2" + +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 useable 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") + # + def index_exists?(table_name, column_name, **options) + checks = [] + + if column_name.present? + column_names = Array(column_name).map(&:to_s) + checks << lambda { |i| Array(i.columns) == column_names } + end + + checks << lambda { |i| i.unique } if options[:unique] + checks << lambda { |i| i.name == options[:name].to_s } if options[:name] + + indexes(table_name).any? { |i| checks.all? { |check| check[i] } } + end + + # Returns an array of +Column+ objects for the table specified by +table_name+. + def columns(table_name) + table_name = table_name.to_s + column_definitions(table_name).map do |field| + new_column_from_field(table_name, field) + 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 + # 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 order ( + # 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) + td = create_table_definition(table_name, **extract_table_options!(options)) + + if id && !td.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) + td.primary_keys pk + else + td.primary_key pk, id, **options + end + end + + yield td if block_given? + + 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 + + # 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) + # + # 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| t.to_s.singularize } + + 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 + + # Drops the join table specified by the given arguments. + # See #create_join_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) + 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, **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, self) + 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 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. + def drop_table(table_name, **options) + 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 + + # Add a new +type+ column named +column_name+ to +table_name+. + # + # 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, :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): + # * :limit - + # Requests a maximum column length. This is the number of characters for a :string column + # and number of bytes for :text, :binary, and :integer columns. + # This option is ignored by some backends. + # * :default - + # The column's default value. Use +nil+ for +NULL+. + # * :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. + # * :collation - + # Specifies the collation for a :string or :text column. If not specified, the + # column will have the same collation as the table. + # * :comment - + # Specifies the comment for the column. This option is ignored by some backends. + # * :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..63], :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) + return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type) + + at = create_alter_table table_name + at.add_column(column_name, type, **options) + execute schema_creation.accept at + 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 + + # 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 + + column_names.each do |column_name| + remove_column(table_name, column_name, type, **options) + end + 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. + # Indexes on the column are automatically removed. + # + # 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 used. + # + # 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 + + # 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: SQLite doesn't support index length. + # + # ====== 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 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) + # + # Note: only supported by PostgreSQL. + # + # 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) + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + + create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists) + execute schema_creation.accept(create_index) + 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] + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" + 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. + # #add_reference and #add_belongs_to are acceptable. + # + # 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_to(update_table_definition(table_name, self)) + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # #remove_reference and #remove_belongs_to are acceptable. + # + # ====== 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) + 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 + else + foreign_key_options = { to_table: reference_name } + end + foreign_key_options[:column] ||= "#{ref_name}_id" + remove_foreign_key(table_name, **foreign_key_options) + end + + remove_column(table_name, "#{ref_name}_id") + remove_column(table_name, "#{ref_name}_type") 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 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 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" + # [:primary_key] + # The primary key column name on +to_table+. Defaults to +id+. + # [: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+ + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_foreign_key(from_table, to_table, **options) + return unless supports_foreign_keys? + + 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 + # + # 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 supports_foreign_keys? + + 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) # :nodoc: + name = strip_table_name_prefix_and_suffix(table_name) + "#{name.singularize}_id" + end + + def foreign_key_options(from_table, to_table, options) # :nodoc: + options = options.dup + options[:column] ||= foreign_key_column_for(to_table) + options[:name] ||= foreign_key_name(from_table, options) + 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_. + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_check_constraint(table_name, expression, **options) + return unless supports_check_constraints? + + options = check_constraint_options(table_name, expression, 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. + # + # remove_check_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_check_constraint. + def remove_check_constraint(table_name, expression = nil, **options) + return unless supports_check_constraints? + + 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 + + def dump_schema_information # :nodoc: + versions = schema_migration.all_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(schema_migration.table_name) + + 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 + + # 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) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) && supports_datetime_with_precision? + options[:precision] = 6 + end + + add_column table_name, :created_at, :datetime, **options + add_column table_name, :updated_at, :datetime, **options + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. + # + # remove_timestamps(:suppliers) + # + def remove_timestamps(table_name, **options) + remove_column table_name, :updated_at + remove_column table_name, :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) + + 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], + 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 + + private + 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) && column_name.is_a?(String) && /\W/.match?(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? + 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) + indexes(new_name).each do |index| + generated_index_name = index_name(table_name, column: index.columns) + if generated_index_name == index.name + rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) + 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 schema_creation + SchemaCreation.new(self) + 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 extract_table_options!(options) + options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation) + 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 column_names.is_a?(String) && /\W/.match?(column_names) + column_names + else + Array(column_names) + end + end + + def index_name_options(column_names) + if column_names.is_a?(String) && /\W/.match?(column_names) + column_names = column_names.scan(/\w+/).join("_") + end + + { column: column_names } + 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 + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + + "fk_rails_#{hashed_identifier}" + end + end + + def foreign_key_for(from_table, **options) + return unless supports_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 check_constraint_name(table_name, **options) + options.fetch(:name) do + expression = options.fetch(:expression) + identifier = "#{table_name}_#{expression}_chk" + hashed_identifier = 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.name == chk_name } + 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 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 bulk_change_table(table_name, operations) + 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 << 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 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 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(schema_migration.table_name) + + if versions.is_a?(Array) + sql = +"INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") + sql << ";\n\n" + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/transaction.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/transaction.rb new file mode 100644 index 0000000000..d26e869d08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract/transaction.rb @@ -0,0 +1,381 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + 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 NullTransaction #:nodoc: + def initialize; end + def state; end + def closed?; true; end + def open?; false; end + def joinable?; false; end + def add_record(record, _ = true); end + end + + class Transaction #:nodoc: + attr_reader :connection, :state, :savepoint_name, :isolation_level + attr_accessor :written + + def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false) + @connection = connection + @state = TransactionState.new + @records = nil + @isolation_level = isolation + @materialized = false + @joinable = joinable + @run_commit_callbacks = run_commit_callbacks + @lazy_enrollment_records = nil + 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 records + if @lazy_enrollment_records + @records.concat @lazy_enrollment_records.values + @lazy_enrollment_records = nil + end + @records + end + + def materialize! + @materialized = true + end + + def materialized? + @materialized + end + + def rollback_records + return unless records + ite = records.uniq(&:__id__) + already_run_callbacks = {} + while record = ite.shift + trigger_callbacks = record.trigger_transactional_callbacks? + should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks + already_run_callbacks[record] ||= trigger_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 + + def before_commit_records + records.uniq.each(&:before_committed!) if records && @run_commit_callbacks + end + + def commit_records + return unless records + ite = records.uniq(&:__id__) + already_run_callbacks = {} + while record = ite.shift + if @run_commit_callbacks + trigger_callbacks = record.trigger_transactional_callbacks? + should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks + already_run_callbacks[record] ||= trigger_callbacks + record.committed!(should_run_callbacks: should_run_callbacks) + else + # 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 + + def full_rollback?; true; end + def joinable?; @joinable; end + def closed?; false; end + def open?; !closed?; end + end + + 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 rollback + connection.rollback_to_savepoint(savepoint_name) if materialized? + @state.rollback! + end + + def commit + connection.release_savepoint(savepoint_name) if materialized? + @state.commit! + end + + def full_rollback?; false; end + end + + class RealTransaction < Transaction + def materialize! + if isolation_level + connection.begin_isolated_db_transaction(isolation_level) + else + connection.begin_db_transaction + end + + super + end + + def rollback + connection.rollback_db_transaction if materialized? + @state.full_rollback! + end + + def commit + connection.commit_db_transaction if materialized? + @state.full_commit! + 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 + ) + else + SavepointTransaction.new( + @connection, + "active_record_#{@stack.size}", + @stack.last, + isolation: isolation, + joinable: joinable, + run_commit_callbacks: run_commit_callbacks + ) + end + + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true + else + transaction.materialize! + 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 materialize_transactions + return if @materializing_transactions + return unless @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 + + def commit_transaction + @connection.lock.synchronize do + transaction = @stack.last + + begin + transaction.before_commit_records + ensure + @stack.pop + end + + transaction.commit + transaction.commit_records + end + end + + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= @stack.pop + transaction.rollback unless transaction.state.invalidated? + transaction.rollback_records + end + end + + def within_new_transaction(isolation: nil, joinable: true) + @connection.lock.synchronize do + transaction = begin_transaction(isolation: isolation, joinable: joinable) + ret = yield + completed = true + ret + rescue Exception => error + if transaction + transaction.state.invalidate! if error.is_a? ActiveRecord::TransactionRollbackError + rollback_transaction + after_failure_actions(transaction, error) + end + + raise + ensure + if transaction + if error + # @connection still holds an open or invalid transaction, so we must not + # put it back in the pool for reuse. + @connection.throw_away! unless transaction.state.rolledback? + else + if Thread.current.status == "aborting" + rollback_transaction + else + if !completed && transaction.written + ActiveSupport::Deprecation.warn(<<~EOW) + Using `return`, `break` or `throw` to exit a transaction block is + deprecated without replacement. If the `throw` came from + `Timeout.timeout(duration)`, pass an exception class as a second + argument so it doesn't use `throw` to abort its block. This results + in the transaction being committed, but in the next release of Rails + it will rollback. + EOW + end + begin + commit_transaction + rescue Exception + rollback_transaction(transaction) unless transaction.state.completed? + raise + end + end + end + end + end + end + + def open_transactions + @stack.size + end + + def current_transaction + @stack.last || NULL_TRANSACTION + end + + private + NULL_TRANSACTION = NullTransaction.new + + # 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_adapter.rb new file mode 100644 index 0000000000..9a02270880 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_adapter.rb @@ -0,0 +1,758 @@ +# frozen_string_literal: true + +require "set" +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/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 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.connection}[rdoc-ref:ConnectionHandling#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)*|/\*(?:[^\*]|\*[^/])*\*/}m + + attr_accessor :pool + attr_reader :visitor, :owner, :logger, :lock + alias :in_use? :owner + + 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 + + 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.quoted_column_names # :nodoc: + @quoted_column_names ||= {} + end + + def self.quoted_table_names # :nodoc: + @quoted_table_names ||= {} + end + + def initialize(connection, logger = nil, config = {}) # :nodoc: + super() + + @connection = connection + @owner = nil + @instrumenter = ActiveSupport::Notifications.instrumenter + @logger = logger + @config = config + @pool = ActiveRecord::ConnectionAdapters::NullPool.new + @idle_since = Concurrent.monotonic_time + @visitor = arel_visitor + @statements = build_statement_pool + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + + @prepared_statements = self.class.type_cast_config_to_boolean( + config.fetch(:prepared_statements, true) + ) + + @advisory_locks_enabled = self.class.type_cast_config_to_boolean( + config.fetch(:advisory_locks, true) + ) + end + + def replica? + @config[:replica] || false + end + + def use_metadata_table? + @config.fetch(:use_metadata_table, true) + end + + # Determines whether writes are currently being prevented. + # + # Returns true if the connection is a replica. + # + # If the application is using legacy handling, returns + # true if +connection_handler.prevent_writes+ is set. + # + # If the application is using the new connection handling + # will return true based on +current_preventing_writes+. + def preventing_writes? + return true if replica? + return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling + return false if connection_klass.nil? + + connection_klass.current_preventing_writes + end + + def migrations_paths # :nodoc: + @config[:migrations_paths] || Migrator.migrations_paths + end + + def migration_context # :nodoc: + MigrationContext.new(migrations_paths, schema_migration) + end + + def schema_migration # :nodoc: + @schema_migration ||= begin + conn = self + spec_name = conn.pool.pool_config.connection_specification_name + + return ActiveRecord::SchemaMigration if spec_name == "ActiveRecord::Base" + + schema_migration_name = "#{spec_name}::SchemaMigration" + + Class.new(ActiveRecord::SchemaMigration) do + define_singleton_method(:name) { schema_migration_name } + define_singleton_method(:to_s) { schema_migration_name } + + self.connection_specification_name = spec_name + end + end + 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: + Thread.current[:ar_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 == Thread.current + msg << "it is already leased by the current thread." + else + msg << "it is already in use by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + raise ActiveRecordError, msg + end + + @owner = Thread.current + end + + def connection_klass # :nodoc: + @pool.connection_klass + end + + def schema_cache + @pool.get_schema_cache(self) + end + + def schema_cache=(cache) + cache.connection = self + @pool.set_schema_cache(cache) + end + + # this method must only be called while holding connection pool's mutex + def expire + if in_use? + if @owner != Thread.current + raise ActiveRecordError, "Cannot expire connection, " \ + "it is owned by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + + @idle_since = Concurrent.monotonic_time + @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 != Thread.current + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = Thread.current + 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? + Concurrent.monotonic_time - @idle_since + 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) + raise NotImplementedError + 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 + + # 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 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 check constraints? + def supports_check_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 + + # 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 + + 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 + + # CONNECTION MANAGEMENT ==================================== + + # 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 call super if they + # override the default implementation. + def reconnect! + clear_cache! + reset_transaction + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + clear_cache! + reset_transaction + 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. + # + # Prevent @connection's finalizer from touching the socket, or + # otherwise communicating with its server, when it is collected. + if schema_cache.connection == self + schema_cache.connection = nil + end + 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. + # + # The default implementation does nothing; the implementation should be + # overridden by concrete adapters. + def reset! + # this should be overridden by concrete adapters + 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! + @lock.synchronize { @statements.clear } if @statements + 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! + reconnect! unless active? + 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. + def raw_connection + disable_lazy_transactions! + @connection + 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: + schema_cache.database_version + end + + def check_version # :nodoc: + end + + private + def type_map + @type_map ||= Type::TypeMap.new.tap do |mapping| + initialize_type_map(mapping) + end + end + + def initialize_type_map(m = type_map) + 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 reload_type_map + type_map.clear + initialize_type_map + 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 register_class_with_precision(mapping, key, klass) + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + 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 + + def translate_exception_class(e, sql, binds) + message = "#{e.class.name}: #{e.message}" + + exception = translate_exception( + e, message: message, sql: sql, binds: binds + ) + exception.set_backtrace e.backtrace + exception + end + + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc: + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection: self) do + @lock.synchronize do + yield + end + rescue => e + raise translate_exception_class(e, sql, binds) + end + end + + def translate_exception(exception, message:, sql:, binds:) + # override in derived class + case exception + when RuntimeError + exception + else + ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds) + end + end + + def without_prepared_statement?(binds) + !prepared_statements || binds.empty? + 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: {}) + ActiveRecord::Result.new(columns, rows, column_types) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new file mode 100644 index 0000000000..e065721eef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -0,0 +1,855 @@ +# 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/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::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 }, + 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 + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + 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 >= "8.0.1" + end + + def supports_expression_index? + !mariadb? && database_version >= "8.0.13" + end + + def supports_transaction_isolation? + 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.2.1" + else + database_version >= "8.0.16" + end + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + mariadb? || database_version >= "5.6.4" + 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 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 =========================================== + + # The two drivers have slightly different ways of yielding hashes of results, so + # this method must be implemented to provide a uniform interface. + def each_hash(result) # :nodoc: + raise NotImplementedError + end + + # 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}") + end + end + + # CONNECTION MANAGEMENT ==================================== + + def clear_cache! # :nodoc: + reload_type_map + super + end + + #-- + # DATABASE STATEMENTS ====================================== + #++ + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.query(sql) + end + end + end + + # Mysql2Adapter doesn't have to free a result after using it, but we use this method + # to write stuff in an abstract way without concerning ourselves about whether it + # needs to be explicitly freed or not. + def execute_and_free(sql, name = nil) # :nodoc: + yield execute(sql, name) + end + + def begin_db_transaction + execute("BEGIN", "TRANSACTION") + end + + def begin_isolated_db_transaction(isolation) + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + begin_db_transaction + end + + def commit_db_transaction #:nodoc: + execute("COMMIT", "TRANSACTION") + end + + def exec_rollback_db_transaction #:nodoc: + execute("ROLLBACK", "TRANSACTION") + end + + def empty_insert_statement_value(primary_key = nil) + "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) + 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) + end + + # Drops a table 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. + def drop_table(table_name, **options) + schema_cache.clear_data_source_cache!(table_name.to_s) + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' 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: + default = extract_new_default_value(default_or_changes) + change_column table_name, column_name, nil, default: default + end + + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + 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 + + 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: + 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) + + create_index = CreateIndexDefinition.new(index, algorithm) + execute schema_creation.accept(create_index) + 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) + + fk_info = 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', + 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 + + fk_info.map do |row| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + + ForeignKeyDefinition.new(table_name, row["to_table"], options) + end + end + + def check_constraints(table_name) + if supports_check_constraints? + scope = quoted_scope(table_name) + + chk_info = exec_query(<<~SQL, "SCHEMA") + 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 + + chk_info.map do |row| + options = { + name: row["name"] + } + expression = row["expression"] + expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql + 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") + 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: + sql = +"INSERT #{insert.into} #{insert.values_list}" + + if insert.skip_duplicates? + no_op_column = quote_column_name(insert.keys.first) + sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}" + elsif insert.update_duplicates? + sql << " ON DUPLICATE KEY UPDATE " + sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" } + sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",") + end + + sql + end + + def check_version # :nodoc: + if database_version < "5.5.8" + raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." + end + end + + private + def initialize_type_map(m = type_map) + 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(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.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2) + m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2) + 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 + + # 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_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_QUERY_TIMEOUT = 3024 + ER_FK_INCOMPATIBLE_COLUMNS = 3780 + + 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) + else + super + end + when ER_DB_CREATE_EXISTS + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) + when ER_DUP_ENTRY + RecordNotUnique.new(message, sql: sql, binds: binds) + 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) + when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS + mismatched_foreign_key(message, sql: sql, binds: binds) + when ER_CANNOT_CREATE_TABLE + if message.include?("errno: 150") + mismatched_foreign_key(message, sql: sql, binds: binds) + else + super + end + when ER_DATA_TOO_LONG + ValueTooLong.new(message, sql: sql, binds: binds) + when ER_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds) + when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT + NotNullViolation.new(message, sql: sql, binds: binds) + when ER_LOCK_DEADLOCK + Deadlocked.new(message, sql: sql, binds: binds) + when ER_LOCK_WAIT_TIMEOUT + LockWaitTimeout.new(message, sql: sql, binds: binds) + when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT + StatementTimeout.new(message, sql: sql, binds: binds) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message, sql: sql, binds: binds) + else + super + end + end + + def change_column_for_alter(table_name, column_name, type, **options) + column = column_for(table_name, column_name) + type ||= column.sql_type + + unless options.key?(:default) + options[:default] = column.default + end + + unless options.key?(:null) + options[:null] = column.null + end + + unless options.key?(:comment) + options[:comment] = column.comment + end + + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, **options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + 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 = 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_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 + variables = @config.fetch(:variables, {}).stringify_keys + + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 + + # 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.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 + # or else nil; compact to clear nils out + end.compact.join(", ") + + # ...and send them all in one query + execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA") + end + + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) + end + end + + def create_table_info(table_name) # :nodoc: + 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(message, sql:, binds:) + match = %r/ + (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?\w+)`?.+? + FOREIGN\s+KEY\s*\(`?(?\w+)`?\)\s* + REFERENCES\s*(`?(?\w+)`?)\s*\(`?(?\w+)`?\) + /xmi.match(sql) + + options = { + message: message, + sql: sql, + binds: binds, + } + + 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 + + MismatchedForeignKey.new(**options) + end + + def version_string(full_version_string) + full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] + end + + # Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")). + # TODO: Remove the constant alias once Rails 6.1 has released. + MysqlString = Type::String # :nodoc: + + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/column.rb new file mode 100644 index 0000000000..85521ce9ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/column.rb @@ -0,0 +1,109 @@ +# 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 + + 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 + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/deduplicable.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/deduplicable.rb new file mode 100644 index 0000000000..030fd171be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/legacy_pool_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/legacy_pool_manager.rb new file mode 100644 index 0000000000..c6e216e864 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/legacy_pool_manager.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class LegacyPoolManager # :nodoc: + def initialize + @name_to_pool_config = {} + end + + def shard_names + @name_to_pool_config.keys + end + + def pool_configs(_ = nil) + @name_to_pool_config.values + end + + def remove_pool_config(_, shard) + @name_to_pool_config.delete(shard) + end + + def get_pool_config(_, shard) + @name_to_pool_config[shard] + end + + def set_pool_config(role, shard, pool_config) + if pool_config + @name_to_pool_config[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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/column.rb new file mode 100644 index 0000000000..c21529b0a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/column.rb @@ -0,0 +1,27 @@ +# 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 + + def virtual? + /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/database_statements.rb new file mode 100644 index 0000000000..73a828764c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module DatabaseStatements + # Returns an ActiveRecord::Result instance. + def select_all(*, **) # :nodoc: + result = if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + @connection.abandon_results! + result + end + + def query(sql, name = nil) # :nodoc: + execute(sql, name).to_a + end + + READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp( + :desc, :describe, :set, :show, :use + ) # :nodoc: + private_constant :READ_QUERY + + def write_query?(sql) # :nodoc: + !READ_QUERY.match?(sql) + end + + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + start = Concurrent.monotonic_time + result = exec_query(sql, "EXPLAIN", binds) + elapsed = Concurrent.monotonic_time - start + + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + super + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + if without_prepared_statement?(binds) + execute_and_free(sql, name) do |result| + if result + build_result(columns: result.fields, rows: result.to_a) + else + build_result(columns: [], rows: []) + end + end + else + exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result| + if result + build_result(columns: result.fields, rows: result.to_a) + else + build_result(columns: [], rows: []) + end + end + end + end + + def exec_delete(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + @lock.synchronize do + execute_and_free(sql, name) { @connection.affected_rows } + end + else + exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows } + end + end + alias :exec_update :exec_delete + + private + def execute_batch(statements, name = nil) + combine_multi_statements(statements).each do |statement| + execute(statement, name) + end + @connection.abandon_results! + end + + def default_insert_value(column) + super unless column.auto_increment? + end + + def last_inserted_id(result) + @connection.last_id + 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 with_multi_statements + multi_statements_was = multi_statements_enabled? + + unless multi_statements_was + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + end + + yield + ensure + unless multi_statements_was + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + 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 + + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + if cache_stmt + stmt = @statements[sql] ||= @connection.prepare(sql) + else + stmt = @connection.prepare(sql) + end + + begin + result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + stmt.execute(*type_casted_binds) + end + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e + end + + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 0000000000..ed3e68132b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/quoting.rb new file mode 100644 index 0000000000..4a88ed1834 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/quoting.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveRecord + module ConnectionAdapters + module MySQL + module Quoting # :nodoc: + def quote_column_name(name) + self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`" + end + + def quote_table_name(name) + self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze + end + + def unquoted_true + 1 + end + + def unquoted_false + 0 + end + + def quoted_date(value) + if supports_datetime_with_precision? + super + else + super.sub(/\.\d{6}\z/, "") + end + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \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 + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # `table_name`.`column_name` | function(one or no argument) + ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\) + ) + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + # Override +_type_cast+ we pass to mysql2 Date and Time objects instead + # of Strings since mysql2 is able to handle those classes more efficiently. + def _type_cast(value) + 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 ActiveRecord::Base.default_timezone == :utc + value.getutc + else + value.getlocal + end + when Date, Time + value + else + super + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_creation.rb new file mode 100644 index 0000000000..60dd7846e2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -0,0 +1,97 @@ +# 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_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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_definitions.rb new file mode 100644 index 0000000000..52a8a0b97d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -0,0 +1,103 @@ +# 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) + + ## + # :method: unsigned_float + # :call-seq: unsigned_float(*names, **options) + + ## + # :method: unsigned_decimal + # :call-seq: unsigned_decimal(*names, **options) + + included do + define_column_methods :blob, :tinyblob, :mediumblob, :longblob, + :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint, + :unsigned_float, :unsigned_decimal + end + end + + 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 aliased_types(name, fallback) + fallback + end + + def integer_like_primary_key_type(type, options) + options[:auto_increment] = true + type + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_dumper.rb new file mode 100644 index 0000000000..0eff3131b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -0,0 +1,91 @@ +# 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) + super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0 + end + + def schema_collation(column) + if column.collation + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= + @connection.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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_statements.rb new file mode 100644 index 0000000000..0f2e206edd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -0,0 +1,268 @@ +# 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 + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) 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 row[:Expression] + expression = row[:Expression] + 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 + end + + indexes.map do |index| + options = index.pop + + if expressions = options.delete(:expressions) + orders = options.delete(:orders) + lengths = options.delete(:lengths) + + columns = index[-1].map { |name| + [ name.to_sym, expressions[name] || +quote_column_name(name) ] + }.to_h + + index[-1] = add_options_for_index_columns( + columns, order: orders, length: lengths + ).values.join(", ") + end + + IndexDefinition.new(*index, **options) + 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 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 + + 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 schema_creation + MySQL::SchemaCreation.new(self) + end + + def create_table_definition(name, **options) + MySQL::TableDefinition.new(self, name, **options) + end + + def new_column_from_field(table_name, 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_function = nil, default + elsif type_metadata.extra == "DEFAULT_GENERATED" + default = +"(#{default})" unless default.start_with?("(") + 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 (SELECT * FROM information_schema.tables " + sql << " WHERE table_schema = #{scope[:schema]}) _subquery" + if scope[:type] || scope[:name] + conditions = [] + conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type] + conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name] + sql << " WHERE #{conditions.join(" AND ")}" + end + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql/type_metadata.rb new file mode 100644 index 0000000000..a7232fa249 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql2_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql2_adapter.rb new file mode 100644 index 0000000000..478b4e4729 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" + +gem "mysql2", "~> 0.5" +require "mysql2" + +module ActiveRecord + module ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects. + def mysql2_connection(config) + config = config.symbolize_keys + config[:flags] ||= 0 + + if config[:flags].kind_of? Array + config[:flags].push "FOUND_ROWS" + else + config[:flags] |= Mysql2::Client::FOUND_ROWS + end + + ConnectionAdapters::Mysql2Adapter.new( + ConnectionAdapters::Mysql2Adapter.new_client(config), + logger, + nil, + config, + ) + end + end + + module ConnectionAdapters + class Mysql2Adapter < AbstractMysqlAdapter + ER_BAD_DB_ERROR = 1049 + ADAPTER_NAME = "Mysql2" + + include MySQL::DatabaseStatements + + class << self + def new_client(config) + Mysql2::Client.new(config) + rescue Mysql2::Error => error + if error.error_number == ConnectionAdapters::Mysql2Adapter::ER_BAD_DB_ERROR + raise ActiveRecord::NoDatabaseError + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + end + end + + def initialize(connection, logger, connection_options, config) + superclass_config = config.reverse_merge(prepared_statements: false) + super(connection, logger, connection_options, superclass_config) + configure_connection + end + + def self.database_exists?(config) + !!ActiveRecord::Base.mysql2_connection(config) + rescue ActiveRecord::NoDatabaseError + false + 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 supports_lazy_transactions? + true + end + + # HELPER METHODS =========================================== + + def each_hash(result) # :nodoc: + if block_given? + result.each(as: :hash, symbolize_keys: true) do |row| + yield row + end + else + to_enum(:each_hash, result) + end + end + + def error_number(exception) + exception.error_number if exception.respond_to?(:error_number) + end + + #-- + # QUOTING ================================================== + #++ + + def quote_string(string) + @connection.escape(string) + rescue Mysql2::Error => error + raise translate_exception(error, message: error.message, sql: "", binds: []) + end + + #-- + # CONNECTION MANAGEMENT ==================================== + #++ + + def active? + @connection.ping + end + + def reconnect! + super + disconnect! + connect + end + alias :reset! :reconnect! + + # Disconnects from the database if already connected. + # Otherwise, this method does nothing. + def disconnect! + super + @connection.close + end + + def discard! # :nodoc: + super + @connection.automatic_close = false + @connection = nil + end + + private + def connect + @connection = self.class.new_client(@config) + configure_connection + end + + def configure_connection + @connection.query_options[:as] = :array + super + end + + def full_version + schema_cache.database_version.full_version_string + end + + def get_full_version + @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) + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_config.rb new file mode 100644 index 0000000000..681880a408 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_config.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolConfig # :nodoc: + include Mutex_m + + attr_reader :db_config, :connection_klass + attr_accessor :schema_cache + + INSTANCES = ObjectSpace::WeakMap.new + private_constant :INSTANCES + + class << self + def discard_pools! + INSTANCES.each_key(&:discard_pool!) + end + end + + def initialize(connection_klass, db_config) + super() + @connection_klass = connection_klass + @db_config = db_config + @pool = nil + INSTANCES[self] = self + end + + def connection_specification_name + if connection_klass.is_a?(String) + connection_klass + elsif connection_klass.primary_class? + "ActiveRecord::Base" + else + connection_klass.name + end + end + + def disconnect! + ActiveSupport::ForkTracker.check! + + return unless @pool + + synchronize do + return unless @pool + + @pool.automatic_reconnect = false + @pool.disconnect! + end + + nil + end + + def pool + ActiveSupport::ForkTracker.check! + + @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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_manager.rb new file mode 100644 index 0000000000..2ee0c3dc2a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/pool_manager.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolManager # :nodoc: + def initialize + @name_to_role_mapping = Hash.new { |h, k| h[k] = {} } + end + + def shard_names + @name_to_role_mapping.values.flat_map { |shard_map| shard_map.keys } + end + + def role_names + @name_to_role_mapping.keys + end + + def pool_configs(role = nil) + if role + @name_to_role_mapping[role].values + else + @name_to_role_mapping.flat_map { |_, shard_map| shard_map.values } + end + end + + def remove_role(role) + @name_to_role_mapping.delete(role) + end + + def remove_pool_config(role, shard) + @name_to_role_mapping[role].delete(shard) + end + + def get_pool_config(role, shard) + @name_to_role_mapping[role][shard] + end + + def set_pool_config(role, shard, pool_config) + if pool_config + @name_to_role_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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 0000000000..b9d10e9e2f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,53 @@ +# 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, **) + super + @serial = serial + end + + def serial? + @serial + end + + def array + sql_type_metadata.sql_type.end_with?("[]") + end + alias :array? :array + + def sql_type + super.delete_suffix("[]") + end + + def init_with(coder) + @serial = coder["serial"] + super + end + + def encode_with(coder) + coder["serial"] = @serial + super + end + + def ==(other) + other.is_a?(Column) && + super && + serial? == other.serial? + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + serial?.hash + end + end + end + PostgreSQLColumn = PostgreSQL::Column # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb new file mode 100644 index 0000000000..5f60064e4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module DatabaseStatements + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) + end + + # Queries the database and returns the results in an Array-like object + def query(sql, name = nil) #:nodoc: + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql).map_types!(@type_map_for_results).values + end + end + 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) + end + + # Executes an SQL statement, returning a PG::Result object on success + # or raising a PG::Error exception otherwise. + # 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(sql, name = nil) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql) + end + end + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + execute_and_clear(sql, name, binds, prepare: prepare) do |result| + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + case type = get_oid_type(ftype, fmod, fname) + when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean + # skip if a column has already been type casted by pg decoders + else types[fname] = type + end + end + build_result(columns: fields, rows: result.values, column_types: types) + end + end + + def exec_delete(sql, name = nil, binds = []) + execute_and_clear(sql, name, binds) { |result| result.cmd_tuples } + end + alias :exec_update :exec_delete + + def sql_for_insert(sql, pk, binds) # :nodoc: + 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 + + if pk = suppress_composite_primary_key(pk) + sql = "#{sql} RETURNING #{quote_column_name(pk)}" + end + + super + end + private :sql_for_insert + + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + if use_insert_returning? || pk == false + super + else + result = 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 + execute("BEGIN", "TRANSACTION") + end + + def begin_isolated_db_transaction(isolation) + begin_db_transaction + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + end + + # Commits a transaction. + def commit_db_transaction + execute("COMMIT", "TRANSACTION") + end + + # Aborts a transaction. + def exec_rollback_db_transaction + execute("ROLLBACK", "TRANSACTION") + end + + private + def execute_batch(statements, name = nil) + execute(combine_multi_statements(statements)) + 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) + exec_query("SELECT currval(#{quote(sequence_name)})", "SQL") + end + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 0000000000..086a5dcc15 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid.rb new file mode 100644 index 0000000000..1540b2ee28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid.rb @@ -0,0 +1,36 @@ +# 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/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/array.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 0000000000..0bbe98145a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.map(&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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 0000000000..e9a79526f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb new file mode 100644 index 0000000000..dc7079dda2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 0000000000..a3c60ecef6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/cidr.rb new file mode 100644 index 0000000000..a7f61e2375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,48 @@ +# 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 + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 0000000000..0fe72e01ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,23 @@ +# 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 0000000000..8fa052968e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,31 @@ +# 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 0000000000..e7d33855c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 0000000000..bae34472e1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/hstore.rb new file mode 100644 index 0000000000..8d4dacbd64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Hstore < Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable + + def type + :hstore + end + + def deserialize(value) + if value.is_a?(::String) + ::Hash[value.scan(HstorePair).map { |k, v| + v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + [k, v] + }] + else + value + end + 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 + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 0000000000..55be71fd26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/interval.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/interval.rb new file mode 100644 index 0000000000..0cb686033b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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) + value.seconds.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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb new file mode 100644 index 0000000000..e0216f1089 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb new file mode 100644 index 0000000000..687f75956b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb new file mode 100644 index 0000000000..13e77c6ab3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/money.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 0000000000..3703e9a646 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,41 @@ +# 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.gsub!(/[^-\d.]/, "") + when /^-?\D*+[\d.]+,\d{2}$/ # (2) + value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") + end + + super(value) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 0000000000..86d84b00ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/point.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/point.rb new file mode 100644 index 0000000000..f53b3fa602 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,64 @@ +# 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) + 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)) + 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/range.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 0000000000..64dafbd89f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,115 @@ +# 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 value == "empty" + 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(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 + + # 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 0000000000..80316b35fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 0000000000..203087bc36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,113 @@ +# 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_initial_load + known_type_names = @store.keys.map { |n| "'#{n}'" } + known_type_types = %w('r' 'e' 'd') + <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")] + WHERE + t.typname IN (%s) + OR t.typtype IN (%s) + OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure + OR t.typelem != 0 + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 0000000000..78f05429d8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,35 @@ +# 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} + + 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.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 + + private + def cast_value(value) + casted = value.to_s + casted if casted.match?(ACCEPTABLE_UUID) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 0000000000..88ef626a16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/oid/xml.rb new file mode 100644 index 0000000000..042f32fdc3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/quoting.rb new file mode 100644 index 0000000000..3d94d4bb36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Quoting + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + @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) + @connection.unescape_bytea(value) if value + end + + # Quotes strings for use in SQL input. + def quote_string(s) #:nodoc: + PG::Connection.escape(s) + 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: + self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze + end + + # Quotes schema names for use in SQL queries. + def quote_schema_name(name) + PG::Connection.quote_ident(name) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + # Quotes column names for use in SQL queries. + def quote_column_name(name) # :nodoc: + self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze + 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) && /\(\)/.match?(value) + 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 lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + + def column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \A + ( + (?: + # "table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)? + ) + (?:(?:\s+AS)?\s+(?:\w+|"\w+"))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # "table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)? + ) + (?:\s+ASC|\s+DESC)? + (?:\s+NULLS\s+(?:FIRST|LAST))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def lookup_cast_type(sql_type) + super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + end + + def _quote(value) + 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 + + def _type_cast(value) + 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) + else + super + end + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/referential_integrity.rb new file mode 100644 index 0000000000..dfb0029daf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -0,0 +1,43 @@ +# 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 0000000000..ad84b30a78 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < SchemaCreation # :nodoc: + private + def visit_AlterTable(o) + super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ") + end + + def visit_AddForeignKey(o) + super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? } + 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_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 add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new file mode 100644 index 0000000000..754cbbdd6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -0,0 +1,222 @@ +# 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) + + 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 + end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + attr_reader :unlogged + + def initialize(*, **) + super + @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables + end + + private + def integer_like_primary_key_type(type, options) + if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + attr_reader :constraint_validations + + def initialize(td) + super + @constraint_validations = [] + end + + def validate_constraint(name) + @constraint_validations << name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_dumper.rb new file mode 100644 index 0000000000..d201e40190 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -0,0 +1,49 @@ +# 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 prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_statements.rb new file mode 100644 index 0000000000..f9cb5613ce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -0,0 +1,794 @@ +# 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_name, **options) # :nodoc: + schema_cache.clear_data_source_cache!(table_name.to_s) + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' 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 = i.relnamespace + WHERE i.relkind IN ('i', 'I') + AND i.relname = #{index[:name]} + AND t.relname = #{table[:name]} + AND n.nspname = #{index[: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 + 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 = i.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] + + using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten + + orders = {} + opclasses = {} + + if indkey.include?(0) + columns = expressions + else + columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + SQL + + # 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)?\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, + comment: comment.presence + ) + end + end + + def table_options(table_name) # :nodoc: + if comment = table_comment(table_name) + { comment: comment } + end + 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 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) + execute "CREATE SCHEMA #{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 + execute("SET search_path TO #{schema_csv}", "SCHEMA") + @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) + 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: + 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 >= 100000 + 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 ? 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' + 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) + 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 + idx = "#{table_name}_pkey" + new_idx = "#{new_name}_pkey" + execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" + if seq && seq.identifier == "#{table_name}_#{pk}_seq" + new_seq = "#{new_name}_#{pk}_seq" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" + end + end + rename_table_indexes(table_name, new_name) + 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 + + # 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 change_column_null(table_name, column_name, null, default = nil) #:nodoc: + 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)} #{change_column_null_for_alter(table_name, column_name, null, default)}" + 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: + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + + create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists) + result = execute schema_creation.accept(create_index) + + execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment + result + 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) + + execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" + end + + def foreign_keys(table_name) + scope = quoted_scope(table_name) + fk_info = exec_query(<<~SQL, "SCHEMA") + 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 + 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| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:validate] = row["valid"] + + ForeignKeyDefinition.new(table_name, row["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 = exec_query(<<-SQL, "SCHEMA") + SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + WHERE c.contype = 'c' + AND t.relname = #{scope[:name]} + SQL + + check_info.map do |row| + options = { + name: row["conname"], + validate: row["valid"] + } + expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1] + + CheckConstraintDefinition.new(table_name, expression, options) + end + end + + # Maps logical Rails types to PostgreSQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: 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 + 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 + + private + def schema_creation + PostgreSQL::SchemaCreation.new(self) + end + + 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) + column_name, type, default, notnull, oid, fmod, collation, comment = field + type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) + default_value = extract_value_from_default(default) + default_function = extract_default_function(default_value, default) + + 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 + ) + 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 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) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, **options) + sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))] + sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + sqls + end + + def change_column_default_for_alter(table_name, column_name, default_or_changes) + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s" + if default.nil? + # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will + # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". + alter_column_query % "DROP DEFAULT" + else + alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}" + end + end + + def change_column_null_for_alter(table_name, column_name, null, default = nil) + "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + 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 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/type_metadata.rb new file mode 100644 index 0000000000..b7f6479357 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/utils.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 0000000000..e8caeb8132 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,80 @@ +# 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 = unquote(schema), unquote(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 + + private + def unquote(part) + if part && part.start_with?('"') + part[1..-2] + else + part + end + 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb new file mode 100644 index 0000000000..4d97ad420a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,967 @@ +# 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 ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects + def postgresql_connection(config) + conn_params = config.symbolize_keys.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) + + ConnectionAdapters::PostgreSQLAdapter.new( + ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params), + logger, + conn_params, + config, + ) + end + end + + module ConnectionAdapters + # 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 user name. + # * :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] && error.message.include?(conn_params[:dbname]) + raise ActiveRecord::NoDatabaseError + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + 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 line to your test.rb + # file: + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + class_attribute :create_unlogged_tables, default: false + + NATIVE_DATABASE_TYPES = { + primary_key: "bigserial primary key", + string: { name: "character varying" }, + text: { name: "text" }, + integer: { name: "integer", limit: 4 }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "timestamp" }, + 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" }, + } + + 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 >= 110_000 + end + + def supports_partial_index? + true + 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_validate_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_insert_returning? + true + end + + def supports_insert_on_conflict? + database_version >= 90500 + 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 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) + @connection.query "DEALLOCATE #{key}" if connection_active? + rescue PG::Error + end + + def connection_active? + @connection.status == PG::CONNECTION_OK + rescue PG::Error + false + end + end + + # Initializes and connects a PostgreSQL adapter. + def initialize(connection, logger, connection_parameters, config) + super(connection, logger, config) + + @connection_parameters = connection_parameters + + # @local_tz is initialized as nil to avoid warnings when connect tries to use it + @local_tz = nil + @max_identifier_length = nil + + configure_connection + add_pg_encoders + add_pg_decoders + + @type_map = Type::HashLookupTypeMap.new + initialize_type_map + @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"] + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true + end + + def self.database_exists?(config) + !!ActiveRecord::Base.postgresql_connection(config) + rescue ActiveRecord::NoDatabaseError + false + end + + # Is this connection alive and ready for queries? + def active? + @lock.synchronize do + @connection.query "SELECT 1" + end + true + rescue PG::Error + false + end + + # Close then reopen the connection. + def reconnect! + @lock.synchronize do + super + @connection.reset + configure_connection + rescue PG::ConnectionBad + connect + end + end + + def reset! + @lock.synchronize do + clear_cache! + reset_transaction + unless @connection.transaction_status == ::PG::PQTRANS_IDLE + @connection.query "ROLLBACK" + end + @connection.query "DISCARD ALL" + configure_connection + end + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + @lock.synchronize do + super + @connection.close rescue nil + end + end + + def discard! # :nodoc: + super + @connection.socket_io.reopen(IO::NULL) rescue nil + @connection = nil + end + + def native_database_types #:nodoc: + NATIVE_DATABASE_TYPES + end + + def set_standard_conforming_strings + 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 >= 90400 + 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) + exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { + reload_type_map + } + end + + def disable_extension(name) + exec_query("DROP EXTENSION IF EXISTS \"#{name}\" 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 + exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values + 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! + execute("SET SESSION AUTHORIZATION #{user}") + end + + def use_insert_returning? + @use_insert_returning + end + + # Returns the version of the connected PostgreSQL server. + def get_database_version # :nodoc: + @connection.server_version + 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 " + 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 + + sql << " RETURNING #{insert.returning}" if insert.returning + sql + end + + def check_version # :nodoc: + if database_version < 90300 + raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3." + end + end + + private + # 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) + ConnectionNotEstablished.new(exception) + else + super + end + when UNIQUE_VIOLATION + RecordNotUnique.new(message, sql: sql, binds: binds) + when FOREIGN_KEY_VIOLATION + InvalidForeignKey.new(message, sql: sql, binds: binds) + when VALUE_LIMIT_VIOLATION + ValueTooLong.new(message, sql: sql, binds: binds) + when NUMERIC_VALUE_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds) + when NOT_NULL_VIOLATION + NotNullViolation.new(message, sql: sql, binds: binds) + when SERIALIZATION_FAILURE + SerializationFailure.new(message, sql: sql, binds: binds) + when DEADLOCK_DETECTED + Deadlocked.new(message, sql: sql, binds: binds) + when DUPLICATE_DATABASE + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) + when LOCK_NOT_AVAILABLE + LockWaitTimeout.new(message, sql: sql, binds: binds) + when QUERY_CANCELED + QueryCanceled.new(message, sql: sql, binds: binds) + else + super + end + 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 initialize_type_map(m = type_map) + 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 + m.alias_type "float8", "float4" + 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.alias_type "timestamptz", "timestamp" + 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) + + register_class_with_precision m, "time", Type::Time + register_class_with_precision m, "timestamp", OID::DateTime + + 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 + + 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 + + def load_additional_types(oids = nil) + initializer = OID::TypeMapInitializer.new(type_map) + + 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 + query += "WHERE t.oid IN (%s)" % oids.join(", ") + else + query += initializer.query_conditions_for_initial_load + end + + execute_and_clear(query, "SCHEMA", []) do |records| + initializer.run(records) + end + end + + FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: + + def execute_and_clear(sql, name, binds, prepare: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + if !prepare || without_prepared_statement?(binds) + result = exec_no_cache(sql, name, binds) + else + result = exec_cache(sql, name, binds) + end + begin + ret = yield result + ensure + result.clear + end + ret + end + + def exec_no_cache(sql, name, binds) + materialize_transactions + mark_transaction_written_if_write(sql) + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + update_typemap_for_default_timezone + + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_params(sql, type_casted_binds) + end + end + end + + def exec_cache(sql, name, binds) + materialize_transactions + mark_transaction_written_if_write(sql) + update_typemap_for_default_timezone + + stmt_key = prepare_statement(sql, binds) + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds, stmt_key) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_prepared(stmt_key, type_casted_binds) + end + end + rescue ActiveRecord::StatementInvalid => e + raise unless is_cached_plan_failure?(e) + + # Nothing we can do if we are in a transaction because all commands + # will raise InFailedSQLTransaction + if in_transaction? + raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message) + else + @lock.synchronize do + # outside of transactions we can simply flush this query and retry + @statements.delete sql_key(sql) + end + retry + end + end + + # 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?(e) + pgerror = e.cause + 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) + @lock.synchronize do + sql_key = sql_key(sql) + unless @statements.key? sql_key + nextkey = @statements.next_key + begin + @connection.prepare nextkey, sql + rescue => e + raise translate_exception_class(e, sql, binds) + end + # Clear the queue + @connection.get_last_result + @statements[sql_key] = nextkey + end + @statements[sql_key] + end + end + + # Connects to a PostgreSQL server and sets up the adapter depending on the + # connected server's characteristics. + def connect + @connection = self.class.new_client(@connection_parameters) + configure_connection + add_pg_encoders + add_pg_decoders + 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 + if @config[:encoding] + @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] + + # Use standard-conforming strings so we don't have to do the E'...' dance. + set_standard_conforming_strings + + variables = @config.fetch(:variables, {}).stringify_keys + + # If using Active Record's time zone support configure the connection to return + # TIMESTAMP WITH ZONE types in UTC. + unless variables["timezone"] + if ActiveRecord::Base.default_timezone == :utc + variables["timezone"] = "UTC" + elsif @local_tz + variables["timezone"] = @local_tz + end + end + + # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse + 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 + execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") + elsif !v.nil? + execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") + end + 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 + 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 extract_table_ref_from_insert_sql(sql) + sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im] + $1.strip if $1 + end + + def arel_visitor + Arel::Visitors::PostgreSQL.new(self) + end + + def build_statement_pool + StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit])) + end + + def can_perform_case_insensitive_comparison_for?(column) + @case_insensitive_cache ||= {} + @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 + execute_and_clear(sql, "SCHEMA", []) do |result| + 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 + @connection.type_map_for_queries = map + end + + def update_typemap_for_default_timezone + if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder + decoder_class = ActiveRecord::Base.default_timezone == :utc ? + PG::TextDecoder::TimestampUtc : + PG::TextDecoder::TimestampWithoutTimeZone + + @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h) + @connection.type_map_for_results.add_coder(@timestamp_decoder) + @default_timezone = ActiveRecord::Base.default_timezone + end + end + + def add_pg_decoders + @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, + } + + 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 + coders = execute_and_clear(query, "SCHEMA", []) do |result| + result + .map { |row| construct_coder(row, coders_by_name[row["typname"]]) } + .compact + end + + map = PG::TypeMapByOid.new + coders.each { |coder| map.add_coder(coder) } + @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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/schema_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/schema_cache.rb new file mode 100644 index 0000000000..8daaae2715 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/schema_cache.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" + +module ActiveRecord + module ConnectionAdapters + class SchemaCache + def self.load_from(filename) + 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 + + attr_reader :version + attr_accessor :connection + + def initialize(conn) + @connection = conn + + @columns = {} + @columns_hash = {} + @primary_keys = {} + @data_sources = {} + @indexes = {} + end + + def initialize_dup(other) + 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) + reset_version! + + coder["columns"] = @columns + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["indexes"] = @indexes + coder["version"] = @version + coder["database_version"] = database_version + end + + def init_with(coder) + @columns = coder["columns"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @indexes = coder["indexes"] || {} + @version = coder["version"] + @database_version = coder["database_version"] + + derive_columns_hash_and_deduplicate_values + end + + def primary_keys(table_name) + @primary_keys.fetch(table_name) do + if data_source_exists?(table_name) + @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name)) + end + end + end + + # A cached lookup for table existence. + def data_source_exists?(name) + prepare_data_sources if @data_sources.empty? + return @data_sources[name] if @data_sources.key? name + + @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name) + end + + # Add internal cache for table with +table_name+. + def add(table_name) + if data_source_exists?(table_name) + primary_keys(table_name) + columns(table_name) + columns_hash(table_name) + indexes(table_name) + end + end + + def data_sources(name) + @data_sources[name] + end + + # Get the columns for a table + def columns(table_name) + @columns.fetch(table_name) do + @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name)) + end + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(table_name) + @columns_hash.fetch(table_name) do + @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze + end + end + + # Checks whether the columns hash is already cached for a table. + def columns_hash?(table_name) + @columns_hash.key?(table_name) + end + + def indexes(table_name) + @indexes.fetch(table_name) do + @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name)) + end + end + + def database_version # :nodoc: + @database_version ||= connection.get_database_version + end + + # Clears out internal caches + def clear! + @columns.clear + @columns_hash.clear + @primary_keys.clear + @data_sources.clear + @indexes.clear + @version = nil + @database_version = nil + 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!(name) + @columns.delete name + @columns_hash.delete name + @primary_keys.delete name + @data_sources.delete name + @indexes.delete name + end + + def dump_to(filename) + clear! + connection.data_sources.each { |table| add(table) } + open(filename) { |f| + if filename.include?(".dump") + f.write(Marshal.dump(self)) + else + f.write(YAML.dump(self)) + end + } + end + + def marshal_dump + reset_version! + + [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version] + end + + def marshal_load(array) + @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array + @indexes ||= {} + + derive_columns_hash_and_deduplicate_values + end + + private + def reset_version! + @version = connection.migration_context.current_version + 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 prepare_data_sources + connection.data_sources.each { |source| @data_sources[source] = true } + end + + def open(filename) + File.atomic_write(filename) do |file| + if File.extname(filename) == ".gz" + zipper = Zlib::GzipWriter.new file + yield zipper + zipper.flush + zipper.close + else + yield file + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sql_type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sql_type_metadata.rb new file mode 100644 index 0000000000..63f07b915d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb new file mode 100644 index 0000000000..ce3b1769d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb @@ -0,0 +1,144 @@ +# 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) + end + + def explain(arel, binds = []) + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) + end + + def execute(sql, name = nil) #:nodoc: + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute(sql) + end + end + end + + def exec_query(sql, name = nil, binds = [], prepare: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + # Don't cache statements if they are not prepared + unless prepare + stmt = @connection.prepare(sql) + begin + cols = stmt.columns + unless without_prepared_statement?(binds) + stmt.bind_params(type_casted_binds) + end + records = stmt.to_a + ensure + stmt.close + end + else + stmt = @statements[sql] ||= @connection.prepare(sql) + cols = stmt.columns + stmt.reset! + stmt.bind_params(type_casted_binds) + records = stmt.to_a + end + + build_result(columns: cols, rows: records) + end + end + end + + def exec_delete(sql, name = "SQL", binds = []) + exec_query(sql, name, binds) + @connection.changes + end + alias :exec_update :exec_delete + + def begin_isolated_db_transaction(isolation) #:nodoc + 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? + + Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted")) + @connection.read_uncommitted = true + begin_db_transaction + end + + def begin_db_transaction #:nodoc: + log("begin transaction", "TRANSACTION") { @connection.transaction } + end + + def commit_db_transaction #:nodoc: + log("commit transaction", "TRANSACTION") { @connection.commit } + reset_read_uncommitted + end + + def exec_rollback_db_transaction #:nodoc: + log("rollback transaction", "TRANSACTION") { @connection.rollback } + reset_read_uncommitted + end + + private + def reset_read_uncommitted + read_uncommitted = Thread.current.thread_variable_get("read_uncommitted") + return unless read_uncommitted + + @connection.read_uncommitted = read_uncommitted + end + + def execute_batch(statements, name = nil) + sql = combine_multi_statements(statements) + + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute_batch2(sql) + end + end + end + + def last_inserted_id(result) + @connection.last_insert_row_id + end + + def build_fixture_statements(fixture_set) + fixture_set.flat_map do |table_name, fixtures| + next if fixtures.empty? + fixtures.map { |fixture| build_fixture_sql([fixture], table_name) } + end.compact + end + + def build_truncate_statement(table_name) + "DELETE FROM #{quote_table_name(table_name)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 0000000000..832fdfe5c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/quoting.rb new file mode 100644 index 0000000000..9b74a774e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module Quoting # :nodoc: + def quote_string(s) + @connection.class.quote(s) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + def quote_table_name(name) + self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze + end + + def quote_column_name(name) + self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}") + 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 column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \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 + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # "table_name"."column_name" | function(one or no argument) + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\) + ) + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def _type_cast(value) + case value + when BigDecimal + 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.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_creation.rb new file mode 100644 index 0000000000..c461eeed5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaCreation < SchemaCreation # :nodoc: + private + def supports_index_using? + false + end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb new file mode 100644 index 0000000000..c9855019c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + + private + def integer_like_primary_key_type(type, options) + :primary_key + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb new file mode 100644 index 0000000000..621678ec65 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def default_primary_key?(column) + schema_type(column) == :integer + end + + def explicit_primary_key_default?(column) + column.bigint? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_statements.rb new file mode 100644 index 0000000000..d9698b01ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -0,0 +1,170 @@ +# 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) + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").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*(?.+))?\z/i =~ index_sql + + columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end + + 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.compact + end + + def add_foreign_key(from_table, to_table, **options) + 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) + 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) + 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 check_constraints(table_name) + table_sql = query_value(<<-SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote_table_name(table_name)} AND type = 'table' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote_table_name(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, **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 + + private + def schema_creation + SQLite3::SchemaCreation.new(self) + 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) + default = \ + case field["dflt_value"] + when /^null$/i + nil + when /^'(.*)'$/m + $1.gsub("''", "'") + when /^"(.*)"$/m + $1.gsub('""', '"') + else + field["dflt_value"] + end + + type_metadata = fetch_type_metadata(field["type"]) + Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"]) + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'table','view'" + + sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'" + 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'" + end + scope = {} + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3_adapter.rb new file mode 100644 index 0000000000..6bba324cec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -0,0 +1,561 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +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", "~> 1.4" +require "sqlite3" + +module ActiveRecord + module ConnectionHandling # :nodoc: + def sqlite3_connection(config) + config = config.symbolize_keys + + # Require database. + unless config[:database] + raise ArgumentError, "No database file specified. Missing argument: database" + end + + # Allow database path relative to Rails.root, but only if the database + # path is not the special path that tells sqlite to build a database only + # in memory. + if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:") + config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) + dirname = File.dirname(config[:database]) + Dir.mkdir(dirname) unless File.directory?(dirname) + end + + db = SQLite3::Database.new( + config[:database].to_s, + config.merge(results_as_hash: true) + ) + + ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end + + module ConnectionAdapters #:nodoc: + # 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" + + include SQLite3::Quoting + include SQLite3::SchemaStatements + include SQLite3::DatabaseStatements + + 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" }, + } + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + private + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + configure_connection + end + + def self.database_exists?(config) + config = config.symbolize_keys + if config[:database] == ":memory:" + true + else + database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database] + File.exist?(database_file) + end + 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_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 active? + !@connection.closed? + end + + def reconnect! + super + connect if @connection.closed? + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + super + @connection.close rescue 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, eg: 'UTF-8' + def encoding + @connection.encoding.to_s + end + + def supports_explain? + true + end + + def supports_lazy_transactions? + 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 + + # 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 + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_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) + end + + def add_column(table_name, column_name, type, **options) #:nodoc: + 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 do |_, fk_options| + fk_options[:column] == column_name.to_s + end + 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: + unless null || default.nil? + 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[column_name].instance_eval do + self.type = aliased_types(type.to_s, type) + self.options.merge!(options) + end + 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_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + def foreign_keys(table_name) + fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") + fk_info.map do |row| + options = { + column: row["from"], + primary_key: row["to"], + on_delete: extract_foreign_key_action(row["on_delete"]), + on_update: extract_foreign_key_action(row["on_update"]) + } + 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 " + sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" } + sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",") + end + + 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(*)")) + 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 + + private + # See https://www.sqlite.org/limits.html, + # the default value is 999 when not configured. + def bind_params_length + 999 + end + + def initialize_type_map(m = type_map) + super + register_class_with_limit m, %r(int)i, SQLite3Integer + end + + def table_structure(table_name) + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") + raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? + table_structure_with_collation(table_name, structure) + end + alias column_definitions table_structure + + # 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.to_sym == :primary_key || options[:primary_key] || + options[:null] == false && options[:default].nil? + 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 + + @definition.column(column_name, column.type, + limit: column.limit, default: column.default, + precision: column.precision, scale: column.scale, + null: column.null, collation: column.collation, + primary_key: column_name == from_primary_key + ) + end + + yield @definition if block_given? + end + copy_table_indexes(from, to, options[:rename] || {}) + copy_table_contents(from, to, + @definition.columns.map(&:name), + 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 + 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) } * "," + + 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) + elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i) + NotNullViolation.new(message, sql: sql, binds: binds) + elsif exception.message.match?(/FOREIGN KEY constraint failed/i) + InvalidForeignKey.new(message, sql: sql, binds: binds) + elsif exception.message.match?(/called on a closed database/i) + ConnectionNotEstablished.new(exception) + else + super + end + end + + COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze + + def table_structure_with_collation(table_name, basic_structure) + collation_hash = {} + 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"); + result = query_value(sql, "SCHEMA") + + if result + # Splitting with left parentheses and discarding the first part will return all + # columns separated with comma(,). + columns_string = result.split("(", 2).last + + columns_string.split(",").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 + end + + basic_structure.map do |column| + column_name = column["name"] + + if collation_hash.has_key? column_name + column["collation"] = collation_hash[column_name] + end + + column + end + else + basic_structure.to_a + 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 + @connection = ::SQLite3::Database.new( + @config[:database].to_s, + @config.merge(results_as_hash: true) + ) + configure_connection + end + + def configure_connection + @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout] + + execute("PRAGMA foreign_keys = ON", "SCHEMA") + 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) + end + ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/statement_pool.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 0000000000..0960feed84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,60 @@ +# 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 + + def delete(key) + dealloc cache[key] + cache.delete(key) + end + + private + def cache + @cache[Process.pid] + end + + def dealloc(stmt) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_handling.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_handling.rb new file mode 100644 index 0000000000..0aad91288a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/connection_handling.rb @@ -0,0 +1,405 @@ +# frozen_string_literal: true + +module ActiveRecord + 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, owner_name = resolve_config_for_connection(config_or_env) + connection_handler.establish_connection(db_config, owner_name: owner_name, 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 create a connection handler for switching between connections, + # look up the config hash using the +database_key+ and finally + # establishes 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 + # also supports read replicas. 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 = [] + + database.each do |role, database_key| + db_config, owner_name = resolve_config_for_connection(database_key) + handler = lookup_connection_handler(role.to_sym) + + self.connection_class = true + connections << handler.establish_connection(db_config, owner_name: owner_name, role: role) + end + + shards.each do |shard, database_keys| + database_keys.each do |role, database_key| + db_config, owner_name = resolve_config_for_connection(database_key) + handler = lookup_connection_handler(role.to_sym) + + self.connection_class = true + connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym) + end + end + + connections + end + + # Connects to a role (ex 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 + # + # The database kwarg is deprecated and will be removed in 6.2.0 without replacement. + def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk) + if legacy_connection_handling + if self != Base + raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling." + end + else + if self != Base && !abstract_class + raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes." + end + + if name != connection_specification_name && !primary_class? + raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection." + end + end + + if database && (role || shard) + raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments." + elsif database + ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.") + + if database.is_a?(Hash) + role, database = database.first + role = role.to_sym + end + + db_config, owner_name = resolve_config_for_connection(database) + handler = lookup_connection_handler(role) + + handler.establish_connection(db_config, owner_name: owner_name, role: role) + + with_handler(role, &blk) + elsif role || shard + unless role + raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`." + end + + with_role_and_shard(role, shard, prevent_writes, &blk) + else + raise ArgumentError, "must provide a `shard` and/or `role`." + end + 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 legacy_connection_handling + raise NotImplementedError, "connected_to_many is not available with legacy connection handling" + end + + if self != Base || classes.include?(Base) + raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base." + end + + prevent_writes = true if role == reading_role + + connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes } + yield + ensure + connected_to_stack.pop + 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) + if legacy_connection_handling + raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`." + end + + prevent_writes = true if role == reading_role + + self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] } + 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) + if legacy_connection_handling + connection_handler.while_preventing_writes(enabled, &block) + else + connected_to(role: current_role, prevent_writes: enabled, &block) + end + end + + # Returns true if role is the current connected role. + # + # ActiveRecord::Base.connected_to(role: :writing) do + # ActiveRecord::Base.connected_to?(role: :writing) #=> true + # ActiveRecord::Base.connected_to?(role: :reading) #=> false + # end + def connected_to?(role:, shard: ActiveRecord::Base.default_shard) + current_role == role.to_sym && current_shard == shard.to_sym + end + + def lookup_connection_handler(handler_key) # :nodoc: + if ActiveRecord::Base.legacy_connection_handling + handler_key ||= ActiveRecord::Base.writing_role + connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new + else + ActiveRecord::Base.connection_handler + end + end + + # Clears the query cache for all connections associated with the current thread. + def clear_query_caches_for_current_thread + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each_value do |handler| + clear_on_handler(handler) + end + else + clear_on_handler(ActiveRecord::Base.connection_handler) + 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. + def connection + retrieve_connection + end + + attr_writer :connection_specification_name + + # Return the connection specification name from the current class or its parent. + def connection_specification_name + if !defined?(@connection_specification_name) || @connection_specification_name.nil? + return self == Base ? Base.name : superclass.connection_specification_name + end + @connection_specification_name + end + + def primary_class? # :nodoc: + self == Base || defined?(ApplicationRecord) && self == ApplicationRecord + end + + # Returns the configuration of the associated connection as a hash: + # + # ActiveRecord::Base.connection_config + # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"} + # + # Please use only for reading. + def connection_config + connection_pool.db_config.configuration_hash + end + deprecate connection_config: "Use connection_db_config instead" + + # 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 connection_pool + connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished) + 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 = nil) + 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 clear_cache! # :nodoc: + connection.schema_cache.clear! + end + + delegate :clear_active_connections!, :clear_reloadable_connections!, + :clear_all_connections!, :flush_idle_connections!, to: :connection_handler + + private + def clear_on_handler(handler) + handler.all_connection_pools.each do |pool| + pool.connection.clear_query_cache if pool.active_connection? + end + end + + def resolve_config_for_connection(config_or_env) + raise "Anonymous class is not allowed." unless name + + owner_name = primary_class? ? Base.name : name + self.connection_specification_name = owner_name + + db_config = Base.configurations.resolve(config_or_env) + [db_config, self] + end + + def with_handler(handler_key, &blk) + handler = lookup_connection_handler(handler_key) + swap_connection_handler(handler, &blk) + end + + def with_role_and_shard(role, shard, prevent_writes) + prevent_writes = true if role == reading_role + + if ActiveRecord::Base.legacy_connection_handling + with_handler(role.to_sym) do + connection_handler.while_preventing_writes(prevent_writes) do + self.connected_to_stack << { shard: shard, klasses: [self] } + yield + end + end + else + self.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 + end + ensure + self.connected_to_stack.pop + end + + def swap_connection_handler(handler, &blk) # :nodoc: + old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler + return_value = yield + return_value.load if return_value.is_a? ActiveRecord::Relation + return_value + ensure + ActiveRecord::Base.connection_handler = old_handler + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/core.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/core.rb new file mode 100644 index 0000000000..630853a4b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/core.rb @@ -0,0 +1,796 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/filters" +require "active_support/parameter_filter" +require "concurrent/map" + +module ActiveRecord + module Core + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # + # Accepts a logger conforming to the interface of Log4r which is then + # passed on to any new database connections made and which can be + # retrieved on both a class and instance level by calling +logger+. + mattr_accessor :logger, instance_writer: false + + ## + # :singleton-method: + # + # Specifies if the methods calling database queries should be logged below + # their relevant queries. Defaults to false. + mattr_accessor :verbose_query_logs, instance_writer: false, default: false + + ## + # :singleton-method: + # + # Specifies the names of the queues used by background jobs. + mattr_accessor :queues, instance_accessor: false, default: {} + + ## + # :singleton-method: + # + # Specifies the job used to destroy associations in the background + class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false + + ## + # 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: db/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: db/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 fully resolved ActiveRecord::DatabaseConfigurations object + def self.configurations + @@configurations + end + + ## + # :singleton-method: + # 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. + mattr_accessor :default_timezone, instance_writer: false, default: :utc + + ## + # :singleton-method: + # 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. + mattr_accessor :schema_format, instance_writer: false, default: :ruby + + ## + # :singleton-method: + # 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. + mattr_accessor :error_on_ignored_order, instance_writer: false, default: false + + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + mattr_accessor :timestamped_migrations, instance_writer: false, default: true + + ## + # :singleton-method: + # Specify whether schema dump should happen at the end of the + # db:migrate rails 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. + mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true + + ## + # :singleton-method: + # 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. + mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path + + ## + # :singleton-method: + # Specify a threshold for the size of query result sets. If the number of + # records in the set exceeds the threshold, a warning is logged. This can + # be used to identify queries which load thousands of records and + # potentially cause memory bloat. + mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false + + ## + # :singleton-method: + # Show a warning when Rails couldn't parse your database.yml + # for multiple databases. + mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false + + mattr_accessor :maintain_test_schema, instance_accessor: false + + class_attribute :belongs_to_required_by_default, instance_accessor: false + + ## + # :singleton-method: + # Set the application to log or raise when an association violates strict loading. + # Defaults to :raise. + mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise + + class_attribute :strict_loading_by_default, instance_accessor: false, default: false + + mattr_accessor :writing_role, instance_accessor: false, default: :writing + + mattr_accessor :reading_role, instance_accessor: false, default: :reading + + mattr_accessor :has_many_inversing, instance_accessor: false, default: false + + class_attribute :default_connection_handler, instance_writer: false + + class_attribute :default_role, instance_writer: false + + class_attribute :default_shard, instance_writer: false + + mattr_accessor :legacy_connection_handling, instance_writer: false, default: true + + self.filter_attributes = [] + + def self.connection_handler + Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler + end + + def self.connection_handler=(handler) + Thread.current.thread_variable_set(:ar_connection_handler, handler) + end + + def self.connection_handlers + unless legacy_connection_handling + raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers." + end + + @@connection_handlers ||= {} + end + + def self.connection_handlers=(handlers) + unless legacy_connection_handling + raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers." + end + + @@connection_handlers = handlers + 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 + if ActiveRecord::Base.legacy_connection_handling + connection_handlers.key(connection_handler) || default_role + else + 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_classes) + end + + default_role + end + 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_classes) + 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 + if legacy_connection_handling + connection_handler.prevent_writes + else + 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_classes) + end + + false + end + end + + def self.connected_to_stack # :nodoc: + if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack) + connected_to_stack + else + connected_to_stack = Concurrent::Array.new + Thread.current.thread_variable_set(:ar_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_classes # :nodoc: + klass = self + + until klass == Base + break if klass.connection_class? + klass = klass.superclass + end + + klass + end + + def self.allow_unsafe_raw_sql # :nodoc: + ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 6.2") + end + + def self.allow_unsafe_raw_sql=(value) # :nodoc: + ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 6.2") + end + + self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new + self.default_role = writing_role + self.default_shard = :default + + def self.strict_loading_violation!(owner:, reflection:) # :nodoc: + case action_on_strict_loading_violation + when :raise + message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded." + 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 inherited(child_class) # :nodoc: + # initialize cache at class definition for thread safety + child_class.initialize_find_by_cache + unless child_class.base_class? + klass = self + until klass.base_class? + klass.initialize_find_by_cache + klass = klass.superclass + end + end + super + 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) + + key = primary_key + + statement = cached_find_by_statement(key) { |params| + where(key => params.bind).limit(1) + } + + statement.execute([id], connection).first || + raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, 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 + value = value.public_send(pkey) if value.respond_to?(pkey) + end + + if !columns_hash.key?(key) || StatementCache.unsupported_value?(value) + return super + end + + h[key] = value + end + + keys = hash.keys + statement = cached_find_by_statement(keys) { |params| + wheres = keys.index_with { params.bind } + where(wheres).limit(1) + } + + begin + statement.execute(hash.values, connection).first + rescue TypeError + raise ActiveRecord::StatementInvalid + end + end + + def find_by!(*args) # :nodoc: + find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name)) + 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 defined?(@filter_attributes) + @filter_attributes + else + superclass.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 defined?(@filter_attributes) + @inspection_filter ||= begin + mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED) + ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask) + end + else + superclass.inspection_filter + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect # :nodoc: + if self == Base + super + elsif abstract_class? + "#{super}(abstract)" + elsif !connected? + "#{super} (call '#{super}.connection' to establish a connection)" + elsif table_exists? + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", " + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Overwrite the default class equality method to provide support for decorated models. + def ===(object) # :nodoc: + object.is_a?(self) + end + + # Returns an instance of Arel::Table loaded with the current table name. + # + # class Post < ActiveRecord::Base + # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) } + # end + def arel_table # :nodoc: + @arel_table ||= Arel::Table.new(table_name, klass: self) + end + + def arel_attribute(name, table = arel_table) # :nodoc: + table[name] + end + deprecate :arel_attribute + + def predicate_builder # :nodoc: + @predicate_builder ||= PredicateBuilder.new(table_metadata) + end + + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + + def _internal? # :nodoc: + false + end + + def cached_find_by_statement(key, &block) # :nodoc: + cache = @find_by_statement_cache[connection.prepared_statements] + cache.compute_if_absent(key) { StatementCache.create(connection, &block) } + end + + private + def relation + relation = Relation.create(self) + + if finder_needs_type_condition? && !ignore_default_scope? + relation.where!(type_condition) + else + relation + end + end + + def table_metadata + TableMetadata.new(self, arel_table) + 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 + + assign_attributes(attributes) if attributes + + 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(self.class, 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). + + ## + def initialize_dup(other) # :nodoc: + @attributes = @attributes.deep_dup + @attributes.reset(@primary_key) + + _run_initialize_callbacks + + @new_record = true + @previously_new_record = false + @destroyed = false + @_start_transaction_state = nil + + super + 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 + + # 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) && + !id.nil? && + 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 + if id + 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! + # user.comments.to_a + # => ActiveRecord::StrictLoadingViolationError + def strict_loading! + @strict_loading = true + end + + # Marks this record as read only. + def readonly! + @readonly = true + end + + def connection_handler + self.class.connection_handler + end + + # Returns the contents of the record as a nicely formatted string. + def inspect + # We check defined?(@attributes) not to issue warnings if the object is + # allocated but not initialized. + inspection = if defined?(@attributes) && @attributes + self.class.attribute_names.collect do |name| + if _has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + end.compact.join(", ") + else + "not initialized" + end + + "#<#{self.class} #{inspection}>" + 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 defined?(@attributes) && @attributes + attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) } + pp.seplist(attr_names, proc { pp.text "," }) do |attr_name| + pp.breakable " " + pp.group(1) do + pp.text attr_name + pp.text ":" + pp.breakable + value = _read_attribute(attr_name) + value = inspection_filter.filter_param(attr_name, value) unless value.nil? + pp.pp value + end + end + else + pp.breakable " " + pp.text "not initialized" + end + end + end + + # Returns a hash of the given methods with their names as keys and returned values as values. + def slice(*methods) + methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access + end + + # Returns an array of the values returned by the given methods. + def values_at(*methods) + methods.flatten.map! { |method| public_send(method) } + 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 + @primary_key = self.class.primary_key + @readonly = false + @previously_new_record = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @_start_transaction_state = nil + @strict_loading = self.class.strict_loading_by_default + + self.class.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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/counter_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/counter_cache.rb new file mode 100644 index 0000000000..2d91bd3da5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/counter_cache.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Counter Cache + module CounterCache + extend ActiveSupport::Concern + + 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) + + 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 + + updates = { counter_name => object.send(counter_association).count(:all) } + + 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) + end + + 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 comment_count by 1, and + # # increment the action_count by 1 + # Post.update_counters 5, comment_count: -1, action_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) - 1, + # # action_count = COALESCE(action_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], comment_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1 + # # WHERE id IN (10, 15) + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # # and update the updated_at value for each counter. + # Post.update_counters [10, 15], comment_count: 1, touch: true + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1, + # # `updated_at` = '2016-10-13T09:59:23-05:00' + # # WHERE id IN (10, 15) + def update_counters(id, counters) + 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. + # * :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 + # # and update the updated_at value. + # DiscussionBoard.increment_counter(:posts_count, 5, touch: true) + def increment_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => 1, 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. + # * :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 + # # and update the updated_at value. + # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true) + def decrement_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => -1, touch: touch) + end + end + + private + def _create_record(attribute_names = self.attribute_names) + id = super + + each_counter_cached_associations do |association| + association.increment_counters + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + each_counter_cached_associations do |association| + foreign_key = association.reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + association.decrement_counters + end + end + end + + affected_rows + end + + def each_counter_cached_associations + _reflections.each do |name, reflection| + yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations.rb new file mode 100644 index 0000000000..c33f33a75e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +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 + # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig + # objects (either a HashConfig or UrlConfig) that are constructed from the + # application's database configuration hash or URL string. + class DatabaseConfigurations + class InvalidConfigurationError < StandardError; end + + attr_reader :configurations + delegate :any?, to: :configurations + + 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_replicas: 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. + # * include_replicas: Determines whether to include replicas in + # the returned list. Most of the time we're only iterating over the write + # connection (i.e. migrations don't need to run for the write and read connection). + # Defaults to +false+. + def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false) + if spec_name + name = spec_name + ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 6.2") + end + + env_name ||= default_env if name + configs = env_with_configs(env_name) + + unless include_replicas + configs = configs.select do |db_config| + !db_config.replica? + end + end + + if name + configs.find do |db_config| + db_config.name == name + end + else + configs + end + end + + # Returns the config hash that corresponds with the environment + # + # If the application has multiple databases +default_hash+ will + # return the first config hash for the environment. + # + # { database: "my_db", adapter: "mysql2" } + def default_hash(env = default_env) + default = find_db_config(env) + default.configuration_hash if default + end + alias :[] :default_hash + deprecate "[]": "Use configs_for", default_hash: "Use configs_for" + + # 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) + configurations + .sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] } + .find do |db_config| + db_config.env_name == env.to_s || + (db_config.for_current_env? && db_config.name == env.to_s) + 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 + + # Returns the DatabaseConfigurations object as a Hash. + def to_h + configurations.inject({}) do |memo, db_config| + memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys) + end + end + deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes." + + # Checks if the application's configurations are empty. + # + # Aliased to blank? + 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.all? { |_, v| v.is_a?(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 databases configurations are: + + #{build_configuration_sentence} + MSG + end + end + + def build_configuration_sentence + configs = configs_for(include_replicas: 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) + if config.has_key?(:url) + url = config[:url] + config_without_url = config.dup + config_without_url.delete :url + + UrlConfig.new(env_name, name, url, config_without_url) + else + HashConfig.new(env_name, name, config) + end + 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.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/connection_url_resolver.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/connection_url_resolver.rb new file mode 100644 index 0000000000..13fddb98f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/connection_url_resolver.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +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 = @uri.scheme && @uri.scheme.tr("-", "_") + @adapter = "postgresql" if @adapter == "postgres" + + 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::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.merge( + adapter: @adapter, + username: uri.user, + password: uri.password, + port: uri.port, + database: database_from_path, + host: uri.hostname + ) + end + 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.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/database_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/database_config.rb new file mode 100644 index 0000000000..6ad9b3922e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/database_config.rb @@ -0,0 +1,80 @@ +# 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 + + attr_accessor :owner_name + + def initialize(env_name, name) + @env_name = env_name + @name = name + end + + def spec_name + @name + end + deprecate spec_name: "please use name instead" + + def config + raise NotImplementedError + end + + def adapter_method + "#{adapter}_connection" + 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 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/hash_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/hash_config.rb new file mode 100644 index 0000000000..18b109ce6f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/hash_config.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # A HashConfig object is created for each database configuration entry that + # is created from a hash. + # + # A hash config: + # + # { "development" => { "database" => "db_name" } } + # + # Becomes: + # + # # + # + # ==== 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". + # * :config - The config hash. This is the hash that contains the + # database adapter, name, and other important information for database + # connections. + class HashConfig < DatabaseConfig + attr_reader :configuration_hash + def initialize(env_name, name, configuration_hash) + super(env_name, name) + @configuration_hash = configuration_hash.symbolize_keys.freeze + end + + def config + ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 6.2.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys") + configuration_hash.stringify_keys + 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 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 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] + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/url_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/url_config.rb new file mode 100644 index 0000000000..a8ee4f64fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/database_configurations/url_config.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # 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: + # + # # + # + # ==== Options + # + # * :env_name - The Rails environment, ie "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. + class UrlConfig < HashConfig + attr_reader :url + + def initialize(env_name, name, url, configuration_hash = {}) + super(env_name, name, configuration_hash) + + @url = url + @configuration_hash = @configuration_hash.merge(build_url_hash).freeze + end + + private + # Return a Hash that can be merged into the main config that represents + # the passed in url + def build_url_hash + if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) } + { url: url } + else + ConnectionUrlResolver.new(url).to_hash + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/delegated_type.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/delegated_type.rb new file mode 100644 index 0000000000..4a4d27fb29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/delegated_type.rb @@ -0,0 +1,209 @@ +# 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, created_at, updated_at, entryable_type, entryable_id ] + # 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 ] + # class Message < ApplicationRecord + # include Entryable + # has_rich_text :content + # end + # + # # Schema: comments[ id, content ] + # 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.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 + #
+ # Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %> + #
+ # + # # 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 + # + # 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) + # create! entryable: Comment.new(content: content), creator: creator + # end + # end + # + # == 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. + 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 + # + # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc. + # + # 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? + def delegated_type(role, types:, **options) + belongs_to role, options.delete(:scope), **options.merge(polymorphic: true) + define_delegated_type_methods role, types: types + end + + private + def define_delegated_type_methods(role, types:) + role_type = "#{role}_type" + role_id = "#{role}_id" + + 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 + + types.each do |type| + scope_name = type.tableize.gsub("/", "_") + 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}_id" do + public_send(role_id) if public_send(query) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/destroy_association_async_job.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/destroy_association_async_job.rb new file mode 100644 index 0000000000..e4ccaceb86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/destroy_association_async_job.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveRecord + class DestroyAssociationAsyncError < StandardError + end + + # Job to destroy the records associated with a destroyed record in background. + class DestroyAssociationAsyncJob < ActiveJob::Base + queue_as { ActiveRecord::Base.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.to_sym => 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.0.0/gems/activerecord-6.1.4/lib/active_record/dynamic_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/dynamic_matchers.rb new file mode 100644 index 0000000000..7d9e221faa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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, *arguments, &block) + match = Method.match(self, name) + + if match && match.valid? + match.define + send(name, *arguments, &block) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/enum.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/enum.rb new file mode 100644 index 0000000000..65c16e6bd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/enum.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true + +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" + # + # Finally, it's also 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 + # + # 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 +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 + + module Enum + def self.extended(base) # :nodoc: + base.class_attribute(:defined_enums, instance_writer: false, default: {}) + end + + def inherited(base) # :nodoc: + base.defined_enums = defined_enums.deep_dup + super + end + + class EnumType < Type::Value # :nodoc: + delegate :type, to: :subtype + + def initialize(name, mapping, subtype) + @name = name + @mapping = mapping + @subtype = subtype + end + + def cast(value) + if mapping.has_key?(value) + value.to_s + elsif mapping.has_value?(value) + mapping.key(value) + elsif value.blank? + nil + else + assert_valid_value(value) + end + end + + def deserialize(value) + mapping.key(subtype.deserialize(value)) + end + + def serialize(value) + mapping.fetch(value, value) + end + + def assert_valid_value(value) + 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(definitions) + enum_prefix = definitions.delete(:_prefix) + enum_suffix = definitions.delete(:_suffix) + enum_scopes = definitions.delete(:_scopes) + + default = {} + default[:default] = definitions.delete(:_default) if definitions.key?(:_default) + + definitions.each do |name, values| + assert_valid_enum_definition_values(values) + # 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}=") + + attr = attribute_alias?(name) ? attribute_alias(name) : name + + decorate_attribute_type(attr, **default) do |subtype| + EnumType.new(attr, enum_values, subtype) + end + + value_method_names = [] + _enum_methods_module.module_eval do + prefix = if enum_prefix == true + "#{name}_" + elsif enum_prefix + "#{enum_prefix}_" + end + + suffix = if enum_suffix == true + "_#{name}" + elsif enum_suffix + "_#{enum_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, enum_scopes) + + 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, enum_scopes) + end + end + end + detect_negative_enum_conditions!(value_method_names) if enum_scopes != false + enum_values.freeze + end + end + + private + class EnumMethods < Module # :nodoc: + def initialize(klass) + @klass = klass + end + + private + attr_reader :klass + + def define_enum_methods(name, value_method_name, value, enum_scopes) + # 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) } + + # scope :active, -> { where(status: 0) } + # scope :not_active, -> { where.not(status: 0) } + if enum_scopes != false + klass.send(:detect_enum_conflict!, name, value_method_name, true) + klass.scope value_method_name, -> { where(name => value) } + + 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) + unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) } + error_message = <<~MSG + Enum values #{values} must be either a hash, an array of symbols, or an array of strings. + MSG + raise ArgumentError, error_message + end + + if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?) + raise ArgumentError, "Enum label name must not be blank." + 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 && 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.0.0/gems/activerecord-6.1.4/lib/active_record/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/errors.rb new file mode 100644 index 0000000000..ffb6978daf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/errors.rb @@ -0,0 +1,423 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Errors + # + # Generic Active Record exception class. + class ActiveRecordError < StandardError + end + + # Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present. + class ActiveJobRequiredError < ActiveRecordError + 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 + + # Raised when connection to the database could not been established (for example when + # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection] + # is given a +nil+ object). + class ConnectionNotEstablished < ActiveRecordError + 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 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.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.create!}[rdoc-ref:Persistence::ClassMethods#create!] + # methods when a record is invalid and cannot be saved. + 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 call to {#destroy}[rdoc-ref:Persistence#destroy!] + # would return false. + # + # begin + # complex_operation_that_internally_calls_destroy! + # rescue ActiveRecord::RecordNotDestroyed => invalid + # puts invalid.record.errors + # end + # + class RecordNotDestroyed < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Superclass for all database execution errors. + # + # Wraps the underlying database error as +cause+. + class StatementInvalid < ActiveRecordError + def initialize(message = nil, sql: nil, binds: nil) + super(message || $!&.message) + @sql = sql + @binds = binds + end + + attr_reader :sql, :binds + 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 + ) + 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) + 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 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 + 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. + 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, "Call tech support!" + # 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::ImmutableRelation + # relation.limit!(5) # => ActiveRecord::ImmutableRelation + class ImmutableRelation < 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 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. + # + # 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 + + # SerializationFailure will be raised when a transaction is rolled + # back by the database due to a serialization failure. + class SerializationFailure < TransactionRollbackError + end + + # Deadlocked will be raised when a transaction is rolled + # back by the database when a deadlock is encountered. + 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 + + # 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("length(title)").first + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("length(title)")).first + # + # 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain.rb new file mode 100644 index 0000000000..7fe04106ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain.rb @@ -0,0 +1,54 @@ +# 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) # :nodoc: + str = queries.map do |sql, binds| + msg = +"EXPLAIN for: #{sql}" + unless binds.empty? + msg << " " + msg << binds.map { |attr| render_bind(attr) }.inspect + end + msg << "\n" + msg << connection.explain(sql, binds) + end.join("\n") + + # Overriding inspect to be more human readable, especially in the console. + def str.inspect + self + end + + str + end + + private + def render_bind(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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain_registry.rb new file mode 100644 index 0000000000..7fd078941a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain_registry.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class ExplainRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :queries, :collect + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/explain_subscriber.rb new file mode 100644 index 0000000000..ce209092f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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*(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.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/file.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/file.rb new file mode 100644 index 0000000000..52febbd163 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/file.rb @@ -0,0 +1,75 @@ +# 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 + 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 + + # 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.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/model_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/model_metadata.rb new file mode 100644 index 0000000000..5fb7824558 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/model_metadata.rb @@ -0,0 +1,32 @@ +# 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 && @model_class.type_for_attribute(@model_class.primary_key).type + end + + def has_primary_key_column? + @has_primary_key_column ||= primary_key_name && + @model_class.columns.any? { |col| col.name == primary_key_name } + 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.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/render_context.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/render_context.rb new file mode 100644 index 0000000000..b6ca84192f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/render_context.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# 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.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/table_row.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/table_row.rb new file mode 100644 index 0000000000..481740b135 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/table_row.rb @@ -0,0 +1,152 @@ +# 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 + 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 + 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 + # generate a primary key if necessary + if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name) + @row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify( + @label, model_metadata.primary_key_type + ) + end + end + + def resolve_enums + model_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? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") + # support polymorphic belongs_to as "label (Type)" + @row[association.join_foreign_type] = $1 + end + + fk_type = reflection_class.type_for_attribute(fk_name).type + @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) + 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| + { lhs_key => @row[model_metadata.primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } + end + @table_rows.tables[table_name].concat(joins) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/table_rows.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixture_set/table_rows.rb new file mode 100644 index 0000000000..df1cd63963 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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:, config:) + @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, config) + 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, config) + now = config.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.0.0/gems/activerecord-6.1.4/lib/active_record/fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixtures.rb new file mode 100644 index 0000000000..331023b8cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/fixtures.rb @@ -0,0 +1,784 @@ +# frozen_string_literal: true + +require "erb" +require "yaml" +require "zlib" +require "set" +require "active_support/dependencies" +require "active_support/core_ext/digest/uuid" +require "active_record/fixture_set/file" +require "active_record/fixture_set/render_context" +require "active_record/fixture_set/table_rows" +require "active_record/test_fixtures" + +module ActiveRecord + class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: + end + + # \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 placed in the directory + # appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically + # configured for Rails, so you can just put your files in /test/fixtures/). + # 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 (ie. 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. + # + # Note: Fixtures 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. 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 + # + # 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) + # Digest::SHA2.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 + # + # 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) %> + # + # == 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_s(: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 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. + # + # == 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 + #-- + # 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 ClassCache + def initialize(class_names, config) + @class_names = class_names.stringify_keys + @config = config + + # Remove string values that aren't constants or subclasses of AR + @class_names.delete_if do |klass_name, klass| + !insert_class(@class_names, klass_name, klass) + end + end + + def [](fs_name) + @class_names.fetch(fs_name) do + klass = default_fixture_model(fs_name, @config).safe_constantize + insert_class(@class_names, fs_name, klass) + end + end + + private + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end + end + + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end + end + + 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(connection) + @@all_cached_fixtures[connection] + end + + def fixture_is_cached?(connection, table_name) + cache_for_connection(connection)[table_name] + end + + def cached_fixtures(connection, keys_to_fetch = nil) + if keys_to_fetch + cache_for_connection(connection).values_at(*keys_to_fetch) + else + cache_for_connection(connection).values + end + end + + def cache_fixtures(connection, fixtures_map) + cache_for_connection(connection).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_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block) + fixture_set_names = Array(fixture_set_names).map(&:to_s) + class_names = ClassCache.new class_names, config + + # FIXME: Apparently JK uses this. + connection = block_given? ? block : lambda { ActiveRecord::Base.connection } + + fixture_files_to_read = fixture_set_names.reject do |fs_name| + fixture_is_cached?(connection.call, fs_name) + end + + if fixture_files_to_read.any? + fixtures_map = read_and_insert( + fixtures_directory, + fixture_files_to_read, + class_names, + connection, + ) + cache_fixtures(connection.call, fixtures_map) + end + cached_fixtures(connection.call, 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 + + def signed_global_id(fixture_set_name, label, column_type: :integer, **options) + identifier = identify(label, column_type) + model_name = default_fixture_model_name(fixture_set_name) + uri = URI::GID.build([GlobalID.app, model_name, identifier, {}]) + + SignedGlobalID.new(uri, **options) + end + + # Superclass for the evaluation contexts used by ERB fixtures. + def context_class + @context_class ||= Class.new + end + + private + def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc: + fixtures_map = {} + 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(fixtures_directory, fixture_set_name) + ) + end + update_all_loaded_fixtures(fixtures_map) + + insert(fixture_sets, connection) + + fixtures_map + end + + def insert(fixture_sets, connection) # :nodoc: + fixture_sets_by_connection = fixture_sets.group_by do |fixture_set| + if fixture_set.model_class + fixture_set.model_class.connection + else + connection.call + end + end + + fixture_sets_by_connection.each do |conn, 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 + + conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys) + + # 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 + + 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, + config: config, + ).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"].select { |f| + ::File.file?(f) + } + [yaml_file_path(path)] + + 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.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 yaml_file_path(path) + "#{path}.yml" + 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 + fixture.each { |item| yield item } + 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 + model_class.find(fixture[model_class.primary_key]) + end + # Fixtures can't be eagerly loaded + object.instance_variable_set(:@strict_loading, false) + object + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/gem_version.rb new file mode 100644 index 0000000000..0664dda646 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + # Returns the version of the currently loaded Active Record as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 4 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/inheritance.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/inheritance.rb new file mode 100644 index 0000000000..d0ef768433 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/inheritance.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +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 + 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 class descending directly from ActiveRecord::Base, or + # an abstract class, if any, in the inheritance hierarchy. + # + # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A + # through some arbitrarily deep hierarchy, B.base_class will return A. + # + # If B < A and C < B and if A is an abstract_class then both B.base_class + # and C.base_class would return B as the answer since A is an abstract_class. + def base_class + 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 + + # 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 behaviour: + # + # 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? + defined?(@abstract_class) && @abstract_class == true + 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 + ActiveSupport::Dependencies.constantize(type_name) + 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." + 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 + ActiveSupport::Dependencies.constantize(name) + else + compute_type(name) + end + end + + def inherited(subclass) + subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) + super + 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. + ActiveSupport::Dependencies.constantize(type_name) + else + type_candidate = @_type_candidates_cache[type_name] + if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate) + 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 = ActiveSupport::Dependencies.safe_constantize(candidate) + 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 + + private + # 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.0.0/gems/activerecord-6.1.4/lib/active_record/insert_all.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/insert_all.rb new file mode 100644 index 0000000000..ca303ef857 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/insert_all.rb @@ -0,0 +1,212 @@ +# 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, :returning, :unique_by + + def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil) + raise ArgumentError, "Empty list of attributes passed" if inserts.blank? + + @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s) + @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by + + if model.scope_attributes? + @scope_attributes = model.scope_attributes + @keys |= @scope_attributes.keys + end + @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) + @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty? + + ensure_valid_options_for_connection! + end + + def execute + message = +"#{model} " + message << "Bulk " if inserts.many? + message << (on_duplicate == :update ? "Upsert" : "Insert") + connection.exec_insert_all to_sql, message + end + + def updatable_columns + keys - readonly_columns - unique_by_columns + end + + def primary_keys + Array(connection.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) if scope_attributes + + verify_attributes(attributes) + + keys.map do |key| + yield key, attributes[key] + end + end + end + + private + attr_reader :scope_attributes + + 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) + + if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == 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 + connection.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.to_a + end + + def unique_by_columns + Array(unique_by&.columns) + end + + + def verify_attributes(attributes) + if keys != attributes.keys.to_set + raise ArgumentError, "All objects being inserted must have the same keys" + end + end + + class Builder # :nodoc: + attr_reader :model + + delegate :skip_duplicates?, :update_duplicates?, :keys, 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) + + values_list = insert_all.map_key_with_value do |key, value| + connection.with_yaml_fallback(types[key].serialize(value)) + end + + connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list)) + end + + def returning + format_columns(insert_all.returning) if insert_all.returning + 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) + model.send(:timestamp_attributes_for_update_in_model).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 CURRENT_TIMESTAMP END)," + end + end.compact.join + end + + private + attr_reader :connection, :insert_all + + def touch_timestamp_attribute?(column_name) + update_duplicates? && !insert_all.updatable_columns.include?(column_name) + end + + def columns_list + format_columns(insert_all.keys) + end + + def extract_types_from_columns_on(table_name, keys:) + columns = connection.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(&connection.method(:quote_column_name)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/integration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/integration.rb new file mode 100644 index 0000000000..256e00e82c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/integration.rb @@ -0,0 +1,205 @@ +# 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 + # We can't use alias_method here, because method 'id' optimizes itself on the fly. + id && id.to_s # Be sure to stringify the id for routes + 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_s(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_s(cache_timestamp_format) + end + elsif self.class.has_attribute?("updated_at") + raise ActiveModel::MissingAttributeError, "missing attribute: updated_at" + 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 && + 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.0.0/gems/activerecord-6.1.4/lib/active_record/internal_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/internal_metadata.rb new file mode 100644 index 0000000000..ed9ad50eaf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/internal_metadata.rb @@ -0,0 +1,62 @@ +# 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 < ActiveRecord::Base # :nodoc: + class << self + def enabled? + ActiveRecord::Base.connection.use_metadata_table? + end + + def _internal? + true + end + + def primary_key + "key" + end + + def table_name + "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}" + end + + def []=(key, value) + return unless enabled? + + find_or_initialize_by(key: key).update!(value: value) + end + + def [](key) + return unless enabled? + + where(key: key).pluck(:value).first + end + + # Creates an internal metadata table with columns +key+ and +value+ + def create_table + return unless enabled? + + 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 + + def drop_table + return unless enabled? + + connection.drop_table table_name, if_exists: true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/legacy_yaml_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/legacy_yaml_adapter.rb new file mode 100644 index 0000000000..252d608ea0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/legacy_yaml_adapter.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module LegacyYamlAdapter # :nodoc: + def self.convert(klass, coder) + return coder unless coder.is_a?(Psych::Coder) + + case coder["active_record_yaml_version"] + when 1, 2 then coder + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + YAML loading from legacy format older than Rails 5.0 is deprecated + and will be removed in Rails 6.2. + MSG + if coder["attributes"].is_a?(ActiveModel::AttributeSet) + Rails420.convert(klass, coder) + else + Rails41.convert(klass, coder) + end + end + end + + module Rails420 # :nodoc: + def self.convert(klass, coder) + attribute_set = coder["attributes"] + + klass.attribute_names.each do |attr_name| + attribute = attribute_set[attr_name] + if attribute.type.is_a?(Delegator) + type_from_klass = klass.type_for_attribute(attr_name) + attribute_set[attr_name] = attribute.with_type(type_from_klass) + end + end + + coder + end + end + + module Rails41 # :nodoc: + def self.convert(klass, coder) + attributes = klass.attributes_builder + .build_from_database(coder["attributes"]) + new_record = coder["attributes"][klass.primary_key].blank? + + { + "attributes" => attributes, + "new_record" => new_record, + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locale/en.yml new file mode 100644 index 0000000000..0b35027b2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/locking/optimistic.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locking/optimistic.rb new file mode 100644 index 0000000000..2611b8d723 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locking/optimistic.rb @@ -0,0 +1,212 @@ +# 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 +lock_version+ column 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 + + 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] + lock_value_for_database = _lock_value_for_database(locking_column) + + 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), + @primary_key => id_in_database, + locking_column => lock_value_for_database + ) + + 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 + return super unless locking_enabled? + + locking_column = self.class.locking_column + + affected_rows = self.class._delete_record( + @primary_key => id_in_database, + locking_column => _lock_value_for_database(locking_column) + ) + + if 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 + + 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+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end + + # 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 + + def define_attribute(name, cast_type, **) # :nodoc: + if lock_optimistically && name == locking_column + cast_type = LockingType.new(cast_type) + end + super + 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.0.0/gems/activerecord-6.1.4/lib/active_record/locking/pessimistic.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locking/pessimistic.rb new file mode 100644 index 0000000000..0849db8a7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/locking/pessimistic.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveRecord + module 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. + MSG + end + + reload(lock: lock) + end + self + end + + # Wraps the passed block in a transaction, locking the object + # before yielding. You can pass the SQL locking clause + # as argument (see lock!). + def with_lock(lock = true) + transaction do + lock!(lock) + yield + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/log_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/log_subscriber.rb new file mode 100644 index 0000000000..a9d9271b74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/log_subscriber.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module ActiveRecord + class LogSubscriber < ActiveSupport::LogSubscriber + IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + + class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new + + def self.runtime=(value) + ActiveRecord::RuntimeRegistry.sql_runtime = value + end + + def self.runtime + ActiveRecord::RuntimeRegistry.sql_runtime ||= 0 + end + + def self.reset_runtime + rt, self.runtime = runtime, 0 + rt + end + + def strict_loading_violation(event) + debug do + owner = event.payload[:owner] + association = event.payload[:reflection].klass + name = event.payload[:reflection].name + + color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED) + end + end + + def sql(event) + self.class.runtime += event.duration + return unless logger.debug? + + payload = event.payload + + return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) + + name = "#{payload[:name]} (#{event.duration.round(1)}ms)" + 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| + binds << render_bind(attr, casted_params[i]) + end + binds = binds.inspect + binds.prepend(" ") + end + + name = colorize_payload_name(name, payload[:name]) + sql = color(sql, sql_color(sql), true) if colorize_logging + + debug " #{name} #{sql}#{binds}" + end + + 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, true) + else + color(name, CYAN, 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::Base.verbose_query_logs + log_query_source + end + end + + def log_query_source + source = extract_query_source_location(caller) + + if source + logger.debug(" ↳ #{source}") + end + end + + def extract_query_source_location(locations) + backtrace_cleaner.clean(locations.lazy).first + end + end +end + +ActiveRecord::LogSubscriber.attach_to :active_record diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector.rb new file mode 100644 index 0000000000..3538d203ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "active_record/middleware/database_selector/resolver" + +module ActiveRecord + module 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 add + # the following options to your environment config: + # + # 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 + # + # New applications will include these lines commented out in the production.rb. + # + # 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 + 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 reading_request?(request) + resolver.read(&blk) + else + resolver.write(&blk) + end + + resolver.update_context(response) + response + end + + def reading_request?(request) + request.get? || request.head? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector/resolver.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector/resolver.rb new file mode 100644 index 0000000000..96f1b8e9a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + + private + def read_from_primary(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_primary") do + yield + end + end + end + + def read_from_replica(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_replica") do + yield + end + end + end + + def write_to_primary(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.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.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector/resolver/session.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/middleware/database_selector/resolver/session.rb new file mode 100644 index 0000000000..530701fb8d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration.rb new file mode 100644 index 0000000000..0846f47d2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration.rb @@ -0,0 +1,1427 @@ +# frozen_string_literal: true + +require "benchmark" +require "set" +require "zlib" +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" + +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[6.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[6.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[6.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 PendingMigrationError < MigrationError #:nodoc: + include ActiveSupport::ActionableError + + action "Run pending migrations" do + ActiveRecord::Tasks::DatabaseTasks.migrate + + if ActiveRecord::Base.dump_schema_after_migration + ActiveRecord::Tasks::DatabaseTasks.dump_schema( + ActiveRecord::Base.connection_db_config + ) + end + end + + def initialize(message = nil) + super(message || detailed_migration_message) + end + + private + def detailed_migration_message + message = "Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate" + message += " RAILS_ENV=#{::Rails.env}" if defined?(Rails.env) + message += "\n\n" + + pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations + + message += "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}\n\n" + + pending_migrations.each do |pending_migration| + message += "#{pending_migration.basename}\n" + end + + message + 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[6.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[6.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(name): Drops the table called +name+. + # * 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[6.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[6.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[6.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[6.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[6.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). + # + # 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[6.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[6.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" + + # This must be defined before the inherited hook, below + class Current < Migration #:nodoc: + end + + def self.inherited(subclass) #:nodoc: + super + if subclass.superclass == Migration + raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \ + "Please specify the Rails release the migration was written for:\n" \ + "\n" \ + " class #{subclass} < ActiveRecord::Migration[4.2]" + 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: + + # 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!(connection) + @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) + paths = Array(connection.migration_context.migrations_paths) + @file_watcher.new([], paths.index_with(["rb"]), &block) + end + + def connection + ActiveRecord::Base.connection + 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. + def check_pending!(connection = Base.connection) + raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration? + end + + def load_schema_if_pending! + current_db_config = Base.connection_db_config + all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env) + + needs_update = !all_configs.all? do |db_config| + Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord::Base.schema_format) + end + + if needs_update + # Roundtrip to Rake to allow plugins to hook into database initialization. + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + FileUtils.cd(root) do + Base.clear_all_connections! + system("bin/rails db:test:prepare") + end + end + + # Establish a new connection, the old database may be gone (db:test:prepare uses purge) + Base.establish_connection(current_db_config) + + check_pending! + end + + def maintain_test_schema! #:nodoc: + if ActiveRecord::Base.maintain_test_schema + suppress_messages { load_schema_if_pending! } + end + end + + def method_missing(name, *args, &block) #:nodoc: + nearest_delegate.send(name, *args, &block) + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + 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 + end + + def disable_ddl_transaction #:nodoc: + self.class.disable_ddl_transaction + end + + cattr_accessor :verbose + attr_accessor :name, :version + + def initialize(name = self.class.name, version = nil) + @name = name + @version = version + @connection = nil + 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[6.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[6.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) + run(*migration_classes.reverse, revert: true) unless migration_classes.empty? + if block_given? + if connection.respond_to? :revert + connection.revert { yield } + else + recorder = command_recorder + @connection = recorder + suppress_messages do + connection.revert { yield } + 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[6.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[6.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 + execute_block { yield } 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 = nil + ActiveRecord::Base.connection_pool.with_connection do |conn| + time = Benchmark.measure do + exec_migration(conn, direction) + end + end + + case direction + when :up then announce "migrated (%.4fs)" % time.real; write + when :down then announce "reverted (%.4fs)" % time.real; 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 + 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 = Benchmark.measure { result = yield } + say "%.4fs" % time.real, :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::Base.connection + end + + def method_missing(method, *arguments, &block) + arg_list = arguments.map(&:inspect) * ", " + + say_with_time "#{method}(#{arg_list})" 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 [:rename_table, :add_foreign_key].include?(method) || + (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 connection.respond_to?(method) + connection.send(method, *arguments, &block) + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + def copy(destination, sources, options = {}) + copied = [] + schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration + + FileUtils.mkdir_p(destination) unless File.exist?(destination) + + destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations + last = destination_migrations.last + sources.each do |scope, path| + source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).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 + 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::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max + else + SchemaMigration.normalize_migration_number(number) + 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 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 + require(File.expand_path(filename)) + name.constantize.new(name, version) + end + end + + class MigrationContext #:nodoc: + attr_reader :migrations_paths, :schema_migration + + def initialize(migrations_paths, schema_migration) + @migrations_paths = migrations_paths + @schema_migration = schema_migration + end + + 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) + move(:down, steps) + end + + def forward(steps = 1) + move(:up, steps) + end + + def up(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate + end + + def down(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate + end + + def run(direction, target_version) + Migrator.new(direction, migrations, schema_migration, target_version).run + end + + def open + Migrator.new(:up, migrations, schema_migration) + end + + def get_all_versions + if schema_migration.table_exists? + schema_migration.all_versions.map(&:to_i) + else + [] + end + end + + def current_version + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError + end + + def needs_migration? + (migrations.collect(&:version) - get_all_versions).size > 0 + end + + def any_migrations? + migrations.any? + end + + def migrations + migrations = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + name = name.camelize + + MigrationProxy.new(name, version, file, scope) + end + + migrations.sort_by(&:version) + end + + def migrations_status + db_list = schema_migration.normalized_versions + + file_list = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = schema_migration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end.compact + + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] + end + + (db_list + file_list).sort_by { |_, version, _| version } + end + + def current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end + + def last_stored_environment + return nil unless ActiveRecord::InternalMetadata.enabled? + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? + + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end + + private + 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 move(direction, steps) + migrator = Migrator.new(direction, migrations, schema_migration) + + 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 + MigrationContext.new(migrations_paths, SchemaMigration).current_version + end + end + + self.migrations_paths = ["db/migrate"] + + def initialize(direction, migrations, schema_migration, target_version = nil) + @direction = direction + @target_version = target_version + @migrated_versions = nil + @migrations = migrations + @schema_migration = schema_migration + + validate(@migrations) + + @schema_migration.create_table + ActiveRecord::InternalMetadata.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.all_versions.map(&:to_i)) + end + + private + # 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? + result = execute_migration_in_transaction(migration) + + record_environment + result + 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 + + result = runnable.each(&method(:execute_migration_in_transaction)) + record_environment + result + end + + # Stores the current environment in the database. + def record_environment + return if down? + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment + 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_by(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) + if use_transaction?(migration) + Base.transaction { yield } + else + yield + end + end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end + + def use_advisory_lock? + Base.connection.advisory_locks_enabled? + end + + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + + with_advisory_lock_connection do |connection| + 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 + end + + def with_advisory_lock_connection + pool = ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection( + ActiveRecord::Base.connection_db_config + ) + + pool.with_connection { |connection| yield(connection) } + ensure + pool&.disconnect! + end + + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(Base.connection.current_database) + MIGRATOR_SALT * db_name_hash + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/command_recorder.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/command_recorder.rb new file mode 100644 index 0000000000..dd7035d8ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/command_recorder.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # 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_index + # * add_reference + # * add_timestamps + # * change_column + # * 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_join_table + # * create_table + # * disable_extension + # * drop_join_table + # * drop_table (must supply a block) + # * enable_extension + # * remove_column (must supply a type) + # * remove_columns (must specify at least one column name or more) + # * remove_foreign_key (must supply a second table) + # * remove_check_constraint + # * remove_index + # * remove_reference + # * remove_timestamps + # * rename_column + # * 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 + ] + 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) if respond_to?(:ruby2_keywords, true) + end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference + + def change_table(table_name, **options) # :nodoc: + yield delegate.update_table_definition(table_name, self) + 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, + enable_extension: :disable_extension + }.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) + sub_recorder = CommandRecorder.new(delegate) + sub_recorder.revert { yield } + + invertions_proc = proc { + sub_recorder.replay(self) + } + + [:transaction, args, invertions_proc] + end + + def invert_drop_table(args, &block) + if args.size == 1 && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + super + end + + def invert_rename_table(args) + [:rename_table, args.reverse] + 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_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_remove_check_constraint(args) + raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2 + 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, *args, &block) + if delegate.respond_to?(method) + delegate.public_send(method, *args, &block) + else + super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/compatibility.rb new file mode 100644 index 0000000000..77ea2fdcbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/compatibility.rb @@ -0,0 +1,295 @@ +# 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 + + V6_1 = Current + + 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) + args.each do |ref_name| + ReferenceDefinition.new(ref_name, **options).add_to(self) + end + end + alias :belongs_to :references + end + + 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 add_reference(table_name, ref_name, **options) + ReferenceDefinition.new(ref_name, **options) + .add_to(connection.update_table_definition(table_name, self)) + end + alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end + end + + class V5_2 < V6_0 + module TableDefinition + def timestamps(**options) + options[:precision] ||= nil + super + 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 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 add_timestamps(table_name, **options) + options[:precision] ||= nil + super + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + 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" + 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 + 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 connection.adapter_name == "Mysql2" && 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 + 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 + 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.0.0/gems/activerecord-6.1.4/lib/active_record/migration/join_table.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/migration/join_table.rb new file mode 100644 index 0000000000..45169617c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/model_schema.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/model_schema.rb new file mode 100644 index 0000000000..883e272efa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/model_schema.rb @@ -0,0 +1,647 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveRecord + module ModelSchema + extend ActiveSupport::Concern + + ## + # :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. + + included do + mattr_accessor :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 + + self.protected_environments = ["production"] + self.inheritance_column = "type" + 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 + 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". + # + # 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 + @quoted_table_name = nil + @arel_table = nil + @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name + @predicate_builder = nil + end + + # Returns a quoted version of the table name, used to construct SQL statements. + def quoted_table_name + @quoted_table_name ||= connection.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 abstract_class? + superclass == Base ? nil : 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 + + # Defines the name of the table column which will store 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' + def inheritance_column + (@inheritance_column ||= nil) || superclass.inheritance_column + end + + # Sets the value of inheritance_column + def inheritance_column=(value) + @inheritance_column = value.to_s + @explicit_inheritance_column = true + 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 + if defined?(@ignored_columns) + @ignored_columns + else + superclass.ignored_columns + end + 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 = connection.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? + connection.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 + connection.next_sequence_value(sequence_name) + end + + # Indicates whether the table associated with this class exists + def table_exists? + connection.schema_cache.data_source_exists?(table_name) + end + + def attributes_builder # :nodoc: + unless defined?(@attributes_builder) && @attributes_builder + defaults = _default_attributes.except(*(column_names - [primary_key])) + @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults) + end + @attributes_builder + end + + def columns_hash # :nodoc: + load_schema + @columns_hash + end + + def columns + load_schema + @columns ||= columns_hash.values.freeze + end + + def attribute_types # :nodoc: + load_schema + @attribute_types ||= Hash.new(Type.default_value) + end + + def yaml_encoder # :nodoc: + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) + end + + # Returns the type of the attribute with the given name, after applying + # all modifiers. This method is the only valid source of information for + # anything related to the types of a model's attributes. This method will + # access the database and load the model's schema if it is required. + # + # The return value of this method will implement the interface described + # by ActiveModel::Type::Value (though the object itself may not subclass + # it). + # + # +attr_name+ The name of the attribute to retrieve the type for. Must be + # a string or a symbol. + def type_for_attribute(attr_name, &block) + attr_name = attr_name.to_s + attr_name = attribute_aliases[attr_name] || attr_name + + if block + attribute_types.fetch(attr_name, &block) + else + attribute_types[attr_name] + end + 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 + + def _default_attributes # :nodoc: + load_schema + @default_attributes ||= ActiveModel::AttributeSet.new({}) + 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, eg: + # + # class CreateJobLevels < ActiveRecord::Migration[6.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.clear_cache! + ([self] + descendants).each(&:undefine_attribute_methods) + connection.schema_cache.clear_data_source_cache!(table_name) + + reload_schema_from_cache + initialize_find_by_cache + end + + protected + def initialize_load_schema_monitor + @load_schema_monitor = Monitor.new + end + + private + def inherited(child_class) + super + child_class.initialize_load_schema_monitor + end + + def schema_loaded? + defined?(@schema_loaded) && @schema_loaded + end + + def load_schema + return if schema_loaded? + @load_schema_monitor.synchronize do + return if defined?(@columns_hash) && @columns_hash + + 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 + + def load_schema! + unless table_name + raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name=" + end + + columns_hash = connection.schema_cache.columns_hash(table_name) + columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty? + @columns_hash = columns_hash.freeze + @columns_hash.each do |name, column| + type = connection.lookup_cast_type_from_column(column) + type = _convert_type_from_options(type) + warn_if_deprecated_type(column) + define_attribute( + name, + type, + default: column.default, + user_provided_default: false + ) + end + end + + def reload_schema_from_cache + @arel_table = nil + @column_names = nil + @symbol_column_to_string_name_hash = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @column_defaults = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @schema_loaded = false + @attribute_names = nil + @yaml_encoder = nil + direct_descendants.each do |descendant| + descendant.send(:reload_schema_from_cache) + end + end + + # Guesses the table name, but does not decorate it with prefix and suffix information. + def undecorated_table_name(class_name = base_class.name) + table_name = class_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(name)}#{full_table_name_suffix}" + else + # STI subclasses always use their superclass' table. + base_class.table_name + end + end + + def _convert_type_from_options(type) + if immutable_strings_by_default && type.respond_to?(:to_immutable_string) + type.to_immutable_string + else + type + end + end + + def warn_if_deprecated_type(column) + return if attributes_to_define_after_schema_loads.key?(column.name) + return unless column.respond_to?(:oid) + + if column.array? + array_arguments = ", array: true" + else + array_arguments = "" + end + + if column.sql_type.start_with?("interval") + precision_arguments = column.precision.presence && ", precision: #{column.precision}" + ActiveSupport::Deprecation.warn(<<~WARNING) + The behavior of the `:interval` type will be changing in Rails 6.2 + to return an `ActiveSupport::Duration` object. If you'd like to keep + the old behavior, you can add this line to #{self.name} model: + + attribute :#{column.name}, :string#{precision_arguments}#{array_arguments} + + If you'd like the new behavior today, you can add this line: + + attribute :#{column.name}, :interval#{precision_arguments}#{array_arguments} + WARNING + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/nested_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/nested_attributes.rb new file mode 100644 index 0000000000..fcff10a3e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/nested_attributes.rb @@ -0,0 +1,597 @@ +# 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 + # + # If you want to validate that a child record is associated with a parent + # record, you can use the +validates_presence_of+ method and the +:inverse_of+ + # key as this example illustrates: + # + # class Member < ActiveRecord::Base + # has_many :posts, inverse_of: :member + # accepts_nested_attributes_for :posts + # end + # + # class Post < ActiveRecord::Base + # belongs_to :member, inverse_of: :posts + # validates_presence_of :member + # 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 + 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 off 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) + options = nested_attributes_options[association_name] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + 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 attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + 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.map { |a| a["id"] || a[:id] }.compact + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) + end + + attributes_collection.each 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 = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + 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 = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + 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]) + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + end + end + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/no_touching.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/no_touching.rb new file mode 100644 index 0000000000..45c5e1fd53 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + Thread.current[: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.0.0/gems/activerecord-6.1.4/lib/active_record/null_relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/null_relation.rb new file mode 100644 index 0000000000..bee5b5f24a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/null_relation.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveRecord + module NullRelation # :nodoc: + def pluck(*column_names) + [] + end + + def delete_all + 0 + end + + def update_all(_updates) + 0 + end + + def delete(_id_or_array) + 0 + end + + def empty? + true + end + + def none? + true + end + + def any? + false + end + + def one? + false + end + + def many? + false + end + + def to_sql + "" + end + + def calculate(operation, _column_name) + case operation + when :count, :sum + group_values.any? ? Hash.new : 0 + when :average, :minimum, :maximum + group_values.any? ? Hash.new : nil + end + end + + def exists?(_conditions = :none) + false + end + + def or(other) + other.spawn + end + + private + def exec_queries + @records = [].freeze + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/persistence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/persistence.rb new file mode 100644 index 0000000000..011b7359fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/persistence.rb @@ -0,0 +1,971 @@ +# 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 + + # 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 ActiveRecord::Persistence#insert_all for documentation. + def insert(attributes, returning: nil, unique_by: nil) + insert_all([ attributes ], returning: returning, unique_by: unique_by) + 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 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. + # + # [: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. + # + # ==== 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" } + # ]) + def insert_all(attributes, returning: nil, unique_by: nil) + InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute + 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 ActiveRecord::Persistence#insert_all! for more. + def insert!(attributes, returning: nil) + insert_all!([ attributes ], returning: returning) + 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 ActiveRecord::Persistence#insert_all. + # To replace them, see ActiveRecord::Persistence#upsert_all. + # + # Returns an ActiveRecord::Result with its contents based on + # :returning (see below). + # + # ==== Options + # + # [:returning] + # (PostgreSQL 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. + # + # ==== 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) + InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute + 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 ActiveRecord::Persistence#upsert_all for documentation. + def upsert(attributes, returning: nil, unique_by: nil) + upsert_all([ attributes ], returning: returning, unique_by: unique_by) + 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). + # + # ==== Options + # + # [:returning] + # (PostgreSQL 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. + # + # [: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. + # + # ==== 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, returning: nil, unique_by: nil) + InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute + 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. + # * +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) + 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 + + # 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) + if id.is_a?(Array) + find(id).each(&:destroy) + else + find(id).destroy + 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) + delete_by(primary_key => id_or_array) + end + + def _insert_record(values) # :nodoc: + primary_key = self.primary_key + primary_key_value = nil + + if primary_key && Hash === values + primary_key_value = values[primary_key] + + if !primary_key_value && prefetch_primary_key? + primary_key_value = next_sequence_value + values[primary_key] = primary_key_value + end + end + + if values.empty? + im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key)) + im.into arel_table + else + im = arel_table.compile_insert(_substitute_values(values)) + end + + connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) + end + + def _update_record(values, constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + um = arel_table.where( + constraints.reduce(&:and) + ).compile_update(_substitute_values(values), primary_key) + + connection.update(um, "#{self} Update") + end + + def _delete_record(constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + dm = Arel::DeleteManager.new + dm.from(arel_table) + dm.wheres = constraints + + connection.delete(dm, "#{self} Destroy") + end + + private + # 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 + + def _substitute_values(values) + values.map do |name, value| + attr = arel_table[name] + bind = predicate_builder.build_bind_attribute(attr.name, value) + [attr, bind] + end + 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 + # save, 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 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 + 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 = if persisted? + destroy_row > 0 + else + true + end + @destroyed = true + 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 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. + # If you want to change the sti column as well, use #becomes! instead. + def becomes(klass) + became = klass.allocate + + became.send(:initialize) do |becoming| + 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 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? + + attributes = attributes.transform_keys do |key| + name = key.to_s + name = self.class.attribute_aliases[name] || name + verify_readonly_attribute(name) || name + end + + id_in_database = self.id_in_database + attributes.each do |k, v| + write_attribute_without_type_cast(k, v) + end + + affected_rows = self.class._update_record( + attributes, + @primary_key => id_in_database + ) + + 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.clear_query_cache + + fresh_object = + if options && options[:lock] + self.class.unscoped { self.class.lock(options[:lock]).find(id) } + else + self.class.unscoped { self.class.find(id) } + end + + @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? + + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map! do |name| + name = name.to_s + self.class.attribute_aliases[name] || name + end unless names.empty? + + unless attribute_names.empty? + affected_rows = _touch_row(attribute_names, time) + @_trigger_update_callback = affected_rows == 1 + else + true + end + end + + private + # 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(@primary_key => id_in_database) + 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), + @primary_key => id_in_database + ) + 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) + + new_id = self.class._insert_record( + attributes_with_values(attribute_names) + ) + + self.id ||= new_id if @primary_key + + @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 + raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", 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 + + # The name of the method used to touch a +belongs_to+ association when the + # +:touch+ option is used. + def belongs_to_touch_method + :touch + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/query_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/query_cache.rb new file mode 100644 index 0000000000..b4e24debe4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/query_cache.rb @@ -0,0 +1,62 @@ +# 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? + connection.cache(&block) + 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. + def uncached(&block) + if connected? || !configurations.empty? + connection.uncached(&block) + else + yield + end + end + end + + def self.run + pools = [] + + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each do |key, handler| + pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }) + end + else + pools.concat(ActiveRecord::Base.connection_handler.all_connection_pools.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }) + end + + pools + end + + def self.complete(pools) + pools.each { |pool| pool.disable_query_cache! } + + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each do |_, handler| + handler.connection_pool_list.each do |pool| + pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? + end + end + else + ActiveRecord::Base.connection_handler.all_connection_pools.each do |pool| + pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? + end + end + end + + def self.install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/querying.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/querying.rb new file mode 100644 index 0000000000..5e0669e5ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/querying.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module ActiveRecord + module Querying + QUERYING_METHODS = [ + :find, :find_by, :find_by!, :take, :take!, :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_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by, + :find_each, :find_in_batches, :in_batches, + :select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins, + :where, :rewhere, :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, :strict_loading + ].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 }] + def find_by_sql(sql, binds = [], preparable: nil, &block) + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable) + 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.map { |record| instantiate(record, column_types, &block) } + else + # Instantiate a homogeneous set + result_set.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) + connection.select_value(sanitize_sql(sql), "#{name} Count").to_i + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railtie.rb new file mode 100644 index 0000000000..9d7b6de4ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railtie.rb @@ -0,0 +1,283 @@ +# 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.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.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.extend ActiveSupport::Logger.broadcast console + end + ActiveRecord::Base.verbose_query_logs = false + end + + runner do + require "active_record/base" + end + + initializer "active_record.initialize_timezone" do + ActiveSupport.on_load(:active_record) do + self.time_zone_aware_attributes = true + self.default_timezone = :utc + 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.delete(: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.database_selector" do + if options = config.active_record.delete(:database_selector) + resolver = config.active_record.delete(:database_resolver) + operations = config.active_record.delete(:database_resolver_context) + config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options + end + end + + initializer "Check for 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.check_schema_cache_dump" do + check_schema_cache_dump_version = config.active_record.delete(:check_schema_cache_dump_version) + + if config.active_record.delete(:use_schema_cache_dump) + config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first + + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename( + db_config.name, + schema_cache_path: db_config&.schema_cache_path + ) + + cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename) + next if cache.nil? + + if check_schema_cache_dump_version + current_version = begin + ActiveRecord::Migrator.current_version + rescue ActiveRecordError => error + warn "Failed to validate the schema cache because of #{error.class}: #{error.message}" + nil + end + next if current_version.nil? + + if cache.version != current_version + warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}." + next + end + end + + connection_pool.set_schema_cache(cache) + end + end + end + end + + initializer "active_record.define_attribute_methods" do |app| + config.after_initialize do + ActiveSupport.on_load(:active_record) do + if app.config.eager_load + begin + descendants.each do |model| + # SchemaMigration and InternalMetadata both override `table_exists?` + # to bypass the schema cache, so skip them to avoid the extra queries. + next if model._internal? + + # If the schema cache was loaded from a dump, we can use it without connecting + schema_cache = model.connection_pool.schema_cache + + # If there's no connection yet, we avoid connecting. + schema_cache ||= model.connected? && model.connection.schema_cache + + # If the schema cache doesn't have the columns + # hash for the model cached, `define_attribute_methods` would trigger a query. + if schema_cache && schema_cache.columns_hash?(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.warn_on_records_fetched_greater_than" do + if config.active_record.warn_on_records_fetched_greater_than + ActiveSupport.on_load(:active_record) do + require "active_record/relation/record_fetch_warning" + end + end + end + + initializer "active_record.set_configs" do |app| + ActiveSupport.on_load(:active_record) do + configs = app.config.active_record + + configs.each do |k, v| + send "#{k}=", v + 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 + if ActiveRecord::Base.legacy_connection_handling + self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler } + end + self.configurations = Rails.application.config.database_configuration + establish_connection + end + end + + # Expose database runtime to controller 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 + 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.clear_reloadable_connections! + end + end + end + end + + initializer "active_record.set_executor_hooks" do + ActiveRecord::QueryCache.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. + + clear_active_connections! + flush_idle_connections! + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/console_sandbox.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/console_sandbox.rb new file mode 100644 index 0000000000..70af43925e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/railties/controller_runtime.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/controller_runtime.rb new file mode 100644 index 0000000000..309441a057 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/controller_runtime.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_record/log_subscriber" + +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] + messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime + messages + end + 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::LogSubscriber.reset_runtime + super + end + + def cleanup_view_runtime + if logger && logger.info? && ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render + runtime = super + db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime += db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + def append_info_to_payload(payload) + super + if ActiveRecord::Base.connected? + payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/databases.rake b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/databases.rake new file mode 100644 index 0000000000..de8af50129 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/railties/databases.rake @@ -0,0 +1,708 @@ +# 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 + raise ActiveRecord::EnvironmentStorageError unless ActiveRecord::InternalMetadata.enabled? + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.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 "Creates 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 "Drops 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 + original_db_config = ActiveRecord::Base.connection_db_config + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate + end + db_namespace["_dump"].invoke + ensure + ActiveRecord::Base.establish_connection(original_db_config) + end + + # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false + task :_dump do + if ActiveRecord::Base.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::Base.dump_schema_after_migration is set to false + task name do + if ActiveRecord::Base.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 + original_db_config = ActiveRecord::Base.connection_db_config + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate + db_namespace["_dump:#{name}"].invoke + ensure + ActiveRecord::Base.establish_connection(original_db_config) + end + end + + desc "Rolls back the database one migration and re-migrates 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 "Rolls back #{name} database one migration and re-migrates 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:migrate"] + + desc 'Runs 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::Base.connection.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| + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.check_target_version + ActiveRecord::Base.connection.migration_context.run( + :up, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + + db_namespace["_dump"].invoke + end + end + end + + desc 'Runs 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::Base.connection.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| + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.check_target_version + ActiveRecord::Base.connection.migration_context.run( + :down, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + + db_namespace["_dump"].invoke + end + end + end + + desc "Display status of migrations" + task status: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + 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 + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate_status + 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 + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Base.connection.migration_context.rollback(step) + + db_namespace["_dump"].invoke + end + end + end + + desc "Rolls 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") + + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + + ActiveRecord::Base.connection.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::Base.connection.migration_context.forward(step) + db_namespace["_dump"].invoke + end + + desc "Drops and recreates the database from db/schema.rb for the current environment and loads the seeds." + task reset: [ "db:drop", "db:setup" ] + + # desc "Retrieves the charset for the current environment's database" + task charset: :load_config do + puts ActiveRecord::Tasks::DatabaseTasks.charset_current + end + + # desc "Retrieves 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 "Retrieves the current schema version number" + task version: :load_config do + puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}" + end + + # desc "Raises an error if there are pending migrations" + task abort_if_pending_migrations: :load_config do + pending_migrations = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).flat_map do |db_config| + ActiveRecord::Base.establish_connection(db_config) + + ActiveRecord::Base.connection.migration_context.open.pending_migrations + end + + 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 + ensure + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + + namespace :abort_if_pending_migrations do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Raises an error if there are pending migrations for #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + + pending_migrations = ActiveRecord::Base.connection.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 + + desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)" + task setup: ["db:create", :environment, "db:schema:load", :seed] + + desc "Runs setup if database does not exist, or runs migrations if it does" + task prepare: :load_config do + seed = false + + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + + # Skipped when no database + ActiveRecord::Tasks::DatabaseTasks.migrate + + if ActiveRecord::Base.dump_schema_after_migration + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ActiveRecord::Base.schema_format) + end + rescue ActiveRecord::NoDatabaseError + config_name = db_config.name + ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, config_name) + + if File.exist?(ActiveRecord::Tasks::DatabaseTasks.dump_filename(config_name)) + ActiveRecord::Tasks::DatabaseTasks.load_schema( + db_config, + ActiveRecord::Base.schema_format, + nil + ) + else + ActiveRecord::Tasks::DatabaseTasks.migrate + end + + seed = true + end + + ActiveRecord::Base.establish_connection + ActiveRecord::Tasks::DatabaseTasks.load_seed if seed + end + + desc "Loads 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 "Truncates tables of each database for current environment and loads the seeds" + task replant: [:load_config, :truncate_all, :seed] + end + + namespace :fixtures do + desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (e.g. spec/fixtures) using 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 "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`)" + task dump: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config) + end + + db_namespace["schema:dump"].reenable + end + + desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the database" + task load: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"]) + end + + task load_if_ruby: ["db:create", :environment] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:schema:load_if_ruby` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :ruby` to use `schema.rb` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) for #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config) + db_namespace["schema:dump:#{name}"].reenable + end + end + end + + namespace :load do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, ENV["SCHEMA"]) + end + end + end + + namespace :cache do + desc "Creates a db/schema_cache.yml file." + task dump: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename( + db_config.name, + schema_cache_path: db_config.schema_cache_path, + ) + ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache( + ActiveRecord::Base.connection, + filename, + ) + end + end + + desc "Clears 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.name, + schema_cache_path: db_config.schema_cache_path, + ) + ActiveRecord::Tasks::DatabaseTasks.clear_schema_cache( + filename, + ) + end + end + end + end + + namespace :structure do + desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task dump: :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:dump` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:dump` instead. + MSG + + db_namespace["schema:dump"].invoke + db_namespace["structure:dump"].reenable + end + + desc "Recreates the databases from the structure.sql file" + task load: [:load_config, :check_protected_environments] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke + end + + task load_if_sql: ["db:create", :environment] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load_if_sql` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :sql + end + + namespace :dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Dumps the #{name} database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task name => :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:dump:#{name}` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:dump:#{name}` instead. + MSG + db_namespace["schema:dump:#{name}"].invoke + db_namespace["structure:dump:#{name}"].reenable + end + end + end + + namespace :load do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Recreates the #{name} database from the structure.sql file" + task name => :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load:#{name}` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load:#{name}` instead. + MSG + db_namespace["schema:load:#{name}"].invoke + end + end + end + end + + namespace :test do + # desc "Recreate the test database from the current schema" + task load: %w(db:test:purge) do + db_namespace["test:load_schema"].invoke + end + + # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `config.active_record.schema_format`)" + task load_schema: %w(db:test:purge) do + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? + ActiveRecord::Schema.verbose = false + ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config| + filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, filename) + end + ensure + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + end + + # desc "Recreate the test database from an existent structure.sql file" + task load_structure: %w(db:test:purge) do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:test:load_structure` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:test:load_schema` instead. + MSG + db_namespace["test:load_schema"].invoke + 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"].invoke + end + end + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Recreate the #{name} test database" + namespace :load do + task name => "db:test:purge:#{name}" do + db_namespace["test:load_schema:#{name}"].invoke + end + end + + # desc "Recreate the #{name} test database from an existent schema.rb file" + namespace :load_schema do + task name => "db:test:purge:#{name}" do + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? + ActiveRecord::Schema.verbose = false + filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(name) + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, filename) + ensure + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + end + end + + # desc "Recreate the #{name} test database from an existent structure.sql file" + namespace :load_structure do + task name => "db:test:purge:#{name}" do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:test:load_structure:#{name}` is deprecated and will be removed in Rails 6.2. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:test:load_structure:#{name}` instead. + MSG + db_namespace["test:load_schema:#{name}"].invoke + end + end + + # desc "Empty the #{name} test database" + namespace :purge do + task name => %w(load_config check_protected_environments) do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name) + ActiveRecord::Tasks::DatabaseTasks.purge(db_config) + end + end + + # desc 'Load the #{name} database test schema' + namespace :prepare do + task name => :load_config do + db_namespace["test:load:#{name}"].invoke + end + end + end + end +end + +namespace :railties do + namespace :install do + # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" + 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 + + ActiveRecord::Migration.copy(ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first, railties, + on_skip: on_skip, on_copy: on_copy) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/readonly_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/readonly_attributes.rb new file mode 100644 index 0000000000..c851ed52c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/readonly_attributes.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + 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 but update operations will + # ignore these fields. + def attr_readonly(*attributes) + self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || []) + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/reflection.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/reflection.rb new file mode 100644 index 0000000000..4ec3f8a667 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/reflection.rb @@ -0,0 +1,1056 @@ +# 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: {} + 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_s + 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_s => 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 + + # \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_s] + end + + # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value. + # + # Account.reflections # => {"balance" => AggregateReflection} + # + def reflections + @__reflections ||= begin + ref = {} + + _reflections.each do |name, reflection| + parent_reflection = reflection.parent_reflection + + if parent_reflection + parent_name = parent_reflection.name + ref[parent_name.to_s] = parent_reflection + else + ref[name] = reflection + end + end + + ref + 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 = 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) + reflections[association.to_s] + end + + def _reflect_on_association(association) #:nodoc: + _reflections[association.to_s] + end + + # Returns an array of AssociationReflection objects for all associations which have :autosave enabled. + def reflect_on_all_autosave_associations + reflections.values.select { |reflection| reflection.options[:autosave] } + end + + def clear_reflections_cache # :nodoc: + @__reflections = nil + 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 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 = predicate_builder(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 = join_primary_key + foreign_key = join_foreign_key + + klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key])) + + if klass.finder_needs_type_condition? + klass_scope.where!(klass.send(:type_condition, table)) + end + + klass_scope + end + + def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc: + if scope + [scope_for(build_scope(table, predicate_builder, klass))] + else + [] + end + end + + def klass_join_scope(table, predicate_builder) # :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 ||= if belongs_to? + if options[:counter_cache] == true + -"#{active_record.name.demodulize.underscore.pluralize}_count" + elsif options[:counter_cache] + -options[:counter_cache].to_s + end + else + -(options[:counter_cache]&.to_s || "#{name}_count") + end + end + + def inverse_of + return unless inverse_name + + @inverse_of ||= klass._reflect_on_association inverse_name + end + + def check_validity_of_inverse! + unless polymorphic? + if has_inverse? && inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end + end + end + + # This shit is nasty. 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 + return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache) + @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse| + inverse.counter_cache_column == counter_cache_column + end + 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 a counter cache should be used for this association. + # + # 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 + + 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 = predicate_builder(table), klass = self.klass) + Relation.create( + klass, + table: table, + predicate_builder: predicate_builder + ) + end + + def strict_loading? + options[:strict_loading] + end + + protected + def actual_source_reflection # FIXME: this is a horrible name + self + end + + private + def predicate_builder(table) + PredicateBuilder.new(TableMetadata.new(klass, table)) + end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + 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) + @name = name + @scope = scope + @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 ||= 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 + 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 + active_record.send(:compute_type, name) + 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] + @constructable = calculate_constructable(macro, options) + + if options[:class_name] && options[:class_name].class == Class + raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string." + end + end + + def association_scope_cache(klass, owner, &block) + key = self + if polymorphic? + key = [key, owner._read_attribute(@foreign_type)] + end + klass.cached_find_by_statement(key, &block) + end + + def constructable? # :nodoc: + @constructable + end + + def join_table + @join_table ||= -(options[:join_table]&.to_s || derive_join_table) + end + + def foreign_key + @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key) + 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 + @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record)) + end + + def join_primary_key(klass = nil) + foreign_key + end + + def join_foreign_key + active_record_primary_key + end + + def check_validity! + check_validity_of_inverse! + end + + def check_preloadable! + return unless scope + + unless scope.arity == 0 + raise ArgumentError, <<-MSG.squish + The association scope '#{name}' is instance dependent (the scope + block takes an argument). Preloading instance dependent scopes is + not supported. + MSG + end + end + alias :check_eager_loadable! :check_preloadable! + + def join_id_for(owner) # :nodoc: + owner[join_foreign_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 + + VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] + INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key] + + 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 + def calculate_constructable(macro, options) + true + end + + # 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) + rescue NameError + # 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) + inverse_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 && + foreign_key == reflection.foreign_key && + klass <= reflection.active_record && + can_find_inverse_of_automatically?(reflection) + 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. + # + # Anything with a scope can additionally ruin our attempt at finding an + # inverse, so we exclude reflections with scopes. + def can_find_inverse_of_automatically?(reflection) + reflection.options[:inverse_of] != false && + VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && + !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && + !reflection.scope + 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 + if belongs_to? + "#{name}_id" + elsif options[:as] + "#{options[:as]}_id" + else + active_record.name.foreign_key + 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 + + private + def calculate_constructable(macro, options) + !options[:through] + 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 ||= -primary_key.to_s + 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 + + def calculate_constructable(macro, options) + !polymorphic? + 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) + @delegate_reflection = delegate_reflection + @klass = delegate_reflection.options[:anonymous_class] + @source_reflection_name = delegate_reflection.options[:source] + end + + def through_reflection? + true + end + + def klass + @klass ||= delegate_reflection.compute_class(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 + 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, klass = self.klass) # :nodoc: + source_reflection.join_scopes(table, predicate_builder, klass) + 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: + return @source_reflection_name if @source_reflection_name + + 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 + + @source_reflection_name = names.first + 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.reflections.keys.map(&:to_sym) + + 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) + @reflection = reflection + @previous_reflection = previous_reflection + end + + def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc: + scopes = @previous_reflection.join_scopes(table, predicate_builder) + super + scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &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) + @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.0.0/gems/activerecord-6.1.4/lib/active_record/relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation.rb new file mode 100644 index 0000000000..ca097ab7f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation.rb @@ -0,0 +1,893 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Relation + class Relation + MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, + :order, :joins, :left_outer_joins, :references, + :extending, :unscope, :optimizer_hints, :annotate] + + 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, :group, :having] + + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS + + include Enumerable + include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation + + attr_reader :table, :klass, :loaded, :predicate_builder + attr_accessor :skip_preloading_value + alias :model :klass + alias :loaded? :loaded + alias :locked? :lock_value + + def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) + @klass = klass + @table = table + @values = values + @loaded = false + @predicate_builder = predicate_builder + @delegate_to_klass = false + end + + def initialize_copy(other) + @values = @values.dup + reset + end + + def arel_attribute(name) # :nodoc: + table[name] + end + deprecate :arel_attribute + + def bind_attribute(name, value) # :nodoc: + if reflection = klass._reflect_on_association(name) + name = reflection.foreign_key + value = value.read_attribute(reflection.klass.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) + block = current_scope_restoring_block(&block) + scoping { _new(attributes, &block) } + 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 + # # different 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. + # + # Please note this method is not atomic, it runs first a SELECT, and if + # there are no results an INSERT is attempted. If there are other threads + # or processes there is a race condition between both calls and it could + # be the case that you end up with two similar records. + # + # If this might be a problem for your application, please see #create_or_find_by. + def find_or_create_by(attributes, &block) + find_by(attributes) || create(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!(attributes, &block) + end + + # Attempts to create a record with the given attributes in a table that has a unique 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 avoids the problem of stale reads between the SELECT + # and the INSERT, as that method needs to first query the table, then attempt to insert a row + # if none is found. + # + # There are several drawbacks to #create_or_find_by, though: + # + # * The underlying table must have the relevant columns defined with unique 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). + # + # 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) + transaction(requires_new: true) { create(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + find_by!(attributes) + 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) + transaction(requires_new: true) { create!(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + find_by!(attributes) + 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. + # + # 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. + # + # Please see further details in the + # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain]. + def explain + exec_explain(collecting_queries_for_explain { exec_queries }) + 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 + loaded? ? @records.length : count(:all) + end + + # Returns true if there are no records. + def empty? + return @records.empty? if loaded? + !exists? + end + + # Returns true if there are no records. + def none? + return super if block_given? + empty? + end + + # Returns true if there are any records. + def any? + return super if block_given? + !empty? + end + + # Returns true if there is exactly one record. + def one? + return super if block_given? + limit_value ? records.one? : size == 1 + end + + # Returns true if there is more than one record. + def many? + return super if block_given? + limit_value ? records.many? : size > 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] ||= klass.collection_cache_key(self, timestamp_column) + end + + def compute_cache_key(timestamp_column = :updated_at) # :nodoc: + query_signature = ActiveSupport::Digest.hexdigest(to_sql) + key = "#{klass.model_name.cache_key}/query-#{query_signature}" + + if 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 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? || distinct_value + 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 + + column = connection.visitor.compile(table[timestamp_column]) + select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" + + if collection.has_limit_or_offset? + query = collection.select("#{column} AS collection_cache_key_timestamp") + 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 = connection.select_rows(arel, nil).first + + if size + column_type = klass.type_for_attribute(timestamp_column) + timestamp = column_type.deserialize(timestamp) + else + size = 0 + end + end + + if timestamp + "#{size}-#{timestamp.utc.to_s(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 + # + # Please check unscoped if you want to remove all previous scopes (including + # the default_scope) during the execution of a block. + def scoping + already_in_scope? ? yield : _scoping(self) { yield } + end + + def _exec_scope(*args, &block) # :nodoc: + @delegate_to_klass = true + _scoping(nil) { instance_exec(*args, &block) || self } + ensure + @delegate_to_klass = 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. + # + # ==== 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') + def update_all(updates) + raise ArgumentError, "Empty list of attributes to change" if updates.blank? + + arel = eager_loading? ? apply_join_dependency.arel : build_arel + arel.source.left = table + + stmt = Arel::UpdateManager.new + stmt.table(arel.source) + stmt.key = table[primary_key] + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints + + if updates.is_a?(Hash) + if klass.locking_enabled? && + !updates.key?(klass.locking_column) && + !updates.key?(klass.locking_column.to_sym) + attr = table[klass.locking_column] + updates[attr.name] = _increment_attribute(attr) + end + stmt.set _substitute_values(updates) + else + stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) + end + + klass.connection.update(stmt, "#{klass} Update All").tap { reset } + end + + def update(id = :all, attributes) # :nodoc: + if id == :all + each { |record| record.update(attributes) } + else + klass.update(id, attributes) + end + 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 = klass.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 klass.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 + 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 + + arel = eager_loading? ? apply_join_dependency.arel : build_arel + arel.source.left = table + + stmt = Arel::DeleteManager.new + stmt.from(arel.source) + stmt.key = table[primary_key] + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints + + klass.connection.delete(stmt, "#{klass} Destroy").tap { reset } + 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 + + # 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) + unless loaded? + @records = exec_queries(&block) + @loaded = true + end + + self + end + + # Forces reloading of relation. + def reload + reset + load + end + + def reset + @delegate_to_klass = false + @to_sql = @arel = @loaded = @should_eager_load = nil + @offsets = @take = nil + @cache_keys = nil + @records = [].freeze + 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 ||= begin + if eager_loading? + apply_join_dependency do |relation, join_dependency| + relation = join_dependency.apply_column_aliases(relation) + relation.to_sql + end + else + conn = klass.connection + 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 = klass.table_name) + where_clause.to_h(relation_table_name) + end + + def scope_for_create + hash = where_clause.to_h(klass.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(q) + q.pp(records) + end + + # Returns true if relation is blank. + def blank? + records.blank? + end + + def values + @values.dup + 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 == klass.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(connection, 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? + preloader = nil + scope = strict_loading_value ? StrictLoadingScope : nil + preload.each do |associations| + preloader ||= build_preloader + preloader.preload records, associations, scope + end + end + + protected + def load_records(records) + @records = records.freeze + @loaded = true + end + + def null_relation? # :nodoc: + is_a?(NullRelation) + end + + private + def already_in_scope? + @delegate_to_klass && klass.current_scope(true) + end + + def current_scope_restoring_block(&block) + current_scope = klass.current_scope(true) + -> record do + klass.current_scope = current_scope + yield record if block_given? + end + end + + def _new(attributes, &block) + klass.new(attributes, &block) + end + + def _create(attributes, &block) + klass.create(attributes, &block) + end + + def _create!(attributes, &block) + klass.create!(attributes, &block) + end + + def _scoping(scope) + previous, klass.current_scope = klass.current_scope(true), scope + yield + ensure + klass.current_scope = previous + end + + def _substitute_values(values) + values.map do |name, value| + attr = table[name] + unless Arel.arel_node?(value) + type = klass.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 + records = + if where_clause.contradiction? + [] + elsif eager_loading? + apply_join_dependency do |relation, join_dependency| + if relation.null_relation? + [] + else + relation = join_dependency.apply_column_aliases(relation) + rows = connection.select_all(relation.arel, "SQL") + join_dependency.instantiate(rows, strict_loading_value, &block) + end.freeze + end + else + klass.find_by_sql(arel, &block).freeze + end + + preload_associations(records) unless skip_preloading_value + + records.each(&:readonly!) if readonly_value + records.each(&:strict_loading!) if strict_loading_value + + records + end + end + + def skip_query_cache_if_necessary + if skip_query_cache_value + uncached do + yield + end + else + yield + end + end + + def build_preloader + ActiveRecord::Associations::Preloader.new + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches.rb new file mode 100644 index 0000000000..1917dcc164 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +require "active_record/relation/batches/batch_enumerator" + +module ActiveRecord + module Batches + ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order." + + # 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + if block_given? + find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records| + records.each { |record| yield record } + end + else + enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do + relation = self + apply_limits(relation, start, finish, 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + 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, order: order) do + total = apply_limits(relation, start, finish, 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, 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + relation = self + unless block_given? + return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self) + end + + unless [:asc, :desc].include?(order) + raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}" + end + + if arel.orders.present? + act_on_ignored_order(error_on_ignore) + end + + batch_limit = of + if limit_value + remaining = limit_value + batch_limit = remaining if remaining < batch_limit + end + + relation = relation.reorder(batch_order(order)).limit(batch_limit) + relation = apply_limits(relation, start, finish, order) + relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching + batch_relation = relation + + loop do + if load + records = batch_relation.records + ids = records.map(&:id) + yielded_relation = where(primary_key => ids) + yielded_relation.load_records(records) + else + ids = batch_relation.pluck(primary_key) + yielded_relation = where(primary_key => ids) + end + + break if ids.empty? + + primary_key_offset = ids.last + raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset + + yield yielded_relation + + break if ids.length < batch_limit + + if limit_value + remaining -= ids.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_relation = relation.where( + predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt] + ) + end + end + + private + def apply_limits(relation, start, finish, order) + relation = apply_start_limit(relation, start, order) if start + relation = apply_finish_limit(relation, finish, order) if finish + relation + end + + def apply_start_limit(relation, start, order) + relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq]) + end + + def apply_finish_limit(relation, finish, order) + relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq]) + end + + def batch_order(order) + table[primary_key].public_send(order) + end + + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore) + + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif logger + logger.warn(ORDER_IGNORE_MESSAGE) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches/batch_enumerator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches/batch_enumerator.rb new file mode 100644 index 0000000000..1e9c07f6df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/batches/batch_enumerator.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module ActiveRecord + module Batches + class BatchEnumerator + include Enumerable + + def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc: + @of = of + @relation = relation + @start = start + @finish = finish + 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 + return to_enum(:each_record) unless block_given? + + @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation| + relation.records.each { |record| yield record } + 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 + + # Destroys records in batches. + # + # Person.where("age < 10").in_batches.destroy_all + # + # See Relation#destroy_all for details of how each batch is destroyed. + def destroy_all + each(&:destroy_all) + 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 + enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false) + return enum.each { |relation| yield relation } if block_given? + enum + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/calculations.rb new file mode 100644 index 0000000000..7f1b0f7850 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/calculations.rb @@ -0,0 +1,485 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Calculations + # 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", "business"]=>0, ["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. + 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 + + # 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 + + # 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 + + # 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 + + # 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 + def sum(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(:sum, column_name) + end + 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) + if has_include?(column_name) + relation = apply_join_dependency + + if operation.to_s.downcase == "count" + unless distinct_value || distinct_select?(column_name || select_for_count) + relation.distinct! + relation.select_values = [ klass.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 a bunch of records just to grab the attributes you want. + # + # 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] + # + # 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 loaded? && all_attributes?(column_names) + return records.pluck(*column_names) + end + + if has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + else + klass.disallow_raw_sql!(column_names) + columns = arel_columns(column_names) + relation = spawn + relation.select_values = columns + result = skip_query_cache_if_necessary do + if where_clause.contradiction? + ActiveRecord::Result.new([], []) + else + klass.connection.select_all(relation.arel, nil) + end + end + type_cast_pluck_values(result, columns) + end + 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) + return records.pick(*column_names) + end + + limit(1).pluck(*column_names).first + end + + # Pluck all the ID's for the relation using the table's primary key + # + # Person.ids # SELECT people.id FROM people + # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id + def ids + pluck primary_key + end + + private + def all_attributes?(column_names) + (column_names.map(&:to_s) - @klass.attribute_names - @klass.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 aggregate_column(column_name) + return column_name if Arel::Expressions === column_name + + arel_column(column_name.to_s) do |name| + Arel.sql(column_name == :all ? "*" : name) + end + 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 operation == "count" && (column_name == :all && distinct || has_limit_or_offset?) + # Shortcut when limit is zero. + return 0 if limit_value == 0 + + 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 = 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 + + result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) } + + type_cast_calculated_value(result.cast_values.first, operation) do |value| + 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 + type.deserialize(value) + 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 + + unless group_fields == group_values + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2. + To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields + (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`). + MSG + group_fields = group_values + end + + if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym) + association = klass._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 + group_fields = arel_columns(group_fields) + + group_aliases = group_fields.map { |field| + field = connection.visitor.compile(field) if Arel.arel_node?(field) + column_alias_for(field.to_s.downcase) + } + group_columns = group_aliases.zip(group_fields) + + column = aggregate_column(column_name) + column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}") + select_value = operation_over_aggregate_column(column, operation, distinct) + select_value.as(column_alias) + + select_values = [select_value] + select_values += self.select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end + } + + relation = except(:group).distinct!(false) + relation.group_values = group_fields + relation.select_values = select_values + + calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) } + + 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] = 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 + + type = nil + 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) do |value| + unless type + 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.deserialize(value) + end + end + end + + # 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 type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + @klass.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 + klass.attribute_types + else + join_dependencies = nil + columns.map.with_index do |column, i| + column.try(:type_caster) || + klass.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[name] || Type.default_value + end + end + end + result.cast_values(cast_types) + end + + def type_cast_calculated_value(value, operation) + case operation + when "count" + value.to_i + when "sum" + yield value || 0 + when "average" + value&.respond_to?(:to_d) ? value.to_d : value + else # "minimum", "maximum" + yield value + end + end + + def select_for_count + if select_values.present? + return select_values.first if select_values.one? + select_values.join(", ") + else + :all + end + 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 = [ aggregate_column(column_name).as(column_alias) ] + end + + subquery_alias = Arel.sql("subquery_for_count") + select_value = operation_over_aggregate_column(column_alias, "count", false) + + relation.build_subquery(subquery_alias, select_value) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/delegation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/delegation.rb new file mode 100644 index 0000000000..1c5fb3cdf3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/delegation.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "mutex_m" +require "active_support/core_ext/module/delegation" + +module ActiveRecord + module Delegation # :nodoc: + module DelegateCache # :nodoc: + def relation_delegate_class(klass) + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache + @relation_delegate_cache = cache = {} + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation + ].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: + include Mutex_m + + def generate_method(method) + synchronize do + return if method_defined?(method) + + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s) + definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block" + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(#{definition}) + scoping { klass.#{method}(#{definition}) } + end + RUBY + else + define_method(method) do |*args, &block| + scoping { klass.public_send(method, *args, &block) } + end + ruby2_keywords(method) if respond_to?(:ruby2_keywords, true) + 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 klass of a relation, so we create a subclass of Relation + # for each different klass, and the delegations are compiled into that subclass only. + + delegate :to_xml, :encode_with, :length, :each, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :as_json, + :shuffle, :split, :slice, :index, :rindex, to: :records + + delegate :primary_key, :connection, to: :klass + + module ClassSpecificRelation # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + def name + superclass.name + end + end + + private + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + @klass.generate_relation_method(method) + scoping { @klass.public_send(method, *args, &block) } + else + super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end + + module ClassMethods # :nodoc: + def create(klass, *args, **kwargs) + relation_class_for(klass).new(klass, *args, **kwargs) + end + + private + def relation_class_for(klass) + klass.relation_delegate_class(self) + end + end + + private + def respond_to_missing?(method, _) + super || @klass.respond_to?(method) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/finder_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/finder_methods.rb new file mode 100644 index 0000000000..4975836d5a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/finder_methods.rb @@ -0,0 +1,590 @@ +# 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 (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). + # 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) + # Person.find([1]) # returns an array for the object with ID = 1 + # Person.where("administrator = 1").order("created_on DESC").find(1) + # + # 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. + 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 + + # 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) + check_reorder_deprecation unless loaded? + + 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) + 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 { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 } + 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) + if loaded? || offset_value || limit_value || having_clause.any? + records.include?(record) + else + record.is_a?(klass) && exists?(record.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(klass)}]" unless where_clause.empty? + + name = @klass.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 check_reorder_deprecation + if !order_values.empty? && order_values.all?(&:blank?) + blank_value = order_values.first + ActiveSupport::Deprecation.warn(<<~MSG.squish) + `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer + takes non-deterministic result in Rails 6.2. + To continue taking non-deterministic result, use `.take` / `.take!` instead. + MSG + end + end + + 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 && !( + 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 + ) + ) + if has_limit_or_offset? + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + end + relation.limit_value = relation.offset_value = nil + end + + if block_given? + yield relation, join_dependency + else + relation + end + end + + def limited_ids_for(relation) + values = @klass.connection.columns_for_distinct( + connection.visitor.compile(table[primary_key]), + relation.order_values + ) + + relation = relation.except(:select).select(values).distinct! + + id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") } + id_rows.map(&:last) + end + + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end + + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + + expects_array = ids.first.kind_of?(Array) + return [] if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + model_name = @klass.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 = where(primary_key => id) + 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? + + result = where(primary_key => ids).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) || [] + + result = except(:limit, :offset).where(primary_key => ids).records + + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) + + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.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 equal?(relation) || has_limit_or_offset? + relation.records[-index] + else + relation.last(index)[-index] + end + end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end + + def ordered_relation + if order_values.empty? && (implicit_order_column || primary_key) + if implicit_order_column && primary_key && implicit_order_column != primary_key + order(table[implicit_order_column].asc, table[primary_key].asc) + else + order(table[implicit_order_column || primary_key].asc) + end + else + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/from_clause.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/from_clause.rb new file mode 100644 index 0000000000..d738154a50 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/relation/merger.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/merger.rb new file mode 100644 index 0000000000..e436373012 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/merger.rb @@ -0,0 +1,185 @@ +# 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, rewhere = nil) + hash.assert_valid_keys(*Relation::VALUE_METHODS) + + @relation = relation + @hash = hash + @rewhere = rewhere + end + + def merge + Merger.new(relation, other, @rewhere).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.klass, + 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, rewhere = nil) + @relation = relation + @values = other.values + @other = other + @rewhere = rewhere + end + + NORMAL_VALUES = Relation::VALUE_METHODS - + Relation::CLAUSE_METHODS - + [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: + + def normal_values + NORMAL_VALUES + end + + 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) + if name == :select + relation._select!(*value) + else + relation.public_send("#{name}!", *value) + end + end + end + + merge_multi_values + merge_single_values + merge_clauses + merge_preloads + merge_joins + merge_outer_joins + + relation + end + + private + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.klass == relation.klass + 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.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.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.klass == relation.klass + 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.klass == relation.klass + 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, @rewhere) + 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.klass.base_class == other.klass.base_class + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 0000000000..f839578bcc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,169 @@ +# 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" + + # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")). + # TODO: Remove the constant alias once Rails 6.1 has released. + BaseHandler = BasicObjectHandler + + 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) + elsif key.include?(".") + result << Arel.sql(key.split(".").first) + 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) + attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name)) + Arel::Nodes::BindParam.new(attr) + end + + def resolve_arel_attribute(table_name, column_name, &block) + table.associated_table(table_name, &block).arel_table[column_name] + end + + protected + def expand_from_hash(attributes, &block) + return ["1=0"] if attributes.empty? + + attributes.flat_map do |key, value| + if 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 = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) } + Arel::Nodes::Grouping.new(queries) + end + end + + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.select do |k, v| + k.include?(".") && !v.is_a?(Hash) + end + + dot_notation.each_key do |key| + table_name, column_name = key.split(".") + value = attributes.delete(key) + attributes[table_name] ||= {} + + attributes[table_name] = attributes[table_name].merge(column_name => value) + end + + attributes + end + + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/array_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/array_handler.rb new file mode 100644 index 0000000000..f07afd0d0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.extract!(&:nil?) + 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 + + unless nils.empty? + 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.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/association_query_value.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/association_query_value.rb new file mode 100644 index 0000000000..c7019bee4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -0,0 +1,42 @@ +# 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 + [ associated_table.join_foreign_key => ids ] + end + + private + attr_reader :associated_table, :value + + def ids + case value + when Relation + value.select_values.empty? ? value.select(primary_key) : value + 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 convert_to_id(value) + if value.respond_to?(primary_key) + value.public_send(primary_key) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/basic_object_handler.rb new file mode 100644 index 0000000000..e8c9f60860 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb new file mode 100644 index 0000000000..fd2762959a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -0,0 +1,57 @@ +# 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) + case value + when Base + value.class + when Relation + value.klass + end + end + + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key(value)) + when Relation + value.select(primary_key(value)) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/range_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/range_handler.rb new file mode 100644 index 0000000000..2ea27c8490 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/relation_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/relation_handler.rb new file mode 100644 index 0000000000..f310117c5e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -0,0 +1,19 @@ +# 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? + value = value.select(value.table[value.klass.primary_key]) + end + + attribute.in(value.arel) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_attribute.rb new file mode 100644 index 0000000000..cd18f27330 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveRecord + class Relation + class QueryAttribute < ActiveModel::Attribute # :nodoc: + def type_cast(value) + value + end + + def value_for_database + @value_for_database ||= super + 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, true) && value_for_database.nil? + end + rescue ::RangeError + end + + def infinite? + infinity?(value_before_type_cast) || infinity?(value_for_database) + rescue ::RangeError + end + + def unboundable? + if defined?(@_unboundable) + @_unboundable + else + value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute) + @_unboundable = nil + end + rescue ::RangeError + @_unboundable = type.cast(value_before_type_cast) <=> 0 + end + + private + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_methods.rb new file mode 100644 index 0000000000..4e246dfe4d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/query_methods.rb @@ -0,0 +1,1522 @@ +# frozen_string_literal: true + +require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" +require "active_record/relation/where_clause" +require "active_model/forbidden_attributes_protection" +require "active_support/core_ext/array/wrap" + +module ActiveRecord + module QueryMethods + extend ActiveSupport::Concern + + include ActiveModel::ForbiddenAttributesProtection + + # WhereChain objects act as placeholder for queries in which #where does not have any parameter. + # In this case, #where must be chained with #not to return a new relation. + class WhereChain + def initialize(scope) + @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') + 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 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(*args) + args.each do |arg| + reflection = @scope.klass._reflect_on_association(arg) + opts = { reflection.table_name => { reflection.association_primary_key => nil } } + @scope.left_outer_joins!(arg) + @scope.where!(opts) + end + + @scope + 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_mutability! # assert_mutability! + @values[:#{name}] = value # @values[:includes] = value + end # end + CODE + end + + alias extensions extending_values + + # Specify relationships to be included in the result set. For + # example: + # + # users = User.includes(:address) + # users.each do |user| + # user.address.city + # end + # + # allows you to access the +address+ attribute of the +User+ model without + # firing an additional query. This will often result in a + # performance improvement over a simple join. + # + # You can also specify multiple relationships, like this: + # + # users = User.includes(:address, :friends) + # + # Loading nested relationships is possible using a Hash: + # + # users = 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') + # + # Will throw an error, but this will work: + # + # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) + # + # Note that #includes works with association names while #references needs + # the actual table name. + # + # If you pass the conditions via 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' }) + def includes(*args) + check_if_method_has_arguments!(:includes, args) + spawn.includes!(*args) + end + + def includes!(*args) # :nodoc: + self.includes_values |= args + self + end + + # Forces eager loading by performing a LEFT OUTER JOIN on +args+: + # + # User.eager_load(:posts) + # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... + # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = + # # "users"."id" + def eager_load(*args) + check_if_method_has_arguments!(:eager_load, args) + spawn.eager_load!(*args) + end + + def eager_load!(*args) # :nodoc: + self.eager_load_values |= args + self + end + + # Allows preloading of +args+, in the same way that #includes does: + # + # User.preload(:posts) + # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) + def preload(*args) + check_if_method_has_arguments!(:preload, 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 JOINed 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!(:references, 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) + # # => [#] + # + # 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 + 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!(:select, fields, "Call `select' with at least one field.") + spawn._select!(*fields) + end + + def _select!(*fields) # :nodoc: + self.select_values |= fields + 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!(:reselect, 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!(:group, args) + spawn.group!(*args) + end + + def group!(*args) # :nodoc: + self.group_values += args + self + end + + # Allows to specify an order attribute: + # + # User.order(:name) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC + # + # User.order(email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC + # + # User.order(:name, email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC + # + # 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 + def order(*args) + check_if_method_has_arguments!(:order, 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 + + # 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!(:reorder, args) do + sanitize_order_arguments(args) unless args.all?(&:blank?) + 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) unless args.all?(&:blank?) + 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, :from, :readonly, :having, :optimizer_hints]) + + # 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!(:unscope, 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_mutability! + @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 a 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!(:joins, args) + spawn.joins!(*args) + end + + def joins!(*args) # :nodoc: + self.joins_values |= args + self + end + + # Performs a 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) + # + # === 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 #not to return a new relation that negates the where clause. + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # See WhereChain for more details on #not. + # + # === 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) + scope = spawn + where_clause = scope.build_where_clause(conditions) + + scope.unscope!(where: where_clause.extract_attributes) + scope.where_clause += where_clause + scope + 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) + spawn.or!(other) + 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 = self.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: + where!("1=0").extending!(NullRelation) + end + + # Sets readonly attributes for the returned relation. If value is + # true (default), attempting to update a record will result in an error. + # + # users = User.readonly + # users.first.save + # => ActiveRecord::ReadOnlyRecord: User is marked as readonly + 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 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 + # + # Topic.select('a.title').from(Topic.approved, :a) + # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a + # + 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!(:optimizer_hints, 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. + def annotate(*args) + check_if_method_has_arguments!(:annotate, 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 + + # Returns the Arel object associated with the relation. + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) + end + + def construct_join_dependency(associations, join_type) # :nodoc: + ActiveRecord::Associations::JoinDependency.new( + klass, 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) + + case opts + when String, Array + parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])] + when Hash + opts = opts.transform_keys do |key| + key = key.to_s + klass.attribute_aliases[key] || key + 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 + + private + 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) + join_dependencies.each do |join_dependency| + join_dependency.each do |join| + yield join + end + end + end + + def build_join_dependencies + associations = joins_values | left_outer_joins_values + associations |= eager_load_values unless eager_load_values.empty? + associations |= includes_values unless includes_values.empty? + + join_dependencies = [] + join_dependencies.unshift construct_join_dependency( + select_association_list(associations, join_dependencies), nil + ) + end + + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end + + def build_arel(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_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 + unless annotates == annotate_values + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Duplicated query annotations are no longer shown in queries in Rails 6.2. + To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations + (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`). + MSG + annotates = annotate_values + end + arel.comment(*annotates) + end + + arel + end + + def build_cast_value(name, value) + cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value) + Arel::Nodes::BindParam.new(cast_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_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 + + class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc: + end + + def build_join_buckets + buckets = Hash.new { |h, k| h[k] = [] } + + unless left_outer_joins_values.empty? + stashed_left_joins = [] + left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end + + if joins_values.empty? + buckets[:association_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 == klass + 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[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join| + if join.is_a?(Arel::Nodes::Join) + buckets[:join_node] << join + 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 + + association_joins = buckets[:association_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 association_joins.empty? && stashed_joins.empty? + alias_tracker = alias_tracker(leading_joins + join_nodes, aliases) + join_dependency = construct_join_dependency(association_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 klass.ignored_columns.any? + arel.project(*klass.column_names.map { |field| table[field] }) + else + arel.project(table[Arel.star]) + end + end + + def arel_columns(columns) + columns.flat_map do |field| + case field + when Symbol + arel_column(field.to_s) do |attr_name| + connection.quote_table_name(attr_name) + end + when String + arel_column(field, &:itself) + when Proc + field.call + else + field + end + end + end + + def arel_column(field) + field = klass.attribute_aliases[field] || field + from = from_clause.name || from_clause.value + + if klass.columns_hash.key?(field) && (!from || table_name_matches?(from)) + table[field] + elsif field.match?(/\A\w+\.\w+\z/) + table, column = field.split(".") + predicate_builder.resolve_arel_attribute(table, column) do + lookup_table_klass_from_join_dependencies(table) + end + else + yield field + end + end + + def table_name_matches?(from) + table_name = Regexp.escape(table.name) + quoted_table_name = Regexp.escape(connection.quote_table_name(table.name)) + /(?:\A|(? warn_on_records_fetched_greater_than + logger.warn "Query fetched #{records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}" + end + end + end + end + + # :stopdoc: + ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload| + QueryRegistry.queries << payload[:sql] + end + # :startdoc: + + class QueryRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :queries + + def initialize + @queries = [] + end + + def reset + @queries.clear + end + end + end + end +end + +ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/spawn_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 0000000000..8636b34434 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,77 @@ +# 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 + # This is overridden by Associations::CollectionProxy + def spawn #:nodoc: + already_in_scope? ? klass.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. + 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: + options = rest.extract_options! + if other.is_a?(Hash) + Relation::HashMerger.new(self, other, options[:rewhere]).merge + elsif other.is_a?(Relation) + Relation::Merger.new(self, other, options[:rewhere]).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.0.0/gems/activerecord-6.1.4/lib/active_record/relation/where_clause.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/where_clause.rb new file mode 100644 index 0000000000..0f5e173c53 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/relation/where_clause.rb @@ -0,0 +1,239 @@ +# 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, rewhere = nil) + predicates = if rewhere + except_predicates(other.extract_attributes) + else + predicates_unreferenced_by(other) + end + + 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 = Arel::Nodes::Or.new(left, right) + + 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 + + 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 predicates_unreferenced_by(other) + referenced_columns = other.referenced_columns + + predicates.reject do |node| + attr = extract_attribute(node) || begin + node.left if equality_node?(node) && node.left.is_a?(Arel::Predications) + end + next false unless attr + + ref = referenced_columns[attr] + next false unless ref + + if equality_node?(node) && equality_node?(ref) || node == ref + true + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Merging (#{node.to_sql}) and (#{ref.to_sql}) no longer maintain + both conditions, and will be replaced by the latter in Rails 6.2. + To migrate to Rails 6.2's behavior, use `relation.merge(other, rewhere: true)`. + MSG + false + end + end + 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) + case node + when Array + node.map { |v| extract_node_value(v) } + when Arel::Nodes::BindParam, Arel::Nodes::Casted, Arel::Nodes::Quoted + node.value_before_type_cast + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/result.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/result.rb new file mode 100644 index 0000000000..168d95cbf4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/result.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +module ActiveRecord + ### + # 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.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 + + attr_reader :columns, :rows, :column_types + + def initialize(columns, rows, column_types = {}) + @columns = columns + @rows = rows + @hash_rows = nil + @column_types = column_types + 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. + # + # Returns an +Enumerator+ if no block is given. + def each + if block_given? + hash_rows.each { |row| yield row } + else + hash_rows.to_enum { @rows.size } + end + end + + alias :map! :map + alias :collect! :map + deprecate "map!": :map + deprecate "collect!": :map + + # 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 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, type_overrides) + end + + rows.map do |(value)| + type.deserialize(value) + end + else + types = if type_overrides.is_a?(Array) + type_overrides + else + columns.map { |name| column_type(name, type_overrides) } + end + + rows.map do |values| + Array.new(values.size) { |i| types[i].deserialize(values[i]) } + end + end + end + + def initialize_copy(other) + @columns = columns.dup + @rows = rows.dup + @column_types = column_types.dup + @hash_rows = nil + end + + private + def column_type(name, type_overrides = {}) + type_overrides.fetch(name) do + column_types.fetch(name, Type.default_value) + end + end + + def hash_rows + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + columns = @columns.map(&:-@) + length = columns.length + template = nil + + @rows.map { |row| + if template + # We use transform_values to build subsequent rows from the + # hash of the first row. This is faster because we avoid any + # reallocs and in Ruby 2.7+ avoid hashing entirely. + index = -1 + template.transform_values do + row[index += 1] + end + else + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + # It's possible to select the same column twice, in which case + # we can't use a template + template = hash if hash.length == length + + hash + end + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/runtime_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/runtime_registry.rb new file mode 100644 index 0000000000..7363d2d7a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/runtime_registry.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for Active Record. For example: + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns the connection handler local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class RuntimeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :sql_runtime + + [:sql_runtime].each do |val| + class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ + class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/sanitization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/sanitization.rb new file mode 100644 index 0000000000..6e3ffe5fc6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/sanitization.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +module ActiveRecord + module Sanitization + extend ActiveSupport::Concern + + module ClassMethods + # Accepts an array or string 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'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + 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, hash, or string 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" + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" + 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(["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: connection.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) + c = connection + 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 + + # 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 "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # 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(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end + end + + def disallow_raw_sql!(args, permit: connection.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) + (unexpected ||= []) << arg + end + + if unexpected + raise(ActiveRecord::UnknownAttributeReference, + "Query method called with non-attribute argument(s): " + + unexpected.map(&:inspect).join(", ") + ) + end + end + + private + def replace_bind_variables(statement, values) + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + c = connection + statement.gsub(/\?/) do + replace_bind_variable(bound.shift, c) + end + end + + def replace_bind_variable(value, c = connection) + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(value, c) + end + end + + def replace_named_bind_variables(statement, bind_vars) + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip postgresql casts + match # return the whole match + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end + end + end + + def quote_bound_value(value, c = connection) + 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? + c.quote(nil) + else + values.map! { |v| c.quote(v) }.join(",") + end + else + value = value.id_for_database if value.respond_to?(:id_for_database) + c.quote(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.0.0/gems/activerecord-6.1.4/lib/active_record/schema.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema.rb new file mode 100644 index 0000000000..aba25fb375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema.rb @@ -0,0 +1,61 @@ +# 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.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 + # 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.define(version: 2038_01_19_000001) do + # ... + # end + def self.define(info = {}, &block) + new.define(info, &block) + end + + def define(info, &block) # :nodoc: + instance_eval(&block) + + if info[:version].present? + connection.schema_migration.create_table + connection.assume_migrated_upto_version(info[:version]) + end + + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_dumper.rb new file mode 100644 index 0000000000..ecd0c5f0af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_dumper.rb @@ -0,0 +1,300 @@ +# 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 as well as regexp if ActiveRecord::Base.schema_format == :ruby. + # Only strings are accepted if ActiveRecord::Base.schema_format == :sql. + 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}$/ + + class << self + def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) + connection.create_schema_dumper(generate_options(config)).dump(stream) + 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) + extensions(stream) + tables(stream) + trailer(stream) + stream + end + + private + attr_accessor :table_name + + def initialize(connection, options = {}) + @connection = connection + @version = connection.migration_context.current_version rescue nil + @options = options + 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 <
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? + 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 << "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? + add_check_constraint_statements = check_constraints.map do |check_constraint| + parts = [ + "t.check_constraint #{check_constraint.expression.inspect}" + ] + + if check_constraint.export_name_on_schema_dump? + parts << "name: #{check_constraint.name.inspect}" + end + + " #{parts.join(', ')}" + end + + stream.puts add_check_constraint_statements.sort.join("\n") + end + 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) + 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.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) + 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) + [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored| + ignored === remove_prefix_and_suffix(table_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_migration.rb new file mode 100644 index 0000000000..dabf62320f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/schema_migration.rb @@ -0,0 +1,54 @@ +# 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 which migrations + # have been applied to a given database. When a migration is run, its schema + # number is inserted in to the `SchemaMigration.table_name` so it doesn't need + # to be executed the next time. + class SchemaMigration < ActiveRecord::Base # :nodoc: + class << self + def _internal? + true + end + + def primary_key + "version" + end + + def table_name + "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}" + end + + def create_table + 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 + + def drop_table + connection.drop_table table_name, if_exists: true + end + + def normalize_migration_number(number) + "%.3d" % number.to_i + end + + def normalized_versions + all_versions.map { |v| normalize_migration_number v } + end + + def all_versions + order(:version).pluck(:version) + end + end + + def version + super.to_i + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping.rb new file mode 100644 index 0000000000..62c7988bd8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +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.value_for(:current_scope, self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + 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 a thread local, which is + # accessed through +ScopeRegistry.current+. + # + # 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_value_for(:current_scope, Board, some_new_scope) + # + # Now when you run: + # + # registry.value_for(:current_scope, Board) + # + # You will obtain whatever was defined in +some_new_scope+. The #value_for + # and #set_value_for methods are delegated to the current ScopeRegistry + # object, so the above example code can also be called as: + # + # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope, + # Board, some_new_scope) + class ScopeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] + + def initialize + @registry = Hash.new { |hash, key| hash[key] = {} } + end + + # Obtains the value for a given +scope_type+ and +model+. + def value_for(scope_type, model, skip_inherited_scope = false) + raise_invalid_scope_type!(scope_type) + return @registry[scope_type][model.name] if skip_inherited_scope + klass = model + base = model.base_class + while klass <= base + value = @registry[scope_type][klass.name] + return value if value + klass = klass.superclass + end + end + + # Sets the +value+ for a given +scope_type+ and +model+. + def set_value_for(scope_type, model, value) + raise_invalid_scope_type!(scope_type) + @registry[scope_type][model.name] = value + end + + private + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/default.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/default.rb new file mode 100644 index 0000000000..2a4c99c699 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/default.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module ActiveRecord + module Scoping + 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 + # def self.default_scope + # where(published: true) + # end + # 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" + # + # 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_given? ? relation.scoping { yield } : relation + end + + # Are there attributes associated with this scope? + def scope_attributes? # :nodoc: + super || default_scopes.any? || respond_to?(:default_scope) + end + + def before_remove_const #:nodoc: + self.current_scope = nil + 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 a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (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, &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 + + self.default_scopes += [scope] + end + + def build_default_scope(relation = relation()) + 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 |default_scope, scope| + scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) + default_scope.instance_exec(&scope) || default_scope + end + end + end + end + + def ignore_default_scope? + ScopeRegistry.value_for(:ignore_default_scope, base_class) + end + + def ignore_default_scope=(ignore) + ScopeRegistry.set_value_for(: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.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/named.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/named.rb new file mode 100644 index 0000000000..ea1c1d3e76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/scoping/named.rb @@ -0,0 +1,206 @@ +# 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 + scope = current_scope + + if scope + if self == scope.klass + scope.clone + else + relation.merge!(scope) + end + else + default_scoped + 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) + build_default_scope(scope) || 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 + + valid_scope_name?(name) + 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) if respond_to?(:ruby2_keywords, true) + + generate_relation_method(name) + end + + private + def singleton_method_added(name) + generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name) + end + + def valid_scope_name?(name) + if respond_to?(name, true) && logger + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/secure_token.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/secure_token.rb new file mode 100644 index 0000000000..4864f988b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/secure_token.rb @@ -0,0 +1,48 @@ +# 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. + def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH) + 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) } + before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") } + end + + def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH) + SecureRandom.base58(length) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/serialization.rb new file mode 100644 index 0000000000..70cf1ea981 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/serialization.rb @@ -0,0 +1,24 @@ +# 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/signed_id.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/signed_id.rb new file mode 100644 index 0000000000..f0f0094b74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/signed_id.rb @@ -0,0 +1,116 @@ +# 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. + mattr_accessor :signed_id_verifier_secret, instance_writer: false + 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 the + # 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 + 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. + # It can further more 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, purpose: nil) + self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/statement_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/statement_cache.rb new file mode 100644 index 0000000000..aec6343a17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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(Book.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([], Book.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(Book.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"], Book.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 + + 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.klass) + end + + def initialize(query_builder, bind_map, klass) + @query_builder = query_builder + @bind_map = bind_map + @klass = klass + end + + def execute(params, connection, &block) + bind_values = bind_map.bind params + + sql = query_builder.sql_for bind_values, connection + + klass.find_by_sql(sql, bind_values, preparable: true, &block) + rescue ::RangeError + [] + end + + def self.unsupported_value?(value) + case value + when NilClass, Array, Range, Hash, Relation, Base then true + end + end + + private + attr_reader :query_builder, :bind_map, :klass + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/store.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/store.rb new file mode 100644 index 0000000000..59b8a90b59 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/store.rb @@ -0,0 +1,290 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # 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+, or MySQL 5.7+ + # +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', 'red'] + # + # # 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 = {}) + serialize store_attribute, IndifferentCoder.new(store_attribute, options[: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_change_to_attribute(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_change_to_attribute(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_change_to_attribute(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).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) + if value != read(object, attribute, key) + object.public_send :"#{attribute}_will_change!" + object.public_send(attribute)[key] = value + end + 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 self.class.as_indifferent_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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/suppressor.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/suppressor.rb new file mode 100644 index 0000000000..49cce16dc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/suppressor.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord + # 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 + + module ClassMethods + def suppress(&block) + previous_state = SuppressorRegistry.suppressed[name] + SuppressorRegistry.suppressed[name] = true + yield + ensure + SuppressorRegistry.suppressed[name] = previous_state + end + end + + def save(**) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + + def save!(**) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + end + + class SuppressorRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :suppressed + + def initialize + @suppressed = {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/table_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/table_metadata.rb new file mode 100644 index 0000000000..d20f4fa66a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/table_metadata.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveRecord + class TableMetadata # :nodoc: + delegate :join_primary_key, :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) || klass&._reflect_on_association(table_name.singularize) + 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 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 + predicate_builder = klass.predicate_builder.dup + predicate_builder.instance_variable_set(:@table, self) + predicate_builder + else + PredicateBuilder.new(self) + end + end + + attr_reader :arel_table + + private + attr_reader :klass, :reflection + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/database_tasks.rb new file mode 100644 index 0000000000..1e73984c5b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/database_tasks.rb @@ -0,0 +1,533 @@ +# frozen_string_literal: true + +require "active_record/database_configurations" + +module ActiveRecord + module Tasks # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: + + # 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 + mattr_accessor :structure_dump_flags, instance_accessor: false + + ## + # :singleton-method: + # Extra flags passed to database CLI tool when calling db:schema:load + mattr_accessor :structure_load_flags, instance_accessor: false + + extend self + + attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader + deprecate :current_config= + attr_accessor :database_configuration + + LOCAL_HOSTS = ["127.0.0.1", "localhost"] + + def check_protected_environments! + unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] + current = ActiveRecord::Base.connection.migration_context.current_environment + stored = ActiveRecord::Base.connection.migration_context.last_stored_environment + + if ActiveRecord::Base.connection.migration_context.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(stored) + end + + if stored && stored != current + raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored) + end + end + rescue ActiveRecord::NoDatabaseError + end + + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, "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 spec + @spec ||= "primary" + end + deprecate spec: "please use name instead" + + def name + @name ||= "primary" + end + + def seed_loader + @seed_loader ||= Rails.application + end + + def current_config(options = {}) + if options.has_key?(:config) + @current_config = options[:config] + else + env_name = options[:env] || env + name = options[:spec] || "primary" + + @current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: name)&.configuration_hash + end + end + deprecate :current_config + + 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 + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) + each_local_configuration { |db_config| create(db_config) } + if old_pool + ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config) + end + end + + def setup_initial_database_yaml + return {} unless defined?(Rails) + + begin + Rails.application.config.load_database_yaml + rescue + unless ActiveRecord::Base.suppress_multiple_database_warning + $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB." + end + + {} + end + end + + def for_each(databases) + 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| + yield db_config.name + end + end + + def raise_for_multi_db(environment = env, command:) + db_configs = ActiveRecord::Base.configurations.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) } + ActiveRecord::Base.establish_connection(environment.to_sym) + 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) + ActiveRecord::Base.establish_connection(db_config) + + connection = ActiveRecord::Base.connection + connection.truncate_tables(*connection.tables) + end + private :truncate_tables + + def truncate_all(environment = env) + ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config| + truncate_tables(db_config) + end + end + + def migrate + check_target_version + + scope = ENV["SCOPE"] + verbose_was, Migration.verbose = Migration.verbose, verbose? + + Base.connection.migration_context.migrate(target_version) do |migration| + scope.blank? || scope == migration.scope + end + + ActiveRecord::Base.clear_cache! + ensure + Migration.verbose = verbose_was + end + + def migrate_status + unless ActiveRecord::Base.connection.schema_migration.table_exists? + Kernel.abort "Schema migrations table does not exist yet." + end + + # output + puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" + puts "-" * 50 + ActiveRecord::Base.connection.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::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(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 = ActiveRecord::Base.configurations.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 = ActiveRecord::Base.configurations.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) } + ActiveRecord::Base.establish_connection(environment.to_sym) + end + + def structure_dump(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + database_adapter_for(db_config, *arguments).structure_dump(filename, structure_dump_flags) + end + + def structure_load(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + database_adapter_for(db_config, *arguments).structure_load(filename, structure_load_flags) + end + + def load_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + file ||= dump_filename(db_config.name, format) + + verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"] + check_schema_file(file) + ActiveRecord::Base.establish_connection(db_config) + + case format + when :ruby + load(file) + when :sql + structure_load(db_config, file) + else + raise ArgumentError, "unknown format #{format.inspect}" + end + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = db_config.env_name + ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file) + ensure + Migration.verbose = verbose_was + end + + def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = nil, name = nil) + db_config = resolve_configuration(configuration) + + if environment || name + ActiveSupport::Deprecation.warn("`environment` and `name` will be removed as parameters in 6.2.0, you may now pass an ActiveRecord::DatabaseConfigurations::DatabaseConfig as `configuration` instead.") + end + + name ||= db_config.name + + file ||= dump_filename(name, format) + + return true unless File.exist?(file) + + ActiveRecord::Base.establish_connection(db_config) + + return false unless ActiveRecord::InternalMetadata.enabled? + return false unless ActiveRecord::InternalMetadata.table_exists? + + ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file) + end + + def reconstruct_from_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + file ||= dump_filename(db_config.name, format) + + check_schema_file(file) + + ActiveRecord::Base.establish_connection(db_config) + + if schema_up_to_date?(db_config, format, file) + truncate_tables(db_config) + else + purge(db_config) + load_schema(db_config, format, file) + end + rescue ActiveRecord::NoDatabaseError + create(db_config) + load_schema(db_config, format, file) + end + + def dump_schema(db_config, format = ActiveRecord::Base.schema_format) # :nodoc: + require "active_record/schema_dumper" + filename = dump_filename(db_config.name, format) + connection = ActiveRecord::Base.connection + + FileUtils.mkdir_p(db_dir) + case format + when :ruby + File.open(filename, "w:utf-8") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + when :sql + structure_dump(db_config, filename) + if connection.schema_migration.table_exists? + File.open(filename, "a") do |f| + f.puts connection.dump_schema_information + f.print "\n" + end + end + end + end + + def schema_file(format = ActiveRecord::Base.schema_format) + File.join(db_dir, schema_file_type(format)) + end + + def schema_file_type(format = ActiveRecord::Base.schema_format) + case format + when :ruby + "schema.rb" + when :sql + "structure.sql" + end + end + + def dump_filename(db_config_name, format = ActiveRecord::Base.schema_format) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) + schema_file_type(format) + else + "#{db_config_name}_#{schema_file_type(format)}" + end + + ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) + end + + def cache_dump_filename(db_config_name, schema_cache_path: nil) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) + "schema_cache.yml" + else + "#{db_config_name}_schema_cache.yml" + end + + schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) + end + + def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) + each_current_configuration(environment) do |db_config| + load_schema(db_config, format, file) + end + ActiveRecord::Base.establish_connection(environment.to_sym) + 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.connection, "tmp/schema_dump.yaml") + def dump_schema_cache(conn, filename) + conn.schema_cache.dump_to(filename) + end + + def clear_schema_cache(filename) + FileUtils.rm_f filename, verbose: false + end + + private + 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.each_pair.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) + environments = [environment] + environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"] + + environments.each do |env| + ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config| + next if name && name != db_config.name + + yield db_config + end + end + end + + def each_local_configuration + ActiveRecord::Base.configurations.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) + Digest::SHA1.hexdigest(File.read(file)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/mysql_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/mysql_database_tasks.rb new file mode 100644 index 0000000000..fdba782ca3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class MySQLDatabaseTasks # :nodoc: + ER_DB_CREATE_EXISTS = 1007 + + delegate :connection, :establish_connection, to: ActiveRecord::Base + + 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(db_config) + end + + def drop + establish_connection(db_config) + connection.drop_database(db_config.database) + end + + def purge + establish_connection(db_config) + connection.recreate_database(db_config.database, creation_options) + 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? + 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 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" + }.map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] }.compact + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/postgresql_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/postgresql_database_tasks.rb new file mode 100644 index 0000000000..c4789091c6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/postgresql_database_tasks.rb @@ -0,0 +1,138 @@ +# 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 = "--" + + delegate :connection, :establish_connection, :clear_active_connections!, + to: ActiveRecord::Base + + def self.using_database_configurations? + true + end + + def initialize(db_config) + @db_config = db_config + @configuration_hash = db_config.configuration_hash + end + + def create(master_established = false) + establish_master_connection unless master_established + connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding)) + establish_connection(db_config) + end + + def drop + establish_master_connection + connection.drop_database(db_config.database) + end + + def charset + connection.encoding + end + + def collation + connection.collation + end + + def purge + clear_active_connections! + drop + create true + end + + def structure_dump(filename, extra_flags) + set_psql_env + + search_path = \ + case ActiveRecord::Base.dump_schemas + when :schema_search_path + configuration_hash[:schema_search_path] + when :all + nil + when String + ActiveRecord::Base.dump_schemas + end + + args = ["--schema-only", "--no-privileges", "--no-owner", "--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? + 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) + set_psql_env + args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--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 encoding + configuration_hash[:encoding] || DEFAULT_ENCODING + end + + def establish_master_connection + establish_connection configuration_hash.merge( + database: "postgres", + schema_search_path: "public" + ) + end + + def set_psql_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] + 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:\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.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/sqlite_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/sqlite_database_tasks.rb new file mode 100644 index 0000000000..f08e373b9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class SQLiteDatabaseTasks # :nodoc: + delegate :connection, :establish_connection, to: ActiveRecord::Base + + 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(db_config) + connection + end + + def drop + require "pathname" + path = Pathname.new(db_config.database) + file = path.absolute? ? path.to_s : File.join(root, path) + + FileUtils.rm(file) + rescue Errno::ENOENT => error + raise NoDatabaseError.new(error.message) + end + + def purge + drop + rescue NoDatabaseError + ensure + create + 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? + 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" + 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 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.0.0/gems/activerecord-6.1.4/lib/active_record/test_databases.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/test_databases.rb new file mode 100644 index 0000000000..ab4732bb80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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::Base.schema_format, nil) + end + ensure + ActiveRecord::Base.establish_connection + ENV["VERBOSE"] = old + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/test_fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/test_fixtures.rb new file mode 100644 index 0000000000..58c66df685 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/test_fixtures.rb @@ -0,0 +1,287 @@ +# 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 + teardown_fixtures + end + + included do + class_attribute :fixture_path, instance_writer: false + 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 + 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_path`." if fixture_path.blank? + fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq + fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path + fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") } + else + fixture_set_names = fixture_set_names.flatten.map(&:to_s) + end + + self.fixture_table_names |= fixture_set_names + 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) + methods = Module.new do + fixture_set_names.each do |fs_name| + fs_name = fs_name.to_s + accessor_name = fs_name.tr("/", "_").to_sym + + define_method(accessor_name) do |*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 + private accessor_name + end + end + include methods + end + + 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 + + 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_connections = [] + @@already_loaded_fixtures ||= {} + @connection_subscriber = nil + @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} } + @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} } + + # Load fixtures once and begin transaction. + if run_in_transaction? + if @@already_loaded_fixtures[self.class] + @loaded_fixtures = @@already_loaded_fixtures[self.class] + else + @loaded_fixtures = load_fixtures(config) + @@already_loaded_fixtures[self.class] = @loaded_fixtures + end + + # Begin transactions for connections already established + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true if lock_threads + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + spec_name = payload[:spec_name] if payload.key?(:spec_name) + shard = payload[:shard] if payload.key?(:shard) + setup_shared_connection_pool + + if spec_name + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard) + rescue ConnectionNotEstablished + connection = nil + end + + if connection && !@fixture_connections.include?(connection) + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true if lock_threads + @fixture_connections << connection + end + end + end + + # Load fixtures for every test. + else + ActiveRecord::FixtureSet.reset_cache + @@already_loaded_fixtures[self.class] = nil + @loaded_fixtures = load_fixtures(config) + end + + # Instantiate fixtures for every test if requested. + instantiate_fixtures if use_instantiated_fixtures + end + + def teardown_fixtures + # Rollback changes if a transaction is active. + if run_in_transaction? + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + @fixture_connections.each do |connection| + connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false + end + @fixture_connections.clear + teardown_shared_connection_pool + else + ActiveRecord::FixtureSet.reset_cache + end + + ActiveRecord::Base.clear_active_connections! + end + + def enlist_fixture_connections + setup_shared_connection_pool + + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) + end + + private + # 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 + if ActiveRecord::Base.legacy_connection_handling + writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role] + + ActiveRecord::Base.connection_handlers.values.each do |handler| + if handler != writing_handler + handler.connection_pool_names.each do |name| + writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name] + return unless writing_pool_manager + + pool_manager = handler.send(:owner_to_pool_manager)[name] + @legacy_saved_pool_configs[handler][name] ||= {} + pool_manager.shard_names.each do |shard_name| + writing_pool_config = writing_pool_manager.get_pool_config(nil, shard_name) + pool_config = pool_manager.get_pool_config(nil, shard_name) + next if pool_config == writing_pool_config + + @legacy_saved_pool_configs[handler][name][shard_name] = pool_config + pool_manager.set_pool_config(nil, shard_name, writing_pool_config) + end + end + end + end + else + handler = ActiveRecord::Base.connection_handler + + handler.connection_pool_names.each do |name| + pool_manager = handler.send(:owner_to_pool_manager)[name] + pool_manager.shard_names.each do |shard_name| + writing_pool_config = pool_manager.get_pool_config(ActiveRecord::Base.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 + end + + def teardown_shared_connection_pool + if ActiveRecord::Base.legacy_connection_handling + @legacy_saved_pool_configs.each_pair do |handler, names| + names.each_pair do |name, shards| + shards.each_pair do |shard_name, pool_config| + pool_manager = handler.send(:owner_to_pool_manager)[name] + pool_manager.set_pool_config(nil, shard_name, pool_config) + end + end + end + else + handler = ActiveRecord::Base.connection_handler + + @saved_pool_configs.each_pair do |name, shards| + pool_manager = handler.send(:owner_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 + end + + @legacy_saved_pool_configs.clear + @saved_pool_configs.clear + end + + def load_fixtures(config) + ActiveRecord::FixtureSet.create_fixtures(fixture_path, 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/timestamp.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/timestamp.rb new file mode 100644 index 0000000000..9b8eeb8c09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/timestamp.rb @@ -0,0 +1,164 @@ +# 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) + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map(&:to_s) + 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 + default_timezone == :utc ? Time.now.utc : Time.now + 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 + + def reload_schema_from_cache + @timestamp_attributes_for_create_in_model = nil + @timestamp_attributes_for_update_in_model = nil + @all_timestamp_attributes_in_model = nil + super + end + end + + private + 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 + 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 + + super + end + + def create_or_update(touch: true, **) + @_touch_record = touch + super + end + + def should_record_timestamps? + record_timestamps && (!partial_writes? || 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 + .map { |attr| self[attr]&.to_time } + .compact + .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.0.0/gems/activerecord-6.1.4/lib/active_record/touch_later.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/touch_later.rb new file mode 100644 index 0000000000..405a5dad7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/touch_later.rb @@ -0,0 +1,65 @@ +# 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(:belongs_to).each do |r| + if touch = r.options[:touch] + ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later) + 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 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? + defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present? + end + + def belongs_to_touch_method + :touch_later + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/transactions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/transactions.rb new file mode 100644 index 0000000000..9eb08b198d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/transactions.rb @@ -0,0 +1,446 @@ +# 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 + + # = 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. + # + # *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. + # + # === 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.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 + # # ^^^^ BOOM! database error! + # end + # + # Note that "TRUNCATE" is also a MySQL DDL statement! + module ClassMethods + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. + def transaction(**options, &block) + connection.transaction(**options, &block) + 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) + 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 ]) + 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) + 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) + 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) + 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) + set_callback(:rollback, :after, *args, &block) + end + + private + 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: + force_clear_transaction_record_state + 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 +method+ 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 + status = nil + connection = self.class.connection + 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 + + 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 + + # 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 + force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 + end + + # Force to clear the transaction record state. + def force_clear_transaction_record_state + @_start_transaction_state = nil + 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 @attributes.fetch_value(@primary_key) != restore_state[:id] + @attributes.write_from_user(@primary_key, restore_state[:id]) + 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.connection.add_transaction_record(self, ensure_finalize) + 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.0.0/gems/activerecord-6.1.4/lib/active_record/translation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/translation.rb new file mode 100644 index 0000000000..82661a328a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/translation.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module Translation + include ActiveModel::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 overwrite ActiveModel. + def i18n_scope #:nodoc: + :activerecord + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type.rb new file mode 100644 index 0000000000..85df786a33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type.rb @@ -0,0 +1,84 @@ +# 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: + # TODO: this shouldn't depend on a connection to the database + model.connection.adapter_name.downcase.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.0.0/gems/activerecord-6.1.4/lib/active_record/type/adapter_specific_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/adapter_specific_registry.rb new file mode 100644 index 0000000000..77590b821a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require "active_model/type/registry" + +module ActiveRecord + # :stopdoc: + module Type + class AdapterSpecificRegistry < ActiveModel::Type::Registry + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + + private + def registration_klass + Registration + end + + def find_registration(symbol, *args, **kwargs) + registrations + .select { |registration| registration.matches?(symbol, *args, **kwargs) } + .max + end + end + + class Registration + def initialize(name, block, adapter: nil, override: nil) + @name = name + @block = block + @adapter = adapter + @override = override + end + + def call(_registry, *args, adapter: nil, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + 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 + 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 + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/date.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/date.rb new file mode 100644 index 0000000000..8177074a20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/date_time.rb new file mode 100644 index 0000000000..4acde6b9f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type/decimal_without_scale.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 0000000000..a207940dc7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type/hash_lookup_type_map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 0000000000..b260464df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class HashLookupTypeMap < TypeMap # :nodoc: + 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.0.0/gems/activerecord-6.1.4/lib/active_record/type/internal/timezone.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/internal/timezone.rb new file mode 100644 index 0000000000..3059755752 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/internal/timezone.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + module Internal + module Timezone + def is_utc? + ActiveRecord::Base.default_timezone == :utc + end + + def default_timezone + ActiveRecord::Base.default_timezone + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/json.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/json.rb new file mode 100644 index 0000000000..3f9ff22796 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type/serialized.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/serialized.rb new file mode 100644 index 0000000000..17f3932e54 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/serialized.rb @@ -0,0 +1,74 @@ +# 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 + + def inspect + Kernel.instance_method(:inspect).bind(self).call + end + + 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 + + private + def default_value?(value) + value == coder.load(nil) + end + + def encoded(value) + return if default_value?(value) + payload = coder.dump(value) + if payload && binary? && payload.encoding != Encoding::BINARY + payload = payload.dup if payload.frozen? + payload.force_encoding(Encoding::BINARY) + end + payload + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/text.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/text.rb new file mode 100644 index 0000000000..6d19696671 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type/time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/time.rb new file mode 100644 index 0000000000..4e4f2acc63 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/time.rb @@ -0,0 +1,31 @@ +# 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 + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/type/type_map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/type_map.rb new file mode 100644 index 0000000000..58f25ba075 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/type_map.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module Type + class TypeMap # :nodoc: + def initialize + @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 + @cache.clear + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type, *args| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}", *args) + end + end + + def clear + @mapping.clear + end + + private + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/unsigned_integer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type/unsigned_integer.rb new file mode 100644 index 0000000000..535369e630 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster.rb new file mode 100644 index 0000000000..2e5f45fa3d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster/connection.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster/connection.rb new file mode 100644 index 0000000000..1d80d9a2b1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 = connection.schema_cache + + if schema_cache.data_source_exists?(table_name) + column = schema_cache.columns_hash(table_name)[attr_name.to_s] + type = connection.lookup_cast_type_from_column(column) if column + end + + type || Type.default_value + end + + delegate :connection, to: :@klass, private: true + + private + attr_reader :table_name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster/map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/type_caster/map.rb new file mode 100644 index 0000000000..b3dd7a014a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/validations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations.rb new file mode 100644 index 0000000000..818515b4e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations.rb @@ -0,0 +1,94 @@ +# 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 + # all of which accept the :on argument to define the context where the + # validations are active. Active Record will always supply either the context of + # :create or :update dependent on whether the model is a + # {new_record?}[rdoc-ref:Persistence#new_record?]. + module Validations + extend ActiveSupport::Concern + include ActiveModel::Validations + + # 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. + # + # \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? + + 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.0.0/gems/activerecord-6.1.4/lib/active_record/validations/absence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/absence.rb new file mode 100644 index 0000000000..6afb9eabd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 considered absent if it was 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.0.0/gems/activerecord-6.1.4/lib/active_record/validations/associated.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/associated.rb new file mode 100644 index 0000000000..2112a6d7b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/associated.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AssociatedValidator < ActiveModel::EachValidator #:nodoc: + def validate_each(record, attribute, value) + if Array(value).reject { |r| valid_object?(r) }.any? + record.errors.add(attribute, :invalid, **options.merge(value: value)) + end + end + + private + def valid_object?(record) + (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid? + 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.0.0/gems/activerecord-6.1.4/lib/active_record/validations/length.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/length.rb new file mode 100644 index 0000000000..f47b14ae3a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/active_record/validations/numericality.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/numericality.rb new file mode 100644 index 0000000000..69c5e389b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/numericality.rb @@ -0,0 +1,35 @@ +# 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.0.0/gems/activerecord-6.1.4/lib/active_record/validations/presence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/presence.rb new file mode 100644 index 0000000000..75e97e1997 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/presence.rb @@ -0,0 +1,68 @@ +# 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?), and, if the attribute is an association, that the + # associated object is not marked for destruction. Happens by default + # on save. + # + # 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. + # + # 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. + # + # 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. + # + # 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]. + # + # Configuration options: + # * :message - A custom error message (default is: "can't be blank"). + # * :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. + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/uniqueness.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/uniqueness.rb new file mode 100644 index 0000000000..fbe318f3f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/validations/uniqueness.rb @@ -0,0 +1,246 @@ +# 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] + end + + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + value = map_enum_attribute(finder_class, attribute, value) + + 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. Since classes don't know + # their subclasses, we have to build the hierarchy between self and + # the record's class. + def find_finder_class_for(record) + class_hierarchy = [record.class] + + while class_hierarchy.first != @klass + class_hierarchy.unshift(class_hierarchy.first.superclass) + end + + class_hierarchy.detect { |klass| !klass.abstract_class? } + end + + def build_relation(klass, attribute, value) + relation = klass.unscoped + comparison = relation.bind_attribute(attribute, value) do |attr, bind| + return relation.none! if bind.unboundable? + + if !options.key?(:case_sensitive) || bind.nil? + klass.connection.default_uniqueness_comparison(attr, bind) + elsif options[:case_sensitive] + klass.connection.case_sensitive_comparison(attr, bind) + else + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + klass.connection.case_insensitive_comparison(attr, bind) + 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 (+true+ by default). + # * :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::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.0.0/gems/activerecord-6.1.4/lib/active_record/version.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/version.rb new file mode 100644 index 0000000000..6b0d82d8fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/active_record/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveRecord + # Returns the version of the currently loaded ActiveRecord as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel.rb new file mode 100644 index 0000000000..148508461c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "arel/errors" + +require "arel/crud" +require "arel/factory_methods" + +require "arel/expressions" +require "arel/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("length(title)")).last + # + # 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. + def self.sql(raw_sql) + Arel::Nodes::SqlLiteral.new raw_sql + end + + def self.star # :nodoc: + sql "*" + 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.0.0/gems/activerecord-6.1.4/lib/arel/alias_predication.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/alias_predication.rb new file mode 100644 index 0000000000..4abbbb7ef6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/attributes/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/attributes/attribute.rb new file mode 100644 index 0000000000..e7bdd2b171 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/attributes/attribute.rb @@ -0,0 +1,41 @@ +# 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 + + class String < Attribute; end + class Time < Attribute; end + class Boolean < Attribute; end + class Decimal < Attribute; end + class Float < Attribute; end + class Integer < Attribute; end + class Undefined < Attribute; end + end + + Attribute = Attributes::Attribute +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/bind.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/bind.rb new file mode 100644 index 0000000000..6a96b1cb0e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/bind.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Bind + 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.0.0/gems/activerecord-6.1.4/lib/arel/collectors/composite.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/composite.rb new file mode 100644 index 0000000000..0f05dfbe54 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/composite.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Composite + attr_accessor :preparable + + def initialize(left, right) + @left = left + @right = right + 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.0.0/gems/activerecord-6.1.4/lib/arel/collectors/plain_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/plain_string.rb new file mode 100644 index 0000000000..c0e9fff399 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/collectors/sql_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/sql_string.rb new file mode 100644 index 0000000000..8aa8958a1f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + + 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.0.0/gems/activerecord-6.1.4/lib/arel/collectors/substitute_binds.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/collectors/substitute_binds.rb new file mode 100644 index 0000000000..82315c75d3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + + 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.0.0/gems/activerecord-6.1.4/lib/arel/crud.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/crud.rb new file mode 100644 index 0000000000..e8a563ca4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/crud.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + ### + # FIXME hopefully we can remove this + module Crud + def compile_update(values, pk) + um = UpdateManager.new + + if Nodes::SqlLiteral === values + relation = @ctx.from + else + relation = values.first.first.relation + end + um.key = pk + um.table relation + um.set values + um.take @ast.limit.expr if @ast.limit + um.order(*@ast.orders) + um.wheres = @ctx.wheres + um + end + + def compile_insert(values) + im = create_insert + im.insert values + im + end + + def create_insert + InsertManager.new + end + + def compile_delete + dm = DeleteManager.new + dm.take @ast.limit.expr if @ast.limit + dm.wheres = @ctx.wheres + dm.from @ctx.froms + dm + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/delete_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/delete_manager.rb new file mode 100644 index 0000000000..fdba937d64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/delete_manager.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class DeleteManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize + super + @ast = Nodes::DeleteStatement.new + @ctx = @ast + end + + def from(relation) + @ast.relation = relation + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/errors.rb new file mode 100644 index 0000000000..2f8d5e3c02 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/errors.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class ArelError < StandardError + end + + class EmptyJoinError < ArelError + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/expressions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/expressions.rb new file mode 100644 index 0000000000..da8afb338c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/factory_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/factory_methods.rb new file mode 100644 index 0000000000..83ec23e403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/factory_methods.rb @@ -0,0 +1,49 @@ +# 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/insert_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/insert_manager.rb new file mode 100644 index 0000000000..cb31e3060b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/insert_manager.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class InsertManager < Arel::TreeManager + def initialize + super + @ast = Nodes::InsertStatement.new + 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.0.0/gems/activerecord-6.1.4/lib/arel/math.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/math.rb new file mode 100644 index 0000000000..2359f13148 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes.rb new file mode 100644 index 0000000000..5f15d39854 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes.rb @@ -0,0 +1,70 @@ +# 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" + +# 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/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" + +# nary +require "arel/nodes/and" + +# 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/comment" + +require "arel/nodes/sql_literal" + +require "arel/nodes/casted" diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/and.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/and.rb new file mode 100644 index 0000000000..bf516db35f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/and.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class And < Arel::Nodes::NodeExpression + attr_reader :children + + def initialize(children) + super() + @children = children + end + + def left + children.first + end + + def right + children[1] + end + + def hash + children.hash + end + + def eql?(other) + self.class == other.class && + self.children == other.children + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/ascending.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/ascending.rb new file mode 100644 index 0000000000..8b617f4df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/binary.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/binary.rb new file mode 100644 index 0000000000..e3f523d0b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/binary.rb @@ -0,0 +1,126 @@ +# 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 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 + + class Or < Binary + def fetch_attribute(&block) + left.fetch_attribute(&block) && right.fetch_attribute(&block) + end + end + + %w{ + As + 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/bind_param.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/bind_param.rb new file mode 100644 index 0000000000..1d900eeb22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/case.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/case.rb new file mode 100644 index 0000000000..1c4b727bf6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/casted.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/casted.rb new file mode 100644 index 0000000000..07996dd25e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral + 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/comment.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/comment.rb new file mode 100644 index 0000000000..237ff27e7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/count.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/count.rb new file mode 100644 index 0000000000..880464639d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/delete_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/delete_statement.rb new file mode 100644 index 0000000000..a419975335 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/delete_statement.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class DeleteStatement < Arel::Nodes::Node + attr_accessor :left, :right, :orders, :limit, :offset, :key + + alias :relation :left + alias :relation= :left= + alias :wheres :right + alias :wheres= :right= + + def initialize(relation = nil, wheres = []) + super() + @left = relation + @right = wheres + @orders = [] + @limit = nil + @offset = nil + @key = nil + end + + def initialize_copy(other) + super + @left = @left.clone if @left + @right = @right.clone if @right + end + + def hash + [self.class, @left, @right, @orders, @limit, @offset, @key].hash + end + + def eql?(other) + self.class == other.class && + self.left == other.left && + self.right == other.right && + 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/descending.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/descending.rb new file mode 100644 index 0000000000..f3f6992ca8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/equality.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/equality.rb new file mode 100644 index 0000000000..610411979b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/extract.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/extract.rb new file mode 100644 index 0000000000..5799ee9b8f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/false.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/false.rb new file mode 100644 index 0000000000..1e5bf04be5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/full_outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/full_outer_join.rb new file mode 100644 index 0000000000..91bb81f2e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/function.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/function.rb new file mode 100644 index 0000000000..0a439b39f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/function.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Function < Arel::Nodes::NodeExpression + include Arel::WindowPredications + 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/grouping.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/grouping.rb new file mode 100644 index 0000000000..e01d97ffcb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/homogeneous_in.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/homogeneous_in.rb new file mode 100644 index 0000000000..7a4ce27f26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/homogeneous_in.rb @@ -0,0 +1,76 @@ +# 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 table_name + attribute.relation.table_alias || attribute.relation.name + end + + def column_name + attribute.name + 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, attribute.type_caster) } + 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/in.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/in.rb new file mode 100644 index 0000000000..2154bc6e3a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/infix_operation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/infix_operation.rb new file mode 100644 index 0000000000..462870d13d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/inner_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/inner_join.rb new file mode 100644 index 0000000000..519fafad09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/insert_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/insert_statement.rb new file mode 100644 index 0000000000..d28fd1f6c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + super() + @relation = nil + @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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/join_source.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/join_source.rb new file mode 100644 index 0000000000..d65a9e6693 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/matches.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/matches.rb new file mode 100644 index 0000000000..fd5734f4bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/named_function.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/named_function.rb new file mode 100644 index 0000000000..126462d6d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/node.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/node.rb new file mode 100644 index 0000000000..fe50181b4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/node.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + ### + # Abstract base class for all AST nodes + 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 + collector = engine.connection.visitor.accept self, collector + collector.value + end + + def fetch_attribute + end + + def equality?; false; end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/node_expression.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/node_expression.rb new file mode 100644 index 0000000000..cbcfaba37c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/ordering.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/ordering.rb new file mode 100644 index 0000000000..b09ce2b9e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/outer_join.rb new file mode 100644 index 0000000000..0a3042be61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/over.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/over.rb new file mode 100644 index 0000000000..91176764a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/regexp.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/regexp.rb new file mode 100644 index 0000000000..7c25095569 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/right_outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/right_outer_join.rb new file mode 100644 index 0000000000..04ed4aaa78 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/select_core.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/select_core.rb new file mode 100644 index 0000000000..11b4f39ece --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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 + super() + @source = JoinSource.new nil + + # 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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/select_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/select_statement.rb new file mode 100644 index 0000000000..eff5dad939 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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(cores = [SelectCore.new]) + super() + @cores = cores + @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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/sql_literal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/sql_literal.rb new file mode 100644 index 0000000000..f260c1e27a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/sql_literal.rb @@ -0,0 +1,19 @@ +# 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 + + def encode_with(coder) + coder.scalar = self.to_s + end + + def fetch_attribute + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/string_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/string_join.rb new file mode 100644 index 0000000000..86027fcab7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/table_alias.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/table_alias.rb new file mode 100644 index 0000000000..567d4e2ff4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/table_alias.rb @@ -0,0 +1,31 @@ +# 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/terminal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/terminal.rb new file mode 100644 index 0000000000..d84c453f1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/true.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/true.rb new file mode 100644 index 0000000000..c891012969 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unary.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unary.rb new file mode 100644 index 0000000000..cf1eca2831 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unary_operation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unary_operation.rb new file mode 100644 index 0000000000..524282ac84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unqualified_column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/unqualified_column.rb new file mode 100644 index 0000000000..7c3e0720d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/update_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/update_statement.rb new file mode 100644 index 0000000000..cfaa19e392 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/update_statement.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class UpdateStatement < Arel::Nodes::Node + attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key + + def initialize + @relation = nil + @wheres = [] + @values = [] + @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.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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/values_list.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/values_list.rb new file mode 100644 index 0000000000..1a9d9ebf01 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/window.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/window.rb new file mode 100644 index 0000000000..4916fc7fbe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/nodes/with.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/nodes/with.rb new file mode 100644 index 0000000000..157bdcaa08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/order_predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/order_predications.rb new file mode 100644 index 0000000000..d785bbba92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/predications.rb new file mode 100644 index 0000000000..cdf10d6549 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/predications.rb @@ -0,0 +1,250 @@ +# 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) + not_in([]) + 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)) + else + left = quoted_node(other.begin) + right = quoted_node(other.end) + Nodes::Between.new(self, left.and(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) + self.in([]) + 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.0.0/gems/activerecord-6.1.4/lib/arel/select_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/select_manager.rb new file mode 100644 index 0000000000..dd896cefb3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/select_manager.rb @@ -0,0 +1,270 @@ +# 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) + super() + @ast = Nodes::SelectStatement.new + @ctx = @ast.cores.last + from table + 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) + 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.map { |x| x.from }.compact + 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_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.0.0/gems/activerecord-6.1.4/lib/arel/table.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/table.rb new file mode 100644 index 0000000000..6790fb6e96 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/table.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class Table + include Arel::Crud + include Arel::FactoryMethods + include Arel::AliasPredication + + @engine = nil + class << self; attr_accessor :engine; end + + attr_accessor :name, :table_alias + + # TableAlias and Table both have a #table_name which is the name of the underlying table + alias :table_name :name + + def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster) + @name = name.to_s + @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.to_s 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.0.0/gems/activerecord-6.1.4/lib/arel/tree_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/tree_manager.rb new file mode 100644 index 0000000000..0476399618 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/tree_manager.rb @@ -0,0 +1,72 @@ +# 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 = Nodes.build_quoted(key) + 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 initialize + @ctx = nil + end + + 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 + collector = engine.connection.visitor.accept @ast, collector + collector.value + end + + def initialize_copy(other) + super + @ast = @ast.clone + end + + def where(expr) + if Arel::TreeManager === expr + expr = expr.ast + end + @ctx.wheres << expr + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/update_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/update_manager.rb new file mode 100644 index 0000000000..a809dbb307 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/update_manager.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class UpdateManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize + super + @ast = Nodes::UpdateStatement.new + @ctx = @ast + end + + ### + # UPDATE +table+ + def table(table) + @ast.relation = table + self + end + + def set(values) + if String === values + @ast.values = [values] + else + @ast.values = values.map { |column, value| + Nodes::Assignment.new( + Nodes::UnqualifiedColumn.new(column), + value + ) + } + end + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors.rb new file mode 100644 index 0000000000..ea55a5e4b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/arel/visitors/dot.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/dot.rb new file mode 100644 index 0000000000..45cd22e801 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/dot.rb @@ -0,0 +1,308 @@ +# 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_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_InnerJoin(o) + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin + + def visit_Arel_Nodes_DeleteStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + end + + def unary(o) + visit_edge o, "expr" + end + alias :visit_Arel_Nodes_Group :unary + alias :visit_Arel_Nodes_Cube :unary + alias :visit_Arel_Nodes_RollUp :unary + alias :visit_Arel_Nodes_GroupingSet :unary + alias :visit_Arel_Nodes_GroupingElement :unary + alias :visit_Arel_Nodes_Grouping :unary + alias :visit_Arel_Nodes_Having :unary + alias :visit_Arel_Nodes_Limit :unary + alias :visit_Arel_Nodes_Not :unary + alias :visit_Arel_Nodes_Offset :unary + alias :visit_Arel_Nodes_On :unary + alias :visit_Arel_Nodes_UnqualifiedColumn :unary + alias :visit_Arel_Nodes_OptimizerHints :unary + alias :visit_Arel_Nodes_Preceding :unary + alias :visit_Arel_Nodes_Following :unary + alias :visit_Arel_Nodes_Rows :unary + alias :visit_Arel_Nodes_Range :unary + + def window(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + end + alias :visit_Arel_Nodes_Window :window + + def named_window(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + visit_edge o, "name" + end + alias :visit_Arel_Nodes_NamedWindow :named_window + + def function(o) + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Exists :function + alias :visit_Arel_Nodes_Min :function + alias :visit_Arel_Nodes_Max :function + alias :visit_Arel_Nodes_Avg :function + alias :visit_Arel_Nodes_Sum :function + + def extract(o) + visit_edge o, "expressions" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Extract :extract + + 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" + end + + def visit_Arel_Nodes_SelectCore(o) + visit_edge o, "source" + visit_edge o, "projections" + visit_edge o, "wheres" + visit_edge o, "windows" + end + + def visit_Arel_Nodes_SelectStatement(o) + visit_edge o, "cores" + visit_edge o, "limit" + visit_edge o, "orders" + visit_edge o, "offset" + end + + def visit_Arel_Nodes_UpdateStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + visit_edge o, "values" + 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_Attribute(o) + visit_edge o, "relation" + visit_edge o, "name" + end + alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute + alias :visit_Arel_Attributes_Float :visit_Arel_Attribute + alias :visit_Arel_Attributes_String :visit_Arel_Attribute + alias :visit_Arel_Attributes_Time :visit_Arel_Attribute + alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute + alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute + + def nary(o) + o.children.each_with_index do |x, i| + edge(i) { visit x } + end + end + alias :visit_Arel_Nodes_And :nary + + def binary(o) + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_As :binary + alias :visit_Arel_Nodes_Assignment :binary + alias :visit_Arel_Nodes_Between :binary + alias :visit_Arel_Nodes_Concat :binary + alias :visit_Arel_Nodes_DoesNotMatch :binary + alias :visit_Arel_Nodes_Equality :binary + alias :visit_Arel_Nodes_GreaterThan :binary + alias :visit_Arel_Nodes_GreaterThanOrEqual :binary + alias :visit_Arel_Nodes_In :binary + alias :visit_Arel_Nodes_JoinSource :binary + alias :visit_Arel_Nodes_LessThan :binary + alias :visit_Arel_Nodes_LessThanOrEqual :binary + alias :visit_Arel_Nodes_IsNotDistinctFrom :binary + alias :visit_Arel_Nodes_IsDistinctFrom :binary + alias :visit_Arel_Nodes_Matches :binary + alias :visit_Arel_Nodes_NotEqual :binary + alias :visit_Arel_Nodes_NotIn :binary + alias :visit_Arel_Nodes_Or :binary + alias :visit_Arel_Nodes_Over :binary + + 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) + edge("value") { visit o.value } + end + + def visit_ActiveModel_Attribute(o) + edge("value_before_type_cast") { visit 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 |x, i| + edge(i) { visit x } + end + end + alias :visit_Set :visit_Array + + def visit_Arel_Nodes_Comment(o) + visit_edge(o, "values") + 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.0.0/gems/activerecord-6.1.4/lib/arel/visitors/mysql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/mysql.rb new file mode 100644 index 0000000000..f4075095ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/mysql.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class MySQL < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_Bin(o, collector) + collector << "BINARY " + visit o.expr, collector + 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") + 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 + + # 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_join_sources?(o) && has_limit_or_offset_or_orders?(o) + super + else + o + end + end + alias :prepare_delete_statement :prepare_update_statement + + # MySQL is too stupid to 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))] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/postgresql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/postgresql.rb new file mode 100644 index 0000000000..0afd813528 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/postgresql.rb @@ -0,0 +1,120 @@ +# 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, 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 + + 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 + + BIND_BLOCK = proc { |i| "$#{i}" } + private_constant :BIND_BLOCK + + def bind_block; BIND_BLOCK; end + + # Used by Lateral visitor to enclose select queries in parentheses + def grouping_parentheses(o, collector) + if o.expr.is_a? Nodes::SelectStatement + collector << "(" + visit o.expr, collector + collector << ")" + else + visit o.expr, collector + end + 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.0.0/gems/activerecord-6.1.4/lib/arel/visitors/sqlite.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/sqlite.rb new file mode 100644 index 0000000000..62ec74ad82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/sqlite.rb @@ -0,0 +1,38 @@ +# 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/to_sql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/to_sql.rb new file mode 100644 index 0000000000..f339a2f801 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/to_sql.rb @@ -0,0 +1,899 @@ +# 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) + 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) + 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 << "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 + 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 + + 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_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 + + collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name) + + 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 << ")" + 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 + + def visit_Arel_Nodes_Group(o, collector) + visit o.expr, collector + end + + def visit_Arel_Nodes_NamedFunction(o, collector) + 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) + collector = visit o.left, collector + collector << " >= " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThan(o, collector) + collector = visit o.left, collector + collector << " > " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThanOrEqual(o, collector) + collector = visit o.left, collector + collector << " <= " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThan(o, collector) + 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 o.table_alias + collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias) + else + collector << quote_table_name(o.name) + end + end + + def visit_Arel_Nodes_In(o, collector) + collector.preparable = false + attr, values = o.left, o.right + + if Array === values + 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) + collector.preparable = false + attr, values = o.left, o.right + + if Array === values + 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) + stack = [o.right, o.left] + + while o = stack.pop + if o.is_a?(Arel::Nodes::Or) + stack.push o.right, o.left + else + visit o, collector + collector << " OR " unless stack.empty? + end + end + + collector + end + + def visit_Arel_Nodes_Assignment(o, collector) + case o.right + when Arel::Nodes::Node, Arel::Attributes::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_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_Arel_Nodes_BindParam(o, collector) + collector.add_bind(o.value, &bind_block) + end + + def visit_Arel_Nodes_SqlLiteral(o, collector) + collector.preparable = false + collector << o.to_s + end + + def visit_Integer(o, collector) + collector << o.to_s + end + + def unsupported(o, collector) + raise UnsupportedVisitError.new(o) + end + + alias :visit_ActiveSupport_Multibyte_Chars :unsupported + alias :visit_ActiveSupport_StringInquirer :unsupported + alias :visit_BigDecimal :unsupported + alias :visit_Class :unsupported + alias :visit_Date :unsupported + alias :visit_DateTime :unsupported + alias :visit_FalseClass :unsupported + alias :visit_Float :unsupported + alias :visit_Hash :unsupported + alias :visit_NilClass :unsupported + alias :visit_String :unsupported + alias :visit_Symbol :unsupported + alias :visit_Time :unsupported + alias :visit_TrueClass :unsupported + + def visit_Arel_Nodes_InfixOperation(o, collector) + collector = visit o.left, collector + collector << " #{o.operator} " + visit o.right, collector + end + + def visit_Arel_Nodes_UnaryOperation(o, collector) + collector << " #{o.operator} " + visit o.expr, collector + end + + def visit_Array(o, collector) + inject_join o, collector, ", " + end + alias :visit_Set :visit_Array + + def quote(value) + return value if Arel::Nodes::SqlLiteral === value + @connection.quote value + end + + def quote_table_name(name) + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_table_name(name) + end + + def quote_column_name(name) + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_column_name(name) + end + + def sanitize_as_sql_comment(value) + return value if Arel::Nodes::SqlLiteral === value + @connection.sanitize_as_sql_comment(value) + end + + def collect_optimizer_hints(o, collector) + maybe_visit o.optimizer_hints, collector + end + + def maybe_visit(thing, collector) + return collector unless thing + collector << " " + visit thing, collector + end + + def inject_join(list, collector, join_str) + list.each_with_index do |x, i| + collector << join_str unless i == 0 + collector = visit(x, collector) + end + collector + end + + def unboundable?(value) + value.respond_to?(:unboundable?) && value.unboundable? + end + + def has_join_sources?(o) + o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty? + end + + def has_limit_or_offset_or_orders?(o) + o.limit || o.offset || !o.orders.empty? + end + + # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work + # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in + # an UPDATE statement, so in the MySQL visitor we redefine this to do that. + def prepare_update_statement(o) + if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o)) + stmt = o.clone + stmt.limit = nil + stmt.offset = nil + stmt.orders = [] + stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])] + stmt.relation = o.relation.left if has_join_sources?(o) + stmt + else + o + end + end + alias :prepare_delete_statement :prepare_update_statement + + # FIXME: we should probably have a 2-pass visitor for this + def build_subselect(key, o) + stmt = Nodes::SelectStatement.new + core = stmt.cores.first + core.froms = o.relation + core.wheres = o.wheres + core.projections = [key] + stmt.limit = o.limit + stmt.offset = o.offset + stmt.orders = o.orders + stmt + end + + def infix_value(o, collector, value) + collector = visit o.left, collector + collector << value + visit o.right, collector + end + + def infix_value_with_paren(o, collector, value, suppress_parens = false) + collector << "( " unless suppress_parens + collector = if o.left.class == o.class + infix_value_with_paren(o.left, collector, value, true) + else + visit o.left, collector + end + collector << value + collector = if o.right.class == o.class + infix_value_with_paren(o.right, collector, value, true) + else + visit o.right, collector + end + collector << " )" unless suppress_parens + collector + end + + def aggregate(name, o, collector) + collector << "#{name}(" + if o.distinct + collector << "DISTINCT " + end + collector = inject_join(o.expressions, collector, ", ") << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def is_distinct_from(o, collector) + collector << "CASE WHEN " + collector = visit o.left, collector + collector << " = " + collector = visit o.right, collector + collector << " OR (" + collector = visit o.left, collector + collector << " IS NULL AND " + collector = visit o.right, collector + collector << " IS NULL)" + collector << " THEN 0 ELSE 1 END" + end + + def collect_ctes(children, collector) + children.each_with_index do |child, i| + collector << ", " unless i == 0 + + case child + when Arel::Nodes::As + name = child.left.name + relation = child.right + when Arel::Nodes::TableAlias + name = child.name + relation = child.relation + end + + collector << quote_table_name(name) + collector << " AS " + visit relation, collector + end + + collector + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/visitor.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/visitor.rb new file mode 100644 index 0000000000..9066307aed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/visitors/visitor.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class Visitor + def initialize + @dispatch = get_dispatch_cache + end + + def accept(object, collector = nil) + visit object, collector + end + + private + attr_reader :dispatch + + def self.dispatch_cache + @dispatch_cache ||= Hash.new do |hash, klass| + hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}" + end + end + + def get_dispatch_cache + self.class.dispatch_cache + end + + def visit(object, collector = nil) + dispatch_method = dispatch[object.class] + if collector + send dispatch_method, object, collector + else + send dispatch_method, object + end + rescue NoMethodError => 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.0.0/gems/activerecord-6.1.4/lib/arel/window_predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/arel/window_predications.rb new file mode 100644 index 0000000000..3a8ee41f8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record.rb new file mode 100644 index 0000000000..a7e5e373a7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/application_record/application_record_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/application_record/application_record_generator.rb new file mode 100644 index 0000000000..56b9628a92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt new file mode 100644 index 0000000000..60050e0bf8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration.rb new file mode 100644 index 0000000000..724da797f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration.rb @@ -0,0 +1,52 @@ +# 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 + ) + config&.migrations_paths + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/migration_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/migration_generator.rb new file mode 100644 index 0000000000..0620a515bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt new file mode 100644 index 0000000000..e41a197095 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt @@ -0,0 +1,26 @@ +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 -%> +<% 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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/templates/migration.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/migration/templates/migration.rb.tt new file mode 100644 index 0000000000..f203823384 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/model_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/model_generator.rb new file mode 100644 index 0000000000..c025e4d8cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/model_generator.rb @@ -0,0 +1,85 @@ +# 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, 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." + + # 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 "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb") + end + + def create_model_file + generate_abstract_class if database && !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? + 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 parent + parent + elsif database + abstract_class_name + else + "ApplicationRecord" + 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 migration + options[:migration] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt new file mode 100644 index 0000000000..7ca973fcb9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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::Base.writing_role %>: :<%= database -%> } +end +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/model.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/model.rb.tt new file mode 100644 index 0000000000..77b9ea1c86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/module.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/lib/rails/generators/active_record/model/templates/module.rb.tt new file mode 100644 index 0000000000..a3bf1c37b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.4/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.0.0/gems/activerecord-6.1.7.2/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/CHANGELOG.md new file mode 100644 index 0000000000..36cce5816b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/CHANGELOG.md @@ -0,0 +1,1802 @@ +## Rails 6.1.7.2 (January 24, 2023) ## + +* No changes. + + +## Rails 6.1.7.1 (January 17, 2023) ## + +* Make sanitize_as_sql_comment more strict + + Though this method was likely never meant to take user input, it was + attempting sanitization. That sanitization could be bypassed with + carefully crafted input. + + This commit makes the sanitization more robust by replacing any + occurrances of "/*" or "*/" with "/ *" or "* /". It also performs a + first pass to remove one surrounding comment to avoid compatibility + issues for users relying on the existing removal. + + This also clarifies in the documentation of annotate that it should not + be provided user input. + + [CVE-2023-22794] + +* Added integer width check to PostgreSQL::Quoting + + Given a value outside the range for a 64bit signed integer type + PostgreSQL will treat the column type as numeric. Comparing + integer values against numeric values can result in a slow + sequential scan. + + This behavior is configurable via + ActiveRecord::Base.raise_int_wider_than_64bit which defaults to true. + + [CVE-2022-44566] + +## Rails 6.1.7 (September 09, 2022) ## + +* Symbol is allowed by default for YAML columns + + *Étienne Barrié* + +* Fix `ActiveRecord::Store` to serialize as a regular Hash + + Previously it would serialize as an `ActiveSupport::HashWithIndifferentAccess` + which is wasteful and cause problem with YAML safe_load. + + *Jean Boussier* + +* Fix PG.connect keyword arguments deprecation warning on ruby 2.7 + + Fixes #44307. + + *Nikita Vasilevsky* + +## Rails 6.1.6.1 (July 12, 2022) ## + +* Change ActiveRecord::Coders::YAMLColumn default to safe_load + + This adds two new configuration options The configuration options are as + follows: + + * `config.active_storage.use_yaml_unsafe_load` + + When set to true, this configuration option tells Rails to use the old + "unsafe" YAML loading strategy, maintaining the existing behavior but leaving + the possible escalation vulnerability in place. Setting this option to true + is *not* recommended, but can aid in upgrading. + + * `config.active_record.yaml_column_permitted_classes` + + The "safe YAML" loading method does not allow all classes to be deserialized + by default. This option allows you to specify classes deemed "safe" in your + application. For example, if your application uses Symbol and Time in + serialized data, you can add Symbol and Time to the allowed list as follows: + + ``` + config.active_record.yaml_column_permitted_classes = [Symbol, Date, Time] + ``` + + [CVE-2022-32224] + + +## Rails 6.1.6 (May 09, 2022) ## + +* No changes. + + +## Rails 6.1.5.1 (April 26, 2022) ## + +* No changes. + + +## Rails 6.1.5 (March 09, 2022) ## + +* Fix `ActiveRecord::ConnectionAdapters::SchemaCache#deep_deduplicate` for Ruby 2.6. + + Ruby 2.6 and 2.7 have slightly different implementations of the `String#-@` method. + In Ruby 2.6, the receiver of the `String#-@` method is modified under certain circumstances. + This was later identified as a bug (https://bugs.ruby-lang.org/issues/15926) and only + fixed in Ruby 2.7. + + Before the changes in this commit, the + `ActiveRecord::ConnectionAdapters::SchemaCache#deep_deduplicate` method, which internally + calls the `String#-@` method, could also modify an input string argument in Ruby 2.6 -- + changing a tainted, unfrozen string into a tainted, frozen string. + + Fixes #43056 + + *Eric O'Hanlon* + +* Fix migration compatibility to create SQLite references/belongs_to column as integer when + migration version is 6.0. + + `reference`/`belongs_to` in migrations with version 6.0 were creating columns as + bigint instead of integer for the SQLite Adapter. + + *Marcelo Lauxen* + +* Fix dbconsole for 3-tier config. + + *Eileen M. Uchitelle* + +* Better handle SQL queries with invalid encoding. + + ```ruby + Post.create(name: "broken \xC8 UTF-8") + ``` + + Would cause all adapters to fail in a non controlled way in the code + responsible to detect write queries. + + The query is now properly passed to the database connection, which might or might + not be able to handle it, but will either succeed or failed in a more correct way. + + *Jean Boussier* + +* Ignore persisted in-memory records when merging target lists. + + *Kevin Sjöberg* + +* Fix regression bug that caused ignoring additional conditions for preloading + `has_many` through relations. + + Fixes #43132 + + *Alexander Pauly* + +* Fix `ActiveRecord::InternalMetadata` to not be broken by + `config.active_record.record_timestamps = false` + + Since the model always create the timestamp columns, it has to set them, otherwise it breaks + various DB management tasks. + + Fixes #42983 + + *Jean Boussier* + +* Fix duplicate active record objects on `inverse_of`. + + *Justin Carvalho* + +* Fix duplicate objects stored in has many association after save. + + Fixes #42549. + + *Alex Ghiculescu* + +* Fix performance regression in `CollectionAssocation#build`. + + *Alex Ghiculescu* + +* Fix retrieving default value for text column for MariaDB. + + *fatkodima* + + +## Rails 6.1.4.7 (March 08, 2022) ## + +* No changes. + + +## Rails 6.1.4.6 (February 11, 2022) ## + +* No changes. + + +## Rails 6.1.4.5 (February 11, 2022) ## + +* No changes. + + +## Rails 6.1.4.4 (December 15, 2021) ## + +* No changes. + + +## Rails 6.1.4.3 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.2 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.1 (August 19, 2021) ## + +* No changes. + + +## Rails 6.1.4 (June 24, 2021) ## + +* Do not try to rollback transactions that failed due to a `ActiveRecord::TransactionRollbackError`. + + *Jamie McCarthy* + +* Raise an error if `pool_config` is `nil` in `set_pool_config`. + + *Eileen M. Uchitelle* + +* Fix compatibility with `psych >= 4`. + + Starting in Psych 4.0.0 `YAML.load` behaves like `YAML.safe_load`. To preserve compatibility + Active Record's schema cache loader and `YAMLColumn` now uses `YAML.unsafe_load` if available. + + *Jean Boussier* + +* Support using replicas when using `rails dbconsole`. + + *Christopher Thornton* + +* Restore connection pools after transactional tests. + + *Eugene Kenny* + +* Change `upsert_all` to fails cleanly for MySQL when `:unique_by` is used. + + *Bastian Bartmann* + +* Fix user-defined `self.default_scope` to respect table alias. + + *Ryuta Kamizono* + +* Clear `@cache_keys` cache after `update_all`, `delete_all`, `destroy_all`. + + *Ryuta Kamizono* + +* Changed Arel predications `contains` and `overlaps` to use + `quoted_node` so that PostgreSQL arrays are quoted properly. + + *Bradley Priest* + +* Fix `merge` when the `where` clauses have string contents. + + *Ryuta Kamizono* + +* Fix rollback of parent destruction with nested `dependent: :destroy`. + + *Jacopo Beschi* + +* Fix binds logging for `"WHERE ... IN ..."` statements. + + *Ricardo Díaz* + +* Handle `false` in relation strict loading checks. + + Previously when a model had strict loading set to true and then had a + relation set `strict_loading` to false the false wasn't considered when + deciding whether to raise/warn about strict loading. + + ``` + class Dog < ActiveRecord::Base + self.strict_loading_by_default = true + + has_many :treats, strict_loading: false + end + ``` + + In the example, `dog.treats` would still raise even though + `strict_loading` was set to false. This is a bug affecting more than + Active Storage which is why I made this PR superseding #41461. We need + to fix this for all applications since the behavior is a little + surprising. I took the test from #41461 and the code suggestion from #41453 + with some additions. + + *Eileen M. Uchitelle*, *Radamés Roriz* + +* Fix numericality validator without precision. + + *Ryuta Kamizono* + +* Fix aggregate attribute on Enum types. + + *Ryuta Kamizono* + +* Fix `CREATE INDEX` statement generation for PostgreSQL. + + *eltongo* + +* Fix where clause on enum attribute when providing array of strings. + + *Ryuta Kamizono* + +* Fix `unprepared_statement` to work it when nesting. + + *Ryuta Kamizono* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* Fix the MySQL adapter to always set the right collation and charset + to the connection session. + + *Rafael Mendonça França* + +* Fix MySQL adapter handling of time objects when prepared statements + are enabled. + + *Rafael Mendonça França* + +* Fix scoping in enum fields using conditions that would generate + an `IN` clause. + + *Ryuta Kamizono* + +* Skip optimised #exist? query when #include? is called on a relation + with a having clause + + Relations that have aliased select values AND a having clause that + references an aliased select value would generate an error when + #include? was called, due to an optimisation that would generate + call #exists? on the relation instead, which effectively alters + the select values of the query (and thus removes the aliased select + values), but leaves the having clause intact. Because the having + clause is then referencing an aliased column that is no longer + present in the simplified query, an ActiveRecord::InvalidStatement + error was raised. + + An sample query affected by this problem: + + ```ruby + Author.select('COUNT(*) as total_posts', 'authors.*') + .joins(:posts) + .group(:id) + .having('total_posts > 2') + .include?(Author.first) + ``` + + This change adds an addition check to the condition that skips the + simplified #exists? query, which simply checks for the presence of + a having clause. + + Fixes #41417 + + *Michael Smart* + +* Increment postgres prepared statement counter before making a prepared statement, so if the statement is aborted + without Rails knowledge (e.g., if app gets kill -9d during long-running query or due to Rack::Timeout), app won't end + up in perpetual crash state for being inconsistent with Postgres. + + *wbharding*, *Martin Tepper* + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* Fix possible DoS vector in PostgreSQL money type + + Carefully crafted input can cause a DoS via the regular expressions used + for validating the money format in the PostgreSQL adapter. This patch + fixes the regexp. + + Thanks to @dee-see from Hackerone for this patch! + + [CVE-2021-22880] + + *Aaron Patterson* + + +## Rails 6.1.2 (February 09, 2021) ## + +* Fix timestamp type for sqlite3. + + *Eileen M. Uchitelle* + +* Make destroy async transactional. + + An active record rollback could occur while enqueuing a job. In this + case the job would enqueue even though the database deletion + rolledback putting things in a funky state. + + Now the jobs are only enqueued until after the db transaction has been committed. + + *Cory Gwin* + +* Fix malformed packet error in MySQL statement for connection configuration. + + *robinroestenburg* + +* Connection specification now passes the "url" key as a configuration for the + adapter if the "url" protocol is "jdbc", "http", or "https". Previously only + urls with the "jdbc" prefix were passed to the Active Record Adapter, others + are assumed to be adapter specification urls. + + Fixes #41137. + + *Jonathan Bracy* + +* Fix granular connection swapping when there are multiple abstract classes. + + *Eileen M. Uchitelle* + +* Fix `find_by` with custom primary key for belongs_to association. + + *Ryuta Kamizono* + +* Add support for `rails console --sandbox` for multiple database applications. + + *alpaca-tc* + +* Fix `where` on polymorphic association with empty array. + + *Ryuta Kamizono* + +* Fix preventing writes for `ApplicationRecord`. + + *Eileen M. Uchitelle* + + +## Rails 6.1.1 (January 07, 2021) ## + +* Fix fixtures loading when strict loading is enabled for the association. + + *Alex Ghiculescu* + +* Fix `where` with custom primary key for belongs_to association. + + *Ryuta Kamizono* + +* Fix `where` with aliased associations. + + *Ryuta Kamizono* + +* Fix `composed_of` with symbol mapping. + + *Ryuta Kamizono* + +* Don't skip money's type cast for pluck and calculations. + + *Ryuta Kamizono* + +* Fix `where` on polymorphic association with non Active Record object. + + *Ryuta Kamizono* + +* Make sure `db:prepare` works even the schema file doesn't exist. + + *Rafael Mendonça França* + +* Fix complicated `has_many :through` with nested where condition. + + *Ryuta Kamizono* + +* Handle STI models for `has_many dependent: :destroy_async`. + + *Muhammad Usman* + +* Restore possibility of passing `false` to :polymorphic option of `belongs_to`. + + Previously, passing `false` would trigger the option validation logic + to throw an error saying :polymorphic would not be a valid option. + + *glaszig* + +* Allow adding nonnamed expression indexes to be revertible. + + Fixes #40732. + + Previously, the following code would raise an error, when executed while rolling back, + and the index name should be specified explicitly. Now, the index name is inferred + automatically. + + ```ruby + add_index(:items, "to_tsvector('english', description)") + ``` + + *fatkodima* + + +## Rails 6.1.0 (December 09, 2020) ## + +* Only warn about negative enums if a positive form that would cause conflicts exists. + + Fixes #39065. + + *Alex Ghiculescu* + +* Change `attribute_for_inspect` to take `filter_attributes` in consideration. + + *Rafael Mendonça França* + +* Fix odd behavior of inverse_of with multiple belongs_to to same class. + + Fixes #35204. + + *Tomoyuki Kai* + +* Build predicate conditions with objects that delegate `#id` and primary key: + + ```ruby + class AdminAuthor + delegate_missing_to :@author + + def initialize(author) + @author = author + end + end + + Post.where(author: AdminAuthor.new(author)) + ``` + + *Sean Doyle* + +* Add `connected_to_many` API. + + This API allows applications to connect to multiple databases at once without switching all of them or implementing a deeply nested stack. + + Before: + + AnimalsRecord.connected_to(role: :reading) do + MealsRecord.connected_to(role: :reading) do + Dog.first # read from animals replica + Dinner.first # read from meals replica + Person.first # read from primary writer + end + end + + After: + + 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 + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add option to raise or log for `ActiveRecord::StrictLoadingViolationError`. + + Some applications may not want to raise an error in production if using `strict_loading`. This would allow an application to set strict loading to log for the production environment while still raising in development and test environments. + + Set `config.active_record.action_on_strict_loading_violation` to `:log` errors instead of raising. + + *Eileen M. Uchitelle* + +* Allow the inverse of a `has_one` association that was previously autosaved to be loaded. + + Fixes #34255. + + *Steven Weber* + +* Optimise the length of index names for polymorphic references by using the reference name rather than the type and id column names. + + Because the default behaviour when adding an index with multiple columns is to use all column names in the index name, this could frequently lead to overly long index names for polymorphic references which would fail the migration if it exceeded the database limit. + + This change reduces the chance of that happening by using the reference name, e.g. `index_my_table_on_my_reference`. + + Fixes #38655. + + *Luke Redpath* + +* MySQL: Uniqueness validator now respects default database collation, + no longer enforce case sensitive comparison by default. + + *Ryuta Kamizono* + +* Remove deprecated methods from `ActiveRecord::ConnectionAdapters::DatabaseLimits`. + + `column_name_length` + `table_name_length` + `columns_per_table` + `indexes_per_table` + `columns_per_multicolumn_index` + `sql_query_length` + `joins_per_query` + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_multi_insert?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_foreign_keys_in_create?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#supports_ranges?`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::Base#update_attributes` and `ActiveRecord::Base#update_attributes!`. + + *Rafael Mendonça França* + +* Remove deprecated `migrations_path` argument in `ActiveRecord::ConnectionAdapter::SchemaStatements#assume_migrated_upto_version`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.sqlite3.represent_boolean_as_integer`. + + *Rafael Mendonça França* + +* `relation.create` does no longer leak scope to class level querying methods + in initialization block and callbacks. + + Before: + + User.where(name: "John").create do |john| + User.find_by(name: "David") # => nil + end + + After: + + User.where(name: "John").create do |john| + User.find_by(name: "David") # => # + end + + *Ryuta Kamizono* + +* Named scope chain does no longer leak scope to class level querying methods. + + class User < ActiveRecord::Base + scope :david, -> { User.where(name: "David") } + end + + Before: + + User.where(name: "John").david + # SELECT * FROM users WHERE name = 'John' AND name = 'David' + + After: + + User.where(name: "John").david + # SELECT * FROM users WHERE name = 'David' + + *Ryuta Kamizono* + +* Remove deprecated methods from `ActiveRecord::DatabaseConfigurations`. + + `fetch` + `each` + `first` + `values` + `[]=` + + *Rafael Mendonça França* + +* `where.not` now generates NAND predicates instead of NOR. + + Before: + + User.where.not(name: "Jon", role: "admin") + # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' + + After: + + User.where.not(name: "Jon", role: "admin") + # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin') + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::Result#to_hash` method. + + *Rafael Mendonça França* + +* Deprecate `ActiveRecord::Base.allow_unsafe_raw_sql`. + + *Rafael Mendonça França* + +* Remove deprecated support for using unsafe raw SQL in `ActiveRecord::Relation` methods. + + *Rafael Mendonça França* + +* Allow users to silence the "Rails couldn't infer whether you are using multiple databases..." + message using `config.active_record.suppress_multiple_database_warning`. + + *Omri Gabay* + +* Connections can be granularly switched for abstract classes when `connected_to` is called. + + This change allows `connected_to` to switch a `role` and/or `shard` for a single abstract class instead of all classes globally. Applications that want to use the new feature need to set `config.active_record.legacy_connection_handling` to `false` in their application configuration. + + Example usage: + + Given an application we have a `User` model that inherits from `ApplicationRecord` and a `Dog` model that inherits from `AnimalsRecord`. `AnimalsRecord` and `ApplicationRecord` have writing and reading connections as well as shard `default`, `one`, and `two`. + + ```ruby + ActiveRecord::Base.connected_to(role: :reading) do + User.first # reads from default replica + Dog.first # reads from default replica + + AnimalsRecord.connected_to(role: :writing, shard: :one) do + User.first # reads from default replica + Dog.first # reads from shard one primary + end + + User.first # reads from default replica + Dog.first # reads from default replica + + ApplicationRecord.connected_to(role: :writing, shard: :two) do + User.first # reads from shard two primary + Dog.first # reads from default replica + end + end + ``` + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow double-dash comment syntax when querying read-only databases + + *James Adam* + +* Add `values_at` method. + + Returns an array containing the values associated with the given methods. + + ```ruby + topic = Topic.first + topic.values_at(:title, :author_name) + # => ["Budget", "Jason"] + ``` + + Similar to `Hash#values_at` but on an Active Record instance. + + *Guillaume Briday* + +* Fix `read_attribute_before_type_cast` to consider attribute aliases. + + *Marcelo Lauxen* + +* Support passing record to uniqueness validator `:conditions` callable: + + ```ruby + class Article < ApplicationRecord + 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 + ``` + + *Eliot Sykes* + +* `BatchEnumerator#update_all` and `BatchEnumerator#delete_all` now return the + total number of rows affected, just like their non-batched counterparts. + + ```ruby + Person.in_batches.update_all("first_name = 'Eugene'") # => 42 + Person.in_batches.delete_all # => 42 + ``` + + Fixes #40287. + + *Eugene Kenny* + +* Add support for PostgreSQL `interval` data type with conversion to + `ActiveSupport::Duration` when loading records from database and + serialization to ISO 8601 formatted duration string on save. + Add support to define a column in migrations and get it in a schema dump. + Optional column precision is supported. + + To use this in 6.1, you need to place the next string to your model file: + + attribute :duration, :interval + + To keep old behavior until 7.0 is released: + + attribute :duration, :string + + Example: + + create_table :events do |t| + t.string :name + t.interval :duration + end + + class Event < ApplicationRecord + attribute :duration, :interval + end + + Event.create!(name: 'Rock Fest', duration: 2.days) + Event.last.duration # => 2 days + Event.last.duration.iso8601 # => "P2D" + Event.new(duration: 'P1DT12H3S').duration # => 1 day, 12 hours, and 3 seconds + Event.new(duration: '1 day') # Unknown value will be ignored and NULL will be written to database + + *Andrey Novikov* + +* Allow associations supporting the `dependent:` key to take `dependent: :destroy_async`. + + ```ruby + class Account < ActiveRecord::Base + belongs_to :supplier, dependent: :destroy_async + end + ``` + + `:destroy_async` will enqueue a job to destroy associated records in the background. + + *DHH*, *George Claghorn*, *Cory Gwin*, *Rafael Mendonça França*, *Adrianna Chang* + +* Add `SKIP_TEST_DATABASE` environment variable to disable modifying the test database when `rails db:create` and `rails db:drop` are called. + + *Jason Schweier* + +* `connects_to` can only be called on `ActiveRecord::Base` or abstract classes. + + Ensure that `connects_to` can only be called from `ActiveRecord::Base` or abstract classes. This protects the application from opening duplicate or too many connections. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* All connection adapters `execute` now raises `ActiveRecord::ConnectionNotEstablished` rather than + `ActiveRecord::StatementInvalid` when they encounter a connection error. + + *Jean Boussier* + +* `Mysql2Adapter#quote_string` now raises `ActiveRecord::ConnectionNotEstablished` rather than + `ActiveRecord::StatementInvalid` when it can't connect to the MySQL server. + + *Jean Boussier* + +* Add support for check constraints that are `NOT VALID` via `validate: false` (PostgreSQL-only). + + *Alex Robbin* + +* Ensure the default configuration is considered primary or first for an environment + + If a multiple database application provides a configuration named primary, that will be treated as default. In applications that do not have a primary entry, the default database configuration will be the first configuration for an environment. + + *Eileen M. Uchitelle* + +* Allow `where` references association names as joined table name aliases. + + ```ruby + class Comment < ActiveRecord::Base + enum label: [:default, :child] + has_many :children, class_name: "Comment", foreign_key: :parent_id + end + + # ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1 + Comment.includes(:children).where("children.label": "child") + ``` + + *Ryuta Kamizono* + +* Support storing demodulized class name for polymorphic type. + + Before Rails 6.1, storing demodulized class name is supported only for STI type + by `store_full_sti_class` class attribute. + + Now `store_full_class_name` class attribute can handle both STI and polymorphic types. + + *Ryuta Kamizono* + +* Deprecate `rails db:structure:{load, dump}` tasks and extend + `rails db:schema:{load, dump}` tasks to work with either `:ruby` or `:sql` format, + depending on `config.active_record.schema_format` configuration value. + + *fatkodima* + +* Respect the `select` values for eager loading. + + ```ruby + post = Post.select("UPPER(title) AS title").first + post.title # => "WELCOME TO THE WEBLOG" + post.body # => ActiveModel::MissingAttributeError + + # Rails 6.0 (ignore the `select` values) + post = Post.select("UPPER(title) AS title").eager_load(:comments).first + post.title # => "Welcome to the weblog" + post.body # => "Such a lovely day" + + # Rails 6.1 (respect the `select` values) + post = Post.select("UPPER(title) AS title").eager_load(:comments).first + post.title # => "WELCOME TO THE WEBLOG" + post.body # => ActiveModel::MissingAttributeError + ``` + + *Ryuta Kamizono* + +* Allow attribute's default to be configured but keeping its own type. + + ```ruby + class Post < ActiveRecord::Base + attribute :written_at, default: -> { Time.now.utc } + end + + # Rails 6.0 + Post.type_for_attribute(:written_at) # => # + + # Rails 6.1 + Post.type_for_attribute(:written_at) # => # + ``` + + *Ryuta Kamizono* + +* Allow default to be configured for Enum. + + ```ruby + class Book < ActiveRecord::Base + enum status: [:proposed, :written, :published], _default: :published + end + + Book.new.status # => "published" + ``` + + *Ryuta Kamizono* + +* Deprecate YAML loading from legacy format older than Rails 5.0. + + *Ryuta Kamizono* + +* Added the setting `ActiveRecord::Base.immutable_strings_by_default`, which + allows you to specify that all string columns should be frozen unless + otherwise specified. This will reduce memory pressure for applications which + do not generally mutate string properties of Active Record objects. + + *Sean Griffin*, *Ryuta Kamizono* + +* Deprecate `map!` and `collect!` on `ActiveRecord::Result`. + + *Ryuta Kamizono* + +* Support `relation.and` for intersection as Set theory. + + ```ruby + david_and_mary = Author.where(id: [david, mary]) + mary_and_bob = Author.where(id: [mary, bob]) + + david_and_mary.merge(mary_and_bob) # => [mary, bob] + + david_and_mary.and(mary_and_bob) # => [mary] + david_and_mary.or(mary_and_bob) # => [david, mary, bob] + ``` + + *Ryuta Kamizono* + +* Merging conditions on the same column no longer maintain both conditions, + and will be consistently replaced by the latter condition in Rails 7.0. + To migrate to Rails 7.0's behavior, use `relation.merge(other, rewhere: true)`. + + ```ruby + # Rails 6.1 (IN clause is replaced by merger side equality condition) + Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob] + + # Rails 6.1 (both conflict conditions exists, deprecated) + Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [] + + # Rails 6.1 with rewhere to migrate to Rails 7.0's behavior + Author.where(id: david.id..mary.id).merge(Author.where(id: bob), rewhere: true) # => [bob] + + # Rails 7.0 (same behavior with IN clause, mergee side condition is consistently replaced) + Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob] + Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [bob] + ``` + + *Ryuta Kamizono* + +* Do not mark Postgresql MAC address and UUID attributes as changed when the assigned value only varies by case. + + *Peter Fry* + +* Resolve issue with insert_all unique_by option when used with expression index. + + When the `:unique_by` option of `ActiveRecord::Persistence.insert_all` and + `ActiveRecord::Persistence.upsert_all` was used with the name of an expression index, an error + was raised. Adding a guard around the formatting behavior for the `:unique_by` corrects this. + + Usage: + + ```ruby + create_table :books, id: :integer, force: true do |t| + t.column :name, :string + t.index "lower(name)", unique: true + end + + Book.insert_all [{ name: "MyTest" }], unique_by: :index_books_on_lower_name + ``` + + Fixes #39516. + + *Austen Madden* + +* Add basic support for CHECK constraints to database migrations. + + Usage: + + ```ruby + add_check_constraint :products, "price > 0", name: "price_check" + remove_check_constraint :products, name: "price_check" + ``` + + *fatkodima* + +* Add `ActiveRecord::Base.strict_loading_by_default` and `ActiveRecord::Base.strict_loading_by_default=` + to enable/disable strict_loading mode by default for a model. The configuration's value is + inheritable by subclasses, but they can override that value and it will not impact parent class. + + Usage: + + ```ruby + class Developer < ApplicationRecord + self.strict_loading_by_default = true + + has_many :projects + end + + dev = Developer.first + dev.projects.first + # => ActiveRecord::StrictLoadingViolationError Exception: Developer is marked as strict_loading and Project cannot be lazily loaded. + ``` + + *bogdanvlviv* + +* Deprecate passing an Active Record object to `quote`/`type_cast` directly. + + *Ryuta Kamizono* + +* Default engine `ENGINE=InnoDB` is no longer dumped to make schema more agnostic. + + Before: + + ```ruby + create_table "accounts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| + end + ``` + + After: + + ```ruby + create_table "accounts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + end + ``` + + *Ryuta Kamizono* + +* Added delegated type as an alternative to single-table inheritance for representing class hierarchies. + See ActiveRecord::DelegatedType for the full description. + + *DHH* + +* Deprecate aggregations with group by duplicated fields. + + To migrate to Rails 7.0's behavior, use `uniq!(:group)` to deduplicate group fields. + + ```ruby + accounts = Account.group(:firm_id) + + # duplicated group fields, deprecated. + accounts.merge(accounts.where.not(credit_limit: nil)).sum(:credit_limit) + # => { + # [1, 1] => 50, + # [2, 2] => 60 + # } + + # use `uniq!(:group)` to deduplicate group fields. + accounts.merge(accounts.where.not(credit_limit: nil)).uniq!(:group).sum(:credit_limit) + # => { + # 1 => 50, + # 2 => 60 + # } + ``` + + *Ryuta Kamizono* + +* Deprecate duplicated query annotations. + + To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations. + + ```ruby + accounts = Account.where(id: [1, 2]).annotate("david and mary") + + # duplicated annotations, deprecated. + accounts.merge(accounts.rewhere(id: 3)) + # SELECT accounts.* FROM accounts WHERE accounts.id = 3 /* david and mary */ /* david and mary */ + + # use `uniq!(:annotate)` to deduplicate annotations. + accounts.merge(accounts.rewhere(id: 3)).uniq!(:annotate) + # SELECT accounts.* FROM accounts WHERE accounts.id = 3 /* david and mary */ + ``` + + *Ryuta Kamizono* + +* Resolve conflict between counter cache and optimistic locking. + + Bump an Active Record instance's lock version after updating its counter + cache. This avoids raising an unnecessary `ActiveRecord::StaleObjectError` + upon subsequent transactions by maintaining parity with the corresponding + database record's `lock_version` column. + + Fixes #16449. + + *Aaron Lipman* + +* Support merging option `:rewhere` to allow mergee side condition to be replaced exactly. + + ```ruby + david_and_mary = Author.where(id: david.id..mary.id) + + # both conflict conditions exists + david_and_mary.merge(Author.where(id: bob)) # => [] + + # mergee side condition is replaced by rewhere + david_and_mary.merge(Author.rewhere(id: bob)) # => [bob] + + # mergee side condition is replaced by rewhere option + david_and_mary.merge(Author.where(id: bob), rewhere: true) # => [bob] + ``` + + *Ryuta Kamizono* + +* Add support for finding records based on signed ids, which are tamper-proof, verified ids that can be + set to expire and scoped with a purpose. 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. + + ```ruby + 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 + + User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature + ``` + + *DHH* + +* Support `ALGORITHM = INSTANT` DDL option for index operations on MySQL. + + *Ryuta Kamizono* + +* Fix index creation to preserve index comment in bulk change table on MySQL. + + *Ryuta Kamizono* + +* Allow `unscope` to be aware of table name qualified values. + + It is possible to unscope only the column in the specified table. + + ```ruby + posts = Post.joins(:comments).group(:"posts.hidden") + posts = posts.where("posts.hidden": false, "comments.hidden": false) + + posts.count + # => { false => 10 } + + # unscope both hidden columns + posts.unscope(where: :hidden).count + # => { false => 11, true => 1 } + + # unscope only comments.hidden column + posts.unscope(where: :"comments.hidden").count + # => { false => 11 } + ``` + + *Ryuta Kamizono*, *Slava Korolev* + +* Fix `rewhere` to truly overwrite collided where clause by new where clause. + + ```ruby + steve = Person.find_by(name: "Steve") + david = Author.find_by(name: "David") + + relation = Essay.where(writer: steve) + + # Before + relation.rewhere(writer: david).to_a # => [] + + # After + relation.rewhere(writer: david).to_a # => [david] + ``` + + *Ryuta Kamizono* + +* Inspect time attributes with subsec and time zone offset. + + ```ruby + p Knot.create + => # + ``` + + *akinomaeni*, *Jonathan Hefner* + +* Deprecate passing a column to `type_cast`. + + *Ryuta Kamizono* + +* Deprecate `in_clause_length` and `allowed_index_name_length` in `DatabaseLimits`. + + *Ryuta Kamizono* + +* Support bulk insert/upsert on relation to preserve scope values. + + *Josef Šimánek*, *Ryuta Kamizono* + +* Preserve column comment value on changing column name on MySQL. + + *Islam Taha* + +* Add support for `if_exists` option for removing an index. + + The `remove_index` method can take an `if_exists` option. If this is set to true an error won't be raised if the index doesn't exist. + + *Eileen M. Uchitelle* + +* Remove ibm_db, informix, mssql, oracle, and oracle12 Arel visitors which are not used in the code base. + + *Ryuta Kamizono* + +* Prevent `build_association` from `touching` a parent record if the record isn't persisted for `has_one` associations. + + Fixes #38219. + + *Josh Brody* + +* Add support for `if_not_exists` option for adding index. + + The `add_index` method respects `if_not_exists` option. If it is set to true + index won't be added. + + Usage: + + ```ruby + add_index :users, :account_id, if_not_exists: true + ``` + + The `if_not_exists` option passed to `create_table` also gets propagated to indexes + created within that migration so that if table and its indexes exist then there is no + attempt to create them again. + + *Prathamesh Sonpatki* + +* Add `ActiveRecord::Base#previously_new_record?` to show if a record was new before the last save. + + *Tom Ward* + +* Support descending order for `find_each`, `find_in_batches`, and `in_batches`. + + Batch processing methods allow you to work with the records in batches, greatly reducing memory consumption, but records are always batched from oldest id to newest. + + This change allows reversing the order, batching from newest to oldest. This is useful when you need to process newer batches of records first. + + Pass `order: :desc` to yield batches in descending order. The default remains `order: :asc`. + + ```ruby + Person.find_each(order: :desc) do |person| + person.party_all_night! + end + ``` + + *Alexey Vasiliev* + +* Fix `insert_all` with enum values. + + Fixes #38716. + + *Joel Blum* + +* Add support for `db:rollback:name` for multiple database applications. + + Multiple database applications will now raise if `db:rollback` is call and recommend using the `db:rollback:[NAME]` to rollback migrations. + + *Eileen M. Uchitelle* + +* `Relation#pick` now uses already loaded results instead of making another query. + + *Eugene Kenny* + +* Deprecate using `return`, `break` or `throw` to exit a transaction block after writes. + + *Dylan Thacker-Smith* + +* Dump the schema or structure of a database when calling `db:migrate:name`. + + In previous versions of Rails, `rails db:migrate` would dump the schema of the database. In Rails 6, that holds true (`rails db:migrate` dumps all databases' schemas), but `rails db:migrate:name` does not share that behavior. + + Going forward, calls to `rails db:migrate:name` will dump the schema (or structure) of the database being migrated. + + *Kyle Thompson* + +* Reset the `ActiveRecord::Base` connection after `rails db:migrate:name`. + + When `rails db:migrate` has finished, it ensures the `ActiveRecord::Base` connection is reset to its original configuration. Going forward, `rails db:migrate:name` will have the same behavior. + + *Kyle Thompson* + +* Disallow calling `connected_to` on subclasses of `ActiveRecord::Base`. + + Behavior has not changed here but the previous API could be misleading to people who thought it would switch connections for only that class. `connected_to` switches the context from which we are getting connections, not the connections themselves. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add support for horizontal sharding to `connects_to` and `connected_to`. + + Applications can now connect to multiple shards and switch between their shards in an application. Note that the shard swapping is still a manual process as this change does not include an API for automatic shard swapping. + + Usage: + + Given the following configuration: + + ```yaml + # config/database.yml + production: + primary: + database: my_database + primary_shard_one: + database: my_database_shard_one + ``` + + Connect to multiple shards: + + ```ruby + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + + connects_to shards: { + default: { writing: :primary }, + shard_one: { writing: :primary_shard_one } + } + ``` + + Swap between shards in your controller / model code: + + ```ruby + ActiveRecord::Base.connected_to(shard: :shard_one) do + # Read from shard one + end + ``` + + The horizontal sharding API also supports read replicas. See guides for more details. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Deprecate `spec_name` in favor of `name` on database configurations. + + The accessors for `spec_name` on `configs_for` and `DatabaseConfig` are deprecated. Please use `name` instead. + + Deprecated behavior: + + ```ruby + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary") + db_config.spec_name + ``` + + New behavior: + + ```ruby + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", name: "primary") + db_config.name + ``` + + *Eileen M. Uchitelle* + +* Add additional database-specific rake tasks for multi-database users. + + Previously, `rails db:create`, `rails db:drop`, and `rails db:migrate` were the only rails tasks that could operate on a single + database. For example: + + ``` + rails db:create + rails db:create:primary + rails db:create:animals + rails db:drop + rails db:drop:primary + rails db:drop:animals + rails db:migrate + rails db:migrate:primary + rails db:migrate:animals + ``` + + With these changes, `rails db:schema:dump`, `rails db:schema:load`, `rails db:structure:dump`, `rails db:structure:load` and + `rails db:test:prepare` can additionally operate on a single database. For example: + + ``` + rails db:schema:dump + rails db:schema:dump:primary + rails db:schema:dump:animals + rails db:schema:load + rails db:schema:load:primary + rails db:schema:load:animals + rails db:structure:dump + rails db:structure:dump:primary + rails db:structure:dump:animals + rails db:structure:load + rails db:structure:load:primary + rails db:structure:load:animals + rails db:test:prepare + rails db:test:prepare:primary + rails db:test:prepare:animals + ``` + + *Kyle Thompson* + +* Add support for `strict_loading` mode on association declarations. + + Raise an error if attempting to load a record from an association that has been marked as `strict_loading` unless it was explicitly eager loaded. + + Usage: + + ```ruby + class Developer < ApplicationRecord + has_many :projects, strict_loading: true + end + + dev = Developer.first + dev.projects.first + # => ActiveRecord::StrictLoadingViolationError: The projects association is marked as strict_loading and cannot be lazily loaded. + ``` + + *Kevin Deisz* + +* Add support for `strict_loading` mode to prevent lazy loading of records. + + Raise an error if a parent record is marked as `strict_loading` and attempts to lazily load its associations. This is useful for finding places you may want to preload an association and avoid additional queries. + + Usage: + + ```ruby + dev = Developer.strict_loading.first + dev.audit_logs.to_a + # => ActiveRecord::StrictLoadingViolationError: Developer is marked as strict_loading and AuditLog cannot be lazily loaded. + ``` + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Add support for PostgreSQL 11+ partitioned indexes when using `upsert_all`. + + *Sebastián Palma* + +* Adds support for `if_not_exists` to `add_column` and `if_exists` to `remove_column`. + + Applications can set their migrations to ignore exceptions raised when adding a column that already exists or when removing a column that does not exist. + + Example Usage: + + ```ruby + class AddColumnTitle < ActiveRecord::Migration[6.1] + def change + add_column :posts, :title, :string, if_not_exists: true + end + end + ``` + + ```ruby + class RemoveColumnTitle < ActiveRecord::Migration[6.1] + def change + remove_column :posts, :title, if_exists: true + end + end + ``` + + *Eileen M. Uchitelle* + +* Regexp-escape table name for MS SQL Server. + + Add `Regexp.escape` to one method in ActiveRecord, so that table names with regular expression characters in them work as expected. Since MS SQL Server uses "[" and "]" to quote table and column names, and those characters are regular expression characters, methods like `pluck` and `select` fail in certain cases when used with the MS SQL Server adapter. + + *Larry Reid* + +* Store advisory locks on their own named connection. + + Previously advisory locks were taken out against a connection when a migration started. This works fine in single database applications but doesn't work well when migrations need to open new connections which results in the lock getting dropped. + + In order to fix this we are storing the advisory lock on a new connection with the connection specification name `AdvisoryLockBase`. The caveat is that we need to maintain at least 2 connections to a database while migrations are running in order to do this. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow schema cache path to be defined in the database configuration file. + + For example: + + ```yaml + development: + adapter: postgresql + database: blog_development + pool: 5 + schema_cache_path: tmp/schema/main.yml + ``` + + *Katrina Owen* + +* Deprecate `#remove_connection` in favor of `#remove_connection_pool` when called on the handler. + + `#remove_connection` is deprecated in order to support returning a `DatabaseConfig` object instead of a `Hash`. Use `#remove_connection_pool`, `#remove_connection` will be removed in Rails 7.0. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Deprecate `#default_hash` and it's alias `#[]` on database configurations. + + Applications should use `configs_for`. `#default_hash` and `#[]` will be removed in Rails 7.0. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add scale support to `ActiveRecord::Validations::NumericalityValidator`. + + *Gannon McGibbon* + +* Find orphans by looking for missing relations through chaining `where.missing`: + + Before: + + ```ruby + Post.left_joins(:author).where(authors: { id: nil }) + ``` + + After: + + ```ruby + Post.where.missing(:author) + ``` + + *Tom Rossi* + +* Ensure `:reading` connections always raise if a write is attempted. + + Now Rails will raise an `ActiveRecord::ReadOnlyError` if any connection on the reading handler attempts to make a write. If your reading role needs to write you should name the role something other than `:reading`. + + *Eileen M. Uchitelle* + +* Deprecate `"primary"` as the `connection_specification_name` for `ActiveRecord::Base`. + + `"primary"` has been deprecated as the `connection_specification_name` for `ActiveRecord::Base` in favor of using `"ActiveRecord::Base"`. This change affects calls to `ActiveRecord::Base.connection_handler.retrieve_connection` and `ActiveRecord::Base.connection_handler.remove_connection`. If you're calling these methods with `"primary"`, please switch to `"ActiveRecord::Base"`. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Add `ActiveRecord::Validations::NumericalityValidator` with + support for casting floats using a database columns' precision value. + + *Gannon McGibbon* + +* Enforce fresh ETag header after a collection's contents change by adding + ActiveRecord::Relation#cache_key_with_version. This method will be used by + ActionController::ConditionalGet to ensure that when collection cache versioning + is enabled, requests using ConditionalGet don't return the same ETag header + after a collection is modified. + + Fixes #38078. + + *Aaron Lipman* + +* Skip test database when running `db:create` or `db:drop` in development + with `DATABASE_URL` set. + + *Brian Buchalter* + +* Don't allow mutations on the database configurations hash. + + Freeze the configurations hash to disallow directly changing it. If applications need to change the hash, for example to create databases for parallelization, they should use the `DatabaseConfig` object directly. + + Before: + + ```ruby + @db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: "primary") + @db_config.configuration_hash.merge!(idle_timeout: "0.02") + ``` + + After: + + ```ruby + @db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: "primary") + config = @db_config.configuration_hash.merge(idle_timeout: "0.02") + db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(@db_config.env_name, @db_config.spec_name, config) + ``` + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Remove `:connection_id` from the `sql.active_record` notification. + + *Aaron Patterson*, *Rafael Mendonça França* + +* The `:name` key will no longer be returned as part of `DatabaseConfig#configuration_hash`. Please use `DatabaseConfig#owner_name` instead. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* ActiveRecord's `belongs_to_required_by_default` flag can now be set per model. + + You can now opt-out/opt-in specific models from having their associations required + by default. + + This change is meant to ease the process of migrating all your models to have + their association required. + + *Edouard Chin* + +* The `connection_config` method has been deprecated, please use `connection_db_config` instead which will return a `DatabaseConfigurations::DatabaseConfig` instead of a `Hash`. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Retain explicit selections on the base model after applying `includes` and `joins`. + + Resolves #34889. + + *Patrick Rebsch* + +* The `database` kwarg is deprecated without replacement because it can't be used for sharding and creates an issue if it's used during a request. Applications that need to create new connections should use `connects_to` instead. + + *Eileen M. Uchitelle*, *John Crepezzi* + +* Allow attributes to be fetched from Arel node groupings. + + *Jeff Emminger*, *Gannon McGibbon* + +* A database URL can now contain a querystring value that contains an equal sign. This is needed to support passing PostgreSQL `options`. + + *Joshua Flanagan* + +* Calling methods like `establish_connection` with a `Hash` which is invalid (eg: no `adapter`) will now raise an error the same way as connections defined in `config/database.yml`. + + *John Crepezzi* + +* Specifying `implicit_order_column` now subsorts the records by primary key if available to ensure deterministic results. + + *Paweł Urbanek* + +* `where(attr => [])` now loads an empty result without making a query. + + *John Hawthorn* + +* Fixed the performance regression for `primary_keys` introduced MySQL 8.0. + + *Hiroyuki Ishii* + +* Add support for `belongs_to` to `has_many` inversing. + + *Gannon McGibbon* + +* Allow length configuration for `has_secure_token` method. The minimum length + is set at 24 characters. + + Before: + + ```ruby + has_secure_token :auth_token + ``` + + After: + + ```ruby + has_secure_token :default_token # 24 characters + has_secure_token :auth_token, length: 36 # 36 characters + has_secure_token :invalid_token, length: 12 # => ActiveRecord::SecureToken::MinimumLengthError + ``` + + *Bernardo de Araujo* + +* Deprecate `DatabaseConfigurations#to_h`. These connection hashes are still available via `ActiveRecord::Base.configurations.configs_for`. + + *Eileen Uchitelle*, *John Crepezzi* + +* Add `DatabaseConfig#configuration_hash` to return database configuration hashes with symbol keys, and use all symbol-key configuration hashes internally. Deprecate `DatabaseConfig#config` which returns a String-keyed `Hash` with the same values. + + *John Crepezzi*, *Eileen Uchitelle* + +* Allow column names to be passed to `remove_index` positionally along with other options. + + Passing other options can be necessary to make `remove_index` correctly reversible. + + Before: + + add_index :reports, :report_id # => works + add_index :reports, :report_id, unique: true # => works + remove_index :reports, :report_id # => works + remove_index :reports, :report_id, unique: true # => ArgumentError + + After: + + remove_index :reports, :report_id, unique: true # => works + + *Eugene Kenny* + +* Allow bulk `ALTER` statements to drop and recreate indexes with the same name. + + *Eugene Kenny* + +* `insert`, `insert_all`, `upsert`, and `upsert_all` now clear the query cache. + + *Eugene Kenny* + +* Call `while_preventing_writes` directly from `connected_to`. + + In some cases application authors want to use the database switching middleware and make explicit calls with `connected_to`. It's possible for an app to turn off writes and not turn them back on by the time we call `connected_to(role: :writing)`. + + This change allows apps to fix this by assuming if a role is writing we want to allow writes, except in the case it's explicitly turned off. + + *Eileen M. Uchitelle* + +* Improve detection of ActiveRecord::StatementTimeout with mysql2 adapter in the edge case when the query is terminated during filesort. + + *Kir Shatrov* + +* Stop trying to read yaml file fixtures when loading Active Record fixtures. + + *Gannon McGibbon* + +* Deprecate `.reorder(nil)` with `.first` / `.first!` taking non-deterministic result. + + To continue taking non-deterministic result, use `.take` / `.take!` instead. + + *Ryuta Kamizono* + +* Preserve user supplied joins order as much as possible. + + Fixes #36761, #34328, #24281, #12953. + + *Ryuta Kamizono* + +* Allow `matches_regex` and `does_not_match_regexp` on the MySQL Arel visitor. + + *James Pearson* + +* Allow specifying fixtures to be ignored by setting `ignore` in YAML file's '_fixture' section. + + *Tongfei Gao* + +* Make the DATABASE_URL env variable only affect the primary connection. Add new env variables for multiple databases. + + *John Crepezzi*, *Eileen Uchitelle* + +* Add a warning for enum elements with 'not_' prefix. + + class Foo + enum status: [:sent, :not_sent] + end + + *Edu Depetris* + +* Make currency symbols optional for money column type in PostgreSQL. + + *Joel Schneider* + +* Add support for beginless ranges, introduced in Ruby 2.7. + + *Josh Goodall* + +* Add `database_exists?` method to connection adapters to check if a database exists. + + *Guilherme Mansur* + +* Loading the schema for a model that has no `table_name` raises a `TableNotSpecified` error. + + *Guilherme Mansur*, *Eugene Kenny* + +* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute. + + Fixes #36022. + + *Ryuta Kamizono* + +* Make ActiveRecord `ConnectionPool.connections` method thread-safe. + + Fixes #36465. + + *Jeff Doering* + +* Add support for multiple databases to `rails db:abort_if_pending_migrations`. + + *Mark Lee* + +* Fix sqlite3 collation parsing when using decimal columns. + + *Martin R. Schuster* + +* Fix invalid schema when primary key column has a comment. + + Fixes #29966. + + *Guilherme Goettems Schneider* + +* Fix table comment also being applied to the primary key column. + + *Guilherme Goettems Schneider* + +* Allow generated `create_table` migrations to include or skip timestamps. + + *Michael Duchemin* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/MIT-LICENSE new file mode 100644 index 0000000000..508e65ed03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/MIT-LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2004-2022 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.0.0/gems/activerecord-6.1.7.2/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/README.rdoc new file mode 100644 index 0000000000..306982d17b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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://edgeguides.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 + + {Learn more}[link:classes/ActiveRecord/Base.html] + +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). + + +* Associations between objects defined by simple class methods. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomerate + end + + {Learn more}[link: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}[link: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}[link: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}[link: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}[link:classes/ActiveRecord/Base.html] + + +* Transactions. + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link: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}[link: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}[link:classes/ActiveRecord/Base.html] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html], + PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and + SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. + + +* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-doc.org/stdlib/libdoc/logger/rdoc/]. + + 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[6.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}[link: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.0.0/gems/activerecord-6.1.7.2/examples/performance.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/examples/performance.rb new file mode 100644 index 0000000000..024e503ec7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.connection.send(:log, "hello", "world") { } + end + + x.report "AR.execute(query)" do + ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/examples/simple.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/examples/simple.rb new file mode 100644 index 0000000000..280b786d73 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record.rb new file mode 100644 index 0000000000..9b41ba8f47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2022 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" +require "arel" +require "yaml" + +require "active_record/version" +require "active_model/attribute_set" +require "active_record/errors" + +module ActiveRecord + extend ActiveSupport::Autoload + + autoload :Base + autoload :Callbacks + autoload :Core + autoload :ConnectionHandling + autoload :CounterCache + autoload :DynamicMatchers + autoload :DelegatedType + autoload :Enum + autoload :InternalMetadata + autoload :Explain + autoload :Inheritance + autoload :Integration + autoload :Migration + autoload :Migrator, "active_record/migration" + autoload :ModelSchema + autoload :NestedAttributes + autoload :NoTouching + autoload :TouchLater + autoload :Persistence + autoload :QueryCache + autoload :Querying + autoload :ReadonlyAttributes + autoload :RecordInvalid, "active_record/validations" + autoload :Reflection + autoload :RuntimeRegistry + autoload :Sanitization + autoload :Schema + autoload :SchemaDumper + autoload :SchemaMigration + autoload :Scoping + autoload :Serialization + autoload :StatementCache + autoload :Store + autoload :SignedId + autoload :Suppressor + autoload :Timestamp + autoload :Transactions + autoload :Translation + autoload :Validations + autoload :SecureToken + autoload :DestroyAssociationAsyncJob + + eager_autoload do + autoload :ConnectionAdapters + + autoload :Aggregations + autoload :Associations + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :AutosaveAssociation + + autoload :LegacyYamlAdapter + + autoload :Relation + autoload :AssociationRelation + autoload :NullRelation + + autoload_under "relation" do + autoload :QueryMethods + autoload :FinderMethods + autoload :Calculations + autoload :PredicateBuilder + autoload :SpawnMethods + autoload :Batches + autoload :Delegation + end + + autoload :Result + autoload :TableMetadata + autoload :Type + end + + module Coders + autoload :YAMLColumn, "active_record/coders/yaml_column" + autoload :JSON, "active_record/coders/json" + end + + module AttributeMethods + extend ActiveSupport::Autoload + + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write + autoload :Serialization + 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 :Named + autoload :Default + end + end + + module Middleware + extend ActiveSupport::Autoload + + autoload :DatabaseSelector, "active_record/middleware/database_selector" + end + + module Tasks + extend ActiveSupport::Autoload + + autoload :DatabaseTasks + autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks" + autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks" + autoload :PostgreSQLDatabaseTasks, + "active_record/tasks/postgresql_database_tasks" + end + + autoload :TestDatabases, "active_record/test_databases" + autoload :TestFixtures, "active_record/fixtures" + + 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! + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/aggregations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/aggregations.rb new file mode 100644 index 0000000000..0379b8d6dc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/aggregations.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Aggregations::ClassMethods for documentation + module Aggregations + def initialize_dup(*) # :nodoc: + @aggregation_cache = {} + super + end + + def reload(*) # :nodoc: + clear_aggregation_cache + super + end + + private + def clear_aggregation_cache + @aggregation_cache.clear if persisted? + end + + def init_internals + @aggregation_cache = {} + super + end + + # 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: %w(balance amount) + # composed_of :address, mapping: [ %w(address_street street), %w(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: [ %w(network_address network), %w(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 an array where the first item is the name of the + # entity attribute and the second item 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. + # * :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: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) + # 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: %w(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 + 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.each_key.all? { |k| k.is_a?(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.freeze + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/association_relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/association_relation.rb new file mode 100644 index 0000000000..41571857b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/association_relation.rb @@ -0,0 +1,60 @@ +# 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 + 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 + + scoping { klass.#{method}(attributes, **kwargs) } + end + RUBY + end + + def build(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, &block) } + else + block = current_scope_restoring_block(&block) + scoping { _new(attributes, &block) } + end + end + alias new build + + 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) + yield record if block_given? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations.rb new file mode 100644 index 0000000000..b7545b6e20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations.rb @@ -0,0 +1,1972 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/conversions" + +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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.association_name + maybe_these = @error.record.class.reflections.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.association_name.to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.reflection && @error.associated_class + maybe_these = @error.associated_class.reflections.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.reflection && @error.owner_class + maybe_these = @error.owner_class.reflections.keys + maybe_these -= [@error.reflection.name.to_s] # remove failing reflection + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + 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 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 + + # 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 :AliasTracker + end + + def self.eager_load! + super + Preloader.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 + + def reload(*) # :nodoc: + clear_association_cache + super + end + + private + # Clears out the association cache. + def clear_association_cache + @association_cache.clear if persisted? + end + + def init_internals + @association_cache = {} + super + 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 + + # \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#portfolio, Project#portfolio=(portfolio), Project#reload_portfolio + # * Project#project_manager, Project#project_manager=(project_manager), Project#reload_project_manager + # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), + # Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id), + # Project#milestones.build, Project#milestones.create + # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), + # 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 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 + # + # === 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, eager loading and preloading of these associations is not possible. + # These operations happen before instance creation and the scope will be called with a +nil+ argument. + # + # == 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 Project + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity + # + # def evaluate_velocity(developer) + # ... + # end + # end + # + # It's possible to stack callbacks by passing them as an array. Example: + # + # class Project + # has_and_belongs_to_many :developers, + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # 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. + # + # == 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, + # or a custom scope, will also prevent the association's inverse + # from being found automatically. + # + # 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 fallback + # 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. + # + # == 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 + # + # A Firm class declares has_many :clients, which will add: + # * Firm#clients (similar to Client.where(firm_id: id)) + # * Firm#clients<< + # * Firm#clients.delete + # * Firm#clients.destroy + # * Firm#clients= + # * Firm#client_ids + # * Firm#client_ids= + # * Firm#clients.clear + # * Firm#clients.empty? (similar to firm.clients.size == 0) + # * Firm#clients.size (similar to Client.count "firm_id = #{id}") + # * Firm#clients.find (similar to Client.where(firm_id: id).find(id)) + # * Firm#clients.exists?(name: 'ACME') (similar to Client.exists?(name: 'ACME', firm_id: firm.id)) + # * Firm#clients.build (similar to Client.new(firm_id: id)) + # * Firm#clients.create (similar to c = Client.new(firm_id: id); c.save; c) + # * Firm#clients.create! (similar to c = Client.new(firm_id: id); c.save!) + # * 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. + # + # 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. + # [: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 the 'Association Join Models' + # section above.) + # [: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 ActiveRecord::Associations::ClassMethods's overview on 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] + # 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. + # + # 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 :comments, strict_loading: true + 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 also ActiveRecord::Associations::ClassMethods's overview + # 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. + # + # === Example + # + # An Account class declares has_one :beneficiary, which will add: + # * Account#beneficiary (similar to Beneficiary.where(account_id: id).first) + # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) + # * Account#build_beneficiary (similar to Beneficiary.new(account_id: id)) + # * Account#create_beneficiary (similar to b = Beneficiary.new(account_id: id); b.save; b) + # * Account#create_beneficiary! (similar to b = Beneficiary.new(account_id: id); b.save!; b) + # * Account#reload_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. + # + # 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. + # [: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 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. + # [: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 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. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. + # See ActiveRecord::Associations::ClassMethods's overview on 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. + # + # 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 :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + # has_one :credit_card, strict_loading: true + 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 also ActiveRecord::Associations::ClassMethods's overview + # 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. + # + # === Example + # + # A Post class declares belongs_to :author, which will add: + # * Post#author (similar to Author.find(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 + # 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 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 + # + # [: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". + # + # 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. + # [: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, see note below). 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.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. + # [:polymorphic] + # Specify this association is a polymorphic association by passing +true+. + # 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). + # [: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/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/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are 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 ActiveRecord::Associations::ClassMethods's overview on 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. + # [: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. + # + # 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 + 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[6.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 + # + # A Developer class declares has_and_belongs_to_many :projects, which will add: + # * Developer#projects + # * Developer#projects<< + # * Developer#projects.delete + # * Developer#projects.destroy + # * Developer#projects= + # * Developer#project_ids + # * Developer#project_ids= + # * Developer#projects.clear + # * Developer#projects.empty? + # * Developer#projects.size + # * Developer#projects.find(id) + # * Developer#projects.exists?(...) + # * Developer#projects.build (similar to Project.new(developer_id: id)) + # * Developer#projects.create (similar to c = Project.new(developer_id: id); c.save; c) + # * 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. + # + # 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. + # [: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.to_s].parent_reflection = habtm_reflection + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/alias_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/alias_tracker.rb new file mode 100644 index 0000000000..27cb2f62f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/alias_tracker.rb @@ -0,0 +1,84 @@ +# 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(connection, initial_table, joins, aliases = nil) + 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, aliases) + end + + def self.initial_count_for(connection, name, table_joins) + quoted_name = nil + + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name ||= connection.quote_table_name(name) + + # Table names + table aliases + join.left.scan( + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\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(connection, aliases) + @aliases = aliases + @connection = connection + 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 = @connection.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 truncate(name) + name.slice(0, @connection.table_alias_length - 2) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association.rb new file mode 100644 index 0000000000..0b731e5d29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association.rb @@ -0,0 +1,358 @@ +# 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_reader :owner, :target, :reflection + + delegate :options, to: :reflection + + def initialize(owner, reflection) + reflection.check_validity! + + @owner, @reflection = owner, reflection + + reset + reset_scope + end + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + @loaded = false + @target = nil + @stale_state = nil + @inversed = false + 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.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 + @inversed = false + 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? + !@inversed && 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 (scope = klass.current_scope) && scope.try(:proxy_association) == self + scope.spawn + else + target_scope.merge!(association_scope) + end + end + + def reset_scope + @association_scope = nil + 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 + @inversed = !!record + end + + def inversed_from_queries(record) + if inversable?(record) + self.target = record + @inversed = true + else + @inversed = false + 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 if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + target + rescue ActiveRecord::RecordNotFound + reset + 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 + + private + def find_target + if strict_loading? && owner.validation_context.nil? + Base.strict_loading_violation!(owner: owner.class, reflection: reflection) + end + + scope = self.scope + return scope.to_a if skip_statement_cache?(scope) + + 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) + sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) } + end + + def strict_loading? + return reflection.strict_loading? if reflection.options.key?(:strict_loading) + + owner.strict_loading? + 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 ||= AssociationScope.scope(self) + 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 sanity 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) + record._has_attribute?(reflection.foreign_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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association_scope.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association_scope.rb new file mode 100644 index 0000000000..c5e394d7af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/association_scope.rb @@ -0,0 +1,170 @@ +# 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 << 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 = reflection.join_primary_key + foreign_key = reflection.join_foreign_key + + table = reflection.aliased_table + value = transform_value(owner[foreign_key]) + scope = apply_scope(scope, table, primary_key, value) + + 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 = reflection.join_primary_key + foreign_key = reflection.join_foreign_key + + table = reflection.aliased_table + foreign_table = next_reflection.aliased_table + constraint = table[primary_key].eq(foreign_table[foreign_key]) + + 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, constraint)) + 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| + # Exclude the scope of the association itself, because that + # was already merged in the #scope method. + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_association.rb new file mode 100644 index 0000000000..2ed6a700b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_association.rb @@ -0,0 +1,137 @@ +# 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 + id = owner.public_send(reflection.foreign_key.to_sym) + primary_key_column = reflection.active_record_primary_key.to_sym + + 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) + ) + 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_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize + 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.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 + 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) + target_key = record ? record._read_attribute(primary_key(record.class)) : nil + + if force || owner[reflection.foreign_key] != target_key + owner[reflection.foreign_key] = target_key + end + end + + def primary_key(klass) + reflection.association_primary_key(klass) + end + + def foreign_key_present? + owner._read_attribute(reflection.foreign_key) + end + + def invertible_for?(record) + inverse = inverse_reflection_for(record) + inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing) + end + + def stale_state + result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) } + result && result.to_s + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_polymorphic_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_polymorphic_association.rb new file mode 100644 index 0000000000..a0d5522fa0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -0,0 +1,41 @@ +# 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.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[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 + foreign_key = super + foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/association.rb new file mode 100644 index 0000000000..b7f45ed5ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/association.rb @@ -0,0 +1,163 @@ +# 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 + ].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 + 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) + 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.valid_dependent_options + raise NotImplementedError + end + + def self.check_dependent_options(dependent, model) + if dependent == :destroy_async && !model.destroy_association_async_job + err_message = "ActiveJob is required to use destroy_async on associations" + raise ActiveRecord::ActiveJobRequiredError, 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, + :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/belongs_to.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/belongs_to.rb new file mode 100644 index 0000000000..584af2c3f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/belongs_to.rb @@ -0,0 +1,133 @@ +# 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.target_changed? + association.increment_counters + association.decrement_counters_before_last_save + end + } + + klass = reflection.class_name.safe_constantize + klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) + end + + def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :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 = klass.constantize + 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.public_send(touch_method, touch) + else + old_record.public_send(touch_method) + end + end + end + + record = o.public_send name + if record && record.persisted? + if touch != true + record.public_send(touch_method, touch) + else + record.public_send(touch_method) + 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, belongs_to_touch_method) + }} + + if reflection.counter_cache_column + touch_callback = callback.(:saved_changes) + update_callback = lambda { |record| + instance_exec(record, &touch_callback) unless association(reflection.name).target_changed? + } + 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 + model.validates_presence_of reflection.name, message: :required + end + end + + private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations, + :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/collection_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/collection_association.rb new file mode 100644 index 0000000000..0c0613d95f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/collection_association.rb @@ -0,0 +1,73 @@ +# 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}" + + unless model.method_defined?(full_callback_name) + model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false) + end + + callbacks = Array(options[callback_name.to_sym]).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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb new file mode 100644 index 0000000000..170bdd7907 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -0,0 +1,111 @@ +# 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 + + 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.retrieve_connection + left_model.retrieve_connection + end + + private + def self.suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_many.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_many.rb new file mode 100644 index 0000000000..b21dd943aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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, :ensuring_owner_was] + valid += [:as, :foreign_type] if options[:as] + valid += [:through, :source, :source_type] 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_one.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/has_one.rb new file mode 100644 index 0000000000..1773faa01b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + valid += [:as, :foreign_type] if options[:as] + valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async + valid += [:through, :source, :source_type] 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/singular_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/singular_association.rb new file mode 100644 index 0000000000..7537aa468f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/builder/singular_association.rb @@ -0,0 +1,44 @@ +# 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) if reflection.constructable? + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def reload_#{name} + association(:#{name}).force_reload_reader + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_association.rb new file mode 100644 index 0000000000..3e087695ed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_association.rb @@ -0,0 +1,523 @@ +# frozen_string_literal: true + +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: + # Implements the reader method, e.g. foo.items for Foo.has_many :items + def reader + 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! { |i| pk_type.cast(i) } + + records = klass.where(primary_key => ids).index_by do |r| + r.public_send(primary_key) + end.values_at(*ids).compact + + if records.size != ids.size + found_ids = records.map { |record| record.public_send(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 + @association_ids = nil + end + + def find(*args) + if options[:inverse_of] && loaded? + args_flatten = args.flatten + model = scope.klass + + 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? + load_target + concat_records(records) + else + transaction { concat_records(records) } + end + end + + # Starts a transaction in the association class's database connection. + # + # class Author < ActiveRecord::Base + # has_many :books + # end + # + # Author.first.books.transaction do + # # same effect as calling Book.transaction + # end + def transaction(*args) + reflection.klass.transaction(*args) do + yield + 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_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 = 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) + if record.is_a?(reflection.klass) + if record.new_record? + include_in_memory?(record) + else + loaded? ? target.include?(record) : scope.exists?(record.id) + end + else + false + 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 ActiveRecord::Base.has_many_inversing + + case record + 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? || + reflection.strict_loading? || + owner.new_record? || + target.any? { |record| record.new_record? || record.changed? } + end + + private + # 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? + return memory if persisted.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).each do |name| + mem_record[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, "You cannot call create unless the parent is saved" + 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}" + owner.class.send(full_callback_name) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_proxy.rb new file mode 100644 index 0000000000..323a95d8cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/collection_proxy.rb @@ -0,0 +1,1135 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # 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 + # 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 + # + # *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::Base.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 #first except returns only the third-to-last record. + + ## + # :method: second_to_last + # + # :call-seq: + # second_to_last() + # + # Same as #first 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::Base.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.length.zero?. + # + # 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 + # + # 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 + + def proxy_association # :nodoc: + @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 + # + # other = [Pet.new(id: 1), Pet.new(id: 2)] + # + # person.pets == other + # # => false + 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 + + 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 + ] + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/foreign_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/foreign_association.rb new file mode 100644 index 0000000000..6c9d28cfed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/foreign_association.rb @@ -0,0 +1,33 @@ +# 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| + attrs[reflection.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] + + key = owner._read_attribute(reflection.join_foreign_key) + record._write_attribute(reflection.join_primary_key, key) + + if reflection.type + record._write_attribute(reflection.type, owner.class.polymorphic_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_association.rb new file mode 100644 index 0000000000..2553fd0ef1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_association.rb @@ -0,0 +1,157 @@ +# 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 + primary_key_column = association_class.primary_key.to_sym + + ids = target.collect do |assoc| + assoc.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: ids, + association_primary_key_column: primary_key_column, + ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil) + ) + 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_cached_counter? + owner.read_attribute(reflection.counter_cache_column).to_i + else + scope.count(:all) + end + + # If there's nothing in the database and @target has no new records + # we are certain the current target is an empty array. This is a + # documented side-effect of the method that may avoid an extra SELECT. + loaded! if count == 0 + + [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 + scope = self.scope.where(reflection.klass.primary_key => records) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_through_association.rb new file mode 100644 index 0000000000..0b9fcb00d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_many_through_association.rb @@ -0,0 +1,226 @@ +# 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 + attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type] + + through_association.build(attributes) + end + end + + attr_reader :through_scope + + def through_scope_attributes + scope = through_scope || self.scope + scope.where_values_hash(through_association.reflection.name.to_s). + except!(through_association.reflection.foreign_key, + through_association.reflection.klass.inheritance_column) + 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 = source_reflection.inverse_of + 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? && owner[through_reflection.foreign_key].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.klass.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 + return [] unless target_reflection_has_associated_record? + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_one_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_one_association.rb new file mode 100644 index 0000000000..d25f0fa55a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_one_association.rb @@ -0,0 +1,132 @@ +# 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 + primary_key_column = target.class.primary_key.to_sym + id = target.public_send(primary_key_column) + + 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, "Failed to save the new associated #{reflection.name}." + 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, "Failed to remove the existing associated #{reflection.name}. " \ + "The record failed to save after its foreign key was set to nil." + end + end + end + + def nullify_owner_attributes(record) + record[reflection.foreign_key] = nil + end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end + + def _create_record(attributes, raise_error = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_one_through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/has_one_through_association.rb new file mode 100644 index 0000000000..10978b2d93 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency.rb new file mode 100644 index 0000000000..325a803c98 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency.rb @@ -0,0 +1,293 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + autoload :JoinBase, "active_record/associations/join_dependency/join_base" + autoload :JoinAssociation, "active_record/associations/join_dependency/join_association" + + 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 + 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| + table, terminated = @joined_tables[reflection] + root = reflection == child.reflection + + if table && (!root || !terminated) + @joined_tables[reflection] = [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[reflection] ||= [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 + + key = aliases.column_alias(node, node.primary_key) + id = row[key] + if id.nil? + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + model = seen[ar_parent][node][id] + + if model + construct(model, node, row, seen, model_cache, strict_loading_value) + else + model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value) + + seen[ar_parent][node][id] = model + construct(model, node, row, seen, model_cache, strict_loading_value) + end + end + end + + def construct_model(record, node, row, model_cache, id, strict_loading_value) + other = record.association(node.reflection.name) + + model = model_cache[node][id] ||= + node.instantiate(row, aliases.column_aliases(node)) do |m| + m.strict_loading! if strict_loading_value + other.set_inverse_instance(m) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_association.rb new file mode 100644 index 0000000000..88f21b80ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_association.rb @@ -0,0 +1,103 @@ +# 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.each do |reflection| + table, terminated = yield reflection + @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 = Arel.sql(base_klass.connection.visitor.compile(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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_base.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_base.rb new file mode 100644 index 0000000000..988b4e8fa2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_part.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/join_dependency/join_part.rb new file mode 100644 index 0000000000..3ffa079c61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader.rb new file mode 100644 index 0000000000..de7288983a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Associations + # 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 :ThroughAssociation, "active_record/associations/preloader/through_association" + end + + # 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 his + # 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 +:include+ option for + # ActiveRecord::Base.find. So +associations+ could look like this: + # + # :books + # [ :books, :author ] + # { author: :avatar } + # [ :books, { author: :avatar } ] + def preload(records, associations, preload_scope = nil) + records = Array.wrap(records).compact + + if records.empty? + [] + else + Array.wrap(associations).flat_map { |association| + preloaders_on association, records, preload_scope + } + end + end + + def initialize(associate_by_default: true) + @associate_by_default = associate_by_default + end + + private + # Loads all the given data into +records+ for the +association+. + def preloaders_on(association, records, scope, polymorphic_parent = false) + case association + when Hash + preloaders_for_hash(association, records, scope, polymorphic_parent) + when Symbol, String + preloaders_for_one(association, records, scope, polymorphic_parent) + else + raise ArgumentError, "#{association.inspect} was not recognized for preload" + end + end + + def preloaders_for_hash(association, records, scope, polymorphic_parent) + association.flat_map { |parent, child| + grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records| + loaders = preloaders_for_reflection(reflection, reflection_records, scope) + recs = loaders.flat_map(&:preloaded_records).uniq + child_polymorphic_parent = reflection && reflection.options[:polymorphic] + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope, child_polymorphic_parent + } + loaders + end + } + end + + # Loads all the given data into +records+ for a singular +association+. + # + # Functions by instantiating a preloader class such as Preloader::Association and + # call the +run+ method for each passed in class in the +records+ argument. + # + # Not all records have the same class, so group then preload group on the reflection + # itself so that if various subclass share the same association then we do not split + # them unnecessarily + # + # Additionally, polymorphic belongs_to associations can have multiple associated + # classes, depending on the polymorphic_type field. So we group by the classes as + # well. + def preloaders_for_one(association, records, scope, polymorphic_parent) + grouped_records(association, records, polymorphic_parent) + .flat_map do |reflection, reflection_records| + preloaders_for_reflection reflection, reflection_records, scope + end + end + + def preloaders_for_reflection(reflection, records, scope) + records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs| + preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run + end + end + + def grouped_records(association, records, polymorphic_parent) + h = {} + 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 + + class AlreadyLoaded # :nodoc: + def initialize(klass, owners, reflection, preload_scope, associate_by_default = true) + @owners = owners + @reflection = reflection + end + + def run + self + end + + def preloaded_records + @preloaded_records ||= records_by_owner.flat_map(&:last) + end + + def records_by_owner + @records_by_owner ||= owners.index_with do |owner| + Array(owner.association(reflection.name).target) + end + end + + private + attr_reader :owners, :reflection + 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, owners) + if owners.all? { |o| o.association(reflection.name).loaded? } + return AlreadyLoaded + end + reflection.check_preloadable! + + if reflection.options[:through] + ThroughAssociation + else + Association + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/association.rb new file mode 100644 index 0000000000..f3bbd1c532 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/association.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class Association #:nodoc: + def initialize(klass, owners, reflection, preload_scope, associate_by_default = true) + @klass = klass + @owners = owners.uniq(&:__id__) + @reflection = reflection + @preload_scope = preload_scope + @associate = associate_by_default || !preload_scope || preload_scope.empty_scope? + @model = owners.first && owners.first.class + end + + def run + 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 + + private + attr_reader :owners, :reflection, :preload_scope, :model, :klass + + def load_records + # 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 = owner_keys.empty? ? [] : records_for(owner_keys) + + @preloaded_records = raw_records.select do |record| + assignments = false + + owners_by_key[convert_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 + + # The name of the key on the associated records + def association_key_name + reflection.join_primary_key(klass) + end + + # 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) + association = owner.association(reflection.name) + if reflection.collection? + association.target = records + else + association.target = records.first + end + end + + def owner_keys + @owner_keys ||= owners_by_key.keys + end + + def owners_by_key + @owners_by_key ||= owners.each_with_object({}) do |owner, result| + key = convert_key(owner[owner_key_name]) + (result[key] ||= []) << owner if key + 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 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 records_for(ids) + scope.where(association_key_name => ids).load do |record| + # Processing only the first owner + # because the record is modified but not an owner + owner = owners_by_key[convert_key(record[association_key_name])].first + association = owner.association(reflection.name) + association.set_inverse_instance(record) + end + end + + def scope + @scope ||= build_scope + 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 + + if preload_scope && preload_scope.strict_loading_value + scope.strict_loading + else + scope + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/through_association.rb new file mode 100644 index 0000000000..b39a235a19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/preloader/through_association.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class ThroughAssociation < Association # :nodoc: + PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false) + + def initialize(*) + super + @already_loaded = owners.first.association(through_reflection.name).loaded? + end + + def preloaded_records + @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records) + end + + def records_by_owner + return @records_by_owner if defined?(@records_by_owner) + source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge) + through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge) + + @records_by_owner = owners.each_with_object({}) do |owner, result| + through_records = through_records_by_owner[owner] || [] + + if @already_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 + + private + def source_preloaders + @source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope) + end + + def middle_records + through_preloaders.flat_map(&:preloaded_records) + end + + def through_preloaders + @through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope) + end + + def through_reflection + reflection.through_reflection + end + + def source_reflection + reflection.source_reflection + 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 + + 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 + + scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/singular_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/singular_association.rb new file mode 100644 index 0000000000..b7e5987c4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/singular_association.rb @@ -0,0 +1,59 @@ +# 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 + if !loaded? || stale_target? + reload + end + + target + 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!(klass.primary_key) + end + + def find_target + super.first + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/through_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/through_association.rb new file mode 100644 index 0000000000..3f5e9066eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/associations/through_association.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Through Association + module ThroughAssociation #:nodoc: + delegate :source_reflection, to: :reflection + + private + 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 association_primary_key == reflection.klass.primary_key && !options[:source_type] + join_attributes = { source_reflection.name => records } + else + join_attributes = { + source_reflection.foreign_key => records.map(&association_primary_key.to_sym) + } + 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 crazy to try to + # properly support stale-checking for nested associations. + def stale_state + if through_reflection.belongs_to? + owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s + end + end + + def foreign_key_present? + through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil? + 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) + inverse = source_reflection.inverse_of + target = through_association.target + + if inverse && target && !target.is_a?(Array) + attributes[inverse.foreign_key] = target.id + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_assignment.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_assignment.rb new file mode 100644 index 0000000000..553484883c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_assignment.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require "active_model/forbidden_attributes_protection" + +module ActiveRecord + module AttributeAssignment + include ActiveModel::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?(&:nil?) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods.rb new file mode 100644 index 0000000000..3b58472184 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods.rb @@ -0,0 +1,430 @@ +# frozen_string_literal: true + +require "mutex_m" +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 parent superclass) + + class GeneratedAttributeMethods < Module #:nodoc: + include Mutex_m + 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 + ).map { |m| -m.to_s }.to_set.freeze + end + end + + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules # :nodoc: + @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new) + private_constant :GeneratedAttributeMethods + @attribute_methods_generated = false + include @generated_attribute_methods + + super + 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. + generated_attribute_methods.synchronize do + return false if @attribute_methods_generated + superclass.define_attribute_methods unless base_class? + super(attribute_names) + @attribute_methods_generated = true + end + end + + def undefine_attribute_methods # :nodoc: + generated_attribute_methods.synchronize do + super if defined?(@attribute_methods_generated) && @attribute_methods_generated + @attribute_methods_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 overwrite 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 + 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. + # We check defined?(@attributes) not to issue warnings if called on objects that + # have been allocated but not yet initialized. + if defined?(@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, Date and Time attributes are returned in the + # :db format. 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\"" + # + # 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 typecast (for example, + # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises + # ActiveModel::MissingAttributeError if the identified attribute is missing. + # + # Note: +:id+ is always present. + # + # class Person < ActiveRecord::Base + # belongs_to :organization + # end + # + # person = Person.new(name: 'Francesco', age: '22') + # person[:name] # => "Francesco" + # person[:age] # => 22 + # + # person = Person.select('id').first + # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name + # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id + def [](attr_name) + read_attribute(attr_name) { |n| missing_attribute(n, caller) } + end + + # Updates the attribute identified by attr_name with the specified +value+. + # (Alias for the protected #write_attribute method). + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person[:age] = '22' + # person[:age] # => 22 + # person[:age].class # => Integer + 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 attribute_method?(attr_name) + # We check defined? because Syck calls respond_to? before actually calling initialize. + defined?(@attributes) && @attributes.key?(attr_name) + end + + def attributes_with_values(attribute_names) + attribute_names.index_with do |name| + _read_attribute(name) + end + end + + # Filters the primary keys and readonly attributes 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) + end + end + + # Filters out the 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? + 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_s(: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.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/before_type_cast.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/before_type_cast.rb new file mode 100644 index 0000000000..33ca3d38b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/before_type_cast.rb @@ -0,0 +1,84 @@ +# 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" + attribute_method_suffix "_came_from_user?" + 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 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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/dirty.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/dirty.rb new file mode 100644 index 0000000000..bf5cc82f7a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/dirty.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActiveRecord + module AttributeMethods + 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_writes, instance_writer: false, default: true + + # Attribute methods for "changed in last call to save?" + attribute_method_affix(prefix: "saved_change_to_", suffix: "?") + attribute_method_prefix("saved_change_to_") + attribute_method_suffix("_before_last_save") + + # Attribute methods for "will change if I call save?" + attribute_method_affix(prefix: "will_save_change_to_", suffix: "?") + attribute_method_suffix("_change_to_be_saved", "_in_database") + 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 passed, this method will return false unless the original + # value is equal to the given option + # + # +to+ When passed, this method will return false unless the value was + # 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 passed, this method will return false unless the original + # value is equal to the given option + # + # +to+ When passed, 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 write_attribute_without_type_cast(attr_name, value) + result = super + clear_attribute_change(attr_name) + result + 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_writes) + affected_rows = super + changes_applied + affected_rows + end + + def _create_record(attribute_names = attribute_names_for_partial_writes) + id = super + changes_applied + id + end + + def attribute_names_for_partial_writes + partial_writes? ? changed_attribute_names_to_save : attribute_names + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/primary_key.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/primary_key.rb new file mode 100644 index 0000000000..977409ff4c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/primary_key.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "set" + +module ActiveRecord + module AttributeMethods + 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 + [key] if key + end + + # Returns the primary key column's value. + def id + _read_attribute(@primary_key) + end + + # Sets the primary key column's value. + def id=(value) + _write_attribute(@primary_key, value) + end + + # Queries the primary key column's value. + def id? + query_attribute(@primary_key) + end + + # Returns the primary key column's value before type cast. + def id_before_type_cast + attribute_before_type_cast(@primary_key) + end + + # Returns the primary key column's previous value. + def id_was + attribute_was(@primary_key) + end + + # Returns the primary key column's value from the 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 + + 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 + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key + end + + # Returns a quoted version of the primary key name, used to construct + # SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.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 + else + if ActiveRecord::Base != self && table_exists? + pk = connection.schema_cache.primary_keys(table_name) + suppress_composite_primary_key(pk) + else + "id" + end + 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 = value && -value.to_s + @quoted_primary_key = nil + @attributes_builder = nil + end + + private + def suppress_composite_primary_key(pk) + return pk unless pk.is_a?(Array) + + warn <<~WARNING + WARNING: Active Record does not support composite primary key. + + #{table_name} has composite primary key. Composite primary key is ignored. + WARNING + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/query.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/query.rb new file mode 100644 index 0000000000..d17e8d8513 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/query.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Query + extend ActiveSupport::Concern + + included do + attribute_method_suffix "?" + end + + def query_attribute(attr_name) + value = self[attr_name] + + 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 + + alias :attribute? :query_attribute + private :attribute? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/read.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/read.rb new file mode 100644 index 0000000000..12cbfc867f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/read.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Read + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + private + def define_method_attribute(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}" << + " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" << + "end" + end + end + end + + # Returns the value of the attribute identified by attr_name after + # it has been typecast (for example, "2004-12-12" in a date column is cast + # to a date object, like Date.new(2004, 12, 12)). + def read_attribute(attr_name, &block) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + name = @primary_key if name == "id" && @primary_key + @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.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/serialization.rb new file mode 100644 index 0000000000..624a70b425 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/serialization.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + 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 + + module ClassMethods + # If you have an attribute that needs to be saved to the database as an + # object, and retrieved as the same object, then specify the name of that + # attribute using this method and it will be handled automatically. The + # serialization is done through YAML. If +class_name+ is specified, the + # serialized object must be of that class on assignment and retrieval. + # Otherwise SerializationTypeMismatch will be raised. + # + # Empty objects as {}, in the case of +Hash+, or [], in the case of + # +Array+, will always be persisted as null. + # + # 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 field name that should be serialized. + # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+ + # or a class name that the object type should be equal to. + # + # ==== 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+. + # + # ==== Example + # + # # Serialize a preferences attribute. + # class User < ActiveRecord::Base + # serialize :preferences + # end + # + # # Serialize preferences using JSON as coder. + # class User < ActiveRecord::Base + # serialize :preferences, JSON + # end + # + # # Serialize preferences as Hash using YAML coder. + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + def serialize(attr_name, class_name_or_coder = Object, **options) + # 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 = if class_name_or_coder == ::JSON + Coders::JSON + elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder + else + Coders::YAMLColumn.new(attr_name, class_name_or_coder) + end + + decorate_attribute_type(attr_name.to_s, **options) do |cast_type| + if type_incompatible_with_serialize?(cast_type, class_name_or_coder) + raise ColumnNotSerializableError.new(attr_name, cast_type) + end + + Type::Serialized.new(cast_type, coder) + end + end + + private + def type_incompatible_with_serialize?(type, class_name) + type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON || + type.respond_to?(:type_cast_array, true) && class_name == ::Array + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/time_zone_conversion.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/time_zone_conversion.rb new file mode 100644 index 0000000000..f5eb10c079 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -0,0 +1,88 @@ +# 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 + else + map_avoiding_infinite_recursion(super) { |v| cast(v) } + end + end + + private + def convert_time_to_time_zone(value) + return if value.nil? + + if value.acts_like?(:time) + value.in_time_zone + elsif value.is_a?(::Float) + value + else + map_avoiding_infinite_recursion(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 + + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end + end + end + end + + extend ActiveSupport::Concern + + included do + mattr_accessor :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: + def define_attribute(name, cast_type, **) + if create_time_zone_conversion_attribute?(name, cast_type) + cast_type = TimeZoneConverter.new(cast_type) + end + super + end + + private + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/write.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/write.rb new file mode 100644 index 0000000000..b0eb37c080 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attribute_methods/write.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Write + extend ActiveSupport::Concern + + included do + attribute_method_suffix "=" + end + + module ClassMethods # :nodoc: + private + def define_method_attribute=(name, owner:) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + end + + # Updates the attribute identified by attr_name with the + # specified +value+. Empty strings for Integer and Float columns are + # turned into +nil+. + 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= + + private + def write_attribute_without_type_cast(attr_name, value) + @attributes.write_cast_value(attr_name, value) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attributes.rb new file mode 100644 index 0000000000..5f1514d878 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/attributes.rb @@ -0,0 +1,303 @@ +# frozen_string_literal: true + +require "active_model/attribute/user_provided_default" + +module ActiveRecord + # See ActiveRecord::Attributes::ClassMethods for documentation + module Attributes + extend ActiveSupport::Concern + + included do + class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal: + end + + module ClassMethods + ## + # :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. 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 previous default value (if any) 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 MoneyType < 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(:money, MoneyType) + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :money + # 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 MoneyType < 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(:money, MoneyType) + # + # # app/models/product.rb + # class Product < ActiveRecord::Base + # currency_converter = ConversionRatesFromTheInternet.new + # attribute :price_in_bitcoins, :money, 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. + def attribute(name, cast_type = nil, **options, &block) + name = name.to_s + reload_schema_from_cache + + self.attributes_to_define_after_schema_loads = + attributes_to_define_after_schema_loads.merge( + name => [cast_type || block, options] + ) + end + + # This is the low level API which sits beneath +attribute+. It only + # accepts type objects, and will do its work immediately instead of + # waiting for the schema to load. Automatic schema detection and + # ClassMethods#attribute both call this under the hood. 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 load_schema! # :nodoc: + super + attributes_to_define_after_schema_loads.each do |name, (type, options)| + define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default)) + end + 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 decorate_attribute_type(attr_name, **default) + type, options = attributes_to_define_after_schema_loads[attr_name] + + default.with_defaults!(default: options[:default]) if options&.key?(:default) + + attribute(attr_name, **default) do |cast_type| + if type && !type.is_a?(Proc) + cast_type = _lookup_cast_type(attr_name, type, options) + end + + yield cast_type + end + end + + def _lookup_cast_type(name, type, options) + case type + when Symbol + adapter_name = ActiveRecord::Type.adapter_name_from(self) + ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name) + when Proc + type[type_for_attribute(name)] + else + type || type_for_attribute(name) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/autosave_association.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/autosave_association.rb new file mode 100644 index 0000000000..7b550dae5f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/autosave_association.rb @@ -0,0 +1,527 @@ +# frozen_string_literal: true + +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 + mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false + end + + module ClassMethods # :nodoc: + private + if Module.method(:method_defined?).arity == 1 # MRI 2.5 and older + using Module.new { + refine Module do + def method_defined?(method, inherit = true) + if inherit + super(method) + else + instance_methods(false).include?(method.to_sym) + end + end + end + } + end + + 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_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) + # 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 + else + method = :validate_single_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 + + private + # 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 association. + def validate_single_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.reader + association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?) + 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_with_index { |record, index| association_valid?(reflection, record, index) } + 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?(reflection, record, index = nil) + return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) + + context = validation_context if custom_validation_context? + + unless valid = record.valid?(context) + if reflection.options[:autosave] + indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) + + record.errors.group_by_attribute.each { |attribute, errors| + attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + + errors.each { |error| + self.errors.import( + error, + attribute: attribute + ) + } + } + else + errors.add(reflection.name) + end + end + valid + end + + def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + if indexed_attribute + "#{reflection.name}[#{index}].#{attribute}" + else + "#{reflection.name}.#{attribute}" + end + 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?) + 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) + record = association && association.load_target + + if record && !record.destroyed? + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + record.destroy + elsif autosave != false + key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id + + if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key) + unless reflection.through_reflection + record[reflection.foreign_key] = key + if inverse_reflection = reflection.inverse_of + record.association(inverse_reflection.name).inversed_from(self) + end + end + + saved = record.save(validate: !autosave) + raise ActiveRecord::Rollback if !saved && autosave + saved + end + end + 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) || + record.will_save_change_to_attribute?(reflection.foreign_key) + end + + def association_foreign_key_changed?(reflection, record, key) + return false if reflection.through_reflection? + + record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key + 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? + self[reflection.foreign_key] = nil + record.destroy + elsif autosave != false + saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) + + if association.updated? + association_id = record.public_send(reflection.options[:primary_key] || :id) + self[reflection.foreign_key] = association_id + association.loaded! + end + + saved if autosave + end + end + end + + def custom_validation_context? + validation_context && [:create, :update].exclude?(validation_context) + end + + def _ensure_no_duplicate_errors + errors.uniq! + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/base.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/base.rb new file mode 100644 index 0000000000..a79ce54fbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/base.rb @@ -0,0 +1,316 @@ +# 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 + # + # == 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.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 + extend ActiveModel::Naming + + 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 ActiveModel::Conversion + include Integration + include Validations + include CounterCache + include Attributes + include Locking::Optimistic + include Locking::Pessimistic + include AttributeMethods + include Callbacks + include Timestamp + include Associations + include ActiveModel::SecurePassword + include AutosaveAssociation + include NestedAttributes + include Transactions + include TouchLater + include NoTouching + include Reflection + include Serialization + include Store + include SecureToken + include SignedId + include Suppressor + end + + ActiveSupport.run_load_hooks(:active_record, Base) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/callbacks.rb new file mode 100644 index 0000000000..ee61d063bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/callbacks.rb @@ -0,0 +1,468 @@ +# 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+. + # The same applies to all non-transactional callbacks. + # + # As seen below, in case there are multiple transactional callbacks the order + # is reversed. + # + # For example: + # + # class Topic < ActiveRecord::Base + # has_many :children + # + # after_commit :log_children + # after_commit :do_something_else + # + # private + # + # def log_children + # # Child processing + # end + # + # def do_something_else + # # Something else + # end + # end + # + # In this case the +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 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 { super } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/json.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/json.rb new file mode 100644 index 0000000000..a69b38487e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/json.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Coders # :nodoc: + class 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/yaml_column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/yaml_column.rb new file mode 100644 index 0000000000..b700b8c86f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/coders/yaml_column.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "yaml" + +module ActiveRecord + module Coders # :nodoc: + class YAMLColumn # :nodoc: + attr_accessor :object_class + + def initialize(attr_name, object_class = Object) + @attr_name = attr_name + @object_class = object_class + check_arity_of_constructor + end + + def dump(obj) + return if obj.nil? + + assert_valid_value(obj, action: "dump") + YAML.dump obj + end + + def load(yaml) + return object_class.new if object_class != Object && yaml.nil? + return yaml unless yaml.is_a?(String) && yaml.start_with?("---") + obj = yaml_load(yaml) + + assert_valid_value(obj, action: "load") + obj ||= object_class.new if object_class != Object + + obj + end + + def assert_valid_value(obj, action:) + unless obj.nil? || obj.is_a?(object_class) + raise SerializationTypeMismatch, + "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.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 + + if YAML.respond_to?(:unsafe_load) + def yaml_load(payload) + if ActiveRecord::Base.use_yaml_unsafe_load + YAML.unsafe_load(payload) + elsif YAML.method(:safe_load).parameters.include?([:key, :permitted_classes]) + YAML.safe_load(payload, permitted_classes: ActiveRecord::Base.yaml_column_permitted_classes, aliases: true) + else + YAML.safe_load(payload, ActiveRecord::Base.yaml_column_permitted_classes, [], true) + end + end + else + def yaml_load(payload) + if ActiveRecord::Base.use_yaml_unsafe_load + YAML.load(payload) + elsif YAML.method(:safe_load).parameters.include?([:key, :permitted_classes]) + YAML.safe_load(payload, permitted_classes: ActiveRecord::Base.yaml_column_permitted_classes, aliases: true) + else + YAML.safe_load(payload, ActiveRecord::Base.yaml_column_permitted_classes, [], true) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters.rb new file mode 100644 index 0000000000..da07d5d295 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + extend ActiveSupport::Autoload + + eager_autoload do + autoload :AbstractAdapter + end + + autoload :Column + autoload :PoolConfig + autoload :PoolManager + autoload :LegacyPoolManager + autoload :SchemaCache + autoload :Deduplicable + + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do + autoload :IndexDefinition + autoload :ColumnDefinition + autoload :ChangeColumnDefinition + autoload :ForeignKeyDefinition + autoload :CheckConstraintDefinition + autoload :TableDefinition + autoload :Table + autoload :AlterTable + autoload :ReferenceDefinition + end + + autoload_at "active_record/connection_adapters/abstract/connection_pool" do + autoload :ConnectionHandler + end + + autoload_under "abstract" do + autoload :SchemaStatements + autoload :DatabaseStatements + autoload :DatabaseLimits + autoload :Quoting + autoload :ConnectionPool + autoload :QueryCache + autoload :Savepoints + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/connection_pool.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/connection_pool.rb new file mode 100644 index 0000000000..7e0981e09c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -0,0 +1,1229 @@ +# frozen_string_literal: true + +require "thread" +require "concurrent/map" +require "monitor" +require "weakref" + +module ActiveRecord + module ConnectionAdapters + module AbstractPool # :nodoc: + def get_schema_cache(connection) + self.schema_cache ||= SchemaCache.new(connection) + schema_cache.connection = connection + schema_cache + end + + def set_schema_cache(cache) + self.schema_cache = cache + end + end + + class NullPool # :nodoc: + include ConnectionAdapters::AbstractPool + + attr_accessor :schema_cache + + def connection_klass + nil + end + end + + # 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. + # + # == Obtaining (checking out) a connection + # + # Connections can be obtained and used from a connection pool in several + # ways: + # + # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection] + # as with Active Record 2.1 and + # earlier (pre-connection-pooling). Eventually, when you're done with + # the connection(s) and wish it to be returned to the pool, you call + # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!]. + # This will be 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). + # + # == 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 + # 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 = Concurrent.monotonic_time + elapsed = 0 + loop do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @cond.wait(timeout - elapsed) + end + + return remove if any? + + elapsed = Concurrent.monotonic_time - 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 + + # 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) + 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 + + include MonitorMixin + include QueryCache::ConnectionPoolConfiguration + include ConnectionAdapters::AbstractPool + + attr_accessor :automatic_reconnect, :checkout_timeout + attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass + + delegate :schema_cache, :schema_cache=, 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 + @connection_klass = pool_config.connection_klass + + @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 @thread_cached_conns does not require + # synchronization. + @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) + + @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 + + @lock_thread = false + + @reaper = Reaper.new(self, db_config.reaping_frequency) + @reaper.run + end + + def lock_thread=(lock_thread) + if lock_thread + @lock_thread = Thread.current + else + @lock_thread = nil + end + end + + # Retrieve the connection associated with the current thread, or call + # #checkout to obtain one if necessary. + # + # #connection can be called any number of times; the connection is + # held in a cache keyed by a thread. + def connection + @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout + 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 + # #connection or #with_connection methods. Connections obtained through + # #checkout will not be detected by #active_connection? + def active_connection? + @thread_cached_conns[connection_cache_key(current_thread)] + end + + # 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 + # #connection or #with_connection methods, connections obtained through + # #checkout will not be automatically released. + def release_connection(owner_thread = Thread.current) + if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)) + checkin conn + end + end + + # If a connection obtained through #connection or #with_connection methods + # already exists yield it to the block. If no such connection + # exists checkout a connection, yield it to the block, and checkin the + # connection when finished. + def with_connection + unless conn = @thread_cached_conns[connection_cache_key(Thread.current)] + conn = connection + fresh_connection = true + end + yield conn + ensure + release_connection if fresh_connection + end + + # Returns true if a connection has already been opened. + def connected? + synchronize { @connections.any? } + 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 = [] + @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 = @thread_cached_conns = 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) + checkout_and_verify(acquire_connection(checkout_timeout)) + 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) + conn.lock.synchronize do + synchronize do + remove_connection_from_thread_cache 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 + + # Return connection pool's usage statistic + # Example: + # + # 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 + + private + #-- + # 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 + + #-- + # From the discussion on GitHub: + # https://github.com/rails/rails/pull/14938#commitcomment-6601951 + # This hook-in method allows for easier monkey-patching fixes needed by + # JRuby users that use Fibers. + def connection_cache_key(thread) + thread + end + + def current_thread + @lock_thread || Thread.current + 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 + # account for our own connections + @connections.select { |conn| conn.owner == Thread.current } + end + + newly_checked_out = [] + timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2) + + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Concurrent.monotonic_time + 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 == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end + end + + msg << " (#{thread_report.join(', ')})" if thread_report.any? + + raise ExclusiveConnectionTimeoutError, msg + 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 + @available.poll(checkout_timeout) + end + 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) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache + + def new_connection + Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn| + conn.check_version + end + end + + # 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 + end + + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end + + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise + end + end + + # 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 + FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! } + private_constant :FINALIZER + + def initialize + # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name). + @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2) + + # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred + ObjectSpace.define_finalizer self, FINALIZER + end + + def prevent_writes # :nodoc: + Thread.current[:prevent_writes] + end + + def prevent_writes=(prevent_writes) # :nodoc: + Thread.current[:prevent_writes] = prevent_writes + 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) + unless ActiveRecord::Base.legacy_connection_handling + raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling" + end + + original, self.prevent_writes = self.prevent_writes, enabled + yield + ensure + self.prevent_writes = original + end + + def connection_pool_names # :nodoc: + owner_to_pool_manager.keys + end + + def all_connection_pools + owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) } + end + + def connection_pool_list(role = ActiveRecord::Base.current_role) + owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) } + end + alias :connection_pools :connection_pool_list + + def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard) + owner_name = config.to_s if config.is_a?(Symbol) + + pool_config = resolve_pool_config(config, owner_name) + db_config = pool_config.db_config + + # Protects the connection named `ActiveRecord::Base` from being removed + # if the user calls `establish_connection :primary`. + if owner_to_pool_manager.key?(pool_config.connection_specification_name) + remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard) + end + + message_bus = ActiveSupport::Notifications.instrumenter + payload = {} + if pool_config + payload[:spec_name] = pool_config.connection_specification_name + payload[:shard] = shard + payload[:config] = db_config.configuration_hash + end + + if ActiveRecord::Base.legacy_connection_handling + owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new + else + owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new + end + pool_manager = get_pool_manager(pool_config.connection_specification_name) + pool_manager.set_pool_config(role, shard, pool_config) + + message_bus.instrument("!connection.active_record", payload) do + pool_config.pool + end + end + + # Returns true if there are any active connections among the connection + # pools that the ConnectionHandler is managing. + def active_connections?(role = ActiveRecord::Base.current_role) + connection_pool_list(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 = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:release_connection) + end + + # Clears the cache which maps classes. + # + # See ConnectionPool#clear_reloadable_connections! for details. + def clear_reloadable_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:clear_reloadable_connections!) + end + + def clear_all_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(role).each(&:disconnect!) + end + + # Disconnects all currently idle connections. + # + # See ConnectionPool#flush! for details. + def flush_idle_connections!(role = ActiveRecord::Base.current_role) + connection_pool_list(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(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc: + pool = retrieve_connection_pool(spec_name, role: role, shard: shard) + + unless pool + if shard != ActiveRecord::Base.default_shard + message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard." + elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler + message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role." + elsif role != ActiveRecord::Base.default_role + message = "No connection pool for '#{spec_name}' found for the '#{role}' role." + else + message = "No connection pool for '#{spec_name}' found." + end + + raise ConnectionNotEstablished, message + end + + pool.connection + end + + # Returns true if a connection that's accessible to this class has + # already been opened. + def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + pool = retrieve_connection_pool(spec_name, role: role, shard: shard) + pool && pool.connected? + end + + # Remove the connection for this class. This will close the active + # connection and the defined connection (if they exist). The result + # can be used as an argument for #establish_connection, for easily + # re-establishing the connection. + def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash + end + deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash" + + def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + if pool_manager = get_pool_manager(owner) + pool_config = pool_manager.remove_pool_config(role, shard) + + if pool_config + pool_config.disconnect! + pool_config.db_config + end + end + end + + # Retrieving the connection pool happens a lot, so we cache it in @owner_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(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + pool_config = get_pool_manager(owner)&.get_pool_config(role, shard) + pool_config&.pool + end + + private + attr_reader :owner_to_pool_manager + + # Returns the pool manager for an owner. + # + # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is + # deprecated in favor of looking it up by `"ActiveRecord::Base"`. + # + # During the deprecation period, if `"primary"` is passed, the pool manager + # for `ActiveRecord::Base` will still be returned. + def get_pool_manager(owner) + return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner) + + if owner == "primary" + ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 7.0.0. Please use `ActiveRecord::Base`.") + owner_to_pool_manager[Base.name] + 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, owner_name) + db_config = Base.configurations.resolve(config) + + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. + path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter" + begin + require path_to_adapter + rescue LoadError => e + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end + + unless ActiveRecord::Base.respond_to?(db_config.adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter" + end + + ConnectionAdapters::PoolConfig.new(owner_name, db_config) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_limits.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_limits.rb new file mode 100644 index 0000000000..b1ff8eec74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -0,0 +1,43 @@ +# 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 alias. + def table_alias_length + max_identifier_length + end + + # Returns the maximum allowed length for an index name. This + # limit is enforced by \Rails and is less than or equal to + # #index_name_length. The gap between + # #index_name_length is to allow internal \Rails + # operations to use prefixes in temporary operations. + def allowed_index_name_length + index_name_length + end + deprecate :allowed_index_name_length + + # Returns the maximum length of an index name. + def index_name_length + max_identifier_length + end + + # Returns the maximum number of elements in an IN (x,y,z) clause. + # +nil+ means no limit. + def in_clause_length + nil + end + deprecate :in_clause_length + + private + def bind_params_length + 65535 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 0000000000..f1db1513ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -0,0 +1,561 @@ +# 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) # :nodoc: + if arel_or_sql_string.respond_to?(:ast) + 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() + + if prepared_statements + collector.preparable = true + sql, binds = visitor.compile(arel_or_sql_string.ast, 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.ast, collector) + end + [sql.freeze, binds, preparable] + else + arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen? + [arel_or_sql_string, binds, preparable] + 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) + arel = arel_from_relation(arel) + sql, binds, preparable = to_sql_and_binds(arel, binds, preparable) + + if prepared_statements && preparable + select_prepared(sql, name, binds) + else + select(sql, name, binds) + end + rescue ::RangeError + ActiveRecord::Result.new([], []) + end + + # Returns a record hash with the column names as keys and column values + # as values. + def select_one(arel, name = nil, binds = []) + select_all(arel, name, binds).first + end + + # Returns a single value from a record + def select_value(arel, name = nil, binds = []) + single_value_from_rows(select_rows(arel, name, binds)) + 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 = []) + select_all(arel, name, binds).rows + end + + def query_value(sql, name = nil) # :nodoc: + single_value_from_rows(query(sql, name)) + end + + def query_values(sql, name = nil) # :nodoc: + query(sql, name).map(&:first) + end + + def query(sql, name = nil) # :nodoc: + exec_query(sql, name).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. + # Note: depending on your database connector, the result returned by this + # method may be manually memory managed. Consider using the exec_query + # wrapper instead. + def execute(sql, name = nil) + raise NotImplementedError + 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. + def exec_query(sql, name = "SQL", binds = [], prepare: false) + raise NotImplementedError + 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. + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + sql, binds = sql_for_insert(sql, pk, binds) + 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 = []) + exec_query(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 = []) + exec_query(sql, name, binds) + end + + def exec_insert_all(sql, name) # :nodoc: + exec_query(sql, name) + end + + def explain(arel, binds = []) # :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+. + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + value = exec_insert(sql, name, binds, pk, sequence_name) + 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 -= [schema_migration.table_name, InternalMetadata.table_name] + + return if table_names.empty? + + with_multi_statements do + disable_referential_integrity do + statements = build_truncate_statements(table_names) + execute_batch(statements, "Truncate Tables") + end + end + end + + # Runs the given block in a database transaction, and returns the result + # of the block. + # + # == 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.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.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 and postgresql adapters support setting the transaction + # isolation level. + def transaction(requires_new: nil, isolation: nil, joinable: true) + if !requires_new && current_transaction.joinable? + if isolation + raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" + end + yield + else + transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield } + 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!, 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 #:nodoc: + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) + 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 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 + + # 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 + end + + def exec_rollback_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 + + with_multi_statements do + disable_referential_integrity do + transaction(requires_new: true) do + execute_batch(statements, "Fixtures Load") + end + 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 + + private + def execute_batch(statements, name = nil) + statements.each do |statement| + execute(statement, name) + 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) + + 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 + manager.into(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.map do |table_name, fixtures| + next if fixtures.empty? + build_fixture_sql(fixtures, table_name) + end.compact + 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 with_multi_statements + yield + end + + def combine_multi_statements(total_sql) + total_sql.join(";\n") + end + + # Returns an ActiveRecord::Result instance. + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: false) + end + + def select_prepared(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: true) + end + + def sql_for_insert(sql, pk, binds) + [sql, binds] + end + + def last_inserted_id(result) + single_value_from_rows(result.rows) + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/query_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/query_cache.rb new file mode 100644 index 0000000000..6223e37698 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module QueryCache + class << self + def included(base) #:nodoc: + dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables, + :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all + + base.set_callback :checkout, :after, :configure_query_cache! + base.set_callback :checkin, :after, :disable_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}(*) + ActiveRecord::Base.clear_query_caches_for_current_thread + super + end + end_code + end + end + end + + module ConnectionPoolConfiguration + def initialize(*) + super + @query_cache_enabled = Concurrent::Map.new { false } + end + + def enable_query_cache! + @query_cache_enabled[connection_cache_key(current_thread)] = true + connection.enable_query_cache! if active_connection? + end + + def disable_query_cache! + @query_cache_enabled.delete connection_cache_key(current_thread) + connection.disable_query_cache! if active_connection? + end + + def query_cache_enabled + @query_cache_enabled[connection_cache_key(current_thread)] + end + end + + attr_reader :query_cache, :query_cache_enabled + + def initialize(*) + super + @query_cache = Hash.new { |h, sql| h[sql] = {} } + @query_cache_enabled = false + end + + # Enable the query cache within the block. + def cache + old, @query_cache_enabled = @query_cache_enabled, true + yield + ensure + @query_cache_enabled = old + clear_query_cache unless @query_cache_enabled + end + + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + clear_query_cache + end + + # Disable the query cache within the block. + def uncached + old, @query_cache_enabled = @query_cache_enabled, false + yield + ensure + @query_cache_enabled = old + 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 + @lock.synchronize do + @query_cache.clear + end + end + + def select_all(arel, name = nil, binds = [], preparable: nil) + if @query_cache_enabled && !locked?(arel) + arel = arel_from_relation(arel) + sql, binds, preparable = to_sql_and_binds(arel, binds, preparable) + + cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } + else + super + end + end + + private + def cache_sql(sql, name, binds) + @lock.synchronize do + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument( + "sql.active_record", + cache_notification_info(sql, name, binds) + ) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end + 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, + cached: true + } + end + + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. + def locked?(arel) + arel = arel.arel if arel.is_a?(Relation) + arel.respond_to?(:locked) && arel.locked + end + + def configure_query_cache! + enable_query_cache! if pool.query_cache_enabled + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 0000000000..aac5bfe0a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/quoting.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/multibyte/chars" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module Quoting + # Quotes the column value to help prevent + # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection]. + def quote(value) + if value.is_a?(Base) + ActiveSupport::Deprecation.warn(<<~MSG) + Passing an Active Record object to `quote` directly is deprecated + and will be no longer quoted as id value in Rails 7.0. + MSG + value = value.id_for_database + end + + _quote(value) + 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, column = nil) + if value.is_a?(Base) + ActiveSupport::Deprecation.warn(<<~MSG) + Passing an Active Record object to `type_cast` directly is deprecated + and will be no longer type casted as id value in Rails 7.0. + MSG + value = value.id_for_database + end + + if column + ActiveSupport::Deprecation.warn(<<~MSG) + Passing a column to `type_cast` is deprecated and will be removed in Rails 7.0. + MSG + type = lookup_cast_type_from_column(column) + value = type.serialize(value) + end + + _type_cast(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. Defaults to no quoting. + def quote_column_name(column_name) + column_name.to_s + end + + # Quotes the table name. Defaults to column name quoting. + def quote_table_name(table_name) + quote_column_name(table_name) + end + + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for mysql2 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 ActiveRecord::Base.default_timezone == :utc + value = value.getutc if value.respond_to?(:getutc) && !value.utc? + else + value = value.getlocal if value.respond_to?(:getlocal) + end + end + + result = value.to_s(: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 + + def column_name_matcher # :nodoc: + COLUMN_NAME + end + + def column_name_with_order_matcher # :nodoc: + COLUMN_NAME_WITH_ORDER + end + + # Regexp for column names (with or without a table name prefix). + # Matches the following: + # + # "#{table_name}.#{column_name}" + # "#{column_name}" + COLUMN_NAME = / + \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 + + # 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" + COLUMN_NAME_WITH_ORDER = / + \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 + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def type_casted_binds(binds) + case binds.first + when Array + binds.map { |column, value| type_cast(value, column) } + else + binds.map do |value| + if ActiveModel::Attribute === value + type_cast(value.value_for_database) + else + type_cast(value) + end + end + end + end + + def lookup_cast_type(sql_type) + type_map.lookup(sql_type) + end + + 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, ActiveSupport::Duration 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 + + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/savepoints.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/savepoints.rb new file mode 100644 index 0000000000..d6dbef3fc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module Savepoints + def current_savepoint_name + current_transaction.savepoint_name + end + + def create_savepoint(name = current_savepoint_name) + execute("SAVEPOINT #{name}", "TRANSACTION") + end + + def exec_rollback_to_savepoint(name = current_savepoint_name) + execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION") + end + + def release_savepoint(name = current_savepoint_name) + execute("RELEASE SAVEPOINT #{name}", "TRANSACTION") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_creation.rb new file mode 100644 index 0000000000..55cce5e501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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?, :supports_foreign_keys?, :foreign_key_options, + :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?, :check_constraint_options, + 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(" ") + 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 supports_foreign_keys? + statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) }) + end + + if supports_check_constraints? + statements.concat(o.check_constraints.map { |expression, options| check_constraint_in_create(o.name, expression, options) }) + 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) + sql = +<<~SQL + CONSTRAINT #{quote_column_name(o.name)} + FOREIGN KEY (#{quote_column_name(o.column)}) + REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) + 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_DropForeignKey(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + end + + 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 << "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 visit_DropCheckConstraint(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + 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 foreign_key_in_create(from_table, to_table, options) + prefix = ActiveRecord::Base.table_name_prefix + suffix = ActiveRecord::Base.table_name_suffix + to_table = "#{prefix}#{to_table}#{suffix}" + options = foreign_key_options(from_table, to_table, options) + accept ForeignKeyDefinition.new(from_table, to_table, options) + end + + def check_constraint_in_create(table_name, expression, options) + options = check_constraint_options(table_name, expression, options) + accept CheckConstraintDefinition.new(table_name, expression, options) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 0000000000..c2d53d556b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -0,0 +1,806 @@ +# 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, :comment + + def initialize( + table, name, + unique = false, + columns = [], + lengths: {}, + orders: {}, + opclasses: {}, + where: nil, + type: nil, + using: nil, + comment: nil + ) + @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 + @comment = comment + end + + def column_options + { + length: lengths, + order: orders, + opclass: opclasses, + } + 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: + def primary_key? + options[:primary_key] + end + + [:limit, :precision, :scale, :default, :null, :collation, :comment].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: + + 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 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) + (to_table.nil? || to_table.to_s == self.to_table) && + (validate.nil? || validate == options.fetch(:validate, validate)) && + options.all? { |k, v| self.options[k].to_s == v.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 + 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_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 polymorphic_options + as_options(polymorphic).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) + + # 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) + 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 :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 + + # 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[6.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 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 + + if @columns_hash[name] + if @columns_hash[name].primary_key? + raise ArgumentError, "you can't redefine the primary key column '#{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}'." + end + end + + @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(table_name, **options) # :nodoc: + foreign_keys << [table_name, options] + end + + def check_constraint(expression, **options) + check_constraints << [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) + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) + end + + private + def create_column_definition(name, type, options) + 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 + end + + class AlterTable # :nodoc: + attr_reader :adds + attr_reader :foreign_key_adds, :foreign_key_drops + attr_reader :check_constraint_adds, :check_constraint_drops + + def initialize(td) + @td = td + @adds = [] + @foreign_key_adds = [] + @foreign_key_drops = [] + @check_constraint_adds = [] + @check_constraint_drops = [] + end + + def name; @td.name; end + + def add_foreign_key(to_table, options) + @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options) + end + + def drop_foreign_key(name) + @foreign_key_drops << name + end + + def add_check_constraint(expression, options) + @check_constraint_adds << CheckConstraintDefinition.new(name, expression, options) + end + + def drop_check_constraint(constraint_name) + @check_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 + + # 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.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) + @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) + @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) + @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) + @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) + @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) + @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) + 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) + 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) + @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) + @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) + @base.add_check_constraint(name, *args) + 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) + @base.remove_check_constraint(name, *args) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb new file mode 100644 index 0000000000..3817c8829e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + class SchemaDumper < SchemaDumper # :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) + column.precision.inspect if column.precision + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 0000000000..0c5cdf62e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -0,0 +1,1637 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/access" +require "digest/sha2" + +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 useable 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") + # + def index_exists?(table_name, column_name, **options) + checks = [] + + if column_name.present? + column_names = Array(column_name).map(&:to_s) + checks << lambda { |i| Array(i.columns) == column_names } + end + + checks << lambda { |i| i.unique } if options[:unique] + checks << lambda { |i| i.name == options[:name].to_s } if options[:name] + + indexes(table_name).any? { |i| checks.all? { |check| check[i] } } + end + + # Returns an array of +Column+ objects for the table specified by +table_name+. + def columns(table_name) + table_name = table_name.to_s + column_definitions(table_name).map do |field| + new_column_from_field(table_name, field) + 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 + # 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 order ( + # 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) + td = create_table_definition(table_name, **extract_table_options!(options)) + + if id && !td.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) + td.primary_keys pk + else + td.primary_key pk, id, **options + end + end + + yield td if block_given? + + 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 + + # 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) + # + # 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| t.to_s.singularize } + + 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 + + # Drops the join table specified by the given arguments. + # See #create_join_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) + 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, **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, self) + 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 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. + def drop_table(table_name, **options) + 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 + + # Add a new +type+ column named +column_name+ to +table_name+. + # + # 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, :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): + # * :limit - + # Requests a maximum column length. This is the number of characters for a :string column + # and number of bytes for :text, :binary, and :integer columns. + # This option is ignored by some backends. + # * :default - + # The column's default value. Use +nil+ for +NULL+. + # * :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. + # * :collation - + # Specifies the collation for a :string or :text column. If not specified, the + # column will have the same collation as the table. + # * :comment - + # Specifies the comment for the column. This option is ignored by some backends. + # * :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..63], :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) + return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type) + + at = create_alter_table table_name + at.add_column(column_name, type, **options) + execute schema_creation.accept at + 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 + + # 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 + + column_names.each do |column_name| + remove_column(table_name, column_name, type, **options) + end + 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. + # Indexes on the column are automatically removed. + # + # 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 used. + # + # 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 + + # 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: SQLite doesn't support index length. + # + # ====== 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 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) + # + # Note: only supported by PostgreSQL. + # + # 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) + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + + create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists) + execute schema_creation.accept(create_index) + 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] + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" + 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. + # #add_reference and #add_belongs_to are acceptable. + # + # 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_to(update_table_definition(table_name, self)) + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # #remove_reference and #remove_belongs_to are acceptable. + # + # ====== 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) + 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 + else + foreign_key_options = { to_table: reference_name } + end + foreign_key_options[:column] ||= "#{ref_name}_id" + remove_foreign_key(table_name, **foreign_key_options) + end + + remove_column(table_name, "#{ref_name}_id") + remove_column(table_name, "#{ref_name}_type") 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 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 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" + # [:primary_key] + # The primary key column name on +to_table+. Defaults to +id+. + # [: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+ + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_foreign_key(from_table, to_table, **options) + return unless supports_foreign_keys? + + 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 + # + # 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 supports_foreign_keys? + + 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) # :nodoc: + name = strip_table_name_prefix_and_suffix(table_name) + "#{name.singularize}_id" + end + + def foreign_key_options(from_table, to_table, options) # :nodoc: + options = options.dup + options[:column] ||= foreign_key_column_for(to_table) + options[:name] ||= foreign_key_name(from_table, options) + 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_. + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_check_constraint(table_name, expression, **options) + return unless supports_check_constraints? + + options = check_constraint_options(table_name, expression, 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. + # + # remove_check_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_check_constraint. + def remove_check_constraint(table_name, expression = nil, **options) + return unless supports_check_constraints? + + 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 + + def dump_schema_information # :nodoc: + versions = schema_migration.all_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(schema_migration.table_name) + + 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 + + # 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) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) && supports_datetime_with_precision? + options[:precision] = 6 + end + + add_column table_name, :created_at, :datetime, **options + add_column table_name, :updated_at, :datetime, **options + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. + # + # remove_timestamps(:suppliers) + # + def remove_timestamps(table_name, **options) + remove_column table_name, :updated_at + remove_column table_name, :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) + + 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], + 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 + + private + 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) && column_name.is_a?(String) && /\W/.match?(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? + 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) + indexes(new_name).each do |index| + generated_index_name = index_name(table_name, column: index.columns) + if generated_index_name == index.name + rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) + 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 schema_creation + SchemaCreation.new(self) + 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 extract_table_options!(options) + options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation) + 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 column_names.is_a?(String) && /\W/.match?(column_names) + column_names + else + Array(column_names) + end + end + + def index_name_options(column_names) + if column_names.is_a?(String) && /\W/.match?(column_names) + column_names = column_names.scan(/\w+/).join("_") + end + + { column: column_names } + 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 + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + + "fk_rails_#{hashed_identifier}" + end + end + + def foreign_key_for(from_table, **options) + return unless supports_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 check_constraint_name(table_name, **options) + options.fetch(:name) do + expression = options.fetch(:expression) + identifier = "#{table_name}_#{expression}_chk" + hashed_identifier = 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.name == chk_name } + 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 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 bulk_change_table(table_name, operations) + 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 << 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 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 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(schema_migration.table_name) + + if versions.is_a?(Array) + sql = +"INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") + sql << ";\n\n" + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/transaction.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/transaction.rb new file mode 100644 index 0000000000..d26e869d08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract/transaction.rb @@ -0,0 +1,381 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + 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 NullTransaction #:nodoc: + def initialize; end + def state; end + def closed?; true; end + def open?; false; end + def joinable?; false; end + def add_record(record, _ = true); end + end + + class Transaction #:nodoc: + attr_reader :connection, :state, :savepoint_name, :isolation_level + attr_accessor :written + + def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false) + @connection = connection + @state = TransactionState.new + @records = nil + @isolation_level = isolation + @materialized = false + @joinable = joinable + @run_commit_callbacks = run_commit_callbacks + @lazy_enrollment_records = nil + 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 records + if @lazy_enrollment_records + @records.concat @lazy_enrollment_records.values + @lazy_enrollment_records = nil + end + @records + end + + def materialize! + @materialized = true + end + + def materialized? + @materialized + end + + def rollback_records + return unless records + ite = records.uniq(&:__id__) + already_run_callbacks = {} + while record = ite.shift + trigger_callbacks = record.trigger_transactional_callbacks? + should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks + already_run_callbacks[record] ||= trigger_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 + + def before_commit_records + records.uniq.each(&:before_committed!) if records && @run_commit_callbacks + end + + def commit_records + return unless records + ite = records.uniq(&:__id__) + already_run_callbacks = {} + while record = ite.shift + if @run_commit_callbacks + trigger_callbacks = record.trigger_transactional_callbacks? + should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks + already_run_callbacks[record] ||= trigger_callbacks + record.committed!(should_run_callbacks: should_run_callbacks) + else + # 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 + + def full_rollback?; true; end + def joinable?; @joinable; end + def closed?; false; end + def open?; !closed?; end + end + + 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 rollback + connection.rollback_to_savepoint(savepoint_name) if materialized? + @state.rollback! + end + + def commit + connection.release_savepoint(savepoint_name) if materialized? + @state.commit! + end + + def full_rollback?; false; end + end + + class RealTransaction < Transaction + def materialize! + if isolation_level + connection.begin_isolated_db_transaction(isolation_level) + else + connection.begin_db_transaction + end + + super + end + + def rollback + connection.rollback_db_transaction if materialized? + @state.full_rollback! + end + + def commit + connection.commit_db_transaction if materialized? + @state.full_commit! + 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 + ) + else + SavepointTransaction.new( + @connection, + "active_record_#{@stack.size}", + @stack.last, + isolation: isolation, + joinable: joinable, + run_commit_callbacks: run_commit_callbacks + ) + end + + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true + else + transaction.materialize! + 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 materialize_transactions + return if @materializing_transactions + return unless @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 + + def commit_transaction + @connection.lock.synchronize do + transaction = @stack.last + + begin + transaction.before_commit_records + ensure + @stack.pop + end + + transaction.commit + transaction.commit_records + end + end + + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= @stack.pop + transaction.rollback unless transaction.state.invalidated? + transaction.rollback_records + end + end + + def within_new_transaction(isolation: nil, joinable: true) + @connection.lock.synchronize do + transaction = begin_transaction(isolation: isolation, joinable: joinable) + ret = yield + completed = true + ret + rescue Exception => error + if transaction + transaction.state.invalidate! if error.is_a? ActiveRecord::TransactionRollbackError + rollback_transaction + after_failure_actions(transaction, error) + end + + raise + ensure + if transaction + if error + # @connection still holds an open or invalid transaction, so we must not + # put it back in the pool for reuse. + @connection.throw_away! unless transaction.state.rolledback? + else + if Thread.current.status == "aborting" + rollback_transaction + else + if !completed && transaction.written + ActiveSupport::Deprecation.warn(<<~EOW) + Using `return`, `break` or `throw` to exit a transaction block is + deprecated without replacement. If the `throw` came from + `Timeout.timeout(duration)`, pass an exception class as a second + argument so it doesn't use `throw` to abort its block. This results + in the transaction being committed, but in the next release of Rails + it will rollback. + EOW + end + begin + commit_transaction + rescue Exception + rollback_transaction(transaction) unless transaction.state.completed? + raise + end + end + end + end + end + end + + def open_transactions + @stack.size + end + + def current_transaction + @stack.last || NULL_TRANSACTION + end + + private + NULL_TRANSACTION = NullTransaction.new + + # 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_adapter.rb new file mode 100644 index 0000000000..9a02270880 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_adapter.rb @@ -0,0 +1,758 @@ +# frozen_string_literal: true + +require "set" +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/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 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.connection}[rdoc-ref:ConnectionHandling#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)*|/\*(?:[^\*]|\*[^/])*\*/}m + + attr_accessor :pool + attr_reader :visitor, :owner, :logger, :lock + alias :in_use? :owner + + 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 + + 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.quoted_column_names # :nodoc: + @quoted_column_names ||= {} + end + + def self.quoted_table_names # :nodoc: + @quoted_table_names ||= {} + end + + def initialize(connection, logger = nil, config = {}) # :nodoc: + super() + + @connection = connection + @owner = nil + @instrumenter = ActiveSupport::Notifications.instrumenter + @logger = logger + @config = config + @pool = ActiveRecord::ConnectionAdapters::NullPool.new + @idle_since = Concurrent.monotonic_time + @visitor = arel_visitor + @statements = build_statement_pool + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + + @prepared_statements = self.class.type_cast_config_to_boolean( + config.fetch(:prepared_statements, true) + ) + + @advisory_locks_enabled = self.class.type_cast_config_to_boolean( + config.fetch(:advisory_locks, true) + ) + end + + def replica? + @config[:replica] || false + end + + def use_metadata_table? + @config.fetch(:use_metadata_table, true) + end + + # Determines whether writes are currently being prevented. + # + # Returns true if the connection is a replica. + # + # If the application is using legacy handling, returns + # true if +connection_handler.prevent_writes+ is set. + # + # If the application is using the new connection handling + # will return true based on +current_preventing_writes+. + def preventing_writes? + return true if replica? + return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling + return false if connection_klass.nil? + + connection_klass.current_preventing_writes + end + + def migrations_paths # :nodoc: + @config[:migrations_paths] || Migrator.migrations_paths + end + + def migration_context # :nodoc: + MigrationContext.new(migrations_paths, schema_migration) + end + + def schema_migration # :nodoc: + @schema_migration ||= begin + conn = self + spec_name = conn.pool.pool_config.connection_specification_name + + return ActiveRecord::SchemaMigration if spec_name == "ActiveRecord::Base" + + schema_migration_name = "#{spec_name}::SchemaMigration" + + Class.new(ActiveRecord::SchemaMigration) do + define_singleton_method(:name) { schema_migration_name } + define_singleton_method(:to_s) { schema_migration_name } + + self.connection_specification_name = spec_name + end + end + 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: + Thread.current[:ar_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 == Thread.current + msg << "it is already leased by the current thread." + else + msg << "it is already in use by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + raise ActiveRecordError, msg + end + + @owner = Thread.current + end + + def connection_klass # :nodoc: + @pool.connection_klass + end + + def schema_cache + @pool.get_schema_cache(self) + end + + def schema_cache=(cache) + cache.connection = self + @pool.set_schema_cache(cache) + end + + # this method must only be called while holding connection pool's mutex + def expire + if in_use? + if @owner != Thread.current + raise ActiveRecordError, "Cannot expire connection, " \ + "it is owned by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + + @idle_since = Concurrent.monotonic_time + @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 != Thread.current + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = Thread.current + 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? + Concurrent.monotonic_time - @idle_since + 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) + raise NotImplementedError + 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 + + # 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 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 check constraints? + def supports_check_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 + + # 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 + + 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 + + # CONNECTION MANAGEMENT ==================================== + + # 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 call super if they + # override the default implementation. + def reconnect! + clear_cache! + reset_transaction + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + clear_cache! + reset_transaction + 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. + # + # Prevent @connection's finalizer from touching the socket, or + # otherwise communicating with its server, when it is collected. + if schema_cache.connection == self + schema_cache.connection = nil + end + 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. + # + # The default implementation does nothing; the implementation should be + # overridden by concrete adapters. + def reset! + # this should be overridden by concrete adapters + 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! + @lock.synchronize { @statements.clear } if @statements + 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! + reconnect! unless active? + 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. + def raw_connection + disable_lazy_transactions! + @connection + 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: + schema_cache.database_version + end + + def check_version # :nodoc: + end + + private + def type_map + @type_map ||= Type::TypeMap.new.tap do |mapping| + initialize_type_map(mapping) + end + end + + def initialize_type_map(m = type_map) + 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 reload_type_map + type_map.clear + initialize_type_map + 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 register_class_with_precision(mapping, key, klass) + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + 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 + + def translate_exception_class(e, sql, binds) + message = "#{e.class.name}: #{e.message}" + + exception = translate_exception( + e, message: message, sql: sql, binds: binds + ) + exception.set_backtrace e.backtrace + exception + end + + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc: + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection: self) do + @lock.synchronize do + yield + end + rescue => e + raise translate_exception_class(e, sql, binds) + end + end + + def translate_exception(exception, message:, sql:, binds:) + # override in derived class + case exception + when RuntimeError + exception + else + ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds) + end + end + + def without_prepared_statement?(binds) + !prepared_statements || binds.empty? + 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: {}) + ActiveRecord::Result.new(columns, rows, column_types) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new file mode 100644 index 0000000000..e065721eef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -0,0 +1,855 @@ +# 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/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::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 }, + 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 + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + 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 >= "8.0.1" + end + + def supports_expression_index? + !mariadb? && database_version >= "8.0.13" + end + + def supports_transaction_isolation? + 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.2.1" + else + database_version >= "8.0.16" + end + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + mariadb? || database_version >= "5.6.4" + 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 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 =========================================== + + # The two drivers have slightly different ways of yielding hashes of results, so + # this method must be implemented to provide a uniform interface. + def each_hash(result) # :nodoc: + raise NotImplementedError + end + + # 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}") + end + end + + # CONNECTION MANAGEMENT ==================================== + + def clear_cache! # :nodoc: + reload_type_map + super + end + + #-- + # DATABASE STATEMENTS ====================================== + #++ + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.query(sql) + end + end + end + + # Mysql2Adapter doesn't have to free a result after using it, but we use this method + # to write stuff in an abstract way without concerning ourselves about whether it + # needs to be explicitly freed or not. + def execute_and_free(sql, name = nil) # :nodoc: + yield execute(sql, name) + end + + def begin_db_transaction + execute("BEGIN", "TRANSACTION") + end + + def begin_isolated_db_transaction(isolation) + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + begin_db_transaction + end + + def commit_db_transaction #:nodoc: + execute("COMMIT", "TRANSACTION") + end + + def exec_rollback_db_transaction #:nodoc: + execute("ROLLBACK", "TRANSACTION") + end + + def empty_insert_statement_value(primary_key = nil) + "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) + 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) + end + + # Drops a table 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. + def drop_table(table_name, **options) + schema_cache.clear_data_source_cache!(table_name.to_s) + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' 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: + default = extract_new_default_value(default_or_changes) + change_column table_name, column_name, nil, default: default + end + + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + 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 + + 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: + 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) + + create_index = CreateIndexDefinition.new(index, algorithm) + execute schema_creation.accept(create_index) + 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) + + fk_info = 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', + 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 + + fk_info.map do |row| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + + ForeignKeyDefinition.new(table_name, row["to_table"], options) + end + end + + def check_constraints(table_name) + if supports_check_constraints? + scope = quoted_scope(table_name) + + chk_info = exec_query(<<~SQL, "SCHEMA") + 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 + + chk_info.map do |row| + options = { + name: row["name"] + } + expression = row["expression"] + expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql + 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") + 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: + sql = +"INSERT #{insert.into} #{insert.values_list}" + + if insert.skip_duplicates? + no_op_column = quote_column_name(insert.keys.first) + sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}" + elsif insert.update_duplicates? + sql << " ON DUPLICATE KEY UPDATE " + sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" } + sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",") + end + + sql + end + + def check_version # :nodoc: + if database_version < "5.5.8" + raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." + end + end + + private + def initialize_type_map(m = type_map) + 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(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.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2) + m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2) + 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 + + # 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_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_QUERY_TIMEOUT = 3024 + ER_FK_INCOMPATIBLE_COLUMNS = 3780 + + 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) + else + super + end + when ER_DB_CREATE_EXISTS + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) + when ER_DUP_ENTRY + RecordNotUnique.new(message, sql: sql, binds: binds) + 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) + when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS + mismatched_foreign_key(message, sql: sql, binds: binds) + when ER_CANNOT_CREATE_TABLE + if message.include?("errno: 150") + mismatched_foreign_key(message, sql: sql, binds: binds) + else + super + end + when ER_DATA_TOO_LONG + ValueTooLong.new(message, sql: sql, binds: binds) + when ER_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds) + when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT + NotNullViolation.new(message, sql: sql, binds: binds) + when ER_LOCK_DEADLOCK + Deadlocked.new(message, sql: sql, binds: binds) + when ER_LOCK_WAIT_TIMEOUT + LockWaitTimeout.new(message, sql: sql, binds: binds) + when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT + StatementTimeout.new(message, sql: sql, binds: binds) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message, sql: sql, binds: binds) + else + super + end + end + + def change_column_for_alter(table_name, column_name, type, **options) + column = column_for(table_name, column_name) + type ||= column.sql_type + + unless options.key?(:default) + options[:default] = column.default + end + + unless options.key?(:null) + options[:null] = column.null + end + + unless options.key?(:comment) + options[:comment] = column.comment + end + + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, **options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + 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 = 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_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 + variables = @config.fetch(:variables, {}).stringify_keys + + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 + + # 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.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 + # or else nil; compact to clear nils out + end.compact.join(", ") + + # ...and send them all in one query + execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA") + end + + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) + end + end + + def create_table_info(table_name) # :nodoc: + 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(message, sql:, binds:) + match = %r/ + (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?
\w+)`?.+? + FOREIGN\s+KEY\s*\(`?(?\w+)`?\)\s* + REFERENCES\s*(`?(?\w+)`?)\s*\(`?(?\w+)`?\) + /xmi.match(sql) + + options = { + message: message, + sql: sql, + binds: binds, + } + + 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 + + MismatchedForeignKey.new(**options) + end + + def version_string(full_version_string) + full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] + end + + # Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")). + # TODO: Remove the constant alias once Rails 6.1 has released. + MysqlString = Type::String # :nodoc: + + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/column.rb new file mode 100644 index 0000000000..85521ce9ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/column.rb @@ -0,0 +1,109 @@ +# 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 + + 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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/deduplicable.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/deduplicable.rb new file mode 100644 index 0000000000..030fd171be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/legacy_pool_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/legacy_pool_manager.rb new file mode 100644 index 0000000000..c6e216e864 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/legacy_pool_manager.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class LegacyPoolManager # :nodoc: + def initialize + @name_to_pool_config = {} + end + + def shard_names + @name_to_pool_config.keys + end + + def pool_configs(_ = nil) + @name_to_pool_config.values + end + + def remove_pool_config(_, shard) + @name_to_pool_config.delete(shard) + end + + def get_pool_config(_, shard) + @name_to_pool_config[shard] + end + + def set_pool_config(role, shard, pool_config) + if pool_config + @name_to_pool_config[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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/column.rb new file mode 100644 index 0000000000..c21529b0a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/column.rb @@ -0,0 +1,27 @@ +# 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 + + def virtual? + /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/database_statements.rb new file mode 100644 index 0000000000..1ef278c240 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module DatabaseStatements + # Returns an ActiveRecord::Result instance. + def select_all(*, **) # :nodoc: + result = if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + @connection.abandon_results! + result + end + + def query(sql, name = nil) # :nodoc: + execute(sql, name).to_a + end + + READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp( + :desc, :describe, :set, :show, :use + ) # :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 = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + start = Concurrent.monotonic_time + result = exec_query(sql, "EXPLAIN", binds) + elapsed = Concurrent.monotonic_time - start + + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + super + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + if without_prepared_statement?(binds) + execute_and_free(sql, name) do |result| + if result + build_result(columns: result.fields, rows: result.to_a) + else + build_result(columns: [], rows: []) + end + end + else + exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result| + if result + build_result(columns: result.fields, rows: result.to_a) + else + build_result(columns: [], rows: []) + end + end + end + end + + def exec_delete(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + @lock.synchronize do + execute_and_free(sql, name) { @connection.affected_rows } + end + else + exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows } + end + end + alias :exec_update :exec_delete + + private + def execute_batch(statements, name = nil) + combine_multi_statements(statements).each do |statement| + execute(statement, name) + end + @connection.abandon_results! + end + + def default_insert_value(column) + super unless column.auto_increment? + end + + def last_inserted_id(result) + @connection.last_id + 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 with_multi_statements + multi_statements_was = multi_statements_enabled? + + unless multi_statements_was + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + end + + yield + ensure + unless multi_statements_was + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + 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 + + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + if cache_stmt + stmt = @statements[sql] ||= @connection.prepare(sql) + else + stmt = @connection.prepare(sql) + end + + begin + result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + stmt.execute(*type_casted_binds) + end + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e + end + + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 0000000000..ed3e68132b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/quoting.rb new file mode 100644 index 0000000000..4a88ed1834 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/quoting.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveRecord + module ConnectionAdapters + module MySQL + module Quoting # :nodoc: + def quote_column_name(name) + self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`" + end + + def quote_table_name(name) + self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze + end + + def unquoted_true + 1 + end + + def unquoted_false + 0 + end + + def quoted_date(value) + if supports_datetime_with_precision? + super + else + super.sub(/\.\d{6}\z/, "") + end + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \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 + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # `table_name`.`column_name` | function(one or no argument) + ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\) + ) + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + # Override +_type_cast+ we pass to mysql2 Date and Time objects instead + # of Strings since mysql2 is able to handle those classes more efficiently. + def _type_cast(value) + 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 ActiveRecord::Base.default_timezone == :utc + value.getutc + else + value.getlocal + end + when Date, Time + value + else + super + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_creation.rb new file mode 100644 index 0000000000..60dd7846e2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -0,0 +1,97 @@ +# 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_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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb new file mode 100644 index 0000000000..52a8a0b97d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -0,0 +1,103 @@ +# 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) + + ## + # :method: unsigned_float + # :call-seq: unsigned_float(*names, **options) + + ## + # :method: unsigned_decimal + # :call-seq: unsigned_decimal(*names, **options) + + included do + define_column_methods :blob, :tinyblob, :mediumblob, :longblob, + :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint, + :unsigned_float, :unsigned_decimal + end + end + + 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 aliased_types(name, fallback) + fallback + end + + def integer_like_primary_key_type(type, options) + options[:auto_increment] = true + type + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb new file mode 100644 index 0000000000..0eff3131b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -0,0 +1,91 @@ +# 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) + super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0 + end + + def schema_collation(column) + if column.collation + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= + @connection.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_statements.rb new file mode 100644 index 0000000000..42ca9bc732 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -0,0 +1,271 @@ +# 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 + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) 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 row[:Expression] + expression = row[:Expression] + 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 + end + + indexes.map do |index| + options = index.pop + + if expressions = options.delete(:expressions) + orders = options.delete(:orders) + lengths = options.delete(:lengths) + + columns = index[-1].map { |name| + [ name.to_sym, expressions[name] || +quote_column_name(name) ] + }.to_h + + index[-1] = add_options_for_index_columns( + columns, order: orders, length: lengths + ).values.join(", ") + end + + IndexDefinition.new(*index, **options) + 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 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 + + 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 schema_creation + MySQL::SchemaCreation.new(self) + end + + def create_table_definition(name, **options) + MySQL::TableDefinition.new(self, name, **options) + end + + def new_column_from_field(table_name, 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_function = nil, default + elsif type_metadata.extra == "DEFAULT_GENERATED" + default = +"(#{default})" unless default.start_with?("(") + default, default_function = nil, default + elsif type_metadata.type == :text && default + # strip and unescape quotes + default = default[1...-1].gsub("\\'", "'") + 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 (SELECT table_name, table_type FROM information_schema.tables " + sql << " WHERE table_schema = #{scope[:schema]}) _subquery" + if scope[:type] || scope[:name] + conditions = [] + conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type] + conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name] + sql << " WHERE #{conditions.join(" AND ")}" + end + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql/type_metadata.rb new file mode 100644 index 0000000000..a7232fa249 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql2_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql2_adapter.rb new file mode 100644 index 0000000000..478b4e4729 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" + +gem "mysql2", "~> 0.5" +require "mysql2" + +module ActiveRecord + module ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects. + def mysql2_connection(config) + config = config.symbolize_keys + config[:flags] ||= 0 + + if config[:flags].kind_of? Array + config[:flags].push "FOUND_ROWS" + else + config[:flags] |= Mysql2::Client::FOUND_ROWS + end + + ConnectionAdapters::Mysql2Adapter.new( + ConnectionAdapters::Mysql2Adapter.new_client(config), + logger, + nil, + config, + ) + end + end + + module ConnectionAdapters + class Mysql2Adapter < AbstractMysqlAdapter + ER_BAD_DB_ERROR = 1049 + ADAPTER_NAME = "Mysql2" + + include MySQL::DatabaseStatements + + class << self + def new_client(config) + Mysql2::Client.new(config) + rescue Mysql2::Error => error + if error.error_number == ConnectionAdapters::Mysql2Adapter::ER_BAD_DB_ERROR + raise ActiveRecord::NoDatabaseError + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + end + end + + def initialize(connection, logger, connection_options, config) + superclass_config = config.reverse_merge(prepared_statements: false) + super(connection, logger, connection_options, superclass_config) + configure_connection + end + + def self.database_exists?(config) + !!ActiveRecord::Base.mysql2_connection(config) + rescue ActiveRecord::NoDatabaseError + false + 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 supports_lazy_transactions? + true + end + + # HELPER METHODS =========================================== + + def each_hash(result) # :nodoc: + if block_given? + result.each(as: :hash, symbolize_keys: true) do |row| + yield row + end + else + to_enum(:each_hash, result) + end + end + + def error_number(exception) + exception.error_number if exception.respond_to?(:error_number) + end + + #-- + # QUOTING ================================================== + #++ + + def quote_string(string) + @connection.escape(string) + rescue Mysql2::Error => error + raise translate_exception(error, message: error.message, sql: "", binds: []) + end + + #-- + # CONNECTION MANAGEMENT ==================================== + #++ + + def active? + @connection.ping + end + + def reconnect! + super + disconnect! + connect + end + alias :reset! :reconnect! + + # Disconnects from the database if already connected. + # Otherwise, this method does nothing. + def disconnect! + super + @connection.close + end + + def discard! # :nodoc: + super + @connection.automatic_close = false + @connection = nil + end + + private + def connect + @connection = self.class.new_client(@config) + configure_connection + end + + def configure_connection + @connection.query_options[:as] = :array + super + end + + def full_version + schema_cache.database_version.full_version_string + end + + def get_full_version + @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) + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_config.rb new file mode 100644 index 0000000000..681880a408 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_config.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolConfig # :nodoc: + include Mutex_m + + attr_reader :db_config, :connection_klass + attr_accessor :schema_cache + + INSTANCES = ObjectSpace::WeakMap.new + private_constant :INSTANCES + + class << self + def discard_pools! + INSTANCES.each_key(&:discard_pool!) + end + end + + def initialize(connection_klass, db_config) + super() + @connection_klass = connection_klass + @db_config = db_config + @pool = nil + INSTANCES[self] = self + end + + def connection_specification_name + if connection_klass.is_a?(String) + connection_klass + elsif connection_klass.primary_class? + "ActiveRecord::Base" + else + connection_klass.name + end + end + + def disconnect! + ActiveSupport::ForkTracker.check! + + return unless @pool + + synchronize do + return unless @pool + + @pool.automatic_reconnect = false + @pool.disconnect! + end + + nil + end + + def pool + ActiveSupport::ForkTracker.check! + + @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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_manager.rb new file mode 100644 index 0000000000..2ee0c3dc2a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/pool_manager.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolManager # :nodoc: + def initialize + @name_to_role_mapping = Hash.new { |h, k| h[k] = {} } + end + + def shard_names + @name_to_role_mapping.values.flat_map { |shard_map| shard_map.keys } + end + + def role_names + @name_to_role_mapping.keys + end + + def pool_configs(role = nil) + if role + @name_to_role_mapping[role].values + else + @name_to_role_mapping.flat_map { |_, shard_map| shard_map.values } + end + end + + def remove_role(role) + @name_to_role_mapping.delete(role) + end + + def remove_pool_config(role, shard) + @name_to_role_mapping[role].delete(shard) + end + + def get_pool_config(role, shard) + @name_to_role_mapping[role][shard] + end + + def set_pool_config(role, shard, pool_config) + if pool_config + @name_to_role_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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 0000000000..b9d10e9e2f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,53 @@ +# 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, **) + super + @serial = serial + end + + def serial? + @serial + end + + def array + sql_type_metadata.sql_type.end_with?("[]") + end + alias :array? :array + + def sql_type + super.delete_suffix("[]") + end + + def init_with(coder) + @serial = coder["serial"] + super + end + + def encode_with(coder) + coder["serial"] = @serial + super + end + + def ==(other) + other.is_a?(Column) && + super && + serial? == other.serial? + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + serial?.hash + end + end + end + PostgreSQLColumn = PostgreSQL::Column # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/database_statements.rb new file mode 100644 index 0000000000..278cf58ea6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module DatabaseStatements + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) + end + + # Queries the database and returns the results in an Array-like object + def query(sql, name = nil) #:nodoc: + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql).map_types!(@type_map_for_results).values + end + end + 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. + # 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(sql, name = nil) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql) + end + end + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + execute_and_clear(sql, name, binds, prepare: prepare) do |result| + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + case type = get_oid_type(ftype, fmod, fname) + when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean + # skip if a column has already been type casted by pg decoders + else types[fname] = type + end + end + build_result(columns: fields, rows: result.values, column_types: types) + end + end + + def exec_delete(sql, name = nil, binds = []) + execute_and_clear(sql, name, binds) { |result| result.cmd_tuples } + end + alias :exec_update :exec_delete + + def sql_for_insert(sql, pk, binds) # :nodoc: + 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 + + if pk = suppress_composite_primary_key(pk) + sql = "#{sql} RETURNING #{quote_column_name(pk)}" + end + + super + end + private :sql_for_insert + + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + if use_insert_returning? || pk == false + super + else + result = 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 + execute("BEGIN", "TRANSACTION") + end + + def begin_isolated_db_transaction(isolation) + begin_db_transaction + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + end + + # Commits a transaction. + def commit_db_transaction + execute("COMMIT", "TRANSACTION") + end + + # Aborts a transaction. + def exec_rollback_db_transaction + execute("ROLLBACK", "TRANSACTION") + end + + private + def execute_batch(statements, name = nil) + execute(combine_multi_statements(statements)) + 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) + exec_query("SELECT currval(#{quote(sequence_name)})", "SQL") + end + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 0000000000..086a5dcc15 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid.rb new file mode 100644 index 0000000000..1540b2ee28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid.rb @@ -0,0 +1,36 @@ +# 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/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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/array.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 0000000000..0bbe98145a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.map(&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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 0000000000..e9a79526f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb new file mode 100644 index 0000000000..dc7079dda2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 0000000000..a3c60ecef6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb new file mode 100644 index 0000000000..a7f61e2375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,48 @@ +# 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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 0000000000..0fe72e01ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,23 @@ +# 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 0000000000..8fa052968e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,31 @@ +# 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 0000000000..e7d33855c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 0000000000..bae34472e1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb new file mode 100644 index 0000000000..8d4dacbd64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Hstore < Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable + + def type + :hstore + end + + def deserialize(value) + if value.is_a?(::String) + ::Hash[value.scan(HstorePair).map { |k, v| + v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + [k, v] + }] + else + value + end + 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 + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 0000000000..55be71fd26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/interval.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/interval.rb new file mode 100644 index 0000000000..0cb686033b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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) + value.seconds.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb new file mode 100644 index 0000000000..e0216f1089 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb new file mode 100644 index 0000000000..687f75956b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb new file mode 100644 index 0000000000..13e77c6ab3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/money.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 0000000000..3703e9a646 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,41 @@ +# 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.gsub!(/[^-\d.]/, "") + when /^-?\D*+[\d.]+,\d{2}$/ # (2) + value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") + end + + super(value) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 0000000000..86d84b00ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/point.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/point.rb new file mode 100644 index 0000000000..f53b3fa602 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,64 @@ +# 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) + 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)) + 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 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/range.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 0000000000..64dafbd89f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,115 @@ +# 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 value == "empty" + 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(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 + + # 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 0000000000..80316b35fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 0000000000..203087bc36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,113 @@ +# 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_initial_load + known_type_names = @store.keys.map { |n| "'#{n}'" } + known_type_types = %w('r' 'e' 'd') + <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")] + WHERE + t.typname IN (%s) + OR t.typtype IN (%s) + OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure + OR t.typelem != 0 + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 0000000000..78f05429d8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,35 @@ +# 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} + + 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.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 + + private + def cast_value(value) + casted = value.to_s + casted if casted.match?(ACCEPTABLE_UUID) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 0000000000..88ef626a16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/oid/xml.rb new file mode 100644 index 0000000000..042f32fdc3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/quoting.rb new file mode 100644 index 0000000000..4db5f8f528 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Quoting + class IntegerOutOf64BitRange < StandardError + def initialize(msg) + super(msg) + end + end + + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + @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) + @connection.unescape_bytea(value) if value + end + + # Quotes strings for use in SQL input. + def quote_string(s) #:nodoc: + PG::Connection.escape(s) + 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: + self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze + end + + # Quotes schema names for use in SQL queries. + def quote_schema_name(name) + PG::Connection.quote_ident(name) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + # Quotes column names for use in SQL queries. + def quote_column_name(name) # :nodoc: + self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze + 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) && /\(\)/.match?(value) + 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 lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + + def column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \A + ( + (?: + # "table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)? + ) + (?:(?:\s+AS)?\s+(?:\w+|"\w+"))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # "table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)? + ) + (?:\s+ASC|\s+DESC)? + (?:\s+NULLS\s+(?:FIRST|LAST))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def lookup_cast_type(sql_type) + super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + 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::Base.raise_int_wider_than_64bit to false. + ERROR + raise IntegerOutOf64BitRange.new exception + end + end + + def _quote(value) + if ActiveRecord::Base.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 + + def _type_cast(value) + 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) + else + super + end + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb new file mode 100644 index 0000000000..dfb0029daf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -0,0 +1,43 @@ +# 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 0000000000..ad84b30a78 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < SchemaCreation # :nodoc: + private + def visit_AlterTable(o) + super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ") + end + + def visit_AddForeignKey(o) + super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? } + 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_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 add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new file mode 100644 index 0000000000..754cbbdd6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -0,0 +1,222 @@ +# 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) + + 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 + end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + attr_reader :unlogged + + def initialize(*, **) + super + @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables + end + + private + def integer_like_primary_key_type(type, options) + if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + attr_reader :constraint_validations + + def initialize(td) + super + @constraint_validations = [] + end + + def validate_constraint(name) + @constraint_validations << name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb new file mode 100644 index 0000000000..d201e40190 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -0,0 +1,49 @@ +# 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 prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb new file mode 100644 index 0000000000..f9cb5613ce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -0,0 +1,794 @@ +# 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_name, **options) # :nodoc: + schema_cache.clear_data_source_cache!(table_name.to_s) + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' 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 = i.relnamespace + WHERE i.relkind IN ('i', 'I') + AND i.relname = #{index[:name]} + AND t.relname = #{table[:name]} + AND n.nspname = #{index[: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 + 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 = i.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] + + using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten + + orders = {} + opclasses = {} + + if indkey.include?(0) + columns = expressions + else + columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + SQL + + # 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)?\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, + comment: comment.presence + ) + end + end + + def table_options(table_name) # :nodoc: + if comment = table_comment(table_name) + { comment: comment } + end + 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 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) + execute "CREATE SCHEMA #{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 + execute("SET search_path TO #{schema_csv}", "SCHEMA") + @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) + 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: + 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 >= 100000 + 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 ? 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' + 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) + 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 + idx = "#{table_name}_pkey" + new_idx = "#{new_name}_pkey" + execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" + if seq && seq.identifier == "#{table_name}_#{pk}_seq" + new_seq = "#{new_name}_#{pk}_seq" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" + end + end + rename_table_indexes(table_name, new_name) + 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 + + # 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 change_column_null(table_name, column_name, null, default = nil) #:nodoc: + 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)} #{change_column_null_for_alter(table_name, column_name, null, default)}" + 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: + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + + create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists) + result = execute schema_creation.accept(create_index) + + execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment + result + 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) + + execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" + end + + def foreign_keys(table_name) + scope = quoted_scope(table_name) + fk_info = exec_query(<<~SQL, "SCHEMA") + 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 + 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| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:validate] = row["valid"] + + ForeignKeyDefinition.new(table_name, row["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 = exec_query(<<-SQL, "SCHEMA") + SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + WHERE c.contype = 'c' + AND t.relname = #{scope[:name]} + SQL + + check_info.map do |row| + options = { + name: row["conname"], + validate: row["valid"] + } + expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1] + + CheckConstraintDefinition.new(table_name, expression, options) + end + end + + # Maps logical Rails types to PostgreSQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: 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 + 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 + + private + def schema_creation + PostgreSQL::SchemaCreation.new(self) + end + + 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) + column_name, type, default, notnull, oid, fmod, collation, comment = field + type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) + default_value = extract_value_from_default(default) + default_function = extract_default_function(default_value, default) + + 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 + ) + 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 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) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, **options) + sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))] + sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + sqls + end + + def change_column_default_for_alter(table_name, column_name, default_or_changes) + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s" + if default.nil? + # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will + # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". + alter_column_query % "DROP DEFAULT" + else + alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}" + end + end + + def change_column_null_for_alter(table_name, column_name, null, default = nil) + "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + 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 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/type_metadata.rb new file mode 100644 index 0000000000..b7f6479357 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/utils.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 0000000000..e8caeb8132 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,80 @@ +# 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 = unquote(schema), unquote(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 + + private + def unquote(part) + if part && part.start_with?('"') + part[1..-2] + else + part + end + 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 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql_adapter.rb new file mode 100644 index 0000000000..4cb41272bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,967 @@ +# 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 ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects + def postgresql_connection(config) + conn_params = config.symbolize_keys.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) + + ConnectionAdapters::PostgreSQLAdapter.new( + ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params), + logger, + conn_params, + config, + ) + end + end + + module ConnectionAdapters + # 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 user name. + # * :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] && error.message.include?(conn_params[:dbname]) + raise ActiveRecord::NoDatabaseError + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + 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 line to your test.rb + # file: + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + class_attribute :create_unlogged_tables, default: false + + NATIVE_DATABASE_TYPES = { + primary_key: "bigserial primary key", + string: { name: "character varying" }, + text: { name: "text" }, + integer: { name: "integer", limit: 4 }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "timestamp" }, + 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" }, + } + + 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 >= 110_000 + end + + def supports_partial_index? + true + 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_validate_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_insert_returning? + true + end + + def supports_insert_on_conflict? + database_version >= 90500 + 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 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) + @connection.query "DEALLOCATE #{key}" if connection_active? + rescue PG::Error + end + + def connection_active? + @connection.status == PG::CONNECTION_OK + rescue PG::Error + false + end + end + + # Initializes and connects a PostgreSQL adapter. + def initialize(connection, logger, connection_parameters, config) + super(connection, logger, config) + + @connection_parameters = connection_parameters || {} + + # @local_tz is initialized as nil to avoid warnings when connect tries to use it + @local_tz = nil + @max_identifier_length = nil + + configure_connection + add_pg_encoders + add_pg_decoders + + @type_map = Type::HashLookupTypeMap.new + initialize_type_map + @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"] + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true + end + + def self.database_exists?(config) + !!ActiveRecord::Base.postgresql_connection(config) + rescue ActiveRecord::NoDatabaseError + false + end + + # Is this connection alive and ready for queries? + def active? + @lock.synchronize do + @connection.query "SELECT 1" + end + true + rescue PG::Error + false + end + + # Close then reopen the connection. + def reconnect! + @lock.synchronize do + super + @connection.reset + configure_connection + rescue PG::ConnectionBad + connect + end + end + + def reset! + @lock.synchronize do + clear_cache! + reset_transaction + unless @connection.transaction_status == ::PG::PQTRANS_IDLE + @connection.query "ROLLBACK" + end + @connection.query "DISCARD ALL" + configure_connection + end + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + @lock.synchronize do + super + @connection.close rescue nil + end + end + + def discard! # :nodoc: + super + @connection.socket_io.reopen(IO::NULL) rescue nil + @connection = nil + end + + def native_database_types #:nodoc: + NATIVE_DATABASE_TYPES + end + + def set_standard_conforming_strings + 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 >= 90400 + 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) + exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { + reload_type_map + } + end + + def disable_extension(name) + exec_query("DROP EXTENSION IF EXISTS \"#{name}\" 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 + exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values + 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! + execute("SET SESSION AUTHORIZATION #{user}") + end + + def use_insert_returning? + @use_insert_returning + end + + # Returns the version of the connected PostgreSQL server. + def get_database_version # :nodoc: + @connection.server_version + 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 " + 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 + + sql << " RETURNING #{insert.returning}" if insert.returning + sql + end + + def check_version # :nodoc: + if database_version < 90300 + raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3." + end + end + + private + # 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) + ConnectionNotEstablished.new(exception) + else + super + end + when UNIQUE_VIOLATION + RecordNotUnique.new(message, sql: sql, binds: binds) + when FOREIGN_KEY_VIOLATION + InvalidForeignKey.new(message, sql: sql, binds: binds) + when VALUE_LIMIT_VIOLATION + ValueTooLong.new(message, sql: sql, binds: binds) + when NUMERIC_VALUE_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds) + when NOT_NULL_VIOLATION + NotNullViolation.new(message, sql: sql, binds: binds) + when SERIALIZATION_FAILURE + SerializationFailure.new(message, sql: sql, binds: binds) + when DEADLOCK_DETECTED + Deadlocked.new(message, sql: sql, binds: binds) + when DUPLICATE_DATABASE + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) + when LOCK_NOT_AVAILABLE + LockWaitTimeout.new(message, sql: sql, binds: binds) + when QUERY_CANCELED + QueryCanceled.new(message, sql: sql, binds: binds) + else + super + end + 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 initialize_type_map(m = type_map) + 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 + m.alias_type "float8", "float4" + 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.alias_type "timestamptz", "timestamp" + 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) + + register_class_with_precision m, "time", Type::Time + register_class_with_precision m, "timestamp", OID::DateTime + + 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 + + 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 + + def load_additional_types(oids = nil) + initializer = OID::TypeMapInitializer.new(type_map) + + 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 + query += "WHERE t.oid IN (%s)" % oids.join(", ") + else + query += initializer.query_conditions_for_initial_load + end + + execute_and_clear(query, "SCHEMA", []) do |records| + initializer.run(records) + end + end + + FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: + + def execute_and_clear(sql, name, binds, prepare: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + if !prepare || without_prepared_statement?(binds) + result = exec_no_cache(sql, name, binds) + else + result = exec_cache(sql, name, binds) + end + begin + ret = yield result + ensure + result.clear + end + ret + end + + def exec_no_cache(sql, name, binds) + materialize_transactions + mark_transaction_written_if_write(sql) + + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + update_typemap_for_default_timezone + + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_params(sql, type_casted_binds) + end + end + end + + def exec_cache(sql, name, binds) + materialize_transactions + mark_transaction_written_if_write(sql) + update_typemap_for_default_timezone + + stmt_key = prepare_statement(sql, binds) + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds, stmt_key) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_prepared(stmt_key, type_casted_binds) + end + end + rescue ActiveRecord::StatementInvalid => e + raise unless is_cached_plan_failure?(e) + + # Nothing we can do if we are in a transaction because all commands + # will raise InFailedSQLTransaction + if in_transaction? + raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message) + else + @lock.synchronize do + # outside of transactions we can simply flush this query and retry + @statements.delete sql_key(sql) + end + retry + end + end + + # 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?(e) + pgerror = e.cause + 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) + @lock.synchronize do + sql_key = sql_key(sql) + unless @statements.key? sql_key + nextkey = @statements.next_key + begin + @connection.prepare nextkey, sql + rescue => e + raise translate_exception_class(e, sql, binds) + end + # Clear the queue + @connection.get_last_result + @statements[sql_key] = nextkey + end + @statements[sql_key] + end + end + + # Connects to a PostgreSQL server and sets up the adapter depending on the + # connected server's characteristics. + def connect + @connection = self.class.new_client(@connection_parameters) + configure_connection + add_pg_encoders + add_pg_decoders + 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 + if @config[:encoding] + @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] + + # Use standard-conforming strings so we don't have to do the E'...' dance. + set_standard_conforming_strings + + variables = @config.fetch(:variables, {}).stringify_keys + + # If using Active Record's time zone support configure the connection to return + # TIMESTAMP WITH ZONE types in UTC. + unless variables["timezone"] + if ActiveRecord::Base.default_timezone == :utc + variables["timezone"] = "UTC" + elsif @local_tz + variables["timezone"] = @local_tz + end + end + + # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse + 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 + execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") + elsif !v.nil? + execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") + end + 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 + 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 extract_table_ref_from_insert_sql(sql) + sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im] + $1.strip if $1 + end + + def arel_visitor + Arel::Visitors::PostgreSQL.new(self) + end + + def build_statement_pool + StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit])) + end + + def can_perform_case_insensitive_comparison_for?(column) + @case_insensitive_cache ||= {} + @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 + execute_and_clear(sql, "SCHEMA", []) do |result| + 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 + @connection.type_map_for_queries = map + end + + def update_typemap_for_default_timezone + if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder + decoder_class = ActiveRecord::Base.default_timezone == :utc ? + PG::TextDecoder::TimestampUtc : + PG::TextDecoder::TimestampWithoutTimeZone + + @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h) + @connection.type_map_for_results.add_coder(@timestamp_decoder) + @default_timezone = ActiveRecord::Base.default_timezone + end + end + + def add_pg_decoders + @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, + } + + 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 + coders = execute_and_clear(query, "SCHEMA", []) do |result| + result + .map { |row| construct_coder(row, coders_by_name[row["typname"]]) } + .compact + end + + map = PG::TypeMapByOid.new + coders.each { |coder| map.add_coder(coder) } + @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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/schema_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/schema_cache.rb new file mode 100644 index 0000000000..84a559562b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/schema_cache.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" + +module ActiveRecord + module ConnectionAdapters + class SchemaCache + def self.load_from(filename) + 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 + + attr_reader :version + attr_accessor :connection + + def initialize(conn) + @connection = conn + + @columns = {} + @columns_hash = {} + @primary_keys = {} + @data_sources = {} + @indexes = {} + end + + def initialize_dup(other) + 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) + reset_version! + + coder["columns"] = @columns + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["indexes"] = @indexes + coder["version"] = @version + coder["database_version"] = database_version + end + + def init_with(coder) + @columns = coder["columns"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @indexes = coder["indexes"] || {} + @version = coder["version"] + @database_version = coder["database_version"] + + derive_columns_hash_and_deduplicate_values + end + + def primary_keys(table_name) + @primary_keys.fetch(table_name) do + if data_source_exists?(table_name) + @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name)) + end + end + end + + # A cached lookup for table existence. + def data_source_exists?(name) + prepare_data_sources if @data_sources.empty? + return @data_sources[name] if @data_sources.key? name + + @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name) + end + + # Add internal cache for table with +table_name+. + def add(table_name) + if data_source_exists?(table_name) + primary_keys(table_name) + columns(table_name) + columns_hash(table_name) + indexes(table_name) + end + end + + def data_sources(name) + @data_sources[name] + end + + # Get the columns for a table + def columns(table_name) + @columns.fetch(table_name) do + @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name)) + end + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(table_name) + @columns_hash.fetch(table_name) do + @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze + end + end + + # Checks whether the columns hash is already cached for a table. + def columns_hash?(table_name) + @columns_hash.key?(table_name) + end + + def indexes(table_name) + @indexes.fetch(table_name) do + @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name)) + end + end + + def database_version # :nodoc: + @database_version ||= connection.get_database_version + end + + # Clears out internal caches + def clear! + @columns.clear + @columns_hash.clear + @primary_keys.clear + @data_sources.clear + @indexes.clear + @version = nil + @database_version = nil + 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!(name) + @columns.delete name + @columns_hash.delete name + @primary_keys.delete name + @data_sources.delete name + @indexes.delete name + end + + def dump_to(filename) + clear! + connection.data_sources.each { |table| add(table) } + open(filename) { |f| + if filename.include?(".dump") + f.write(Marshal.dump(self)) + else + f.write(YAML.dump(self)) + end + } + end + + def marshal_dump + reset_version! + + [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version] + end + + def marshal_load(array) + @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array + @indexes ||= {} + + derive_columns_hash_and_deduplicate_values + end + + private + def reset_version! + @version = connection.migration_context.current_version + 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 + + if RUBY_VERSION < "2.7" + 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 + if value.tainted? + # Ruby 2.6 and 2.7 have slightly different implementations of the String#-@ method. + # In Ruby 2.6, the receiver of the String#-@ method is modified under certain + # circumstances, and this was later identified as a bug + # (https://bugs.ruby-lang.org/issues/15926) and only fixed in Ruby 2.7. + value = value.dup + end + -value + when Deduplicable + -value + else + value + end + end + else + 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 + end + + def prepare_data_sources + connection.data_sources.each { |source| @data_sources[source] = true } + end + + def open(filename) + File.atomic_write(filename) do |file| + if File.extname(filename) == ".gz" + zipper = Zlib::GzipWriter.new file + yield zipper + zipper.flush + zipper.close + else + yield file + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sql_type_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sql_type_metadata.rb new file mode 100644 index 0000000000..63f07b915d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb new file mode 100644 index 0000000000..582f9f4e0a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb @@ -0,0 +1,146 @@ +# 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 = []) + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) + end + + def execute(sql, name = nil) #:nodoc: + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute(sql) + end + end + end + + def exec_query(sql, name = nil, binds = [], prepare: false) + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + # Don't cache statements if they are not prepared + unless prepare + stmt = @connection.prepare(sql) + begin + cols = stmt.columns + unless without_prepared_statement?(binds) + stmt.bind_params(type_casted_binds) + end + records = stmt.to_a + ensure + stmt.close + end + else + stmt = @statements[sql] ||= @connection.prepare(sql) + cols = stmt.columns + stmt.reset! + stmt.bind_params(type_casted_binds) + records = stmt.to_a + end + + build_result(columns: cols, rows: records) + end + end + end + + def exec_delete(sql, name = "SQL", binds = []) + exec_query(sql, name, binds) + @connection.changes + end + alias :exec_update :exec_delete + + def begin_isolated_db_transaction(isolation) #:nodoc + 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? + + Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted")) + @connection.read_uncommitted = true + begin_db_transaction + end + + def begin_db_transaction #:nodoc: + log("begin transaction", "TRANSACTION") { @connection.transaction } + end + + def commit_db_transaction #:nodoc: + log("commit transaction", "TRANSACTION") { @connection.commit } + reset_read_uncommitted + end + + def exec_rollback_db_transaction #:nodoc: + log("rollback transaction", "TRANSACTION") { @connection.rollback } + reset_read_uncommitted + end + + private + def reset_read_uncommitted + read_uncommitted = Thread.current.thread_variable_get("read_uncommitted") + return unless read_uncommitted + + @connection.read_uncommitted = read_uncommitted + end + + def execute_batch(statements, name = nil) + sql = combine_multi_statements(statements) + + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + + materialize_transactions + mark_transaction_written_if_write(sql) + + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute_batch2(sql) + end + end + end + + def last_inserted_id(result) + @connection.last_insert_row_id + end + + def build_fixture_statements(fixture_set) + fixture_set.flat_map do |table_name, fixtures| + next if fixtures.empty? + fixtures.map { |fixture| build_fixture_sql([fixture], table_name) } + end.compact + end + + def build_truncate_statement(table_name) + "DELETE FROM #{quote_table_name(table_name)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 0000000000..832fdfe5c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/quoting.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/quoting.rb new file mode 100644 index 0000000000..9b74a774e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module Quoting # :nodoc: + def quote_string(s) + @connection.class.quote(s) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + def quote_table_name(name) + self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze + end + + def quote_column_name(name) + self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}") + 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 column_name_matcher + COLUMN_NAME + end + + def column_name_with_order_matcher + COLUMN_NAME_WITH_ORDER + end + + COLUMN_NAME = / + \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 + + COLUMN_NAME_WITH_ORDER = / + \A + ( + (?: + # "table_name"."column_name" | function(one or no argument) + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\) + ) + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + + private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER + + private + def _type_cast(value) + case value + when BigDecimal + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb new file mode 100644 index 0000000000..c461eeed5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaCreation < SchemaCreation # :nodoc: + private + def supports_index_using? + false + end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb new file mode 100644 index 0000000000..c9855019c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + + private + def integer_like_primary_key_type(type, options) + :primary_key + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb new file mode 100644 index 0000000000..621678ec65 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def default_primary_key?(column) + schema_type(column) == :integer + end + + def explicit_primary_key_default?(column) + column.bigint? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb new file mode 100644 index 0000000000..d9698b01ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -0,0 +1,170 @@ +# 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) + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").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*(?.+))?\z/i =~ index_sql + + columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end + + 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.compact + end + + def add_foreign_key(from_table, to_table, **options) + 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) + 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) + 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 check_constraints(table_name) + table_sql = query_value(<<-SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote_table_name(table_name)} AND type = 'table' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote_table_name(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, **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 + + private + def schema_creation + SQLite3::SchemaCreation.new(self) + 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) + default = \ + case field["dflt_value"] + when /^null$/i + nil + when /^'(.*)'$/m + $1.gsub("''", "'") + when /^"(.*)"$/m + $1.gsub('""', '"') + else + field["dflt_value"] + end + + type_metadata = fetch_type_metadata(field["type"]) + Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"]) + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'table','view'" + + sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'" + 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'" + end + scope = {} + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3_adapter.rb new file mode 100644 index 0000000000..6bba324cec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -0,0 +1,561 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +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", "~> 1.4" +require "sqlite3" + +module ActiveRecord + module ConnectionHandling # :nodoc: + def sqlite3_connection(config) + config = config.symbolize_keys + + # Require database. + unless config[:database] + raise ArgumentError, "No database file specified. Missing argument: database" + end + + # Allow database path relative to Rails.root, but only if the database + # path is not the special path that tells sqlite to build a database only + # in memory. + if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:") + config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) + dirname = File.dirname(config[:database]) + Dir.mkdir(dirname) unless File.directory?(dirname) + end + + db = SQLite3::Database.new( + config[:database].to_s, + config.merge(results_as_hash: true) + ) + + ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end + + module ConnectionAdapters #:nodoc: + # 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" + + include SQLite3::Quoting + include SQLite3::SchemaStatements + include SQLite3::DatabaseStatements + + 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" }, + } + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + private + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + configure_connection + end + + def self.database_exists?(config) + config = config.symbolize_keys + if config[:database] == ":memory:" + true + else + database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database] + File.exist?(database_file) + end + 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_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 active? + !@connection.closed? + end + + def reconnect! + super + connect if @connection.closed? + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + super + @connection.close rescue 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, eg: 'UTF-8' + def encoding + @connection.encoding.to_s + end + + def supports_explain? + true + end + + def supports_lazy_transactions? + 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 + + # 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 + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_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) + end + + def add_column(table_name, column_name, type, **options) #:nodoc: + 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 do |_, fk_options| + fk_options[:column] == column_name.to_s + end + 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: + unless null || default.nil? + 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[column_name].instance_eval do + self.type = aliased_types(type.to_s, type) + self.options.merge!(options) + end + 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_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + def foreign_keys(table_name) + fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") + fk_info.map do |row| + options = { + column: row["from"], + primary_key: row["to"], + on_delete: extract_foreign_key_action(row["on_delete"]), + on_update: extract_foreign_key_action(row["on_update"]) + } + 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 " + sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" } + sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",") + end + + 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(*)")) + 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 + + private + # See https://www.sqlite.org/limits.html, + # the default value is 999 when not configured. + def bind_params_length + 999 + end + + def initialize_type_map(m = type_map) + super + register_class_with_limit m, %r(int)i, SQLite3Integer + end + + def table_structure(table_name) + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") + raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? + table_structure_with_collation(table_name, structure) + end + alias column_definitions table_structure + + # 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.to_sym == :primary_key || options[:primary_key] || + options[:null] == false && options[:default].nil? + 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 + + @definition.column(column_name, column.type, + limit: column.limit, default: column.default, + precision: column.precision, scale: column.scale, + null: column.null, collation: column.collation, + primary_key: column_name == from_primary_key + ) + end + + yield @definition if block_given? + end + copy_table_indexes(from, to, options[:rename] || {}) + copy_table_contents(from, to, + @definition.columns.map(&:name), + 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 + 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) } * "," + + 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) + elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i) + NotNullViolation.new(message, sql: sql, binds: binds) + elsif exception.message.match?(/FOREIGN KEY constraint failed/i) + InvalidForeignKey.new(message, sql: sql, binds: binds) + elsif exception.message.match?(/called on a closed database/i) + ConnectionNotEstablished.new(exception) + else + super + end + end + + COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze + + def table_structure_with_collation(table_name, basic_structure) + collation_hash = {} + 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"); + result = query_value(sql, "SCHEMA") + + if result + # Splitting with left parentheses and discarding the first part will return all + # columns separated with comma(,). + columns_string = result.split("(", 2).last + + columns_string.split(",").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 + end + + basic_structure.map do |column| + column_name = column["name"] + + if collation_hash.has_key? column_name + column["collation"] = collation_hash[column_name] + end + + column + end + else + basic_structure.to_a + 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 + @connection = ::SQLite3::Database.new( + @config[:database].to_s, + @config.merge(results_as_hash: true) + ) + configure_connection + end + + def configure_connection + @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout] + + execute("PRAGMA foreign_keys = ON", "SCHEMA") + 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) + end + ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/statement_pool.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 0000000000..0960feed84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,60 @@ +# 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 + + def delete(key) + dealloc cache[key] + cache.delete(key) + end + + private + def cache + @cache[Process.pid] + end + + def dealloc(stmt) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_handling.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_handling.rb new file mode 100644 index 0000000000..a7a01a30c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_handling.rb @@ -0,0 +1,405 @@ +# frozen_string_literal: true + +module ActiveRecord + 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, owner_name = resolve_config_for_connection(config_or_env) + connection_handler.establish_connection(db_config, owner_name: owner_name, 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 create a connection handler for switching between connections, + # look up the config hash using the +database_key+ and finally + # establishes 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 + # also supports read replicas. 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 = [] + + database.each do |role, database_key| + db_config, owner_name = resolve_config_for_connection(database_key) + handler = lookup_connection_handler(role.to_sym) + + self.connection_class = true + connections << handler.establish_connection(db_config, owner_name: owner_name, role: role) + end + + shards.each do |shard, database_keys| + database_keys.each do |role, database_key| + db_config, owner_name = resolve_config_for_connection(database_key) + handler = lookup_connection_handler(role.to_sym) + + self.connection_class = true + connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym) + end + end + + connections + end + + # Connects to a role (ex 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 + # + # The database kwarg is deprecated and will be removed in Rails 7.0.0 without replacement. + def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk) + if legacy_connection_handling + if self != Base + raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling." + end + else + if self != Base && !abstract_class + raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes." + end + + if name != connection_specification_name && !primary_class? + raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection." + end + end + + if database && (role || shard) + raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments." + elsif database + ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 7.0.0 without replacement.") + + if database.is_a?(Hash) + role, database = database.first + role = role.to_sym + end + + db_config, owner_name = resolve_config_for_connection(database) + handler = lookup_connection_handler(role) + + handler.establish_connection(db_config, owner_name: owner_name, role: role) + + with_handler(role, &blk) + elsif role || shard + unless role + raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`." + end + + with_role_and_shard(role, shard, prevent_writes, &blk) + else + raise ArgumentError, "must provide a `shard` and/or `role`." + end + 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 legacy_connection_handling + raise NotImplementedError, "connected_to_many is not available with legacy connection handling" + end + + if self != Base || classes.include?(Base) + raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base." + end + + prevent_writes = true if role == reading_role + + connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes } + yield + ensure + connected_to_stack.pop + 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) + if legacy_connection_handling + raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`." + end + + prevent_writes = true if role == reading_role + + self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] } + 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) + if legacy_connection_handling + connection_handler.while_preventing_writes(enabled, &block) + else + connected_to(role: current_role, prevent_writes: enabled, &block) + end + end + + # Returns true if role is the current connected role. + # + # ActiveRecord::Base.connected_to(role: :writing) do + # ActiveRecord::Base.connected_to?(role: :writing) #=> true + # ActiveRecord::Base.connected_to?(role: :reading) #=> false + # end + def connected_to?(role:, shard: ActiveRecord::Base.default_shard) + current_role == role.to_sym && current_shard == shard.to_sym + end + + def lookup_connection_handler(handler_key) # :nodoc: + if ActiveRecord::Base.legacy_connection_handling + handler_key ||= ActiveRecord::Base.writing_role + connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new + else + ActiveRecord::Base.connection_handler + end + end + + # Clears the query cache for all connections associated with the current thread. + def clear_query_caches_for_current_thread + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each_value do |handler| + clear_on_handler(handler) + end + else + clear_on_handler(ActiveRecord::Base.connection_handler) + 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. + def connection + retrieve_connection + end + + attr_writer :connection_specification_name + + # Return the connection specification name from the current class or its parent. + def connection_specification_name + if !defined?(@connection_specification_name) || @connection_specification_name.nil? + return self == Base ? Base.name : superclass.connection_specification_name + end + @connection_specification_name + end + + def primary_class? # :nodoc: + self == Base || defined?(ApplicationRecord) && self == ApplicationRecord + end + + # Returns the configuration of the associated connection as a hash: + # + # ActiveRecord::Base.connection_config + # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"} + # + # Please use only for reading. + def connection_config + connection_pool.db_config.configuration_hash + end + deprecate connection_config: "Use connection_db_config instead" + + # 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 connection_pool + connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished) + 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 = nil) + 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 clear_cache! # :nodoc: + connection.schema_cache.clear! + end + + delegate :clear_active_connections!, :clear_reloadable_connections!, + :clear_all_connections!, :flush_idle_connections!, to: :connection_handler + + private + def clear_on_handler(handler) + handler.all_connection_pools.each do |pool| + pool.connection.clear_query_cache if pool.active_connection? + end + end + + def resolve_config_for_connection(config_or_env) + raise "Anonymous class is not allowed." unless name + + owner_name = primary_class? ? Base.name : name + self.connection_specification_name = owner_name + + db_config = Base.configurations.resolve(config_or_env) + [db_config, self] + end + + def with_handler(handler_key, &blk) + handler = lookup_connection_handler(handler_key) + swap_connection_handler(handler, &blk) + end + + def with_role_and_shard(role, shard, prevent_writes) + prevent_writes = true if role == reading_role + + if ActiveRecord::Base.legacy_connection_handling + with_handler(role.to_sym) do + connection_handler.while_preventing_writes(prevent_writes) do + self.connected_to_stack << { shard: shard, klasses: [self] } + yield + end + end + else + self.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 + end + ensure + self.connected_to_stack.pop + end + + def swap_connection_handler(handler, &blk) # :nodoc: + old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler + return_value = yield + return_value.load if return_value.is_a? ActiveRecord::Relation + return_value + ensure + ActiveRecord::Base.connection_handler = old_handler + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/core.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/core.rb new file mode 100644 index 0000000000..d3bfd4929e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/core.rb @@ -0,0 +1,811 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/filters" +require "active_support/parameter_filter" +require "concurrent/map" + +module ActiveRecord + module Core + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # + # Accepts a logger conforming to the interface of Log4r which is then + # passed on to any new database connections made and which can be + # retrieved on both a class and instance level by calling +logger+. + mattr_accessor :logger, instance_writer: false + + ## + # :singleton-method: + # + # Specifies if the methods calling database queries should be logged below + # their relevant queries. Defaults to false. + mattr_accessor :verbose_query_logs, instance_writer: false, default: false + + ## + # :singleton-method: + # + # Specifies the names of the queues used by background jobs. + mattr_accessor :queues, instance_accessor: false, default: {} + + ## + # :singleton-method: + # + # Specifies the job used to destroy associations in the background + class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false + + ## + # 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: db/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: db/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 fully resolved ActiveRecord::DatabaseConfigurations object + def self.configurations + @@configurations + end + + ## + # :singleton-method: + # 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. + mattr_accessor :default_timezone, instance_writer: false, default: :utc + + ## + # :singleton-method: + # 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. + mattr_accessor :schema_format, instance_writer: false, default: :ruby + + ## + # :singleton-method: + # 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. + mattr_accessor :error_on_ignored_order, instance_writer: false, default: false + + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + mattr_accessor :timestamped_migrations, instance_writer: false, default: true + + ## + # :singleton-method: + # Specify whether schema dump should happen at the end of the + # db:migrate rails 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. + mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true + + ## + # :singleton-method: + # 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. + mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path + + ## + # :singleton-method: + # Specify a threshold for the size of query result sets. If the number of + # records in the set exceeds the threshold, a warning is logged. This can + # be used to identify queries which load thousands of records and + # potentially cause memory bloat. + mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false + + ## + # :singleton-method: + # Show a warning when Rails couldn't parse your database.yml + # for multiple databases. + mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false + + mattr_accessor :maintain_test_schema, instance_accessor: false + + class_attribute :belongs_to_required_by_default, instance_accessor: false + + ## + # :singleton-method: + # Set the application to log or raise when an association violates strict loading. + # Defaults to :raise. + mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise + + class_attribute :strict_loading_by_default, instance_accessor: false, default: false + + mattr_accessor :writing_role, instance_accessor: false, default: :writing + + mattr_accessor :reading_role, instance_accessor: false, default: :reading + + mattr_accessor :has_many_inversing, instance_accessor: false, default: false + + class_attribute :default_connection_handler, instance_writer: false + + class_attribute :default_role, instance_writer: false + + class_attribute :default_shard, instance_writer: false + + mattr_accessor :legacy_connection_handling, instance_writer: false, default: true + + # Application configurable boolean that instructs the YAML Coder to use + # an unsafe load if set to true. + mattr_accessor :use_yaml_unsafe_load, instance_writer: false, default: false + + # Application configurable array that provides additional permitted classes + # to Psych safe_load in the YAML Coder + mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: [Symbol] + + ## + # :singleton-method: + # 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 + mattr_accessor :raise_int_wider_than_64bit, instance_writer: false, default: true + + self.filter_attributes = [] + + def self.connection_handler + Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler + end + + def self.connection_handler=(handler) + Thread.current.thread_variable_set(:ar_connection_handler, handler) + end + + def self.connection_handlers + unless legacy_connection_handling + raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers." + end + + @@connection_handlers ||= {} + end + + def self.connection_handlers=(handlers) + unless legacy_connection_handling + raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers." + end + + @@connection_handlers = handlers + 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 + if ActiveRecord::Base.legacy_connection_handling + connection_handlers.key(connection_handler) || default_role + else + 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_classes) + end + + default_role + end + 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_classes) + 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 + if legacy_connection_handling + connection_handler.prevent_writes + else + 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_classes) + end + + false + end + end + + def self.connected_to_stack # :nodoc: + if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack) + connected_to_stack + else + connected_to_stack = Concurrent::Array.new + Thread.current.thread_variable_set(:ar_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_classes # :nodoc: + klass = self + + until klass == Base + break if klass.connection_class? + klass = klass.superclass + end + + klass + end + + def self.allow_unsafe_raw_sql # :nodoc: + ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 7.0") + end + + def self.allow_unsafe_raw_sql=(value) # :nodoc: + ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 7.0") + end + + self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new + self.default_role = writing_role + self.default_shard = :default + + def self.strict_loading_violation!(owner:, reflection:) # :nodoc: + case action_on_strict_loading_violation + when :raise + message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded." + 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 inherited(child_class) # :nodoc: + # initialize cache at class definition for thread safety + child_class.initialize_find_by_cache + unless child_class.base_class? + klass = self + until klass.base_class? + klass.initialize_find_by_cache + klass = klass.superclass + end + end + super + 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) + + key = primary_key + + statement = cached_find_by_statement(key) { |params| + where(key => params.bind).limit(1) + } + + statement.execute([id], connection).first || + raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, 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 + value = value.public_send(pkey) if value.respond_to?(pkey) + end + + if !columns_hash.key?(key) || StatementCache.unsupported_value?(value) + return super + end + + h[key] = value + end + + keys = hash.keys + statement = cached_find_by_statement(keys) { |params| + wheres = keys.index_with { params.bind } + where(wheres).limit(1) + } + + begin + statement.execute(hash.values, connection).first + rescue TypeError + raise ActiveRecord::StatementInvalid + end + end + + def find_by!(*args) # :nodoc: + find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name)) + 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 defined?(@filter_attributes) + @filter_attributes + else + superclass.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 defined?(@filter_attributes) + @inspection_filter ||= begin + mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED) + ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask) + end + else + superclass.inspection_filter + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect # :nodoc: + if self == Base + super + elsif abstract_class? + "#{super}(abstract)" + elsif !connected? + "#{super} (call '#{super}.connection' to establish a connection)" + elsif table_exists? + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", " + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Overwrite the default class equality method to provide support for decorated models. + def ===(object) # :nodoc: + object.is_a?(self) + end + + # Returns an instance of Arel::Table loaded with the current table name. + # + # class Post < ActiveRecord::Base + # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) } + # end + def arel_table # :nodoc: + @arel_table ||= Arel::Table.new(table_name, klass: self) + end + + def arel_attribute(name, table = arel_table) # :nodoc: + table[name] + end + deprecate :arel_attribute + + def predicate_builder # :nodoc: + @predicate_builder ||= PredicateBuilder.new(table_metadata) + end + + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + + def _internal? # :nodoc: + false + end + + def cached_find_by_statement(key, &block) # :nodoc: + cache = @find_by_statement_cache[connection.prepared_statements] + cache.compute_if_absent(key) { StatementCache.create(connection, &block) } + end + + private + def relation + relation = Relation.create(self) + + if finder_needs_type_condition? && !ignore_default_scope? + relation.where!(type_condition) + else + relation + end + end + + def table_metadata + TableMetadata.new(self, arel_table) + 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 + + assign_attributes(attributes) if attributes + + 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(self.class, 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). + + ## + def initialize_dup(other) # :nodoc: + @attributes = @attributes.deep_dup + @attributes.reset(@primary_key) + + _run_initialize_callbacks + + @new_record = true + @previously_new_record = false + @destroyed = false + @_start_transaction_state = nil + + super + 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 + + # 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) && + !id.nil? && + 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 + if id + 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! + # user.comments.to_a + # => ActiveRecord::StrictLoadingViolationError + def strict_loading! + @strict_loading = true + end + + # Marks this record as read only. + def readonly! + @readonly = true + end + + def connection_handler + self.class.connection_handler + end + + # Returns the contents of the record as a nicely formatted string. + def inspect + # We check defined?(@attributes) not to issue warnings if the object is + # allocated but not initialized. + inspection = if defined?(@attributes) && @attributes + self.class.attribute_names.collect do |name| + if _has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + end.compact.join(", ") + else + "not initialized" + end + + "#<#{self.class} #{inspection}>" + 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 defined?(@attributes) && @attributes + attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) } + pp.seplist(attr_names, proc { pp.text "," }) do |attr_name| + pp.breakable " " + pp.group(1) do + pp.text attr_name + pp.text ":" + pp.breakable + value = _read_attribute(attr_name) + value = inspection_filter.filter_param(attr_name, value) unless value.nil? + pp.pp value + end + end + else + pp.breakable " " + pp.text "not initialized" + end + end + end + + # Returns a hash of the given methods with their names as keys and returned values as values. + def slice(*methods) + methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access + end + + # Returns an array of the values returned by the given methods. + def values_at(*methods) + methods.flatten.map! { |method| public_send(method) } + 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 + @primary_key = self.class.primary_key + @readonly = false + @previously_new_record = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @_start_transaction_state = nil + @strict_loading = self.class.strict_loading_by_default + + self.class.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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/counter_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/counter_cache.rb new file mode 100644 index 0000000000..2d91bd3da5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/counter_cache.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Counter Cache + module CounterCache + extend ActiveSupport::Concern + + 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) + + 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 + + updates = { counter_name => object.send(counter_association).count(:all) } + + 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) + end + + 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 comment_count by 1, and + # # increment the action_count by 1 + # Post.update_counters 5, comment_count: -1, action_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) - 1, + # # action_count = COALESCE(action_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], comment_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1 + # # WHERE id IN (10, 15) + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # # and update the updated_at value for each counter. + # Post.update_counters [10, 15], comment_count: 1, touch: true + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1, + # # `updated_at` = '2016-10-13T09:59:23-05:00' + # # WHERE id IN (10, 15) + def update_counters(id, counters) + 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. + # * :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 + # # and update the updated_at value. + # DiscussionBoard.increment_counter(:posts_count, 5, touch: true) + def increment_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => 1, 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. + # * :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 + # # and update the updated_at value. + # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true) + def decrement_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => -1, touch: touch) + end + end + + private + def _create_record(attribute_names = self.attribute_names) + id = super + + each_counter_cached_associations do |association| + association.increment_counters + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + each_counter_cached_associations do |association| + foreign_key = association.reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + association.decrement_counters + end + end + end + + affected_rows + end + + def each_counter_cached_associations + _reflections.each do |name, reflection| + yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations.rb new file mode 100644 index 0000000000..75e0e41673 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations.rb @@ -0,0 +1,273 @@ +# 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 + # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig + # objects (either a HashConfig or UrlConfig) that are constructed from the + # application's database configuration hash or URL string. + class DatabaseConfigurations + class InvalidConfigurationError < StandardError; end + + attr_reader :configurations + delegate :any?, to: :configurations + + 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_replicas: 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. + # * include_replicas: Determines whether to include replicas in + # the returned list. Most of the time we're only iterating over the write + # connection (i.e. migrations don't need to run for the write and read connection). + # Defaults to +false+. + def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false) + if spec_name + name = spec_name + ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0") + end + + env_name ||= default_env if name + configs = env_with_configs(env_name) + + unless include_replicas + configs = configs.select do |db_config| + !db_config.replica? + end + end + + if name + configs.find do |db_config| + db_config.name == name + end + else + configs + end + end + + # Returns the config hash that corresponds with the environment + # + # If the application has multiple databases +default_hash+ will + # return the first config hash for the environment. + # + # { database: "my_db", adapter: "mysql2" } + def default_hash(env = default_env) + default = find_db_config(env) + default.configuration_hash if default + end + alias :[] :default_hash + deprecate "[]": "Use configs_for", default_hash: "Use configs_for" + + # 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) + configurations + .sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] } + .find do |db_config| + db_config.env_name == env.to_s || + (db_config.for_current_env? && db_config.name == env.to_s) + 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 + + # Returns the DatabaseConfigurations object as a Hash. + def to_h + configurations.inject({}) do |memo, db_config| + memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys) + end + end + deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes." + + # Checks if the application's configurations are empty. + # + # Aliased to blank? + 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.all? { |_, v| v.is_a?(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 databases configurations are: + + #{build_configuration_sentence} + MSG + end + end + + def build_configuration_sentence + configs = configs_for(include_replicas: 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) + if config.has_key?(:url) + url = config[:url] + config_without_url = config.dup + config_without_url.delete :url + + UrlConfig.new(env_name, name, url, config_without_url) + else + HashConfig.new(env_name, name, config) + end + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/connection_url_resolver.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/connection_url_resolver.rb new file mode 100644 index 0000000000..530a4ea660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/connection_url_resolver.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require "uri" +require "active_support/core_ext/enumerable" + +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 = @uri.scheme && @uri.scheme.tr("-", "_") + @adapter = "postgresql" if @adapter == "postgres" + + 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::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.merge( + adapter: @adapter, + username: uri.user, + password: uri.password, + port: uri.port, + database: database_from_path, + host: uri.hostname + ) + end + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/database_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/database_config.rb new file mode 100644 index 0000000000..6ad9b3922e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/database_config.rb @@ -0,0 +1,80 @@ +# 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 + + attr_accessor :owner_name + + def initialize(env_name, name) + @env_name = env_name + @name = name + end + + def spec_name + @name + end + deprecate spec_name: "please use name instead" + + def config + raise NotImplementedError + end + + def adapter_method + "#{adapter}_connection" + 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 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/hash_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/hash_config.rb new file mode 100644 index 0000000000..100fdcdb3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/hash_config.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # A HashConfig object is created for each database configuration entry that + # is created from a hash. + # + # A hash config: + # + # { "development" => { "database" => "db_name" } } + # + # Becomes: + # + # # + # + # ==== 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". + # * :config - The config hash. This is the hash that contains the + # database adapter, name, and other important information for database + # connections. + class HashConfig < DatabaseConfig + attr_reader :configuration_hash + def initialize(env_name, name, configuration_hash) + super(env_name, name) + @configuration_hash = configuration_hash.symbolize_keys.freeze + end + + def config + ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfig#configuration_hash which returns a hash with symbol keys") + configuration_hash.stringify_keys + 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 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 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] + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/url_config.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/url_config.rb new file mode 100644 index 0000000000..a8ee4f64fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/database_configurations/url_config.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # 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: + # + # # + # + # ==== Options + # + # * :env_name - The Rails environment, ie "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. + class UrlConfig < HashConfig + attr_reader :url + + def initialize(env_name, name, url, configuration_hash = {}) + super(env_name, name, configuration_hash) + + @url = url + @configuration_hash = @configuration_hash.merge(build_url_hash).freeze + end + + private + # Return a Hash that can be merged into the main config that represents + # the passed in url + def build_url_hash + if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) } + { url: url } + else + ConnectionUrlResolver.new(url).to_hash + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/delegated_type.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/delegated_type.rb new file mode 100644 index 0000000000..4a4d27fb29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/delegated_type.rb @@ -0,0 +1,209 @@ +# 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, created_at, updated_at, entryable_type, entryable_id ] + # 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 ] + # class Message < ApplicationRecord + # include Entryable + # has_rich_text :content + # end + # + # # Schema: comments[ id, content ] + # 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.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 + #
+ # Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %> + #
+ # + # # 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 + # + # 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) + # create! entryable: Comment.new(content: content), creator: creator + # end + # end + # + # == 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. + 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 + # + # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc. + # + # 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? + def delegated_type(role, types:, **options) + belongs_to role, options.delete(:scope), **options.merge(polymorphic: true) + define_delegated_type_methods role, types: types + end + + private + def define_delegated_type_methods(role, types:) + role_type = "#{role}_type" + role_id = "#{role}_id" + + 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 + + types.each do |type| + scope_name = type.tableize.gsub("/", "_") + 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}_id" do + public_send(role_id) if public_send(query) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/destroy_association_async_job.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/destroy_association_async_job.rb new file mode 100644 index 0000000000..e4ccaceb86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/destroy_association_async_job.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveRecord + class DestroyAssociationAsyncError < StandardError + end + + # Job to destroy the records associated with a destroyed record in background. + class DestroyAssociationAsyncJob < ActiveJob::Base + queue_as { ActiveRecord::Base.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.to_sym => 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/dynamic_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/dynamic_matchers.rb new file mode 100644 index 0000000000..7d9e221faa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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, *arguments, &block) + match = Method.match(self, name) + + if match && match.valid? + match.define + send(name, *arguments, &block) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/enum.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/enum.rb new file mode 100644 index 0000000000..65c16e6bd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/enum.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true + +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" + # + # Finally, it's also 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 + # + # 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 +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 + + module Enum + def self.extended(base) # :nodoc: + base.class_attribute(:defined_enums, instance_writer: false, default: {}) + end + + def inherited(base) # :nodoc: + base.defined_enums = defined_enums.deep_dup + super + end + + class EnumType < Type::Value # :nodoc: + delegate :type, to: :subtype + + def initialize(name, mapping, subtype) + @name = name + @mapping = mapping + @subtype = subtype + end + + def cast(value) + if mapping.has_key?(value) + value.to_s + elsif mapping.has_value?(value) + mapping.key(value) + elsif value.blank? + nil + else + assert_valid_value(value) + end + end + + def deserialize(value) + mapping.key(subtype.deserialize(value)) + end + + def serialize(value) + mapping.fetch(value, value) + end + + def assert_valid_value(value) + 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(definitions) + enum_prefix = definitions.delete(:_prefix) + enum_suffix = definitions.delete(:_suffix) + enum_scopes = definitions.delete(:_scopes) + + default = {} + default[:default] = definitions.delete(:_default) if definitions.key?(:_default) + + definitions.each do |name, values| + assert_valid_enum_definition_values(values) + # 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}=") + + attr = attribute_alias?(name) ? attribute_alias(name) : name + + decorate_attribute_type(attr, **default) do |subtype| + EnumType.new(attr, enum_values, subtype) + end + + value_method_names = [] + _enum_methods_module.module_eval do + prefix = if enum_prefix == true + "#{name}_" + elsif enum_prefix + "#{enum_prefix}_" + end + + suffix = if enum_suffix == true + "_#{name}" + elsif enum_suffix + "_#{enum_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, enum_scopes) + + 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, enum_scopes) + end + end + end + detect_negative_enum_conditions!(value_method_names) if enum_scopes != false + enum_values.freeze + end + end + + private + class EnumMethods < Module # :nodoc: + def initialize(klass) + @klass = klass + end + + private + attr_reader :klass + + def define_enum_methods(name, value_method_name, value, enum_scopes) + # 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) } + + # scope :active, -> { where(status: 0) } + # scope :not_active, -> { where.not(status: 0) } + if enum_scopes != false + klass.send(:detect_enum_conflict!, name, value_method_name, true) + klass.scope value_method_name, -> { where(name => value) } + + 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) + unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) } + error_message = <<~MSG + Enum values #{values} must be either a hash, an array of symbols, or an array of strings. + MSG + raise ArgumentError, error_message + end + + if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?) + raise ArgumentError, "Enum label name must not be blank." + 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 && 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/errors.rb new file mode 100644 index 0000000000..ffb6978daf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/errors.rb @@ -0,0 +1,423 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Errors + # + # Generic Active Record exception class. + class ActiveRecordError < StandardError + end + + # Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present. + class ActiveJobRequiredError < ActiveRecordError + 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 + + # Raised when connection to the database could not been established (for example when + # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection] + # is given a +nil+ object). + class ConnectionNotEstablished < ActiveRecordError + 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 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.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.create!}[rdoc-ref:Persistence::ClassMethods#create!] + # methods when a record is invalid and cannot be saved. + 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 call to {#destroy}[rdoc-ref:Persistence#destroy!] + # would return false. + # + # begin + # complex_operation_that_internally_calls_destroy! + # rescue ActiveRecord::RecordNotDestroyed => invalid + # puts invalid.record.errors + # end + # + class RecordNotDestroyed < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Superclass for all database execution errors. + # + # Wraps the underlying database error as +cause+. + class StatementInvalid < ActiveRecordError + def initialize(message = nil, sql: nil, binds: nil) + super(message || $!&.message) + @sql = sql + @binds = binds + end + + attr_reader :sql, :binds + 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 + ) + 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) + 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 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 + 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. + 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, "Call tech support!" + # 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::ImmutableRelation + # relation.limit!(5) # => ActiveRecord::ImmutableRelation + class ImmutableRelation < 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 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. + # + # 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 + + # SerializationFailure will be raised when a transaction is rolled + # back by the database due to a serialization failure. + class SerializationFailure < TransactionRollbackError + end + + # Deadlocked will be raised when a transaction is rolled + # back by the database when a deadlock is encountered. + 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 + + # 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("length(title)").first + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("length(title)")).first + # + # 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain.rb new file mode 100644 index 0000000000..7fe04106ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain.rb @@ -0,0 +1,54 @@ +# 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) # :nodoc: + str = queries.map do |sql, binds| + msg = +"EXPLAIN for: #{sql}" + unless binds.empty? + msg << " " + msg << binds.map { |attr| render_bind(attr) }.inspect + end + msg << "\n" + msg << connection.explain(sql, binds) + end.join("\n") + + # Overriding inspect to be more human readable, especially in the console. + def str.inspect + self + end + + str + end + + private + def render_bind(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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain_registry.rb new file mode 100644 index 0000000000..7fd078941a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain_registry.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class ExplainRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :queries, :collect + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/explain_subscriber.rb new file mode 100644 index 0000000000..ce209092f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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*(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.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/file.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/file.rb new file mode 100644 index 0000000000..52febbd163 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/file.rb @@ -0,0 +1,75 @@ +# 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 + 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 + + # 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/model_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/model_metadata.rb new file mode 100644 index 0000000000..5fb7824558 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/model_metadata.rb @@ -0,0 +1,32 @@ +# 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 && @model_class.type_for_attribute(@model_class.primary_key).type + end + + def has_primary_key_column? + @has_primary_key_column ||= primary_key_name && + @model_class.columns.any? { |col| col.name == primary_key_name } + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/render_context.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/render_context.rb new file mode 100644 index 0000000000..b6ca84192f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/render_context.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/table_row.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/table_row.rb new file mode 100644 index 0000000000..481740b135 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/table_row.rb @@ -0,0 +1,152 @@ +# 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 + 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 + 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 + # generate a primary key if necessary + if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name) + @row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify( + @label, model_metadata.primary_key_type + ) + end + end + + def resolve_enums + model_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? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") + # support polymorphic belongs_to as "label (Type)" + @row[association.join_foreign_type] = $1 + end + + fk_type = reflection_class.type_for_attribute(fk_name).type + @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) + 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| + { lhs_key => @row[model_metadata.primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } + end + @table_rows.tables[table_name].concat(joins) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/table_rows.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixture_set/table_rows.rb new file mode 100644 index 0000000000..df1cd63963 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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:, config:) + @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, config) + 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, config) + now = config.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixtures.rb new file mode 100644 index 0000000000..331023b8cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/fixtures.rb @@ -0,0 +1,784 @@ +# frozen_string_literal: true + +require "erb" +require "yaml" +require "zlib" +require "set" +require "active_support/dependencies" +require "active_support/core_ext/digest/uuid" +require "active_record/fixture_set/file" +require "active_record/fixture_set/render_context" +require "active_record/fixture_set/table_rows" +require "active_record/test_fixtures" + +module ActiveRecord + class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: + end + + # \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 placed in the directory + # appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically + # configured for Rails, so you can just put your files in /test/fixtures/). + # 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 (ie. 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. + # + # Note: Fixtures 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. 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 + # + # 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) + # Digest::SHA2.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 + # + # 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) %> + # + # == 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_s(: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 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. + # + # == 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 + #-- + # 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 ClassCache + def initialize(class_names, config) + @class_names = class_names.stringify_keys + @config = config + + # Remove string values that aren't constants or subclasses of AR + @class_names.delete_if do |klass_name, klass| + !insert_class(@class_names, klass_name, klass) + end + end + + def [](fs_name) + @class_names.fetch(fs_name) do + klass = default_fixture_model(fs_name, @config).safe_constantize + insert_class(@class_names, fs_name, klass) + end + end + + private + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end + end + + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end + end + + 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(connection) + @@all_cached_fixtures[connection] + end + + def fixture_is_cached?(connection, table_name) + cache_for_connection(connection)[table_name] + end + + def cached_fixtures(connection, keys_to_fetch = nil) + if keys_to_fetch + cache_for_connection(connection).values_at(*keys_to_fetch) + else + cache_for_connection(connection).values + end + end + + def cache_fixtures(connection, fixtures_map) + cache_for_connection(connection).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_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block) + fixture_set_names = Array(fixture_set_names).map(&:to_s) + class_names = ClassCache.new class_names, config + + # FIXME: Apparently JK uses this. + connection = block_given? ? block : lambda { ActiveRecord::Base.connection } + + fixture_files_to_read = fixture_set_names.reject do |fs_name| + fixture_is_cached?(connection.call, fs_name) + end + + if fixture_files_to_read.any? + fixtures_map = read_and_insert( + fixtures_directory, + fixture_files_to_read, + class_names, + connection, + ) + cache_fixtures(connection.call, fixtures_map) + end + cached_fixtures(connection.call, 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 + + def signed_global_id(fixture_set_name, label, column_type: :integer, **options) + identifier = identify(label, column_type) + model_name = default_fixture_model_name(fixture_set_name) + uri = URI::GID.build([GlobalID.app, model_name, identifier, {}]) + + SignedGlobalID.new(uri, **options) + end + + # Superclass for the evaluation contexts used by ERB fixtures. + def context_class + @context_class ||= Class.new + end + + private + def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc: + fixtures_map = {} + 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(fixtures_directory, fixture_set_name) + ) + end + update_all_loaded_fixtures(fixtures_map) + + insert(fixture_sets, connection) + + fixtures_map + end + + def insert(fixture_sets, connection) # :nodoc: + fixture_sets_by_connection = fixture_sets.group_by do |fixture_set| + if fixture_set.model_class + fixture_set.model_class.connection + else + connection.call + end + end + + fixture_sets_by_connection.each do |conn, 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 + + conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys) + + # 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 + + 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, + config: config, + ).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"].select { |f| + ::File.file?(f) + } + [yaml_file_path(path)] + + 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.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 yaml_file_path(path) + "#{path}.yml" + 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 + fixture.each { |item| yield item } + 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 + model_class.find(fixture[model_class.primary_key]) + end + # Fixtures can't be eagerly loaded + object.instance_variable_set(:@strict_loading, false) + object + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/gem_version.rb new file mode 100644 index 0000000000..f3c448ba7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + # Returns the version of the currently loaded Active Record as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 7 + PRE = "2" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/inheritance.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/inheritance.rb new file mode 100644 index 0000000000..d0ef768433 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/inheritance.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +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 + 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 class descending directly from ActiveRecord::Base, or + # an abstract class, if any, in the inheritance hierarchy. + # + # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A + # through some arbitrarily deep hierarchy, B.base_class will return A. + # + # If B < A and C < B and if A is an abstract_class then both B.base_class + # and C.base_class would return B as the answer since A is an abstract_class. + def base_class + 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 + + # 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 behaviour: + # + # 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? + defined?(@abstract_class) && @abstract_class == true + 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 + ActiveSupport::Dependencies.constantize(type_name) + 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." + 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 + ActiveSupport::Dependencies.constantize(name) + else + compute_type(name) + end + end + + def inherited(subclass) + subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) + super + 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. + ActiveSupport::Dependencies.constantize(type_name) + else + type_candidate = @_type_candidates_cache[type_name] + if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate) + 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 = ActiveSupport::Dependencies.safe_constantize(candidate) + 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 + + private + # 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/insert_all.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/insert_all.rb new file mode 100644 index 0000000000..ca303ef857 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/insert_all.rb @@ -0,0 +1,212 @@ +# 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, :returning, :unique_by + + def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil) + raise ArgumentError, "Empty list of attributes passed" if inserts.blank? + + @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s) + @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by + + if model.scope_attributes? + @scope_attributes = model.scope_attributes + @keys |= @scope_attributes.keys + end + @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) + @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty? + + ensure_valid_options_for_connection! + end + + def execute + message = +"#{model} " + message << "Bulk " if inserts.many? + message << (on_duplicate == :update ? "Upsert" : "Insert") + connection.exec_insert_all to_sql, message + end + + def updatable_columns + keys - readonly_columns - unique_by_columns + end + + def primary_keys + Array(connection.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) if scope_attributes + + verify_attributes(attributes) + + keys.map do |key| + yield key, attributes[key] + end + end + end + + private + attr_reader :scope_attributes + + 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) + + if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == 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 + connection.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.to_a + end + + def unique_by_columns + Array(unique_by&.columns) + end + + + def verify_attributes(attributes) + if keys != attributes.keys.to_set + raise ArgumentError, "All objects being inserted must have the same keys" + end + end + + class Builder # :nodoc: + attr_reader :model + + delegate :skip_duplicates?, :update_duplicates?, :keys, 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) + + values_list = insert_all.map_key_with_value do |key, value| + connection.with_yaml_fallback(types[key].serialize(value)) + end + + connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list)) + end + + def returning + format_columns(insert_all.returning) if insert_all.returning + 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) + model.send(:timestamp_attributes_for_update_in_model).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 CURRENT_TIMESTAMP END)," + end + end.compact.join + end + + private + attr_reader :connection, :insert_all + + def touch_timestamp_attribute?(column_name) + update_duplicates? && !insert_all.updatable_columns.include?(column_name) + end + + def columns_list + format_columns(insert_all.keys) + end + + def extract_types_from_columns_on(table_name, keys:) + columns = connection.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(&connection.method(:quote_column_name)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/integration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/integration.rb new file mode 100644 index 0000000000..256e00e82c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/integration.rb @@ -0,0 +1,205 @@ +# 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 + # We can't use alias_method here, because method 'id' optimizes itself on the fly. + id && id.to_s # Be sure to stringify the id for routes + 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_s(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_s(cache_timestamp_format) + end + elsif self.class.has_attribute?("updated_at") + raise ActiveModel::MissingAttributeError, "missing attribute: updated_at" + 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 && + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/internal_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/internal_metadata.rb new file mode 100644 index 0000000000..f3d3246248 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/internal_metadata.rb @@ -0,0 +1,64 @@ +# 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 < ActiveRecord::Base # :nodoc: + self.record_timestamps = true + + class << self + def enabled? + ActiveRecord::Base.connection.use_metadata_table? + end + + def _internal? + true + end + + def primary_key + "key" + end + + def table_name + "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}" + end + + def []=(key, value) + return unless enabled? + + find_or_initialize_by(key: key).update!(value: value) + end + + def [](key) + return unless enabled? + + where(key: key).pluck(:value).first + end + + # Creates an internal metadata table with columns +key+ and +value+ + def create_table + return unless enabled? + + 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 + + def drop_table + return unless enabled? + + connection.drop_table table_name, if_exists: true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/legacy_yaml_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/legacy_yaml_adapter.rb new file mode 100644 index 0000000000..6ba9a62735 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/legacy_yaml_adapter.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveRecord + module LegacyYamlAdapter # :nodoc: + def self.convert(klass, coder) + return coder unless coder.is_a?(Psych::Coder) + + case coder["active_record_yaml_version"] + when 1, 2 then coder + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + YAML loading from legacy format older than Rails 5.0 is deprecated + and will be removed in Rails 7.0. + MSG + if coder["attributes"].is_a?(ActiveModel::AttributeSet) + Rails420.convert(klass, coder) + else + Rails41.convert(klass, coder) + end + end + end + + module Rails420 # :nodoc: + def self.convert(klass, coder) + attribute_set = coder["attributes"] + + klass.attribute_names.each do |attr_name| + attribute = attribute_set[attr_name] + if attribute.type.is_a?(Delegator) + type_from_klass = klass.type_for_attribute(attr_name) + attribute_set[attr_name] = attribute.with_type(type_from_klass) + end + end + + coder + end + end + + module Rails41 # :nodoc: + def self.convert(klass, coder) + attributes = klass.attributes_builder + .build_from_database(coder["attributes"]) + new_record = coder["attributes"][klass.primary_key].blank? + + { + "attributes" => attributes, + "new_record" => new_record, + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locale/en.yml new file mode 100644 index 0000000000..0b35027b2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/optimistic.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/optimistic.rb new file mode 100644 index 0000000000..2611b8d723 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/optimistic.rb @@ -0,0 +1,212 @@ +# 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 +lock_version+ column 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 + + 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] + lock_value_for_database = _lock_value_for_database(locking_column) + + 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), + @primary_key => id_in_database, + locking_column => lock_value_for_database + ) + + 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 + return super unless locking_enabled? + + locking_column = self.class.locking_column + + affected_rows = self.class._delete_record( + @primary_key => id_in_database, + locking_column => _lock_value_for_database(locking_column) + ) + + if 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 + + 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+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end + + # 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 + + def define_attribute(name, cast_type, **) # :nodoc: + if lock_optimistically && name == locking_column + cast_type = LockingType.new(cast_type) + end + super + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/pessimistic.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/pessimistic.rb new file mode 100644 index 0000000000..0849db8a7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/locking/pessimistic.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveRecord + module 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. + MSG + end + + reload(lock: lock) + end + self + end + + # Wraps the passed block in a transaction, locking the object + # before yielding. You can pass the SQL locking clause + # as argument (see lock!). + def with_lock(lock = true) + transaction do + lock!(lock) + yield + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/log_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/log_subscriber.rb new file mode 100644 index 0000000000..a9d9271b74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/log_subscriber.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module ActiveRecord + class LogSubscriber < ActiveSupport::LogSubscriber + IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + + class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new + + def self.runtime=(value) + ActiveRecord::RuntimeRegistry.sql_runtime = value + end + + def self.runtime + ActiveRecord::RuntimeRegistry.sql_runtime ||= 0 + end + + def self.reset_runtime + rt, self.runtime = runtime, 0 + rt + end + + def strict_loading_violation(event) + debug do + owner = event.payload[:owner] + association = event.payload[:reflection].klass + name = event.payload[:reflection].name + + color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED) + end + end + + def sql(event) + self.class.runtime += event.duration + return unless logger.debug? + + payload = event.payload + + return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) + + name = "#{payload[:name]} (#{event.duration.round(1)}ms)" + 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| + binds << render_bind(attr, casted_params[i]) + end + binds = binds.inspect + binds.prepend(" ") + end + + name = colorize_payload_name(name, payload[:name]) + sql = color(sql, sql_color(sql), true) if colorize_logging + + debug " #{name} #{sql}#{binds}" + end + + 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, true) + else + color(name, CYAN, 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::Base.verbose_query_logs + log_query_source + end + end + + def log_query_source + source = extract_query_source_location(caller) + + if source + logger.debug(" ↳ #{source}") + end + end + + def extract_query_source_location(locations) + backtrace_cleaner.clean(locations.lazy).first + end + end +end + +ActiveRecord::LogSubscriber.attach_to :active_record diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector.rb new file mode 100644 index 0000000000..3538d203ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "active_record/middleware/database_selector/resolver" + +module ActiveRecord + module 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 add + # the following options to your environment config: + # + # 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 + # + # New applications will include these lines commented out in the production.rb. + # + # 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 + 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 reading_request?(request) + resolver.read(&blk) + else + resolver.write(&blk) + end + + resolver.update_context(response) + response + end + + def reading_request?(request) + request.get? || request.head? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector/resolver.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector/resolver.rb new file mode 100644 index 0000000000..96f1b8e9a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + + private + def read_from_primary(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_primary") do + yield + end + end + end + + def read_from_replica(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_replica") do + yield + end + end + end + + def write_to_primary(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord::Base.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector/resolver/session.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/middleware/database_selector/resolver/session.rb new file mode 100644 index 0000000000..530701fb8d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration.rb new file mode 100644 index 0000000000..0846f47d2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration.rb @@ -0,0 +1,1427 @@ +# frozen_string_literal: true + +require "benchmark" +require "set" +require "zlib" +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" + +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[6.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[6.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[6.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 PendingMigrationError < MigrationError #:nodoc: + include ActiveSupport::ActionableError + + action "Run pending migrations" do + ActiveRecord::Tasks::DatabaseTasks.migrate + + if ActiveRecord::Base.dump_schema_after_migration + ActiveRecord::Tasks::DatabaseTasks.dump_schema( + ActiveRecord::Base.connection_db_config + ) + end + end + + def initialize(message = nil) + super(message || detailed_migration_message) + end + + private + def detailed_migration_message + message = "Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate" + message += " RAILS_ENV=#{::Rails.env}" if defined?(Rails.env) + message += "\n\n" + + pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations + + message += "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}\n\n" + + pending_migrations.each do |pending_migration| + message += "#{pending_migration.basename}\n" + end + + message + 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[6.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[6.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(name): Drops the table called +name+. + # * 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[6.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[6.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[6.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[6.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[6.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). + # + # 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[6.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[6.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" + + # This must be defined before the inherited hook, below + class Current < Migration #:nodoc: + end + + def self.inherited(subclass) #:nodoc: + super + if subclass.superclass == Migration + raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \ + "Please specify the Rails release the migration was written for:\n" \ + "\n" \ + " class #{subclass} < ActiveRecord::Migration[4.2]" + 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: + + # 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!(connection) + @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) + paths = Array(connection.migration_context.migrations_paths) + @file_watcher.new([], paths.index_with(["rb"]), &block) + end + + def connection + ActiveRecord::Base.connection + 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. + def check_pending!(connection = Base.connection) + raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration? + end + + def load_schema_if_pending! + current_db_config = Base.connection_db_config + all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env) + + needs_update = !all_configs.all? do |db_config| + Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord::Base.schema_format) + end + + if needs_update + # Roundtrip to Rake to allow plugins to hook into database initialization. + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + FileUtils.cd(root) do + Base.clear_all_connections! + system("bin/rails db:test:prepare") + end + end + + # Establish a new connection, the old database may be gone (db:test:prepare uses purge) + Base.establish_connection(current_db_config) + + check_pending! + end + + def maintain_test_schema! #:nodoc: + if ActiveRecord::Base.maintain_test_schema + suppress_messages { load_schema_if_pending! } + end + end + + def method_missing(name, *args, &block) #:nodoc: + nearest_delegate.send(name, *args, &block) + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + 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 + end + + def disable_ddl_transaction #:nodoc: + self.class.disable_ddl_transaction + end + + cattr_accessor :verbose + attr_accessor :name, :version + + def initialize(name = self.class.name, version = nil) + @name = name + @version = version + @connection = nil + 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[6.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[6.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) + run(*migration_classes.reverse, revert: true) unless migration_classes.empty? + if block_given? + if connection.respond_to? :revert + connection.revert { yield } + else + recorder = command_recorder + @connection = recorder + suppress_messages do + connection.revert { yield } + 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[6.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[6.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 + execute_block { yield } 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 = nil + ActiveRecord::Base.connection_pool.with_connection do |conn| + time = Benchmark.measure do + exec_migration(conn, direction) + end + end + + case direction + when :up then announce "migrated (%.4fs)" % time.real; write + when :down then announce "reverted (%.4fs)" % time.real; 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 + 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 = Benchmark.measure { result = yield } + say "%.4fs" % time.real, :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::Base.connection + end + + def method_missing(method, *arguments, &block) + arg_list = arguments.map(&:inspect) * ", " + + say_with_time "#{method}(#{arg_list})" 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 [:rename_table, :add_foreign_key].include?(method) || + (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 connection.respond_to?(method) + connection.send(method, *arguments, &block) + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + + def copy(destination, sources, options = {}) + copied = [] + schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration + + FileUtils.mkdir_p(destination) unless File.exist?(destination) + + destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations + last = destination_migrations.last + sources.each do |scope, path| + source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).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 + 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::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max + else + SchemaMigration.normalize_migration_number(number) + 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 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 + require(File.expand_path(filename)) + name.constantize.new(name, version) + end + end + + class MigrationContext #:nodoc: + attr_reader :migrations_paths, :schema_migration + + def initialize(migrations_paths, schema_migration) + @migrations_paths = migrations_paths + @schema_migration = schema_migration + end + + 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) + move(:down, steps) + end + + def forward(steps = 1) + move(:up, steps) + end + + def up(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate + end + + def down(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate + end + + def run(direction, target_version) + Migrator.new(direction, migrations, schema_migration, target_version).run + end + + def open + Migrator.new(:up, migrations, schema_migration) + end + + def get_all_versions + if schema_migration.table_exists? + schema_migration.all_versions.map(&:to_i) + else + [] + end + end + + def current_version + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError + end + + def needs_migration? + (migrations.collect(&:version) - get_all_versions).size > 0 + end + + def any_migrations? + migrations.any? + end + + def migrations + migrations = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + name = name.camelize + + MigrationProxy.new(name, version, file, scope) + end + + migrations.sort_by(&:version) + end + + def migrations_status + db_list = schema_migration.normalized_versions + + file_list = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = schema_migration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end.compact + + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] + end + + (db_list + file_list).sort_by { |_, version, _| version } + end + + def current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end + + def last_stored_environment + return nil unless ActiveRecord::InternalMetadata.enabled? + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? + + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end + + private + 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 move(direction, steps) + migrator = Migrator.new(direction, migrations, schema_migration) + + 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 + MigrationContext.new(migrations_paths, SchemaMigration).current_version + end + end + + self.migrations_paths = ["db/migrate"] + + def initialize(direction, migrations, schema_migration, target_version = nil) + @direction = direction + @target_version = target_version + @migrated_versions = nil + @migrations = migrations + @schema_migration = schema_migration + + validate(@migrations) + + @schema_migration.create_table + ActiveRecord::InternalMetadata.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.all_versions.map(&:to_i)) + end + + private + # 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? + result = execute_migration_in_transaction(migration) + + record_environment + result + 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 + + result = runnable.each(&method(:execute_migration_in_transaction)) + record_environment + result + end + + # Stores the current environment in the database. + def record_environment + return if down? + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment + 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_by(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) + if use_transaction?(migration) + Base.transaction { yield } + else + yield + end + end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end + + def use_advisory_lock? + Base.connection.advisory_locks_enabled? + end + + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + + with_advisory_lock_connection do |connection| + 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 + end + + def with_advisory_lock_connection + pool = ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection( + ActiveRecord::Base.connection_db_config + ) + + pool.with_connection { |connection| yield(connection) } + ensure + pool&.disconnect! + end + + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(Base.connection.current_database) + MIGRATOR_SALT * db_name_hash + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/command_recorder.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/command_recorder.rb new file mode 100644 index 0000000000..dd7035d8ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/command_recorder.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # 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_index + # * add_reference + # * add_timestamps + # * change_column + # * 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_join_table + # * create_table + # * disable_extension + # * drop_join_table + # * drop_table (must supply a block) + # * enable_extension + # * remove_column (must supply a type) + # * remove_columns (must specify at least one column name or more) + # * remove_foreign_key (must supply a second table) + # * remove_check_constraint + # * remove_index + # * remove_reference + # * remove_timestamps + # * rename_column + # * 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 + ] + 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) if respond_to?(:ruby2_keywords, true) + end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference + + def change_table(table_name, **options) # :nodoc: + yield delegate.update_table_definition(table_name, self) + 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, + enable_extension: :disable_extension + }.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) + sub_recorder = CommandRecorder.new(delegate) + sub_recorder.revert { yield } + + invertions_proc = proc { + sub_recorder.replay(self) + } + + [:transaction, args, invertions_proc] + end + + def invert_drop_table(args, &block) + if args.size == 1 && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + super + end + + def invert_rename_table(args) + [:rename_table, args.reverse] + 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_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_remove_check_constraint(args) + raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2 + 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, *args, &block) + if delegate.respond_to?(method) + delegate.public_send(method, *args, &block) + else + super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/compatibility.rb new file mode 100644 index 0000000000..4081e89aa0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/compatibility.rb @@ -0,0 +1,298 @@ +# 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 + + V6_1 = Current + + 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 + end + + 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 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 + t + end + end + + class V5_2 < V6_0 + module TableDefinition + def timestamps(**options) + options[:precision] ||= nil + super + 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 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 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" + 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 + 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 connection.adapter_name == "Mysql2" && 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 + 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 + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/join_table.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/migration/join_table.rb new file mode 100644 index 0000000000..45169617c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/model_schema.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/model_schema.rb new file mode 100644 index 0000000000..8e8a914ff9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/model_schema.rb @@ -0,0 +1,647 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveRecord + module ModelSchema + extend ActiveSupport::Concern + + ## + # :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. + + included do + mattr_accessor :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 + + self.protected_environments = ["production"] + self.inheritance_column = "type" + 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 + 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". + # + # 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 + @quoted_table_name = nil + @arel_table = nil + @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name + @predicate_builder = nil + end + + # Returns a quoted version of the table name, used to construct SQL statements. + def quoted_table_name + @quoted_table_name ||= connection.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 abstract_class? + superclass == Base ? nil : 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 + + # Defines the name of the table column which will store 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' + def inheritance_column + (@inheritance_column ||= nil) || superclass.inheritance_column + end + + # Sets the value of inheritance_column + def inheritance_column=(value) + @inheritance_column = value.to_s + @explicit_inheritance_column = true + 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 + if defined?(@ignored_columns) + @ignored_columns + else + superclass.ignored_columns + end + 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 = connection.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? + connection.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 + connection.next_sequence_value(sequence_name) + end + + # Indicates whether the table associated with this class exists + def table_exists? + connection.schema_cache.data_source_exists?(table_name) + end + + def attributes_builder # :nodoc: + unless defined?(@attributes_builder) && @attributes_builder + defaults = _default_attributes.except(*(column_names - [primary_key])) + @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults) + end + @attributes_builder + end + + def columns_hash # :nodoc: + load_schema + @columns_hash + end + + def columns + load_schema + @columns ||= columns_hash.values.freeze + end + + def attribute_types # :nodoc: + load_schema + @attribute_types ||= Hash.new(Type.default_value) + end + + def yaml_encoder # :nodoc: + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) + end + + # Returns the type of the attribute with the given name, after applying + # all modifiers. This method is the only valid source of information for + # anything related to the types of a model's attributes. This method will + # access the database and load the model's schema if it is required. + # + # The return value of this method will implement the interface described + # by ActiveModel::Type::Value (though the object itself may not subclass + # it). + # + # +attr_name+ The name of the attribute to retrieve the type for. Must be + # a string or a symbol. + def type_for_attribute(attr_name, &block) + attr_name = attr_name.to_s + attr_name = attribute_aliases[attr_name] || attr_name + + if block + attribute_types.fetch(attr_name, &block) + else + attribute_types[attr_name] + end + 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 + + def _default_attributes # :nodoc: + load_schema + @default_attributes ||= ActiveModel::AttributeSet.new({}) + 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, eg: + # + # class CreateJobLevels < ActiveRecord::Migration[6.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.clear_cache! + ([self] + descendants).each(&:undefine_attribute_methods) + connection.schema_cache.clear_data_source_cache!(table_name) + + reload_schema_from_cache + initialize_find_by_cache + end + + protected + def initialize_load_schema_monitor + @load_schema_monitor = Monitor.new + end + + private + def inherited(child_class) + super + child_class.initialize_load_schema_monitor + end + + def schema_loaded? + defined?(@schema_loaded) && @schema_loaded + end + + def load_schema + return if schema_loaded? + @load_schema_monitor.synchronize do + return if defined?(@columns_hash) && @columns_hash + + 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 + + def load_schema! + unless table_name + raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name=" + end + + columns_hash = connection.schema_cache.columns_hash(table_name) + columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty? + @columns_hash = columns_hash.freeze + @columns_hash.each do |name, column| + type = connection.lookup_cast_type_from_column(column) + type = _convert_type_from_options(type) + warn_if_deprecated_type(column) + define_attribute( + name, + type, + default: column.default, + user_provided_default: false + ) + end + end + + def reload_schema_from_cache + @arel_table = nil + @column_names = nil + @symbol_column_to_string_name_hash = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @column_defaults = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @schema_loaded = false + @attribute_names = nil + @yaml_encoder = nil + direct_descendants.each do |descendant| + descendant.send(:reload_schema_from_cache) + end + end + + # Guesses the table name, but does not decorate it with prefix and suffix information. + def undecorated_table_name(class_name = base_class.name) + table_name = class_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(name)}#{full_table_name_suffix}" + else + # STI subclasses always use their superclass' table. + base_class.table_name + end + end + + def _convert_type_from_options(type) + if immutable_strings_by_default && type.respond_to?(:to_immutable_string) + type.to_immutable_string + else + type + end + end + + def warn_if_deprecated_type(column) + return if attributes_to_define_after_schema_loads.key?(column.name) + return unless column.respond_to?(:oid) + + if column.array? + array_arguments = ", array: true" + else + array_arguments = "" + end + + if column.sql_type.start_with?("interval") + precision_arguments = column.precision.presence && ", precision: #{column.precision}" + ActiveSupport::Deprecation.warn(<<~WARNING) + The behavior of the `:interval` type will be changing in Rails 7.0 + to return an `ActiveSupport::Duration` object. If you'd like to keep + the old behavior, you can add this line to #{self.name} model: + + attribute :#{column.name}, :string#{precision_arguments}#{array_arguments} + + If you'd like the new behavior today, you can add this line: + + attribute :#{column.name}, :interval#{precision_arguments}#{array_arguments} + WARNING + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/nested_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/nested_attributes.rb new file mode 100644 index 0000000000..fcff10a3e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/nested_attributes.rb @@ -0,0 +1,597 @@ +# 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 + # + # If you want to validate that a child record is associated with a parent + # record, you can use the +validates_presence_of+ method and the +:inverse_of+ + # key as this example illustrates: + # + # class Member < ActiveRecord::Base + # has_many :posts, inverse_of: :member + # accepts_nested_attributes_for :posts + # end + # + # class Post < ActiveRecord::Base + # belongs_to :member, inverse_of: :posts + # validates_presence_of :member + # 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 + 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 off 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) + options = nested_attributes_options[association_name] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + 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 attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + 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.map { |a| a["id"] || a[:id] }.compact + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) + end + + attributes_collection.each 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 = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + 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 = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + 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]) + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + end + end + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/no_touching.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/no_touching.rb new file mode 100644 index 0000000000..45c5e1fd53 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + Thread.current[: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.0.0/gems/activerecord-6.1.7.2/lib/active_record/null_relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/null_relation.rb new file mode 100644 index 0000000000..bee5b5f24a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/null_relation.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveRecord + module NullRelation # :nodoc: + def pluck(*column_names) + [] + end + + def delete_all + 0 + end + + def update_all(_updates) + 0 + end + + def delete(_id_or_array) + 0 + end + + def empty? + true + end + + def none? + true + end + + def any? + false + end + + def one? + false + end + + def many? + false + end + + def to_sql + "" + end + + def calculate(operation, _column_name) + case operation + when :count, :sum + group_values.any? ? Hash.new : 0 + when :average, :minimum, :maximum + group_values.any? ? Hash.new : nil + end + end + + def exists?(_conditions = :none) + false + end + + def or(other) + other.spawn + end + + private + def exec_queries + @records = [].freeze + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/persistence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/persistence.rb new file mode 100644 index 0000000000..011b7359fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/persistence.rb @@ -0,0 +1,971 @@ +# 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 + + # 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 ActiveRecord::Persistence#insert_all for documentation. + def insert(attributes, returning: nil, unique_by: nil) + insert_all([ attributes ], returning: returning, unique_by: unique_by) + 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 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. + # + # [: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. + # + # ==== 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" } + # ]) + def insert_all(attributes, returning: nil, unique_by: nil) + InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute + 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 ActiveRecord::Persistence#insert_all! for more. + def insert!(attributes, returning: nil) + insert_all!([ attributes ], returning: returning) + 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 ActiveRecord::Persistence#insert_all. + # To replace them, see ActiveRecord::Persistence#upsert_all. + # + # Returns an ActiveRecord::Result with its contents based on + # :returning (see below). + # + # ==== Options + # + # [:returning] + # (PostgreSQL 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. + # + # ==== 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) + InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute + 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 ActiveRecord::Persistence#upsert_all for documentation. + def upsert(attributes, returning: nil, unique_by: nil) + upsert_all([ attributes ], returning: returning, unique_by: unique_by) + 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). + # + # ==== Options + # + # [:returning] + # (PostgreSQL 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. + # + # [: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. + # + # ==== 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, returning: nil, unique_by: nil) + InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute + 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. + # * +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) + 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 + + # 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) + if id.is_a?(Array) + find(id).each(&:destroy) + else + find(id).destroy + 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) + delete_by(primary_key => id_or_array) + end + + def _insert_record(values) # :nodoc: + primary_key = self.primary_key + primary_key_value = nil + + if primary_key && Hash === values + primary_key_value = values[primary_key] + + if !primary_key_value && prefetch_primary_key? + primary_key_value = next_sequence_value + values[primary_key] = primary_key_value + end + end + + if values.empty? + im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key)) + im.into arel_table + else + im = arel_table.compile_insert(_substitute_values(values)) + end + + connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) + end + + def _update_record(values, constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + um = arel_table.where( + constraints.reduce(&:and) + ).compile_update(_substitute_values(values), primary_key) + + connection.update(um, "#{self} Update") + end + + def _delete_record(constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + dm = Arel::DeleteManager.new + dm.from(arel_table) + dm.wheres = constraints + + connection.delete(dm, "#{self} Destroy") + end + + private + # 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 + + def _substitute_values(values) + values.map do |name, value| + attr = arel_table[name] + bind = predicate_builder.build_bind_attribute(attr.name, value) + [attr, bind] + end + 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 + # save, 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 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 + 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 = if persisted? + destroy_row > 0 + else + true + end + @destroyed = true + 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 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. + # If you want to change the sti column as well, use #becomes! instead. + def becomes(klass) + became = klass.allocate + + became.send(:initialize) do |becoming| + 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 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? + + attributes = attributes.transform_keys do |key| + name = key.to_s + name = self.class.attribute_aliases[name] || name + verify_readonly_attribute(name) || name + end + + id_in_database = self.id_in_database + attributes.each do |k, v| + write_attribute_without_type_cast(k, v) + end + + affected_rows = self.class._update_record( + attributes, + @primary_key => id_in_database + ) + + 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.clear_query_cache + + fresh_object = + if options && options[:lock] + self.class.unscoped { self.class.lock(options[:lock]).find(id) } + else + self.class.unscoped { self.class.find(id) } + end + + @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? + + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map! do |name| + name = name.to_s + self.class.attribute_aliases[name] || name + end unless names.empty? + + unless attribute_names.empty? + affected_rows = _touch_row(attribute_names, time) + @_trigger_update_callback = affected_rows == 1 + else + true + end + end + + private + # 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(@primary_key => id_in_database) + 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), + @primary_key => id_in_database + ) + 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) + + new_id = self.class._insert_record( + attributes_with_values(attribute_names) + ) + + self.id ||= new_id if @primary_key + + @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 + raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", 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 + + # The name of the method used to touch a +belongs_to+ association when the + # +:touch+ option is used. + def belongs_to_touch_method + :touch + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/query_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/query_cache.rb new file mode 100644 index 0000000000..b4e24debe4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/query_cache.rb @@ -0,0 +1,62 @@ +# 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? + connection.cache(&block) + 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. + def uncached(&block) + if connected? || !configurations.empty? + connection.uncached(&block) + else + yield + end + end + end + + def self.run + pools = [] + + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each do |key, handler| + pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }) + end + else + pools.concat(ActiveRecord::Base.connection_handler.all_connection_pools.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }) + end + + pools + end + + def self.complete(pools) + pools.each { |pool| pool.disable_query_cache! } + + if ActiveRecord::Base.legacy_connection_handling + ActiveRecord::Base.connection_handlers.each do |_, handler| + handler.connection_pool_list.each do |pool| + pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? + end + end + else + ActiveRecord::Base.connection_handler.all_connection_pools.each do |pool| + pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? + end + end + end + + def self.install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/querying.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/querying.rb new file mode 100644 index 0000000000..5e0669e5ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/querying.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module ActiveRecord + module Querying + QUERYING_METHODS = [ + :find, :find_by, :find_by!, :take, :take!, :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_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by, + :find_each, :find_in_batches, :in_batches, + :select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins, + :where, :rewhere, :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, :strict_loading + ].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 }] + def find_by_sql(sql, binds = [], preparable: nil, &block) + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable) + 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.map { |record| instantiate(record, column_types, &block) } + else + # Instantiate a homogeneous set + result_set.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) + connection.select_value(sanitize_sql(sql), "#{name} Count").to_i + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railtie.rb new file mode 100644 index 0000000000..9d7b6de4ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railtie.rb @@ -0,0 +1,283 @@ +# 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.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.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.extend ActiveSupport::Logger.broadcast console + end + ActiveRecord::Base.verbose_query_logs = false + end + + runner do + require "active_record/base" + end + + initializer "active_record.initialize_timezone" do + ActiveSupport.on_load(:active_record) do + self.time_zone_aware_attributes = true + self.default_timezone = :utc + 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.delete(: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.database_selector" do + if options = config.active_record.delete(:database_selector) + resolver = config.active_record.delete(:database_resolver) + operations = config.active_record.delete(:database_resolver_context) + config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options + end + end + + initializer "Check for 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.check_schema_cache_dump" do + check_schema_cache_dump_version = config.active_record.delete(:check_schema_cache_dump_version) + + if config.active_record.delete(:use_schema_cache_dump) + config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first + + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename( + db_config.name, + schema_cache_path: db_config&.schema_cache_path + ) + + cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename) + next if cache.nil? + + if check_schema_cache_dump_version + current_version = begin + ActiveRecord::Migrator.current_version + rescue ActiveRecordError => error + warn "Failed to validate the schema cache because of #{error.class}: #{error.message}" + nil + end + next if current_version.nil? + + if cache.version != current_version + warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}." + next + end + end + + connection_pool.set_schema_cache(cache) + end + end + end + end + + initializer "active_record.define_attribute_methods" do |app| + config.after_initialize do + ActiveSupport.on_load(:active_record) do + if app.config.eager_load + begin + descendants.each do |model| + # SchemaMigration and InternalMetadata both override `table_exists?` + # to bypass the schema cache, so skip them to avoid the extra queries. + next if model._internal? + + # If the schema cache was loaded from a dump, we can use it without connecting + schema_cache = model.connection_pool.schema_cache + + # If there's no connection yet, we avoid connecting. + schema_cache ||= model.connected? && model.connection.schema_cache + + # If the schema cache doesn't have the columns + # hash for the model cached, `define_attribute_methods` would trigger a query. + if schema_cache && schema_cache.columns_hash?(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.warn_on_records_fetched_greater_than" do + if config.active_record.warn_on_records_fetched_greater_than + ActiveSupport.on_load(:active_record) do + require "active_record/relation/record_fetch_warning" + end + end + end + + initializer "active_record.set_configs" do |app| + ActiveSupport.on_load(:active_record) do + configs = app.config.active_record + + configs.each do |k, v| + send "#{k}=", v + 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 + if ActiveRecord::Base.legacy_connection_handling + self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler } + end + self.configurations = Rails.application.config.database_configuration + establish_connection + end + end + + # Expose database runtime to controller 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 + 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.clear_reloadable_connections! + end + end + end + end + + initializer "active_record.set_executor_hooks" do + ActiveRecord::QueryCache.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. + + clear_active_connections! + flush_idle_connections! + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/console_sandbox.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/console_sandbox.rb new file mode 100644 index 0000000000..70af43925e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/controller_runtime.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/controller_runtime.rb new file mode 100644 index 0000000000..309441a057 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/controller_runtime.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_record/log_subscriber" + +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] + messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime + messages + end + 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::LogSubscriber.reset_runtime + super + end + + def cleanup_view_runtime + if logger && logger.info? && ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render + runtime = super + db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime += db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + def append_info_to_payload(payload) + super + if ActiveRecord::Base.connected? + payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/databases.rake b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/databases.rake new file mode 100644 index 0000000000..216daa7c90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/railties/databases.rake @@ -0,0 +1,708 @@ +# 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 + raise ActiveRecord::EnvironmentStorageError unless ActiveRecord::InternalMetadata.enabled? + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.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 "Creates 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 "Drops 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 + original_db_config = ActiveRecord::Base.connection_db_config + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate + end + db_namespace["_dump"].invoke + ensure + ActiveRecord::Base.establish_connection(original_db_config) + end + + # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false + task :_dump do + if ActiveRecord::Base.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::Base.dump_schema_after_migration is set to false + task name do + if ActiveRecord::Base.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 + original_db_config = ActiveRecord::Base.connection_db_config + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate + db_namespace["_dump:#{name}"].invoke + ensure + ActiveRecord::Base.establish_connection(original_db_config) + end + end + + desc "Rolls back the database one migration and re-migrates 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 "Rolls back #{name} database one migration and re-migrates 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:migrate"] + + desc 'Runs 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::Base.connection.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| + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.check_target_version + ActiveRecord::Base.connection.migration_context.run( + :up, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + + db_namespace["_dump"].invoke + end + end + end + + desc 'Runs 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::Base.connection.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| + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.check_target_version + ActiveRecord::Base.connection.migration_context.run( + :down, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + + db_namespace["_dump"].invoke + end + end + end + + desc "Display status of migrations" + task status: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + 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 + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.migrate_status + 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 + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Base.connection.migration_context.rollback(step) + + db_namespace["_dump"].invoke + end + end + end + + desc "Rolls 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") + + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + + ActiveRecord::Base.connection.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::Base.connection.migration_context.forward(step) + db_namespace["_dump"].invoke + end + + desc "Drops and recreates the database from db/schema.rb for the current environment and loads the seeds." + task reset: [ "db:drop", "db:setup" ] + + # desc "Retrieves the charset for the current environment's database" + task charset: :load_config do + puts ActiveRecord::Tasks::DatabaseTasks.charset_current + end + + # desc "Retrieves 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 "Retrieves the current schema version number" + task version: :load_config do + puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}" + end + + # desc "Raises an error if there are pending migrations" + task abort_if_pending_migrations: :load_config do + pending_migrations = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).flat_map do |db_config| + ActiveRecord::Base.establish_connection(db_config) + + ActiveRecord::Base.connection.migration_context.open.pending_migrations + end + + 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 + ensure + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + + namespace :abort_if_pending_migrations do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Raises an error if there are pending migrations for #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + + pending_migrations = ActiveRecord::Base.connection.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 + + desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)" + task setup: ["db:create", :environment, "db:schema:load", :seed] + + desc "Runs setup if database does not exist, or runs migrations if it does" + task prepare: :load_config do + seed = false + + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + + # Skipped when no database + ActiveRecord::Tasks::DatabaseTasks.migrate + + if ActiveRecord::Base.dump_schema_after_migration + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ActiveRecord::Base.schema_format) + end + rescue ActiveRecord::NoDatabaseError + config_name = db_config.name + ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, config_name) + + if File.exist?(ActiveRecord::Tasks::DatabaseTasks.dump_filename(config_name)) + ActiveRecord::Tasks::DatabaseTasks.load_schema( + db_config, + ActiveRecord::Base.schema_format, + nil + ) + else + ActiveRecord::Tasks::DatabaseTasks.migrate + end + + seed = true + end + + ActiveRecord::Base.establish_connection + ActiveRecord::Tasks::DatabaseTasks.load_seed if seed + end + + desc "Loads 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 "Truncates tables of each database for current environment and loads the seeds" + task replant: [:load_config, :truncate_all, :seed] + end + + namespace :fixtures do + desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (e.g. spec/fixtures) using 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 "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`)" + task dump: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config) + end + + db_namespace["schema:dump"].reenable + end + + desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the database" + task load: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"]) + end + + task load_if_ruby: ["db:create", :environment] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:schema:load_if_ruby` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :ruby` to use `schema.rb` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) for #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name) + ActiveRecord::Base.establish_connection(db_config) + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config) + db_namespace["schema:dump:#{name}"].reenable + end + end + end + + namespace :load do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, ENV["SCHEMA"]) + end + end + end + + namespace :cache do + desc "Creates a db/schema_cache.yml file." + task dump: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + ActiveRecord::Base.establish_connection(db_config) + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename( + db_config.name, + schema_cache_path: db_config.schema_cache_path, + ) + ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache( + ActiveRecord::Base.connection, + filename, + ) + end + end + + desc "Clears 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.name, + schema_cache_path: db_config.schema_cache_path, + ) + ActiveRecord::Tasks::DatabaseTasks.clear_schema_cache( + filename, + ) + end + end + end + end + + namespace :structure do + desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task dump: :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:dump` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:dump` instead. + MSG + + db_namespace["schema:dump"].invoke + db_namespace["structure:dump"].reenable + end + + desc "Recreates the databases from the structure.sql file" + task load: [:load_config, :check_protected_environments] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke + end + + task load_if_sql: ["db:create", :environment] do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load_if_sql` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load` instead. + MSG + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :sql + end + + namespace :dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Dumps the #{name} database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task name => :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:dump:#{name}` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:dump:#{name}` instead. + MSG + db_namespace["schema:dump:#{name}"].invoke + db_namespace["structure:dump:#{name}"].reenable + end + end + end + + namespace :load do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Recreates the #{name} database from the structure.sql file" + task name => :load_config do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:structure:load:#{name}` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load:#{name}` instead. + MSG + db_namespace["schema:load:#{name}"].invoke + end + end + end + end + + namespace :test do + # desc "Recreate the test database from the current schema" + task load: %w(db:test:purge) do + db_namespace["test:load_schema"].invoke + end + + # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `config.active_record.schema_format`)" + task load_schema: %w(db:test:purge) do + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? + ActiveRecord::Schema.verbose = false + ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config| + filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, filename) + end + ensure + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + end + + # desc "Recreate the test database from an existent structure.sql file" + task load_structure: %w(db:test:purge) do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:test:load_structure` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:test:load_schema` instead. + MSG + db_namespace["test:load_schema"].invoke + 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"].invoke + end + end + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Recreate the #{name} test database" + namespace :load do + task name => "db:test:purge:#{name}" do + db_namespace["test:load_schema:#{name}"].invoke + end + end + + # desc "Recreate the #{name} test database from an existent schema.rb file" + namespace :load_schema do + task name => "db:test:purge:#{name}" do + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? + ActiveRecord::Schema.verbose = false + filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(name) + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name) + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ActiveRecord::Base.schema_format, filename) + ensure + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym) + end + end + end + + # desc "Recreate the #{name} test database from an existent structure.sql file" + namespace :load_structure do + task name => "db:test:purge:#{name}" do + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `bin/rails db:test:load_structure:#{name}` is deprecated and will be removed in Rails 7.0. + Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:test:load_structure:#{name}` instead. + MSG + db_namespace["test:load_schema:#{name}"].invoke + end + end + + # desc "Empty the #{name} test database" + namespace :purge do + task name => %w(load_config check_protected_environments) do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name) + ActiveRecord::Tasks::DatabaseTasks.purge(db_config) + end + end + + # desc 'Load the #{name} database test schema' + namespace :prepare do + task name => :load_config do + db_namespace["test:load:#{name}"].invoke + end + end + end + end +end + +namespace :railties do + namespace :install do + # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" + 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 + + ActiveRecord::Migration.copy(ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first, railties, + on_skip: on_skip, on_copy: on_copy) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/readonly_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/readonly_attributes.rb new file mode 100644 index 0000000000..c851ed52c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/readonly_attributes.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + 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 but update operations will + # ignore these fields. + def attr_readonly(*attributes) + self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || []) + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/reflection.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/reflection.rb new file mode 100644 index 0000000000..4ec3f8a667 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/reflection.rb @@ -0,0 +1,1056 @@ +# 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: {} + 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_s + 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_s => 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 + + # \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_s] + end + + # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value. + # + # Account.reflections # => {"balance" => AggregateReflection} + # + def reflections + @__reflections ||= begin + ref = {} + + _reflections.each do |name, reflection| + parent_reflection = reflection.parent_reflection + + if parent_reflection + parent_name = parent_reflection.name + ref[parent_name.to_s] = parent_reflection + else + ref[name] = reflection + end + end + + ref + 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 = 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) + reflections[association.to_s] + end + + def _reflect_on_association(association) #:nodoc: + _reflections[association.to_s] + end + + # Returns an array of AssociationReflection objects for all associations which have :autosave enabled. + def reflect_on_all_autosave_associations + reflections.values.select { |reflection| reflection.options[:autosave] } + end + + def clear_reflections_cache # :nodoc: + @__reflections = nil + 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 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 = predicate_builder(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 = join_primary_key + foreign_key = join_foreign_key + + klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key])) + + if klass.finder_needs_type_condition? + klass_scope.where!(klass.send(:type_condition, table)) + end + + klass_scope + end + + def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc: + if scope + [scope_for(build_scope(table, predicate_builder, klass))] + else + [] + end + end + + def klass_join_scope(table, predicate_builder) # :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 ||= if belongs_to? + if options[:counter_cache] == true + -"#{active_record.name.demodulize.underscore.pluralize}_count" + elsif options[:counter_cache] + -options[:counter_cache].to_s + end + else + -(options[:counter_cache]&.to_s || "#{name}_count") + end + end + + def inverse_of + return unless inverse_name + + @inverse_of ||= klass._reflect_on_association inverse_name + end + + def check_validity_of_inverse! + unless polymorphic? + if has_inverse? && inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end + end + end + + # This shit is nasty. 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 + return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache) + @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse| + inverse.counter_cache_column == counter_cache_column + end + 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 a counter cache should be used for this association. + # + # 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 + + 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 = predicate_builder(table), klass = self.klass) + Relation.create( + klass, + table: table, + predicate_builder: predicate_builder + ) + end + + def strict_loading? + options[:strict_loading] + end + + protected + def actual_source_reflection # FIXME: this is a horrible name + self + end + + private + def predicate_builder(table) + PredicateBuilder.new(TableMetadata.new(klass, table)) + end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + 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) + @name = name + @scope = scope + @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 ||= 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 + 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 + active_record.send(:compute_type, name) + 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] + @constructable = calculate_constructable(macro, options) + + if options[:class_name] && options[:class_name].class == Class + raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string." + end + end + + def association_scope_cache(klass, owner, &block) + key = self + if polymorphic? + key = [key, owner._read_attribute(@foreign_type)] + end + klass.cached_find_by_statement(key, &block) + end + + def constructable? # :nodoc: + @constructable + end + + def join_table + @join_table ||= -(options[:join_table]&.to_s || derive_join_table) + end + + def foreign_key + @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key) + 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 + @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record)) + end + + def join_primary_key(klass = nil) + foreign_key + end + + def join_foreign_key + active_record_primary_key + end + + def check_validity! + check_validity_of_inverse! + end + + def check_preloadable! + return unless scope + + unless scope.arity == 0 + raise ArgumentError, <<-MSG.squish + The association scope '#{name}' is instance dependent (the scope + block takes an argument). Preloading instance dependent scopes is + not supported. + MSG + end + end + alias :check_eager_loadable! :check_preloadable! + + def join_id_for(owner) # :nodoc: + owner[join_foreign_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 + + VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] + INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key] + + 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 + def calculate_constructable(macro, options) + true + end + + # 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) + rescue NameError + # 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) + inverse_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 && + foreign_key == reflection.foreign_key && + klass <= reflection.active_record && + can_find_inverse_of_automatically?(reflection) + 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. + # + # Anything with a scope can additionally ruin our attempt at finding an + # inverse, so we exclude reflections with scopes. + def can_find_inverse_of_automatically?(reflection) + reflection.options[:inverse_of] != false && + VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && + !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && + !reflection.scope + 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 + if belongs_to? + "#{name}_id" + elsif options[:as] + "#{options[:as]}_id" + else + active_record.name.foreign_key + 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 + + private + def calculate_constructable(macro, options) + !options[:through] + 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 ||= -primary_key.to_s + 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 + + def calculate_constructable(macro, options) + !polymorphic? + 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) + @delegate_reflection = delegate_reflection + @klass = delegate_reflection.options[:anonymous_class] + @source_reflection_name = delegate_reflection.options[:source] + end + + def through_reflection? + true + end + + def klass + @klass ||= delegate_reflection.compute_class(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 + 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, klass = self.klass) # :nodoc: + source_reflection.join_scopes(table, predicate_builder, klass) + 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: + return @source_reflection_name if @source_reflection_name + + 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 + + @source_reflection_name = names.first + 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.reflections.keys.map(&:to_sym) + + 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) + @reflection = reflection + @previous_reflection = previous_reflection + end + + def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc: + scopes = @previous_reflection.join_scopes(table, predicate_builder) + super + scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &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) + @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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation.rb new file mode 100644 index 0000000000..ca097ab7f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation.rb @@ -0,0 +1,893 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Relation + class Relation + MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, + :order, :joins, :left_outer_joins, :references, + :extending, :unscope, :optimizer_hints, :annotate] + + 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, :group, :having] + + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS + + include Enumerable + include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation + + attr_reader :table, :klass, :loaded, :predicate_builder + attr_accessor :skip_preloading_value + alias :model :klass + alias :loaded? :loaded + alias :locked? :lock_value + + def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) + @klass = klass + @table = table + @values = values + @loaded = false + @predicate_builder = predicate_builder + @delegate_to_klass = false + end + + def initialize_copy(other) + @values = @values.dup + reset + end + + def arel_attribute(name) # :nodoc: + table[name] + end + deprecate :arel_attribute + + def bind_attribute(name, value) # :nodoc: + if reflection = klass._reflect_on_association(name) + name = reflection.foreign_key + value = value.read_attribute(reflection.klass.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) + block = current_scope_restoring_block(&block) + scoping { _new(attributes, &block) } + 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 + # # different 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. + # + # Please note this method is not atomic, it runs first a SELECT, and if + # there are no results an INSERT is attempted. If there are other threads + # or processes there is a race condition between both calls and it could + # be the case that you end up with two similar records. + # + # If this might be a problem for your application, please see #create_or_find_by. + def find_or_create_by(attributes, &block) + find_by(attributes) || create(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!(attributes, &block) + end + + # Attempts to create a record with the given attributes in a table that has a unique 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 avoids the problem of stale reads between the SELECT + # and the INSERT, as that method needs to first query the table, then attempt to insert a row + # if none is found. + # + # There are several drawbacks to #create_or_find_by, though: + # + # * The underlying table must have the relevant columns defined with unique 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). + # + # 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) + transaction(requires_new: true) { create(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + find_by!(attributes) + 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) + transaction(requires_new: true) { create!(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + find_by!(attributes) + 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. + # + # 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. + # + # Please see further details in the + # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain]. + def explain + exec_explain(collecting_queries_for_explain { exec_queries }) + 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 + loaded? ? @records.length : count(:all) + end + + # Returns true if there are no records. + def empty? + return @records.empty? if loaded? + !exists? + end + + # Returns true if there are no records. + def none? + return super if block_given? + empty? + end + + # Returns true if there are any records. + def any? + return super if block_given? + !empty? + end + + # Returns true if there is exactly one record. + def one? + return super if block_given? + limit_value ? records.one? : size == 1 + end + + # Returns true if there is more than one record. + def many? + return super if block_given? + limit_value ? records.many? : size > 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] ||= klass.collection_cache_key(self, timestamp_column) + end + + def compute_cache_key(timestamp_column = :updated_at) # :nodoc: + query_signature = ActiveSupport::Digest.hexdigest(to_sql) + key = "#{klass.model_name.cache_key}/query-#{query_signature}" + + if 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 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? || distinct_value + 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 + + column = connection.visitor.compile(table[timestamp_column]) + select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" + + if collection.has_limit_or_offset? + query = collection.select("#{column} AS collection_cache_key_timestamp") + 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 = connection.select_rows(arel, nil).first + + if size + column_type = klass.type_for_attribute(timestamp_column) + timestamp = column_type.deserialize(timestamp) + else + size = 0 + end + end + + if timestamp + "#{size}-#{timestamp.utc.to_s(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 + # + # Please check unscoped if you want to remove all previous scopes (including + # the default_scope) during the execution of a block. + def scoping + already_in_scope? ? yield : _scoping(self) { yield } + end + + def _exec_scope(*args, &block) # :nodoc: + @delegate_to_klass = true + _scoping(nil) { instance_exec(*args, &block) || self } + ensure + @delegate_to_klass = 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. + # + # ==== 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') + def update_all(updates) + raise ArgumentError, "Empty list of attributes to change" if updates.blank? + + arel = eager_loading? ? apply_join_dependency.arel : build_arel + arel.source.left = table + + stmt = Arel::UpdateManager.new + stmt.table(arel.source) + stmt.key = table[primary_key] + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints + + if updates.is_a?(Hash) + if klass.locking_enabled? && + !updates.key?(klass.locking_column) && + !updates.key?(klass.locking_column.to_sym) + attr = table[klass.locking_column] + updates[attr.name] = _increment_attribute(attr) + end + stmt.set _substitute_values(updates) + else + stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) + end + + klass.connection.update(stmt, "#{klass} Update All").tap { reset } + end + + def update(id = :all, attributes) # :nodoc: + if id == :all + each { |record| record.update(attributes) } + else + klass.update(id, attributes) + end + 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 = klass.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 klass.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 + 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 + + arel = eager_loading? ? apply_join_dependency.arel : build_arel + arel.source.left = table + + stmt = Arel::DeleteManager.new + stmt.from(arel.source) + stmt.key = table[primary_key] + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints + + klass.connection.delete(stmt, "#{klass} Destroy").tap { reset } + 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 + + # 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) + unless loaded? + @records = exec_queries(&block) + @loaded = true + end + + self + end + + # Forces reloading of relation. + def reload + reset + load + end + + def reset + @delegate_to_klass = false + @to_sql = @arel = @loaded = @should_eager_load = nil + @offsets = @take = nil + @cache_keys = nil + @records = [].freeze + 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 ||= begin + if eager_loading? + apply_join_dependency do |relation, join_dependency| + relation = join_dependency.apply_column_aliases(relation) + relation.to_sql + end + else + conn = klass.connection + 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 = klass.table_name) + where_clause.to_h(relation_table_name) + end + + def scope_for_create + hash = where_clause.to_h(klass.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(q) + q.pp(records) + end + + # Returns true if relation is blank. + def blank? + records.blank? + end + + def values + @values.dup + 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 == klass.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(connection, 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? + preloader = nil + scope = strict_loading_value ? StrictLoadingScope : nil + preload.each do |associations| + preloader ||= build_preloader + preloader.preload records, associations, scope + end + end + + protected + def load_records(records) + @records = records.freeze + @loaded = true + end + + def null_relation? # :nodoc: + is_a?(NullRelation) + end + + private + def already_in_scope? + @delegate_to_klass && klass.current_scope(true) + end + + def current_scope_restoring_block(&block) + current_scope = klass.current_scope(true) + -> record do + klass.current_scope = current_scope + yield record if block_given? + end + end + + def _new(attributes, &block) + klass.new(attributes, &block) + end + + def _create(attributes, &block) + klass.create(attributes, &block) + end + + def _create!(attributes, &block) + klass.create!(attributes, &block) + end + + def _scoping(scope) + previous, klass.current_scope = klass.current_scope(true), scope + yield + ensure + klass.current_scope = previous + end + + def _substitute_values(values) + values.map do |name, value| + attr = table[name] + unless Arel.arel_node?(value) + type = klass.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 + records = + if where_clause.contradiction? + [] + elsif eager_loading? + apply_join_dependency do |relation, join_dependency| + if relation.null_relation? + [] + else + relation = join_dependency.apply_column_aliases(relation) + rows = connection.select_all(relation.arel, "SQL") + join_dependency.instantiate(rows, strict_loading_value, &block) + end.freeze + end + else + klass.find_by_sql(arel, &block).freeze + end + + preload_associations(records) unless skip_preloading_value + + records.each(&:readonly!) if readonly_value + records.each(&:strict_loading!) if strict_loading_value + + records + end + end + + def skip_query_cache_if_necessary + if skip_query_cache_value + uncached do + yield + end + else + yield + end + end + + def build_preloader + ActiveRecord::Associations::Preloader.new + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches.rb new file mode 100644 index 0000000000..1917dcc164 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +require "active_record/relation/batches/batch_enumerator" + +module ActiveRecord + module Batches + ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order." + + # 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + if block_given? + find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records| + records.each { |record| yield record } + end + else + enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do + relation = self + apply_limits(relation, start, finish, 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + 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, order: order) do + total = apply_limits(relation, start, finish, 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, 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 primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key 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. + # * :order - Specifies the primary key order (can be :asc or :desc). Defaults to :asc. + # + # 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 primary key is + # orderable (e.g. an integer or string). + # + # 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, order: :asc) + relation = self + unless block_given? + return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self) + end + + unless [:asc, :desc].include?(order) + raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}" + end + + if arel.orders.present? + act_on_ignored_order(error_on_ignore) + end + + batch_limit = of + if limit_value + remaining = limit_value + batch_limit = remaining if remaining < batch_limit + end + + relation = relation.reorder(batch_order(order)).limit(batch_limit) + relation = apply_limits(relation, start, finish, order) + relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching + batch_relation = relation + + loop do + if load + records = batch_relation.records + ids = records.map(&:id) + yielded_relation = where(primary_key => ids) + yielded_relation.load_records(records) + else + ids = batch_relation.pluck(primary_key) + yielded_relation = where(primary_key => ids) + end + + break if ids.empty? + + primary_key_offset = ids.last + raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset + + yield yielded_relation + + break if ids.length < batch_limit + + if limit_value + remaining -= ids.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_relation = relation.where( + predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt] + ) + end + end + + private + def apply_limits(relation, start, finish, order) + relation = apply_start_limit(relation, start, order) if start + relation = apply_finish_limit(relation, finish, order) if finish + relation + end + + def apply_start_limit(relation, start, order) + relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq]) + end + + def apply_finish_limit(relation, finish, order) + relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq]) + end + + def batch_order(order) + table[primary_key].public_send(order) + end + + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore) + + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif logger + logger.warn(ORDER_IGNORE_MESSAGE) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches/batch_enumerator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches/batch_enumerator.rb new file mode 100644 index 0000000000..1e9c07f6df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/batches/batch_enumerator.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module ActiveRecord + module Batches + class BatchEnumerator + include Enumerable + + def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc: + @of = of + @relation = relation + @start = start + @finish = finish + 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 + return to_enum(:each_record) unless block_given? + + @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation| + relation.records.each { |record| yield record } + 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 + + # Destroys records in batches. + # + # Person.where("age < 10").in_batches.destroy_all + # + # See Relation#destroy_all for details of how each batch is destroyed. + def destroy_all + each(&:destroy_all) + 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 + enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false) + return enum.each { |relation| yield relation } if block_given? + enum + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/calculations.rb new file mode 100644 index 0000000000..9e93b77447 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/calculations.rb @@ -0,0 +1,485 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Calculations + # 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", "business"]=>0, ["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. + 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 + + # 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 + + # 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 + + # 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 + + # 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 + def sum(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(:sum, column_name) + end + 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) + if has_include?(column_name) + relation = apply_join_dependency + + if operation.to_s.downcase == "count" + unless distinct_value || distinct_select?(column_name || select_for_count) + relation.distinct! + relation.select_values = [ klass.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 a bunch of records just to grab the attributes you want. + # + # 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] + # + # 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 loaded? && all_attributes?(column_names) + return records.pluck(*column_names) + end + + if has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + else + klass.disallow_raw_sql!(column_names) + columns = arel_columns(column_names) + relation = spawn + relation.select_values = columns + result = skip_query_cache_if_necessary do + if where_clause.contradiction? + ActiveRecord::Result.new([], []) + else + klass.connection.select_all(relation.arel, nil) + end + end + type_cast_pluck_values(result, columns) + end + 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) + return records.pick(*column_names) + end + + limit(1).pluck(*column_names).first + end + + # Pluck all the ID's for the relation using the table's primary key + # + # Person.ids # SELECT people.id FROM people + # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id + def ids + pluck primary_key + end + + private + def all_attributes?(column_names) + (column_names.map(&:to_s) - @klass.attribute_names - @klass.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 aggregate_column(column_name) + return column_name if Arel::Expressions === column_name + + arel_column(column_name.to_s) do |name| + Arel.sql(column_name == :all ? "*" : name) + end + 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 operation == "count" && (column_name == :all && distinct || has_limit_or_offset?) + # Shortcut when limit is zero. + return 0 if limit_value == 0 + + 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 = 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 + + result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) } + + type_cast_calculated_value(result.cast_values.first, operation) do |value| + 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 + type.deserialize(value) + 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 + + unless group_fields == group_values + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `#{operation}` with group by duplicated fields does no longer affect to result in Rails 7.0. + To migrate to Rails 7.0's behavior, use `uniq!(:group)` to deduplicate group fields + (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`). + MSG + group_fields = group_values + end + + if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym) + association = klass._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 + group_fields = arel_columns(group_fields) + + group_aliases = group_fields.map { |field| + field = connection.visitor.compile(field) if Arel.arel_node?(field) + column_alias_for(field.to_s.downcase) + } + group_columns = group_aliases.zip(group_fields) + + column = aggregate_column(column_name) + column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}") + select_value = operation_over_aggregate_column(column, operation, distinct) + select_value.as(column_alias) + + select_values = [select_value] + select_values += self.select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end + } + + relation = except(:group).distinct!(false) + relation.group_values = group_fields + relation.select_values = select_values + + calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) } + + 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] = 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 + + type = nil + 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) do |value| + unless type + 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.deserialize(value) + end + end + end + + # 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 type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + @klass.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 + klass.attribute_types + else + join_dependencies = nil + columns.map.with_index do |column, i| + column.try(:type_caster) || + klass.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[name] || Type.default_value + end + end + end + result.cast_values(cast_types) + end + + def type_cast_calculated_value(value, operation) + case operation + when "count" + value.to_i + when "sum" + yield value || 0 + when "average" + value&.respond_to?(:to_d) ? value.to_d : value + else # "minimum", "maximum" + yield value + end + end + + def select_for_count + if select_values.present? + return select_values.first if select_values.one? + select_values.join(", ") + else + :all + end + 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 = [ aggregate_column(column_name).as(column_alias) ] + end + + subquery_alias = Arel.sql("subquery_for_count") + select_value = operation_over_aggregate_column(column_alias, "count", false) + + relation.build_subquery(subquery_alias, select_value) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/delegation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/delegation.rb new file mode 100644 index 0000000000..1c5fb3cdf3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/delegation.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "mutex_m" +require "active_support/core_ext/module/delegation" + +module ActiveRecord + module Delegation # :nodoc: + module DelegateCache # :nodoc: + def relation_delegate_class(klass) + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache + @relation_delegate_cache = cache = {} + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation + ].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: + include Mutex_m + + def generate_method(method) + synchronize do + return if method_defined?(method) + + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s) + definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block" + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(#{definition}) + scoping { klass.#{method}(#{definition}) } + end + RUBY + else + define_method(method) do |*args, &block| + scoping { klass.public_send(method, *args, &block) } + end + ruby2_keywords(method) if respond_to?(:ruby2_keywords, true) + 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 klass of a relation, so we create a subclass of Relation + # for each different klass, and the delegations are compiled into that subclass only. + + delegate :to_xml, :encode_with, :length, :each, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :as_json, + :shuffle, :split, :slice, :index, :rindex, to: :records + + delegate :primary_key, :connection, to: :klass + + module ClassSpecificRelation # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + def name + superclass.name + end + end + + private + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + @klass.generate_relation_method(method) + scoping { @klass.public_send(method, *args, &block) } + else + super + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end + + module ClassMethods # :nodoc: + def create(klass, *args, **kwargs) + relation_class_for(klass).new(klass, *args, **kwargs) + end + + private + def relation_class_for(klass) + klass.relation_delegate_class(self) + end + end + + private + def respond_to_missing?(method, _) + super || @klass.respond_to?(method) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/finder_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/finder_methods.rb new file mode 100644 index 0000000000..b3b8f892d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/finder_methods.rb @@ -0,0 +1,590 @@ +# 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 (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). + # 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) + # Person.find([1]) # returns an array for the object with ID = 1 + # Person.where("administrator = 1").order("created_on DESC").find(1) + # + # 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. + 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 + + # 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) + check_reorder_deprecation unless loaded? + + 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) + 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 { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 } + 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) + if loaded? || offset_value || limit_value || having_clause.any? + records.include?(record) + else + record.is_a?(klass) && exists?(record.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(klass)}]" unless where_clause.empty? + + name = @klass.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 check_reorder_deprecation + if !order_values.empty? && order_values.all?(&:blank?) + blank_value = order_values.first + ActiveSupport::Deprecation.warn(<<~MSG.squish) + `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer + takes non-deterministic result in Rails 7.0. + To continue taking non-deterministic result, use `.take` / `.take!` instead. + MSG + end + end + + 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 && !( + 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 + ) + ) + if has_limit_or_offset? + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + end + relation.limit_value = relation.offset_value = nil + end + + if block_given? + yield relation, join_dependency + else + relation + end + end + + def limited_ids_for(relation) + values = @klass.connection.columns_for_distinct( + connection.visitor.compile(table[primary_key]), + relation.order_values + ) + + relation = relation.except(:select).select(values).distinct! + + id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") } + id_rows.map(&:last) + end + + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end + + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + + expects_array = ids.first.kind_of?(Array) + return [] if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + model_name = @klass.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 = where(primary_key => id) + 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? + + result = where(primary_key => ids).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) || [] + + result = except(:limit, :offset).where(primary_key => ids).records + + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) + + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.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 equal?(relation) || has_limit_or_offset? + relation.records[-index] + else + relation.last(index)[-index] + end + end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end + + def ordered_relation + if order_values.empty? && (implicit_order_column || primary_key) + if implicit_order_column && primary_key && implicit_order_column != primary_key + order(table[implicit_order_column].asc, table[primary_key].asc) + else + order(table[implicit_order_column || primary_key].asc) + end + else + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/from_clause.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/from_clause.rb new file mode 100644 index 0000000000..d738154a50 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/merger.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/merger.rb new file mode 100644 index 0000000000..e436373012 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/merger.rb @@ -0,0 +1,185 @@ +# 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, rewhere = nil) + hash.assert_valid_keys(*Relation::VALUE_METHODS) + + @relation = relation + @hash = hash + @rewhere = rewhere + end + + def merge + Merger.new(relation, other, @rewhere).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.klass, + 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, rewhere = nil) + @relation = relation + @values = other.values + @other = other + @rewhere = rewhere + end + + NORMAL_VALUES = Relation::VALUE_METHODS - + Relation::CLAUSE_METHODS - + [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: + + def normal_values + NORMAL_VALUES + end + + 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) + if name == :select + relation._select!(*value) + else + relation.public_send("#{name}!", *value) + end + end + end + + merge_multi_values + merge_single_values + merge_clauses + merge_preloads + merge_joins + merge_outer_joins + + relation + end + + private + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.klass == relation.klass + 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.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.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.klass == relation.klass + 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.klass == relation.klass + 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, @rewhere) + 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.klass.base_class == other.klass.base_class + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 0000000000..f839578bcc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,169 @@ +# 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" + + # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")). + # TODO: Remove the constant alias once Rails 6.1 has released. + BaseHandler = BasicObjectHandler + + 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) + elsif key.include?(".") + result << Arel.sql(key.split(".").first) + 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) + attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name)) + Arel::Nodes::BindParam.new(attr) + end + + def resolve_arel_attribute(table_name, column_name, &block) + table.associated_table(table_name, &block).arel_table[column_name] + end + + protected + def expand_from_hash(attributes, &block) + return ["1=0"] if attributes.empty? + + attributes.flat_map do |key, value| + if 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 = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) } + Arel::Nodes::Grouping.new(queries) + end + end + + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.select do |k, v| + k.include?(".") && !v.is_a?(Hash) + end + + dot_notation.each_key do |key| + table_name, column_name = key.split(".") + value = attributes.delete(key) + attributes[table_name] ||= {} + + attributes[table_name] = attributes[table_name].merge(column_name => value) + end + + attributes + end + + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/array_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/array_handler.rb new file mode 100644 index 0000000000..f07afd0d0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.extract!(&:nil?) + 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 + + unless nils.empty? + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/association_query_value.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/association_query_value.rb new file mode 100644 index 0000000000..c7019bee4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -0,0 +1,42 @@ +# 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 + [ associated_table.join_foreign_key => ids ] + end + + private + attr_reader :associated_table, :value + + def ids + case value + when Relation + value.select_values.empty? ? value.select(primary_key) : value + 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 convert_to_id(value) + if value.respond_to?(primary_key) + value.public_send(primary_key) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/basic_object_handler.rb new file mode 100644 index 0000000000..e8c9f60860 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb new file mode 100644 index 0000000000..fd2762959a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -0,0 +1,57 @@ +# 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) + case value + when Base + value.class + when Relation + value.klass + end + end + + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key(value)) + when Relation + value.select(primary_key(value)) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/range_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/range_handler.rb new file mode 100644 index 0000000000..2ea27c8490 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/relation_handler.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/relation_handler.rb new file mode 100644 index 0000000000..f310117c5e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -0,0 +1,19 @@ +# 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? + value = value.select(value.table[value.klass.primary_key]) + end + + attribute.in(value.arel) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_attribute.rb new file mode 100644 index 0000000000..cd18f27330 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveRecord + class Relation + class QueryAttribute < ActiveModel::Attribute # :nodoc: + def type_cast(value) + value + end + + def value_for_database + @value_for_database ||= super + 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, true) && value_for_database.nil? + end + rescue ::RangeError + end + + def infinite? + infinity?(value_before_type_cast) || infinity?(value_for_database) + rescue ::RangeError + end + + def unboundable? + if defined?(@_unboundable) + @_unboundable + else + value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute) + @_unboundable = nil + end + rescue ::RangeError + @_unboundable = type.cast(value_before_type_cast) <=> 0 + end + + private + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_methods.rb new file mode 100644 index 0000000000..8be5d5b0a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/query_methods.rb @@ -0,0 +1,1524 @@ +# frozen_string_literal: true + +require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" +require "active_record/relation/where_clause" +require "active_model/forbidden_attributes_protection" +require "active_support/core_ext/array/wrap" + +module ActiveRecord + module QueryMethods + extend ActiveSupport::Concern + + include ActiveModel::ForbiddenAttributesProtection + + # WhereChain objects act as placeholder for queries in which #where does not have any parameter. + # In this case, #where must be chained with #not to return a new relation. + class WhereChain + def initialize(scope) + @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') + 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 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(*args) + args.each do |arg| + reflection = @scope.klass._reflect_on_association(arg) + opts = { reflection.table_name => { reflection.association_primary_key => nil } } + @scope.left_outer_joins!(arg) + @scope.where!(opts) + end + + @scope + 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_mutability! # assert_mutability! + @values[:#{name}] = value # @values[:includes] = value + end # end + CODE + end + + alias extensions extending_values + + # Specify relationships to be included in the result set. For + # example: + # + # users = User.includes(:address) + # users.each do |user| + # user.address.city + # end + # + # allows you to access the +address+ attribute of the +User+ model without + # firing an additional query. This will often result in a + # performance improvement over a simple join. + # + # You can also specify multiple relationships, like this: + # + # users = User.includes(:address, :friends) + # + # Loading nested relationships is possible using a Hash: + # + # users = 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') + # + # Will throw an error, but this will work: + # + # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) + # + # Note that #includes works with association names while #references needs + # the actual table name. + # + # If you pass the conditions via 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' }) + def includes(*args) + check_if_method_has_arguments!(:includes, args) + spawn.includes!(*args) + end + + def includes!(*args) # :nodoc: + self.includes_values |= args + self + end + + # Forces eager loading by performing a LEFT OUTER JOIN on +args+: + # + # User.eager_load(:posts) + # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... + # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = + # # "users"."id" + def eager_load(*args) + check_if_method_has_arguments!(:eager_load, args) + spawn.eager_load!(*args) + end + + def eager_load!(*args) # :nodoc: + self.eager_load_values |= args + self + end + + # Allows preloading of +args+, in the same way that #includes does: + # + # User.preload(:posts) + # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) + def preload(*args) + check_if_method_has_arguments!(:preload, 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 JOINed 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!(:references, 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) + # # => [#] + # + # 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 + 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!(:select, fields, "Call `select' with at least one field.") + spawn._select!(*fields) + end + + def _select!(*fields) # :nodoc: + self.select_values |= fields + 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!(:reselect, 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!(:group, args) + spawn.group!(*args) + end + + def group!(*args) # :nodoc: + self.group_values += args + self + end + + # Allows to specify an order attribute: + # + # User.order(:name) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC + # + # User.order(email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC + # + # User.order(:name, email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC + # + # 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 + def order(*args) + check_if_method_has_arguments!(:order, 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 + + # 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!(:reorder, args) do + sanitize_order_arguments(args) unless args.all?(&:blank?) + 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) unless args.all?(&:blank?) + 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, :from, :readonly, :having, :optimizer_hints]) + + # 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!(:unscope, 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_mutability! + @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 a 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!(:joins, args) + spawn.joins!(*args) + end + + def joins!(*args) # :nodoc: + self.joins_values |= args + self + end + + # Performs a 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) + # + # === 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 #not to return a new relation that negates the where clause. + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # See WhereChain for more details on #not. + # + # === 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) + scope = spawn + where_clause = scope.build_where_clause(conditions) + + scope.unscope!(where: where_clause.extract_attributes) + scope.where_clause += where_clause + scope + 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) + spawn.or!(other) + 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 = self.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: + where!("1=0").extending!(NullRelation) + end + + # Sets readonly attributes for the returned relation. If value is + # true (default), attempting to update a record will result in an error. + # + # users = User.readonly + # users.first.save + # => ActiveRecord::ReadOnlyRecord: User is marked as readonly + 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 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 + # + # Topic.select('a.title').from(Topic.approved, :a) + # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a + # + 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!(:optimizer_hints, 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!(:annotate, 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 + + # Returns the Arel object associated with the relation. + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) + end + + def construct_join_dependency(associations, join_type) # :nodoc: + ActiveRecord::Associations::JoinDependency.new( + klass, 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) + + case opts + when String, Array + parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])] + when Hash + opts = opts.transform_keys do |key| + key = key.to_s + klass.attribute_aliases[key] || key + 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 + + private + 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) + join_dependencies.each do |join_dependency| + join_dependency.each do |join| + yield join + end + end + end + + def build_join_dependencies + associations = joins_values | left_outer_joins_values + associations |= eager_load_values unless eager_load_values.empty? + associations |= includes_values unless includes_values.empty? + + join_dependencies = [] + join_dependencies.unshift construct_join_dependency( + select_association_list(associations, join_dependencies), nil + ) + end + + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end + + def build_arel(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_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 + unless annotates == annotate_values + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Duplicated query annotations are no longer shown in queries in Rails 7.0. + To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations + (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`). + MSG + annotates = annotate_values + end + arel.comment(*annotates) + end + + arel + end + + def build_cast_value(name, value) + cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value) + Arel::Nodes::BindParam.new(cast_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_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 + + class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc: + end + + def build_join_buckets + buckets = Hash.new { |h, k| h[k] = [] } + + unless left_outer_joins_values.empty? + stashed_left_joins = [] + left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end + + if joins_values.empty? + buckets[:association_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 == klass + 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[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join| + if join.is_a?(Arel::Nodes::Join) + buckets[:join_node] << join + 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 + + association_joins = buckets[:association_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 association_joins.empty? && stashed_joins.empty? + alias_tracker = alias_tracker(leading_joins + join_nodes, aliases) + join_dependency = construct_join_dependency(association_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 klass.ignored_columns.any? + arel.project(*klass.column_names.map { |field| table[field] }) + else + arel.project(table[Arel.star]) + end + end + + def arel_columns(columns) + columns.flat_map do |field| + case field + when Symbol + arel_column(field.to_s) do |attr_name| + connection.quote_table_name(attr_name) + end + when String + arel_column(field, &:itself) + when Proc + field.call + else + field + end + end + end + + def arel_column(field) + field = klass.attribute_aliases[field] || field + from = from_clause.name || from_clause.value + + if klass.columns_hash.key?(field) && (!from || table_name_matches?(from)) + table[field] + elsif field.match?(/\A\w+\.\w+\z/) + table, column = field.split(".") + predicate_builder.resolve_arel_attribute(table, column) do + lookup_table_klass_from_join_dependencies(table) + end + else + yield field + end + end + + def table_name_matches?(from) + table_name = Regexp.escape(table.name) + quoted_table_name = Regexp.escape(connection.quote_table_name(table.name)) + /(?:\A|(? warn_on_records_fetched_greater_than + logger.warn "Query fetched #{records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}" + end + end + end + end + + # :stopdoc: + ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload| + QueryRegistry.queries << payload[:sql] + end + # :startdoc: + + class QueryRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :queries + + def initialize + @queries = [] + end + + def reset + @queries.clear + end + end + end + end +end + +ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/spawn_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 0000000000..8636b34434 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,77 @@ +# 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 + # This is overridden by Associations::CollectionProxy + def spawn #:nodoc: + already_in_scope? ? klass.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. + 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: + options = rest.extract_options! + if other.is_a?(Hash) + Relation::HashMerger.new(self, other, options[:rewhere]).merge + elsif other.is_a?(Relation) + Relation::Merger.new(self, other, options[:rewhere]).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.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/where_clause.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/where_clause.rb new file mode 100644 index 0000000000..398935d77a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/relation/where_clause.rb @@ -0,0 +1,239 @@ +# 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, rewhere = nil) + predicates = if rewhere + except_predicates(other.extract_attributes) + else + predicates_unreferenced_by(other) + end + + 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 = Arel::Nodes::Or.new(left, right) + + 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 + + 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 predicates_unreferenced_by(other) + referenced_columns = other.referenced_columns + + predicates.reject do |node| + attr = extract_attribute(node) || begin + node.left if equality_node?(node) && node.left.is_a?(Arel::Predications) + end + next false unless attr + + ref = referenced_columns[attr] + next false unless ref + + if equality_node?(node) && equality_node?(ref) || node == ref + true + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Merging (#{node.to_sql}) and (#{ref.to_sql}) no longer maintain + both conditions, and will be replaced by the latter in Rails 7.0. + To migrate to Rails 7.0's behavior, use `relation.merge(other, rewhere: true)`. + MSG + false + end + end + 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) + case node + when Array + node.map { |v| extract_node_value(v) } + when Arel::Nodes::BindParam, Arel::Nodes::Casted, Arel::Nodes::Quoted + node.value_before_type_cast + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/result.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/result.rb new file mode 100644 index 0000000000..168d95cbf4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/result.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +module ActiveRecord + ### + # 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.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 + + attr_reader :columns, :rows, :column_types + + def initialize(columns, rows, column_types = {}) + @columns = columns + @rows = rows + @hash_rows = nil + @column_types = column_types + 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. + # + # Returns an +Enumerator+ if no block is given. + def each + if block_given? + hash_rows.each { |row| yield row } + else + hash_rows.to_enum { @rows.size } + end + end + + alias :map! :map + alias :collect! :map + deprecate "map!": :map + deprecate "collect!": :map + + # 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 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, type_overrides) + end + + rows.map do |(value)| + type.deserialize(value) + end + else + types = if type_overrides.is_a?(Array) + type_overrides + else + columns.map { |name| column_type(name, type_overrides) } + end + + rows.map do |values| + Array.new(values.size) { |i| types[i].deserialize(values[i]) } + end + end + end + + def initialize_copy(other) + @columns = columns.dup + @rows = rows.dup + @column_types = column_types.dup + @hash_rows = nil + end + + private + def column_type(name, type_overrides = {}) + type_overrides.fetch(name) do + column_types.fetch(name, Type.default_value) + end + end + + def hash_rows + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + columns = @columns.map(&:-@) + length = columns.length + template = nil + + @rows.map { |row| + if template + # We use transform_values to build subsequent rows from the + # hash of the first row. This is faster because we avoid any + # reallocs and in Ruby 2.7+ avoid hashing entirely. + index = -1 + template.transform_values do + row[index += 1] + end + else + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + # It's possible to select the same column twice, in which case + # we can't use a template + template = hash if hash.length == length + + hash + end + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/runtime_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/runtime_registry.rb new file mode 100644 index 0000000000..7363d2d7a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/runtime_registry.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for Active Record. For example: + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns the connection handler local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class RuntimeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :sql_runtime + + [:sql_runtime].each do |val| + class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ + class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/sanitization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/sanitization.rb new file mode 100644 index 0000000000..6e3ffe5fc6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/sanitization.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +module ActiveRecord + module Sanitization + extend ActiveSupport::Concern + + module ClassMethods + # Accepts an array or string 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'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + 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, hash, or string 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" + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" + 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(["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: connection.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) + c = connection + 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 + + # 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 "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # 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(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end + end + + def disallow_raw_sql!(args, permit: connection.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) + (unexpected ||= []) << arg + end + + if unexpected + raise(ActiveRecord::UnknownAttributeReference, + "Query method called with non-attribute argument(s): " + + unexpected.map(&:inspect).join(", ") + ) + end + end + + private + def replace_bind_variables(statement, values) + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + c = connection + statement.gsub(/\?/) do + replace_bind_variable(bound.shift, c) + end + end + + def replace_bind_variable(value, c = connection) + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(value, c) + end + end + + def replace_named_bind_variables(statement, bind_vars) + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip postgresql casts + match # return the whole match + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end + end + end + + def quote_bound_value(value, c = connection) + 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? + c.quote(nil) + else + values.map! { |v| c.quote(v) }.join(",") + end + else + value = value.id_for_database if value.respond_to?(:id_for_database) + c.quote(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.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema.rb new file mode 100644 index 0000000000..aba25fb375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema.rb @@ -0,0 +1,61 @@ +# 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.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 + # 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.define(version: 2038_01_19_000001) do + # ... + # end + def self.define(info = {}, &block) + new.define(info, &block) + end + + def define(info, &block) # :nodoc: + instance_eval(&block) + + if info[:version].present? + connection.schema_migration.create_table + connection.assume_migrated_upto_version(info[:version]) + end + + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_dumper.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_dumper.rb new file mode 100644 index 0000000000..ecd0c5f0af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_dumper.rb @@ -0,0 +1,300 @@ +# 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 as well as regexp if ActiveRecord::Base.schema_format == :ruby. + # Only strings are accepted if ActiveRecord::Base.schema_format == :sql. + 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}$/ + + class << self + def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) + connection.create_schema_dumper(generate_options(config)).dump(stream) + 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) + extensions(stream) + tables(stream) + trailer(stream) + stream + end + + private + attr_accessor :table_name + + def initialize(connection, options = {}) + @connection = connection + @version = connection.migration_context.current_version rescue nil + @options = options + 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 <
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? + 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 << "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? + add_check_constraint_statements = check_constraints.map do |check_constraint| + parts = [ + "t.check_constraint #{check_constraint.expression.inspect}" + ] + + if check_constraint.export_name_on_schema_dump? + parts << "name: #{check_constraint.name.inspect}" + end + + " #{parts.join(', ')}" + end + + stream.puts add_check_constraint_statements.sort.join("\n") + end + 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) + 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.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) + 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) + [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored| + ignored === remove_prefix_and_suffix(table_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_migration.rb new file mode 100644 index 0000000000..dabf62320f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/schema_migration.rb @@ -0,0 +1,54 @@ +# 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 which migrations + # have been applied to a given database. When a migration is run, its schema + # number is inserted in to the `SchemaMigration.table_name` so it doesn't need + # to be executed the next time. + class SchemaMigration < ActiveRecord::Base # :nodoc: + class << self + def _internal? + true + end + + def primary_key + "version" + end + + def table_name + "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}" + end + + def create_table + 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 + + def drop_table + connection.drop_table table_name, if_exists: true + end + + def normalize_migration_number(number) + "%.3d" % number.to_i + end + + def normalized_versions + all_versions.map { |v| normalize_migration_number v } + end + + def all_versions + order(:version).pluck(:version) + end + end + + def version + super.to_i + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping.rb new file mode 100644 index 0000000000..62c7988bd8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +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.value_for(:current_scope, self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + 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 a thread local, which is + # accessed through +ScopeRegistry.current+. + # + # 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_value_for(:current_scope, Board, some_new_scope) + # + # Now when you run: + # + # registry.value_for(:current_scope, Board) + # + # You will obtain whatever was defined in +some_new_scope+. The #value_for + # and #set_value_for methods are delegated to the current ScopeRegistry + # object, so the above example code can also be called as: + # + # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope, + # Board, some_new_scope) + class ScopeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] + + def initialize + @registry = Hash.new { |hash, key| hash[key] = {} } + end + + # Obtains the value for a given +scope_type+ and +model+. + def value_for(scope_type, model, skip_inherited_scope = false) + raise_invalid_scope_type!(scope_type) + return @registry[scope_type][model.name] if skip_inherited_scope + klass = model + base = model.base_class + while klass <= base + value = @registry[scope_type][klass.name] + return value if value + klass = klass.superclass + end + end + + # Sets the +value+ for a given +scope_type+ and +model+. + def set_value_for(scope_type, model, value) + raise_invalid_scope_type!(scope_type) + @registry[scope_type][model.name] = value + end + + private + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/default.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/default.rb new file mode 100644 index 0000000000..2a4c99c699 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/default.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module ActiveRecord + module Scoping + 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 + # def self.default_scope + # where(published: true) + # end + # 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" + # + # 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_given? ? relation.scoping { yield } : relation + end + + # Are there attributes associated with this scope? + def scope_attributes? # :nodoc: + super || default_scopes.any? || respond_to?(:default_scope) + end + + def before_remove_const #:nodoc: + self.current_scope = nil + 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 a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (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, &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 + + self.default_scopes += [scope] + end + + def build_default_scope(relation = relation()) + 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 |default_scope, scope| + scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) + default_scope.instance_exec(&scope) || default_scope + end + end + end + end + + def ignore_default_scope? + ScopeRegistry.value_for(:ignore_default_scope, base_class) + end + + def ignore_default_scope=(ignore) + ScopeRegistry.set_value_for(: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.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/named.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/named.rb new file mode 100644 index 0000000000..ea1c1d3e76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/scoping/named.rb @@ -0,0 +1,206 @@ +# 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 + scope = current_scope + + if scope + if self == scope.klass + scope.clone + else + relation.merge!(scope) + end + else + default_scoped + 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) + build_default_scope(scope) || 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 + + valid_scope_name?(name) + 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) if respond_to?(:ruby2_keywords, true) + + generate_relation_method(name) + end + + private + def singleton_method_added(name) + generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name) + end + + def valid_scope_name?(name) + if respond_to?(name, true) && logger + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/secure_token.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/secure_token.rb new file mode 100644 index 0000000000..4864f988b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/secure_token.rb @@ -0,0 +1,48 @@ +# 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. + def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH) + 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) } + before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") } + end + + def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH) + SecureRandom.base58(length) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/serialization.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/serialization.rb new file mode 100644 index 0000000000..70cf1ea981 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/serialization.rb @@ -0,0 +1,24 @@ +# 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/signed_id.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/signed_id.rb new file mode 100644 index 0000000000..f0f0094b74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/signed_id.rb @@ -0,0 +1,116 @@ +# 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. + mattr_accessor :signed_id_verifier_secret, instance_writer: false + 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 the + # 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 + 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. + # It can further more 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, purpose: nil) + self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/statement_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/statement_cache.rb new file mode 100644 index 0000000000..aec6343a17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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(Book.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([], Book.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(Book.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"], Book.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 + + 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.klass) + end + + def initialize(query_builder, bind_map, klass) + @query_builder = query_builder + @bind_map = bind_map + @klass = klass + end + + def execute(params, connection, &block) + bind_values = bind_map.bind params + + sql = query_builder.sql_for bind_values, connection + + klass.find_by_sql(sql, bind_values, preparable: true, &block) + rescue ::RangeError + [] + end + + def self.unsupported_value?(value) + case value + when NilClass, Array, Range, Hash, Relation, Base then true + end + end + + private + attr_reader :query_builder, :bind_map, :klass + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/store.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/store.rb new file mode 100644 index 0000000000..1985dc9f55 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/store.rb @@ -0,0 +1,295 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # 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+, or MySQL 5.7+ + # +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', 'red'] + # + # # 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 = {}) + serialize store_attribute, IndifferentCoder.new(store_attribute, options[: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_change_to_attribute(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_change_to_attribute(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_change_to_attribute(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).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) + if value != read(object, attribute, key) + object.public_send :"#{attribute}_will_change!" + object.public_send(attribute)[key] = value + end + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/suppressor.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/suppressor.rb new file mode 100644 index 0000000000..49cce16dc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/suppressor.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord + # 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 + + module ClassMethods + def suppress(&block) + previous_state = SuppressorRegistry.suppressed[name] + SuppressorRegistry.suppressed[name] = true + yield + ensure + SuppressorRegistry.suppressed[name] = previous_state + end + end + + def save(**) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + + def save!(**) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + end + + class SuppressorRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :suppressed + + def initialize + @suppressed = {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/table_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/table_metadata.rb new file mode 100644 index 0000000000..d20f4fa66a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/table_metadata.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveRecord + class TableMetadata # :nodoc: + delegate :join_primary_key, :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) || klass&._reflect_on_association(table_name.singularize) + 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 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 + predicate_builder = klass.predicate_builder.dup + predicate_builder.instance_variable_set(:@table, self) + predicate_builder + else + PredicateBuilder.new(self) + end + end + + attr_reader :arel_table + + private + attr_reader :klass, :reflection + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/database_tasks.rb new file mode 100644 index 0000000000..f4ae1e6e77 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/database_tasks.rb @@ -0,0 +1,533 @@ +# frozen_string_literal: true + +require "active_record/database_configurations" + +module ActiveRecord + module Tasks # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: + + # 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 + mattr_accessor :structure_dump_flags, instance_accessor: false + + ## + # :singleton-method: + # Extra flags passed to database CLI tool when calling db:schema:load + mattr_accessor :structure_load_flags, instance_accessor: false + + extend self + + attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader + deprecate :current_config= + attr_accessor :database_configuration + + LOCAL_HOSTS = ["127.0.0.1", "localhost"] + + def check_protected_environments! + unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] + current = ActiveRecord::Base.connection.migration_context.current_environment + stored = ActiveRecord::Base.connection.migration_context.last_stored_environment + + if ActiveRecord::Base.connection.migration_context.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(stored) + end + + if stored && stored != current + raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored) + end + end + rescue ActiveRecord::NoDatabaseError + end + + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, "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 spec + @spec ||= "primary" + end + deprecate spec: "please use name instead" + + def name + @name ||= "primary" + end + + def seed_loader + @seed_loader ||= Rails.application + end + + def current_config(options = {}) + if options.has_key?(:config) + @current_config = options[:config] + else + env_name = options[:env] || env + name = options[:spec] || "primary" + + @current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: name)&.configuration_hash + end + end + deprecate :current_config + + 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 + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) + each_local_configuration { |db_config| create(db_config) } + if old_pool + ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config) + end + end + + def setup_initial_database_yaml + return {} unless defined?(Rails) + + begin + Rails.application.config.load_database_yaml + rescue + unless ActiveRecord::Base.suppress_multiple_database_warning + $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB." + end + + {} + end + end + + def for_each(databases) + 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| + yield db_config.name + end + end + + def raise_for_multi_db(environment = env, command:) + db_configs = ActiveRecord::Base.configurations.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) } + ActiveRecord::Base.establish_connection(environment.to_sym) + 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) + ActiveRecord::Base.establish_connection(db_config) + + connection = ActiveRecord::Base.connection + connection.truncate_tables(*connection.tables) + end + private :truncate_tables + + def truncate_all(environment = env) + ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config| + truncate_tables(db_config) + end + end + + def migrate + check_target_version + + scope = ENV["SCOPE"] + verbose_was, Migration.verbose = Migration.verbose, verbose? + + Base.connection.migration_context.migrate(target_version) do |migration| + scope.blank? || scope == migration.scope + end + + ActiveRecord::Base.clear_cache! + ensure + Migration.verbose = verbose_was + end + + def migrate_status + unless ActiveRecord::Base.connection.schema_migration.table_exists? + Kernel.abort "Schema migrations table does not exist yet." + end + + # output + puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" + puts "-" * 50 + ActiveRecord::Base.connection.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::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(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 = ActiveRecord::Base.configurations.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 = ActiveRecord::Base.configurations.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) } + ActiveRecord::Base.establish_connection(environment.to_sym) + end + + def structure_dump(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + database_adapter_for(db_config, *arguments).structure_dump(filename, structure_dump_flags) + end + + def structure_load(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + database_adapter_for(db_config, *arguments).structure_load(filename, structure_load_flags) + end + + def load_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + file ||= dump_filename(db_config.name, format) + + verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"] + check_schema_file(file) + ActiveRecord::Base.establish_connection(db_config) + + case format + when :ruby + load(file) + when :sql + structure_load(db_config, file) + else + raise ArgumentError, "unknown format #{format.inspect}" + end + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = db_config.env_name + ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file) + ensure + Migration.verbose = verbose_was + end + + def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = nil, name = nil) + db_config = resolve_configuration(configuration) + + if environment || name + ActiveSupport::Deprecation.warn("`environment` and `name` will be removed as parameters in 7.0.0, you may now pass an ActiveRecord::DatabaseConfigurations::DatabaseConfig as `configuration` instead.") + end + + name ||= db_config.name + + file ||= dump_filename(name, format) + + return true unless File.exist?(file) + + ActiveRecord::Base.establish_connection(db_config) + + return false unless ActiveRecord::InternalMetadata.enabled? + return false unless ActiveRecord::InternalMetadata.table_exists? + + ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file) + end + + def reconstruct_from_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + file ||= dump_filename(db_config.name, format) + + check_schema_file(file) + + ActiveRecord::Base.establish_connection(db_config) + + if schema_up_to_date?(db_config, format, file) + truncate_tables(db_config) + else + purge(db_config) + load_schema(db_config, format, file) + end + rescue ActiveRecord::NoDatabaseError + create(db_config) + load_schema(db_config, format, file) + end + + def dump_schema(db_config, format = ActiveRecord::Base.schema_format) # :nodoc: + require "active_record/schema_dumper" + filename = dump_filename(db_config.name, format) + connection = ActiveRecord::Base.connection + + FileUtils.mkdir_p(db_dir) + case format + when :ruby + File.open(filename, "w:utf-8") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + when :sql + structure_dump(db_config, filename) + if connection.schema_migration.table_exists? + File.open(filename, "a") do |f| + f.puts connection.dump_schema_information + f.print "\n" + end + end + end + end + + def schema_file(format = ActiveRecord::Base.schema_format) + File.join(db_dir, schema_file_type(format)) + end + + def schema_file_type(format = ActiveRecord::Base.schema_format) + case format + when :ruby + "schema.rb" + when :sql + "structure.sql" + end + end + + def dump_filename(db_config_name, format = ActiveRecord::Base.schema_format) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) + schema_file_type(format) + else + "#{db_config_name}_#{schema_file_type(format)}" + end + + ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) + end + + def cache_dump_filename(db_config_name, schema_cache_path: nil) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) + "schema_cache.yml" + else + "#{db_config_name}_schema_cache.yml" + end + + schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) + end + + def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) + each_current_configuration(environment) do |db_config| + load_schema(db_config, format, file) + end + ActiveRecord::Base.establish_connection(environment.to_sym) + 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.connection, "tmp/schema_dump.yaml") + def dump_schema_cache(conn, filename) + conn.schema_cache.dump_to(filename) + end + + def clear_schema_cache(filename) + FileUtils.rm_f filename, verbose: false + end + + private + 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.each_pair.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) + environments = [environment] + environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"] + + environments.each do |env| + ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config| + next if name && name != db_config.name + + yield db_config + end + end + end + + def each_local_configuration + ActiveRecord::Base.configurations.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) + Digest::SHA1.hexdigest(File.read(file)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/mysql_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/mysql_database_tasks.rb new file mode 100644 index 0000000000..fdba782ca3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class MySQLDatabaseTasks # :nodoc: + ER_DB_CREATE_EXISTS = 1007 + + delegate :connection, :establish_connection, to: ActiveRecord::Base + + 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(db_config) + end + + def drop + establish_connection(db_config) + connection.drop_database(db_config.database) + end + + def purge + establish_connection(db_config) + connection.recreate_database(db_config.database, creation_options) + 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? + 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 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" + }.map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] }.compact + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/postgresql_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/postgresql_database_tasks.rb new file mode 100644 index 0000000000..c4789091c6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/postgresql_database_tasks.rb @@ -0,0 +1,138 @@ +# 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 = "--" + + delegate :connection, :establish_connection, :clear_active_connections!, + to: ActiveRecord::Base + + def self.using_database_configurations? + true + end + + def initialize(db_config) + @db_config = db_config + @configuration_hash = db_config.configuration_hash + end + + def create(master_established = false) + establish_master_connection unless master_established + connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding)) + establish_connection(db_config) + end + + def drop + establish_master_connection + connection.drop_database(db_config.database) + end + + def charset + connection.encoding + end + + def collation + connection.collation + end + + def purge + clear_active_connections! + drop + create true + end + + def structure_dump(filename, extra_flags) + set_psql_env + + search_path = \ + case ActiveRecord::Base.dump_schemas + when :schema_search_path + configuration_hash[:schema_search_path] + when :all + nil + when String + ActiveRecord::Base.dump_schemas + end + + args = ["--schema-only", "--no-privileges", "--no-owner", "--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? + 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) + set_psql_env + args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--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 encoding + configuration_hash[:encoding] || DEFAULT_ENCODING + end + + def establish_master_connection + establish_connection configuration_hash.merge( + database: "postgres", + schema_search_path: "public" + ) + end + + def set_psql_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] + 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:\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.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/sqlite_database_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/sqlite_database_tasks.rb new file mode 100644 index 0000000000..f08e373b9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class SQLiteDatabaseTasks # :nodoc: + delegate :connection, :establish_connection, to: ActiveRecord::Base + + 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(db_config) + connection + end + + def drop + require "pathname" + path = Pathname.new(db_config.database) + file = path.absolute? ? path.to_s : File.join(root, path) + + FileUtils.rm(file) + rescue Errno::ENOENT => error + raise NoDatabaseError.new(error.message) + end + + def purge + drop + rescue NoDatabaseError + ensure + create + 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? + 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" + 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 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/test_databases.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/test_databases.rb new file mode 100644 index 0000000000..ab4732bb80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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::Base.schema_format, nil) + end + ensure + ActiveRecord::Base.establish_connection + ENV["VERBOSE"] = old + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/test_fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/test_fixtures.rb new file mode 100644 index 0000000000..f14d60021f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/test_fixtures.rb @@ -0,0 +1,291 @@ +# 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 + teardown_fixtures + end + + included do + class_attribute :fixture_path, instance_writer: false + 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 + 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_path`." if fixture_path.blank? + fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq + fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path + fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") } + else + fixture_set_names = fixture_set_names.flatten.map(&:to_s) + end + + self.fixture_table_names |= fixture_set_names + 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) + methods = Module.new do + fixture_set_names.each do |fs_name| + fs_name = fs_name.to_s + accessor_name = fs_name.tr("/", "_").to_sym + + define_method(accessor_name) do |*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 + private accessor_name + end + end + include methods + end + + 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 + + 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_connections = [] + @@already_loaded_fixtures ||= {} + @connection_subscriber = nil + @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} } + @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} } + + # Load fixtures once and begin transaction. + if run_in_transaction? + if @@already_loaded_fixtures[self.class] + @loaded_fixtures = @@already_loaded_fixtures[self.class] + else + @loaded_fixtures = load_fixtures(config) + @@already_loaded_fixtures[self.class] = @loaded_fixtures + end + + # Begin transactions for connections already established + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true if lock_threads + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + spec_name = payload[:spec_name] if payload.key?(:spec_name) + shard = payload[:shard] if payload.key?(:shard) + setup_shared_connection_pool if ActiveRecord::Base.legacy_connection_handling + + if spec_name + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard) + rescue ConnectionNotEstablished + connection = nil + end + + if connection + setup_shared_connection_pool unless ActiveRecord::Base.legacy_connection_handling + + if !@fixture_connections.include?(connection) + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true if lock_threads + @fixture_connections << connection + end + end + end + end + + # Load fixtures for every test. + else + ActiveRecord::FixtureSet.reset_cache + @@already_loaded_fixtures[self.class] = nil + @loaded_fixtures = load_fixtures(config) + end + + # Instantiate fixtures for every test if requested. + instantiate_fixtures if use_instantiated_fixtures + end + + def teardown_fixtures + # Rollback changes if a transaction is active. + if run_in_transaction? + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + @fixture_connections.each do |connection| + connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false + end + @fixture_connections.clear + teardown_shared_connection_pool + else + ActiveRecord::FixtureSet.reset_cache + end + + ActiveRecord::Base.clear_active_connections! + end + + def enlist_fixture_connections + setup_shared_connection_pool + + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) + end + + private + # 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 + if ActiveRecord::Base.legacy_connection_handling + writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role] + + ActiveRecord::Base.connection_handlers.values.each do |handler| + if handler != writing_handler + handler.connection_pool_names.each do |name| + writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name] + return unless writing_pool_manager + + pool_manager = handler.send(:owner_to_pool_manager)[name] + @legacy_saved_pool_configs[handler][name] ||= {} + pool_manager.shard_names.each do |shard_name| + writing_pool_config = writing_pool_manager.get_pool_config(nil, shard_name) + pool_config = pool_manager.get_pool_config(nil, shard_name) + next if pool_config == writing_pool_config + + @legacy_saved_pool_configs[handler][name][shard_name] = pool_config + pool_manager.set_pool_config(nil, shard_name, writing_pool_config) + end + end + end + end + else + handler = ActiveRecord::Base.connection_handler + + handler.connection_pool_names.each do |name| + pool_manager = handler.send(:owner_to_pool_manager)[name] + pool_manager.shard_names.each do |shard_name| + writing_pool_config = pool_manager.get_pool_config(ActiveRecord::Base.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 + end + + def teardown_shared_connection_pool + if ActiveRecord::Base.legacy_connection_handling + @legacy_saved_pool_configs.each_pair do |handler, names| + names.each_pair do |name, shards| + shards.each_pair do |shard_name, pool_config| + pool_manager = handler.send(:owner_to_pool_manager)[name] + pool_manager.set_pool_config(nil, shard_name, pool_config) + end + end + end + else + handler = ActiveRecord::Base.connection_handler + + @saved_pool_configs.each_pair do |name, shards| + pool_manager = handler.send(:owner_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 + end + + @legacy_saved_pool_configs.clear + @saved_pool_configs.clear + end + + def load_fixtures(config) + ActiveRecord::FixtureSet.create_fixtures(fixture_path, 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/timestamp.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/timestamp.rb new file mode 100644 index 0000000000..9b8eeb8c09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/timestamp.rb @@ -0,0 +1,164 @@ +# 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) + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map(&:to_s) + 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 + default_timezone == :utc ? Time.now.utc : Time.now + 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 + + def reload_schema_from_cache + @timestamp_attributes_for_create_in_model = nil + @timestamp_attributes_for_update_in_model = nil + @all_timestamp_attributes_in_model = nil + super + end + end + + private + 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 + 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 + + super + end + + def create_or_update(touch: true, **) + @_touch_record = touch + super + end + + def should_record_timestamps? + record_timestamps && (!partial_writes? || 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 + .map { |attr| self[attr]&.to_time } + .compact + .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.0.0/gems/activerecord-6.1.7.2/lib/active_record/touch_later.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/touch_later.rb new file mode 100644 index 0000000000..405a5dad7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/touch_later.rb @@ -0,0 +1,65 @@ +# 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(:belongs_to).each do |r| + if touch = r.options[:touch] + ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later) + 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 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? + defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present? + end + + def belongs_to_touch_method + :touch_later + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/transactions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/transactions.rb new file mode 100644 index 0000000000..9eb08b198d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/transactions.rb @@ -0,0 +1,446 @@ +# 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 + + # = 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. + # + # *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. + # + # === 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.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 + # # ^^^^ BOOM! database error! + # end + # + # Note that "TRUNCATE" is also a MySQL DDL statement! + module ClassMethods + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. + def transaction(**options, &block) + connection.transaction(**options, &block) + 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) + 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 ]) + 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) + 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) + 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) + 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) + set_callback(:rollback, :after, *args, &block) + end + + private + 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: + force_clear_transaction_record_state + 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 +method+ 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 + status = nil + connection = self.class.connection + 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 + + 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 + + # 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 + force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 + end + + # Force to clear the transaction record state. + def force_clear_transaction_record_state + @_start_transaction_state = nil + 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 @attributes.fetch_value(@primary_key) != restore_state[:id] + @attributes.write_from_user(@primary_key, restore_state[:id]) + 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.connection.add_transaction_record(self, ensure_finalize) + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/translation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/translation.rb new file mode 100644 index 0000000000..82661a328a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/translation.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module Translation + include ActiveModel::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 overwrite ActiveModel. + def i18n_scope #:nodoc: + :activerecord + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type.rb new file mode 100644 index 0000000000..85df786a33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type.rb @@ -0,0 +1,84 @@ +# 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: + # TODO: this shouldn't depend on a connection to the database + model.connection.adapter_name.downcase.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/adapter_specific_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/adapter_specific_registry.rb new file mode 100644 index 0000000000..77590b821a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require "active_model/type/registry" + +module ActiveRecord + # :stopdoc: + module Type + class AdapterSpecificRegistry < ActiveModel::Type::Registry + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + + private + def registration_klass + Registration + end + + def find_registration(symbol, *args, **kwargs) + registrations + .select { |registration| registration.matches?(symbol, *args, **kwargs) } + .max + end + end + + class Registration + def initialize(name, block, adapter: nil, override: nil) + @name = name + @block = block + @adapter = adapter + @override = override + end + + def call(_registry, *args, adapter: nil, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + 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 + 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 + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/date.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/date.rb new file mode 100644 index 0000000000..8177074a20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/date_time.rb new file mode 100644 index 0000000000..4acde6b9f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/decimal_without_scale.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 0000000000..a207940dc7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/hash_lookup_type_map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 0000000000..b260464df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class HashLookupTypeMap < TypeMap # :nodoc: + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/internal/timezone.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/internal/timezone.rb new file mode 100644 index 0000000000..3059755752 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/internal/timezone.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + module Internal + module Timezone + def is_utc? + ActiveRecord::Base.default_timezone == :utc + end + + def default_timezone + ActiveRecord::Base.default_timezone + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/json.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/json.rb new file mode 100644 index 0000000000..3f9ff22796 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/serialized.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/serialized.rb new file mode 100644 index 0000000000..17f3932e54 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/serialized.rb @@ -0,0 +1,74 @@ +# 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 + + def inspect + Kernel.instance_method(:inspect).bind(self).call + end + + 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 + + private + def default_value?(value) + value == coder.load(nil) + end + + def encoded(value) + return if default_value?(value) + payload = coder.dump(value) + if payload && binary? && payload.encoding != Encoding::BINARY + payload = payload.dup if payload.frozen? + payload.force_encoding(Encoding::BINARY) + end + payload + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/text.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/text.rb new file mode 100644 index 0000000000..6d19696671 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/time.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/time.rb new file mode 100644 index 0000000000..4e4f2acc63 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/time.rb @@ -0,0 +1,31 @@ +# 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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/type_map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/type_map.rb new file mode 100644 index 0000000000..58f25ba075 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/type_map.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module Type + class TypeMap # :nodoc: + def initialize + @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 + @cache.clear + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type, *args| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}", *args) + end + end + + def clear + @mapping.clear + end + + private + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/unsigned_integer.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type/unsigned_integer.rb new file mode 100644 index 0000000000..535369e630 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster.rb new file mode 100644 index 0000000000..2e5f45fa3d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster/connection.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster/connection.rb new file mode 100644 index 0000000000..1d80d9a2b1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 = connection.schema_cache + + if schema_cache.data_source_exists?(table_name) + column = schema_cache.columns_hash(table_name)[attr_name.to_s] + type = connection.lookup_cast_type_from_column(column) if column + end + + type || Type.default_value + end + + delegate :connection, to: :@klass, private: true + + private + attr_reader :table_name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster/map.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/type_caster/map.rb new file mode 100644 index 0000000000..b3dd7a014a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations.rb new file mode 100644 index 0000000000..818515b4e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations.rb @@ -0,0 +1,94 @@ +# 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 + # all of which accept the :on argument to define the context where the + # validations are active. Active Record will always supply either the context of + # :create or :update dependent on whether the model is a + # {new_record?}[rdoc-ref:Persistence#new_record?]. + module Validations + extend ActiveSupport::Concern + include ActiveModel::Validations + + # 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. + # + # \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? + + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/absence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/absence.rb new file mode 100644 index 0000000000..6afb9eabd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 considered absent if it was 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/associated.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/associated.rb new file mode 100644 index 0000000000..2112a6d7b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/associated.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AssociatedValidator < ActiveModel::EachValidator #:nodoc: + def validate_each(record, attribute, value) + if Array(value).reject { |r| valid_object?(r) }.any? + record.errors.add(attribute, :invalid, **options.merge(value: value)) + end + end + + private + def valid_object?(record) + (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid? + 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/length.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/length.rb new file mode 100644 index 0000000000..f47b14ae3a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/numericality.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/numericality.rb new file mode 100644 index 0000000000..69c5e389b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/numericality.rb @@ -0,0 +1,35 @@ +# 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.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/presence.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/presence.rb new file mode 100644 index 0000000000..75e97e1997 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/presence.rb @@ -0,0 +1,68 @@ +# 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?), and, if the attribute is an association, that the + # associated object is not marked for destruction. Happens by default + # on save. + # + # 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. + # + # 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. + # + # 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. + # + # 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]. + # + # Configuration options: + # * :message - A custom error message (default is: "can't be blank"). + # * :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. + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/uniqueness.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/uniqueness.rb new file mode 100644 index 0000000000..fbe318f3f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/validations/uniqueness.rb @@ -0,0 +1,246 @@ +# 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] + end + + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + value = map_enum_attribute(finder_class, attribute, value) + + 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. Since classes don't know + # their subclasses, we have to build the hierarchy between self and + # the record's class. + def find_finder_class_for(record) + class_hierarchy = [record.class] + + while class_hierarchy.first != @klass + class_hierarchy.unshift(class_hierarchy.first.superclass) + end + + class_hierarchy.detect { |klass| !klass.abstract_class? } + end + + def build_relation(klass, attribute, value) + relation = klass.unscoped + comparison = relation.bind_attribute(attribute, value) do |attr, bind| + return relation.none! if bind.unboundable? + + if !options.key?(:case_sensitive) || bind.nil? + klass.connection.default_uniqueness_comparison(attr, bind) + elsif options[:case_sensitive] + klass.connection.case_sensitive_comparison(attr, bind) + else + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + klass.connection.case_insensitive_comparison(attr, bind) + 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 (+true+ by default). + # * :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::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.0.0/gems/activerecord-6.1.7.2/lib/active_record/version.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/version.rb new file mode 100644 index 0000000000..6b0d82d8fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveRecord + # Returns the version of the currently loaded ActiveRecord as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel.rb new file mode 100644 index 0000000000..148508461c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "arel/errors" + +require "arel/crud" +require "arel/factory_methods" + +require "arel/expressions" +require "arel/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("length(title)")).last + # + # 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. + def self.sql(raw_sql) + Arel::Nodes::SqlLiteral.new raw_sql + end + + def self.star # :nodoc: + sql "*" + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/alias_predication.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/alias_predication.rb new file mode 100644 index 0000000000..4abbbb7ef6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/attributes/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/attributes/attribute.rb new file mode 100644 index 0000000000..e7bdd2b171 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/attributes/attribute.rb @@ -0,0 +1,41 @@ +# 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 + + class String < Attribute; end + class Time < Attribute; end + class Boolean < Attribute; end + class Decimal < Attribute; end + class Float < Attribute; end + class Integer < Attribute; end + class Undefined < Attribute; end + end + + Attribute = Attributes::Attribute +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/bind.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/bind.rb new file mode 100644 index 0000000000..6a96b1cb0e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/bind.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Bind + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/composite.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/composite.rb new file mode 100644 index 0000000000..0f05dfbe54 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/composite.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Composite + attr_accessor :preparable + + def initialize(left, right) + @left = left + @right = right + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/plain_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/plain_string.rb new file mode 100644 index 0000000000..c0e9fff399 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/sql_string.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/sql_string.rb new file mode 100644 index 0000000000..8aa8958a1f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/substitute_binds.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/collectors/substitute_binds.rb new file mode 100644 index 0000000000..82315c75d3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/crud.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/crud.rb new file mode 100644 index 0000000000..e8a563ca4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/crud.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + ### + # FIXME hopefully we can remove this + module Crud + def compile_update(values, pk) + um = UpdateManager.new + + if Nodes::SqlLiteral === values + relation = @ctx.from + else + relation = values.first.first.relation + end + um.key = pk + um.table relation + um.set values + um.take @ast.limit.expr if @ast.limit + um.order(*@ast.orders) + um.wheres = @ctx.wheres + um + end + + def compile_insert(values) + im = create_insert + im.insert values + im + end + + def create_insert + InsertManager.new + end + + def compile_delete + dm = DeleteManager.new + dm.take @ast.limit.expr if @ast.limit + dm.wheres = @ctx.wheres + dm.from @ctx.froms + dm + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/delete_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/delete_manager.rb new file mode 100644 index 0000000000..fdba937d64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/delete_manager.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class DeleteManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize + super + @ast = Nodes::DeleteStatement.new + @ctx = @ast + end + + def from(relation) + @ast.relation = relation + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/errors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/errors.rb new file mode 100644 index 0000000000..2f8d5e3c02 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/errors.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class ArelError < StandardError + end + + class EmptyJoinError < ArelError + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/expressions.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/expressions.rb new file mode 100644 index 0000000000..da8afb338c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/factory_methods.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/factory_methods.rb new file mode 100644 index 0000000000..83ec23e403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/factory_methods.rb @@ -0,0 +1,49 @@ +# 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/insert_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/insert_manager.rb new file mode 100644 index 0000000000..cb31e3060b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/insert_manager.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class InsertManager < Arel::TreeManager + def initialize + super + @ast = Nodes::InsertStatement.new + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/math.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/math.rb new file mode 100644 index 0000000000..2359f13148 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes.rb new file mode 100644 index 0000000000..5f15d39854 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes.rb @@ -0,0 +1,70 @@ +# 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" + +# 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/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" + +# nary +require "arel/nodes/and" + +# 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/comment" + +require "arel/nodes/sql_literal" + +require "arel/nodes/casted" diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/and.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/and.rb new file mode 100644 index 0000000000..bf516db35f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/and.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class And < Arel::Nodes::NodeExpression + attr_reader :children + + def initialize(children) + super() + @children = children + end + + def left + children.first + end + + def right + children[1] + end + + def hash + children.hash + end + + def eql?(other) + self.class == other.class && + self.children == other.children + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/ascending.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/ascending.rb new file mode 100644 index 0000000000..8b617f4df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/binary.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/binary.rb new file mode 100644 index 0000000000..e3f523d0b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/binary.rb @@ -0,0 +1,126 @@ +# 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 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 + + class Or < Binary + def fetch_attribute(&block) + left.fetch_attribute(&block) && right.fetch_attribute(&block) + end + end + + %w{ + As + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/bind_param.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/bind_param.rb new file mode 100644 index 0000000000..1d900eeb22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/case.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/case.rb new file mode 100644 index 0000000000..1c4b727bf6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/casted.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/casted.rb new file mode 100644 index 0000000000..07996dd25e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/comment.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/comment.rb new file mode 100644 index 0000000000..237ff27e7e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/count.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/count.rb new file mode 100644 index 0000000000..880464639d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/delete_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/delete_statement.rb new file mode 100644 index 0000000000..a419975335 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/delete_statement.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class DeleteStatement < Arel::Nodes::Node + attr_accessor :left, :right, :orders, :limit, :offset, :key + + alias :relation :left + alias :relation= :left= + alias :wheres :right + alias :wheres= :right= + + def initialize(relation = nil, wheres = []) + super() + @left = relation + @right = wheres + @orders = [] + @limit = nil + @offset = nil + @key = nil + end + + def initialize_copy(other) + super + @left = @left.clone if @left + @right = @right.clone if @right + end + + def hash + [self.class, @left, @right, @orders, @limit, @offset, @key].hash + end + + def eql?(other) + self.class == other.class && + self.left == other.left && + self.right == other.right && + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/descending.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/descending.rb new file mode 100644 index 0000000000..f3f6992ca8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/equality.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/equality.rb new file mode 100644 index 0000000000..610411979b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/extract.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/extract.rb new file mode 100644 index 0000000000..5799ee9b8f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/false.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/false.rb new file mode 100644 index 0000000000..1e5bf04be5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/full_outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/full_outer_join.rb new file mode 100644 index 0000000000..91bb81f2e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/function.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/function.rb new file mode 100644 index 0000000000..0a439b39f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/function.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Function < Arel::Nodes::NodeExpression + include Arel::WindowPredications + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/grouping.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/grouping.rb new file mode 100644 index 0000000000..e01d97ffcb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/homogeneous_in.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/homogeneous_in.rb new file mode 100644 index 0000000000..7a4ce27f26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/homogeneous_in.rb @@ -0,0 +1,76 @@ +# 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 table_name + attribute.relation.table_alias || attribute.relation.name + end + + def column_name + attribute.name + 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, attribute.type_caster) } + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/in.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/in.rb new file mode 100644 index 0000000000..2154bc6e3a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/infix_operation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/infix_operation.rb new file mode 100644 index 0000000000..462870d13d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/inner_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/inner_join.rb new file mode 100644 index 0000000000..519fafad09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/insert_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/insert_statement.rb new file mode 100644 index 0000000000..d28fd1f6c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + super() + @relation = nil + @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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/join_source.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/join_source.rb new file mode 100644 index 0000000000..d65a9e6693 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/matches.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/matches.rb new file mode 100644 index 0000000000..fd5734f4bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/named_function.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/named_function.rb new file mode 100644 index 0000000000..126462d6d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/node.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/node.rb new file mode 100644 index 0000000000..fe50181b4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/node.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + ### + # Abstract base class for all AST nodes + 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 + collector = engine.connection.visitor.accept self, collector + collector.value + end + + def fetch_attribute + end + + def equality?; false; end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/node_expression.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/node_expression.rb new file mode 100644 index 0000000000..cbcfaba37c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/ordering.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/ordering.rb new file mode 100644 index 0000000000..b09ce2b9e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/outer_join.rb new file mode 100644 index 0000000000..0a3042be61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/over.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/over.rb new file mode 100644 index 0000000000..91176764a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/regexp.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/regexp.rb new file mode 100644 index 0000000000..7c25095569 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/right_outer_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/right_outer_join.rb new file mode 100644 index 0000000000..04ed4aaa78 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/select_core.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/select_core.rb new file mode 100644 index 0000000000..11b4f39ece --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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 + super() + @source = JoinSource.new nil + + # 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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/select_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/select_statement.rb new file mode 100644 index 0000000000..eff5dad939 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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(cores = [SelectCore.new]) + super() + @cores = cores + @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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/sql_literal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/sql_literal.rb new file mode 100644 index 0000000000..f260c1e27a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/sql_literal.rb @@ -0,0 +1,19 @@ +# 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 + + def encode_with(coder) + coder.scalar = self.to_s + end + + def fetch_attribute + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/string_join.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/string_join.rb new file mode 100644 index 0000000000..86027fcab7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/table_alias.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/table_alias.rb new file mode 100644 index 0000000000..567d4e2ff4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/table_alias.rb @@ -0,0 +1,31 @@ +# 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/terminal.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/terminal.rb new file mode 100644 index 0000000000..d84c453f1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/true.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/true.rb new file mode 100644 index 0000000000..c891012969 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unary.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unary.rb new file mode 100644 index 0000000000..cf1eca2831 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unary_operation.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unary_operation.rb new file mode 100644 index 0000000000..524282ac84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unqualified_column.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/unqualified_column.rb new file mode 100644 index 0000000000..7c3e0720d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/update_statement.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/update_statement.rb new file mode 100644 index 0000000000..cfaa19e392 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/update_statement.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class UpdateStatement < Arel::Nodes::Node + attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key + + def initialize + @relation = nil + @wheres = [] + @values = [] + @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.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/values_list.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/values_list.rb new file mode 100644 index 0000000000..1a9d9ebf01 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/window.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/window.rb new file mode 100644 index 0000000000..4916fc7fbe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/with.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/nodes/with.rb new file mode 100644 index 0000000000..157bdcaa08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/order_predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/order_predications.rb new file mode 100644 index 0000000000..d785bbba92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/predications.rb new file mode 100644 index 0000000000..cdf10d6549 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/predications.rb @@ -0,0 +1,250 @@ +# 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) + not_in([]) + 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)) + else + left = quoted_node(other.begin) + right = quoted_node(other.end) + Nodes::Between.new(self, left.and(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) + self.in([]) + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/select_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/select_manager.rb new file mode 100644 index 0000000000..dd896cefb3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/select_manager.rb @@ -0,0 +1,270 @@ +# 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) + super() + @ast = Nodes::SelectStatement.new + @ctx = @ast.cores.last + from table + 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) + 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.map { |x| x.from }.compact + 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_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.0.0/gems/activerecord-6.1.7.2/lib/arel/table.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/table.rb new file mode 100644 index 0000000000..6790fb6e96 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/table.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class Table + include Arel::Crud + include Arel::FactoryMethods + include Arel::AliasPredication + + @engine = nil + class << self; attr_accessor :engine; end + + attr_accessor :name, :table_alias + + # TableAlias and Table both have a #table_name which is the name of the underlying table + alias :table_name :name + + def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster) + @name = name.to_s + @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.to_s 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.0.0/gems/activerecord-6.1.7.2/lib/arel/tree_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/tree_manager.rb new file mode 100644 index 0000000000..0476399618 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/tree_manager.rb @@ -0,0 +1,72 @@ +# 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 = Nodes.build_quoted(key) + 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 initialize + @ctx = nil + end + + 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 + collector = engine.connection.visitor.accept @ast, collector + collector.value + end + + def initialize_copy(other) + super + @ast = @ast.clone + end + + def where(expr) + if Arel::TreeManager === expr + expr = expr.ast + end + @ctx.wheres << expr + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/update_manager.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/update_manager.rb new file mode 100644 index 0000000000..a809dbb307 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/update_manager.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class UpdateManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize + super + @ast = Nodes::UpdateStatement.new + @ctx = @ast + end + + ### + # UPDATE +table+ + def table(table) + @ast.relation = table + self + end + + def set(values) + if String === values + @ast.values = [values] + else + @ast.values = values.map { |column, value| + Nodes::Assignment.new( + Nodes::UnqualifiedColumn.new(column), + value + ) + } + end + self + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors.rb new file mode 100644 index 0000000000..ea55a5e4b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/dot.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/dot.rb new file mode 100644 index 0000000000..45cd22e801 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/dot.rb @@ -0,0 +1,308 @@ +# 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_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_InnerJoin(o) + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin + + def visit_Arel_Nodes_DeleteStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + end + + def unary(o) + visit_edge o, "expr" + end + alias :visit_Arel_Nodes_Group :unary + alias :visit_Arel_Nodes_Cube :unary + alias :visit_Arel_Nodes_RollUp :unary + alias :visit_Arel_Nodes_GroupingSet :unary + alias :visit_Arel_Nodes_GroupingElement :unary + alias :visit_Arel_Nodes_Grouping :unary + alias :visit_Arel_Nodes_Having :unary + alias :visit_Arel_Nodes_Limit :unary + alias :visit_Arel_Nodes_Not :unary + alias :visit_Arel_Nodes_Offset :unary + alias :visit_Arel_Nodes_On :unary + alias :visit_Arel_Nodes_UnqualifiedColumn :unary + alias :visit_Arel_Nodes_OptimizerHints :unary + alias :visit_Arel_Nodes_Preceding :unary + alias :visit_Arel_Nodes_Following :unary + alias :visit_Arel_Nodes_Rows :unary + alias :visit_Arel_Nodes_Range :unary + + def window(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + end + alias :visit_Arel_Nodes_Window :window + + def named_window(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + visit_edge o, "name" + end + alias :visit_Arel_Nodes_NamedWindow :named_window + + def function(o) + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Exists :function + alias :visit_Arel_Nodes_Min :function + alias :visit_Arel_Nodes_Max :function + alias :visit_Arel_Nodes_Avg :function + alias :visit_Arel_Nodes_Sum :function + + def extract(o) + visit_edge o, "expressions" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Extract :extract + + 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" + end + + def visit_Arel_Nodes_SelectCore(o) + visit_edge o, "source" + visit_edge o, "projections" + visit_edge o, "wheres" + visit_edge o, "windows" + end + + def visit_Arel_Nodes_SelectStatement(o) + visit_edge o, "cores" + visit_edge o, "limit" + visit_edge o, "orders" + visit_edge o, "offset" + end + + def visit_Arel_Nodes_UpdateStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + visit_edge o, "values" + 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_Attribute(o) + visit_edge o, "relation" + visit_edge o, "name" + end + alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute + alias :visit_Arel_Attributes_Float :visit_Arel_Attribute + alias :visit_Arel_Attributes_String :visit_Arel_Attribute + alias :visit_Arel_Attributes_Time :visit_Arel_Attribute + alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute + alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute + + def nary(o) + o.children.each_with_index do |x, i| + edge(i) { visit x } + end + end + alias :visit_Arel_Nodes_And :nary + + def binary(o) + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_As :binary + alias :visit_Arel_Nodes_Assignment :binary + alias :visit_Arel_Nodes_Between :binary + alias :visit_Arel_Nodes_Concat :binary + alias :visit_Arel_Nodes_DoesNotMatch :binary + alias :visit_Arel_Nodes_Equality :binary + alias :visit_Arel_Nodes_GreaterThan :binary + alias :visit_Arel_Nodes_GreaterThanOrEqual :binary + alias :visit_Arel_Nodes_In :binary + alias :visit_Arel_Nodes_JoinSource :binary + alias :visit_Arel_Nodes_LessThan :binary + alias :visit_Arel_Nodes_LessThanOrEqual :binary + alias :visit_Arel_Nodes_IsNotDistinctFrom :binary + alias :visit_Arel_Nodes_IsDistinctFrom :binary + alias :visit_Arel_Nodes_Matches :binary + alias :visit_Arel_Nodes_NotEqual :binary + alias :visit_Arel_Nodes_NotIn :binary + alias :visit_Arel_Nodes_Or :binary + alias :visit_Arel_Nodes_Over :binary + + 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) + edge("value") { visit o.value } + end + + def visit_ActiveModel_Attribute(o) + edge("value_before_type_cast") { visit 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 |x, i| + edge(i) { visit x } + end + end + alias :visit_Set :visit_Array + + def visit_Arel_Nodes_Comment(o) + visit_edge(o, "values") + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/mysql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/mysql.rb new file mode 100644 index 0000000000..f4075095ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/mysql.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class MySQL < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_Bin(o, collector) + collector << "BINARY " + visit o.expr, collector + 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") + 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 + + # 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_join_sources?(o) && has_limit_or_offset_or_orders?(o) + super + else + o + end + end + alias :prepare_delete_statement :prepare_update_statement + + # MySQL is too stupid to 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))] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/postgresql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/postgresql.rb new file mode 100644 index 0000000000..0afd813528 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/postgresql.rb @@ -0,0 +1,120 @@ +# 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, 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 + + 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 + + BIND_BLOCK = proc { |i| "$#{i}" } + private_constant :BIND_BLOCK + + def bind_block; BIND_BLOCK; end + + # Used by Lateral visitor to enclose select queries in parentheses + def grouping_parentheses(o, collector) + if o.expr.is_a? Nodes::SelectStatement + collector << "(" + visit o.expr, collector + collector << ")" + else + visit o.expr, collector + end + 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.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/sqlite.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/sqlite.rb new file mode 100644 index 0000000000..62ec74ad82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/sqlite.rb @@ -0,0 +1,38 @@ +# 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/to_sql.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/to_sql.rb new file mode 100644 index 0000000000..f339a2f801 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/to_sql.rb @@ -0,0 +1,899 @@ +# 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) + 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) + 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 << "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 + 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 + + 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_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 + + collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name) + + 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 << ")" + 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 + + def visit_Arel_Nodes_Group(o, collector) + visit o.expr, collector + end + + def visit_Arel_Nodes_NamedFunction(o, collector) + 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) + collector = visit o.left, collector + collector << " >= " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThan(o, collector) + collector = visit o.left, collector + collector << " > " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThanOrEqual(o, collector) + collector = visit o.left, collector + collector << " <= " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThan(o, collector) + 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 o.table_alias + collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias) + else + collector << quote_table_name(o.name) + end + end + + def visit_Arel_Nodes_In(o, collector) + collector.preparable = false + attr, values = o.left, o.right + + if Array === values + 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) + collector.preparable = false + attr, values = o.left, o.right + + if Array === values + 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) + stack = [o.right, o.left] + + while o = stack.pop + if o.is_a?(Arel::Nodes::Or) + stack.push o.right, o.left + else + visit o, collector + collector << " OR " unless stack.empty? + end + end + + collector + end + + def visit_Arel_Nodes_Assignment(o, collector) + case o.right + when Arel::Nodes::Node, Arel::Attributes::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_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_Arel_Nodes_BindParam(o, collector) + collector.add_bind(o.value, &bind_block) + end + + def visit_Arel_Nodes_SqlLiteral(o, collector) + collector.preparable = false + collector << o.to_s + end + + def visit_Integer(o, collector) + collector << o.to_s + end + + def unsupported(o, collector) + raise UnsupportedVisitError.new(o) + end + + alias :visit_ActiveSupport_Multibyte_Chars :unsupported + alias :visit_ActiveSupport_StringInquirer :unsupported + alias :visit_BigDecimal :unsupported + alias :visit_Class :unsupported + alias :visit_Date :unsupported + alias :visit_DateTime :unsupported + alias :visit_FalseClass :unsupported + alias :visit_Float :unsupported + alias :visit_Hash :unsupported + alias :visit_NilClass :unsupported + alias :visit_String :unsupported + alias :visit_Symbol :unsupported + alias :visit_Time :unsupported + alias :visit_TrueClass :unsupported + + def visit_Arel_Nodes_InfixOperation(o, collector) + collector = visit o.left, collector + collector << " #{o.operator} " + visit o.right, collector + end + + def visit_Arel_Nodes_UnaryOperation(o, collector) + collector << " #{o.operator} " + visit o.expr, collector + end + + def visit_Array(o, collector) + inject_join o, collector, ", " + end + alias :visit_Set :visit_Array + + def quote(value) + return value if Arel::Nodes::SqlLiteral === value + @connection.quote value + end + + def quote_table_name(name) + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_table_name(name) + end + + def quote_column_name(name) + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_column_name(name) + end + + def sanitize_as_sql_comment(value) + return value if Arel::Nodes::SqlLiteral === value + @connection.sanitize_as_sql_comment(value) + end + + def collect_optimizer_hints(o, collector) + maybe_visit o.optimizer_hints, collector + end + + def maybe_visit(thing, collector) + return collector unless thing + collector << " " + visit thing, collector + end + + def inject_join(list, collector, join_str) + list.each_with_index do |x, i| + collector << join_str unless i == 0 + collector = visit(x, collector) + end + collector + end + + def unboundable?(value) + value.respond_to?(:unboundable?) && value.unboundable? + end + + def has_join_sources?(o) + o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty? + end + + def has_limit_or_offset_or_orders?(o) + o.limit || o.offset || !o.orders.empty? + end + + # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work + # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in + # an UPDATE statement, so in the MySQL visitor we redefine this to do that. + def prepare_update_statement(o) + if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o)) + stmt = o.clone + stmt.limit = nil + stmt.offset = nil + stmt.orders = [] + stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])] + stmt.relation = o.relation.left if has_join_sources?(o) + stmt + else + o + end + end + alias :prepare_delete_statement :prepare_update_statement + + # FIXME: we should probably have a 2-pass visitor for this + def build_subselect(key, o) + stmt = Nodes::SelectStatement.new + core = stmt.cores.first + core.froms = o.relation + core.wheres = o.wheres + core.projections = [key] + stmt.limit = o.limit + stmt.offset = o.offset + stmt.orders = o.orders + stmt + end + + def infix_value(o, collector, value) + collector = visit o.left, collector + collector << value + visit o.right, collector + end + + def infix_value_with_paren(o, collector, value, suppress_parens = false) + collector << "( " unless suppress_parens + collector = if o.left.class == o.class + infix_value_with_paren(o.left, collector, value, true) + else + visit o.left, collector + end + collector << value + collector = if o.right.class == o.class + infix_value_with_paren(o.right, collector, value, true) + else + visit o.right, collector + end + collector << " )" unless suppress_parens + collector + end + + def aggregate(name, o, collector) + collector << "#{name}(" + if o.distinct + collector << "DISTINCT " + end + collector = inject_join(o.expressions, collector, ", ") << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def is_distinct_from(o, collector) + collector << "CASE WHEN " + collector = visit o.left, collector + collector << " = " + collector = visit o.right, collector + collector << " OR (" + collector = visit o.left, collector + collector << " IS NULL AND " + collector = visit o.right, collector + collector << " IS NULL)" + collector << " THEN 0 ELSE 1 END" + end + + def collect_ctes(children, collector) + children.each_with_index do |child, i| + collector << ", " unless i == 0 + + case child + when Arel::Nodes::As + name = child.left.name + relation = child.right + when Arel::Nodes::TableAlias + name = child.name + relation = child.relation + end + + collector << quote_table_name(name) + collector << " AS " + visit relation, collector + end + + collector + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/visitor.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/visitor.rb new file mode 100644 index 0000000000..9066307aed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/visitors/visitor.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class Visitor + def initialize + @dispatch = get_dispatch_cache + end + + def accept(object, collector = nil) + visit object, collector + end + + private + attr_reader :dispatch + + def self.dispatch_cache + @dispatch_cache ||= Hash.new do |hash, klass| + hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}" + end + end + + def get_dispatch_cache + self.class.dispatch_cache + end + + def visit(object, collector = nil) + dispatch_method = dispatch[object.class] + if collector + send dispatch_method, object, collector + else + send dispatch_method, object + end + rescue NoMethodError => 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.0.0/gems/activerecord-6.1.7.2/lib/arel/window_predications.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/arel/window_predications.rb new file mode 100644 index 0000000000..3a8ee41f8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record.rb new file mode 100644 index 0000000000..a7e5e373a7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/application_record/application_record_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/application_record/application_record_generator.rb new file mode 100644 index 0000000000..56b9628a92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt new file mode 100644 index 0000000000..60050e0bf8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration.rb new file mode 100644 index 0000000000..724da797f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration.rb @@ -0,0 +1,52 @@ +# 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 + ) + config&.migrations_paths + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/migration_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/migration_generator.rb new file mode 100644 index 0000000000..0620a515bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt new file mode 100644 index 0000000000..e41a197095 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt @@ -0,0 +1,26 @@ +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 -%> +<% 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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/templates/migration.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/migration/templates/migration.rb.tt new file mode 100644 index 0000000000..f203823384 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/model_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/model_generator.rb new file mode 100644 index 0000000000..c025e4d8cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/model_generator.rb @@ -0,0 +1,85 @@ +# 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, 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." + + # 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 "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb") + end + + def create_model_file + generate_abstract_class if database && !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? + 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 parent + parent + elsif database + abstract_class_name + else + "ApplicationRecord" + 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 migration + options[:migration] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt new file mode 100644 index 0000000000..7ca973fcb9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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::Base.writing_role %>: :<%= database -%> } +end +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/model.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/model.rb.tt new file mode 100644 index 0000000000..77b9ea1c86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/module.rb.tt b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.2/lib/rails/generators/active_record/model/templates/module.rb.tt new file mode 100644 index 0000000000..a3bf1c37b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activerecord-6.1.7.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.0.0/gems/activesupport-6.1.4/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/CHANGELOG.md new file mode 100644 index 0000000000..fff6e66025 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/CHANGELOG.md @@ -0,0 +1,645 @@ +## Rails 6.1.4 (June 24, 2021) ## + +* MemCacheStore: convert any underlying value (including `false`) to an `Entry`. + + See [#42559](https://github.com/rails/rails/pull/42559). + + *Alex Ghiculescu* + +* Fix bug in `number_with_precision` when using large `BigDecimal` values. + + Fixes #42302. + + *Federico Aldunate*, *Zachary Scott* + +* Check byte size instead of length on `secure_compare`. + + *Tietew* + +* Fix `Time.at` to not lose `:in` option. + + *Ryuta Kamizono* + +* Require a path for `config.cache_store = :file_store`. + + *Alex Ghiculescu* + +* Avoid having to store complex object in the default translation file. + + *Rafael Mendonça França* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* No changes. + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* No changes. + + +## Rails 6.1.2 (February 09, 2021) ## + +* `ActiveSupport::Cache::MemCacheStore` now accepts an explicit `nil` for its `addresses` argument. + + ```ruby + config.cache_store = :mem_cache_store, nil + + # is now equivalent to + + config.cache_store = :mem_cache_store + + # and is also equivalent to + + config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211" + + # which is the fallback behavior of Dalli + ``` + + This helps those migrating from `:dalli_store`, where an explicit `nil` was permitted. + + *Michael Overmeyer* + + +## Rails 6.1.1 (January 07, 2021) ## + +* Change `IPAddr#to_json` to match the behavior of the json gem returning the string representation + instead of the instance variables of the object. + + Before: + + ```ruby + IPAddr.new("127.0.0.1").to_json + # => "{\"addr\":2130706433,\"family\":2,\"mask_addr\":4294967295}" + ``` + + After: + + ```ruby + IPAddr.new("127.0.0.1").to_json + # => "\"127.0.0.1\"" + ``` + + +## Rails 6.1.0 (December 09, 2020) ## + +* Ensure `MemoryStore` disables compression by default. Reverts behavior of + `MemoryStore` to its prior rails `5.1` behavior. + + *Max Gurewitz* + +* Calling `iso8601` on negative durations retains the negative sign on individual + digits instead of prepending it. + + This change is required so we can interoperate with PostgreSQL, which prefers + negative signs for each component. + + Compatibility with other iso8601 parsers which support leading negatives as well + as negatives per component is still retained. + + Before: + + (-1.year - 1.day).iso8601 + # => "-P1Y1D" + + After: + + (-1.year - 1.day).iso8601 + # => "P-1Y-1D" + + *Vipul A M* + +* Remove deprecated `ActiveSupport::Notifications::Instrumenter#end=`. + + *Rafael Mendonça França* + +* Deprecate `ActiveSupport::Multibyte::Unicode.default_normalization_form`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::Multibyte::Unicode.pack_graphemes`, + `ActiveSupport::Multibyte::Unicode.unpack_graphemes`, + `ActiveSupport::Multibyte::Unicode.normalize`, + `ActiveSupport::Multibyte::Unicode.downcase`, + `ActiveSupport::Multibyte::Unicode.upcase` and `ActiveSupport::Multibyte::Unicode.swapcase`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::Multibyte::Chars#consumes?` and `ActiveSupport::Multibyte::Chars#normalize`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/range/include_range`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/hash/transform_values`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/hash/compact`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/array/prepend_and_append`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/numeric/inquiry`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/module/reachable`. + + *Rafael Mendonça França* + +* Remove deprecated `Module#parent_name`, `Module#parent` and `Module#parents`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::LoggerThreadSafeLevel#after_initialize`. + + *Rafael Mendonça França* + +* Remove deprecated `LoggerSilence` constant. + + *Rafael Mendonça França* + +* Remove deprecated fallback to `I18n.default_local` when `config.i18n.fallbacks` is empty. + + *Rafael Mendonça França* + +* Remove entries from local cache on `RedisCacheStore#delete_matched` + + Fixes #38627 + + *ojab* + +* Speed up `ActiveSupport::SecurityUtils.fixed_length_secure_compare` by using + `OpenSSL.fixed_length_secure_compare`, if available. + + *Nate Matykiewicz* + +* `ActiveSupport::Cache::MemCacheStore` now checks `ENV["MEMCACHE_SERVERS"]` before falling back to `"localhost:11211"` if configured without any addresses. + + ```ruby + config.cache_store = :mem_cache_store + + # is now equivalent to + + config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211" + + # instead of + + config.cache_store = :mem_cache_store, "localhost:11211" # ignores ENV["MEMCACHE_SERVERS"] + ``` + + *Sam Bostock* + +* `ActiveSupport::Subscriber#attach_to` now accepts an `inherit_all:` argument. When set to true, + it allows a subscriber to receive events for methods defined in the subscriber's ancestor class(es). + + ```ruby + class ActionControllerSubscriber < ActiveSupport::Subscriber + attach_to :action_controller + + def start_processing(event) + info "Processing by #{event.payload[:controller]}##{event.payload[:action]} as #{format}" + end + + def redirect_to(event) + info { "Redirected to #{event.payload[:location]}" } + end + end + + # We detach ActionControllerSubscriber from the :action_controller namespace so that our CustomActionControllerSubscriber + # can provide its own instrumentation for certain events in the namespace + ActionControllerSubscriber.detach_from(:action_controller) + + class CustomActionControllerSubscriber < ActionControllerSubscriber + attach_to :action_controller, inherit_all: true + + def start_processing(event) + info "A custom response to start_processing events" + end + + # => CustomActionControllerSubscriber will process events for "start_processing.action_controller" notifications + # using its own #start_processing implementation, while retaining ActionControllerSubscriber's instrumentation + # for "redirect_to.action_controller" notifications + end + ``` + + *Adrianna Chang* + +* Allow the digest class used to generate non-sensitive digests to be configured with `config.active_support.hash_digest_class`. + + `config.active_support.use_sha1_digests` is deprecated in favour of `config.active_support.hash_digest_class = ::Digest::SHA1`. + + *Dirkjan Bussink* + +* Fix bug to make memcached write_entry expire correctly with unless_exist + + *Jye Lee* + +* Add `ActiveSupport::Duration` conversion methods + + `in_seconds`, `in_minutes`, `in_hours`, `in_days`, `in_weeks`, `in_months`, and `in_years` return the respective duration covered. + + *Jason York* + +* Fixed issue in `ActiveSupport::Cache::RedisCacheStore` not passing options + to `read_multi` causing `fetch_multi` to not work properly + + *Rajesh Sharma* + +* Fixed issue in `ActiveSupport::Cache::MemCacheStore` which caused duplicate compression, + and caused the provided `compression_threshold` to not be respected. + + *Max Gurewitz* + +* Prevent `RedisCacheStore` and `MemCacheStore` from performing compression + when reading entries written with `raw: true`. + + *Max Gurewitz* + +* `URI.parser` is deprecated and will be removed in Rails 6.2. Use + `URI::DEFAULT_PARSER` instead. + + *Jean Boussier* + +* `require_dependency` has been documented to be _obsolete_ in `:zeitwerk` + mode. The method is not deprecated as such (yet), but applications are + encouraged to not use it. + + In `:zeitwerk` mode, semantics match Ruby's and you do not need to be + defensive with load order. Just refer to classes and modules normally. If + the constant name is dynamic, camelize if needed, and constantize. + + *Xavier Noria* + +* Add 3rd person aliases of `Symbol#start_with?` and `Symbol#end_with?`. + + ```ruby + :foo.starts_with?("f") # => true + :foo.ends_with?("o") # => true + ``` + + *Ryuta Kamizono* + +* Add override of unary plus for `ActiveSupport::Duration`. + + `+ 1.second` is now identical to `+1.second` to prevent errors + where a seemingly innocent change of formatting leads to a change in the code behavior. + + Before: + ```ruby + +1.second.class + # => ActiveSupport::Duration + (+ 1.second).class + # => Integer + ``` + + After: + ```ruby + +1.second.class + # => ActiveSupport::Duration + (+ 1.second).class + # => ActiveSupport::Duration + ``` + + Fixes #39079. + + *Roman Kushnir* + +* Add subsec to `ActiveSupport::TimeWithZone#inspect`. + + Before: + + Time.at(1498099140).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + + After: + + Time.at(1498099140).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.000000000 UTC +00:00" + Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.123456780 UTC +00:00" + Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.333333333 UTC +00:00" + + *akinomaeni* + +* Calling `ActiveSupport::TaggedLogging#tagged` without a block now returns a tagged logger. + + ```ruby + logger.tagged("BCX").info("Funky time!") # => [BCX] Funky time! + ``` + + *Eugene Kenny* + +* Align `Range#cover?` extension behavior with Ruby behavior for backwards ranges. + + `(1..10).cover?(5..3)` now returns `false`, as it does in plain Ruby. + + Also update `#include?` and `#===` behavior to match. + + *Michael Groeneman* + +* Update to TZInfo v2.0.0. + + This changes the output of `ActiveSupport::TimeZone.utc_to_local`, but + can be controlled with the + `ActiveSupport.utc_to_local_returns_utc_offset_times` config. + + New Rails 6.1 apps have it enabled by default, existing apps can upgrade + via the config in config/initializers/new_framework_defaults_6_1.rb + + See the `utc_to_local_returns_utc_offset_times` documentation for details. + + *Phil Ross*, *Jared Beck* + +* Add Date and Time `#yesterday?` and `#tomorrow?` alongside `#today?`. + + Aliased to `#prev_day?` and `#next_day?` to match the existing `#prev/next_day` methods. + + *Jatin Dhankhar* + +* Add `Enumerable#pick` to complement `ActiveRecord::Relation#pick`. + + *Eugene Kenny* + +* [Breaking change] `ActiveSupport::Callbacks#halted_callback_hook` now receive a 2nd argument: + + `ActiveSupport::Callbacks#halted_callback_hook` now receive the name of the callback + being halted as second argument. + This change will allow you to differentiate which callbacks halted the chain + and act accordingly. + + ```ruby + class Book < ApplicationRecord + before_save { throw(:abort) } + before_create { throw(:abort) } + + def halted_callback_hook(filter, callback_name) + Rails.logger.info("Book couldn't be #{callback_name}d") + end + + Book.create # => "Book couldn't be created" + book.save # => "Book couldn't be saved" + end + ``` + + *Edouard Chin* + +* Support `prepend` with `ActiveSupport::Concern`. + + Allows a module with `extend ActiveSupport::Concern` to be prepended. + + module Imposter + extend ActiveSupport::Concern + + # Same as `included`, except only run when prepended. + prepended do + end + end + + class Person + prepend Imposter + end + + Class methods are prepended to the base class, concerning is also + updated: `concerning :Imposter, prepend: true do`. + + *Jason Karns*, *Elia Schito* + +* Deprecate using `Range#include?` method to check the inclusion of a value + in a date time range. It is recommended to use `Range#cover?` method + instead of `Range#include?` to check the inclusion of a value + in a date time range. + + *Vishal Telangre* + +* Support added for a `round_mode` parameter, in all number helpers. (See: `BigDecimal::mode`.) + + ```ruby + number_to_currency(1234567890.50, precision: 0, round_mode: :half_down) # => "$1,234,567,890" + number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%" + number_to_rounded(389.32314, precision: 0, round_mode: :ceil) # => "390" + number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB" + number_to_human(489939, precision: 2, round_mode: :floor) # => "480 Thousand" + + 485000.to_s(:human, precision: 2, round_mode: :half_even) # => "480 Thousand" + ``` + + *Tom Lord* + +* `Array#to_sentence` no longer returns a frozen string. + + Before: + + ['one', 'two'].to_sentence.frozen? + # => true + + After: + + ['one', 'two'].to_sentence.frozen? + # => false + + *Nicolas Dular* + +* When an instance of `ActiveSupport::Duration` is converted to an `iso8601` duration string, if `weeks` are mixed with `date` parts, the `week` part will be converted to days. + This keeps the parser and serializer on the same page. + + ```ruby + duration = ActiveSupport::Duration.build(1000000) + # 1 week, 4 days, 13 hours, 46 minutes, and 40.0 seconds + + duration_iso = duration.iso8601 + # P11DT13H46M40S + + ActiveSupport::Duration.parse(duration_iso) + # 11 days, 13 hours, 46 minutes, and 40 seconds + + duration = ActiveSupport::Duration.build(604800) + # 1 week + + duration_iso = duration.iso8601 + # P1W + + ActiveSupport::Duration.parse(duration_iso) + # 1 week + ``` + + *Abhishek Sarkar* + +* Add block support to `ActiveSupport::Testing::TimeHelpers#travel_back`. + + *Tim Masliuchenko* + +* Update `ActiveSupport::Messages::Metadata#fresh?` to work for cookies with expiry set when + `ActiveSupport.parse_json_times = true`. + + *Christian Gregg* + +* Support symbolic links for `content_path` in `ActiveSupport::EncryptedFile`. + + *Takumi Shotoku* + +* Improve `Range#===`, `Range#include?`, and `Range#cover?` to work with beginless (startless) + and endless range targets. + + *Allen Hsu*, *Andrew Hodgkinson* + +* Don't use `Process#clock_gettime(CLOCK_THREAD_CPUTIME_ID)` on Solaris. + + *Iain Beeston* + +* Prevent `ActiveSupport::Duration.build(value)` from creating instances of + `ActiveSupport::Duration` unless `value` is of type `Numeric`. + + Addresses the errant set of behaviours described in #37012 where + `ActiveSupport::Duration` comparisons would fail confusingly + or return unexpected results when comparing durations built from instances of `String`. + + Before: + + small_duration_from_string = ActiveSupport::Duration.build('9') + large_duration_from_string = ActiveSupport::Duration.build('100000000000000') + small_duration_from_int = ActiveSupport::Duration.build(9) + + large_duration_from_string > small_duration_from_string + # => false + + small_duration_from_string == small_duration_from_int + # => false + + small_duration_from_int < large_duration_from_string + # => ArgumentError (comparison of ActiveSupport::Duration::Scalar with ActiveSupport::Duration failed) + + large_duration_from_string > small_duration_from_int + # => ArgumentError (comparison of String with ActiveSupport::Duration failed) + + After: + + small_duration_from_string = ActiveSupport::Duration.build('9') + # => TypeError (can't build an ActiveSupport::Duration from a String) + + *Alexei Emam* + +* Add `ActiveSupport::Cache::Store#delete_multi` method to delete multiple keys from the cache store. + + *Peter Zhu* + +* Support multiple arguments in `HashWithIndifferentAccess` for `merge` and `update` methods, to + follow Ruby 2.6 addition. + + *Wojciech Wnętrzak* + +* Allow initializing `thread_mattr_*` attributes via `:default` option. + + class Scraper + thread_mattr_reader :client, default: Api::Client.new + end + + *Guilherme Mansur* + +* Add `compact_blank` for those times when you want to remove #blank? values from + an Enumerable (also `compact_blank!` on Hash, Array, ActionController::Parameters). + + *Dana Sherson* + +* Make ActiveSupport::Logger Fiber-safe. + + Use `Fiber.current.__id__` in `ActiveSupport::Logger#local_level=` in order + to make log level local to Ruby Fibers in addition to Threads. + + Example: + + logger = ActiveSupport::Logger.new(STDOUT) + logger.level = 1 + puts "Main is debug? #{logger.debug?}" + + Fiber.new { + logger.local_level = 0 + puts "Thread is debug? #{logger.debug?}" + }.resume + + puts "Main is debug? #{logger.debug?}" + + Before: + + Main is debug? false + Thread is debug? true + Main is debug? true + + After: + + Main is debug? false + Thread is debug? true + Main is debug? false + + Fixes #36752. + + *Alexander Varnin* + +* Allow the `on_rotation` proc used when decrypting/verifying a message to be + passed at the constructor level. + + Before: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret') + crypt.decrypt_and_verify(encrypted_message, on_rotation: proc { ... }) + crypt.decrypt_and_verify(another_encrypted_message, on_rotation: proc { ... }) + + After: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... }) + crypt.decrypt_and_verify(encrypted_message) + crypt.decrypt_and_verify(another_encrypted_message) + + *Edouard Chin* + +* `delegate_missing_to` would raise a `DelegationError` if the object + delegated to was `nil`. Now the `allow_nil` option has been added to enable + the user to specify they want `nil` returned in this case. + + *Matthew Tanous* + +* `truncate` would return the original string if it was too short to be truncated + and a frozen string if it were long enough to be truncated. Now truncate will + consistently return an unfrozen string regardless. This behavior is consistent + with `gsub` and `strip`. + + Before: + + 'foobar'.truncate(5).frozen? + # => true + 'foobar'.truncate(6).frozen? + # => false + + After: + + 'foobar'.truncate(5).frozen? + # => false + 'foobar'.truncate(6).frozen? + # => false + + *Jordan Thomas* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/MIT-LICENSE new file mode 100644 index 0000000000..dbae0858e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005-2020 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.0.0/gems/activesupport-6.1.4/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/README.rdoc new file mode 100644 index 0000000000..c2df6d73b2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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://edgeguides.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.0.0/gems/activesupport-6.1.4/lib/active_support.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support.rb new file mode 100644 index 0000000000..6cf116ac9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2005-2020 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/logger" +require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + extend ActiveSupport::Autoload + + autoload :Concern + autoload :ActionableError + autoload :ConfigurationFile + autoload :CurrentAttributes + autoload :Dependencies + autoload :DescendantsTracker + autoload :ExecutionWrapper + autoload :Executor + autoload :FileUpdateChecker + autoload :EventedFileUpdateChecker + autoload :ForkTracker + autoload :LogSubscriber + autoload :Notifications + autoload :Reloader + autoload :SecureCompareRotator + + eager_autoload do + autoload :BacktraceCleaner + autoload :ProxyObject + autoload :Benchmarkable + autoload :Cache + autoload :Callbacks + autoload :Configurable + autoload :Deprecation + autoload :Digest + autoload :Gzip + autoload :Inflector + autoload :JSON + autoload :KeyGenerator + autoload :MessageEncryptor + autoload :MessageVerifier + 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: + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/actionable_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/actionable_error.rb new file mode 100644 index 0000000000..7db14cd178 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/actionable_error.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + # Actionable errors let's 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.0.0/gems/activesupport-6.1.4/lib/active_support/all.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/all.rb new file mode 100644 index 0000000000..4adf446af8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/array_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/array_inquirer.rb new file mode 100644 index 0000000000..0cbba0b6df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/array_inquirer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" + +module ActiveSupport + # 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, *args) + if name.end_with?("?") + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/backtrace_cleaner.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/backtrace_cleaner.rb new file mode 100644 index 0000000000..03c7dc75d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/backtrace_cleaner.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module ActiveSupport + # 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 + # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # 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_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 + + # 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" + # backtrace_cleaner.add_filter { |line| line.gsub(Rails.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 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_gem_silencer + add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) } + end + + def add_stdlib_silencer + add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) } + end + + def filter_backtrace(backtrace) + @filters.each do |f| + backtrace = backtrace.map { |line| f.call(line) } + end + + backtrace + end + + def silence(backtrace) + @silencers.each do |s| + backtrace = backtrace.reject { |line| s.call(line) } + end + + backtrace + end + + def noise(backtrace) + backtrace.select do |line| + @silencers.any? do |s| + s.call(line) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/benchmarkable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/benchmarkable.rb new file mode 100644 index 0000000000..abd0d59d9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/benchmarkable.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" + +module ActiveSupport + 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 = {}) + if logger + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + + result = nil + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ]) + result + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/builder.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/builder.rb new file mode 100644 index 0000000000..3fa7e6b26d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/builder.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +begin + require "builder" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.4/lib/active_support/cache.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache.rb new file mode 100644 index 0000000000..ecc6085348 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache.rb @@ -0,0 +1,878 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +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/numeric/time" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" +require "active_support/core_ext/string/inflections" + +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 = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder] + + module Strategy + autoload :LocalCache, "active_support/cache/strategy/local_cache" + end + + class << self + # 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! + # clean this up once Ruby 2.7 support is dropped + # see https://github.com/rails/rails/pull/41522#discussion_r581186602 + if options.empty? + retrieve_store_class(store).new(*parameters) + else + retrieve_store_class(store).new(*parameters, **options) + end + 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 + + # 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 serializable Ruby object. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # + # cache.read('city') # => nil + # cache.write('city', "Duckburgh") + # cache.read('city') # => "Duckburgh" + # + # 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 + # + # Cached data larger than 1kB are compressed by default. To turn off + # compression, pass compress: false to the initializer or to + # individual +fetch+ or +write+ method calls. The 1kB compression + # threshold is configurable with the :compress_threshold option, + # specified in bytes. + class Store + DEFAULT_CODER = Marshal + + cattr_accessor :logger, instance_writer: true + + attr_reader :silence, :options + alias :silence? :silence + + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + + # Creates a new cache. The options will be passed to any write method calls + # except for :namespace which can be used to set the global + # namespace for the cache. + def initialize(options = nil) + @options = options ? options.dup : {} + @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder + end + + # Silences the logger. + def silence! + @silence = true + self + end + + # Silences the logger within a block. + def mute + previous_silence, @silence = defined?(@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" + # + # You may also specify additional options via the +options+ argument. + # Setting 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 Cache#write. + # + # Setting skip_nil: true will not cache nil result: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + # + # + # Setting compress: false disables compression of the cache entry. + # + # Setting :expires_in will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. + # + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) + # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry + # + # Setting :version verifies the cache stored under name + # is of the same version. nil is returned on mismatches despite contents. + # This feature is used to support recyclable cache keys. + # + # Setting :race_condition_ttl is very useful in situations where + # a cache entry is used very frequently and is under heavy load. If a + # cache expires and due to heavy load several different processes will try + # to read data natively and then they all will try to write to cache. To + # avoid that case the first process to find an expired cache entry will + # bump the cache expiration time by the value set in :race_condition_ttl. + # Yes, this process is extending the time for a stale value by another few + # seconds. Because of extended life of the previous cache, other processes + # will continue to use slightly stale data for a just a bit longer. In the + # meantime that first process will go ahead and will write into cache the + # new value. After that all the processes will start getting the new value. + # The key is to keep :race_condition_ttl small. + # + # If the process regenerating the entry errors out, the entry will be + # regenerated after the specified number of seconds. Also note that the + # life of stale cache is extended only if it expired recently. Otherwise + # a new value is generated and :race_condition_ttl does not play + # any role. + # + # # Set all values to expire after one minute. + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) + # + # cache.write('foo', 'original value') + # val_1 = nil + # val_2 = nil + # sleep 60 + # + # Thread.new do + # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # sleep 1 + # 'new value 1' + # end + # end + # + # Thread.new do + # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # 'new value 2' + # end + # end + # + # cache.fetch('foo') # => "original value" + # sleep 10 # First thread extended the life of cache by another 10 seconds + # cache.fetch('foo') # => "new value 1" + # val_1 # => "new value 1" + # val_2 # => "original value" + # + # Other options will be handled by the specific cache store implementation. + # Internally, #fetch calls #read_entry, and calls #write_entry on a cache + # miss. +options+ will be passed to the #read and #write calls. + # + # For example, MemCacheStore's #write method supports the +:raw+ + # option, which tells the memcached server to store all values as strings. + # We can use this option with #fetch too: + # + # cache = ActiveSupport::Cache::MemCacheStore.new + # cache.fetch("foo", force: true, raw: true) do + # :bar + # end + # cache.fetch('foo') # => "bar" + def fetch(name, options = nil, &block) + if block_given? + options = merged_options(options) + key = normalize_key(name, options) + + entry = nil + instrument(:read, name, options) do |payload| + cached_entry = read_entry(key, **options, event: payload) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) + entry = nil if entry && entry.mismatched?(normalize_version(name, options)) + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end + + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, 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 are passed to the underlying cache implementation. + def read(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + instrument(:read, name, 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 + entry.value + 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) + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + read_multi_entries(names, **options, event: payload).tap do |results| + payload[:hits] = results.keys + end + end + end + + # Cache Storage API to write multiple values at once. + def write_multi(hash, options = nil) + options = merged_options(options) + + instrument :write_multi, 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" } + # + # 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? + + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + reads = read_multi_entries(names, **options) + writes = {} + ordered = names.index_with do |name| + reads.fetch(name) { writes[name] = yield(name) } + end + + payload[:hits] = reads.keys + payload[:super_operation] = :fetch_multi + + write_multi(writes, options) + + ordered + end + end + + # Writes the value to the cache, with the key. + # + # Options are passed to the underlying cache implementation. + def write(name, value, options = nil) + options = merged_options(options) + + instrument(:write, name, options) do + entry = Entry.new(value, **options.merge(version: normalize_version(name, options))) + write_entry(normalize_key(name, options), entry, **options) + end + end + + # Deletes an entry in the cache. Returns +true+ if an entry is deleted. + # + # Options are passed to the underlying cache implementation. + def delete(name, options = nil) + options = merged_options(options) + + instrument(:delete, name) do + delete_entry(normalize_key(name, options), **options) + end + end + + # Deletes multiple entries in the cache. + # + # Options are passed to the underlying cache implementation. + def delete_multi(names, options = nil) + options = merged_options(options) + names.map! { |key| normalize_key(key, options) } + + instrument :delete_multi, names 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) + + instrument(:exist?, name) do |payload| + entry = read_entry(normalize_key(name, options), **options, event: payload) + (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false + end + 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 + + # Cleanups 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 + # 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) + @coder.dump(entry) + end + + def deserialize_entry(payload) + payload.nil? ? nil : @coder.load(payload) + 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 + if options.empty? + call_options + else + options.merge(call_options) + end + else + options + end + end + + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(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, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + 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) + if logger && logger.debug? && !silence? + logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" + end + + payload = { key: key, store: self.class.name } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } + 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 + race_ttl + write_entry(key, entry, 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, options) + result = instrument(:generate, name, options) do + yield(name) + end + + write(name, result, options) unless result.nil? && options[:skip_nil] + result + end + end + + module NullCoder # :nodoc: + class << self + def load(payload) + payload + end + + def dump(entry) + entry + end + end + end + + # 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: + attr_reader :version + + DEFAULT_COMPRESS_LIMIT = 1.kilobyte + + # Creates a new cache entry for the specified value. Options supported are + # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+. + def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **) + @value = value + @version = version + @created_at = Time.now.to_f + @expires_in = expires_in && expires_in.to_f + + compress!(compress_threshold) if compress + 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 + + # 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 + + private + def compress!(compress_threshold) + 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 + @value = compressed + @compressed = true + end + end + end + + def compressed? + defined?(@compressed) + end + + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/file_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/file_store.rb new file mode 100644 index 0000000000..2ab632a3a5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/file_store.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require "active_support/core_ext/marshal" +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" + +module ActiveSupport + module Cache + # A cache store implementation which stores everything on the filesystem. + # + # FileStore implements the Strategy::LocalCache strategy which implements + # an in-memory cache inside of a block. + class FileStore < Store + prepend Strategy::LocalCache + 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 + + # Increments an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrements an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + search_dir(cache_path) do |path| + key = file_path_key(path) + delete_entry(path, **options) if key.match(matcher) + end + end + end + + private + def read_entry(key, **options) + if File.exist?(key) + entry = File.open(key) { |f| deserialize_entry(f.read) } + entry if entry.is_a?(Cache::Entry) + end + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger + nil + end + + def write_entry(key, entry, **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(serialize_entry(entry)) } + true + end + + def delete_entry(key, **options) + if File.exist?(key) + begin + File.delete(key) + delete_empty_directories(File.dirname(key)) + true + rescue => e + # Just in case the error was caused by another process deleting the file first. + raise e if File.exist?(key) + false + end + 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 + 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 already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def modify_value(name, amount, options) + file_name = normalize_key(name, options) + + lock_file(file_name) do + options = merged_options(options) + + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/mem_cache_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/mem_cache_store.rb new file mode 100644 index 0000000000..bb04a94cbe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/mem_cache_store.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +begin + require "dalli" +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/marshal" +require "active_support/core_ext/array/extract_options" + +module ActiveSupport + module Cache + # 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 + DEFAULT_CODER = NullCoder # Dalli automatically Marshal values + + # Provide support for raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def write_entry(key, entry, **options) + if options[:raw] && local_cache + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, **options) + else + super + end + end + end + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + 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.empty? + Dalli::Client.new(addresses, options) + else + ensure_connection_pool_added! + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + 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! + super(options) + + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." + end + if addresses.first.is_a?(Dalli::Client) + @data = addresses.first + else + mem_cache_options = options.dup + UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) } + @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) + end + end + + # Increment a cached value. This method uses the memcached incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:increment, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Decrement a cached value. This method uses the memcached decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:decrement, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } + 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) + rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) } + end + + # Write an entry to the cache. + def write_entry(key, entry, **options) + method = options[:unless_exist] ? :add : :set + value = options[:raw] ? entry.value.to_s : serialize_entry(entry) + 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 false do + # The value "compress: false" prevents duplicate compression within Dalli. + @data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) } + 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 = @data.with { |c| c.get_multi(keys_to_names.keys) } + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value) + + unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + values[keys_to_names[key]] = entry.value + 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 + + # 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}" } + key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 + end + + key + end + + def deserialize_entry(payload) + entry = super + entry = Entry.new(entry, compress: false) unless entry.nil? || entry.is_a?(Entry) + entry + end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + fallback + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/memory_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/memory_store.rb new file mode 100644 index 0000000000..4bb89934e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/memory_store.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Cache + # 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: + class << self + def load(entry) + entry = entry.dup + entry.dup_value! + entry + end + + def dump(entry) + entry.dup_value! + entry + end + end + end + + DEFAULT_CODER = DupCoder + + def initialize(options = nil) + options ||= {} + # 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 = Concurrent.monotonic_time + 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 && Concurrent.monotonic_time - 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 an integer value in the cache. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrement an integer value in the cache. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + # Deletes cache entries if the cache key matches a given pattern. + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + 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) + synchronize do + return false if options[:unless_exist] && @data.key?(key) + + 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 + + def modify_value(name, amount, options) + options = merged_options(options) + synchronize do + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/null_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/null_store.rb new file mode 100644 index 0000000000..5a6b97b853 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/null_store.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + module Cache + # 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 = nil) + end + + def decrement(name, amount = 1, options = nil) + end + + def delete_matched(matcher, options = nil) + end + + private + def read_entry(key, **options) + end + + def write_entry(key, entry, **options) + true + end + + def delete_entry(key, **options) + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/redis_cache_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 0000000000..1afecb3fb7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,493 @@ +# 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\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + require "redis/connection/hiredis" +rescue LoadError +end + +require "digest/sha2" +require "active_support/core_ext/marshal" + +module ActiveSupport + module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It 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 their own SHA2 digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + 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 + + # Support raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def write_entry(key, entry, **options) + if options[:raw] && local_cache + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, **options) + else + super + end + end + + def write_multi_entries(entries, **options) + if options[:raw] && local_cache + raw_entries = entries.map do |key, entry| + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + end.to_h + + super(raw_entries, **options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + 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 + 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(url:, **redis_options) + ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # 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: … }, …]) + # + # 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. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: DEFAULT_CODER, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl, + coder: coder + end + + def redis + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end + end + + def inspect + instance = @redis || @redis_options + "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end + else + super + 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) + instrument :delete_matched, matcher do + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + redis.with do |c| + pattern = namespace_key(matcher, options) + 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 + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + instrument :increment, name, amount: amount do + failsafe :increment do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.incrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + end + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + instrument :decrement, name, amount: amount do + failsafe :decrement do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.decrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + 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.with { |c| c.flushdb } + end + end + end + + def mget_capable? #:nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? #:nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, **options) + failsafe :read_entry do + raw = options&.fetch(:raw, false) + deserialize_entry(redis.with { |c| c.get(key) }, raw: raw) + end + end + + def read_multi_entries(names, **options) + if mget_capable? + read_multi_mget(*names, **options) + else + super + end + end + + def read_multi_mget(*names) + options = names.extract_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_mget, returning: {}) do + redis.with { |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)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) + serialized_entry = serialize_entry(entry, raw: raw) + + # 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 + + failsafe :write_entry, returning: false do + if unless_exist || expires_in + modifiers = {} + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + + redis.with { |c| c.set key, serialized_entry, **modifiers } + else + redis.with { |c| c.set key, serialized_entry } + end + end + end + + def write_key_expiry(client, key, options) + if options[:expires_in] && client.ttl(key).negative? + client.expire key, options[:expires_in].to_i + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.with { |c| c.del key } + end + end + + # Deletes multiple entries in the cache. Returns the number of entries deleted. + def delete_multi_entries(entries, **_options) + redis.with { |c| c.del(entries) } + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) } + end + else + super + 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 = ":sha2:#{::Digest::SHA2.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(payload, raw:) + if payload && raw + Entry.new(payload, compress: false) + else + super(payload) + end + end + + def serialize_entry(entry, raw: false) + if raw + entry.value.to_s + else + super(entry) + end + end + + def serialize_entries(entries, raw: false) + entries.transform_values do |entry| + serialize_entry entry, raw: raw + end + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseError => e + handle_exception exception: e, method: method, returning: returning + returning + end + + def handle_exception(exception:, method:, returning:) + if @error_handler + @error_handler.(method: method, exception: exception, returning: returning) + end + rescue => failsafe + warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/strategy/local_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/strategy/local_cache.rb new file mode 100644 index 0000000000..96b4e1dfb7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/strategy/local_cache.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" +require "active_support/per_thread_registry" + +module ActiveSupport + module Cache + module 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. + class LocalCacheRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def cache_for(local_cache_key) + @registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + @registry[local_cache_key] = value + end + + def self.set_cache_for(l, v); instance.set_cache_for l, v; end + def self.cache_for(l); instance.cache_for l; end + end + + # 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 < Store + def initialize + super + @data = {} + end + + # Don't allow synchronizing since it isn't thread safe. + def synchronize # :nodoc: + yield + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key, **options) + @data[key] + end + + def read_multi_entries(keys, **options) + values = {} + + keys.each do |name| + entry = read_entry(name, **options) + values[name] = entry.value if entry + end + + values + end + + def write_entry(key, entry, **options) + entry.dup_value! + @data[key] = entry + true + end + + def delete_entry(key, **options) + !!@data.delete(key) + end + + def fetch_entry(key, options = nil) # :nodoc: + entry = @data.fetch(key) { @data[key] = yield } + dup_entry = entry.dup + dup_entry&.dup_value! + dup_entry + end + end + + # Use a local cache for the duration of block. + def with_local_cache + use_temporary_local_cache(LocalStore.new) { yield } + 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) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def cleanup(**options) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def delete_matched(matcher, options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def increment(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, **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, **options) + value + end + + private + def read_entry(key, **options) + if cache = local_cache + hit = true + value = cache.fetch_entry(key) do + hit = false + super + end + options[:event][:store] = cache.class.name if hit && options[:event] + value + else + super + end + end + + def read_multi_entries(keys, **options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys, **options) + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, **options)) + else + local_entries + end + end + + def write_entry(key, entry, **options) + if options[:unless_exist] + local_cache.delete_entry(key, **options) if local_cache + else + local_cache.write_entry(key, entry, **options) if local_cache + end + + super + end + + def delete_entry(key, **options) + local_cache.delete_entry(key, **options) if local_cache + super + end + + def write_cache_value(name, value, **options) + name = normalize_key(name, options) + cache = local_cache + cache.mute do + if value + cache.write(name, value, options) + else + cache.delete(name, **options) + end + 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 + use_temporary_local_cache(nil) { yield } + 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.0.0/gems/activesupport-6.1.4/lib/active_support/cache/strategy/local_cache_middleware.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/cache/strategy/local_cache_middleware.rb new file mode 100644 index 0000000000..62542bdb22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/callbacks.rb new file mode 100644 index 0000000000..1a55f8af35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/callbacks.rb @@ -0,0 +1,862 @@ +# 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 "thread" + +module ActiveSupport + # 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, default: {} + end + + CALLBACK_FILTER_TYPES = [:before, :after, :around] + + # 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) + callbacks = __callbacks[kind.to_sym] + + if callbacks.empty? + yield if block_given? + else + env = Filters::Environment.new(self, false, nil) + next_sequence = callbacks.compile + + # 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: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + + module Filters + Environment = Struct.new(:target, :halted, :value) + + class Before + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name) + halted_lambda = chain_config[:terminator] + + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + else + halting(callback_sequence, user_callback, halted_lambda, filter, name) + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + callback_sequence.before do |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 + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback, halted_lambda, filter, name) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + unless halted + 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 + end + private_class_method :halting + end + + class After + def self.build(callback_sequence, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions) + else + halting(callback_sequence, user_callback) + end + else + if user_conditions.any? + conditional callback_sequence, user_callback, user_conditions + else + simple callback_sequence, user_callback + end + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| + unless env.halted + user_callback.call env.target, env.value + end + + env + end + end + private_class_method :halting + + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :conditional + + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| + user_callback.call env.target, env.value + + env + end + end + private_class_method :simple + 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 + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @key = compute_identifier filter + @if = check_conditionals(options[:if]) + @unless = check_conditionals(options[:unless]) + end + + def filter; @key; end + def raw_filter; @filter; 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 + + # Wraps code with filter + def apply(callback_sequence) + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name) + when :after + Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config) + when :around + callback_sequence.around(user_callback, user_conditions) + end + 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? { |c| c.is_a?(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 compute_identifier(filter) + case filter + when ::Proc + filter.object_id + else + filter + end + end + + def conditions_lambdas + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + end + end + + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + class CallTemplate # :nodoc: + def initialize(target, method, arguments, block) + @override_target = target + @method_name = method + @arguments = arguments + @override_block = block + 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) + expanded = [@override_target || target, @override_block || block, @method_name] + + @arguments.each do |arg| + case arg + when :value then expanded << value + when :target then expanded << target + when :block then expanded << (block || raise(ArgumentError)) + end + end + + expanded + end + + # Return a lambda that will make this call when given the input + # values. + def make_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + target.send(method, *arguments, &block) + end + end + + # Return a lambda that will make this call when given the input + # values, but then return the boolean inverse of that result. + def inverted_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + ! target.send(method, *arguments, &block) + 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 + new(nil, filter, [], nil) + when Conditionals::Value + new(filter, :call, [:target, :value], nil) + when ::Proc + if filter.arity > 1 + new(nil, :instance_exec, [:target, :block], filter) + elsif filter.arity > 0 + new(nil, :instance_exec, [:target], filter) + else + new(nil, :instance_exec, [], filter) + end + else + method_to_call = callback.current_scopes.join("_") + + new(filter, method_to_call, [:target], nil) + 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 = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def 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 = [] + @callbacks = nil + @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) + @callbacks = nil + @chain.insert(index, o) + end + + def delete(o) + @callbacks = nil + @chain.delete(o) + end + + def clear + @callbacks = nil + @chain.clear + self + end + + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile + @callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence + 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) + @callbacks = nil + remove_duplicates(callback) + @chain.push(callback) + end + + def prepend_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.unshift(callback) + end + + def remove_duplicates(callback) + @callbacks = nil + @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] + ActiveSupport::DescendantsTracker.descendants(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. + # + # class Writer < Person + # skip_callback :validate, :before, :check_membership, if: -> { age > 18 } + # end + # + # 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 + + ActiveSupport::DescendantsTracker.descendants(self).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] + ActiveSupport::DescendantsTracker.descendants(self)).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 + + if Module.instance_method(:method_defined?).arity == 1 # Ruby 2.5 and older + def set_callbacks(name, callbacks) # :nodoc: + self.__callbacks = __callbacks.merge(name.to_sym => callbacks) + end + else # Ruby 2.6 and newer + def set_callbacks(name, callbacks) # :nodoc: + unless singleton_class.method_defined?(:__callbacks, false) + self.__callbacks = __callbacks.dup + end + self.__callbacks[name.to_sym] = callbacks + self.__callbacks + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concern.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concern.rb new file mode 100644 index 0000000000..e88862bfb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concern.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +module ActiveSupport + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..480c34c640 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + 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 + Thread.handle_interrupt(EXCEPTION_NEVER) do + mon_enter + + begin + Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do + yield + end + ensure + mon_exit + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/share_lock.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 0000000000..eae7d4469f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require "thread" +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) + @sleeping[Thread.current] = method + @cv.wait_while { yield } + ensure + @sleeping.delete Thread.current + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/configurable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/configurable.rb new file mode 100644 index 0000000000..92bd411df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/configurable.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/ordered_options" + +module ActiveSupport + # 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 a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.hair_colors # => [:brown, :black, :blonde, :red] + def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :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}=", yield) if block_given? + end + end + private :config_accessor + 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.0.0/gems/activesupport-6.1.4/lib/active_support/configuration_file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/configuration_file.rb new file mode 100644 index 0000000000..024f0e0bda --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/configuration_file.rb @@ -0,0 +1,51 @@ +# 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 = render(context) + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(source, **options) || {} + else + YAML.load(source, **options) || {} + 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" + require "erb" + + File.read(content_path).tap do |content| + if content.include?("\u00A0") + warn "File contains invisible non-breaking spaces, you may want to remove those" + end + end + end + + def render(context) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext.rb new file mode 100644 index 0000000000..3f5d08186e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array.rb new file mode 100644 index 0000000000..88b6567712 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/access.rb new file mode 100644 index 0000000000..ea01e5891c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/access.rb @@ -0,0 +1,104 @@ +# 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 for #excluding. + def without(*elements) + excluding(*elements) + end + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/conversions.rb new file mode 100644 index 0000000000..0780c4ed8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/conversions.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +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 the elements + # in arrays with two or more elements (default: ", "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more 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 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. + # + # Blog.all.to_formatted_s(:db) # => "1,2,3" + # Blog.none.to_formatted_s(:db) # => "null" + # [1,2].to_formatted_s # => "[1, 2]" + def to_formatted_s(format = :default) + case format + when :db + if empty? + "null" + else + collect(&:id).join(",") + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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? { |e| e.is_a?(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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/extract.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/extract.rb new file mode 100644 index 0000000000..cc5a8a3f88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/extract_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/extract_options.rb new file mode 100644 index 0000000000..8c7cb2e780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/grouping.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/grouping.rb new file mode 100644 index 0000000000..67e760bc4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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) + 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) { |slice| yield(slice) } + 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) + # 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 { |g| yield(g) } + 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) + arr = dup + result = [] + if block_given? + while (idx = arr.index { |i| yield i }) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/inquiry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 0000000000..92c61bf201 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 +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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/wrap.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/array/wrap.rb new file mode 100644 index 0000000000..d62f97edbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/benchmark.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/benchmark.rb new file mode 100644 index 0000000000..f6e1b72bcf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/benchmark.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "benchmark" + +class << Benchmark + # Benchmark realtime in milliseconds. + # + # Benchmark.realtime { User.all } + # # => 8.0e-05 + # + # Benchmark.ms { User.all } + # # => 0.074 + def ms(&block) + 1000 * realtime(&block) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/big_decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/big_decimal.rb new file mode 100644 index 0000000000..9e6a9d6331 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/big_decimal/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/big_decimal/conversions.rb new file mode 100644 index 0000000000..52bd229416 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class.rb new file mode 100644 index 0000000000..1c110fd07b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/attribute.rb new file mode 100644 index 0000000000..ec78845159 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/attribute.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +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 + + class_methods << <<~RUBY # In case the method exists and is not public + silence_redefinition_of_method def #{name} + end + RUBY + + methods << <<~RUBY if instance_reader + silence_redefinition_of_method def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end + RUBY + + class_methods << <<~RUBY + silence_redefinition_of_method def #{name}=(value) + redefine_method(:#{name}) { value } if singleton_class? + redefine_singleton_method(:#{name}) { value } + value + end + RUBY + + 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) + + attrs.each { |name| public_send("#{name}=", default) } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/attribute_accessors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/attribute_accessors.rb new file mode 100644 index 0000000000..a77354e153 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/subclasses.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/subclasses.rb new file mode 100644 index 0000000000..568b413516 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/class/subclasses.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +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 + ObjectSpace.each_object(singleton_class).reject do |k| + k.singleton_class? || k == self + end + end + + # Returns an array with the direct children of +self+. + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Bar; end + # + # Foo.subclasses # => [Bar] + def subclasses + descendants.select { |descendant| descendant.superclass == self } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000000..cce73f2db2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/acts_like.rb new file mode 100644 index 0000000000..c8077f3774 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 0000000000..e6271c79b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class Date #:nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/calculations.rb new file mode 100644 index 0000000000..d03a8d3997 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/calculations.rb @@ -0,0 +1,146 @@ +# 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 + Thread.current[: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) + Thread.current[: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. + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 0000000000..050a62bb31 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,97 @@ +# 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", + iso8601: lambda { |date| date.iso8601 } + } + + # Convert to a formatted string. See DATE_FORMATS for predefined formats. + # + # This method is aliased to to_s. + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_formatted_s(:db) # => "2007-11-10" + # date.to_s(:db) # => "2007-11-10" + # + # date.to_formatted_s(:short) # => "10 Nov" + # date.to_formatted_s(:number) # => "20071110" + # date.to_formatted_s(:long) # => "November 10, 2007" + # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" + # date.to_formatted_s(:rfc822) # => "10 Nov 2007" + # date.to_formatted_s(:iso8601) # => "2007-11-10" + # + # == Adding your own date formats to to_formatted_s + # 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_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date/zones.rb new file mode 100644 index 0000000000..2dcf97cff8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 0000000000..21cfeed557 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,364 @@ +# 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 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 0000000000..b40a0fabb1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +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_writer: false, default: false + + # Change the output of ActiveSupport::TimeZone.utc_to_local. + # + # When `true`, it returns local times with an 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_and_time/zones.rb new file mode 100644 index 0000000000..fb6a27cb27 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time.rb new file mode 100644 index 0000000000..790dbeec1b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/acts_like.rb new file mode 100644 index 0000000000..5dccdfe219 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 0000000000..a52c8bc150 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class DateTime #:nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/calculations.rb new file mode 100644 index 0000000000..bc670c3e76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/calculations.rb @@ -0,0 +1,211 @@ +# 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. + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 0000000000..7600a067cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/date_time/conversions.rb new file mode 100644 index 0000000000..231bf870a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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_s. + # + # === Examples + # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 + # + # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:number) # => "20071204000000" + # datetime.to_formatted_s(:short) # => "04 Dec 00:00" + # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" + # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" + # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" + # + # == Adding your own datetime formats to to_formatted_s + # 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_formatted_s(format = :default) + if formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) + alias_method :to_s, :to_formatted_s + + # 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_s(: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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/digest.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/digest.rb new file mode 100644 index 0000000000..ce1427e13a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/digest/uuid.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 0000000000..6e949a2d72 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "securerandom" + +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 Digest::MD5 generates version 3 UUIDs; 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, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + 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 Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/enumerable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 0000000000..97c918a71f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +module Enumerable + INDEX_WITH_DEFAULT = Object.new + private_constant :INDEX_WITH_DEFAULT + + # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements + # when we omit an identity. + + # :stopdoc: + + # We can't use Refinements here because Refinements with Module which will be prepended + # doesn't work well https://bugs.ruby-lang.org/issues/13446 + alias :_original_sum_with_required_identity :sum + private :_original_sum_with_required_identity + + # :startdoc: + + # Calculates a sum from the elements. + # + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum # => "foobar" + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum(identity) + else + inject(:+) || 0 + end + 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 = INDEX_WITH_DEFAULT) + if block_given? + result = {} + each { |elem| result[elem] = yield(elem) } + result + elsif default != INDEX_WITH_DEFAULT + result = {} + each { |elem| result[elem] = default } + result + else + to_enum(:index_with) { size if respond_to?(:size) } + 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 |element| + cnt += 1 if yield element + 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 for #excluding. + def without(*elements) + excluding(*elements) + end + + # 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, 2]) + # # => [2, 1] (or [1, 2]) + # + # 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 +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(identity = nil) + 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 = identity || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity || 0 + end + end + end +end + +# Using Refinements here in order not to expose our internal method +using Module.new { + refine Array do + alias :orig_sum :sum + end +} + +class Array #:nodoc: + # Array#sum was added in Ruby 2.4 but it only works with Numeric elements. + def sum(init = nil, &block) + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super + end + end + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/file.rb new file mode 100644 index 0000000000..64553bfa4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/file/atomic.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/file/atomic.rb new file mode 100644 index 0000000000..9deceb1bb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/file/atomic.rb @@ -0,0 +1,70 @@ +# 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) + ensure + FileUtils.rm_f(file_name) if file_name + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash.rb new file mode 100644 index 0000000000..2f0901d853 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/conversions.rb new file mode 100644 index 0000000000..2b5e484d21 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/conversions.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/deep_merge.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/deep_merge.rb new file mode 100644 index 0000000000..9bc50b7bc6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/deep_merge.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Hash + # 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 } } + def deep_merge(other_hash, &block) + dup.deep_merge!(other_hash, &block) + end + + # Same as +deep_merge+, but modifies +self+. + def deep_merge!(other_hash, &block) + merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) + else + other_val + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/deep_transform_values.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/deep_transform_values.rb new file mode 100644 index 0000000000..8ad85c0a6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/except.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/except.rb new file mode 100644 index 0000000000..ec96929b0a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/except.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Hash + # Returns a hash that includes everything except given keys. + # hash = { a: true, b: false, c: nil } + # hash.except(:c) # => { a: true, b: false } + # hash.except(:a, :b) # => { c: nil } + # hash # => { a: true, b: false, c: nil } + # + # This is useful for limiting a set of parameters to everything but a few known toggles: + # @person.update(params[:person].except(:admin)) + def except(*keys) + slice(*self.keys - keys) + end unless method_defined?(:except) + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/indifferent_access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/indifferent_access.rb new file mode 100644 index 0000000000..a38f33f128 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 overwrite 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/keys.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/keys.rb new file mode 100644 index 0000000000..f2db61f386 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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(&:to_s) + end + + # Destructively converts all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!(&: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(&: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!(&: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({}) 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/reverse_merge.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/reverse_merge.rb new file mode 100644 index 0000000000..ef8d592829 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/slice.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/hash/slice.rb new file mode 100644 index 0000000000..56bc5de382 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer.rb new file mode 100644 index 0000000000..d22701306a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 0000000000..aef3266f28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/multiple.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/multiple.rb new file mode 100644 index 0000000000..bd57a909c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 0000000000..5efb89cf9f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel.rb new file mode 100644 index 0000000000..7708069301 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/concern.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/concern.rb new file mode 100644 index 0000000000..0b2baed780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/reporting.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 0000000000..9155bd6c10 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 + with_warnings(nil) { yield } + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings + with_warnings(true) { yield } + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/singleton_class.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/kernel/singleton_class.rb new file mode 100644 index 0000000000..6715eba80a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/load_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/load_error.rb new file mode 100644 index 0000000000..03df2ddac4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/marshal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/marshal.rb new file mode 100644 index 0000000000..5ff0e34d82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/marshal.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module MarshalWithAutoloading # :nodoc: + def load(source, proc = nil) + super(source, proc) + rescue ArgumentError, NameError => exc + if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) + # try loading the class/module + loaded = $1.constantize + + raise unless $1 == loaded.name + + # if it is an IO we need to go back to read the object + source.rewind if source.respond_to?(:rewind) + retry + else + raise exc + end + end + end +end + +Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module.rb new file mode 100644 index 0000000000..542af98c04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/aliasing.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 0000000000..6f64d11627 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/anonymous.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/anonymous.rb new file mode 100644 index 0000000000..d1c86b8722 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attr_internal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attr_internal.rb new file mode 100644 index 0000000000..3bd66ff3bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attr_internal.rb @@ -0,0 +1,38 @@ +# 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_accessor :attr_internal_naming_format end + self.attr_internal_naming_format = "@_%s" + + private + def attr_internal_ivar_name(attr) + Module.attr_internal_naming_format % attr + end + + def attr_internal_define(attr_name, type) + internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@") + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 0000000000..1db905ff65 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +# 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb new file mode 100644 index 0000000000..ea4034303e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +# 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. +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 # => nil + # Thread.current[:attr_Current_user] = "DHH" + # Current.user # => "DHH" + # + # 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 `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + Thread.current["attr_" + name + "_#{sym}"] + end + EOS + + if instance_reader && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + self.class.#{sym} + end + EOS + end + + Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil? + 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, 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 `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) + Thread.current["attr_" + name + "_#{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 + + public_send("#{sym}=", default) unless default.nil? + 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" + # + # If a subclass changes the value, the parent class' value is not changed. + # Similarly, if the parent class changes the value, the value of subclasses + # is not changed. + # + # class Customer < Account + # end + # + # 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 + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/concerning.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 0000000000..36f5f85937 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/delegation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 0000000000..a44a3e7c74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,330 @@ +# frozen_string_literal: true + +require "set" + +class Module + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError; end + + RUBY_RESERVED_KEYWORDS = %w(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) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) + DELEGATION_RESERVED_METHOD_NAMES = Set.new( + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS + ).freeze + + # 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 +Module::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 + # +Module::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 + # # => Module::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) + 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 + + to = to.to_s + to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) + + method_def = [] + method_names = [] + + methods.map 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 /[^\]]=$/.match?(method) + "arg" + elsif RUBY_VERSION >= "2.7" + "..." + else + "*args, &block" + 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 allow_nil + method = method.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{to}" << + " 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})" << + " _ = #{to}" << + " _.#{method}(#{definition})" << + "rescue NoMethodError => e" << + " if _.nil? && e.name == :#{method}" << + %( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") << + " else" << + " raise" << + " end" << + "end" + end + end + module_eval(method_def.join(";"), file, line) + private(*method_names) if private + method_names + 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 +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) + target = target.to_s + target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) + + 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, *args, &block) + if #{target}.respond_to?(method) + #{target}.public_send(method, *args, &block) + else + begin + super + rescue NoMethodError + if #{target}.nil? + if #{allow_nil == true} + nil + else + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + end + else + raise + end + end + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + RUBY + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/deprecation.rb new file mode 100644 index 0000000000..71c42eb357 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/deprecation.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Module + # deprecate :foo + # deprecate bar: 'message' + # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!' + # + # You can also use custom deprecator instance: + # + # deprecate :foo, deprecator: MyLib::Deprecator.new + # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new + # + # \Custom deprecators must respond to deprecation_warning(deprecated_method_name, message, caller_backtrace) + # method 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) + ActiveSupport::Deprecation.deprecate_methods(self, *method_names) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/introspection.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 0000000000..7cdcab59b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +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 + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/redefine_method.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/redefine_method.rb new file mode 100644 index 0000000000..5bd8e6e973 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/remove_method.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 0000000000..97eb5f9eca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/name_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/name_error.rb new file mode 100644 index 0000000000..15255d58b9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,65 @@ +# 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 + + if UnboundMethod.method_defined?(:bind_call) + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind_call(mod) + end + else + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric.rb new file mode 100644 index 0000000000..fe778470f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/bytes.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/bytes.rb new file mode 100644 index 0000000000..b002eba406 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/bytes.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class Numeric + KILOBYTE = 1024 + MEGABYTE = KILOBYTE * 1024 + GIGABYTE = MEGABYTE * 1024 + TERABYTE = GIGABYTE * 1024 + PETABYTE = TERABYTE * 1024 + EXABYTE = PETABYTE * 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 0000000000..3e623e0d17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" + +module ActiveSupport + module NumericWithFormat + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size and pretty printing. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_s(:phone) # => "555-1234" + # 1235551234.to_s(:phone) # => "123-555-1234" + # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234" + # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # Currency: + # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_s(:currency, round_mode: :down) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €" + # -1234567890.50.to_s(:currency, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # + # Percentage: + # 100.to_s(:percentage) # => "100.000%" + # 100.to_s(:percentage, precision: 0) # => "100%" + # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%" + # 302.24398923423.to_s(:percentage, round_mode: :down) # => "302.243%" + # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_s(:percentage, format: '%n %') # => "100.000 %" + # + # Delimited: + # 12345678.to_s(:delimited) # => "12,345,678" + # 12345678.05.to_s(:delimited) # => "12,345,678.05" + # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05" + # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # + # Rounded: + # 111.2345.to_s(:rounded) # => "111.235" + # 111.2345.to_s(:rounded, precision: 2) # => "111.23" + # 111.2345.to_s(:rounded, precision: 2, round_mode: :up) # => "111.24" + # 13.to_s(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_s(:rounded, precision: 0) # => "389" + # 111.2345.to_s(:rounded, significant: true) # => "111" + # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100" + # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_s(:rounded, locale: :fr) # => "111,234" + # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3" + # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + # + # Human-friendly size in Bytes: + # 123.to_s(:human_size) # => "123 Bytes" + # 1234.to_s(:human_size) # => "1.21 KB" + # 12345.to_s(:human_size) # => "12.1 KB" + # 1234567.to_s(:human_size) # => "1.18 MB" + # 1234567890.to_s(:human_size) # => "1.15 GB" + # 1234567890123.to_s(:human_size) # => "1.12 TB" + # 1234567890123456.to_s(:human_size) # => "1.1 PB" + # 1234567890123456789.to_s(:human_size) # => "1.07 EB" + # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB" + # 1234567.to_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB" + # 483989.to_s(:human_size, precision: 2) # => "470 KB" + # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB" + # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" + # 524288000.to_s(:human_size, precision: 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_s(:human) # => "123" + # 1234.to_s(:human) # => "1.23 Thousand" + # 12345.to_s(:human) # => "12.3 Thousand" + # 1234567.to_s(:human) # => "1.23 Million" + # 1234567890.to_s(:human) # => "1.23 Billion" + # 1234567890123.to_s(:human) # => "1.23 Trillion" + # 1234567890123456.to_s(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_s(:human) # => "1230 Quadrillion" + # 489939.to_s(:human, precision: 2) # => "490 Thousand" + # 489939.to_s(:human, precision: 2, round_mode: :down) # => "480 Thousand" + # 489939.to_s(:human, precision: 4) # => "489.9 Thousand" + # 1234567.to_s(:human, precision: 4, + # significant: false) # => "1.2346 Million" + # 1234567.to_s(:human, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + def to_s(format = nil, options = nil) + case format + when nil + super() + when Integer, String + super(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 + super() + else + super(format) + end + end + end +end + +Integer.prepend ActiveSupport::NumericWithFormat +Float.prepend ActiveSupport::NumericWithFormat +BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 0000000000..bc4627f7a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object.rb new file mode 100644 index 0000000000..efd34cc692 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object.rb @@ -0,0 +1,16 @@ +# 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_options" diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/acts_like.rb new file mode 100644 index 0000000000..403ee20e39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/acts_like.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Object + # A duck-type assistant method. For example, Active Support extends Date + # to define an acts_like_date? method, and extends Time to define + # acts_like_time?. As a result, we can do x.acts_like?(:time) and + # x.acts_like?(:date) to do duck-type-safe comparisons, since classes that + # we want to act like Time simply need to define an acts_like_time? method. + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/blank.rb new file mode 100644 index 0000000000..f36fef6cc9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/blank.rb @@ -0,0 +1,155 @@ +# 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? : !self + 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 +end + +class FalseClass + # +false+ is blank: + # + # false.blank? # => true + # + # @return [true] + def blank? + true + end +end + +class TrueClass + # +true+ is not blank: + # + # true.blank? # => false + # + # @return [false] + def blank? + false + 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? +end + +class Hash + # A hash is blank if it's empty: + # + # {}.blank? # => true + # { key: 'value' }.blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? +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 +end + +class Numeric #:nodoc: + # No number is blank: + # + # 1.blank? # => false + # 0.blank? # => false + # + # @return [false] + def blank? + false + end +end + +class Time #:nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/conversions.rb new file mode 100644 index 0000000000..624fb8d77c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/deep_dup.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/deep_dup.rb new file mode 100644 index 0000000000..5c903917f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,55 @@ +# 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 && key.frozen?) || ::Symbol === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end + end + hash + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/duplicable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/duplicable.rb new file mode 100644 index 0000000000..3ebcdca02b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/duplicable.rb @@ -0,0 +1,49 @@ +# 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 + +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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/inclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/inclusion.rb new file mode 100644 index 0000000000..6064e92f20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/inclusion.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Object + # Returns true if this object is included in the argument. Argument must be + # any object which responds to +#include?+. Usage: + # + # characters = ["Konata", "Kagami", "Tsukasa"] + # "Konata".in?(characters) # => true + # + # This will throw an +ArgumentError+ if the argument doesn't respond + # to +#include?+. + def in?(another_object) + another_object.include?(self) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/instance_variables.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/instance_variables.rb new file mode 100644 index 0000000000..12fdf840b5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/instance_variables.rb @@ -0,0 +1,30 @@ +# 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 + Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] + 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(&:to_s) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/json.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/json.rb new file mode 100644 index 0000000000..a65e273236 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +# Hack to load json gem first so we can overwrite 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 +# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +# +# 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.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder) +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 Struct #:nodoc: + def as_json(options = nil) + Hash[members.zip(values)].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: + to_s + 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: + map { |v| options ? v.as_json(options.dup) : v.as_json } + 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 = {} + subset.each do |k, v| + result[k.to_s] = options ? v.as_json(options.dup) : 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 + +class IPAddr # :nodoc: + def as_json(options = nil) + to_s + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/to_param.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 0000000000..6d2bdd70f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/to_query.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 0000000000..bac6ff9c97 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,89 @@ +# 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. + # + # This method is also aliased as +to_param+. + def to_query(namespace = nil) + query = collect do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end.compact + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") + end + + alias_method :to_param, :to_query +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/try.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/try.rb new file mode 100644 index 0000000000..999141a993 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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(method_name = nil, *args, &b) + if method_name.nil? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + elsif respond_to?(method_name) + public_send(method_name, *args, &b) + end + end + ruby2_keywords(:try) if respond_to?(:ruby2_keywords, true) + + def try!(method_name = nil, *args, &b) + if method_name.nil? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + else + public_send(method_name, *args, &b) + end + end + ruby2_keywords(:try!) if respond_to?(:ruby2_keywords, true) + end +end + +class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*a, &b) + # + # 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!(*a, &b) + # + # 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(a*, &b) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(a*, &b) + # + # 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(_method_name = nil, *) + nil + end + + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil + def try!(_method_name = nil, *) + nil + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/with_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/with_options.rb new file mode 100644 index 0000000000..1d46add6e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/object/with_options.rb @@ -0,0 +1,82 @@ +# 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 + # 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 + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range.rb new file mode 100644 index 0000000000..78814fd189 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range.rb @@ -0,0 +1,7 @@ +# 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/include_time_with_zone" +require "active_support/core_ext/range/overlaps" +require "active_support/core_ext/range/each" diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/compare_range.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 0000000000..c6d5b77e8e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,82 @@ +# 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 + + # Extends the default Range#cover? to support range comparisons. + # (1..5).cover?(1..5) # => true + # (1..5).cover?(2..3) # => true + # (1..5).cover?(1...6) # => true + # (1..5).cover?(2..6) # => false + # + # The native Range#cover? behavior is untouched. + # ('a'..'f').cover?('c') # => true + # (5..9).cover?(11) # => false + # + # The given range must be fully bounded, with both start and end. + def cover?(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 covers 1..9 but it does not cover 1..10. + # 1..10 covers 1...11 but it does not cover 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 0000000000..024e32db40 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveSupport + module RangeWithFormat + RANGE_FORMATS = { + db: -> (start, stop) do + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" + end + end + } + + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. + # + # range = (1..100) # => 1..100 + # + # range.to_s # => "1..100" + # range.to_s(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_s + # 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_s(:db)} and #{stop.to_s(:db)}" } + def to_s(format = :default) + if formatter = RANGE_FORMATS[format] + formatter.call(first, last) + else + super() + end + end + + alias_method :to_default_s, :to_s + alias_method :to_formatted_s, :to_s + end +end + +Range.prepend(ActiveSupport::RangeWithFormat) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/each.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/each.rb new file mode 100644 index 0000000000..2d86997edf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/include_time_with_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 0000000000..89e911b11d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" +require "active_support/deprecation" + +module ActiveSupport + module IncludeTimeWithZone #:nodoc: + # Extends the default Range#include? to support ActiveSupport::TimeWithZone. + # + # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true + # + def include?(value) + if self.begin.is_a?(TimeWithZone) || self.end.is_a?(TimeWithZone) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `Range#include?` to check the inclusion of a value in + a date time range is deprecated. + It is recommended to use `Range#cover?` instead of `Range#include?` to + check the inclusion of a value in a date time range. + MSG + cover?(value) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::IncludeTimeWithZone) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/overlaps.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/overlaps.rb new file mode 100644 index 0000000000..f753607f8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/range/overlaps.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Range + # Compare two ranges and see if they overlap each other + # (1..5).overlaps?(4..6) # => true + # (1..5).overlaps?(7..9) # => false + def overlaps?(other) + cover?(other.first) || other.cover?(first) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/regexp.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/regexp.rb new file mode 100644 index 0000000000..15534ff52f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/securerandom.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..ef812f7e1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,45 @@ +# 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" + 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 + + # 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" + 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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string.rb new file mode 100644 index 0000000000..757d15c51a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/access.rb new file mode 100644 index 0000000000..f6a14c08bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/behavior.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/behavior.rb new file mode 100644 index 0000000000..35a5aa7840 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/conversions.rb new file mode 100644 index 0000000000..e84a3909a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 & used_keys).empty? + + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/exclude.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/exclude.rb new file mode 100644 index 0000000000..8e462689f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/filters.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/filters.rb new file mode 100644 index 0000000000..7f28bd52f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/filters.rb @@ -0,0 +1,145 @@ +# 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+ after a given length if +text+ is longer than length: + # + # '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 "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at + + omission = options[:omission] || "..." + length_with_room_for_omission = truncate_at - 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 bytesize 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 bytesize. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + scan(/\X/) 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/indent.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/indent.rb new file mode 100644 index 0000000000..af9d181487 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 fallback 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/inflections.rb new file mode 100644 index 0000000000..9bf465bda3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/inflections.rb @@ -0,0 +1,293 @@ +# 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" + # + # +camelize+ is also aliased as +camelcase+. + # + # 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" + # + # +titleize+ is also aliased as +titlecase+. + # + # 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 just 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 + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/inquiry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/inquiry.rb new file mode 100644 index 0000000000..d78ad9b741 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/multibyte.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/multibyte.rb new file mode 100644 index 0000000000..0542121e39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/output_safety.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 0000000000..b01cd784db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,315 @@ +# frozen_string_literal: true + +require "erb" +require "active_support/core_ext/module/redefine_method" +require "active_support/multibyte/unicode" + +class ERB + module Util + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + + # 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) + unwrapped_html_escape(s).html_safe + end + + silence_redefinition_of_method :h + alias h html_escape + + module_function :h + + singleton_class.silence_redefinition_of_method :html_escape + module_function :html_escape + + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def unwrapped_html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) + end + end + module_function :unwrapped_html_escape + + # 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) + result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) + s.html_safe? ? result.html_safe : result + 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.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) + s.html_safe? ? result.html_safe : result + end + + module_function :json_escape + end +end + +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 slice 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 + + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set :@html_safe, true + new_safe_buffer + else + to_str[*args] + end + 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 clone_empty + self[0, 0] + end + + def concat(value) + super(html_escape_interpolated_argument(value)) + end + alias << concat + + def insert(index, value) + super(index, html_escape_interpolated_argument(value)) + end + + def prepend(value) + super(html_escape_interpolated_argument(value)) + end + + def replace(value) + super(html_escape_interpolated_argument(value)) + end + + def []=(*args) + if args.length == 3 + super(args[0], args[1], html_escape_interpolated_argument(args[2])) + else + super(args[0], html_escape_interpolated_argument(args[1])) + 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| html_escape_interpolated_argument(arg) } + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } + end + + self.class.new(super(escaped_args)) + end + + def html_safe? + defined?(@html_safe) && @html_safe + end + + 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| + if unsafe_method.respond_to?(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 + end + + private + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + 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 + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/starts_ends_with.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 0000000000..1e216370e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/strip.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/strip.rb new file mode 100644 index 0000000000..60e9952ee6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/string/zones.rb new file mode 100644 index 0000000000..55dc231464 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/symbol.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/symbol.rb new file mode 100644 index 0000000000..709fed2024 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/symbol/starts_ends_with.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/symbol/starts_ends_with.rb new file mode 100644 index 0000000000..655a539403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/symbol/starts_ends_with.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Symbol + def start_with?(*prefixes) + to_s.start_with?(*prefixes) + end unless method_defined?(:start_with?) + + def end_with?(*suffixes) + to_s.end_with?(*suffixes) + end unless method_defined?(:end_with?) + + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000000..c809def05f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/acts_like.rb new file mode 100644 index 0000000000..8572b49639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/calculations.rb new file mode 100644 index 0000000000..d9a130d379 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/calculations.rb @@ -0,0 +1,363 @@ +# 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(*args) + return at_without_coercion(*args) if args.size != 1 + + # Time.at can be called with a time or numerical value + time_or_number = args.first + + 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 + end + ruby2_keywords(:at_with_coercion) if respond_to?(:ruby2_keywords, true) + 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 + + unless Time.method_defined?(:floor) + def floor(precision = 0) + change(nsec: 0) + subsec.floor(precision) + end + end + + # Restricted Ruby version due to a bug in `Time#ceil` + # See https://bugs.ruby-lang.org/issues/17025 for more details + if RUBY_VERSION <= "2.8" + remove_possible_method :ceil + def ceil(precision = 0) + change(nsec: 0) + subsec.ceil(precision) + end + 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 + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec) + 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 + 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 + to_datetime.since(seconds) + 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 + + # 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) + compare_without_coercion(other.to_time) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 0000000000..495e4f307b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,16 @@ +# 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 0000000000..d61a191e33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,74 @@ +# 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}") + }, + iso8601: lambda { |time| time.iso8601 } + } + + # Converts to a formatted string. See DATE_FORMATS for built-in formats. + # + # This method is aliased to to_s. + # + # time = Time.now # => 2007-01-18 06:10:17 -06:00 + # + # time.to_formatted_s(:time) # => "06:10" + # time.to_s(:time) # => "06:10" + # + # time.to_formatted_s(:db) # => "2007-01-18 06:10:17" + # time.to_formatted_s(:number) # => "20070118061017" + # time.to_formatted_s(:short) # => "18 Jan 06:10" + # time.to_formatted_s(:long) # => "January 18, 2007 06:10" + # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" + # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" + # + # == Adding your own time formats to +to_formatted_s+ + # 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_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/zones.rb new file mode 100644 index 0000000000..a5588fd488 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/time/zones.rb @@ -0,0 +1,113 @@ +# 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 + Thread.current[: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) + Thread.current[: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) + if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + # Look up the timezone based on the identifier (unless we've been + # passed a TZInfo::Timezone) + unless time_zone.respond_to?(:period_for_local) + time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + end + + # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone + if time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) + end + end + rescue TZInfo::InvalidTimezoneIdentifier + 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.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/uri.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/uri.rb new file mode 100644 index 0000000000..1cb36f9f2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/core_ext/uri.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "uri" + +if RUBY_VERSION < "2.6.0" + require "active_support/core_ext/module/redefine_method" + URI::Parser.class_eval do + silence_redefinition_of_method :unescape + def unescape(str, escaped = /%[a-fA-F\d]{2}/) + # TODO: Are we actually sure that ASCII == UTF-8? + # YK: My initial experiments say yes, but let's be sure please + enc = str.encoding + enc = Encoding::UTF_8 if enc == Encoding::US_ASCII + str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) + end + end +end + +module URI + class << self + def parser + ActiveSupport::Deprecation.warn(<<-MSG.squish) + URI.parser is deprecated and will be removed in Rails 6.2. + Use `URI::DEFAULT_PARSER` instead. + MSG + URI::DEFAULT_PARSER + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/current_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/current_attributes.rb new file mode 100644 index 0000000000..9be8f881c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/current_attributes.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # 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 + + 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. + def attribute(*names) + generated_attribute_methods.module_eval do + names.each do |name| + define_method(name) do + attributes[name.to_sym] + end + + define_method("#{name}=") do |attribute| + attributes[name.to_sym] = attribute + end + end + end + + names.each do |name| + define_singleton_method(name) do + instance.public_send(name) + end + + define_singleton_method("#{name}=") do |attribute| + instance.public_send("#{name}=", attribute) + end + end + end + + # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values. + def before_reset(&block) + set_callback :reset, :before, &block + end + + # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. + def resets(&block) + set_callback :reset, :after, &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 + Thread.current[:current_attributes_instances] ||= {} + end + + def current_instances_key + @current_instances_key ||= name.to_sym + end + + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + end + + attr_accessor :attributes + + def initialize + @attributes = {} + 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(set_attributes) + old_attributes = compute_attributes(set_attributes.keys) + assign_attributes(set_attributes) + yield + ensure + assign_attributes(old_attributes) + 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 = {} + end + end + + private + def assign_attributes(new_attributes) + new_attributes.each { |key, value| public_send("#{key}=", value) } + end + + def compute_attributes(keys) + keys.index_with { |key| public_send(key) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/current_attributes/test_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/current_attributes/test_helper.rb new file mode 100644 index 0000000000..2016384a80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies.rb new file mode 100644 index 0000000000..6ab9d1ec3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies.rb @@ -0,0 +1,828 @@ +# frozen_string_literal: true + +require "set" +require "thread" +require "concurrent/map" +require "pathname" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/load_error" +require "active_support/core_ext/name_error" +require "active_support/dependencies/interlock" +require "active_support/inflector" + +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + extend self + + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + + mattr_accessor :interlock, default: Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads. + def self.run_interlock + Dependencies.interlock.running { yield } + 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 + Dependencies.interlock.loading { yield } + 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 + Dependencies.interlock.unloading { yield } + end + + # :nodoc: + + # Should we turn on Ruby warnings on the first load of dependent files? + mattr_accessor :warnings_on_first_load, default: false + + # All files ever loaded. + mattr_accessor :history, default: Set.new + + # All files currently loaded. + mattr_accessor :loaded, default: Set.new + + # Stack of files being loaded. + mattr_accessor :loading, default: [] + + # Should we load files or require them? + mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load + + # The set of directories from which we may automatically load files. Files + # under these directories will be reloaded on each request in development mode, + # unless the directory also appears in autoload_once_paths. + mattr_accessor :autoload_paths, default: [] + + # The set of directories from which automatically loaded constants are loaded + # only once. All directories in this set must also be present in +autoload_paths+. + mattr_accessor :autoload_once_paths, default: [] + + # This is a private set that collects all eager load paths during bootstrap. + # Useful for Zeitwerk integration. Its public interface is the config.* path + # accessors of each engine. + mattr_accessor :_eager_load_paths, default: Set.new + + # An array of qualified constant names that have been loaded. Adding a name + # to this array will cause it to be unloaded the next time Dependencies are + # cleared. + mattr_accessor :autoloaded_constants, default: [] + + # An array of constant names that need to be unloaded on every request. Used + # to allow arbitrary constants to be marked for unloading. + mattr_accessor :explicitly_unloadable_constants, default: [] + + # The logger used when tracing autoloads. + mattr_accessor :logger + + # If true, trace autoloads with +logger.debug+. + mattr_accessor :verbose, default: false + + # The WatchStack keeps a stack of the modules being watched as files are + # loaded. If a file in the process of being loaded (parent.rb) triggers the + # load of another file (child.rb) the stack will ensure that child.rb + # handles the new constants. + # + # If child.rb is being autoloaded, its constants will be added to + # autoloaded_constants. If it was being required, they will be discarded. + # + # This is handled by walking back up the watch stack and adding the constants + # found by child.rb to the list of original constants in parent.rb. + class WatchStack + include Enumerable + + # @watching is a stack of lists of constants being watched. For instance, + # if parent.rb is autoloaded, the stack will look like [[Object]]. If + # parent.rb then requires namespace/child.rb, the stack will look like + # [[Object], [Namespace]]. + + attr_reader :watching + + def initialize + @watching = [] + @stack = Hash.new { |h, k| h[k] = [] } + end + + def each(&block) + @stack.each(&block) + end + + def watching? + !@watching.empty? + end + + # Returns a list of new constants found since the last call to + # watch_namespaces. + def new_constants + constants = [] + + # Grab the list of namespaces that we're looking for new constants under + @watching.last.each do |namespace| + # Retrieve the constants that were present under the namespace when watch_namespaces + # was originally called + original_constants = @stack[namespace].last + + mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) + next unless mod.is_a?(Module) + + # Get a list of the constants that were added + new_constants = mod.constants(false) - original_constants + + # @stack[namespace] returns an Array of the constants that are being evaluated + # for that namespace. For instance, if parent.rb requires child.rb, the first + # element of @stack[Object] will be an Array of the constants that were present + # before parent.rb was required. The second element will be an Array of the + # constants that were present before child.rb was required. + @stack[namespace].each do |namespace_constants| + namespace_constants.concat(new_constants) + end + + # Normalize the list of new constants, and add them to the list we will return + new_constants.each do |suffix| + constants << ([namespace, suffix] - ["Object"]).join("::") + end + end + constants + ensure + # A call to new_constants is always called after a call to watch_namespaces + pop_modules(@watching.pop) + end + + # Add a set of modules to the watch stack, remembering the initial + # constants. + def watch_namespaces(namespaces) + @watching << namespaces.map do |namespace| + module_name = Dependencies.to_constant_name(namespace) + original_constants = Dependencies.qualified_const_defined?(module_name) ? + Inflector.constantize(module_name).constants(false) : [] + + @stack[module_name] << original_constants + module_name + end + end + + private + def pop_modules(modules) + modules.each { |mod| @stack[mod].pop } + end + end + + # An internal stack used to record which constants are loaded by any block. + mattr_accessor :constant_watch_stack, default: WatchStack.new + + # Module includes this module. + module ModuleConstMissing #:nodoc: + def self.append_features(base) + base.class_eval do + # Emulate #exclude via an ivar + return if defined?(@_const_missing) && @_const_missing + @_const_missing = instance_method(:const_missing) + remove_method(:const_missing) + end + super + end + + def self.exclude_from(base) + base.class_eval do + define_method :const_missing, @_const_missing + @_const_missing = nil + end + end + + def self.include_into(base) + base.include(self) + append_features(base) + end + + def const_missing(const_name) + from_mod = anonymous? ? guess_for_anonymous(const_name) : self + Dependencies.load_missing_constant(from_mod, const_name) + end + + # We assume that the name of the module reflects the nesting + # (unless it can be proven that is not the case) and the path to the file + # that defines the constant. Anonymous modules cannot follow these + # conventions and therefore we assume that the user wants to refer to a + # top-level constant. + def guess_for_anonymous(const_name) + if Object.const_defined?(const_name) + raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name + else + Object + end + end + + def unloadable(const_desc = self) + super(const_desc) + end + end + + # Object includes this module. + module Loadable #:nodoc: + def self.exclude_from(base) + base.class_eval do + define_method(:load, Kernel.instance_method(:load)) + private :load + + define_method(:require, Kernel.instance_method(:require)) + private :require + end + end + + def self.include_into(base) + base.include(self) + + if base.instance_method(:load).owner == base + base.remove_method(:load) + end + + if base.instance_method(:require).owner == base + base.remove_method(:require) + end + end + + def require_or_load(file_name) + Dependencies.require_or_load(file_name) + end + + # :doc: + + # Warning: This method is obsolete in +:zeitwerk+ mode. In + # +:zeitwerk+ mode semantics match Ruby's and you do not need to be + # defensive with load order. Just refer to classes and modules normally. + # If the constant name is dynamic, camelize if needed, and constantize. + # + # In +:classic+ mode, interprets a file using +mechanism+ and marks its + # defined constants as autoloaded. +file_name+ can be either a string or + # respond to to_path. + # + # In +:classic+ mode, use this method in code that absolutely needs a + # certain constant to be defined at that point. A typical use case is to + # make constant name resolution deterministic for constants with the same + # relative name in different namespaces whose evaluation would depend on + # load order otherwise. + # + # 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(file_name, message = "No such file to load -- %s.rb") + file_name = file_name.to_path if file_name.respond_to?(:to_path) + unless file_name.is_a?(String) + raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" + end + + Dependencies.depend_on(file_name, message) + end + + # :nodoc: + + def load_dependency(file) + if Dependencies.load? && Dependencies.constant_watch_stack.watching? + descs = Dependencies.constant_watch_stack.watching.flatten.uniq + + Dependencies.new_constants_in(*descs) { yield } + else + yield + end + rescue Exception => exception # errors from loading file + exception.blame_file! file if exception.respond_to? :blame_file! + raise + end + + # Mark the given constant as unloadable. Unloadable constants are removed + # each time dependencies are cleared. + # + # Note that marking a constant for unloading need only be done once. Setup + # or init scripts may list each unloadable constant that may need unloading; + # each constant will be removed for every subsequent clear, as opposed to + # for the first clear. + # + # The provided constant descriptor may be a (non-anonymous) module or class, + # or a qualified constant name as a string or symbol. + # + # Returns +true+ if the constant was not previously marked for unloading, + # +false+ otherwise. + def unloadable(const_desc) + Dependencies.mark_for_unload const_desc + end + + private + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result + end + + def require(file) + result = false + load_dependency(file) { result = super } + result + end + end + + # Exception file-blaming. + module Blamable #:nodoc: + def blame_file!(file) + (@blamed_files ||= []).unshift file + end + + def blamed_files + @blamed_files ||= [] + end + + def describe_blame + return nil if blamed_files.empty? + "This error occurred while loading the following files:\n #{blamed_files.join "\n "}" + end + + def copy_blame!(exc) + @blamed_files = exc.blamed_files.clone + self + end + end + + def hook! + Loadable.include_into(Object) + ModuleConstMissing.include_into(Module) + Exception.include(Blamable) + end + + def unhook! + ModuleConstMissing.exclude_from(Module) + Loadable.exclude_from(Object) + end + + def load? + mechanism == :load + end + + def depend_on(file_name, message = "No such file to load -- %s.rb") + path = search_for_file(file_name) + require_or_load(path || file_name) + rescue LoadError => load_error + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + load_error_message = if load_error.respond_to?(:original_message) + load_error.original_message + else + load_error.message + end + load_error_message.replace(message % file_name) + load_error.copy_blame!(load_error) + end + raise + end + + def clear + Dependencies.unload_interlock do + loaded.clear + loading.clear + remove_unloadable_constants! + end + end + + def require_or_load(file_name, const_path = nil) + file_name = file_name.chomp(".rb") + expanded = File.expand_path(file_name) + return if loaded.include?(expanded) + + Dependencies.load_interlock do + # Maybe it got loaded while we were waiting for our lock: + return if loaded.include?(expanded) + + # Record that we've seen this file *before* loading it to avoid an + # infinite loop with mutual dependencies. + loaded << expanded + loading << expanded + + begin + if load? + # Enable warnings if this file has not been loaded before and + # warnings_on_first_load is set. + load_args = ["#{file_name}.rb"] + load_args << const_path unless const_path.nil? + + if !warnings_on_first_load || history.include?(expanded) + result = load_file(*load_args) + else + enable_warnings { result = load_file(*load_args) } + end + else + result = require file_name + end + rescue Exception + loaded.delete expanded + raise + ensure + loading.pop + end + + # Record history *after* loading so first load gets warnings. + history << expanded + result + end + end + + # Is the provided constant path defined? + def qualified_const_defined?(path) + Object.const_defined?(path, false) + end + + # Given +path+, a filesystem path to a ruby file, return an array of + # constant paths which would cause Dependencies to attempt to load this + # file. + def loadable_constants_for_path(path, bases = autoload_paths) + path = path.chomp(".rb") + expanded_path = File.expand_path(path) + paths = [] + + bases.each do |root| + expanded_root = File.expand_path(root) + next unless expanded_path.start_with?(expanded_root) + + root_size = expanded_root.size + next if expanded_path[root_size] != ?/ + + nesting = expanded_path[(root_size + 1)..-1] + paths << nesting.camelize unless nesting.blank? + end + + paths.uniq! + paths + end + + # Search for a file in autoload_paths matching the provided suffix. + def search_for_file(path_suffix) + path_suffix += ".rb" unless path_suffix.end_with?(".rb") + + autoload_paths.each do |root| + path = File.join(root, path_suffix) + return path if File.file? path + end + nil # Gee, I sure wish we had first_match ;-) + end + + # Does the provided path_suffix correspond to an autoloadable module? + # Instead of returning a boolean, the autoload base for this module is + # returned. + def autoloadable_module?(path_suffix) + autoload_paths.each do |load_path| + return load_path if File.directory? File.join(load_path, path_suffix) + end + nil + end + + def load_once_path?(path) + # to_s works around a ruby issue where String#start_with?(Pathname) + # will raise a TypeError: no implicit conversion of Pathname into String + autoload_once_paths.any? { |base| path.start_with?(base.to_s) } + end + + # Attempt to autoload the provided module name by searching for a directory + # matching the expected path suffix. If found, the module is created and + # assigned to +into+'s constants with the name +const_name+. Provided that + # the directory was loaded from a reloadable base path, it is added to the + # set of constants that are to be unloaded. + def autoload_module!(into, const_name, qualified_name, path_suffix) + return nil unless base_path = autoloadable_module?(path_suffix) + mod = Module.new + into.const_set const_name, mod + log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})") + autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) + autoloaded_constants.uniq! + mod + end + + # Load the file at the provided path. +const_paths+ is a set of qualified + # constant names. When loading the file, Dependencies will watch for the + # addition of these constants. Each that is defined will be marked as + # autoloaded, and will be removed when Dependencies.clear is next called. + # + # If the second parameter is left off, then Dependencies will construct a + # set of names that the file at +path+ may define. See + # +loadable_constants_for_path+ for more details. + def load_file(path, const_paths = loadable_constants_for_path(path)) + const_paths = [const_paths].compact unless const_paths.is_a? Array + parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } + + result = nil + newly_defined_paths = new_constants_in(*parent_paths) do + result = Kernel.load path + end + + autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) + autoloaded_constants.uniq! + result + end + + # Returns the constant path for the provided parent and constant name. + def qualified_name_for(mod, name) + mod_name = to_constant_name mod + mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}" + end + + # Load the constant named +const_name+ which is missing from +from_mod+. If + # it is not possible to load the constant into from_mod, try its parent + # module using +const_missing+. + def load_missing_constant(from_mod, const_name) + from_mod_name = real_mod_name(from_mod) + unless qualified_const_defined?(from_mod_name) && Inflector.constantize(from_mod_name).equal?(from_mod) + raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" + end + + qualified_name = qualified_name_for(from_mod, const_name) + path_suffix = qualified_name.underscore + + file_path = search_for_file(path_suffix) + + if file_path + expanded = File.expand_path(file_path) + expanded.delete_suffix!(".rb") + + if loading.include?(expanded) + raise "Circular dependency detected while autoloading constant #{qualified_name}" + else + require_or_load(expanded, qualified_name) + + if from_mod.const_defined?(const_name, false) + log("constant #{qualified_name} autoloaded from #{expanded}.rb") + return from_mod.const_get(const_name) + else + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" + end + end + elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) + return mod + elsif (parent = from_mod.module_parent) && parent != from_mod && + ! from_mod.module_parents.any? { |p| p.const_defined?(const_name, false) } + # If our parents do not have a constant named +const_name+ then we are free + # to attempt to load upwards. If they do have such a constant, then this + # const_missing must be due to from_mod::const_name, which should not + # return constants from from_mod's parents. + begin + # Since Ruby does not pass the nesting at the point the unknown + # constant triggered the callback we cannot fully emulate constant + # name lookup and need to make a trade-off: we are going to assume + # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even + # though it might not be. Counterexamples are + # + # class Foo::Bar + # Module.nesting # => [Foo::Bar] + # end + # + # or + # + # module M::N + # module S::T + # Module.nesting # => [S::T, M::N] + # end + # end + # + # for example. + return parent.const_missing(const_name) + rescue NameError => e + raise unless e.missing_name? qualified_name_for(parent, const_name) + end + end + + name_error = uninitialized_constant(qualified_name, const_name, receiver: from_mod) + name_error.set_backtrace(caller.reject { |l| l.start_with? __FILE__ }) + raise name_error + end + + # Remove the constants that have been autoloaded, and those that have been + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the environment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. + def remove_unloadable_constants! + log("removing unloadable constants") + autoloaded_constants.each { |const| remove_constant const } + autoloaded_constants.clear + Reference.clear! + explicitly_unloadable_constants.each { |const| remove_constant const } + end + + class ClassCache + def initialize + @store = Concurrent::Map.new + end + + def empty? + @store.empty? + end + + def key?(key) + @store.key?(key) + end + + def get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.constantize(key) + end + alias :[] :get + + def safe_get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.safe_constantize(key) + end + + def store(klass) + return self unless klass.respond_to?(:name) + raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty? + @store[klass.name] = klass + self + end + + def clear! + @store.clear + end + end + + Reference = ClassCache.new + + # Store a reference to a class +klass+. + def reference(klass) + Reference.store klass + end + + # Get the reference for class named +name+. + # Raises an exception if referenced class does not exist. + def constantize(name) + Reference.get(name) + end + + # Get the reference for class named +name+ if one exists. + # Otherwise returns +nil+. + def safe_constantize(name) + Reference.safe_get(name) + end + + # Determine if the given constant has been automatically loaded. + def autoloaded?(desc) + return false if desc.is_a?(Module) && real_mod_name(desc).nil? + name = to_constant_name desc + return false unless qualified_const_defined?(name) + autoloaded_constants.include?(name) + end + + # Will the provided constant descriptor be unloaded? + def will_unload?(const_desc) + autoloaded?(const_desc) || + explicitly_unloadable_constants.include?(to_constant_name(const_desc)) + end + + # Mark the provided constant name for unloading. This constant will be + # unloaded on each request, not just the next one. + def mark_for_unload(const_desc) + name = to_constant_name const_desc + if explicitly_unloadable_constants.include? name + false + else + explicitly_unloadable_constants << name + true + end + end + + # Run the provided block and detect the new constants that were loaded during + # its execution. Constants may only be regarded as 'new' once -- so if the + # block calls +new_constants_in+ again, then the constants defined within the + # inner call will not be reported in this one. + # + # If the provided block does not run to completion, and instead raises an + # exception, any new constants are regarded as being only partially defined + # and will be removed immediately. + def new_constants_in(*descs) + constant_watch_stack.watch_namespaces(descs) + success = false + + begin + yield # Now yield to the code that is to define new constants. + success = true + ensure + new_constants = constant_watch_stack.new_constants + + return new_constants if success + + # Remove partially loaded constants. + new_constants.each { |c| remove_constant(c) } + end + end + + # Convert the provided const desc to a qualified constant name (as a string). + # A module, class, symbol, or string may be provided. + def to_constant_name(desc) #:nodoc: + case desc + when String then desc.delete_prefix("::") + when Symbol then desc.to_s + when Module + real_mod_name(desc) || + raise(ArgumentError, "Anonymous modules have no name to be referenced by") + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" + end + end + + def remove_constant(const) #:nodoc: + # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. + normalized = const.to_s.delete_prefix("::") + normalized.sub!(/\A(Object::)+/, "") + + constants = normalized.split("::") + to_remove = constants.pop + + # Remove the file path from the loaded list. + file_path = search_for_file(const.underscore) + if file_path + expanded = File.expand_path(file_path) + expanded.delete_suffix!(".rb") + loaded.delete(expanded) + end + + if constants.empty? + parent = Object + else + # This method is robust to non-reachable constants. + # + # Non-reachable constants may be passed if some of the parents were + # autoloaded and already removed. It is easier to do a sanity check + # here than require the caller to be clever. We check the parent + # rather than the very const argument because we do not want to + # trigger Kernel#autoloads, see the comment below. + parent_name = constants.join("::") + return unless qualified_const_defined?(parent_name) + parent = constantize(parent_name) + end + + # In an autoloaded user.rb like this + # + # autoload :Foo, 'foo' + # + # class User < ActiveRecord::Base + # end + # + # we correctly register "Foo" as being autoloaded. But if the app does + # not use the "Foo" constant we need to be careful not to trigger + # loading "foo.rb" ourselves. While #const_defined? and #const_get? do + # require the file, #autoload? and #remove_const don't. + # + # We are going to remove the constant nonetheless ---which exists as + # far as Ruby is concerned--- because if the user removes the macro + # call from a class or module that were not autoloaded, as in the + # example above with Object, accessing to that constant must err. + unless parent.autoload?(to_remove) + begin + constantized = parent.const_get(to_remove, false) + rescue NameError + # The constant is no longer reachable, just skip it. + return + else + constantized.before_remove_const if constantized.respond_to?(:before_remove_const) + end + end + + begin + parent.instance_eval { remove_const to_remove } + rescue NameError + # The constant is no longer reachable, just skip it. + end + end + + def log(message) + logger.debug("autoloading: #{message}") if logger && verbose + end + + private + if RUBY_VERSION < "2.6" + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name) + end + else + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name, receiver: receiver) + end + end + + # Returns the original name of a class or module even if `name` has been + # overridden. + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end + end +end + +ActiveSupport::Dependencies.hook! diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/autoload.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/autoload.rb new file mode 100644 index 0000000000..1cee85d98f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" + +module ActiveSupport + # 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 self.extended(base) # :nodoc: + base.class_eval do + @_autoloads = {} + @_under_path = nil + @_at_path = nil + @_eager_autoload = false + end + end + + 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 + @_autoloads[const_name] = path + 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! + @_autoloads.each_value { |file| require file } + end + + def autoloads + @_autoloads + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/interlock.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/interlock.rb new file mode 100644 index 0000000000..948be75638 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,57 @@ +# 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 + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do + yield + end + end + + def unloading + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do + yield + end + 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 + @lock.sharing do + yield + end + end + + def permit_concurrent_loads + @lock.yield_shares(compatible: [:load]) do + yield + end + end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/zeitwerk_integration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/zeitwerk_integration.rb new file mode 100644 index 0000000000..2155fba0a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/dependencies/zeitwerk_integration.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "set" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module Dependencies + module ZeitwerkIntegration # :nodoc: all + module Decorations + def clear + Dependencies.unload_interlock do + Rails.autoloaders.main.reload + rescue Zeitwerk::ReloadingDisabledError + raise "reloading is disabled because config.cache_classes is true" + end + end + + def constantize(cpath) + ActiveSupport::Inflector.constantize(cpath) + end + + def safe_constantize(cpath) + ActiveSupport::Inflector.safe_constantize(cpath) + end + + def autoloaded_constants + Rails.autoloaders.main.unloadable_cpaths + end + + def autoloaded?(object) + cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s + Rails.autoloaders.main.unloadable_cpath?(cpath) + end + + def verbose=(verbose) + l = verbose ? logger || Rails.logger : nil + Rails.autoloaders.each { |autoloader| autoloader.logger = l } + end + + def unhook! + :no_op + end + end + + module RequireDependency + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + end + + module Inflector + # Concurrent::Map is not needed. This is a private class, and overrides + # must be defined while the application boots. + @overrides = {} + + def self.camelize(basename, _abspath) + @overrides[basename] || basename.camelize + end + + def self.inflect(overrides) + @overrides.merge!(overrides) + end + end + + class << self + def take_over(enable_reloading:) + setup_autoloaders(enable_reloading) + freeze_paths + decorate_dependencies + end + + private + def setup_autoloaders(enable_reloading) + Dependencies.autoload_paths.each do |autoload_path| + # Zeitwerk only accepts existing directories in `push_dir` to + # prevent misconfigurations. + next unless File.directory?(autoload_path) + + autoloader = \ + autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main + + autoloader.push_dir(autoload_path) + autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path) + end + + Rails.autoloaders.main.enable_reloading if enable_reloading + Rails.autoloaders.each(&:setup) + end + + def autoload_once?(autoload_path) + Dependencies.autoload_once_paths.include?(autoload_path) + end + + def eager_load?(autoload_path) + Dependencies._eager_load_paths.member?(autoload_path) + end + + def freeze_paths + Dependencies.autoload_paths.freeze + Dependencies.autoload_once_paths.freeze + Dependencies._eager_load_paths.freeze + end + + def decorate_dependencies + Dependencies.unhook! + Dependencies.singleton_class.prepend(Decorations) + Object.prepend(RequireDependency) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation.rb new file mode 100644 index 0000000000..006e404cab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "singleton" + +module ActiveSupport + # \Deprecation specifies the API used by Rails to deprecate methods, instance + # variables, objects and constants. + 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/instance_delegator" + 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/core_ext/module/deprecation" + require "concurrent/atomic/thread_local_var" + + include Singleton + include InstanceDelegator + include Behavior + include Reporting + include Disallowed + include MethodWrapper + + # 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 = "6.2", 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 + @silenced_thread = Concurrent::ThreadLocalVar.new(false) + @explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/behaviors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/behaviors.rb new file mode 100644 index 0000000000..9d1fc78360 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/behaviors.rb @@ -0,0 +1,122 @@ +# 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, deprecation_horizon, gem_name) { + e = DeprecationException.new(message) + e.set_backtrace(callstack.map(&:to_s)) + raise e + }, + + stderr: ->(message, callstack, deprecation_horizon, gem_name) { + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + + log: ->(message, callstack, deprecation_horizon, gem_name) { + 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 debug + }, + + notify: ->(message, callstack, deprecation_horizon, gem_name) { + notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}" + ActiveSupport::Notifications.instrument(notification_name, + message: message, + callstack: callstack, + gem_name: gem_name, + deprecation_horizon: deprecation_horizon) + }, + + silence: ->(message, callstack, deprecation_horizon, gem_name) { }, + } + + # 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+. + # [+silence+] Do nothing. + # + # 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+. + # [+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. + # + # ActiveSupport::Deprecation.behavior = :stderr + # ActiveSupport::Deprecation.behavior = [:stderr, :log] + # ActiveSupport::Deprecation.behavior = MyCustomHandler + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { + # # custom stuff + # } + 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 + + if behavior.arity == 4 || behavior.arity == -1 + behavior + else + -> message, callstack, _, _ { behavior.call(message, callstack) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/constant_accessor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 0000000000..1ed0015812 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + # DeprecatedConstantAccessor transforms a constant into a deprecated one by + # hooking +const_missing+. + # + # It takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. + # + # The deprecated constant now returns the same object as the new one rather + # than a proxy object, so it can be used transparently in +rescue+ blocks + # etc. + # + # 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) + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # deprecate_constant 'PLANETS', 'PLANETS_POST_2006' + # + # 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"] + 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 + + def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/disallowed.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/disallowed.rb new file mode 100644 index 0000000000..096ecaae85 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/disallowed.rb @@ -0,0 +1,56 @@ +# 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 +ActiveSupport::Deprecation.disallowed_behavior+ + # rather than +ActiveSupport::Deprecation.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) + disallowed = ActiveSupport::Deprecation.disallowed_warnings + return false if explicitly_allowed?(message) + return true if disallowed == :all + disallowed.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 + allowances = [allowances] unless allowances.kind_of?(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.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/instance_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/instance_delegator.rb new file mode 100644 index 0000000000..59dd30ae30 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/instance_delegator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class Deprecation + module InstanceDelegator # :nodoc: + def self.included(base) + base.extend(ClassMethods) + base.singleton_class.prepend(OverrideDelegators) + base.public_class_method :new + end + + module ClassMethods # :nodoc: + def include(included_module) + included_module.instance_methods.each { |m| method_added(m) } + super + end + + def method_added(method_name) + singleton_class.delegate(method_name, to: :instance) + end + end + + module OverrideDelegators # :nodoc: + def warn(message = nil, callstack = nil) + callstack ||= caller_locations(2) + super + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/method_wrappers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/method_wrappers.rb new file mode 100644 index 0000000000..e6cf28a89f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/method_wrappers.rb @@ -0,0 +1,85 @@ +# 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 + # + # Using the default deprecator: + # ActiveSupport::Deprecation.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 Rails 5.1. (called from irb_binding at (irb):10) + # # => nil + # + # Fred.new.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) + # # => nil + # + # Fred.new.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + # + # Passing in a custom deprecator: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) + # # => [:ddd] + # + # Fred.new.ddd + # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) + # # => nil + # + # Using a custom deprecator directly: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # custom_deprecator.deprecate_methods(Fred, eee: :zzz) + # # => [:eee] + # + # Fred.new.eee + # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) + # # => 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(self).call(*args, &block) + end + ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true) + 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) if respond_to?(:ruby2_keywords, true) + end + end + end + + target_module.prepend(mod) if mod + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/proxy_wrappers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/proxy_wrappers.rb new file mode 100644 index 0000000000..4bc112d6c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/proxy_wrappers.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + class DeprecationProxy #:nodoc: + def self.new(*args, &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 optionally a deprecator. The + # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified. + # + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated") + # # => # + # + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#" + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) + @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 + # and an instance variable. It optionally takes a deprecator as the last + # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none + # is specified. + # + # class Example + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request) + # @_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 = ActiveSupport::Deprecation.instance) + @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 names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. 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') + # + # 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 = ActiveSupport::Deprecation.instance, 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 + + 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(called, *args, &block) + @deprecator.warn(@message, caller_locations) + target.__send__(called, *args, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/reporting.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/reporting.rb new file mode 100644 index 0000000000..51514eb3a6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/deprecation/reporting.rb @@ -0,0 +1,157 @@ +# 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.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 |m| + if deprecation_disallowed?(message) + disallowed_behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + else + behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + end + end + end + + # Silence deprecation warnings within the block. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # ActiveSupport::Deprecation.silence do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + def silence(&block) + @silenced_thread.bind(true, &block) + 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. + # + # ActiveSupport::Deprecation.disallowed_behavior = :raise + # ActiveSupport::Deprecation.disallowed_warnings = [ + # "something broke" + # ] + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => ActiveSupport::DeprecationException + # + # ActiveSupport::Deprecation.allow ['something broke'] do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + # + # ActiveSupport::Deprecation.allow ['something broke'], if: Rails.env.production? do + # ActiveSupport::Deprecation.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 silenced + @silenced || @silenced_thread.value + 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 _extract_callstack(callstack) if callstack.first.is_a? String + + offending_line = callstack.find { |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + } || callstack.first + + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + + if offending_line + if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) + md.captures + else + offending_line + end + end + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/descendants_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/descendants_tracker.rb new file mode 100644 index 0000000000..2362914dce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/descendants_tracker.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "weakref" + +module ActiveSupport + # This module provides an internal implementation to track descendants + # which is faster than iterating through ObjectSpace. + module DescendantsTracker + @@direct_descendants = {} + + class << self + def direct_descendants(klass) + descendants = @@direct_descendants[klass] + descendants ? descendants.to_a : [] + end + alias_method :subclasses, :direct_descendants + + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr + end + + def clear + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| Dependencies.autoloaded?(v) } + end + end + else + @@direct_descendants.clear + end + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant + end + + private + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + direct_descendants.each do |direct_descendant| + acc << direct_descendant + accumulate_descendants(direct_descendant, acc) + end + end + end + end + + def inherited(base) + DescendantsTracker.store_inherited(self, base) + super + end + + def direct_descendants + DescendantsTracker.direct_descendants(self) + end + alias_method :subclasses, :direct_descendants + + def descendants + DescendantsTracker.descendants(self) + end + + # DescendantsArray is an array that contains weak references to classes. + class DescendantsArray # :nodoc: + include Enumerable + + def initialize + @refs = [] + end + + def initialize_copy(orig) + @refs = @refs.dup + end + + def <<(klass) + @refs << WeakRef.new(klass) + end + + def each + @refs.reject! do |ref| + yield ref.__getobj__ + false + rescue WeakRef::RefError + true + end + self + end + + def refs_size + @refs.size + end + + def cleanup! + @refs.delete_if { |ref| !ref.weakref_alive? } + end + + def reject! + @refs.reject! do |ref| + yield ref.__getobj__ + rescue WeakRef::RefError + true + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/digest.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/digest.rb new file mode 100644 index 0000000000..fba10fbdcf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/digest.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + class Digest #:nodoc: + class <(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) + 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) + 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) + 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 + + 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 + + attr_accessor :value, :parts + + 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) + end + + def minutes(value) #:nodoc: + new(value * SECONDS_PER_MINUTE, minutes: value) + end + + def hours(value) #:nodoc: + new(value * SECONDS_PER_HOUR, hours: value) + end + + def days(value) #:nodoc: + new(value * SECONDS_PER_DAY, days: value) + end + + def weeks(value) #:nodoc: + new(value * SECONDS_PER_WEEK, weeks: value) + end + + def months(value) #:nodoc: + new(value * SECONDS_PER_MONTH, months: value) + end + + def years(value) #:nodoc: + new(value * SECONDS_PER_YEAR, years: value) + 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 = value.round(9) + + PARTS.each do |part| + unless part == :seconds + part_in_seconds = PARTS_IN_SECONDS[part] + parts[part] = remainder.div(part_in_seconds) + remainder %= part_in_seconds + end + end unless value == 0 + + parts[:seconds] = remainder + + new(value, parts) + end + + private + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + + def initialize(value, parts) #:nodoc: + @value, @parts = value, parts + @parts.reject! { |k, v| v.zero? } unless value == 0 + 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) + else + seconds = @parts.fetch(:seconds, 0) + other + Duration.new(value + other, @parts.merge(seconds: seconds)) + 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 }) + elsif Numeric === other + Duration.new(value * other, parts.transform_values { |number| number * other }) + 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 }) + elsif Duration === other + value / other.value + elsif Numeric === other + Duration.new(value / other, parts.transform_values { |number| number / other }) + 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(&:-@)) + 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://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/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: ::I18n.default_locale) + 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 + + 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.inject(time) do |t, (type, number)| + 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 + end + end + + def respond_to_missing?(method, _) + value.respond_to?(method) + end + + def method_missing(method, *args, &block) + value.public_send(method, *args, &block) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/duration/iso8601_parser.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 0000000000..83f3b28602 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 & DATE_COMPONENTS).any? + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && (parts.keys & TIME_COMPONENTS).empty? + 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.0.0/gems/activesupport-6.1.4/lib/active_support/duration/iso8601_serializer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 0000000000..96296274df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +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 << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', 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. + # If all parts are negative it will negate all of them and return minus as a sign. + 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 & DATE_COMPONENTS).any? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_configuration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_configuration.rb new file mode 100644 index 0000000000..cc1d026737 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_configuration.rb @@ -0,0 +1,45 @@ +# 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/module/delegation" + +module ActiveSupport + class EncryptedConfiguration < EncryptedFile + delegate :[], :fetch, to: :config + 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 + end + + # Allow a config to be started without a file present + def read + super + rescue ActiveSupport::EncryptedFile::MissingContentError + "" + end + + def write(contents) + deserialize(contents) + + super + end + + def config + @config ||= deserialize(read).deep_symbolize_keys + end + + private + def options + @options ||= ActiveSupport::InheritableOptions.new(config) + end + + def deserialize(config) + YAML.load(config).presence || {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_file.rb new file mode 100644 index 0000000000..a35cc54ef5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/encrypted_file.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "pathname" +require "tmpdir" +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 + + def key + read_env_key || read_key_file || handle_missing_key + end + + def read + if !key.nil? && content_path.exist? + decrypt content_path.binread + 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) + tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}" + tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file) + tmp_path.binwrite contents + + yield tmp_path + + updated_contents = tmp_path.binread + + write(updated_contents) if updated_contents != contents + ensure + FileUtils.rm(tmp_path) if tmp_path&.exist? + 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) + end + + + def read_env_key + ENV[env_key] + end + + def read_key_file + return @key_file_contents if defined?(@key_file_contents) + @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.0.0/gems/activesupport-6.1.4/lib/active_support/environment_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/environment_inquirer.rb new file mode 100644 index 0000000000..05361d9327 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/environment_inquirer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" + +module ActiveSupport + class EnvironmentInquirer < StringInquirer #:nodoc: + DEFAULT_ENVIRONMENTS = ["development", "test", "production"] + def initialize(env) + super(env) + + DEFAULT_ENVIRONMENTS.each do |default| + instance_variable_set :"@#{default}", env == default + end + end + + DEFAULT_ENVIRONMENTS.each do |env| + class_eval "def #{env}?; @#{env}; end" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/evented_file_update_checker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/evented_file_update_checker.rb new file mode 100644 index 0000000000..f9bc3be9be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/evented_file_update_checker.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require "set" +require "pathname" +require "concurrent/atomic/atomic_boolean" +require "listen" +require "active_support/fork_tracker" + +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. + # + # Note: Forking will cause the first call to `updated?` to return `true`. + # + # 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 updated? + if @core.restart? + @core.thread_safely(&:restart) + @core.updated.make_true + end + + @core.updated.true? + end + + def execute + @core.updated.make_false + @block.call + end + + def execute_if_updated + if updated? + yield if block_given? + execute + true + end + end + + class Core + attr_reader :updated + + def initialize(files, dirs) + @files = files.map { |file| Pathname(file).expand_path }.to_set + + @dirs = dirs.each_with_object({}) do |(dir, exts), hash| + hash[Pathname(dir).expand_path] = Array(exts).map { |ext| ext.to_s.sub(/\A\.?/, ".") }.to_set + end + + @common_path = common_path(@dirs.keys) + + @dtw = directories_to_watch + @missing = [] + + @updated = Concurrent::AtomicBoolean.new(false) + @mutex = Mutex.new + + start + @after_fork = ActiveSupport::ForkTracker.after_fork { start } + end + + def finalizer + proc do + stop + ActiveSupport::ForkTracker.unregister(@after_fork) + end + end + + def thread_safely + @mutex.synchronize do + yield self + end + end + + def start + normalize_dirs! + @dtw, @missing = [*@dtw, *@missing].partition(&:exist?) + @listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil + @listener&.start + end + + def stop + @listener&.stop + end + + def restart + stop + start + end + + def restart? + @missing.any?(&:exist?) + end + + def normalize_dirs! + @dirs.transform_keys! do |dir| + dir.exist? ? dir.realpath : dir + end + end + + def changed(modified, added, removed) + unless @updated.true? + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = Pathname(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = file.extname + + file.dirname.ascend do |dir| + matching = @dirs[dir] + + if matching && (matching.empty? || matching.include?(ext)) + break true + elsif dir == @common_path || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = @dirs.keys | @files.map(&:dirname) + accounted_for = dtw.to_set + Gem.path.map { |path| Pathname(path) } + dtw.reject { |dir| dir.ascend.drop(1).any? { |parent| accounted_for.include?(parent) } } + end + + def common_path(paths) + paths.map { |path| path.ascend.to_a }.reduce(&:&)&.first + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/execution_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/execution_wrapper.rb new file mode 100644 index 0000000000..ca810db584 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/execution_wrapper.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "concurrent/hash" + +module ActiveSupport + class ExecutionWrapper + include ActiveSupport::Callbacks + + Null = Object.new # :nodoc: + def Null.complete! # :nodoc: + end + + define_callbacks :run + define_callbacks :complete + + def self.to_run(*args, &block) + set_callback(:run, *args, &block) + end + + def self.to_complete(*args, &block) + set_callback(:complete, *args, &block) + end + + RunHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + hook_state[hook] = hook.run + end + end + + CompleteHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + if hook_state.key?(hook) + hook.complete hook_state[hook] + end + end + alias after before + end + + # Register an object to be invoked during both the +run+ and + # +complete+ steps. + # + # +hook.complete+ will be passed the value returned from +hook.run+, + # and will only be invoked if +run+ has previously been called. + # (Mostly, this means it won't be invoked if an exception occurs in + # a preceding +to_run+ block; all ordinary +to_complete+ blocks are + # invoked in that situation.) + def self.register_hook(hook, outer: false) + if outer + to_run RunHook.new(hook), prepend: true + to_complete :after, CompleteHook.new(hook) + else + to_run RunHook.new(hook) + to_complete CompleteHook.new(hook) + end + end + + # Run this execution. + # + # Returns an instance, whose +complete!+ method *must* be invoked + # after the work has been performed. + # + # Where possible, prefer +wrap+. + def self.run! + if active? + Null + else + new.tap do |instance| + success = nil + begin + instance.run! + success = true + ensure + instance.complete! unless success + end + end + end + end + + # Perform the work in the supplied block as an execution. + def self.wrap + return yield if active? + + instance = run! + begin + yield + ensure + instance.complete! + end + end + + class << self # :nodoc: + attr_accessor :active + end + + def self.inherited(other) # :nodoc: + super + other.active = Concurrent::Hash.new + end + + self.active = Concurrent::Hash.new + + def self.active? # :nodoc: + @active[Thread.current] + end + + def run! # :nodoc: + self.class.active[Thread.current] = true + 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! + run_callbacks(:complete) + ensure + self.class.active.delete Thread.current + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/executor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/executor.rb new file mode 100644 index 0000000000..ce391b07ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/file_update_checker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/file_update_checker.rb new file mode 100644 index 0000000000..9b665e7f19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/file_update_checker.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActiveSupport + # 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 + 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.0.0/gems/activesupport-6.1.4/lib/active_support/fork_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/fork_tracker.rb new file mode 100644 index 0000000000..2f250378ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/fork_tracker.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module ActiveSupport + module ForkTracker # :nodoc: + module CoreExt + def fork(*) + if block_given? + super do + ForkTracker.check! + yield + end + else + unless pid = super + ForkTracker.check! + end + pid + end + end + ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true) + end + + module CoreExtPrivate + include CoreExt + + private + def fork(*) + super + end + ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true) + end + + @pid = Process.pid + @callbacks = [] + + class << self + def check! + if @pid != Process.pid + @callbacks.each(&:call) + @pid = Process.pid + end + end + + def hook! + if Process.respond_to?(:fork) + ::Object.prepend(CoreExtPrivate) + ::Kernel.prepend(CoreExtPrivate) + ::Kernel.singleton_class.prepend(CoreExt) + ::Process.singleton_class.prepend(CoreExt) + end + 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.0.0/gems/activesupport-6.1.4/lib/active_support/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/gem_version.rb new file mode 100644 index 0000000000..7c6c62bdb8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveSupport + # Returns the version of the currently loaded Active Support as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 4 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/gzip.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/gzip.rb new file mode 100644 index 0000000000..7ffa6d90a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/gzip.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "zlib" +require "stringio" + +module ActiveSupport + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/hash_with_indifferent_access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/hash_with_indifferent_access.rb new file mode 100644 index 0000000000..4e574cd059 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/hash_with_indifferent_access.rb @@ -0,0 +1,423 @@ +# 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 + # 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 = {}) + 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 + 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(*args) + super(*args.map { |arg| convert_key(arg) }) + 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) + super(*keys.map { |key| convert_key(key) }) + 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) + super(*indices.map { |key| convert_key(key) }, &block) + 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) + slice(*self.keys - keys.map { |key| convert_key(key) }) + end + alias_method :without, :except + + def stringify_keys!; self end + def deep_stringify_keys!; self end + def stringify_keys; dup end + def deep_stringify_keys; dup end + 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(*args, &block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(*args, &block) } + end + + def transform_keys(*args, &block) + return to_enum(:transform_keys) unless block_given? + dup.tap { |hash| hash.transform_keys!(*args, &block) } + end + + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(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 + _new_hash = Hash.new + set_defaults(_new_hash) + + each do |key, value| + _new_hash[key] = convert_value(value, conversion: :to_hash) + end + _new_hash + end + + private + if Symbol.method_defined?(:name) + def convert_key(key) + key.kind_of?(Symbol) ? key.name : key + end + else + def convert_key(key) + key.kind_of?(Symbol) ? key.to_s : key + end + end + + def convert_value(value, conversion: nil) + if value.is_a? Hash + if conversion == :to_hash + value.to_hash + else + value.nested_under_indifferent_access + end + 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 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.0.0/gems/activesupport-6.1.4/lib/active_support/i18n.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/i18n.rb new file mode 100644 index 0000000000..39dab1cc71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/i18n.rb @@ -0,0 +1,16 @@ +# 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" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.4/lib/active_support/i18n_railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/i18n_railtie.rb new file mode 100644 index 0000000000..094f65ad6c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/i18n_railtie.rb @@ -0,0 +1,125 @@ +# 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 + + # Set the i18n configuration after initialization since a lot of + # configuration is still usually done in application initializers. + config.after_initialize do |app| + I18n::Railtie.initialize_i18n(app) + end + + # Trigger i18n config before any eager loading has happened + # so it's ready if any classes require it when eager loaded. + config.before_eager_load 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 + forward_raise_on_missing_translations_config(app) + 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 + + directories = watched_dirs_with_extensions(reloadable_paths) + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do + I18n.load_path.keep_if { |p| File.exist?(p) } + 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 + reloader.execute + + @i18n_inited = true + end + + def self.forward_raise_on_missing_translations_config(app) + ActiveSupport.on_load(:action_view) do + self.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + end + + ActiveSupport.on_load(:action_controller) do + AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + 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.0.0/gems/activesupport-6.1.4/lib/active_support/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflections.rb new file mode 100644 index 0000000000..baf1cb3038 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/inflector.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector.rb new file mode 100644 index 0000000000..d77f04c9c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/inflections.rb new file mode 100644 index 0000000000..99228a506f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/inflections.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/i18n" + +module ActiveSupport + module Inflector + extend self + + # 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 'octopus', 'octopi' + # + # inflect.uncountable 'equipment' + # end + # + # New rules are added at the top. So in the example above, the irregular + # rule for octopus 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 + + 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 'octopus', 'octopi' + # 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. + # + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] + else + 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(locale) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/methods.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/methods.rb new file mode 100644 index 0000000000..ad136532bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/methods.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +require "active_support/inflections" +require "active_support/core_ext/object/blank" + +module ActiveSupport + # 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 + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } + else + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase } + end + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!("/", "::") + 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 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\d]+)([A-Z][a-z])/, '\1_\2') + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') + 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 a "_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.sub!(/\A_+/, "") + unless keep_id_suffix + result.delete_suffix!("_id") + end + result.tr!("_", " ") + + result.gsub!(/([a-z\d]*)/i) do |match| + "#{inflections.acronyms[match.downcase] || match.downcase}" + end + + if capitalize + result.sub!(/\A\w/) { |match| match.upcase } + end + + result + end + + # Converts just the first character 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 + + # 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+ is also aliased as +titlecase+. + # + # 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)..-1] + 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) + if camel_cased_word.blank? || !camel_cased_word.include?("::") + Object.const_get(camel_cased_word) + else + names = camel_cased_word.split("::") + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + 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.blank? + + 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.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/transliterate.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/transliterate.rb new file mode 100644 index 0000000000..c398b25d0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,147 @@ +# 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) + string = string.dup if string.frozen? + 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) + + 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.0.0/gems/activesupport-6.1.4/lib/active_support/json.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/json.rb new file mode 100644 index 0000000000..d7887175c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/json/decoding.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/json/decoding.rb new file mode 100644 index 0000000000..e40957ef3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/json/decoding.rb @@ -0,0 +1,75 @@ +# 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 + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/json/encoding.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/json/encoding.rb new file mode 100644 index 0000000000..04de632c8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 + # 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\"}" + def self.encode(value, options = nil) + Encoding.json_encoder.new(options).encode(value) + 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) + stringify jsonify value.as_json(options.dup) + end + + private + # Rails does more escaping than the JSON gem natively does (we + # escape \u2028 and \u2029 and optionally >, <, & to work around + # certain browser problems). + ESCAPED_CHARS = { + "\u2028" => '\u2028', + "\u2029" => '\u2029', + ">" => '\u003e', + "<" => '\u003c', + "&" => '\u0026', + } + + ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u + ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u + + # This class wraps all the strings we see and does the extra escaping + class EscapedString < String #:nodoc: + def to_json(*) + if Encoding.escape_html_entities_in_json + s = super + s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s + else + s = super + s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s + end + end + + def to_s + self + end + end + + # Mark these as private so we don't leak encoding-specific constructs + private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, + :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString + + # Convert an object into a "JSON-ready" representation composed of + # primitives like Hash, Array, String, 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 + EscapedString.new(value) + when Numeric, NilClass, TrueClass, FalseClass + value.as_json + when Hash + result = {} + value.each do |k, v| + result[jsonify(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.0.0/gems/activesupport-6.1.4/lib/active_support/key_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/key_generator.rb new file mode 100644 index 0000000000..21f7ab1bcb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/key_generator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "openssl" + +module ActiveSupport + # 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 + 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 + 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_sha1(@secret, salt, @iterations, key_size) + end + end + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/lazy_load_hooks.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 0000000000..c6f7ccf0a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveSupport + # lazy_load_hooks 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) + 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. + # + # 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 + + 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.0.0/gems/activesupport-6.1.4/lib/active_support/locale/en.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/locale/en.rb new file mode 100644 index 0000000000..29eb9dec0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/locale/en.yml new file mode 100644 index 0000000000..725ec3532c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/locale/en.yml @@ -0,0 +1,139 @@ +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 the number (default: $5.00) + 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" + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/log_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/log_subscriber.rb new file mode 100644 index 0000000000..af90bccebf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/log_subscriber.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" + +module ActiveSupport + # 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 + # def sql(event) + # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # end + # end + # end + # + # And it's finally registered as: + # + # ActiveRecord::LogSubscriber.attach_to :active_record + # + # Since we need to know all instance methods before attaching the log + # subscriber, the line above should be called after your + # ActiveRecord::LogSubscriber definition. + # + # A logger also needs to be set with ActiveRecord::LogSubscriber.logger=. + # This 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 + # + # Log subscriber also has some helpers to deal with logging and automatically + # flushes all logs when the request finishes + # (via action_dispatch.callback notification) in a Rails environment. + class LogSubscriber < Subscriber + # Embed in a String to clear all previous ANSI sequences. + CLEAR = "\e[0m" + BOLD = "\e[1m" + + # 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 << self + def logger + @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) + Rails.logger + end + 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 + end + + def logger + LogSubscriber.logger + end + + def start(name, id, payload) + super if logger + end + + def finish(name, id, payload) + super if logger + rescue => e + if logger + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + end + end + + 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. If a third + # option is set to +true+, it also adds bold to the string. This is based + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. + def color(text, color, bold = false) # :doc: + return text unless colorize_logging + color = self.class.const_get(color.upcase) if color.is_a?(Symbol) + bold = bold ? BOLD : "" + "#{bold}#{color}#{text}#{CLEAR}" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/log_subscriber/test_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/log_subscriber/test_helper.rb new file mode 100644 index 0000000000..3f19ef5009 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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, 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.0.0/gems/activesupport-6.1.4/lib/active_support/logger.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger.rb new file mode 100644 index 0000000000..1e241c13ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger.rb @@ -0,0 +1,93 @@ +# 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 + def self.logger_outputs_to?(logger, *sources) + logdev = logger.instance_variable_get(:@logdev) + logger_source = logdev.dev if logdev.respond_to?(:dev) + sources.any? { |source| source == logger_source } + end + + # Broadcasts logs to multiple loggers. + def self.broadcast(logger) # :nodoc: + Module.new do + define_method(:add) do |*args, &block| + logger.add(*args, &block) + super(*args, &block) + end + + define_method(:<<) do |x| + logger << x + super(x) + end + + define_method(:close) do + logger.close + super() + end + + define_method(:progname=) do |name| + logger.progname = name + super(name) + end + + define_method(:formatter=) do |formatter| + logger.formatter = formatter + super(formatter) + end + + define_method(:level=) do |level| + logger.level = level + super(level) + end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end + + define_method(:silence) do |level = Logger::ERROR, &block| + if logger.respond_to?(:silence) + logger.silence(level) do + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + else + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + end + end + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger_silence.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger_silence.rb new file mode 100644 index 0000000000..8567eff403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/logger_thread_safe_level.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 0000000000..1de9ecdbbc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "concurrent" +require "fiber" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + included do + cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false + end + + Logger::Severity.constants.each do |severity| + class_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{severity.downcase}? # def debug? + Logger::#{severity} >= level # DEBUG >= level + end # end + EOT + end + + def local_log_id + Fiber.current.__id__ + end + + def local_level + self.class.local_levels[local_log_id] + end + + def local_level=(level) + case level + when Integer + self.class.local_levels[local_log_id] = level + when Symbol + self.class.local_levels[local_log_id] = Logger::Severity.const_get(level.to_s.upcase) + when nil + self.class.local_levels.delete(local_log_id) + else + raise ArgumentError, "Invalid log level: #{level.inspect}" + 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 + + # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+. + # FIXME: Remove when the minimum Ruby version supports overriding Logger#level. + def add(severity, message = nil, progname = nil, &block) #:nodoc: + severity ||= UNKNOWN + progname ||= @progname + + return true if @logdev.nil? || severity < level + + 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) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_encryptor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_encryptor.rb new file mode 100644 index 0000000000..4cf4a44426 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_encryptor.rb @@ -0,0 +1,224 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/message_verifier" +require "active_support/messages/metadata" + +module ActiveSupport + # 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" + # + # === 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 + prepend Messages::Rotator::Encryptor + + 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 + + module NullVerifier #:nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + # 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. + # + # First additional parameter is used as the signature key for +MessageVerifier+. + # This allows you to specify keys to encrypt and sign data. + # + # 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 - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. + # * :serializer - Object serializer to use. Default is +Marshal+. + def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil) + @secret = secret + @sign_secret = sign_secret + @cipher = cipher || self.class.default_cipher + @digest = digest || "SHA1" unless aead_mode? + @verifier = resolve_verifier + @serializer = serializer || Marshal + 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/. + def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil) + verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + 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/. + def decrypt_and_verify(data, purpose: nil, **) + _decrypt(verifier.verify(data), purpose) + 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 + + private + def _encrypt(value, **metadata_options) + 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(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options)) + encrypted_data << cipher.final + + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob + end + + def _decrypt(encrypted_message, purpose) + cipher = new_cipher + encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) } + + # 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 InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16) + + 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 + + message = Messages::Metadata.verify(decrypted_data, purpose) + @serializer.load(message) if message + rescue OpenSSLCipherError, TypeError, ArgumentError + raise InvalidMessage + end + + def new_cipher + OpenSSL::Cipher.new(@cipher) + end + + attr_reader :verifier + + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end + + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_verifier.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_verifier.rb new file mode 100644 index 0000000000..ba992a17a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/message_verifier.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/metadata" +require "active_support/messages/rotator" + +module ActiveSupport + # +MessageVerifier+ makes it easy to generate and verify messages which are + # signed to prevent tampering. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links + # where the session store isn't suitable or available. + # + # Remember Me: + # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) + # + # In the authentication filter: + # + # id, time = @verifier.verify(cookies[:remember_me]) + # if Time.now < time + # self.current_user = User.find(id) + # end + # + # By default it uses Marshal to serialize the message. If you want to use + # another serialization method, you can set the serializer in the options + # hash upon initialization: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # +:digest+ key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === 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 = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.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+. + # + # @verifier.generate(parcel, expires_in: 1.month) + # @verifier.generate(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned up to the expire time. + # 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 to + # 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 + prepend Messages::Rotator::Verifier + + class InvalidSignature < StandardError; end + + def initialize(secret, digest: nil, serializer: nil) + raise ArgumentError, "Secret should not be nil." unless secret + @secret = secret + @digest = digest || "SHA1" + @serializer = serializer || Marshal + end + + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private 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?(signed_message) + return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? + + data, digest = signed_message.split("--") + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # + # signed_message = verifier.generate 'a private message' + # verifier.verified(signed_message) # => 'a private message' + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # 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 + def verified(signed_message, purpose: nil, **) + if valid_message?(signed_message) + begin + data = signed_message.split("--")[0] + message = Messages::Metadata.verify(decode(data), purpose) + @serializer.load(message) if message + rescue ArgumentError => argument_error + return if argument_error.message.include?("invalid base64") + raise + end + end + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # + # verifier.verify(signed_message) # => 'a private message' + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + def verify(*args, **options) + verified(*args, **options) || raise(InvalidSignature) + 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 's3Krit' + # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" + def generate(value, expires_at: nil, expires_in: nil, purpose: nil) + data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + "#{data}--#{generate_digest(data)}" + end + + private + def encode(data) + ::Base64.strict_encode64(data) + end + + def decode(data) + ::Base64.strict_decode64(data) + end + + def generate_digest(data) + require "openssl" unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/metadata.rb new file mode 100644 index 0000000000..4734cce819 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/metadata.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "time" + +module ActiveSupport + module Messages #:nodoc: + class Metadata #:nodoc: + def initialize(message, expires_at = nil, purpose = nil) + @message, @purpose = message, purpose + @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at + end + + def as_json(options = {}) + { _rails: { message: @message, exp: @expires_at, pur: @purpose } } + end + + class << self + def wrap(message, expires_at: nil, expires_in: nil, purpose: nil) + if expires_at || expires_in || purpose + JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose) + else + message + end + end + + def verify(message, purpose) + extract_metadata(message).verify(purpose) + end + + private + def pick_expiry(expires_at, expires_in) + if expires_at + expires_at.utc.iso8601(3) + elsif expires_in + Time.now.utc.advance(seconds: expires_in).iso8601(3) + end + end + + def extract_metadata(message) + data = JSON.decode(message) rescue nil + + if data.is_a?(Hash) && data.key?("_rails") + new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"]) + else + new(message) + end + end + + def encode(message) + ::Base64.strict_encode64(message) + end + + def decode(message) + ::Base64.strict_decode64(message) + end + end + + def verify(purpose) + @message if match?(purpose) && fresh? + end + + private + def match?(purpose) + @purpose.to_s == purpose.to_s + end + + def fresh? + @expires_at.nil? || Time.now.utc < @expires_at + end + + def parse_expires_at(expires_at) + if ActiveSupport.use_standard_json_time_format + Time.iso8601(expires_at) + else + Time.parse(expires_at) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/rotation_configuration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 0000000000..eef05fe317 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/messages/rotator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/rotator.rb new file mode 100644 index 0000000000..b19e1851e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/messages/rotator.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*secrets, on_rotation: nil, **options) + super(*secrets, **options) + + @options = options + @rotations = [] + @on_rotation = on_rotation + end + + def rotate(*secrets, **options) + @rotations << build_rotation(*secrets, @options.merge(options)) + end + + module Encryptor + include Rotator + + def decrypt_and_verify(*args, on_rotation: @on_rotation, **options) + super + rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature + run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, **options) } || raise + end + + private + def build_rotation(secret = @secret, sign_secret = @sign_secret, options) + self.class.new(secret, sign_secret, **options) + end + end + + module Verifier + include Rotator + + def verified(*args, on_rotation: @on_rotation, **options) + super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, **options) } + end + + private + def build_rotation(secret = @secret, options) + self.class.new(secret, **options) + end + end + + private + def run_rotations(on_rotation) + @rotations.find do |rotation| + if message = yield(rotation) rescue next + on_rotation&.call + return message + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte.rb new file mode 100644 index 0000000000..3fe3a05e93 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/chars.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/chars.rb new file mode 100644 index 0000000000..d58ab1ba05 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/chars.rb @@ -0,0 +1,177 @@ +# 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/symbol/starts_ends_with" +require "active_support/core_ext/module/delegation" + +module ActiveSupport #:nodoc: + module Multibyte #:nodoc: + # 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 + @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen? + end + + # Forward all undefined methods to the wrapped string. + def method_missing(method, *args, &block) + result = @wrapped_string.__send__(method, *args, &block) + 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.scan(/\X/).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 # => 2 + # 'é'.mb_chars.decompose.to_s.length # => 3 + def decompose + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) + end + + # Performs composition on all the characters. + # + # 'é'.length # => 3 + # 'é'.mb_chars.compose.to_s.length # => 2 + 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 # => 3 + def grapheme_length + @wrapped_string.scan(/\X/).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.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/unicode.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/unicode.rb new file mode 100644 index 0000000000..552790b05b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/multibyte/unicode.rb @@ -0,0 +1,82 @@ +# 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"] + + def default_normalization_form + ActiveSupport::Deprecation.warn( + "ActiveSupport::Multibyte::Unicode.default_normalization_form is deprecated and will be removed in Rails 6.2." + ) + end + + def default_normalization_form=(_) + ActiveSupport::Deprecation.warn( + "ActiveSupport::Multibyte::Unicode.default_normalization_form= is deprecated and will be removed in Rails 6.2." + ) + end + + # 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 + + # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. + if !defined?(Rubinius) + # 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 + else + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + + # We can't transcode to the same format, so we choose a nearly-identical encoding. + # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to + # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 + # before returning. + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) + + source = string.dup + out = "".force_encoding(Encoding::UTF_16LE) + + loop do + reader.primitive_convert(source, out) + _, _, _, error_bytes, _ = reader.primitive_errinfo + break if error_bytes.nil? + out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + + reader.finish + + out.encode!(Encoding::UTF_8) + end + 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.0.0/gems/activesupport-6.1.4/lib/active_support/notifications.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications.rb new file mode 100644 index 0000000000..d1227c2473 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications.rb @@ -0,0 +1,280 @@ +# frozen_string_literal: true + +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" +require "active_support/per_thread_registry" + +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 |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 # => Monotonic time, when the instrumented block started execution + # finish # => Monotonic time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # The +start+ and +finish+ values above represent monotonic time. + # + # For instance, let's store all "render" events in an array: + # + # events = [] + # + # ActiveSupport::Notifications.subscribe('render') do |*args| + # events << ActiveSupport::Notifications::Event.new(*args) + # 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 } + # + # The block in the subscribe call gets the name of the event, start + # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter + # (something like "535801666f04d0298cd6"), and a hash with the payload, in + # that order. + # + # 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 {|*args| ... } + # 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 |*args| + # ... + # 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 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. + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # The +block+ will receive five parameters 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 + # + # If the block passed to the method only takes one parameter, + # it will yield an event object to the block: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @event = event + # end + def subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: false, &block) + end + + 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 + InstrumentationRegistry.instance.instrumenter_for(notifier) + end + end + + # This class is a registry which holds all of the +Instrumenter+ objects + # in a particular thread local. To access the +Instrumenter+ object for a + # particular +notifier+, you can call the following method: + # + # InstrumentationRegistry.instrumenter_for(notifier) + # + # The instrumenters for multiple notifiers are held in a single instance of + # this class. + class InstrumentationRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def instrumenter_for(notifier) + @registry[notifier] ||= Instrumenter.new(notifier) + end + end + + self.notifier = Fanout.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/fanout.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/fanout.rb new file mode 100644 index 0000000000..5fba8347d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/fanout.rb @@ -0,0 +1,259 @@ +# frozen_string_literal: true + +require "mutex_m" +require "concurrent/map" +require "set" +require "active_support/core_ext/object/try" + +module ActiveSupport + module Notifications + # 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 + include Mutex_m + + def initialize + @string_subscribers = Hash.new { |h, k| h[k] = [] } + @other_subscribers = [] + @listeners_for = Concurrent::Map.new + super + end + + def subscribe(pattern = nil, callable = nil, monotonic: false, &block) + subscriber = Subscribers.new(pattern, callable || block, monotonic) + synchronize do + if String === pattern + @string_subscribers[pattern] << subscriber + @listeners_for.delete(pattern) + else + @other_subscribers << subscriber + @listeners_for.clear + end + end + subscriber + end + + def unsubscribe(subscriber_or_name) + synchronize do + case subscriber_or_name + when String + @string_subscribers[subscriber_or_name].clear + @listeners_for.delete(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) + @listeners_for.delete(pattern) + else + @other_subscribers.delete(subscriber_or_name) + @listeners_for.clear + end + end + end + end + + def start(name, id, payload) + listeners_for(name).each { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload, listeners = listeners_for(name)) + listeners.each { |s| s.finish(name, id, payload) } + end + + def publish(name, *args) + listeners_for(name).each { |s| s.publish(name, *args) } + end + + def listeners_for(name) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) + @listeners_for[name] || synchronize do + # use synchronisation when accessing @subscribers + @listeners_for[name] ||= + @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) } + end + end + + def listening?(name) + listeners_for(name).any? + 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 all this to detect a block like `proc { |x| }` vs + # `proc { |*x| }` or `proc { |**x| }` + if listener.respond_to?(:parameters) + params = listener.parameters + if params.length == 1 && params.first.first == :opt + subscriber_class = EventObject + end + end + end + + wrap_all pattern, subscriber_class.new(pattern, listener) + end + + def self.wrap_all(pattern, subscriber) + unless pattern + AllMessages.new(subscriber) + else + subscriber + end + end + + class Matcher #:nodoc: + attr_reader :pattern, :exclusions + + def self.wrap(pattern) + return pattern if String === pattern + new(pattern) + 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 + end + + class Evented #:nodoc: + attr_reader :pattern + + def initialize(pattern, delegate) + @pattern = Matcher.wrap(pattern) + @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + pattern === name + end + + def matches?(name) + pattern && pattern === name + end + + def unsubscribe!(name) + pattern.unsubscribe!(name) + end + end + + class Timed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack] ||= [] + timestack.push Time.now + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack] + started = timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end + end + + class MonotonicTimed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] ||= [] + timestack.push Concurrent.monotonic_time + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] + started = timestack.pop + @delegate.call(name, started, Concurrent.monotonic_time, id, payload) + end + end + + class EventObject < Evented + def start(name, id, payload) + stack = Thread.current[:_event_stack] ||= [] + event = build_event name, id, payload + event.start! + stack.push event + end + + def finish(name, id, payload) + stack = Thread.current[:_event_stack] + event = stack.pop + event.payload = payload + event.finish! + @delegate.call event + end + + private + def build_event(name, id, payload) + ActiveSupport::Notifications::Event.new name, nil, nil, id, payload + end + end + + class AllMessages # :nodoc: + def initialize(delegate) + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def publish(name, *args) + @delegate.publish name, *args + end + + def subscribed_to?(name) + true + end + + def unsubscribe!(*) + false + end + + alias :matches? :=== + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/instrumenter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 0000000000..e1a9fe349c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "securerandom" + +module ActiveSupport + module Notifications + # Instrumenters are stored in a thread local. + class Instrumenter + attr_reader :id + + def initialize(notifier) + @id = unique_id + @notifier = notifier + 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 = {}) + # some of the listeners might have state + listeners_state = start name, payload + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish_with_state listeners_state, name, payload + end + 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, :time, :end, :transaction_id, :children + attr_accessor :payload + + def initialize(name, start, ending, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @children = [] + @cpu_time_start = 0 + @cpu_time_finish = 0 + @allocation_count_start = 0 + @allocation_count_finish = 0 + end + + # Record information at the time this event starts + def start! + @time = now + @cpu_time_start = now_cpu + @allocation_count_start = now_allocations + end + + # Record information at the time this event finishes + def finish! + @cpu_time_finish = now_cpu + @end = now + @allocation_count_finish = now_allocations + end + + # Returns the CPU time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def cpu_time + (@cpu_time_finish - @cpu_time_start) * 1000 + end + + # Returns the idle time time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def idle_time + duration - cpu_time + end + + # Returns the number of allocations made since the call to +start!+ and + # the call to +finish!+ + def allocations + @allocation_count_finish - @allocation_count_start + end + + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 + def duration + 1000.0 * (self.end - time) + end + + def <<(event) + @children << event + end + + def parent_of?(event) + @children.include? event + end + + private + def now + Concurrent.monotonic_time + end + + begin + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) + + def now_cpu + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) + end + rescue + def now_cpu + 0 + end + end + + if defined?(JRUBY_VERSION) + def now_allocations + 0 + end + else + def now_allocations + GC.stat :total_allocated_objects + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper.rb new file mode 100644 index 0000000000..93d4791e42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper.rb @@ -0,0 +1,397 @@ +# frozen_string_literal: true + +module ActiveSupport + 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 a +number+ into a phone number (US by default e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :area_code - Adds parentheses around the area code. + # * :delimiter - Specifies the delimiter to use + # (defaults to "-"). + # * :extension - Specifies an extension to add to the + # end of the generated number. + # * :country_code - Sets the country code for the phone + # number. + # * :pattern - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. + # ==== Examples + # + # number_to_phone(5551234) # => "555-1234" + # number_to_phone('5551234') # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234" + # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234" + # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234" + # number_to_phone('123a456') # => "123a456" + # + # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) + # # => "133-1234-5678" + def number_to_phone(number, options = {}) + NumberToPhoneConverter.convert(number, options) + end + + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. + # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided 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 - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the level of precision (defaults + # to 2). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :unit - Sets the denomination of the currency + # (defaults to "$"). + # * :separator - Sets the separator between the units + # (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :format - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are %u for the + # currency, and %n for the number. + # * :negative_format - Sets the format for negative + # numbers (defaults to prepending a hyphen to the formatted + # number given by :format). Accepts the same fields + # than :format, except %n is here the + # absolute value of the number. + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506" + # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €" + # number_to_currency('123a456') # => "$123a456" + # + # number_to_currency("123a456", raise: true) # => InvalidNumberError + # + # number_to_currency(-0.456789, precision: 0) + # # => "$0" + # number_to_currency(-1234567890.50, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" + # number_to_currency(1234567890.50, precision: 0, round_mode: :up) + # # => "$1,234,567,891" + def number_to_currency(number, options = {}) + NumberToCurrencyConverter.convert(number, options) + end + + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * :format - Specifies the format of the percentage + # string The number field is %n (defaults to "%n%"). + # + # ==== Examples + # + # number_to_percentage(100) # => "100.000%" + # number_to_percentage('98') # => "98.000%" + # number_to_percentage(100, precision: 0) # => "100%" + # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%" + # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%" + # number_to_percentage(1000, locale: :fr) # => "1000,000%" + # number_to_percentage(1000, precision: nil) # => "1000%" + # number_to_percentage('98a') # => "98a%" + # number_to_percentage(100, format: '%n %') # => "100.000 %" + # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%" + def number_to_percentage(number, options = {}) + NumberToPercentageConverter.convert(number, options) + end + + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter_pattern - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. + # + # ==== Examples + # + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited('123456') # => "123,456" + # number_to_delimited(12345678.05) # => "12,345,678.05" + # number_to_delimited(12345678, delimiter: '.') # => "12.345.678" + # number_to_delimited(12345678, delimiter: ',') # => "12,345,678" + # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05" + # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05" + # number_to_delimited('112a') # => "112a" + # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # 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 a +number+ with the specified level of + # :precision (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). + # You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_rounded(111.2345) # => "111.235" + # number_to_rounded(111.2345, precision: 2) # => "111.23" + # number_to_rounded(13, precision: 5) # => "13.00000" + # number_to_rounded(389.32314, precision: 0) # => "389" + # number_to_rounded(111.2345, significant: true) # => "111" + # number_to_rounded(111.2345, precision: 1, significant: true) # => "100" + # number_to_rounded(13, precision: 5, significant: true) # => "13.000" + # number_to_rounded(13, precision: nil) # => "13" + # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390" + # number_to_rounded(111.234, locale: :fr) # => "111,234" + # + # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # + # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3" + # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + def number_to_rounded(number, options = {}) + NumberToRoundedConverter.convert(number, options) + end + + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.46 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. + # + # See number_to_human if you want to pretty-print a + # generic number. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # + # ==== Examples + # + # 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" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # number_to_human_size(483989, precision: 2) # => "470 KB" + # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB" + # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB" + # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" + def number_to_human_size(number, options = {}) + NumberToHumanSizeConverter.convert(number, options) + end + + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (e.g.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). + # + # See number_to_human_size if you want to print a file + # size. + # + # You can also define your own unit-quantifier names if you want + # to use other decimal units (e.g.: 1500 becomes "1.5 + # kilometers", 0.150 becomes "150 milliliters", etc). You may + # define a wide range of unit quantifiers, even fractional ones + # (centi, deci, mili, etc). + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * :units - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: :unit, :ten, + # :hundred, :thousand, :million, + # :billion, :trillion, + # :quadrillion + # * *fractionals*: :deci, :centi, + # :mili, :micro, :nano, + # :pico, :femto + # * :format - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # + # ==== Examples + # + # 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" + # number_to_human(489939, precision: 2) # => "490 Thousand" + # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(489939, precision: 2 + # , round_mode: :down) # => "480 Thousand" + # number_to_human(1234567, precision: 4, + # significant: false) # => "1.2346 Million" + # number_to_human(1234567, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + # + # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12345012345, significant: false) # => "12.345 Billion" + # + # Non-significant zeros after the decimal separator are stripped + # out by default (set :strip_insignificant_zeros to + # +false+ to change that): + # + # number_to_human(12.00001) # => "12" + # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt" + # + # If in your I18n locale you have: + # + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazillion-distance" + # + # Then you could do: + # + # number_to_human(543934, units: :distance) # => "544 kilometers" + # number_to_human(54393498, units: :distance) # => "54400 kilometers" + # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" + # number_to_human(343, units: :distance, precision: 1) # => "300 meters" + # number_to_human(1, units: :distance) # => "1 meter" + # number_to_human(0.34, units: :distance) # => "34 centimeters" + def number_to_human(number, options = {}) + NumberToHumanConverter.convert(number, options) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_converter.rb new file mode 100644 index 0000000000..75509f1eca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_converter.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +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 + end + + def execute + if !number + nil + elsif validate_float? && !valid_float? + 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 }.merge!(i18n_options)) + end + + def translate_in_locale(key, **i18n_options) + translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options)) + end + + def default_value(key) + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + end + + def valid_float? + Float(number) + rescue ArgumentError, TypeError + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_currency_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 0000000000..c0efababa9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + self.namespace = :currency + + def convert + number = self.number.to_s.strip + format = options[:format] + + if number.sub!(/^-/, "") && + (options[:precision] != 0 || number.to_f > 0.5) + format = options[:negative_format] + end + + rounded_number = NumberToRoundedConverter.convert(number, options) + format.gsub("%n", rounded_number).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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_delimited_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 0000000000..351444289c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_human_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_human_converter.rb new file mode 100644 index 0000000000..3f92628501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_human_size_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_human_size_converter.rb new file mode 100644 index 0000000000..a9b7ce263f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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] + + 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) / 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 < base + end + + def base + 1024 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_percentage_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_percentage_converter.rb new file mode 100644 index 0000000000..0c2e190f8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_phone_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_phone_converter.rb new file mode 100644 index 0000000000..21eadfdcc7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_phone_converter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_rounded_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 0000000000..f48a5158c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/rounding_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/rounding_helper.rb new file mode 100644 index 0000000000..56b996a35b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/number_helper/rounding_helper.rb @@ -0,0 +1,50 @@ +# 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 significant && options[:precision] > 0 + options[:precision] - digit_count(convert_to_decimal(number)) + else + options[:precision] + end + end + + def significant + options[:significant] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/option_merger.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/option_merger.rb new file mode 100644 index 0000000000..c7f7c0aa6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/option_merger.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/symbol/starts_ends_with" + +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.first.is_a?(Proc) + proc = arguments.pop + 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 + + invoke_method(method, arguments, options, &block) + end + + if RUBY_VERSION >= "2.7" + def invoke_method(method, arguments, options, &block) + if options + @context.__send__(method, *arguments, **options, &block) + else + @context.__send__(method, *arguments, &block) + end + end + else + def invoke_method(method, arguments, options, &block) + arguments << options.dup if options + @context.__send__(method, *arguments, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/ordered_hash.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/ordered_hash.rb new file mode 100644 index 0000000000..ad11524137 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 + 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.0.0/gems/activesupport-6.1.4/lib/active_support/ordered_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/ordered_options.rb new file mode 100644 index 0000000000..ba14907d9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/ordered_options.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +module ActiveSupport + # +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 method_missing(name, *args) + name_string = +name.to_s + if name_string.chomp!("=") + self[name_string] = args.first + else + bangs = name_string.chomp!("!") + + if bangs + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) + else + self[name_string] + end + end + end + + def respond_to_missing?(name, include_private) + true + end + + def extractable_options? + true + end + + def inspect + "#<#{self.class.name} #{super}>" + end + end + + # +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' + class InheritableOptions < OrderedOptions + def initialize(parent = nil) + 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() + end + end + + def inheritable_copy + self.class.new(self) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/parameter_filter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/parameter_filter.rb new file mode 100644 index 0000000000..134778251f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/parameter_filter.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActiveSupport + # +ParameterFilter+ allows you to specify keys for sensitive data from + # hash-like object and replace corresponding value. Filtering only certain + # sub-keys from a hash is possible by using the dot notation: + # 'credit_card.number'. If a proc is given, each key and value of a hash and + # all sub-hashes are passed to it, where the value or the key can be replaced + # using String#replace or similar methods. + # + # ActiveSupport::ParameterFilter.new([:password]) + # => replaces the value to all keys matching /password/i with "[FILTERED]" + # + # ActiveSupport::ParameterFilter.new([:foo, "bar"]) + # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + # + # ActiveSupport::ParameterFilter.new(["credit_card.code"]) + # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not + # change { file: { code: "xxxx"} } + # + # ActiveSupport::ParameterFilter.new([-> (k, v) do + # v.reverse! if /secret/i.match?(k) + # end]) + # => reverses the value to all keys matching /secret/i + class ParameterFilter + FILTERED = "[FILTERED]" # :nodoc: + + # 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) + @filters = filters + @mask = mask + end + + # Mask value of +params+ if key matches one of filters. + def filter(params) + compiled_filter.call(params) + end + + # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated. + def filter_param(key, value) + @filters.empty? ? value : compiled_filter.value_for_key(key, value) + end + + private + def compiled_filter + @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask) + end + + class CompiledFilter # :nodoc: + def self.compile(filters, mask:) + return lambda { |params| params.dup } if filters.empty? + + strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, 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&.any? + + new regexps, deep_regexps, blocks, mask: mask + end + + attr_reader :regexps, :deep_regexps, :blocks + + def initialize(regexps, deep_regexps, blocks, mask:) + @regexps = regexps + @deep_regexps = deep_regexps&.any? ? deep_regexps : nil + @blocks = blocks + @mask = mask + end + + def call(params, parents = [], original_params = params) + filtered_params = params.class.new + + params.each do |key, value| + filtered_params[key] = value_for_key(key, value, parents, original_params) + end + + filtered_params + end + + def value_for_key(key, value, parents = [], original_params = nil) + parents.push(key) if deep_regexps + if regexps.any? { |r| r.match?(key.to_s) } + value = @mask + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) } + value = @mask + elsif value.is_a?(Hash) + value = call(value, parents, original_params) + elsif value.is_a?(Array) + # If we don't pop the current parent it will be duplicated as we + # process each array value. + parents.pop if deep_regexps + value = value.map { |v| value_for_key(key, v, parents, original_params) } + # Restore the parent stack after processing the array. + parents.push(key) if deep_regexps + elsif blocks.any? + 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 + parents.pop if deep_regexps + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/per_thread_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/per_thread_registry.rb new file mode 100644 index 0000000000..bf5a3b97ae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/per_thread_registry.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. + # Please use that approach instead. + # + # This module is used to encapsulate access to thread local variables. + # + # Instead of polluting the thread locals namespace: + # + # Thread.current[:connection_handler] + # + # you define a class that extends this module: + # + # module ActiveRecord + # class RuntimeRegistry + # extend ActiveSupport::PerThreadRegistry + # + # attr_accessor :connection_handler + # end + # end + # + # and invoke the declared instance accessors as class methods. So + # + # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler + # + # sets a connection handler local to the current thread, and + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns a connection handler local to the current thread. + # + # This feature is accomplished by instantiating the class and storing the + # instance as a thread local keyed by the class name. In the example above + # a key "ActiveRecord::RuntimeRegistry" is stored in Thread.current. + # The class methods proxy to said thread local instance. + # + # If the class has an initializer, it must accept no arguments. + module PerThreadRegistry + def self.extended(object) + object.instance_variable_set :@per_thread_registry_key, object.name.freeze + end + + def instance + Thread.current[@per_thread_registry_key] ||= new + end + + private + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/proxy_object.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/proxy_object.rb new file mode 100644 index 0000000000..0965fcd2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/proxy_object.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveSupport + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. + class ProxyObject < ::BasicObject + undef_method :== + undef_method :equal? + + # Let ActiveSupport::ProxyObject at least raise exceptions. + def raise(*args) + ::Object.send(:raise, *args) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/rails.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/rails.rb new file mode 100644 index 0000000000..75676a2e47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/railtie.rb new file mode 100644 index 0000000000..c36453f7de --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/railtie.rb @@ -0,0 +1,102 @@ +# 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.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_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 + require "active_support/current_attributes/test_helper" + include ActiveSupport::CurrentAttributes::TestHelper + end + end + + initializer "active_support.deprecation_behavior" do |app| + if deprecation = app.config.active_support.deprecation + ActiveSupport::Deprecation.behavior = deprecation + end + + if disallowed_deprecation = app.config.active_support.disallowed_deprecation + ActiveSupport::Deprecation.disallowed_behavior = disallowed_deprecation + end + + if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings + ActiveSupport::Deprecation.disallowed_warnings = disallowed_warnings + 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) + 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 app.config.active_support.use_sha1_digests + ActiveSupport::Deprecation.warn(<<-MSG.squish) + config.active_support.use_sha1_digests is deprecated and will + be removed from Rails 6.2. Use + config.active_support.hash_digest_class = ::Digest::SHA1 instead. + MSG + ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1 + end + + if klass = app.config.active_support.hash_digest_class + ActiveSupport::Digest.hash_digest_class = klass + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/reloader.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/reloader.rb new file mode 100644 index 0000000000..2f81cd4f80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/reloader.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" +require "active_support/executor" + +module ActiveSupport + #-- + # 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! # :nodoc: + if check! + super + else + Null + end + end + + # Run the supplied block as a work unit, reloading code as needed + def self.wrap + executor.wrap do + super + 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.0.0/gems/activesupport-6.1.4/lib/active_support/rescuable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/rescuable.rb new file mode 100644 index 0000000000..2b965677f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/rescuable.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # 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 # self defined exception + # rescue_from ActiveRecord::RecordInvalid, with: :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render xml: exception, status: 500 + # end + # + # private + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/secure_compare_rotator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 0000000000..269703c34a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # 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 + prepend Messages::Rotator + + InvalidMatch = Class.new(StandardError) + + def initialize(value, **_options) + @value = value + end + + def secure_compare!(other_value, on_rotation: @on_rotation) + secure_compare(@value, other_value) || + run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } || + raise(InvalidMatch) + end + + private + def build_rotation(previous_value, _options) + self.class.new(previous_value) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/security_utils.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..aa00474448 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/string_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/string_inquirer.rb new file mode 100644 index 0000000000..5ff3f4bfe7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/string_inquirer.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" + +module ActiveSupport + # 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, *arguments) + if method_name.end_with?("?") + self == method_name[0..-2] + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/subscriber.rb new file mode 100644 index 0000000000..24f8681af8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/subscriber.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" +require "active_support/notifications" + +module ActiveSupport + # 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) + # 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 + @queue_key = [self.class.name, object_id].join "-" + @patterns = {} + super + end + + def start(name, id, payload) + event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload) + event.start! + parent = event_stack.last + parent << event if parent + + event_stack.push event + end + + def finish(name, id, payload) + event = event_stack.pop + event.finish! + event.payload.merge!(payload) + + method = name.split(".").first + send(method, event) + end + + private + def event_stack + SubscriberQueueRegistry.instance.get_queue(@queue_key) + end + end + + # This is a registry for all the event stacks kept for subscribers. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class SubscriberQueueRegistry # :nodoc: + extend PerThreadRegistry + + def initialize + @registry = {} + end + + def get_queue(queue_key) + @registry[queue_key] ||= [] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/tagged_logging.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..ed551011a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/tagged_logging.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "logger" +require "active_support/logger" + +module ActiveSupport + # 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") { 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, "#{tags_text}#{msg}") + end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten! + tags.reject!(&:blank?) + current_tags.concat tags + tags + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + def current_tags + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}" + Thread.current[thread_key] ||= [] + end + + def tags_text + tags = current_tags + if tags.one? + "[#{tags[0]}] " + elsif tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end + end + end + + module LocalTagStorage # :nodoc: + attr_accessor :current_tags + + def self.extended(base) + base.current_tags = [] + end + end + + def self.new(logger) + logger = logger.dup + + if logger.formatter + logger.formatter = logger.formatter.dup + 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.0.0/gems/activesupport-6.1.4/lib/active_support/test_case.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/test_case.rb new file mode 100644 index 0000000000..7be4108ed7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/test_case.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +gem "minitest" # make sure we get the gem, not stdlib +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/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/file_fixtures" +require "active_support/testing/parallelization" +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 + # database and will not work with system tests at this time. + # + # parallelize(workers: :number_of_processors, with: :threads) + # + # The threaded parallelization uses minitest's parallel executor directly. + # The processes parallelization uses a Ruby DRb server. + def parallelize(workers: :number_of_processors, with: :processes) + workers = Concurrent.physical_processor_count if workers == :number_of_processors + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + return if workers <= 1 + + executor = case with + when :processes + Testing::Parallelization.new(workers) + when :threads + Minitest::Parallel::Executor.new(workers) + else + raise ArgumentError, "#{with} is not a supported parallelization executor." + end + + self.lock_threads = false if defined?(self.lock_threads) && with == :threads + + Minitest.parallel_executor = executor + + parallelize_me! + 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 do |worker| + yield worker + end + 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 do |worker| + yield worker + end + end + end + + alias_method :method_name, :name + + include ActiveSupport::Testing::TaggedLogging + prepend ActiveSupport::Testing::SetupAndTeardown + include ActiveSupport::Testing::Assertions + include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures + extend ActiveSupport::Testing::Declarative + + # test/unit backwards compatibility methods + alias :assert_raise :assert_raises + alias :assert_not_empty :refute_empty + alias :assert_not_equal :refute_equal + alias :assert_not_in_delta :refute_in_delta + alias :assert_not_in_epsilon :refute_in_epsilon + alias :assert_not_includes :refute_includes + alias :assert_not_instance_of :refute_instance_of + alias :assert_not_kind_of :refute_kind_of + alias :assert_no_match :refute_match + alias :assert_not_nil :refute_nil + alias :assert_not_operator :refute_operator + alias :assert_not_predicate :refute_predicate + alias :assert_not_respond_to :refute_respond_to + alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/assertions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/assertions.rb new file mode 100644 index 0000000000..d564cad808 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/assertions.rb @@ -0,0 +1,235 @@ +# 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 + + # 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 + 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(&block) + + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" + error = "#{message}.\n#{error}" if message + assert_equal(before_value + diff, exp.call, error) + 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(&block) + + unless from == UNTRACKED + error = "Expected change from #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + assert_not_equal before, after, error + + unless to == UNTRACKED + error = "Expected change to #{to}\n" + error = "#{message}.\n#{error}" if message + assert to === after, error + 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 + # + # 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, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = assert_nothing_raised(&block) + after = exp.call + + error = "#{expression.inspect} changed" + error = "#{message}.\n#{error}" if message + + if before.nil? + assert_nil after, error + else + assert_equal before, after, error + end + + retval + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/autorun.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/autorun.rb new file mode 100644 index 0000000000..889b41659a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/autorun.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +gem "minitest" + +require "minitest" + +Minitest.autorun diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/constant_lookup.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/constant_lookup.rb new file mode 100644 index 0000000000..51167e9237 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/declarative.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/declarative.rb new file mode 100644 index 0000000000..7c3403684d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/deprecation.rb new file mode 100644 index 0000000000..18d63d2780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/deprecation.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/deprecation" + +module ActiveSupport + module Testing + module Deprecation #:nodoc: + def assert_deprecated(match = nil, deprecator = nil, &block) + 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 + + def assert_not_deprecated(deprecator = nil, &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 + + def collect_deprecations(deprecator = nil) + deprecator ||= ActiveSupport::Deprecation + 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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/file_fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 0000000000..4eb7a88576 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/isolation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/isolation.rb new file mode 100644 index 0000000000..652a10da23 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/isolation.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Isolation + require "thread" + + def self.included(klass) #:nodoc: + klass.class_eval do + parallelize_me! + end + end + + def self.forking_env? + !ENV["NO_FORK"] && Process.respond_to?(:fork) + end + + def run + serialized = run_in_isolation do + super + end + + Marshal.load(serialized) + end + + module Forking + def run_in_isolation(&blk) + read, write = IO.pipe + 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! + end + + write.close + result = read.read + Process.wait2(pid) + result.unpack1("m") + end + end + + module Subprocess + ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) + + # Crazy 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! + 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]) + + begin + Process.wait(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end + + return tmpfile.read.unpack1("m") + end + end + end + end + + include forking_env? ? Forking : Subprocess + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/method_call_assertions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 0000000000..03c38be481 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,70 @@ +# 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) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }) { 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 + end + + def assert_called_with(object, method_name, args, returns: nil) + mock = Minitest::Mock.new + + if args.all? { |arg| arg.is_a?(Array) } + args.each { |arg| mock.expect(:call, returns, arg) } + else + mock.expect(:call, returns, args) + end + + object.stub(method_name, mock) { yield } + + mock.verify + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..fc6e689260 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization.rb @@ -0,0 +1,51 @@ +# 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 shutdown + @queue_server.shutdown + @worker_pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/server.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/server.rb new file mode 100644 index 0000000000..492101e297 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/server.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + 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.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 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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/worker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/worker.rb new file mode 100644 index 0000000000..60c504a2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/parallelization/worker.rb @@ -0,0 +1,100 @@ +# 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) + 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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/setup_and_teardown.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/setup_and_teardown.rb new file mode 100644 index 0000000000..35321cd157 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/setup_and_teardown.rb @@ -0,0 +1,55 @@ +# 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) + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/stream.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/stream.rb new file mode 100644 index 0000000000..f895a74644 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/stream.rb @@ -0,0 +1,43 @@ +# 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 + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + 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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/tagged_logging.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/tagged_logging.rb new file mode 100644 index 0000000000..9ca50c7918 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/testing/time_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/time_helpers.rb new file mode 100644 index 0000000000..ec97381e90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/time/calculations" +require "concurrent/map" + +module ActiveSupport + module Testing + # Manages stubs for TimeHelpers + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = Concurrent::Map.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}" + + @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. + # + # 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, &block) + travel_to Time.now + duration, &block + end + + # Changes current time to the given time by stubbing +Time.now+, + # +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). + # + # 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) + if block_given? && simple_stubs.stubbing(Time, :now) + 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 + else + now = date_or_time.to_time.change(usec: 0) + end + + simple_stubs.stub_object(Time, :now) { at(now.to_i) } + simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + simple_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 + yield + ensure + travel_back + 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+. + # + # 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(&block) + travel_to Time.now, &block + end + + private + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/time.rb new file mode 100644 index 0000000000..51854675bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/time_with_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/time_with_zone.rb new file mode 100644 index 0000000000..03d49d597f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/time_with_zone.rb @@ -0,0 +1,585 @@ +# frozen_string_literal: true + +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 + # 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_s(: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 + # Report class name as 'Time' to thwart type checking. + def self.name + "Time" + end + + 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 # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00" + def inspect + "#{time.strftime('%a, %d %b %Y %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").to_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").to_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.tag = "!ruby/object:ActiveSupport::TimeWithZone" + 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_s(:rfc822) + end + alias_method :rfc822, :rfc2822 + + # Returns a string of the object's date and time. + # Accepts an optional format: + # * :default - default value, mimics Ruby Time#to_s format. + # * :db - format outputs time in UTC :db time. See Time#to_formatted_s(:db). + # * Any key in Time::DATE_FORMATS can be used. See active_support/core_ext/time/conversions.rb. + def to_s(format = :default) + if format == :db + utc.to_s(format) + elsif formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + end + end + alias_method :to_formatted_s, :to_s + + # 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 + result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) + 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. Then it will return a Float of the difference + # between the two times that represents the difference between the current + # object's time and the +other+ time. + # + # 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) + to_time - other.to_time + elsif duration_of_variable_length?(other) + method_missing(:-, other) + else + result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(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.116992711 EST -05:00 + # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.116992711 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 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 + @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 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) + return false if sym.to_sym == :acts_like_date? + 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(sym, *args, &block) + wrap_with_time_zone time.__send__(sym, *args, &block) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, inspect), 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.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) } + 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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/values/time_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/values/time_zone.rb new file mode 100644 index 0000000000..4c289a5836 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/values/time_zone.rb @@ -0,0 +1,580 @@ +# frozen_string_literal: true + +require "tzinfo" +require "concurrent/map" + +module ActiveSupport + # 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", + "Astana" => "Asia/Dhaka", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Colombo", + "Almaty" => "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/Melbourne", + "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 + + # 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 String + begin + @lazy_zones_map[arg] ||= create(arg) + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + 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 + + # 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). + def initialize(name, utc_offset = nil, tzinfo = nil) + @name = name + @utc_offset = utc_offset + @tzinfo = tzinfo || TimeZone.find_tzinfo(name) + end + + # 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) + parts = Date._iso8601(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + 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 + 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) + 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 + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_utc(time) + tzinfo.period_for_utc(time) + end + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_local(time, dst = true) + tzinfo.period_for_local(time, dst) { |periods| periods.last } + end + + def periods_for_local(time) #:nodoc: + tzinfo.periods_for_local(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.0.0/gems/activesupport-6.1.4/lib/active_support/version.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/version.rb new file mode 100644 index 0000000000..928838c837 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveSupport + # Returns the version of the currently loaded ActiveSupport as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini.rb new file mode 100644 index 0000000000..f6ae08bb5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini.rb @@ -0,0 +1,201 @@ +# 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', '=0.9.7' + # 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", + "Array" => "array", + "Hash" => "hash" + } + end + + FORMATTING = { + "symbol" => Proc.new { |symbol| symbol.to_s }, + "date" => Proc.new { |date| date.to_s(:db) }, + "dateTime" => Proc.new { |time| time.xmlschema }, + "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 }, + "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) }, + "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 + + # TODO: Add support for other encodings + def _parse_binary(bin, entity) + case entity["encoding"] + when "base64" + ::Base64.decode64(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 current_thread_backend + Thread.current[:xml_mini_backend] + end + + def current_thread_backend=(name) + Thread.current[: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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/jdom.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/jdom.rb new file mode 100644 index 0000000000..12ca19a76f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/jdom.rb @@ -0,0 +1,182 @@ +# 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__" + + NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE + DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE + PROCESSING_INSTRUCTION_NODE TEXT_NODE} + + node_type_map = {} + NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type } + + # 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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/libxml.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/libxml.rb new file mode 100644 index 0000000000..c2e999ef6c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/libxmlsax.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/libxmlsax.rb new file mode 100644 index 0000000000..ac8acdfc3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogiri.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogiri.rb new file mode 100644 index 0000000000..f76513f48b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogiri.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogirisax.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogirisax.rb new file mode 100644 index 0000000000..55cd72e093 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/nokogirisax.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/rexml.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/lib/active_support/xml_mini/rexml.rb new file mode 100644 index 0000000000..c700959f71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4/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 + $stderr.puts "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.0.0/gems/activesupport-6.1.7.2/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/CHANGELOG.md new file mode 100644 index 0000000000..1c852de461 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/CHANGELOG.md @@ -0,0 +1,744 @@ +## Rails 6.1.7.2 (January 24, 2023) ## + +* No changes. + + +## Rails 6.1.7.1 (January 17, 2023) ## + +* Avoid regex backtracking in Inflector.underscore + + [CVE-2023-22796] + + +## Rails 6.1.7 (September 09, 2022) ## + +* No changes. + + +## Rails 6.1.6.1 (July 12, 2022) ## + +* No changes. + + +## Rails 6.1.6 (May 09, 2022) ## + +* No changes. + + +## Rails 6.1.5.1 (April 26, 2022) ## + +* Fix and add protections for XSS in `ActionView::Helpers` and `ERB::Util`. + + Add the method `ERB::Util.xml_name_escape` to escape dangerous characters + in names of tags and names of attributes, following the specification of XML. + + *Álvaro Martín Fraguas* + +## Rails 6.1.5 (March 09, 2022) ## + +* Fix `ActiveSupport::Duration.build` to support negative values. + + The algorithm to collect the `parts` of the `ActiveSupport::Duration` + ignored the sign of the `value` and accumulated incorrect part values. This + impacted `ActiveSupport::Duration#sum` (which is dependent on `parts`) but + not `ActiveSupport::Duration#eql?` (which is dependent on `value`). + + *Caleb Buxton*, *Braden Staudacher* + +* `Time#change` and methods that call it (eg. `Time#advance`) will now + return a `Time` with the timezone argument provided, if the caller was + initialized with a timezone argument. + + Fixes [#42467](https://github.com/rails/rails/issues/42467). + + *Alex Ghiculescu* + +* Clone to keep extended Logger methods for tagged logger. + + *Orhan Toy* + +* `assert_changes` works on including `ActiveSupport::Assertions` module. + + *Pedro Medeiros* + + +## Rails 6.1.4.7 (March 08, 2022) ## + +* No changes. + + +## Rails 6.1.4.6 (February 11, 2022) ## + +* Fix Reloader method signature to work with the new Executor signature + + +## Rails 6.1.4.5 (February 11, 2022) ## + +* No changes. + + +## Rails 6.1.4.4 (December 15, 2021) ## + +* No changes. + + +## Rails 6.1.4.3 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.2 (December 14, 2021) ## + +* No changes. + + +## Rails 6.1.4.1 (August 19, 2021) ## + +* No changes. + + +## Rails 6.1.4 (June 24, 2021) ## + +* MemCacheStore: convert any underlying value (including `false`) to an `Entry`. + + See [#42559](https://github.com/rails/rails/pull/42559). + + *Alex Ghiculescu* + +* Fix bug in `number_with_precision` when using large `BigDecimal` values. + + Fixes #42302. + + *Federico Aldunate*, *Zachary Scott* + +* Check byte size instead of length on `secure_compare`. + + *Tietew* + +* Fix `Time.at` to not lose `:in` option. + + *Ryuta Kamizono* + +* Require a path for `config.cache_store = :file_store`. + + *Alex Ghiculescu* + +* Avoid having to store complex object in the default translation file. + + *Rafael Mendonça França* + + +## Rails 6.1.3.2 (May 05, 2021) ## + +* No changes. + + +## Rails 6.1.3.1 (March 26, 2021) ## + +* No changes. + + +## Rails 6.1.3 (February 17, 2021) ## + +* No changes. + + +## Rails 6.1.2.1 (February 10, 2021) ## + +* No changes. + + +## Rails 6.1.2 (February 09, 2021) ## + +* `ActiveSupport::Cache::MemCacheStore` now accepts an explicit `nil` for its `addresses` argument. + + ```ruby + config.cache_store = :mem_cache_store, nil + + # is now equivalent to + + config.cache_store = :mem_cache_store + + # and is also equivalent to + + config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211" + + # which is the fallback behavior of Dalli + ``` + + This helps those migrating from `:dalli_store`, where an explicit `nil` was permitted. + + *Michael Overmeyer* + + +## Rails 6.1.1 (January 07, 2021) ## + +* Change `IPAddr#to_json` to match the behavior of the json gem returning the string representation + instead of the instance variables of the object. + + Before: + + ```ruby + IPAddr.new("127.0.0.1").to_json + # => "{\"addr\":2130706433,\"family\":2,\"mask_addr\":4294967295}" + ``` + + After: + + ```ruby + IPAddr.new("127.0.0.1").to_json + # => "\"127.0.0.1\"" + ``` + + +## Rails 6.1.0 (December 09, 2020) ## + +* Ensure `MemoryStore` disables compression by default. Reverts behavior of + `MemoryStore` to its prior rails `5.1` behavior. + + *Max Gurewitz* + +* Calling `iso8601` on negative durations retains the negative sign on individual + digits instead of prepending it. + + This change is required so we can interoperate with PostgreSQL, which prefers + negative signs for each component. + + Compatibility with other iso8601 parsers which support leading negatives as well + as negatives per component is still retained. + + Before: + + (-1.year - 1.day).iso8601 + # => "-P1Y1D" + + After: + + (-1.year - 1.day).iso8601 + # => "P-1Y-1D" + + *Vipul A M* + +* Remove deprecated `ActiveSupport::Notifications::Instrumenter#end=`. + + *Rafael Mendonça França* + +* Deprecate `ActiveSupport::Multibyte::Unicode.default_normalization_form`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::Multibyte::Unicode.pack_graphemes`, + `ActiveSupport::Multibyte::Unicode.unpack_graphemes`, + `ActiveSupport::Multibyte::Unicode.normalize`, + `ActiveSupport::Multibyte::Unicode.downcase`, + `ActiveSupport::Multibyte::Unicode.upcase` and `ActiveSupport::Multibyte::Unicode.swapcase`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::Multibyte::Chars#consumes?` and `ActiveSupport::Multibyte::Chars#normalize`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/range/include_range`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/hash/transform_values`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/hash/compact`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/array/prepend_and_append`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/numeric/inquiry`. + + *Rafael Mendonça França* + +* Remove deprecated file `active_support/core_ext/module/reachable`. + + *Rafael Mendonça França* + +* Remove deprecated `Module#parent_name`, `Module#parent` and `Module#parents`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::LoggerThreadSafeLevel#after_initialize`. + + *Rafael Mendonça França* + +* Remove deprecated `LoggerSilence` constant. + + *Rafael Mendonça França* + +* Remove deprecated fallback to `I18n.default_local` when `config.i18n.fallbacks` is empty. + + *Rafael Mendonça França* + +* Remove entries from local cache on `RedisCacheStore#delete_matched` + + Fixes #38627 + + *ojab* + +* Speed up `ActiveSupport::SecurityUtils.fixed_length_secure_compare` by using + `OpenSSL.fixed_length_secure_compare`, if available. + + *Nate Matykiewicz* + +* `ActiveSupport::Cache::MemCacheStore` now checks `ENV["MEMCACHE_SERVERS"]` before falling back to `"localhost:11211"` if configured without any addresses. + + ```ruby + config.cache_store = :mem_cache_store + + # is now equivalent to + + config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211" + + # instead of + + config.cache_store = :mem_cache_store, "localhost:11211" # ignores ENV["MEMCACHE_SERVERS"] + ``` + + *Sam Bostock* + +* `ActiveSupport::Subscriber#attach_to` now accepts an `inherit_all:` argument. When set to true, + it allows a subscriber to receive events for methods defined in the subscriber's ancestor class(es). + + ```ruby + class ActionControllerSubscriber < ActiveSupport::Subscriber + attach_to :action_controller + + def start_processing(event) + info "Processing by #{event.payload[:controller]}##{event.payload[:action]} as #{format}" + end + + def redirect_to(event) + info { "Redirected to #{event.payload[:location]}" } + end + end + + # We detach ActionControllerSubscriber from the :action_controller namespace so that our CustomActionControllerSubscriber + # can provide its own instrumentation for certain events in the namespace + ActionControllerSubscriber.detach_from(:action_controller) + + class CustomActionControllerSubscriber < ActionControllerSubscriber + attach_to :action_controller, inherit_all: true + + def start_processing(event) + info "A custom response to start_processing events" + end + + # => CustomActionControllerSubscriber will process events for "start_processing.action_controller" notifications + # using its own #start_processing implementation, while retaining ActionControllerSubscriber's instrumentation + # for "redirect_to.action_controller" notifications + end + ``` + + *Adrianna Chang* + +* Allow the digest class used to generate non-sensitive digests to be configured with `config.active_support.hash_digest_class`. + + `config.active_support.use_sha1_digests` is deprecated in favour of `config.active_support.hash_digest_class = ::Digest::SHA1`. + + *Dirkjan Bussink* + +* Fix bug to make memcached write_entry expire correctly with unless_exist + + *Jye Lee* + +* Add `ActiveSupport::Duration` conversion methods + + `in_seconds`, `in_minutes`, `in_hours`, `in_days`, `in_weeks`, `in_months`, and `in_years` return the respective duration covered. + + *Jason York* + +* Fixed issue in `ActiveSupport::Cache::RedisCacheStore` not passing options + to `read_multi` causing `fetch_multi` to not work properly + + *Rajesh Sharma* + +* Fixed issue in `ActiveSupport::Cache::MemCacheStore` which caused duplicate compression, + and caused the provided `compression_threshold` to not be respected. + + *Max Gurewitz* + +* Prevent `RedisCacheStore` and `MemCacheStore` from performing compression + when reading entries written with `raw: true`. + + *Max Gurewitz* + +* `URI.parser` is deprecated and will be removed in Rails 7.0. Use + `URI::DEFAULT_PARSER` instead. + + *Jean Boussier* + +* `require_dependency` has been documented to be _obsolete_ in `:zeitwerk` + mode. The method is not deprecated as such (yet), but applications are + encouraged to not use it. + + In `:zeitwerk` mode, semantics match Ruby's and you do not need to be + defensive with load order. Just refer to classes and modules normally. If + the constant name is dynamic, camelize if needed, and constantize. + + *Xavier Noria* + +* Add 3rd person aliases of `Symbol#start_with?` and `Symbol#end_with?`. + + ```ruby + :foo.starts_with?("f") # => true + :foo.ends_with?("o") # => true + ``` + + *Ryuta Kamizono* + +* Add override of unary plus for `ActiveSupport::Duration`. + + `+ 1.second` is now identical to `+1.second` to prevent errors + where a seemingly innocent change of formatting leads to a change in the code behavior. + + Before: + ```ruby + +1.second.class + # => ActiveSupport::Duration + (+ 1.second).class + # => Integer + ``` + + After: + ```ruby + +1.second.class + # => ActiveSupport::Duration + (+ 1.second).class + # => ActiveSupport::Duration + ``` + + Fixes #39079. + + *Roman Kushnir* + +* Add subsec to `ActiveSupport::TimeWithZone#inspect`. + + Before: + + Time.at(1498099140).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00 UTC +00:00" + + After: + + Time.at(1498099140).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.000000000 UTC +00:00" + Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.123456780 UTC +00:00" + Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect + # => "Thu, 22 Jun 2017 02:39:00.333333333 UTC +00:00" + + *akinomaeni* + +* Calling `ActiveSupport::TaggedLogging#tagged` without a block now returns a tagged logger. + + ```ruby + logger.tagged("BCX").info("Funky time!") # => [BCX] Funky time! + ``` + + *Eugene Kenny* + +* Align `Range#cover?` extension behavior with Ruby behavior for backwards ranges. + + `(1..10).cover?(5..3)` now returns `false`, as it does in plain Ruby. + + Also update `#include?` and `#===` behavior to match. + + *Michael Groeneman* + +* Update to TZInfo v2.0.0. + + This changes the output of `ActiveSupport::TimeZone.utc_to_local`, but + can be controlled with the + `ActiveSupport.utc_to_local_returns_utc_offset_times` config. + + New Rails 6.1 apps have it enabled by default, existing apps can upgrade + via the config in config/initializers/new_framework_defaults_6_1.rb + + See the `utc_to_local_returns_utc_offset_times` documentation for details. + + *Phil Ross*, *Jared Beck* + +* Add Date and Time `#yesterday?` and `#tomorrow?` alongside `#today?`. + + Aliased to `#prev_day?` and `#next_day?` to match the existing `#prev/next_day` methods. + + *Jatin Dhankhar* + +* Add `Enumerable#pick` to complement `ActiveRecord::Relation#pick`. + + *Eugene Kenny* + +* [Breaking change] `ActiveSupport::Callbacks#halted_callback_hook` now receive a 2nd argument: + + `ActiveSupport::Callbacks#halted_callback_hook` now receive the name of the callback + being halted as second argument. + This change will allow you to differentiate which callbacks halted the chain + and act accordingly. + + ```ruby + class Book < ApplicationRecord + before_save { throw(:abort) } + before_create { throw(:abort) } + + def halted_callback_hook(filter, callback_name) + Rails.logger.info("Book couldn't be #{callback_name}d") + end + + Book.create # => "Book couldn't be created" + book.save # => "Book couldn't be saved" + end + ``` + + *Edouard Chin* + +* Support `prepend` with `ActiveSupport::Concern`. + + Allows a module with `extend ActiveSupport::Concern` to be prepended. + + module Imposter + extend ActiveSupport::Concern + + # Same as `included`, except only run when prepended. + prepended do + end + end + + class Person + prepend Imposter + end + + Class methods are prepended to the base class, concerning is also + updated: `concerning :Imposter, prepend: true do`. + + *Jason Karns*, *Elia Schito* + +* Deprecate using `Range#include?` method to check the inclusion of a value + in a date time range. It is recommended to use `Range#cover?` method + instead of `Range#include?` to check the inclusion of a value + in a date time range. + + *Vishal Telangre* + +* Support added for a `round_mode` parameter, in all number helpers. (See: `BigDecimal::mode`.) + + ```ruby + number_to_currency(1234567890.50, precision: 0, round_mode: :half_down) # => "$1,234,567,890" + number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%" + number_to_rounded(389.32314, precision: 0, round_mode: :ceil) # => "390" + number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB" + number_to_human(489939, precision: 2, round_mode: :floor) # => "480 Thousand" + + 485000.to_s(:human, precision: 2, round_mode: :half_even) # => "480 Thousand" + ``` + + *Tom Lord* + +* `Array#to_sentence` no longer returns a frozen string. + + Before: + + ['one', 'two'].to_sentence.frozen? + # => true + + After: + + ['one', 'two'].to_sentence.frozen? + # => false + + *Nicolas Dular* + +* When an instance of `ActiveSupport::Duration` is converted to an `iso8601` duration string, if `weeks` are mixed with `date` parts, the `week` part will be converted to days. + This keeps the parser and serializer on the same page. + + ```ruby + duration = ActiveSupport::Duration.build(1000000) + # 1 week, 4 days, 13 hours, 46 minutes, and 40.0 seconds + + duration_iso = duration.iso8601 + # P11DT13H46M40S + + ActiveSupport::Duration.parse(duration_iso) + # 11 days, 13 hours, 46 minutes, and 40 seconds + + duration = ActiveSupport::Duration.build(604800) + # 1 week + + duration_iso = duration.iso8601 + # P1W + + ActiveSupport::Duration.parse(duration_iso) + # 1 week + ``` + + *Abhishek Sarkar* + +* Add block support to `ActiveSupport::Testing::TimeHelpers#travel_back`. + + *Tim Masliuchenko* + +* Update `ActiveSupport::Messages::Metadata#fresh?` to work for cookies with expiry set when + `ActiveSupport.parse_json_times = true`. + + *Christian Gregg* + +* Support symbolic links for `content_path` in `ActiveSupport::EncryptedFile`. + + *Takumi Shotoku* + +* Improve `Range#===`, `Range#include?`, and `Range#cover?` to work with beginless (startless) + and endless range targets. + + *Allen Hsu*, *Andrew Hodgkinson* + +* Don't use `Process#clock_gettime(CLOCK_THREAD_CPUTIME_ID)` on Solaris. + + *Iain Beeston* + +* Prevent `ActiveSupport::Duration.build(value)` from creating instances of + `ActiveSupport::Duration` unless `value` is of type `Numeric`. + + Addresses the errant set of behaviours described in #37012 where + `ActiveSupport::Duration` comparisons would fail confusingly + or return unexpected results when comparing durations built from instances of `String`. + + Before: + + small_duration_from_string = ActiveSupport::Duration.build('9') + large_duration_from_string = ActiveSupport::Duration.build('100000000000000') + small_duration_from_int = ActiveSupport::Duration.build(9) + + large_duration_from_string > small_duration_from_string + # => false + + small_duration_from_string == small_duration_from_int + # => false + + small_duration_from_int < large_duration_from_string + # => ArgumentError (comparison of ActiveSupport::Duration::Scalar with ActiveSupport::Duration failed) + + large_duration_from_string > small_duration_from_int + # => ArgumentError (comparison of String with ActiveSupport::Duration failed) + + After: + + small_duration_from_string = ActiveSupport::Duration.build('9') + # => TypeError (can't build an ActiveSupport::Duration from a String) + + *Alexei Emam* + +* Add `ActiveSupport::Cache::Store#delete_multi` method to delete multiple keys from the cache store. + + *Peter Zhu* + +* Support multiple arguments in `HashWithIndifferentAccess` for `merge` and `update` methods, to + follow Ruby 2.6 addition. + + *Wojciech Wnętrzak* + +* Allow initializing `thread_mattr_*` attributes via `:default` option. + + class Scraper + thread_mattr_reader :client, default: Api::Client.new + end + + *Guilherme Mansur* + +* Add `compact_blank` for those times when you want to remove #blank? values from + an Enumerable (also `compact_blank!` on Hash, Array, ActionController::Parameters). + + *Dana Sherson* + +* Make ActiveSupport::Logger Fiber-safe. + + Use `Fiber.current.__id__` in `ActiveSupport::Logger#local_level=` in order + to make log level local to Ruby Fibers in addition to Threads. + + Example: + + logger = ActiveSupport::Logger.new(STDOUT) + logger.level = 1 + puts "Main is debug? #{logger.debug?}" + + Fiber.new { + logger.local_level = 0 + puts "Thread is debug? #{logger.debug?}" + }.resume + + puts "Main is debug? #{logger.debug?}" + + Before: + + Main is debug? false + Thread is debug? true + Main is debug? true + + After: + + Main is debug? false + Thread is debug? true + Main is debug? false + + Fixes #36752. + + *Alexander Varnin* + +* Allow the `on_rotation` proc used when decrypting/verifying a message to be + passed at the constructor level. + + Before: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret') + crypt.decrypt_and_verify(encrypted_message, on_rotation: proc { ... }) + crypt.decrypt_and_verify(another_encrypted_message, on_rotation: proc { ... }) + + After: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... }) + crypt.decrypt_and_verify(encrypted_message) + crypt.decrypt_and_verify(another_encrypted_message) + + *Edouard Chin* + +* `delegate_missing_to` would raise a `DelegationError` if the object + delegated to was `nil`. Now the `allow_nil` option has been added to enable + the user to specify they want `nil` returned in this case. + + *Matthew Tanous* + +* `truncate` would return the original string if it was too short to be truncated + and a frozen string if it were long enough to be truncated. Now truncate will + consistently return an unfrozen string regardless. This behavior is consistent + with `gsub` and `strip`. + + Before: + + 'foobar'.truncate(5).frozen? + # => true + 'foobar'.truncate(6).frozen? + # => false + + After: + + 'foobar'.truncate(5).frozen? + # => false + 'foobar'.truncate(6).frozen? + # => false + + *Jordan Thomas* + + +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/MIT-LICENSE new file mode 100644 index 0000000000..0a0ce3889a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005-2022 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.0.0/gems/activesupport-6.1.7.2/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/README.rdoc new file mode 100644 index 0000000000..c2df6d73b2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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://edgeguides.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.0.0/gems/activesupport-6.1.7.2/lib/active_support.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support.rb new file mode 100644 index 0000000000..f6e26a8dfb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2005-2022 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/logger" +require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + extend ActiveSupport::Autoload + + autoload :Concern + autoload :ActionableError + autoload :ConfigurationFile + autoload :CurrentAttributes + autoload :Dependencies + autoload :DescendantsTracker + autoload :ExecutionWrapper + autoload :Executor + autoload :FileUpdateChecker + autoload :EventedFileUpdateChecker + autoload :ForkTracker + autoload :LogSubscriber + autoload :Notifications + autoload :Reloader + autoload :SecureCompareRotator + + eager_autoload do + autoload :BacktraceCleaner + autoload :ProxyObject + autoload :Benchmarkable + autoload :Cache + autoload :Callbacks + autoload :Configurable + autoload :Deprecation + autoload :Digest + autoload :Gzip + autoload :Inflector + autoload :JSON + autoload :KeyGenerator + autoload :MessageEncryptor + autoload :MessageVerifier + 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: + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/actionable_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/actionable_error.rb new file mode 100644 index 0000000000..7db14cd178 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/actionable_error.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + # Actionable errors let's 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/all.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/all.rb new file mode 100644 index 0000000000..4adf446af8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/array_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/array_inquirer.rb new file mode 100644 index 0000000000..0cbba0b6df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/array_inquirer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" + +module ActiveSupport + # 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, *args) + if name.end_with?("?") + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/backtrace_cleaner.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/backtrace_cleaner.rb new file mode 100644 index 0000000000..03c7dc75d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/backtrace_cleaner.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module ActiveSupport + # 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 + # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # 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_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 + + # 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" + # backtrace_cleaner.add_filter { |line| line.gsub(Rails.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 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_gem_silencer + add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) } + end + + def add_stdlib_silencer + add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) } + end + + def filter_backtrace(backtrace) + @filters.each do |f| + backtrace = backtrace.map { |line| f.call(line) } + end + + backtrace + end + + def silence(backtrace) + @silencers.each do |s| + backtrace = backtrace.reject { |line| s.call(line) } + end + + backtrace + end + + def noise(backtrace) + backtrace.select do |line| + @silencers.any? do |s| + s.call(line) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/benchmarkable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/benchmarkable.rb new file mode 100644 index 0000000000..abd0d59d9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/benchmarkable.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" + +module ActiveSupport + 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 = {}) + if logger + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + + result = nil + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ]) + result + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/builder.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/builder.rb new file mode 100644 index 0000000000..3fa7e6b26d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/builder.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +begin + require "builder" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache.rb new file mode 100644 index 0000000000..ecc6085348 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache.rb @@ -0,0 +1,878 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +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/numeric/time" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" +require "active_support/core_ext/string/inflections" + +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 = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder] + + module Strategy + autoload :LocalCache, "active_support/cache/strategy/local_cache" + end + + class << self + # 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! + # clean this up once Ruby 2.7 support is dropped + # see https://github.com/rails/rails/pull/41522#discussion_r581186602 + if options.empty? + retrieve_store_class(store).new(*parameters) + else + retrieve_store_class(store).new(*parameters, **options) + end + 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 + + # 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 serializable Ruby object. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # + # cache.read('city') # => nil + # cache.write('city', "Duckburgh") + # cache.read('city') # => "Duckburgh" + # + # 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 + # + # Cached data larger than 1kB are compressed by default. To turn off + # compression, pass compress: false to the initializer or to + # individual +fetch+ or +write+ method calls. The 1kB compression + # threshold is configurable with the :compress_threshold option, + # specified in bytes. + class Store + DEFAULT_CODER = Marshal + + cattr_accessor :logger, instance_writer: true + + attr_reader :silence, :options + alias :silence? :silence + + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + + # Creates a new cache. The options will be passed to any write method calls + # except for :namespace which can be used to set the global + # namespace for the cache. + def initialize(options = nil) + @options = options ? options.dup : {} + @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder + end + + # Silences the logger. + def silence! + @silence = true + self + end + + # Silences the logger within a block. + def mute + previous_silence, @silence = defined?(@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" + # + # You may also specify additional options via the +options+ argument. + # Setting 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 Cache#write. + # + # Setting skip_nil: true will not cache nil result: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + # + # + # Setting compress: false disables compression of the cache entry. + # + # Setting :expires_in will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. + # + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) + # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry + # + # Setting :version verifies the cache stored under name + # is of the same version. nil is returned on mismatches despite contents. + # This feature is used to support recyclable cache keys. + # + # Setting :race_condition_ttl is very useful in situations where + # a cache entry is used very frequently and is under heavy load. If a + # cache expires and due to heavy load several different processes will try + # to read data natively and then they all will try to write to cache. To + # avoid that case the first process to find an expired cache entry will + # bump the cache expiration time by the value set in :race_condition_ttl. + # Yes, this process is extending the time for a stale value by another few + # seconds. Because of extended life of the previous cache, other processes + # will continue to use slightly stale data for a just a bit longer. In the + # meantime that first process will go ahead and will write into cache the + # new value. After that all the processes will start getting the new value. + # The key is to keep :race_condition_ttl small. + # + # If the process regenerating the entry errors out, the entry will be + # regenerated after the specified number of seconds. Also note that the + # life of stale cache is extended only if it expired recently. Otherwise + # a new value is generated and :race_condition_ttl does not play + # any role. + # + # # Set all values to expire after one minute. + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) + # + # cache.write('foo', 'original value') + # val_1 = nil + # val_2 = nil + # sleep 60 + # + # Thread.new do + # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # sleep 1 + # 'new value 1' + # end + # end + # + # Thread.new do + # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # 'new value 2' + # end + # end + # + # cache.fetch('foo') # => "original value" + # sleep 10 # First thread extended the life of cache by another 10 seconds + # cache.fetch('foo') # => "new value 1" + # val_1 # => "new value 1" + # val_2 # => "original value" + # + # Other options will be handled by the specific cache store implementation. + # Internally, #fetch calls #read_entry, and calls #write_entry on a cache + # miss. +options+ will be passed to the #read and #write calls. + # + # For example, MemCacheStore's #write method supports the +:raw+ + # option, which tells the memcached server to store all values as strings. + # We can use this option with #fetch too: + # + # cache = ActiveSupport::Cache::MemCacheStore.new + # cache.fetch("foo", force: true, raw: true) do + # :bar + # end + # cache.fetch('foo') # => "bar" + def fetch(name, options = nil, &block) + if block_given? + options = merged_options(options) + key = normalize_key(name, options) + + entry = nil + instrument(:read, name, options) do |payload| + cached_entry = read_entry(key, **options, event: payload) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) + entry = nil if entry && entry.mismatched?(normalize_version(name, options)) + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end + + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, 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 are passed to the underlying cache implementation. + def read(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + instrument(:read, name, 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 + entry.value + 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) + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + read_multi_entries(names, **options, event: payload).tap do |results| + payload[:hits] = results.keys + end + end + end + + # Cache Storage API to write multiple values at once. + def write_multi(hash, options = nil) + options = merged_options(options) + + instrument :write_multi, 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" } + # + # 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? + + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + reads = read_multi_entries(names, **options) + writes = {} + ordered = names.index_with do |name| + reads.fetch(name) { writes[name] = yield(name) } + end + + payload[:hits] = reads.keys + payload[:super_operation] = :fetch_multi + + write_multi(writes, options) + + ordered + end + end + + # Writes the value to the cache, with the key. + # + # Options are passed to the underlying cache implementation. + def write(name, value, options = nil) + options = merged_options(options) + + instrument(:write, name, options) do + entry = Entry.new(value, **options.merge(version: normalize_version(name, options))) + write_entry(normalize_key(name, options), entry, **options) + end + end + + # Deletes an entry in the cache. Returns +true+ if an entry is deleted. + # + # Options are passed to the underlying cache implementation. + def delete(name, options = nil) + options = merged_options(options) + + instrument(:delete, name) do + delete_entry(normalize_key(name, options), **options) + end + end + + # Deletes multiple entries in the cache. + # + # Options are passed to the underlying cache implementation. + def delete_multi(names, options = nil) + options = merged_options(options) + names.map! { |key| normalize_key(key, options) } + + instrument :delete_multi, names 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) + + instrument(:exist?, name) do |payload| + entry = read_entry(normalize_key(name, options), **options, event: payload) + (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false + end + 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 + + # Cleanups 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 + # 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) + @coder.dump(entry) + end + + def deserialize_entry(payload) + payload.nil? ? nil : @coder.load(payload) + 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 + if options.empty? + call_options + else + options.merge(call_options) + end + else + options + end + end + + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(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, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + 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) + if logger && logger.debug? && !silence? + logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" + end + + payload = { key: key, store: self.class.name } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } + 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 + race_ttl + write_entry(key, entry, 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, options) + result = instrument(:generate, name, options) do + yield(name) + end + + write(name, result, options) unless result.nil? && options[:skip_nil] + result + end + end + + module NullCoder # :nodoc: + class << self + def load(payload) + payload + end + + def dump(entry) + entry + end + end + end + + # 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: + attr_reader :version + + DEFAULT_COMPRESS_LIMIT = 1.kilobyte + + # Creates a new cache entry for the specified value. Options supported are + # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+. + def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **) + @value = value + @version = version + @created_at = Time.now.to_f + @expires_in = expires_in && expires_in.to_f + + compress!(compress_threshold) if compress + 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 + + # 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 + + private + def compress!(compress_threshold) + 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 + @value = compressed + @compressed = true + end + end + end + + def compressed? + defined?(@compressed) + end + + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/file_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/file_store.rb new file mode 100644 index 0000000000..2ab632a3a5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/file_store.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require "active_support/core_ext/marshal" +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" + +module ActiveSupport + module Cache + # A cache store implementation which stores everything on the filesystem. + # + # FileStore implements the Strategy::LocalCache strategy which implements + # an in-memory cache inside of a block. + class FileStore < Store + prepend Strategy::LocalCache + 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 + + # Increments an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrements an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + search_dir(cache_path) do |path| + key = file_path_key(path) + delete_entry(path, **options) if key.match(matcher) + end + end + end + + private + def read_entry(key, **options) + if File.exist?(key) + entry = File.open(key) { |f| deserialize_entry(f.read) } + entry if entry.is_a?(Cache::Entry) + end + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger + nil + end + + def write_entry(key, entry, **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(serialize_entry(entry)) } + true + end + + def delete_entry(key, **options) + if File.exist?(key) + begin + File.delete(key) + delete_empty_directories(File.dirname(key)) + true + rescue => e + # Just in case the error was caused by another process deleting the file first. + raise e if File.exist?(key) + false + end + 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 + 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 already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def modify_value(name, amount, options) + file_name = normalize_key(name, options) + + lock_file(file_name) do + options = merged_options(options) + + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/mem_cache_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/mem_cache_store.rb new file mode 100644 index 0000000000..bb04a94cbe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/mem_cache_store.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +begin + require "dalli" +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/marshal" +require "active_support/core_ext/array/extract_options" + +module ActiveSupport + module Cache + # 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 + DEFAULT_CODER = NullCoder # Dalli automatically Marshal values + + # Provide support for raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def write_entry(key, entry, **options) + if options[:raw] && local_cache + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, **options) + else + super + end + end + end + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + 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.empty? + Dalli::Client.new(addresses, options) + else + ensure_connection_pool_added! + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + 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! + super(options) + + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." + end + if addresses.first.is_a?(Dalli::Client) + @data = addresses.first + else + mem_cache_options = options.dup + UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) } + @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) + end + end + + # Increment a cached value. This method uses the memcached incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:increment, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Decrement a cached value. This method uses the memcached decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:decrement, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } + 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) + rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) } + end + + # Write an entry to the cache. + def write_entry(key, entry, **options) + method = options[:unless_exist] ? :add : :set + value = options[:raw] ? entry.value.to_s : serialize_entry(entry) + 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 false do + # The value "compress: false" prevents duplicate compression within Dalli. + @data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) } + 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 = @data.with { |c| c.get_multi(keys_to_names.keys) } + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value) + + unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + values[keys_to_names[key]] = entry.value + 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 + + # 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}" } + key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 + end + + key + end + + def deserialize_entry(payload) + entry = super + entry = Entry.new(entry, compress: false) unless entry.nil? || entry.is_a?(Entry) + entry + end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + fallback + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/memory_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/memory_store.rb new file mode 100644 index 0000000000..4bb89934e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/memory_store.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Cache + # 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: + class << self + def load(entry) + entry = entry.dup + entry.dup_value! + entry + end + + def dump(entry) + entry.dup_value! + entry + end + end + end + + DEFAULT_CODER = DupCoder + + def initialize(options = nil) + options ||= {} + # 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 = Concurrent.monotonic_time + 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 && Concurrent.monotonic_time - 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 an integer value in the cache. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrement an integer value in the cache. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + # Deletes cache entries if the cache key matches a given pattern. + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + 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) + synchronize do + return false if options[:unless_exist] && @data.key?(key) + + 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 + + def modify_value(name, amount, options) + options = merged_options(options) + synchronize do + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/null_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/null_store.rb new file mode 100644 index 0000000000..5a6b97b853 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/null_store.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + module Cache + # 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 = nil) + end + + def decrement(name, amount = 1, options = nil) + end + + def delete_matched(matcher, options = nil) + end + + private + def read_entry(key, **options) + end + + def write_entry(key, entry, **options) + true + end + + def delete_entry(key, **options) + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/redis_cache_store.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 0000000000..1afecb3fb7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,493 @@ +# 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\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + require "redis/connection/hiredis" +rescue LoadError +end + +require "digest/sha2" +require "active_support/core_ext/marshal" + +module ActiveSupport + module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It 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 their own SHA2 digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + 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 + + # Support raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def write_entry(key, entry, **options) + if options[:raw] && local_cache + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, **options) + else + super + end + end + + def write_multi_entries(entries, **options) + if options[:raw] && local_cache + raw_entries = entries.map do |key, entry| + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + end.to_h + + super(raw_entries, **options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + 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 + 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(url:, **redis_options) + ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # 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: … }, …]) + # + # 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. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: DEFAULT_CODER, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl, + coder: coder + end + + def redis + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end + end + + def inspect + instance = @redis || @redis_options + "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end + else + super + 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) + instrument :delete_matched, matcher do + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + redis.with do |c| + pattern = namespace_key(matcher, options) + 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 + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + instrument :increment, name, amount: amount do + failsafe :increment do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.incrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + end + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + instrument :decrement, name, amount: amount do + failsafe :decrement do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.decrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + 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.with { |c| c.flushdb } + end + end + end + + def mget_capable? #:nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? #:nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, **options) + failsafe :read_entry do + raw = options&.fetch(:raw, false) + deserialize_entry(redis.with { |c| c.get(key) }, raw: raw) + end + end + + def read_multi_entries(names, **options) + if mget_capable? + read_multi_mget(*names, **options) + else + super + end + end + + def read_multi_mget(*names) + options = names.extract_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_mget, returning: {}) do + redis.with { |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)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) + serialized_entry = serialize_entry(entry, raw: raw) + + # 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 + + failsafe :write_entry, returning: false do + if unless_exist || expires_in + modifiers = {} + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + + redis.with { |c| c.set key, serialized_entry, **modifiers } + else + redis.with { |c| c.set key, serialized_entry } + end + end + end + + def write_key_expiry(client, key, options) + if options[:expires_in] && client.ttl(key).negative? + client.expire key, options[:expires_in].to_i + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.with { |c| c.del key } + end + end + + # Deletes multiple entries in the cache. Returns the number of entries deleted. + def delete_multi_entries(entries, **_options) + redis.with { |c| c.del(entries) } + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) } + end + else + super + 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 = ":sha2:#{::Digest::SHA2.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(payload, raw:) + if payload && raw + Entry.new(payload, compress: false) + else + super(payload) + end + end + + def serialize_entry(entry, raw: false) + if raw + entry.value.to_s + else + super(entry) + end + end + + def serialize_entries(entries, raw: false) + entries.transform_values do |entry| + serialize_entry entry, raw: raw + end + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseError => e + handle_exception exception: e, method: method, returning: returning + returning + end + + def handle_exception(exception:, method:, returning:) + if @error_handler + @error_handler.(method: method, exception: exception, returning: returning) + end + rescue => failsafe + warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/strategy/local_cache.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/strategy/local_cache.rb new file mode 100644 index 0000000000..96b4e1dfb7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/strategy/local_cache.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" +require "active_support/per_thread_registry" + +module ActiveSupport + module Cache + module 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. + class LocalCacheRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def cache_for(local_cache_key) + @registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + @registry[local_cache_key] = value + end + + def self.set_cache_for(l, v); instance.set_cache_for l, v; end + def self.cache_for(l); instance.cache_for l; end + end + + # 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 < Store + def initialize + super + @data = {} + end + + # Don't allow synchronizing since it isn't thread safe. + def synchronize # :nodoc: + yield + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key, **options) + @data[key] + end + + def read_multi_entries(keys, **options) + values = {} + + keys.each do |name| + entry = read_entry(name, **options) + values[name] = entry.value if entry + end + + values + end + + def write_entry(key, entry, **options) + entry.dup_value! + @data[key] = entry + true + end + + def delete_entry(key, **options) + !!@data.delete(key) + end + + def fetch_entry(key, options = nil) # :nodoc: + entry = @data.fetch(key) { @data[key] = yield } + dup_entry = entry.dup + dup_entry&.dup_value! + dup_entry + end + end + + # Use a local cache for the duration of block. + def with_local_cache + use_temporary_local_cache(LocalStore.new) { yield } + 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) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def cleanup(**options) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def delete_matched(matcher, options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def increment(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, **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, **options) + value + end + + private + def read_entry(key, **options) + if cache = local_cache + hit = true + value = cache.fetch_entry(key) do + hit = false + super + end + options[:event][:store] = cache.class.name if hit && options[:event] + value + else + super + end + end + + def read_multi_entries(keys, **options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys, **options) + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, **options)) + else + local_entries + end + end + + def write_entry(key, entry, **options) + if options[:unless_exist] + local_cache.delete_entry(key, **options) if local_cache + else + local_cache.write_entry(key, entry, **options) if local_cache + end + + super + end + + def delete_entry(key, **options) + local_cache.delete_entry(key, **options) if local_cache + super + end + + def write_cache_value(name, value, **options) + name = normalize_key(name, options) + cache = local_cache + cache.mute do + if value + cache.write(name, value, options) + else + cache.delete(name, **options) + end + 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 + use_temporary_local_cache(nil) { yield } + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/strategy/local_cache_middleware.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/cache/strategy/local_cache_middleware.rb new file mode 100644 index 0000000000..62542bdb22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/callbacks.rb new file mode 100644 index 0000000000..1a55f8af35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/callbacks.rb @@ -0,0 +1,862 @@ +# 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 "thread" + +module ActiveSupport + # 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, default: {} + end + + CALLBACK_FILTER_TYPES = [:before, :after, :around] + + # 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) + callbacks = __callbacks[kind.to_sym] + + if callbacks.empty? + yield if block_given? + else + env = Filters::Environment.new(self, false, nil) + next_sequence = callbacks.compile + + # 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: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + + module Filters + Environment = Struct.new(:target, :halted, :value) + + class Before + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name) + halted_lambda = chain_config[:terminator] + + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + else + halting(callback_sequence, user_callback, halted_lambda, filter, name) + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + callback_sequence.before do |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 + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback, halted_lambda, filter, name) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + unless halted + 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 + end + private_class_method :halting + end + + class After + def self.build(callback_sequence, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions) + else + halting(callback_sequence, user_callback) + end + else + if user_conditions.any? + conditional callback_sequence, user_callback, user_conditions + else + simple callback_sequence, user_callback + end + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| + unless env.halted + user_callback.call env.target, env.value + end + + env + end + end + private_class_method :halting + + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :conditional + + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| + user_callback.call env.target, env.value + + env + end + end + private_class_method :simple + 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 + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @key = compute_identifier filter + @if = check_conditionals(options[:if]) + @unless = check_conditionals(options[:unless]) + end + + def filter; @key; end + def raw_filter; @filter; 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 + + # Wraps code with filter + def apply(callback_sequence) + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name) + when :after + Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config) + when :around + callback_sequence.around(user_callback, user_conditions) + end + 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? { |c| c.is_a?(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 compute_identifier(filter) + case filter + when ::Proc + filter.object_id + else + filter + end + end + + def conditions_lambdas + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + end + end + + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + class CallTemplate # :nodoc: + def initialize(target, method, arguments, block) + @override_target = target + @method_name = method + @arguments = arguments + @override_block = block + 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) + expanded = [@override_target || target, @override_block || block, @method_name] + + @arguments.each do |arg| + case arg + when :value then expanded << value + when :target then expanded << target + when :block then expanded << (block || raise(ArgumentError)) + end + end + + expanded + end + + # Return a lambda that will make this call when given the input + # values. + def make_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + target.send(method, *arguments, &block) + end + end + + # Return a lambda that will make this call when given the input + # values, but then return the boolean inverse of that result. + def inverted_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + ! target.send(method, *arguments, &block) + 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 + new(nil, filter, [], nil) + when Conditionals::Value + new(filter, :call, [:target, :value], nil) + when ::Proc + if filter.arity > 1 + new(nil, :instance_exec, [:target, :block], filter) + elsif filter.arity > 0 + new(nil, :instance_exec, [:target], filter) + else + new(nil, :instance_exec, [], filter) + end + else + method_to_call = callback.current_scopes.join("_") + + new(filter, method_to_call, [:target], nil) + 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 = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def 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 = [] + @callbacks = nil + @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) + @callbacks = nil + @chain.insert(index, o) + end + + def delete(o) + @callbacks = nil + @chain.delete(o) + end + + def clear + @callbacks = nil + @chain.clear + self + end + + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile + @callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence + 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) + @callbacks = nil + remove_duplicates(callback) + @chain.push(callback) + end + + def prepend_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.unshift(callback) + end + + def remove_duplicates(callback) + @callbacks = nil + @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] + ActiveSupport::DescendantsTracker.descendants(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. + # + # class Writer < Person + # skip_callback :validate, :before, :check_membership, if: -> { age > 18 } + # end + # + # 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 + + ActiveSupport::DescendantsTracker.descendants(self).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] + ActiveSupport::DescendantsTracker.descendants(self)).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 + + if Module.instance_method(:method_defined?).arity == 1 # Ruby 2.5 and older + def set_callbacks(name, callbacks) # :nodoc: + self.__callbacks = __callbacks.merge(name.to_sym => callbacks) + end + else # Ruby 2.6 and newer + def set_callbacks(name, callbacks) # :nodoc: + unless singleton_class.method_defined?(:__callbacks, false) + self.__callbacks = __callbacks.dup + end + self.__callbacks[name.to_sym] = callbacks + self.__callbacks + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concern.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concern.rb new file mode 100644 index 0000000000..e88862bfb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concern.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +module ActiveSupport + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..480c34c640 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + 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 + Thread.handle_interrupt(EXCEPTION_NEVER) do + mon_enter + + begin + Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do + yield + end + ensure + mon_exit + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/share_lock.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 0000000000..eae7d4469f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require "thread" +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) + @sleeping[Thread.current] = method + @cv.wait_while { yield } + ensure + @sleeping.delete Thread.current + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/configurable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/configurable.rb new file mode 100644 index 0000000000..92bd411df5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/configurable.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/ordered_options" + +module ActiveSupport + # 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 a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.hair_colors # => [:brown, :black, :blonde, :red] + def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :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}=", yield) if block_given? + end + end + private :config_accessor + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/configuration_file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/configuration_file.rb new file mode 100644 index 0000000000..024f0e0bda --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/configuration_file.rb @@ -0,0 +1,51 @@ +# 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 = render(context) + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(source, **options) || {} + else + YAML.load(source, **options) || {} + 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" + require "erb" + + File.read(content_path).tap do |content| + if content.include?("\u00A0") + warn "File contains invisible non-breaking spaces, you may want to remove those" + end + end + end + + def render(context) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext.rb new file mode 100644 index 0000000000..3f5d08186e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array.rb new file mode 100644 index 0000000000..88b6567712 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/access.rb new file mode 100644 index 0000000000..ea01e5891c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/access.rb @@ -0,0 +1,104 @@ +# 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 for #excluding. + def without(*elements) + excluding(*elements) + end + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/conversions.rb new file mode 100644 index 0000000000..0780c4ed8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/conversions.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +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 the elements + # in arrays with two or more elements (default: ", "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more 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 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. + # + # Blog.all.to_formatted_s(:db) # => "1,2,3" + # Blog.none.to_formatted_s(:db) # => "null" + # [1,2].to_formatted_s # => "[1, 2]" + def to_formatted_s(format = :default) + case format + when :db + if empty? + "null" + else + collect(&:id).join(",") + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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? { |e| e.is_a?(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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/extract.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/extract.rb new file mode 100644 index 0000000000..cc5a8a3f88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/extract_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/extract_options.rb new file mode 100644 index 0000000000..8c7cb2e780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/grouping.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/grouping.rb new file mode 100644 index 0000000000..67e760bc4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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) + 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) { |slice| yield(slice) } + 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) + # 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 { |g| yield(g) } + 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) + arr = dup + result = [] + if block_given? + while (idx = arr.index { |i| yield i }) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/inquiry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 0000000000..92c61bf201 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 +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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/wrap.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/array/wrap.rb new file mode 100644 index 0000000000..d62f97edbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/benchmark.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/benchmark.rb new file mode 100644 index 0000000000..f6e1b72bcf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/benchmark.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "benchmark" + +class << Benchmark + # Benchmark realtime in milliseconds. + # + # Benchmark.realtime { User.all } + # # => 8.0e-05 + # + # Benchmark.ms { User.all } + # # => 0.074 + def ms(&block) + 1000 * realtime(&block) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/big_decimal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/big_decimal.rb new file mode 100644 index 0000000000..9e6a9d6331 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/big_decimal/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/big_decimal/conversions.rb new file mode 100644 index 0000000000..52bd229416 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class.rb new file mode 100644 index 0000000000..1c110fd07b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/attribute.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/attribute.rb new file mode 100644 index 0000000000..ec78845159 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/attribute.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +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 + + class_methods << <<~RUBY # In case the method exists and is not public + silence_redefinition_of_method def #{name} + end + RUBY + + methods << <<~RUBY if instance_reader + silence_redefinition_of_method def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end + RUBY + + class_methods << <<~RUBY + silence_redefinition_of_method def #{name}=(value) + redefine_method(:#{name}) { value } if singleton_class? + redefine_singleton_method(:#{name}) { value } + value + end + RUBY + + 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) + + attrs.each { |name| public_send("#{name}=", default) } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/attribute_accessors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/attribute_accessors.rb new file mode 100644 index 0000000000..a77354e153 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/subclasses.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/subclasses.rb new file mode 100644 index 0000000000..568b413516 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/class/subclasses.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +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 + ObjectSpace.each_object(singleton_class).reject do |k| + k.singleton_class? || k == self + end + end + + # Returns an array with the direct children of +self+. + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Bar; end + # + # Foo.subclasses # => [Bar] + def subclasses + descendants.select { |descendant| descendant.superclass == self } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000000..cce73f2db2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/acts_like.rb new file mode 100644 index 0000000000..c8077f3774 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 0000000000..e6271c79b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class Date #:nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/calculations.rb new file mode 100644 index 0000000000..d03a8d3997 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/calculations.rb @@ -0,0 +1,146 @@ +# 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 + Thread.current[: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) + Thread.current[: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. + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 0000000000..050a62bb31 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,97 @@ +# 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", + iso8601: lambda { |date| date.iso8601 } + } + + # Convert to a formatted string. See DATE_FORMATS for predefined formats. + # + # This method is aliased to to_s. + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_formatted_s(:db) # => "2007-11-10" + # date.to_s(:db) # => "2007-11-10" + # + # date.to_formatted_s(:short) # => "10 Nov" + # date.to_formatted_s(:number) # => "20071110" + # date.to_formatted_s(:long) # => "November 10, 2007" + # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" + # date.to_formatted_s(:rfc822) # => "10 Nov 2007" + # date.to_formatted_s(:iso8601) # => "2007-11-10" + # + # == Adding your own date formats to to_formatted_s + # 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_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date/zones.rb new file mode 100644 index 0000000000..2dcf97cff8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 0000000000..21cfeed557 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,364 @@ +# 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 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 0000000000..b40a0fabb1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +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_writer: false, default: false + + # Change the output of ActiveSupport::TimeZone.utc_to_local. + # + # When `true`, it returns local times with an 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_and_time/zones.rb new file mode 100644 index 0000000000..fb6a27cb27 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time.rb new file mode 100644 index 0000000000..790dbeec1b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/acts_like.rb new file mode 100644 index 0000000000..5dccdfe219 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 0000000000..a52c8bc150 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class DateTime #:nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/calculations.rb new file mode 100644 index 0000000000..bc670c3e76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/calculations.rb @@ -0,0 +1,211 @@ +# 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. + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 0000000000..7600a067cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/date_time/conversions.rb new file mode 100644 index 0000000000..231bf870a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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_s. + # + # === Examples + # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 + # + # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:number) # => "20071204000000" + # datetime.to_formatted_s(:short) # => "04 Dec 00:00" + # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" + # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" + # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" + # + # == Adding your own datetime formats to to_formatted_s + # 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_formatted_s(format = :default) + if formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) + alias_method :to_s, :to_formatted_s + + # 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_s(: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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/digest.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/digest.rb new file mode 100644 index 0000000000..ce1427e13a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/digest/uuid.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 0000000000..37f0a8e66f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "securerandom" +require "digest" + +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 Digest::MD5 generates version 3 UUIDs; 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, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + 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 Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/enumerable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 0000000000..97c918a71f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +module Enumerable + INDEX_WITH_DEFAULT = Object.new + private_constant :INDEX_WITH_DEFAULT + + # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements + # when we omit an identity. + + # :stopdoc: + + # We can't use Refinements here because Refinements with Module which will be prepended + # doesn't work well https://bugs.ruby-lang.org/issues/13446 + alias :_original_sum_with_required_identity :sum + private :_original_sum_with_required_identity + + # :startdoc: + + # Calculates a sum from the elements. + # + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum # => "foobar" + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum(identity) + else + inject(:+) || 0 + end + 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 = INDEX_WITH_DEFAULT) + if block_given? + result = {} + each { |elem| result[elem] = yield(elem) } + result + elsif default != INDEX_WITH_DEFAULT + result = {} + each { |elem| result[elem] = default } + result + else + to_enum(:index_with) { size if respond_to?(:size) } + 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 |element| + cnt += 1 if yield element + 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 for #excluding. + def without(*elements) + excluding(*elements) + end + + # 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, 2]) + # # => [2, 1] (or [1, 2]) + # + # 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 +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(identity = nil) + 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 = identity || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity || 0 + end + end + end +end + +# Using Refinements here in order not to expose our internal method +using Module.new { + refine Array do + alias :orig_sum :sum + end +} + +class Array #:nodoc: + # Array#sum was added in Ruby 2.4 but it only works with Numeric elements. + def sum(init = nil, &block) + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super + end + end + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/file.rb new file mode 100644 index 0000000000..64553bfa4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/file/atomic.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/file/atomic.rb new file mode 100644 index 0000000000..9deceb1bb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/file/atomic.rb @@ -0,0 +1,70 @@ +# 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) + ensure + FileUtils.rm_f(file_name) if file_name + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash.rb new file mode 100644 index 0000000000..2f0901d853 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/conversions.rb new file mode 100644 index 0000000000..2b5e484d21 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/conversions.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/deep_merge.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/deep_merge.rb new file mode 100644 index 0000000000..9bc50b7bc6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/deep_merge.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Hash + # 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 } } + def deep_merge(other_hash, &block) + dup.deep_merge!(other_hash, &block) + end + + # Same as +deep_merge+, but modifies +self+. + def deep_merge!(other_hash, &block) + merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) + else + other_val + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/deep_transform_values.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/deep_transform_values.rb new file mode 100644 index 0000000000..8ad85c0a6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/except.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/except.rb new file mode 100644 index 0000000000..ec96929b0a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/except.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Hash + # Returns a hash that includes everything except given keys. + # hash = { a: true, b: false, c: nil } + # hash.except(:c) # => { a: true, b: false } + # hash.except(:a, :b) # => { c: nil } + # hash # => { a: true, b: false, c: nil } + # + # This is useful for limiting a set of parameters to everything but a few known toggles: + # @person.update(params[:person].except(:admin)) + def except(*keys) + slice(*self.keys - keys) + end unless method_defined?(:except) + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/indifferent_access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/indifferent_access.rb new file mode 100644 index 0000000000..a38f33f128 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 overwrite 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/keys.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/keys.rb new file mode 100644 index 0000000000..f2db61f386 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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(&:to_s) + end + + # Destructively converts all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!(&: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(&: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!(&: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({}) 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/reverse_merge.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/reverse_merge.rb new file mode 100644 index 0000000000..ef8d592829 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/slice.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/hash/slice.rb new file mode 100644 index 0000000000..56bc5de382 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer.rb new file mode 100644 index 0000000000..d22701306a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 0000000000..aef3266f28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/multiple.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/multiple.rb new file mode 100644 index 0000000000..bd57a909c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 0000000000..5efb89cf9f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel.rb new file mode 100644 index 0000000000..7708069301 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/concern.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/concern.rb new file mode 100644 index 0000000000..0b2baed780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/reporting.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 0000000000..9155bd6c10 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 + with_warnings(nil) { yield } + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings + with_warnings(true) { yield } + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/singleton_class.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/kernel/singleton_class.rb new file mode 100644 index 0000000000..6715eba80a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/load_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/load_error.rb new file mode 100644 index 0000000000..03df2ddac4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/marshal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/marshal.rb new file mode 100644 index 0000000000..5ff0e34d82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/marshal.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module MarshalWithAutoloading # :nodoc: + def load(source, proc = nil) + super(source, proc) + rescue ArgumentError, NameError => exc + if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) + # try loading the class/module + loaded = $1.constantize + + raise unless $1 == loaded.name + + # if it is an IO we need to go back to read the object + source.rewind if source.respond_to?(:rewind) + retry + else + raise exc + end + end + end +end + +Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module.rb new file mode 100644 index 0000000000..542af98c04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/aliasing.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 0000000000..6f64d11627 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/anonymous.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/anonymous.rb new file mode 100644 index 0000000000..d1c86b8722 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attr_internal.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attr_internal.rb new file mode 100644 index 0000000000..3bd66ff3bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attr_internal.rb @@ -0,0 +1,38 @@ +# 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_accessor :attr_internal_naming_format end + self.attr_internal_naming_format = "@_%s" + + private + def attr_internal_ivar_name(attr) + Module.attr_internal_naming_format % attr + end + + def attr_internal_define(attr_name, type) + internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@") + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 0000000000..1db905ff65 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +# 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + 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] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb new file mode 100644 index 0000000000..ea4034303e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +# 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. +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 # => nil + # Thread.current[:attr_Current_user] = "DHH" + # Current.user # => "DHH" + # + # 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 `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + Thread.current["attr_" + name + "_#{sym}"] + end + EOS + + if instance_reader && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + self.class.#{sym} + end + EOS + end + + Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil? + 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, 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 `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) + Thread.current["attr_" + name + "_#{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 + + public_send("#{sym}=", default) unless default.nil? + 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" + # + # If a subclass changes the value, the parent class' value is not changed. + # Similarly, if the parent class changes the value, the value of subclasses + # is not changed. + # + # class Customer < Account + # end + # + # 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 + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/concerning.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 0000000000..36f5f85937 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/delegation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 0000000000..a44a3e7c74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,330 @@ +# frozen_string_literal: true + +require "set" + +class Module + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError; end + + RUBY_RESERVED_KEYWORDS = %w(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) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) + DELEGATION_RESERVED_METHOD_NAMES = Set.new( + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS + ).freeze + + # 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 +Module::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 + # +Module::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 + # # => Module::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) + 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 + + to = to.to_s + to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) + + method_def = [] + method_names = [] + + methods.map 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 /[^\]]=$/.match?(method) + "arg" + elsif RUBY_VERSION >= "2.7" + "..." + else + "*args, &block" + 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 allow_nil + method = method.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{to}" << + " 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})" << + " _ = #{to}" << + " _.#{method}(#{definition})" << + "rescue NoMethodError => e" << + " if _.nil? && e.name == :#{method}" << + %( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") << + " else" << + " raise" << + " end" << + "end" + end + end + module_eval(method_def.join(";"), file, line) + private(*method_names) if private + method_names + 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 +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) + target = target.to_s + target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) + + 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, *args, &block) + if #{target}.respond_to?(method) + #{target}.public_send(method, *args, &block) + else + begin + super + rescue NoMethodError + if #{target}.nil? + if #{allow_nil == true} + nil + else + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + end + else + raise + end + end + end + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + RUBY + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/deprecation.rb new file mode 100644 index 0000000000..71c42eb357 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/deprecation.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Module + # deprecate :foo + # deprecate bar: 'message' + # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!' + # + # You can also use custom deprecator instance: + # + # deprecate :foo, deprecator: MyLib::Deprecator.new + # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new + # + # \Custom deprecators must respond to deprecation_warning(deprecated_method_name, message, caller_backtrace) + # method 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) + ActiveSupport::Deprecation.deprecate_methods(self, *method_names) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/introspection.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 0000000000..7cdcab59b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +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 + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/redefine_method.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/redefine_method.rb new file mode 100644 index 0000000000..5bd8e6e973 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/remove_method.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 0000000000..97eb5f9eca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/name_error.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/name_error.rb new file mode 100644 index 0000000000..15255d58b9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,65 @@ +# 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 + + if UnboundMethod.method_defined?(:bind_call) + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind_call(mod) + end + else + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric.rb new file mode 100644 index 0000000000..fe778470f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/bytes.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/bytes.rb new file mode 100644 index 0000000000..b002eba406 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/bytes.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class Numeric + KILOBYTE = 1024 + MEGABYTE = KILOBYTE * 1024 + GIGABYTE = MEGABYTE * 1024 + TERABYTE = GIGABYTE * 1024 + PETABYTE = TERABYTE * 1024 + EXABYTE = PETABYTE * 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 0000000000..3e623e0d17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" + +module ActiveSupport + module NumericWithFormat + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size and pretty printing. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_s(:phone) # => "555-1234" + # 1235551234.to_s(:phone) # => "123-555-1234" + # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234" + # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # Currency: + # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_s(:currency, round_mode: :down) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €" + # -1234567890.50.to_s(:currency, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # + # Percentage: + # 100.to_s(:percentage) # => "100.000%" + # 100.to_s(:percentage, precision: 0) # => "100%" + # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%" + # 302.24398923423.to_s(:percentage, round_mode: :down) # => "302.243%" + # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_s(:percentage, format: '%n %') # => "100.000 %" + # + # Delimited: + # 12345678.to_s(:delimited) # => "12,345,678" + # 12345678.05.to_s(:delimited) # => "12,345,678.05" + # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05" + # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # + # Rounded: + # 111.2345.to_s(:rounded) # => "111.235" + # 111.2345.to_s(:rounded, precision: 2) # => "111.23" + # 111.2345.to_s(:rounded, precision: 2, round_mode: :up) # => "111.24" + # 13.to_s(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_s(:rounded, precision: 0) # => "389" + # 111.2345.to_s(:rounded, significant: true) # => "111" + # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100" + # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_s(:rounded, locale: :fr) # => "111,234" + # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3" + # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + # + # Human-friendly size in Bytes: + # 123.to_s(:human_size) # => "123 Bytes" + # 1234.to_s(:human_size) # => "1.21 KB" + # 12345.to_s(:human_size) # => "12.1 KB" + # 1234567.to_s(:human_size) # => "1.18 MB" + # 1234567890.to_s(:human_size) # => "1.15 GB" + # 1234567890123.to_s(:human_size) # => "1.12 TB" + # 1234567890123456.to_s(:human_size) # => "1.1 PB" + # 1234567890123456789.to_s(:human_size) # => "1.07 EB" + # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB" + # 1234567.to_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB" + # 483989.to_s(:human_size, precision: 2) # => "470 KB" + # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB" + # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" + # 524288000.to_s(:human_size, precision: 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_s(:human) # => "123" + # 1234.to_s(:human) # => "1.23 Thousand" + # 12345.to_s(:human) # => "12.3 Thousand" + # 1234567.to_s(:human) # => "1.23 Million" + # 1234567890.to_s(:human) # => "1.23 Billion" + # 1234567890123.to_s(:human) # => "1.23 Trillion" + # 1234567890123456.to_s(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_s(:human) # => "1230 Quadrillion" + # 489939.to_s(:human, precision: 2) # => "490 Thousand" + # 489939.to_s(:human, precision: 2, round_mode: :down) # => "480 Thousand" + # 489939.to_s(:human, precision: 4) # => "489.9 Thousand" + # 1234567.to_s(:human, precision: 4, + # significant: false) # => "1.2346 Million" + # 1234567.to_s(:human, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + def to_s(format = nil, options = nil) + case format + when nil + super() + when Integer, String + super(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 + super() + else + super(format) + end + end + end +end + +Integer.prepend ActiveSupport::NumericWithFormat +Float.prepend ActiveSupport::NumericWithFormat +BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 0000000000..bc4627f7a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object.rb new file mode 100644 index 0000000000..efd34cc692 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object.rb @@ -0,0 +1,16 @@ +# 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_options" diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/acts_like.rb new file mode 100644 index 0000000000..403ee20e39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/acts_like.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Object + # A duck-type assistant method. For example, Active Support extends Date + # to define an acts_like_date? method, and extends Time to define + # acts_like_time?. As a result, we can do x.acts_like?(:time) and + # x.acts_like?(:date) to do duck-type-safe comparisons, since classes that + # we want to act like Time simply need to define an acts_like_time? method. + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/blank.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/blank.rb new file mode 100644 index 0000000000..f36fef6cc9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/blank.rb @@ -0,0 +1,155 @@ +# 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? : !self + 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 +end + +class FalseClass + # +false+ is blank: + # + # false.blank? # => true + # + # @return [true] + def blank? + true + end +end + +class TrueClass + # +true+ is not blank: + # + # true.blank? # => false + # + # @return [false] + def blank? + false + 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? +end + +class Hash + # A hash is blank if it's empty: + # + # {}.blank? # => true + # { key: 'value' }.blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? +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 +end + +class Numeric #:nodoc: + # No number is blank: + # + # 1.blank? # => false + # 0.blank? # => false + # + # @return [false] + def blank? + false + end +end + +class Time #:nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/conversions.rb new file mode 100644 index 0000000000..624fb8d77c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/deep_dup.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/deep_dup.rb new file mode 100644 index 0000000000..5c903917f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,55 @@ +# 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 && key.frozen?) || ::Symbol === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end + end + hash + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/duplicable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/duplicable.rb new file mode 100644 index 0000000000..3ebcdca02b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/duplicable.rb @@ -0,0 +1,49 @@ +# 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 + +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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/inclusion.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/inclusion.rb new file mode 100644 index 0000000000..6064e92f20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/inclusion.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Object + # Returns true if this object is included in the argument. Argument must be + # any object which responds to +#include?+. Usage: + # + # characters = ["Konata", "Kagami", "Tsukasa"] + # "Konata".in?(characters) # => true + # + # This will throw an +ArgumentError+ if the argument doesn't respond + # to +#include?+. + def in?(another_object) + another_object.include?(self) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/instance_variables.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/instance_variables.rb new file mode 100644 index 0000000000..12fdf840b5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/instance_variables.rb @@ -0,0 +1,30 @@ +# 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 + Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] + 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(&:to_s) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/json.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/json.rb new file mode 100644 index 0000000000..a65e273236 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +# Hack to load json gem first so we can overwrite 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 +# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +# +# 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.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder) +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 Struct #:nodoc: + def as_json(options = nil) + Hash[members.zip(values)].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: + to_s + 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: + map { |v| options ? v.as_json(options.dup) : v.as_json } + 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 = {} + subset.each do |k, v| + result[k.to_s] = options ? v.as_json(options.dup) : 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 + +class IPAddr # :nodoc: + def as_json(options = nil) + to_s + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/to_param.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 0000000000..6d2bdd70f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/to_query.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 0000000000..bac6ff9c97 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,89 @@ +# 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. + # + # This method is also aliased as +to_param+. + def to_query(namespace = nil) + query = collect do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end.compact + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") + end + + alias_method :to_param, :to_query +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/try.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/try.rb new file mode 100644 index 0000000000..999141a993 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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(method_name = nil, *args, &b) + if method_name.nil? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + elsif respond_to?(method_name) + public_send(method_name, *args, &b) + end + end + ruby2_keywords(:try) if respond_to?(:ruby2_keywords, true) + + def try!(method_name = nil, *args, &b) + if method_name.nil? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + else + public_send(method_name, *args, &b) + end + end + ruby2_keywords(:try!) if respond_to?(:ruby2_keywords, true) + end +end + +class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*a, &b) + # + # 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!(*a, &b) + # + # 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(a*, &b) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(a*, &b) + # + # 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(_method_name = nil, *) + nil + end + + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil + def try!(_method_name = nil, *) + nil + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/with_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/with_options.rb new file mode 100644 index 0000000000..1d46add6e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/object/with_options.rb @@ -0,0 +1,82 @@ +# 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 + # 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 + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range.rb new file mode 100644 index 0000000000..78814fd189 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range.rb @@ -0,0 +1,7 @@ +# 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/include_time_with_zone" +require "active_support/core_ext/range/overlaps" +require "active_support/core_ext/range/each" diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/compare_range.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 0000000000..c6d5b77e8e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,82 @@ +# 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 + + # Extends the default Range#cover? to support range comparisons. + # (1..5).cover?(1..5) # => true + # (1..5).cover?(2..3) # => true + # (1..5).cover?(1...6) # => true + # (1..5).cover?(2..6) # => false + # + # The native Range#cover? behavior is untouched. + # ('a'..'f').cover?('c') # => true + # (5..9).cover?(11) # => false + # + # The given range must be fully bounded, with both start and end. + def cover?(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 covers 1..9 but it does not cover 1..10. + # 1..10 covers 1...11 but it does not cover 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 0000000000..024e32db40 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveSupport + module RangeWithFormat + RANGE_FORMATS = { + db: -> (start, stop) do + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" + end + end + } + + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. + # + # range = (1..100) # => 1..100 + # + # range.to_s # => "1..100" + # range.to_s(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_s + # 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_s(:db)} and #{stop.to_s(:db)}" } + def to_s(format = :default) + if formatter = RANGE_FORMATS[format] + formatter.call(first, last) + else + super() + end + end + + alias_method :to_default_s, :to_s + alias_method :to_formatted_s, :to_s + end +end + +Range.prepend(ActiveSupport::RangeWithFormat) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/each.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/each.rb new file mode 100644 index 0000000000..2d86997edf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/include_time_with_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 0000000000..89e911b11d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" +require "active_support/deprecation" + +module ActiveSupport + module IncludeTimeWithZone #:nodoc: + # Extends the default Range#include? to support ActiveSupport::TimeWithZone. + # + # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true + # + def include?(value) + if self.begin.is_a?(TimeWithZone) || self.end.is_a?(TimeWithZone) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using `Range#include?` to check the inclusion of a value in + a date time range is deprecated. + It is recommended to use `Range#cover?` instead of `Range#include?` to + check the inclusion of a value in a date time range. + MSG + cover?(value) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::IncludeTimeWithZone) diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/overlaps.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/overlaps.rb new file mode 100644 index 0000000000..f753607f8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/range/overlaps.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Range + # Compare two ranges and see if they overlap each other + # (1..5).overlaps?(4..6) # => true + # (1..5).overlaps?(7..9) # => false + def overlaps?(other) + cover?(other.first) || other.cover?(first) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/regexp.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/regexp.rb new file mode 100644 index 0000000000..15534ff52f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/securerandom.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..ef812f7e1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,45 @@ +# 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" + 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 + + # 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" + 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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string.rb new file mode 100644 index 0000000000..757d15c51a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/access.rb new file mode 100644 index 0000000000..f6a14c08bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/behavior.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/behavior.rb new file mode 100644 index 0000000000..35a5aa7840 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/conversions.rb new file mode 100644 index 0000000000..e84a3909a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 & used_keys).empty? + + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/exclude.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/exclude.rb new file mode 100644 index 0000000000..8e462689f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/filters.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/filters.rb new file mode 100644 index 0000000000..7f28bd52f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/filters.rb @@ -0,0 +1,145 @@ +# 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+ after a given length if +text+ is longer than length: + # + # '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 "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at + + omission = options[:omission] || "..." + length_with_room_for_omission = truncate_at - 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 bytesize 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 bytesize. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + scan(/\X/) 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/indent.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/indent.rb new file mode 100644 index 0000000000..af9d181487 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 fallback 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/inflections.rb new file mode 100644 index 0000000000..9bf465bda3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/inflections.rb @@ -0,0 +1,293 @@ +# 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" + # + # +camelize+ is also aliased as +camelcase+. + # + # 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" + # + # +titleize+ is also aliased as +titlecase+. + # + # 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 just 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 + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/inquiry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/inquiry.rb new file mode 100644 index 0000000000..d78ad9b741 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/multibyte.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/multibyte.rb new file mode 100644 index 0000000000..0542121e39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/output_safety.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 0000000000..8a06ccdd8e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,343 @@ +# frozen_string_literal: true + +require "erb" +require "active_support/core_ext/module/redefine_method" +require "active_support/multibyte/unicode" + +class ERB + module Util + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + + # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name + TAG_NAME_START_REGEXP_SET = "@: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}" + TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/ + TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/ + TAG_NAME_REPLACEMENT_CHAR = "_" + + # 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) + unwrapped_html_escape(s).html_safe + end + + silence_redefinition_of_method :h + alias h html_escape + + module_function :h + + singleton_class.silence_redefinition_of_method :html_escape + module_function :html_escape + + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def unwrapped_html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) + end + end + module_function :unwrapped_html_escape + + # 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) + result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) + s.html_safe? ? result.html_safe : result + 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.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) + 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? + + starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + return starting_char if name.size == 1 + + following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + starting_char + following_chars + end + module_function :xml_name_escape + end +end + +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 slice 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 + + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set :@html_safe, true + new_safe_buffer + else + to_str[*args] + end + 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 clone_empty + self[0, 0] + end + + def concat(value) + super(html_escape_interpolated_argument(value)) + end + alias << concat + + def insert(index, value) + super(index, html_escape_interpolated_argument(value)) + end + + def prepend(value) + super(html_escape_interpolated_argument(value)) + end + + def replace(value) + super(html_escape_interpolated_argument(value)) + end + + def []=(*args) + if args.length == 3 + super(args[0], args[1], html_escape_interpolated_argument(args[2])) + else + super(args[0], html_escape_interpolated_argument(args[1])) + 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| html_escape_interpolated_argument(arg) } + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } + end + + self.class.new(super(escaped_args)) + end + + def html_safe? + defined?(@html_safe) && @html_safe + end + + 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| + if unsafe_method.respond_to?(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 + end + + private + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + 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 + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/starts_ends_with.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 0000000000..1e216370e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/strip.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/strip.rb new file mode 100644 index 0000000000..60e9952ee6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/string/zones.rb new file mode 100644 index 0000000000..55dc231464 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/symbol.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/symbol.rb new file mode 100644 index 0000000000..709fed2024 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/symbol/starts_ends_with.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/symbol/starts_ends_with.rb new file mode 100644 index 0000000000..655a539403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/symbol/starts_ends_with.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Symbol + def start_with?(*prefixes) + to_s.start_with?(*prefixes) + end unless method_defined?(:start_with?) + + def end_with?(*suffixes) + to_s.end_with?(*suffixes) + end unless method_defined?(:end_with?) + + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000000..c809def05f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/acts_like.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/acts_like.rb new file mode 100644 index 0000000000..8572b49639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/calculations.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/calculations.rb new file mode 100644 index 0000000000..1a7d503fe5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/calculations.rb @@ -0,0 +1,365 @@ +# 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(*args) + return at_without_coercion(*args) if args.size != 1 + + # Time.at can be called with a time or numerical value + time_or_number = args.first + + 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 + end + ruby2_keywords(:at_with_coercion) if respond_to?(:ruby2_keywords, true) + 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 + + unless Time.method_defined?(:floor) + def floor(precision = 0) + change(nsec: 0) + subsec.floor(precision) + end + end + + # Restricted Ruby version due to a bug in `Time#ceil` + # See https://bugs.ruby-lang.org/issues/17025 for more details + if RUBY_VERSION <= "2.8" + remove_possible_method :ceil + def ceil(precision = 0) + change(nsec: 0) + subsec.ceil(precision) + end + 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) + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone) + elsif zone + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec) + 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 + 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 + to_datetime.since(seconds) + 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 + + # 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) + compare_without_coercion(other.to_time) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/compatibility.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 0000000000..495e4f307b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,16 @@ +# 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/conversions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 0000000000..d61a191e33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,74 @@ +# 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}") + }, + iso8601: lambda { |time| time.iso8601 } + } + + # Converts to a formatted string. See DATE_FORMATS for built-in formats. + # + # This method is aliased to to_s. + # + # time = Time.now # => 2007-01-18 06:10:17 -06:00 + # + # time.to_formatted_s(:time) # => "06:10" + # time.to_s(:time) # => "06:10" + # + # time.to_formatted_s(:db) # => "2007-01-18 06:10:17" + # time.to_formatted_s(:number) # => "20070118061017" + # time.to_formatted_s(:short) # => "18 Jan 06:10" + # time.to_formatted_s(:long) # => "January 18, 2007 06:10" + # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" + # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" + # + # == Adding your own time formats to +to_formatted_s+ + # 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_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/zones.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/zones.rb new file mode 100644 index 0000000000..a5588fd488 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/time/zones.rb @@ -0,0 +1,113 @@ +# 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 + Thread.current[: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) + Thread.current[: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) + if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + # Look up the timezone based on the identifier (unless we've been + # passed a TZInfo::Timezone) + unless time_zone.respond_to?(:period_for_local) + time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + end + + # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone + if time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) + end + end + rescue TZInfo::InvalidTimezoneIdentifier + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/uri.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/uri.rb new file mode 100644 index 0000000000..fd5b0b31cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/core_ext/uri.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "uri" + +if RUBY_VERSION < "2.6.0" + require "active_support/core_ext/module/redefine_method" + URI::Parser.class_eval do + silence_redefinition_of_method :unescape + def unescape(str, escaped = /%[a-fA-F\d]{2}/) + # TODO: Are we actually sure that ASCII == UTF-8? + # YK: My initial experiments say yes, but let's be sure please + enc = str.encoding + enc = Encoding::UTF_8 if enc == Encoding::US_ASCII + str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) + end + end +end + +module URI + class << self + def parser + ActiveSupport::Deprecation.warn(<<-MSG.squish) + URI.parser is deprecated and will be removed in Rails 7.0. + Use `URI::DEFAULT_PARSER` instead. + MSG + URI::DEFAULT_PARSER + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/current_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/current_attributes.rb new file mode 100644 index 0000000000..ec487725d4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/current_attributes.rb @@ -0,0 +1,210 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # 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 + + 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. + def attribute(*names) + generated_attribute_methods.module_eval do + names.each do |name| + define_method(name) do + attributes[name.to_sym] + end + + define_method("#{name}=") do |attribute| + attributes[name.to_sym] = attribute + end + end + end + + names.each do |name| + define_singleton_method(name) do + instance.public_send(name) + end + + define_singleton_method("#{name}=") do |attribute| + instance.public_send("#{name}=", attribute) + end + end + end + + # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values. + def before_reset(&block) + set_callback :reset, :before, &block + end + + # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. + def resets(&block) + set_callback :reset, :after, &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 + Thread.current[:current_attributes_instances] ||= {} + end + + def current_instances_key + @current_instances_key ||= name.to_sym + end + + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end + + attr_accessor :attributes + + def initialize + @attributes = {} + 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(set_attributes) + old_attributes = compute_attributes(set_attributes.keys) + assign_attributes(set_attributes) + yield + ensure + assign_attributes(old_attributes) + 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 = {} + end + end + + private + def assign_attributes(new_attributes) + new_attributes.each { |key, value| public_send("#{key}=", value) } + end + + def compute_attributes(keys) + keys.index_with { |key| public_send(key) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/current_attributes/test_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/current_attributes/test_helper.rb new file mode 100644 index 0000000000..2016384a80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies.rb new file mode 100644 index 0000000000..6ab9d1ec3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies.rb @@ -0,0 +1,828 @@ +# frozen_string_literal: true + +require "set" +require "thread" +require "concurrent/map" +require "pathname" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/load_error" +require "active_support/core_ext/name_error" +require "active_support/dependencies/interlock" +require "active_support/inflector" + +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + extend self + + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + + mattr_accessor :interlock, default: Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads. + def self.run_interlock + Dependencies.interlock.running { yield } + 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 + Dependencies.interlock.loading { yield } + 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 + Dependencies.interlock.unloading { yield } + end + + # :nodoc: + + # Should we turn on Ruby warnings on the first load of dependent files? + mattr_accessor :warnings_on_first_load, default: false + + # All files ever loaded. + mattr_accessor :history, default: Set.new + + # All files currently loaded. + mattr_accessor :loaded, default: Set.new + + # Stack of files being loaded. + mattr_accessor :loading, default: [] + + # Should we load files or require them? + mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load + + # The set of directories from which we may automatically load files. Files + # under these directories will be reloaded on each request in development mode, + # unless the directory also appears in autoload_once_paths. + mattr_accessor :autoload_paths, default: [] + + # The set of directories from which automatically loaded constants are loaded + # only once. All directories in this set must also be present in +autoload_paths+. + mattr_accessor :autoload_once_paths, default: [] + + # This is a private set that collects all eager load paths during bootstrap. + # Useful for Zeitwerk integration. Its public interface is the config.* path + # accessors of each engine. + mattr_accessor :_eager_load_paths, default: Set.new + + # An array of qualified constant names that have been loaded. Adding a name + # to this array will cause it to be unloaded the next time Dependencies are + # cleared. + mattr_accessor :autoloaded_constants, default: [] + + # An array of constant names that need to be unloaded on every request. Used + # to allow arbitrary constants to be marked for unloading. + mattr_accessor :explicitly_unloadable_constants, default: [] + + # The logger used when tracing autoloads. + mattr_accessor :logger + + # If true, trace autoloads with +logger.debug+. + mattr_accessor :verbose, default: false + + # The WatchStack keeps a stack of the modules being watched as files are + # loaded. If a file in the process of being loaded (parent.rb) triggers the + # load of another file (child.rb) the stack will ensure that child.rb + # handles the new constants. + # + # If child.rb is being autoloaded, its constants will be added to + # autoloaded_constants. If it was being required, they will be discarded. + # + # This is handled by walking back up the watch stack and adding the constants + # found by child.rb to the list of original constants in parent.rb. + class WatchStack + include Enumerable + + # @watching is a stack of lists of constants being watched. For instance, + # if parent.rb is autoloaded, the stack will look like [[Object]]. If + # parent.rb then requires namespace/child.rb, the stack will look like + # [[Object], [Namespace]]. + + attr_reader :watching + + def initialize + @watching = [] + @stack = Hash.new { |h, k| h[k] = [] } + end + + def each(&block) + @stack.each(&block) + end + + def watching? + !@watching.empty? + end + + # Returns a list of new constants found since the last call to + # watch_namespaces. + def new_constants + constants = [] + + # Grab the list of namespaces that we're looking for new constants under + @watching.last.each do |namespace| + # Retrieve the constants that were present under the namespace when watch_namespaces + # was originally called + original_constants = @stack[namespace].last + + mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) + next unless mod.is_a?(Module) + + # Get a list of the constants that were added + new_constants = mod.constants(false) - original_constants + + # @stack[namespace] returns an Array of the constants that are being evaluated + # for that namespace. For instance, if parent.rb requires child.rb, the first + # element of @stack[Object] will be an Array of the constants that were present + # before parent.rb was required. The second element will be an Array of the + # constants that were present before child.rb was required. + @stack[namespace].each do |namespace_constants| + namespace_constants.concat(new_constants) + end + + # Normalize the list of new constants, and add them to the list we will return + new_constants.each do |suffix| + constants << ([namespace, suffix] - ["Object"]).join("::") + end + end + constants + ensure + # A call to new_constants is always called after a call to watch_namespaces + pop_modules(@watching.pop) + end + + # Add a set of modules to the watch stack, remembering the initial + # constants. + def watch_namespaces(namespaces) + @watching << namespaces.map do |namespace| + module_name = Dependencies.to_constant_name(namespace) + original_constants = Dependencies.qualified_const_defined?(module_name) ? + Inflector.constantize(module_name).constants(false) : [] + + @stack[module_name] << original_constants + module_name + end + end + + private + def pop_modules(modules) + modules.each { |mod| @stack[mod].pop } + end + end + + # An internal stack used to record which constants are loaded by any block. + mattr_accessor :constant_watch_stack, default: WatchStack.new + + # Module includes this module. + module ModuleConstMissing #:nodoc: + def self.append_features(base) + base.class_eval do + # Emulate #exclude via an ivar + return if defined?(@_const_missing) && @_const_missing + @_const_missing = instance_method(:const_missing) + remove_method(:const_missing) + end + super + end + + def self.exclude_from(base) + base.class_eval do + define_method :const_missing, @_const_missing + @_const_missing = nil + end + end + + def self.include_into(base) + base.include(self) + append_features(base) + end + + def const_missing(const_name) + from_mod = anonymous? ? guess_for_anonymous(const_name) : self + Dependencies.load_missing_constant(from_mod, const_name) + end + + # We assume that the name of the module reflects the nesting + # (unless it can be proven that is not the case) and the path to the file + # that defines the constant. Anonymous modules cannot follow these + # conventions and therefore we assume that the user wants to refer to a + # top-level constant. + def guess_for_anonymous(const_name) + if Object.const_defined?(const_name) + raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name + else + Object + end + end + + def unloadable(const_desc = self) + super(const_desc) + end + end + + # Object includes this module. + module Loadable #:nodoc: + def self.exclude_from(base) + base.class_eval do + define_method(:load, Kernel.instance_method(:load)) + private :load + + define_method(:require, Kernel.instance_method(:require)) + private :require + end + end + + def self.include_into(base) + base.include(self) + + if base.instance_method(:load).owner == base + base.remove_method(:load) + end + + if base.instance_method(:require).owner == base + base.remove_method(:require) + end + end + + def require_or_load(file_name) + Dependencies.require_or_load(file_name) + end + + # :doc: + + # Warning: This method is obsolete in +:zeitwerk+ mode. In + # +:zeitwerk+ mode semantics match Ruby's and you do not need to be + # defensive with load order. Just refer to classes and modules normally. + # If the constant name is dynamic, camelize if needed, and constantize. + # + # In +:classic+ mode, interprets a file using +mechanism+ and marks its + # defined constants as autoloaded. +file_name+ can be either a string or + # respond to to_path. + # + # In +:classic+ mode, use this method in code that absolutely needs a + # certain constant to be defined at that point. A typical use case is to + # make constant name resolution deterministic for constants with the same + # relative name in different namespaces whose evaluation would depend on + # load order otherwise. + # + # 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(file_name, message = "No such file to load -- %s.rb") + file_name = file_name.to_path if file_name.respond_to?(:to_path) + unless file_name.is_a?(String) + raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" + end + + Dependencies.depend_on(file_name, message) + end + + # :nodoc: + + def load_dependency(file) + if Dependencies.load? && Dependencies.constant_watch_stack.watching? + descs = Dependencies.constant_watch_stack.watching.flatten.uniq + + Dependencies.new_constants_in(*descs) { yield } + else + yield + end + rescue Exception => exception # errors from loading file + exception.blame_file! file if exception.respond_to? :blame_file! + raise + end + + # Mark the given constant as unloadable. Unloadable constants are removed + # each time dependencies are cleared. + # + # Note that marking a constant for unloading need only be done once. Setup + # or init scripts may list each unloadable constant that may need unloading; + # each constant will be removed for every subsequent clear, as opposed to + # for the first clear. + # + # The provided constant descriptor may be a (non-anonymous) module or class, + # or a qualified constant name as a string or symbol. + # + # Returns +true+ if the constant was not previously marked for unloading, + # +false+ otherwise. + def unloadable(const_desc) + Dependencies.mark_for_unload const_desc + end + + private + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result + end + + def require(file) + result = false + load_dependency(file) { result = super } + result + end + end + + # Exception file-blaming. + module Blamable #:nodoc: + def blame_file!(file) + (@blamed_files ||= []).unshift file + end + + def blamed_files + @blamed_files ||= [] + end + + def describe_blame + return nil if blamed_files.empty? + "This error occurred while loading the following files:\n #{blamed_files.join "\n "}" + end + + def copy_blame!(exc) + @blamed_files = exc.blamed_files.clone + self + end + end + + def hook! + Loadable.include_into(Object) + ModuleConstMissing.include_into(Module) + Exception.include(Blamable) + end + + def unhook! + ModuleConstMissing.exclude_from(Module) + Loadable.exclude_from(Object) + end + + def load? + mechanism == :load + end + + def depend_on(file_name, message = "No such file to load -- %s.rb") + path = search_for_file(file_name) + require_or_load(path || file_name) + rescue LoadError => load_error + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + load_error_message = if load_error.respond_to?(:original_message) + load_error.original_message + else + load_error.message + end + load_error_message.replace(message % file_name) + load_error.copy_blame!(load_error) + end + raise + end + + def clear + Dependencies.unload_interlock do + loaded.clear + loading.clear + remove_unloadable_constants! + end + end + + def require_or_load(file_name, const_path = nil) + file_name = file_name.chomp(".rb") + expanded = File.expand_path(file_name) + return if loaded.include?(expanded) + + Dependencies.load_interlock do + # Maybe it got loaded while we were waiting for our lock: + return if loaded.include?(expanded) + + # Record that we've seen this file *before* loading it to avoid an + # infinite loop with mutual dependencies. + loaded << expanded + loading << expanded + + begin + if load? + # Enable warnings if this file has not been loaded before and + # warnings_on_first_load is set. + load_args = ["#{file_name}.rb"] + load_args << const_path unless const_path.nil? + + if !warnings_on_first_load || history.include?(expanded) + result = load_file(*load_args) + else + enable_warnings { result = load_file(*load_args) } + end + else + result = require file_name + end + rescue Exception + loaded.delete expanded + raise + ensure + loading.pop + end + + # Record history *after* loading so first load gets warnings. + history << expanded + result + end + end + + # Is the provided constant path defined? + def qualified_const_defined?(path) + Object.const_defined?(path, false) + end + + # Given +path+, a filesystem path to a ruby file, return an array of + # constant paths which would cause Dependencies to attempt to load this + # file. + def loadable_constants_for_path(path, bases = autoload_paths) + path = path.chomp(".rb") + expanded_path = File.expand_path(path) + paths = [] + + bases.each do |root| + expanded_root = File.expand_path(root) + next unless expanded_path.start_with?(expanded_root) + + root_size = expanded_root.size + next if expanded_path[root_size] != ?/ + + nesting = expanded_path[(root_size + 1)..-1] + paths << nesting.camelize unless nesting.blank? + end + + paths.uniq! + paths + end + + # Search for a file in autoload_paths matching the provided suffix. + def search_for_file(path_suffix) + path_suffix += ".rb" unless path_suffix.end_with?(".rb") + + autoload_paths.each do |root| + path = File.join(root, path_suffix) + return path if File.file? path + end + nil # Gee, I sure wish we had first_match ;-) + end + + # Does the provided path_suffix correspond to an autoloadable module? + # Instead of returning a boolean, the autoload base for this module is + # returned. + def autoloadable_module?(path_suffix) + autoload_paths.each do |load_path| + return load_path if File.directory? File.join(load_path, path_suffix) + end + nil + end + + def load_once_path?(path) + # to_s works around a ruby issue where String#start_with?(Pathname) + # will raise a TypeError: no implicit conversion of Pathname into String + autoload_once_paths.any? { |base| path.start_with?(base.to_s) } + end + + # Attempt to autoload the provided module name by searching for a directory + # matching the expected path suffix. If found, the module is created and + # assigned to +into+'s constants with the name +const_name+. Provided that + # the directory was loaded from a reloadable base path, it is added to the + # set of constants that are to be unloaded. + def autoload_module!(into, const_name, qualified_name, path_suffix) + return nil unless base_path = autoloadable_module?(path_suffix) + mod = Module.new + into.const_set const_name, mod + log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})") + autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) + autoloaded_constants.uniq! + mod + end + + # Load the file at the provided path. +const_paths+ is a set of qualified + # constant names. When loading the file, Dependencies will watch for the + # addition of these constants. Each that is defined will be marked as + # autoloaded, and will be removed when Dependencies.clear is next called. + # + # If the second parameter is left off, then Dependencies will construct a + # set of names that the file at +path+ may define. See + # +loadable_constants_for_path+ for more details. + def load_file(path, const_paths = loadable_constants_for_path(path)) + const_paths = [const_paths].compact unless const_paths.is_a? Array + parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } + + result = nil + newly_defined_paths = new_constants_in(*parent_paths) do + result = Kernel.load path + end + + autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) + autoloaded_constants.uniq! + result + end + + # Returns the constant path for the provided parent and constant name. + def qualified_name_for(mod, name) + mod_name = to_constant_name mod + mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}" + end + + # Load the constant named +const_name+ which is missing from +from_mod+. If + # it is not possible to load the constant into from_mod, try its parent + # module using +const_missing+. + def load_missing_constant(from_mod, const_name) + from_mod_name = real_mod_name(from_mod) + unless qualified_const_defined?(from_mod_name) && Inflector.constantize(from_mod_name).equal?(from_mod) + raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" + end + + qualified_name = qualified_name_for(from_mod, const_name) + path_suffix = qualified_name.underscore + + file_path = search_for_file(path_suffix) + + if file_path + expanded = File.expand_path(file_path) + expanded.delete_suffix!(".rb") + + if loading.include?(expanded) + raise "Circular dependency detected while autoloading constant #{qualified_name}" + else + require_or_load(expanded, qualified_name) + + if from_mod.const_defined?(const_name, false) + log("constant #{qualified_name} autoloaded from #{expanded}.rb") + return from_mod.const_get(const_name) + else + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" + end + end + elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) + return mod + elsif (parent = from_mod.module_parent) && parent != from_mod && + ! from_mod.module_parents.any? { |p| p.const_defined?(const_name, false) } + # If our parents do not have a constant named +const_name+ then we are free + # to attempt to load upwards. If they do have such a constant, then this + # const_missing must be due to from_mod::const_name, which should not + # return constants from from_mod's parents. + begin + # Since Ruby does not pass the nesting at the point the unknown + # constant triggered the callback we cannot fully emulate constant + # name lookup and need to make a trade-off: we are going to assume + # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even + # though it might not be. Counterexamples are + # + # class Foo::Bar + # Module.nesting # => [Foo::Bar] + # end + # + # or + # + # module M::N + # module S::T + # Module.nesting # => [S::T, M::N] + # end + # end + # + # for example. + return parent.const_missing(const_name) + rescue NameError => e + raise unless e.missing_name? qualified_name_for(parent, const_name) + end + end + + name_error = uninitialized_constant(qualified_name, const_name, receiver: from_mod) + name_error.set_backtrace(caller.reject { |l| l.start_with? __FILE__ }) + raise name_error + end + + # Remove the constants that have been autoloaded, and those that have been + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the environment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. + def remove_unloadable_constants! + log("removing unloadable constants") + autoloaded_constants.each { |const| remove_constant const } + autoloaded_constants.clear + Reference.clear! + explicitly_unloadable_constants.each { |const| remove_constant const } + end + + class ClassCache + def initialize + @store = Concurrent::Map.new + end + + def empty? + @store.empty? + end + + def key?(key) + @store.key?(key) + end + + def get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.constantize(key) + end + alias :[] :get + + def safe_get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.safe_constantize(key) + end + + def store(klass) + return self unless klass.respond_to?(:name) + raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty? + @store[klass.name] = klass + self + end + + def clear! + @store.clear + end + end + + Reference = ClassCache.new + + # Store a reference to a class +klass+. + def reference(klass) + Reference.store klass + end + + # Get the reference for class named +name+. + # Raises an exception if referenced class does not exist. + def constantize(name) + Reference.get(name) + end + + # Get the reference for class named +name+ if one exists. + # Otherwise returns +nil+. + def safe_constantize(name) + Reference.safe_get(name) + end + + # Determine if the given constant has been automatically loaded. + def autoloaded?(desc) + return false if desc.is_a?(Module) && real_mod_name(desc).nil? + name = to_constant_name desc + return false unless qualified_const_defined?(name) + autoloaded_constants.include?(name) + end + + # Will the provided constant descriptor be unloaded? + def will_unload?(const_desc) + autoloaded?(const_desc) || + explicitly_unloadable_constants.include?(to_constant_name(const_desc)) + end + + # Mark the provided constant name for unloading. This constant will be + # unloaded on each request, not just the next one. + def mark_for_unload(const_desc) + name = to_constant_name const_desc + if explicitly_unloadable_constants.include? name + false + else + explicitly_unloadable_constants << name + true + end + end + + # Run the provided block and detect the new constants that were loaded during + # its execution. Constants may only be regarded as 'new' once -- so if the + # block calls +new_constants_in+ again, then the constants defined within the + # inner call will not be reported in this one. + # + # If the provided block does not run to completion, and instead raises an + # exception, any new constants are regarded as being only partially defined + # and will be removed immediately. + def new_constants_in(*descs) + constant_watch_stack.watch_namespaces(descs) + success = false + + begin + yield # Now yield to the code that is to define new constants. + success = true + ensure + new_constants = constant_watch_stack.new_constants + + return new_constants if success + + # Remove partially loaded constants. + new_constants.each { |c| remove_constant(c) } + end + end + + # Convert the provided const desc to a qualified constant name (as a string). + # A module, class, symbol, or string may be provided. + def to_constant_name(desc) #:nodoc: + case desc + when String then desc.delete_prefix("::") + when Symbol then desc.to_s + when Module + real_mod_name(desc) || + raise(ArgumentError, "Anonymous modules have no name to be referenced by") + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" + end + end + + def remove_constant(const) #:nodoc: + # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. + normalized = const.to_s.delete_prefix("::") + normalized.sub!(/\A(Object::)+/, "") + + constants = normalized.split("::") + to_remove = constants.pop + + # Remove the file path from the loaded list. + file_path = search_for_file(const.underscore) + if file_path + expanded = File.expand_path(file_path) + expanded.delete_suffix!(".rb") + loaded.delete(expanded) + end + + if constants.empty? + parent = Object + else + # This method is robust to non-reachable constants. + # + # Non-reachable constants may be passed if some of the parents were + # autoloaded and already removed. It is easier to do a sanity check + # here than require the caller to be clever. We check the parent + # rather than the very const argument because we do not want to + # trigger Kernel#autoloads, see the comment below. + parent_name = constants.join("::") + return unless qualified_const_defined?(parent_name) + parent = constantize(parent_name) + end + + # In an autoloaded user.rb like this + # + # autoload :Foo, 'foo' + # + # class User < ActiveRecord::Base + # end + # + # we correctly register "Foo" as being autoloaded. But if the app does + # not use the "Foo" constant we need to be careful not to trigger + # loading "foo.rb" ourselves. While #const_defined? and #const_get? do + # require the file, #autoload? and #remove_const don't. + # + # We are going to remove the constant nonetheless ---which exists as + # far as Ruby is concerned--- because if the user removes the macro + # call from a class or module that were not autoloaded, as in the + # example above with Object, accessing to that constant must err. + unless parent.autoload?(to_remove) + begin + constantized = parent.const_get(to_remove, false) + rescue NameError + # The constant is no longer reachable, just skip it. + return + else + constantized.before_remove_const if constantized.respond_to?(:before_remove_const) + end + end + + begin + parent.instance_eval { remove_const to_remove } + rescue NameError + # The constant is no longer reachable, just skip it. + end + end + + def log(message) + logger.debug("autoloading: #{message}") if logger && verbose + end + + private + if RUBY_VERSION < "2.6" + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name) + end + else + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name, receiver: receiver) + end + end + + # Returns the original name of a class or module even if `name` has been + # overridden. + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end + end +end + +ActiveSupport::Dependencies.hook! diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/autoload.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/autoload.rb new file mode 100644 index 0000000000..1cee85d98f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" + +module ActiveSupport + # 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 self.extended(base) # :nodoc: + base.class_eval do + @_autoloads = {} + @_under_path = nil + @_at_path = nil + @_eager_autoload = false + end + end + + 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 + @_autoloads[const_name] = path + 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! + @_autoloads.each_value { |file| require file } + end + + def autoloads + @_autoloads + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/interlock.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/interlock.rb new file mode 100644 index 0000000000..948be75638 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,57 @@ +# 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 + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do + yield + end + end + + def unloading + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do + yield + end + 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 + @lock.sharing do + yield + end + end + + def permit_concurrent_loads + @lock.yield_shares(compatible: [:load]) do + yield + end + end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/zeitwerk_integration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/zeitwerk_integration.rb new file mode 100644 index 0000000000..ef603d2647 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/dependencies/zeitwerk_integration.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "set" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module Dependencies + module ZeitwerkIntegration # :nodoc: all + module Decorations + def clear + Dependencies.unload_interlock do + Rails.autoloaders.main.reload + rescue Zeitwerk::ReloadingDisabledError + raise "reloading is disabled because config.cache_classes is true" + end + end + + def constantize(cpath) + ActiveSupport::Inflector.constantize(cpath) + end + + def safe_constantize(cpath) + ActiveSupport::Inflector.safe_constantize(cpath) + end + + def autoloaded_constants + Rails.autoloaders.main.unloadable_cpaths + end + + def autoloaded?(object) + cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s + Rails.autoloaders.main.unloadable_cpath?(cpath) + end + + def verbose=(verbose) + l = verbose ? logger || Rails.logger : nil + Rails.autoloaders.each { |autoloader| autoloader.logger = l } + end + + def unhook! + :no_op + end + end + + module RequireDependency + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + end + + module Inflector + # Concurrent::Map is not needed. This is a private class, and overrides + # must be defined while the application boots. + @overrides = {} + + def self.camelize(basename, _abspath) + @overrides[basename] || basename.camelize + end + + def self.inflect(overrides) + @overrides.merge!(overrides) + end + end + + class << self + def take_over(enable_reloading:) + setup_autoloaders(enable_reloading) + freeze_paths + decorate_dependencies + end + + private + def setup_autoloaders(enable_reloading) + Dependencies.autoload_paths.each do |autoload_path| + # Zeitwerk only accepts existing directories in `push_dir` to + # prevent misconfigurations. + next unless File.directory?(autoload_path) + + autoloader = \ + autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main + + autoloader.push_dir(autoload_path) + autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path) + end + + Rails.autoloaders.main.enable_reloading if enable_reloading + + # Order matters. + Rails.autoloaders.once.setup + Rails.autoloaders.main.setup + end + + def autoload_once?(autoload_path) + Dependencies.autoload_once_paths.include?(autoload_path) + end + + def eager_load?(autoload_path) + Dependencies._eager_load_paths.member?(autoload_path) + end + + def freeze_paths + Dependencies.autoload_paths.freeze + Dependencies.autoload_once_paths.freeze + Dependencies._eager_load_paths.freeze + end + + def decorate_dependencies + Dependencies.unhook! + Dependencies.singleton_class.prepend(Decorations) + Object.prepend(RequireDependency) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation.rb new file mode 100644 index 0000000000..887328dcc3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "singleton" + +module ActiveSupport + # \Deprecation specifies the API used by Rails to deprecate methods, instance + # variables, objects and constants. + 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/instance_delegator" + 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/core_ext/module/deprecation" + require "concurrent/atomic/thread_local_var" + + include Singleton + include InstanceDelegator + include Behavior + include Reporting + include Disallowed + include MethodWrapper + + # 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 = "7.0", 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 + @silenced_thread = Concurrent::ThreadLocalVar.new(false) + @explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/behaviors.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/behaviors.rb new file mode 100644 index 0000000000..9d1fc78360 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/behaviors.rb @@ -0,0 +1,122 @@ +# 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, deprecation_horizon, gem_name) { + e = DeprecationException.new(message) + e.set_backtrace(callstack.map(&:to_s)) + raise e + }, + + stderr: ->(message, callstack, deprecation_horizon, gem_name) { + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + + log: ->(message, callstack, deprecation_horizon, gem_name) { + 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 debug + }, + + notify: ->(message, callstack, deprecation_horizon, gem_name) { + notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}" + ActiveSupport::Notifications.instrument(notification_name, + message: message, + callstack: callstack, + gem_name: gem_name, + deprecation_horizon: deprecation_horizon) + }, + + silence: ->(message, callstack, deprecation_horizon, gem_name) { }, + } + + # 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+. + # [+silence+] Do nothing. + # + # 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+. + # [+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. + # + # ActiveSupport::Deprecation.behavior = :stderr + # ActiveSupport::Deprecation.behavior = [:stderr, :log] + # ActiveSupport::Deprecation.behavior = MyCustomHandler + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { + # # custom stuff + # } + 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 + + if behavior.arity == 4 || behavior.arity == -1 + behavior + else + -> message, callstack, _, _ { behavior.call(message, callstack) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/constant_accessor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 0000000000..1ed0015812 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + # DeprecatedConstantAccessor transforms a constant into a deprecated one by + # hooking +const_missing+. + # + # It takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. + # + # The deprecated constant now returns the same object as the new one rather + # than a proxy object, so it can be used transparently in +rescue+ blocks + # etc. + # + # 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) + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # deprecate_constant 'PLANETS', 'PLANETS_POST_2006' + # + # 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"] + 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 + + def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/disallowed.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/disallowed.rb new file mode 100644 index 0000000000..096ecaae85 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/disallowed.rb @@ -0,0 +1,56 @@ +# 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 +ActiveSupport::Deprecation.disallowed_behavior+ + # rather than +ActiveSupport::Deprecation.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) + disallowed = ActiveSupport::Deprecation.disallowed_warnings + return false if explicitly_allowed?(message) + return true if disallowed == :all + disallowed.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 + allowances = [allowances] unless allowances.kind_of?(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.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/instance_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/instance_delegator.rb new file mode 100644 index 0000000000..59dd30ae30 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/instance_delegator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class Deprecation + module InstanceDelegator # :nodoc: + def self.included(base) + base.extend(ClassMethods) + base.singleton_class.prepend(OverrideDelegators) + base.public_class_method :new + end + + module ClassMethods # :nodoc: + def include(included_module) + included_module.instance_methods.each { |m| method_added(m) } + super + end + + def method_added(method_name) + singleton_class.delegate(method_name, to: :instance) + end + end + + module OverrideDelegators # :nodoc: + def warn(message = nil, callstack = nil) + callstack ||= caller_locations(2) + super + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/method_wrappers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/method_wrappers.rb new file mode 100644 index 0000000000..e6cf28a89f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/method_wrappers.rb @@ -0,0 +1,85 @@ +# 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 + # + # Using the default deprecator: + # ActiveSupport::Deprecation.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 Rails 5.1. (called from irb_binding at (irb):10) + # # => nil + # + # Fred.new.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) + # # => nil + # + # Fred.new.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + # + # Passing in a custom deprecator: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) + # # => [:ddd] + # + # Fred.new.ddd + # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) + # # => nil + # + # Using a custom deprecator directly: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # custom_deprecator.deprecate_methods(Fred, eee: :zzz) + # # => [:eee] + # + # Fred.new.eee + # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) + # # => 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(self).call(*args, &block) + end + ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true) + 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) if respond_to?(:ruby2_keywords, true) + end + end + end + + target_module.prepend(mod) if mod + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/proxy_wrappers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/proxy_wrappers.rb new file mode 100644 index 0000000000..4bc112d6c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/proxy_wrappers.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + class DeprecationProxy #:nodoc: + def self.new(*args, &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 optionally a deprecator. The + # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified. + # + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated") + # # => # + # + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#" + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) + @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 + # and an instance variable. It optionally takes a deprecator as the last + # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none + # is specified. + # + # class Example + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request) + # @_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 = ActiveSupport::Deprecation.instance) + @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 names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. 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') + # + # 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 = ActiveSupport::Deprecation.instance, 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 + + 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(called, *args, &block) + @deprecator.warn(@message, caller_locations) + target.__send__(called, *args, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/reporting.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/reporting.rb new file mode 100644 index 0000000000..51514eb3a6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/deprecation/reporting.rb @@ -0,0 +1,157 @@ +# 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.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 |m| + if deprecation_disallowed?(message) + disallowed_behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + else + behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + end + end + end + + # Silence deprecation warnings within the block. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # ActiveSupport::Deprecation.silence do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + def silence(&block) + @silenced_thread.bind(true, &block) + 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. + # + # ActiveSupport::Deprecation.disallowed_behavior = :raise + # ActiveSupport::Deprecation.disallowed_warnings = [ + # "something broke" + # ] + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => ActiveSupport::DeprecationException + # + # ActiveSupport::Deprecation.allow ['something broke'] do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + # + # ActiveSupport::Deprecation.allow ['something broke'], if: Rails.env.production? do + # ActiveSupport::Deprecation.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 silenced + @silenced || @silenced_thread.value + 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 _extract_callstack(callstack) if callstack.first.is_a? String + + offending_line = callstack.find { |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + } || callstack.first + + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + + if offending_line + if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) + md.captures + else + offending_line + end + end + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/descendants_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/descendants_tracker.rb new file mode 100644 index 0000000000..2362914dce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/descendants_tracker.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "weakref" + +module ActiveSupport + # This module provides an internal implementation to track descendants + # which is faster than iterating through ObjectSpace. + module DescendantsTracker + @@direct_descendants = {} + + class << self + def direct_descendants(klass) + descendants = @@direct_descendants[klass] + descendants ? descendants.to_a : [] + end + alias_method :subclasses, :direct_descendants + + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr + end + + def clear + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| Dependencies.autoloaded?(v) } + end + end + else + @@direct_descendants.clear + end + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant + end + + private + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + direct_descendants.each do |direct_descendant| + acc << direct_descendant + accumulate_descendants(direct_descendant, acc) + end + end + end + end + + def inherited(base) + DescendantsTracker.store_inherited(self, base) + super + end + + def direct_descendants + DescendantsTracker.direct_descendants(self) + end + alias_method :subclasses, :direct_descendants + + def descendants + DescendantsTracker.descendants(self) + end + + # DescendantsArray is an array that contains weak references to classes. + class DescendantsArray # :nodoc: + include Enumerable + + def initialize + @refs = [] + end + + def initialize_copy(orig) + @refs = @refs.dup + end + + def <<(klass) + @refs << WeakRef.new(klass) + end + + def each + @refs.reject! do |ref| + yield ref.__getobj__ + false + rescue WeakRef::RefError + true + end + self + end + + def refs_size + @refs.size + end + + def cleanup! + @refs.delete_if { |ref| !ref.weakref_alive? } + end + + def reject! + @refs.reject! do |ref| + yield ref.__getobj__ + rescue WeakRef::RefError + true + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/digest.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/digest.rb new file mode 100644 index 0000000000..c044214648 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/digest.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "digest" + +module ActiveSupport + class Digest #:nodoc: + class <(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) + 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) + 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) + 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 + + 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 + + attr_accessor :value, :parts + + 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) + end + + def minutes(value) #:nodoc: + new(value * SECONDS_PER_MINUTE, minutes: value) + end + + def hours(value) #:nodoc: + new(value * SECONDS_PER_HOUR, hours: value) + end + + def days(value) #:nodoc: + new(value * SECONDS_PER_DAY, days: value) + end + + def weeks(value) #:nodoc: + new(value * SECONDS_PER_WEEK, weeks: value) + end + + def months(value) #:nodoc: + new(value * SECONDS_PER_MONTH, months: value) + end + + def years(value) #:nodoc: + new(value * SECONDS_PER_YEAR, years: value) + 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 + + 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 + end + end unless value == 0 + + parts[:seconds] = remainder * remainder_sign + + new(value, parts) + end + + private + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + + def initialize(value, parts) #:nodoc: + @value, @parts = value, parts + @parts.reject! { |k, v| v.zero? } unless value == 0 + 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) + else + seconds = @parts.fetch(:seconds, 0) + other + Duration.new(value + other, @parts.merge(seconds: seconds)) + 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 }) + elsif Numeric === other + Duration.new(value * other, parts.transform_values { |number| number * other }) + 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 }) + elsif Duration === other + value / other.value + elsif Numeric === other + Duration.new(value / other, parts.transform_values { |number| number / other }) + 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(&:-@)) + 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://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/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: ::I18n.default_locale) + 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 + + 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.inject(time) do |t, (type, number)| + 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 + end + end + + def respond_to_missing?(method, _) + value.respond_to?(method) + end + + def method_missing(method, *args, &block) + value.public_send(method, *args, &block) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/duration/iso8601_parser.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 0000000000..83f3b28602 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 & DATE_COMPONENTS).any? + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && (parts.keys & TIME_COMPONENTS).empty? + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/duration/iso8601_serializer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 0000000000..96296274df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +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 << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', 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. + # If all parts are negative it will negate all of them and return minus as a sign. + 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 & DATE_COMPONENTS).any? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_configuration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_configuration.rb new file mode 100644 index 0000000000..cc1d026737 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_configuration.rb @@ -0,0 +1,45 @@ +# 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/module/delegation" + +module ActiveSupport + class EncryptedConfiguration < EncryptedFile + delegate :[], :fetch, to: :config + 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 + end + + # Allow a config to be started without a file present + def read + super + rescue ActiveSupport::EncryptedFile::MissingContentError + "" + end + + def write(contents) + deserialize(contents) + + super + end + + def config + @config ||= deserialize(read).deep_symbolize_keys + end + + private + def options + @options ||= ActiveSupport::InheritableOptions.new(config) + end + + def deserialize(config) + YAML.load(config).presence || {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_file.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_file.rb new file mode 100644 index 0000000000..a35cc54ef5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/encrypted_file.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "pathname" +require "tmpdir" +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 + + def key + read_env_key || read_key_file || handle_missing_key + end + + def read + if !key.nil? && content_path.exist? + decrypt content_path.binread + 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) + tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}" + tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file) + tmp_path.binwrite contents + + yield tmp_path + + updated_contents = tmp_path.binread + + write(updated_contents) if updated_contents != contents + ensure + FileUtils.rm(tmp_path) if tmp_path&.exist? + 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) + end + + + def read_env_key + ENV[env_key] + end + + def read_key_file + return @key_file_contents if defined?(@key_file_contents) + @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.0.0/gems/activesupport-6.1.7.2/lib/active_support/environment_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/environment_inquirer.rb new file mode 100644 index 0000000000..05361d9327 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/environment_inquirer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" + +module ActiveSupport + class EnvironmentInquirer < StringInquirer #:nodoc: + DEFAULT_ENVIRONMENTS = ["development", "test", "production"] + def initialize(env) + super(env) + + DEFAULT_ENVIRONMENTS.each do |default| + instance_variable_set :"@#{default}", env == default + end + end + + DEFAULT_ENVIRONMENTS.each do |env| + class_eval "def #{env}?; @#{env}; end" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/evented_file_update_checker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/evented_file_update_checker.rb new file mode 100644 index 0000000000..f9bc3be9be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/evented_file_update_checker.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require "set" +require "pathname" +require "concurrent/atomic/atomic_boolean" +require "listen" +require "active_support/fork_tracker" + +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. + # + # Note: Forking will cause the first call to `updated?` to return `true`. + # + # 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 updated? + if @core.restart? + @core.thread_safely(&:restart) + @core.updated.make_true + end + + @core.updated.true? + end + + def execute + @core.updated.make_false + @block.call + end + + def execute_if_updated + if updated? + yield if block_given? + execute + true + end + end + + class Core + attr_reader :updated + + def initialize(files, dirs) + @files = files.map { |file| Pathname(file).expand_path }.to_set + + @dirs = dirs.each_with_object({}) do |(dir, exts), hash| + hash[Pathname(dir).expand_path] = Array(exts).map { |ext| ext.to_s.sub(/\A\.?/, ".") }.to_set + end + + @common_path = common_path(@dirs.keys) + + @dtw = directories_to_watch + @missing = [] + + @updated = Concurrent::AtomicBoolean.new(false) + @mutex = Mutex.new + + start + @after_fork = ActiveSupport::ForkTracker.after_fork { start } + end + + def finalizer + proc do + stop + ActiveSupport::ForkTracker.unregister(@after_fork) + end + end + + def thread_safely + @mutex.synchronize do + yield self + end + end + + def start + normalize_dirs! + @dtw, @missing = [*@dtw, *@missing].partition(&:exist?) + @listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil + @listener&.start + end + + def stop + @listener&.stop + end + + def restart + stop + start + end + + def restart? + @missing.any?(&:exist?) + end + + def normalize_dirs! + @dirs.transform_keys! do |dir| + dir.exist? ? dir.realpath : dir + end + end + + def changed(modified, added, removed) + unless @updated.true? + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = Pathname(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = file.extname + + file.dirname.ascend do |dir| + matching = @dirs[dir] + + if matching && (matching.empty? || matching.include?(ext)) + break true + elsif dir == @common_path || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = @dirs.keys | @files.map(&:dirname) + accounted_for = dtw.to_set + Gem.path.map { |path| Pathname(path) } + dtw.reject { |dir| dir.ascend.drop(1).any? { |parent| accounted_for.include?(parent) } } + end + + def common_path(paths) + paths.map { |path| path.ascend.to_a }.reduce(&:&)&.first + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/execution_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/execution_wrapper.rb new file mode 100644 index 0000000000..07c4f435db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/execution_wrapper.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "concurrent/hash" + +module ActiveSupport + class ExecutionWrapper + include ActiveSupport::Callbacks + + Null = Object.new # :nodoc: + def Null.complete! # :nodoc: + end + + define_callbacks :run + define_callbacks :complete + + def self.to_run(*args, &block) + set_callback(:run, *args, &block) + end + + def self.to_complete(*args, &block) + set_callback(:complete, *args, &block) + end + + RunHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + hook_state[hook] = hook.run + end + end + + CompleteHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + if hook_state.key?(hook) + hook.complete hook_state[hook] + end + end + alias after before + end + + # Register an object to be invoked during both the +run+ and + # +complete+ steps. + # + # +hook.complete+ will be passed the value returned from +hook.run+, + # and will only be invoked if +run+ has previously been called. + # (Mostly, this means it won't be invoked if an exception occurs in + # a preceding +to_run+ block; all ordinary +to_complete+ blocks are + # invoked in that situation.) + def self.register_hook(hook, outer: false) + if outer + to_run RunHook.new(hook), prepend: true + to_complete :after, CompleteHook.new(hook) + else + to_run RunHook.new(hook) + to_complete CompleteHook.new(hook) + end + end + + # Run this execution. + # + # Returns an instance, whose +complete!+ method *must* be invoked + # after the work has been performed. + # + # Where possible, prefer +wrap+. + def self.run!(reset: false) + if reset + lost_instance = active.delete(Thread.current) + lost_instance&.complete! + else + return Null if active? + end + + new.tap do |instance| + success = nil + begin + instance.run! + success = true + ensure + instance.complete! unless success + end + end + end + + # Perform the work in the supplied block as an execution. + def self.wrap + return yield if active? + + instance = run! + begin + yield + ensure + instance.complete! + end + end + + class << self # :nodoc: + attr_accessor :active + end + + def self.inherited(other) # :nodoc: + super + other.active = Concurrent::Hash.new + end + + self.active = Concurrent::Hash.new + + def self.active? # :nodoc: + @active.key?(Thread.current) + end + + def run! # :nodoc: + self.class.active[Thread.current] = self + 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! + run_callbacks(:complete) + ensure + self.class.active.delete Thread.current + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/executor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/executor.rb new file mode 100644 index 0000000000..ce391b07ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/file_update_checker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/file_update_checker.rb new file mode 100644 index 0000000000..9b665e7f19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/file_update_checker.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActiveSupport + # 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 + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/fork_tracker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/fork_tracker.rb new file mode 100644 index 0000000000..2f250378ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/fork_tracker.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module ActiveSupport + module ForkTracker # :nodoc: + module CoreExt + def fork(*) + if block_given? + super do + ForkTracker.check! + yield + end + else + unless pid = super + ForkTracker.check! + end + pid + end + end + ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true) + end + + module CoreExtPrivate + include CoreExt + + private + def fork(*) + super + end + ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true) + end + + @pid = Process.pid + @callbacks = [] + + class << self + def check! + if @pid != Process.pid + @callbacks.each(&:call) + @pid = Process.pid + end + end + + def hook! + if Process.respond_to?(:fork) + ::Object.prepend(CoreExtPrivate) + ::Kernel.prepend(CoreExtPrivate) + ::Kernel.singleton_class.prepend(CoreExt) + ::Process.singleton_class.prepend(CoreExt) + end + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/gem_version.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/gem_version.rb new file mode 100644 index 0000000000..514010021f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveSupport + # Returns the version of the currently loaded Active Support as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 6 + MINOR = 1 + TINY = 7 + PRE = "2" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/gzip.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/gzip.rb new file mode 100644 index 0000000000..7ffa6d90a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/gzip.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "zlib" +require "stringio" + +module ActiveSupport + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/hash_with_indifferent_access.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/hash_with_indifferent_access.rb new file mode 100644 index 0000000000..4e574cd059 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/hash_with_indifferent_access.rb @@ -0,0 +1,423 @@ +# 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 + # 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 = {}) + 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 + 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(*args) + super(*args.map { |arg| convert_key(arg) }) + 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) + super(*keys.map { |key| convert_key(key) }) + 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) + super(*indices.map { |key| convert_key(key) }, &block) + 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) + slice(*self.keys - keys.map { |key| convert_key(key) }) + end + alias_method :without, :except + + def stringify_keys!; self end + def deep_stringify_keys!; self end + def stringify_keys; dup end + def deep_stringify_keys; dup end + 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(*args, &block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(*args, &block) } + end + + def transform_keys(*args, &block) + return to_enum(:transform_keys) unless block_given? + dup.tap { |hash| hash.transform_keys!(*args, &block) } + end + + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(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 + _new_hash = Hash.new + set_defaults(_new_hash) + + each do |key, value| + _new_hash[key] = convert_value(value, conversion: :to_hash) + end + _new_hash + end + + private + if Symbol.method_defined?(:name) + def convert_key(key) + key.kind_of?(Symbol) ? key.name : key + end + else + def convert_key(key) + key.kind_of?(Symbol) ? key.to_s : key + end + end + + def convert_value(value, conversion: nil) + if value.is_a? Hash + if conversion == :to_hash + value.to_hash + else + value.nested_under_indifferent_access + end + 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 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n.rb new file mode 100644 index 0000000000..39dab1cc71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n.rb @@ -0,0 +1,16 @@ +# 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" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n_railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n_railtie.rb new file mode 100644 index 0000000000..094f65ad6c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/i18n_railtie.rb @@ -0,0 +1,125 @@ +# 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 + + # Set the i18n configuration after initialization since a lot of + # configuration is still usually done in application initializers. + config.after_initialize do |app| + I18n::Railtie.initialize_i18n(app) + end + + # Trigger i18n config before any eager loading has happened + # so it's ready if any classes require it when eager loaded. + config.before_eager_load 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 + forward_raise_on_missing_translations_config(app) + 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 + + directories = watched_dirs_with_extensions(reloadable_paths) + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do + I18n.load_path.keep_if { |p| File.exist?(p) } + 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 + reloader.execute + + @i18n_inited = true + end + + def self.forward_raise_on_missing_translations_config(app) + ActiveSupport.on_load(:action_view) do + self.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + end + + ActiveSupport.on_load(:action_controller) do + AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflections.rb new file mode 100644 index 0000000000..baf1cb3038 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector.rb new file mode 100644 index 0000000000..d77f04c9c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/inflections.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/inflections.rb new file mode 100644 index 0000000000..99228a506f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/inflections.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/i18n" + +module ActiveSupport + module Inflector + extend self + + # 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 'octopus', 'octopi' + # + # inflect.uncountable 'equipment' + # end + # + # New rules are added at the top. So in the example above, the irregular + # rule for octopus 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 + + 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 'octopus', 'octopi' + # 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. + # + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] + else + 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(locale) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/methods.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/methods.rb new file mode 100644 index 0000000000..acb86fe1a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/methods.rb @@ -0,0 +1,400 @@ +# frozen_string_literal: true + +require "active_support/inflections" +require "active_support/core_ext/object/blank" + +module ActiveSupport + # 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 + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } + else + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase } + end + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!("/", "::") + 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 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])/) { ($1 || $2) << "_" } + 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 a "_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.sub!(/\A_+/, "") + unless keep_id_suffix + result.delete_suffix!("_id") + end + result.tr!("_", " ") + + result.gsub!(/([a-z\d]*)/i) do |match| + "#{inflections.acronyms[match.downcase] || match.downcase}" + end + + if capitalize + result.sub!(/\A\w/) { |match| match.upcase } + end + + result + end + + # Converts just the first character 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 + + # 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+ is also aliased as +titlecase+. + # + # 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)..-1] + 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) + if camel_cased_word.blank? || !camel_cased_word.include?("::") + Object.const_get(camel_cased_word) + else + names = camel_cased_word.split("::") + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + 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.blank? + + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/transliterate.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/transliterate.rb new file mode 100644 index 0000000000..c398b25d0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,147 @@ +# 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) + string = string.dup if string.frozen? + 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) + + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/json.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/json.rb new file mode 100644 index 0000000000..d7887175c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/json/decoding.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/json/decoding.rb new file mode 100644 index 0000000000..e40957ef3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/json/decoding.rb @@ -0,0 +1,75 @@ +# 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 + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/json/encoding.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/json/encoding.rb new file mode 100644 index 0000000000..04de632c8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 + # 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\"}" + def self.encode(value, options = nil) + Encoding.json_encoder.new(options).encode(value) + 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) + stringify jsonify value.as_json(options.dup) + end + + private + # Rails does more escaping than the JSON gem natively does (we + # escape \u2028 and \u2029 and optionally >, <, & to work around + # certain browser problems). + ESCAPED_CHARS = { + "\u2028" => '\u2028', + "\u2029" => '\u2029', + ">" => '\u003e', + "<" => '\u003c', + "&" => '\u0026', + } + + ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u + ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u + + # This class wraps all the strings we see and does the extra escaping + class EscapedString < String #:nodoc: + def to_json(*) + if Encoding.escape_html_entities_in_json + s = super + s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s + else + s = super + s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s + end + end + + def to_s + self + end + end + + # Mark these as private so we don't leak encoding-specific constructs + private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, + :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString + + # Convert an object into a "JSON-ready" representation composed of + # primitives like Hash, Array, String, 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 + EscapedString.new(value) + when Numeric, NilClass, TrueClass, FalseClass + value.as_json + when Hash + result = {} + value.each do |k, v| + result[jsonify(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.0.0/gems/activesupport-6.1.7.2/lib/active_support/key_generator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/key_generator.rb new file mode 100644 index 0000000000..21f7ab1bcb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/key_generator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "openssl" + +module ActiveSupport + # 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 + 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 + 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_sha1(@secret, salt, @iterations, key_size) + end + end + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/lazy_load_hooks.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 0000000000..c6f7ccf0a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveSupport + # lazy_load_hooks 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) + 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. + # + # 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 + + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/locale/en.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/locale/en.rb new file mode 100644 index 0000000000..29eb9dec0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/locale/en.yml b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/locale/en.yml new file mode 100644 index 0000000000..725ec3532c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/locale/en.yml @@ -0,0 +1,139 @@ +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 the number (default: $5.00) + 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" + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/log_subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/log_subscriber.rb new file mode 100644 index 0000000000..af90bccebf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/log_subscriber.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" + +module ActiveSupport + # 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 + # def sql(event) + # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # end + # end + # end + # + # And it's finally registered as: + # + # ActiveRecord::LogSubscriber.attach_to :active_record + # + # Since we need to know all instance methods before attaching the log + # subscriber, the line above should be called after your + # ActiveRecord::LogSubscriber definition. + # + # A logger also needs to be set with ActiveRecord::LogSubscriber.logger=. + # This 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 + # + # Log subscriber also has some helpers to deal with logging and automatically + # flushes all logs when the request finishes + # (via action_dispatch.callback notification) in a Rails environment. + class LogSubscriber < Subscriber + # Embed in a String to clear all previous ANSI sequences. + CLEAR = "\e[0m" + BOLD = "\e[1m" + + # 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 << self + def logger + @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) + Rails.logger + end + 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 + end + + def logger + LogSubscriber.logger + end + + def start(name, id, payload) + super if logger + end + + def finish(name, id, payload) + super if logger + rescue => e + if logger + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + end + end + + 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. If a third + # option is set to +true+, it also adds bold to the string. This is based + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. + def color(text, color, bold = false) # :doc: + return text unless colorize_logging + color = self.class.const_get(color.upcase) if color.is_a?(Symbol) + bold = bold ? BOLD : "" + "#{bold}#{color}#{text}#{CLEAR}" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/log_subscriber/test_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/log_subscriber/test_helper.rb new file mode 100644 index 0000000000..3f19ef5009 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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, 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger.rb new file mode 100644 index 0000000000..1e241c13ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger.rb @@ -0,0 +1,93 @@ +# 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 + def self.logger_outputs_to?(logger, *sources) + logdev = logger.instance_variable_get(:@logdev) + logger_source = logdev.dev if logdev.respond_to?(:dev) + sources.any? { |source| source == logger_source } + end + + # Broadcasts logs to multiple loggers. + def self.broadcast(logger) # :nodoc: + Module.new do + define_method(:add) do |*args, &block| + logger.add(*args, &block) + super(*args, &block) + end + + define_method(:<<) do |x| + logger << x + super(x) + end + + define_method(:close) do + logger.close + super() + end + + define_method(:progname=) do |name| + logger.progname = name + super(name) + end + + define_method(:formatter=) do |formatter| + logger.formatter = formatter + super(formatter) + end + + define_method(:level=) do |level| + logger.level = level + super(level) + end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end + + define_method(:silence) do |level = Logger::ERROR, &block| + if logger.respond_to?(:silence) + logger.silence(level) do + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + else + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + end + end + 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 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger_silence.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger_silence.rb new file mode 100644 index 0000000000..8567eff403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger_thread_safe_level.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 0000000000..1de9ecdbbc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "concurrent" +require "fiber" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + included do + cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false + end + + Logger::Severity.constants.each do |severity| + class_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{severity.downcase}? # def debug? + Logger::#{severity} >= level # DEBUG >= level + end # end + EOT + end + + def local_log_id + Fiber.current.__id__ + end + + def local_level + self.class.local_levels[local_log_id] + end + + def local_level=(level) + case level + when Integer + self.class.local_levels[local_log_id] = level + when Symbol + self.class.local_levels[local_log_id] = Logger::Severity.const_get(level.to_s.upcase) + when nil + self.class.local_levels.delete(local_log_id) + else + raise ArgumentError, "Invalid log level: #{level.inspect}" + 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 + + # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+. + # FIXME: Remove when the minimum Ruby version supports overriding Logger#level. + def add(severity, message = nil, progname = nil, &block) #:nodoc: + severity ||= UNKNOWN + progname ||= @progname + + return true if @logdev.nil? || severity < level + + 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) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_encryptor.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_encryptor.rb new file mode 100644 index 0000000000..4cf4a44426 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_encryptor.rb @@ -0,0 +1,224 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/message_verifier" +require "active_support/messages/metadata" + +module ActiveSupport + # 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" + # + # === 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 + prepend Messages::Rotator::Encryptor + + 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 + + module NullVerifier #:nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + # 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. + # + # First additional parameter is used as the signature key for +MessageVerifier+. + # This allows you to specify keys to encrypt and sign data. + # + # 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 - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. + # * :serializer - Object serializer to use. Default is +Marshal+. + def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil) + @secret = secret + @sign_secret = sign_secret + @cipher = cipher || self.class.default_cipher + @digest = digest || "SHA1" unless aead_mode? + @verifier = resolve_verifier + @serializer = serializer || Marshal + 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/. + def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil) + verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + 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/. + def decrypt_and_verify(data, purpose: nil, **) + _decrypt(verifier.verify(data), purpose) + 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 + + private + def _encrypt(value, **metadata_options) + 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(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options)) + encrypted_data << cipher.final + + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob + end + + def _decrypt(encrypted_message, purpose) + cipher = new_cipher + encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) } + + # 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 InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16) + + 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 + + message = Messages::Metadata.verify(decrypted_data, purpose) + @serializer.load(message) if message + rescue OpenSSLCipherError, TypeError, ArgumentError + raise InvalidMessage + end + + def new_cipher + OpenSSL::Cipher.new(@cipher) + end + + attr_reader :verifier + + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end + + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_verifier.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_verifier.rb new file mode 100644 index 0000000000..ba992a17a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/message_verifier.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/metadata" +require "active_support/messages/rotator" + +module ActiveSupport + # +MessageVerifier+ makes it easy to generate and verify messages which are + # signed to prevent tampering. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links + # where the session store isn't suitable or available. + # + # Remember Me: + # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) + # + # In the authentication filter: + # + # id, time = @verifier.verify(cookies[:remember_me]) + # if Time.now < time + # self.current_user = User.find(id) + # end + # + # By default it uses Marshal to serialize the message. If you want to use + # another serialization method, you can set the serializer in the options + # hash upon initialization: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # +:digest+ key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === 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 = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.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+. + # + # @verifier.generate(parcel, expires_in: 1.month) + # @verifier.generate(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned up to the expire time. + # 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 to + # 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 + prepend Messages::Rotator::Verifier + + class InvalidSignature < StandardError; end + + def initialize(secret, digest: nil, serializer: nil) + raise ArgumentError, "Secret should not be nil." unless secret + @secret = secret + @digest = digest || "SHA1" + @serializer = serializer || Marshal + end + + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private 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?(signed_message) + return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? + + data, digest = signed_message.split("--") + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # + # signed_message = verifier.generate 'a private message' + # verifier.verified(signed_message) # => 'a private message' + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # 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 + def verified(signed_message, purpose: nil, **) + if valid_message?(signed_message) + begin + data = signed_message.split("--")[0] + message = Messages::Metadata.verify(decode(data), purpose) + @serializer.load(message) if message + rescue ArgumentError => argument_error + return if argument_error.message.include?("invalid base64") + raise + end + end + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # + # verifier.verify(signed_message) # => 'a private message' + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + def verify(*args, **options) + verified(*args, **options) || raise(InvalidSignature) + 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 's3Krit' + # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" + def generate(value, expires_at: nil, expires_in: nil, purpose: nil) + data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + "#{data}--#{generate_digest(data)}" + end + + private + def encode(data) + ::Base64.strict_encode64(data) + end + + def decode(data) + ::Base64.strict_decode64(data) + end + + def generate_digest(data) + require "openssl" unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/metadata.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/metadata.rb new file mode 100644 index 0000000000..4734cce819 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/metadata.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "time" + +module ActiveSupport + module Messages #:nodoc: + class Metadata #:nodoc: + def initialize(message, expires_at = nil, purpose = nil) + @message, @purpose = message, purpose + @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at + end + + def as_json(options = {}) + { _rails: { message: @message, exp: @expires_at, pur: @purpose } } + end + + class << self + def wrap(message, expires_at: nil, expires_in: nil, purpose: nil) + if expires_at || expires_in || purpose + JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose) + else + message + end + end + + def verify(message, purpose) + extract_metadata(message).verify(purpose) + end + + private + def pick_expiry(expires_at, expires_in) + if expires_at + expires_at.utc.iso8601(3) + elsif expires_in + Time.now.utc.advance(seconds: expires_in).iso8601(3) + end + end + + def extract_metadata(message) + data = JSON.decode(message) rescue nil + + if data.is_a?(Hash) && data.key?("_rails") + new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"]) + else + new(message) + end + end + + def encode(message) + ::Base64.strict_encode64(message) + end + + def decode(message) + ::Base64.strict_decode64(message) + end + end + + def verify(purpose) + @message if match?(purpose) && fresh? + end + + private + def match?(purpose) + @purpose.to_s == purpose.to_s + end + + def fresh? + @expires_at.nil? || Time.now.utc < @expires_at + end + + def parse_expires_at(expires_at) + if ActiveSupport.use_standard_json_time_format + Time.iso8601(expires_at) + else + Time.parse(expires_at) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/rotation_configuration.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 0000000000..eef05fe317 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/rotator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/rotator.rb new file mode 100644 index 0000000000..b19e1851e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/messages/rotator.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*secrets, on_rotation: nil, **options) + super(*secrets, **options) + + @options = options + @rotations = [] + @on_rotation = on_rotation + end + + def rotate(*secrets, **options) + @rotations << build_rotation(*secrets, @options.merge(options)) + end + + module Encryptor + include Rotator + + def decrypt_and_verify(*args, on_rotation: @on_rotation, **options) + super + rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature + run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, **options) } || raise + end + + private + def build_rotation(secret = @secret, sign_secret = @sign_secret, options) + self.class.new(secret, sign_secret, **options) + end + end + + module Verifier + include Rotator + + def verified(*args, on_rotation: @on_rotation, **options) + super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, **options) } + end + + private + def build_rotation(secret = @secret, options) + self.class.new(secret, **options) + end + end + + private + def run_rotations(on_rotation) + @rotations.find do |rotation| + if message = yield(rotation) rescue next + on_rotation&.call + return message + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte.rb new file mode 100644 index 0000000000..3fe3a05e93 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/chars.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/chars.rb new file mode 100644 index 0000000000..d58ab1ba05 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/chars.rb @@ -0,0 +1,177 @@ +# 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/symbol/starts_ends_with" +require "active_support/core_ext/module/delegation" + +module ActiveSupport #:nodoc: + module Multibyte #:nodoc: + # 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 + @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen? + end + + # Forward all undefined methods to the wrapped string. + def method_missing(method, *args, &block) + result = @wrapped_string.__send__(method, *args, &block) + 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.scan(/\X/).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 # => 2 + # 'é'.mb_chars.decompose.to_s.length # => 3 + def decompose + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) + end + + # Performs composition on all the characters. + # + # 'é'.length # => 3 + # 'é'.mb_chars.compose.to_s.length # => 2 + 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 # => 3 + def grapheme_length + @wrapped_string.scan(/\X/).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.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/unicode.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/unicode.rb new file mode 100644 index 0000000000..78906831df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/multibyte/unicode.rb @@ -0,0 +1,82 @@ +# 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"] + + def default_normalization_form + ActiveSupport::Deprecation.warn( + "ActiveSupport::Multibyte::Unicode.default_normalization_form is deprecated and will be removed in Rails 7.0." + ) + end + + def default_normalization_form=(_) + ActiveSupport::Deprecation.warn( + "ActiveSupport::Multibyte::Unicode.default_normalization_form= is deprecated and will be removed in Rails 7.0." + ) + end + + # 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 + + # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. + if !defined?(Rubinius) + # 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 + else + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + + # We can't transcode to the same format, so we choose a nearly-identical encoding. + # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to + # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 + # before returning. + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) + + source = string.dup + out = "".force_encoding(Encoding::UTF_16LE) + + loop do + reader.primitive_convert(source, out) + _, _, _, error_bytes, _ = reader.primitive_errinfo + break if error_bytes.nil? + out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + + reader.finish + + out.encode!(Encoding::UTF_8) + end + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications.rb new file mode 100644 index 0000000000..d1227c2473 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications.rb @@ -0,0 +1,280 @@ +# frozen_string_literal: true + +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" +require "active_support/per_thread_registry" + +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 |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 # => Monotonic time, when the instrumented block started execution + # finish # => Monotonic time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # The +start+ and +finish+ values above represent monotonic time. + # + # For instance, let's store all "render" events in an array: + # + # events = [] + # + # ActiveSupport::Notifications.subscribe('render') do |*args| + # events << ActiveSupport::Notifications::Event.new(*args) + # 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 } + # + # The block in the subscribe call gets the name of the event, start + # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter + # (something like "535801666f04d0298cd6"), and a hash with the payload, in + # that order. + # + # 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 {|*args| ... } + # 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 |*args| + # ... + # 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 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. + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # The +block+ will receive five parameters 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 + # + # If the block passed to the method only takes one parameter, + # it will yield an event object to the block: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @event = event + # end + def subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: false, &block) + end + + 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 + InstrumentationRegistry.instance.instrumenter_for(notifier) + end + end + + # This class is a registry which holds all of the +Instrumenter+ objects + # in a particular thread local. To access the +Instrumenter+ object for a + # particular +notifier+, you can call the following method: + # + # InstrumentationRegistry.instrumenter_for(notifier) + # + # The instrumenters for multiple notifiers are held in a single instance of + # this class. + class InstrumentationRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def instrumenter_for(notifier) + @registry[notifier] ||= Instrumenter.new(notifier) + end + end + + self.notifier = Fanout.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/fanout.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/fanout.rb new file mode 100644 index 0000000000..5fba8347d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/fanout.rb @@ -0,0 +1,259 @@ +# frozen_string_literal: true + +require "mutex_m" +require "concurrent/map" +require "set" +require "active_support/core_ext/object/try" + +module ActiveSupport + module Notifications + # 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 + include Mutex_m + + def initialize + @string_subscribers = Hash.new { |h, k| h[k] = [] } + @other_subscribers = [] + @listeners_for = Concurrent::Map.new + super + end + + def subscribe(pattern = nil, callable = nil, monotonic: false, &block) + subscriber = Subscribers.new(pattern, callable || block, monotonic) + synchronize do + if String === pattern + @string_subscribers[pattern] << subscriber + @listeners_for.delete(pattern) + else + @other_subscribers << subscriber + @listeners_for.clear + end + end + subscriber + end + + def unsubscribe(subscriber_or_name) + synchronize do + case subscriber_or_name + when String + @string_subscribers[subscriber_or_name].clear + @listeners_for.delete(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) + @listeners_for.delete(pattern) + else + @other_subscribers.delete(subscriber_or_name) + @listeners_for.clear + end + end + end + end + + def start(name, id, payload) + listeners_for(name).each { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload, listeners = listeners_for(name)) + listeners.each { |s| s.finish(name, id, payload) } + end + + def publish(name, *args) + listeners_for(name).each { |s| s.publish(name, *args) } + end + + def listeners_for(name) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) + @listeners_for[name] || synchronize do + # use synchronisation when accessing @subscribers + @listeners_for[name] ||= + @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) } + end + end + + def listening?(name) + listeners_for(name).any? + 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 all this to detect a block like `proc { |x| }` vs + # `proc { |*x| }` or `proc { |**x| }` + if listener.respond_to?(:parameters) + params = listener.parameters + if params.length == 1 && params.first.first == :opt + subscriber_class = EventObject + end + end + end + + wrap_all pattern, subscriber_class.new(pattern, listener) + end + + def self.wrap_all(pattern, subscriber) + unless pattern + AllMessages.new(subscriber) + else + subscriber + end + end + + class Matcher #:nodoc: + attr_reader :pattern, :exclusions + + def self.wrap(pattern) + return pattern if String === pattern + new(pattern) + 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 + end + + class Evented #:nodoc: + attr_reader :pattern + + def initialize(pattern, delegate) + @pattern = Matcher.wrap(pattern) + @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + pattern === name + end + + def matches?(name) + pattern && pattern === name + end + + def unsubscribe!(name) + pattern.unsubscribe!(name) + end + end + + class Timed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack] ||= [] + timestack.push Time.now + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack] + started = timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end + end + + class MonotonicTimed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] ||= [] + timestack.push Concurrent.monotonic_time + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] + started = timestack.pop + @delegate.call(name, started, Concurrent.monotonic_time, id, payload) + end + end + + class EventObject < Evented + def start(name, id, payload) + stack = Thread.current[:_event_stack] ||= [] + event = build_event name, id, payload + event.start! + stack.push event + end + + def finish(name, id, payload) + stack = Thread.current[:_event_stack] + event = stack.pop + event.payload = payload + event.finish! + @delegate.call event + end + + private + def build_event(name, id, payload) + ActiveSupport::Notifications::Event.new name, nil, nil, id, payload + end + end + + class AllMessages # :nodoc: + def initialize(delegate) + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def publish(name, *args) + @delegate.publish name, *args + end + + def subscribed_to?(name) + true + end + + def unsubscribe!(*) + false + end + + alias :matches? :=== + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/instrumenter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 0000000000..e1a9fe349c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "securerandom" + +module ActiveSupport + module Notifications + # Instrumenters are stored in a thread local. + class Instrumenter + attr_reader :id + + def initialize(notifier) + @id = unique_id + @notifier = notifier + 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 = {}) + # some of the listeners might have state + listeners_state = start name, payload + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish_with_state listeners_state, name, payload + end + 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, :time, :end, :transaction_id, :children + attr_accessor :payload + + def initialize(name, start, ending, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @children = [] + @cpu_time_start = 0 + @cpu_time_finish = 0 + @allocation_count_start = 0 + @allocation_count_finish = 0 + end + + # Record information at the time this event starts + def start! + @time = now + @cpu_time_start = now_cpu + @allocation_count_start = now_allocations + end + + # Record information at the time this event finishes + def finish! + @cpu_time_finish = now_cpu + @end = now + @allocation_count_finish = now_allocations + end + + # Returns the CPU time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def cpu_time + (@cpu_time_finish - @cpu_time_start) * 1000 + end + + # Returns the idle time time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def idle_time + duration - cpu_time + end + + # Returns the number of allocations made since the call to +start!+ and + # the call to +finish!+ + def allocations + @allocation_count_finish - @allocation_count_start + end + + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 + def duration + 1000.0 * (self.end - time) + end + + def <<(event) + @children << event + end + + def parent_of?(event) + @children.include? event + end + + private + def now + Concurrent.monotonic_time + end + + begin + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) + + def now_cpu + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) + end + rescue + def now_cpu + 0 + end + end + + if defined?(JRUBY_VERSION) + def now_allocations + 0 + end + else + def now_allocations + GC.stat :total_allocated_objects + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper.rb new file mode 100644 index 0000000000..93d4791e42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper.rb @@ -0,0 +1,397 @@ +# frozen_string_literal: true + +module ActiveSupport + 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 a +number+ into a phone number (US by default e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :area_code - Adds parentheses around the area code. + # * :delimiter - Specifies the delimiter to use + # (defaults to "-"). + # * :extension - Specifies an extension to add to the + # end of the generated number. + # * :country_code - Sets the country code for the phone + # number. + # * :pattern - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. + # ==== Examples + # + # number_to_phone(5551234) # => "555-1234" + # number_to_phone('5551234') # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234" + # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234" + # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234" + # number_to_phone('123a456') # => "123a456" + # + # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) + # # => "133-1234-5678" + def number_to_phone(number, options = {}) + NumberToPhoneConverter.convert(number, options) + end + + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. + # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided 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 - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the level of precision (defaults + # to 2). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :unit - Sets the denomination of the currency + # (defaults to "$"). + # * :separator - Sets the separator between the units + # (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :format - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are %u for the + # currency, and %n for the number. + # * :negative_format - Sets the format for negative + # numbers (defaults to prepending a hyphen to the formatted + # number given by :format). Accepts the same fields + # than :format, except %n is here the + # absolute value of the number. + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506" + # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €" + # number_to_currency('123a456') # => "$123a456" + # + # number_to_currency("123a456", raise: true) # => InvalidNumberError + # + # number_to_currency(-0.456789, precision: 0) + # # => "$0" + # number_to_currency(-1234567890.50, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" + # number_to_currency(1234567890.50, precision: 0, round_mode: :up) + # # => "$1,234,567,891" + def number_to_currency(number, options = {}) + NumberToCurrencyConverter.convert(number, options) + end + + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * :format - Specifies the format of the percentage + # string The number field is %n (defaults to "%n%"). + # + # ==== Examples + # + # number_to_percentage(100) # => "100.000%" + # number_to_percentage('98') # => "98.000%" + # number_to_percentage(100, precision: 0) # => "100%" + # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%" + # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%" + # number_to_percentage(1000, locale: :fr) # => "1000,000%" + # number_to_percentage(1000, precision: nil) # => "1000%" + # number_to_percentage('98a') # => "98a%" + # number_to_percentage(100, format: '%n %') # => "100.000 %" + # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%" + def number_to_percentage(number, options = {}) + NumberToPercentageConverter.convert(number, options) + end + + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter_pattern - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. + # + # ==== Examples + # + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited('123456') # => "123,456" + # number_to_delimited(12345678.05) # => "12,345,678.05" + # number_to_delimited(12345678, delimiter: '.') # => "12.345.678" + # number_to_delimited(12345678, delimiter: ',') # => "12,345,678" + # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05" + # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05" + # number_to_delimited('112a') # => "112a" + # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # 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 a +number+ with the specified level of + # :precision (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). + # You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_rounded(111.2345) # => "111.235" + # number_to_rounded(111.2345, precision: 2) # => "111.23" + # number_to_rounded(13, precision: 5) # => "13.00000" + # number_to_rounded(389.32314, precision: 0) # => "389" + # number_to_rounded(111.2345, significant: true) # => "111" + # number_to_rounded(111.2345, precision: 1, significant: true) # => "100" + # number_to_rounded(13, precision: 5, significant: true) # => "13.000" + # number_to_rounded(13, precision: nil) # => "13" + # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390" + # number_to_rounded(111.234, locale: :fr) # => "111,234" + # + # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # + # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3" + # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + def number_to_rounded(number, options = {}) + NumberToRoundedConverter.convert(number, options) + end + + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.46 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. + # + # See number_to_human if you want to pretty-print a + # generic number. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # + # ==== Examples + # + # 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" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # number_to_human_size(483989, precision: 2) # => "470 KB" + # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB" + # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB" + # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" + def number_to_human_size(number, options = {}) + NumberToHumanSizeConverter.convert(number, options) + end + + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (e.g.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). + # + # See number_to_human_size if you want to print a file + # size. + # + # You can also define your own unit-quantifier names if you want + # to use other decimal units (e.g.: 1500 becomes "1.5 + # kilometers", 0.150 becomes "150 milliliters", etc). You may + # define a wide range of unit quantifiers, even fractional ones + # (centi, deci, mili, etc). + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * :units - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: :unit, :ten, + # :hundred, :thousand, :million, + # :billion, :trillion, + # :quadrillion + # * *fractionals*: :deci, :centi, + # :mili, :micro, :nano, + # :pico, :femto + # * :format - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # + # ==== Examples + # + # 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" + # number_to_human(489939, precision: 2) # => "490 Thousand" + # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(489939, precision: 2 + # , round_mode: :down) # => "480 Thousand" + # number_to_human(1234567, precision: 4, + # significant: false) # => "1.2346 Million" + # number_to_human(1234567, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + # + # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12345012345, significant: false) # => "12.345 Billion" + # + # Non-significant zeros after the decimal separator are stripped + # out by default (set :strip_insignificant_zeros to + # +false+ to change that): + # + # number_to_human(12.00001) # => "12" + # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt" + # + # If in your I18n locale you have: + # + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazillion-distance" + # + # Then you could do: + # + # number_to_human(543934, units: :distance) # => "544 kilometers" + # number_to_human(54393498, units: :distance) # => "54400 kilometers" + # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" + # number_to_human(343, units: :distance, precision: 1) # => "300 meters" + # number_to_human(1, units: :distance) # => "1 meter" + # number_to_human(0.34, units: :distance) # => "34 centimeters" + def number_to_human(number, options = {}) + NumberToHumanConverter.convert(number, options) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_converter.rb new file mode 100644 index 0000000000..75509f1eca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_converter.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +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 + end + + def execute + if !number + nil + elsif validate_float? && !valid_float? + 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 }.merge!(i18n_options)) + end + + def translate_in_locale(key, **i18n_options) + translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options)) + end + + def default_value(key) + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + end + + def valid_float? + Float(number) + rescue ArgumentError, TypeError + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_currency_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 0000000000..c0efababa9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + self.namespace = :currency + + def convert + number = self.number.to_s.strip + format = options[:format] + + if number.sub!(/^-/, "") && + (options[:precision] != 0 || number.to_f > 0.5) + format = options[:negative_format] + end + + rounded_number = NumberToRoundedConverter.convert(number, options) + format.gsub("%n", rounded_number).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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_delimited_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 0000000000..351444289c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_human_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_human_converter.rb new file mode 100644 index 0000000000..3f92628501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_human_size_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_human_size_converter.rb new file mode 100644 index 0000000000..a9b7ce263f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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] + + 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) / 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 < base + end + + def base + 1024 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_percentage_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_percentage_converter.rb new file mode 100644 index 0000000000..0c2e190f8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_phone_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_phone_converter.rb new file mode 100644 index 0000000000..21eadfdcc7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_phone_converter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_rounded_converter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 0000000000..f48a5158c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/rounding_helper.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/rounding_helper.rb new file mode 100644 index 0000000000..56b996a35b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/number_helper/rounding_helper.rb @@ -0,0 +1,50 @@ +# 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 significant && options[:precision] > 0 + options[:precision] - digit_count(convert_to_decimal(number)) + else + options[:precision] + end + end + + def significant + options[:significant] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/option_merger.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/option_merger.rb new file mode 100644 index 0000000000..c7f7c0aa6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/option_merger.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/symbol/starts_ends_with" + +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.first.is_a?(Proc) + proc = arguments.pop + 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 + + invoke_method(method, arguments, options, &block) + end + + if RUBY_VERSION >= "2.7" + def invoke_method(method, arguments, options, &block) + if options + @context.__send__(method, *arguments, **options, &block) + else + @context.__send__(method, *arguments, &block) + end + end + else + def invoke_method(method, arguments, options, &block) + arguments << options.dup if options + @context.__send__(method, *arguments, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/ordered_hash.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/ordered_hash.rb new file mode 100644 index 0000000000..ad11524137 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/ordered_options.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/ordered_options.rb new file mode 100644 index 0000000000..ba14907d9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/ordered_options.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +module ActiveSupport + # +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 method_missing(name, *args) + name_string = +name.to_s + if name_string.chomp!("=") + self[name_string] = args.first + else + bangs = name_string.chomp!("!") + + if bangs + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) + else + self[name_string] + end + end + end + + def respond_to_missing?(name, include_private) + true + end + + def extractable_options? + true + end + + def inspect + "#<#{self.class.name} #{super}>" + end + end + + # +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' + class InheritableOptions < OrderedOptions + def initialize(parent = nil) + 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() + end + end + + def inheritable_copy + self.class.new(self) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/parameter_filter.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/parameter_filter.rb new file mode 100644 index 0000000000..134778251f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/parameter_filter.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActiveSupport + # +ParameterFilter+ allows you to specify keys for sensitive data from + # hash-like object and replace corresponding value. Filtering only certain + # sub-keys from a hash is possible by using the dot notation: + # 'credit_card.number'. If a proc is given, each key and value of a hash and + # all sub-hashes are passed to it, where the value or the key can be replaced + # using String#replace or similar methods. + # + # ActiveSupport::ParameterFilter.new([:password]) + # => replaces the value to all keys matching /password/i with "[FILTERED]" + # + # ActiveSupport::ParameterFilter.new([:foo, "bar"]) + # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + # + # ActiveSupport::ParameterFilter.new(["credit_card.code"]) + # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not + # change { file: { code: "xxxx"} } + # + # ActiveSupport::ParameterFilter.new([-> (k, v) do + # v.reverse! if /secret/i.match?(k) + # end]) + # => reverses the value to all keys matching /secret/i + class ParameterFilter + FILTERED = "[FILTERED]" # :nodoc: + + # 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) + @filters = filters + @mask = mask + end + + # Mask value of +params+ if key matches one of filters. + def filter(params) + compiled_filter.call(params) + end + + # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated. + def filter_param(key, value) + @filters.empty? ? value : compiled_filter.value_for_key(key, value) + end + + private + def compiled_filter + @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask) + end + + class CompiledFilter # :nodoc: + def self.compile(filters, mask:) + return lambda { |params| params.dup } if filters.empty? + + strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, 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&.any? + + new regexps, deep_regexps, blocks, mask: mask + end + + attr_reader :regexps, :deep_regexps, :blocks + + def initialize(regexps, deep_regexps, blocks, mask:) + @regexps = regexps + @deep_regexps = deep_regexps&.any? ? deep_regexps : nil + @blocks = blocks + @mask = mask + end + + def call(params, parents = [], original_params = params) + filtered_params = params.class.new + + params.each do |key, value| + filtered_params[key] = value_for_key(key, value, parents, original_params) + end + + filtered_params + end + + def value_for_key(key, value, parents = [], original_params = nil) + parents.push(key) if deep_regexps + if regexps.any? { |r| r.match?(key.to_s) } + value = @mask + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) } + value = @mask + elsif value.is_a?(Hash) + value = call(value, parents, original_params) + elsif value.is_a?(Array) + # If we don't pop the current parent it will be duplicated as we + # process each array value. + parents.pop if deep_regexps + value = value.map { |v| value_for_key(key, v, parents, original_params) } + # Restore the parent stack after processing the array. + parents.push(key) if deep_regexps + elsif blocks.any? + 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 + parents.pop if deep_regexps + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/per_thread_registry.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/per_thread_registry.rb new file mode 100644 index 0000000000..2ab4707937 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/per_thread_registry.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. + # Please use that approach instead. + # + # This module is used to encapsulate access to thread local variables. + # + # Instead of polluting the thread locals namespace: + # + # Thread.current[:connection_handler] + # + # you define a class that extends this module: + # + # module ActiveRecord + # class RuntimeRegistry + # extend ActiveSupport::PerThreadRegistry + # + # attr_accessor :connection_handler + # end + # end + # + # and invoke the declared instance accessors as class methods. So + # + # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler + # + # sets a connection handler local to the current thread, and + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns a connection handler local to the current thread. + # + # This feature is accomplished by instantiating the class and storing the + # instance as a thread local keyed by the class name. In the example above + # a key "ActiveRecord::RuntimeRegistry" is stored in Thread.current. + # The class methods proxy to said thread local instance. + # + # If the class has an initializer, it must accept no arguments. + module PerThreadRegistry + def self.extended(object) + object.instance_variable_set :@per_thread_registry_key, object.name.freeze + end + + def instance + Thread.current[@per_thread_registry_key] ||= new + end + + private + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/proxy_object.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/proxy_object.rb new file mode 100644 index 0000000000..0965fcd2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/proxy_object.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveSupport + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. + class ProxyObject < ::BasicObject + undef_method :== + undef_method :equal? + + # Let ActiveSupport::ProxyObject at least raise exceptions. + def raise(*args) + ::Object.send(:raise, *args) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/rails.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/rails.rb new file mode 100644 index 0000000000..75676a2e47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/railtie.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/railtie.rb new file mode 100644 index 0000000000..a6dd1bb025 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/railtie.rb @@ -0,0 +1,102 @@ +# 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.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_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 + require "active_support/current_attributes/test_helper" + include ActiveSupport::CurrentAttributes::TestHelper + end + end + + initializer "active_support.deprecation_behavior" do |app| + if deprecation = app.config.active_support.deprecation + ActiveSupport::Deprecation.behavior = deprecation + end + + if disallowed_deprecation = app.config.active_support.disallowed_deprecation + ActiveSupport::Deprecation.disallowed_behavior = disallowed_deprecation + end + + if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings + ActiveSupport::Deprecation.disallowed_warnings = disallowed_warnings + 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) + 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 app.config.active_support.use_sha1_digests + ActiveSupport::Deprecation.warn(<<-MSG.squish) + config.active_support.use_sha1_digests is deprecated and will + be removed from Rails 7.0. Use + config.active_support.hash_digest_class = ::Digest::SHA1 instead. + MSG + ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1 + end + + if klass = app.config.active_support.hash_digest_class + ActiveSupport::Digest.hash_digest_class = klass + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/reloader.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/reloader.rb new file mode 100644 index 0000000000..e751866a27 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/reloader.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" +require "active_support/executor" + +module ActiveSupport + #-- + # 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 + executor.wrap do + super + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/rescuable.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/rescuable.rb new file mode 100644 index 0000000000..2b965677f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/rescuable.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # 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 # self defined exception + # rescue_from ActiveRecord::RecordInvalid, with: :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render xml: exception, status: 500 + # end + # + # private + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/secure_compare_rotator.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 0000000000..269703c34a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # 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 + prepend Messages::Rotator + + InvalidMatch = Class.new(StandardError) + + def initialize(value, **_options) + @value = value + end + + def secure_compare!(other_value, on_rotation: @on_rotation) + secure_compare(@value, other_value) || + run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } || + raise(InvalidMatch) + end + + private + def build_rotation(previous_value, _options) + self.class.new(previous_value) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/security_utils.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..aa00474448 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/string_inquirer.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/string_inquirer.rb new file mode 100644 index 0000000000..5ff3f4bfe7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/string_inquirer.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" + +module ActiveSupport + # 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, *arguments) + if method_name.end_with?("?") + self == method_name[0..-2] + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/subscriber.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/subscriber.rb new file mode 100644 index 0000000000..24f8681af8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/subscriber.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" +require "active_support/notifications" + +module ActiveSupport + # 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) + # 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 + @queue_key = [self.class.name, object_id].join "-" + @patterns = {} + super + end + + def start(name, id, payload) + event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload) + event.start! + parent = event_stack.last + parent << event if parent + + event_stack.push event + end + + def finish(name, id, payload) + event = event_stack.pop + event.finish! + event.payload.merge!(payload) + + method = name.split(".").first + send(method, event) + end + + private + def event_stack + SubscriberQueueRegistry.instance.get_queue(@queue_key) + end + end + + # This is a registry for all the event stacks kept for subscribers. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class SubscriberQueueRegistry # :nodoc: + extend PerThreadRegistry + + def initialize + @registry = {} + end + + def get_queue(queue_key) + @registry[queue_key] ||= [] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/tagged_logging.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..0bc4a21401 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/tagged_logging.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "logger" +require "active_support/logger" + +module ActiveSupport + # 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") { 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, "#{tags_text}#{msg}") + end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten! + tags.reject!(&:blank?) + current_tags.concat tags + tags + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + def current_tags + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}" + Thread.current[thread_key] ||= [] + end + + def tags_text + tags = current_tags + if tags.one? + "[#{tags[0]}] " + elsif tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end + end + end + + module LocalTagStorage # :nodoc: + attr_accessor :current_tags + + def self.extended(base) + base.current_tags = [] + end + end + + def self.new(logger) + logger = logger.clone + + if logger.formatter + logger.formatter = logger.formatter.dup + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/test_case.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/test_case.rb new file mode 100644 index 0000000000..7be4108ed7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/test_case.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +gem "minitest" # make sure we get the gem, not stdlib +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/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/file_fixtures" +require "active_support/testing/parallelization" +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 + # database and will not work with system tests at this time. + # + # parallelize(workers: :number_of_processors, with: :threads) + # + # The threaded parallelization uses minitest's parallel executor directly. + # The processes parallelization uses a Ruby DRb server. + def parallelize(workers: :number_of_processors, with: :processes) + workers = Concurrent.physical_processor_count if workers == :number_of_processors + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + return if workers <= 1 + + executor = case with + when :processes + Testing::Parallelization.new(workers) + when :threads + Minitest::Parallel::Executor.new(workers) + else + raise ArgumentError, "#{with} is not a supported parallelization executor." + end + + self.lock_threads = false if defined?(self.lock_threads) && with == :threads + + Minitest.parallel_executor = executor + + parallelize_me! + 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 do |worker| + yield worker + end + 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 do |worker| + yield worker + end + end + end + + alias_method :method_name, :name + + include ActiveSupport::Testing::TaggedLogging + prepend ActiveSupport::Testing::SetupAndTeardown + include ActiveSupport::Testing::Assertions + include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures + extend ActiveSupport::Testing::Declarative + + # test/unit backwards compatibility methods + alias :assert_raise :assert_raises + alias :assert_not_empty :refute_empty + alias :assert_not_equal :refute_equal + alias :assert_not_in_delta :refute_in_delta + alias :assert_not_in_epsilon :refute_in_epsilon + alias :assert_not_includes :refute_includes + alias :assert_not_instance_of :refute_instance_of + alias :assert_not_kind_of :refute_kind_of + alias :assert_no_match :refute_match + alias :assert_not_nil :refute_nil + alias :assert_not_operator :refute_operator + alias :assert_not_predicate :refute_predicate + alias :assert_not_respond_to :refute_respond_to + alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/assertions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/assertions.rb new file mode 100644 index 0000000000..226c142220 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/assertions.rb @@ -0,0 +1,235 @@ +# 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 + + # 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 + 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(&block) + + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" + error = "#{message}.\n#{error}" if message + assert_equal(before_value + diff, exp.call, error) + 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(&block) + + unless from == UNTRACKED + error = "Expected change from #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + refute_equal before, after, error + + unless to == UNTRACKED + error = "Expected change to #{to}\n" + error = "#{message}.\n#{error}" if message + assert to === after, error + 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 + # + # 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, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = assert_nothing_raised(&block) + after = exp.call + + error = "#{expression.inspect} changed" + error = "#{message}.\n#{error}" if message + + if before.nil? + assert_nil after, error + else + assert_equal before, after, error + end + + retval + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/autorun.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/autorun.rb new file mode 100644 index 0000000000..889b41659a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/autorun.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +gem "minitest" + +require "minitest" + +Minitest.autorun diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/constant_lookup.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/constant_lookup.rb new file mode 100644 index 0000000000..51167e9237 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/declarative.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/declarative.rb new file mode 100644 index 0000000000..7c3403684d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/deprecation.rb new file mode 100644 index 0000000000..18d63d2780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/deprecation.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/deprecation" + +module ActiveSupport + module Testing + module Deprecation #:nodoc: + def assert_deprecated(match = nil, deprecator = nil, &block) + 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 + + def assert_not_deprecated(deprecator = nil, &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 + + def collect_deprecations(deprecator = nil) + deprecator ||= ActiveSupport::Deprecation + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/file_fixtures.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 0000000000..4eb7a88576 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/isolation.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/isolation.rb new file mode 100644 index 0000000000..652a10da23 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/isolation.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Isolation + require "thread" + + def self.included(klass) #:nodoc: + klass.class_eval do + parallelize_me! + end + end + + def self.forking_env? + !ENV["NO_FORK"] && Process.respond_to?(:fork) + end + + def run + serialized = run_in_isolation do + super + end + + Marshal.load(serialized) + end + + module Forking + def run_in_isolation(&blk) + read, write = IO.pipe + 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! + end + + write.close + result = read.read + Process.wait2(pid) + result.unpack1("m") + end + end + + module Subprocess + ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) + + # Crazy 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! + 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]) + + begin + Process.wait(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end + + return tmpfile.read.unpack1("m") + end + end + end + end + + include forking_env? ? Forking : Subprocess + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/method_call_assertions.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 0000000000..03c38be481 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,70 @@ +# 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) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }) { 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 + end + + def assert_called_with(object, method_name, args, returns: nil) + mock = Minitest::Mock.new + + if args.all? { |arg| arg.is_a?(Array) } + args.each { |arg| mock.expect(:call, returns, arg) } + else + mock.expect(:call, returns, args) + end + + object.stub(method_name, mock) { yield } + + mock.verify + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..fc6e689260 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization.rb @@ -0,0 +1,51 @@ +# 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 shutdown + @queue_server.shutdown + @worker_pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/server.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/server.rb new file mode 100644 index 0000000000..492101e297 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/server.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + 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.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 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/worker.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/worker.rb new file mode 100644 index 0000000000..60c504a2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/parallelization/worker.rb @@ -0,0 +1,100 @@ +# 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) + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/setup_and_teardown.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/setup_and_teardown.rb new file mode 100644 index 0000000000..35321cd157 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/setup_and_teardown.rb @@ -0,0 +1,55 @@ +# 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) + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/stream.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/stream.rb new file mode 100644 index 0000000000..f895a74644 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/stream.rb @@ -0,0 +1,43 @@ +# 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 + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/tagged_logging.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/tagged_logging.rb new file mode 100644 index 0000000000..9ca50c7918 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/time_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/time_helpers.rb new file mode 100644 index 0000000000..ec97381e90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/time/calculations" +require "concurrent/map" + +module ActiveSupport + module Testing + # Manages stubs for TimeHelpers + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = Concurrent::Map.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}" + + @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. + # + # 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, &block) + travel_to Time.now + duration, &block + end + + # Changes current time to the given time by stubbing +Time.now+, + # +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). + # + # 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) + if block_given? && simple_stubs.stubbing(Time, :now) + 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 + else + now = date_or_time.to_time.change(usec: 0) + end + + simple_stubs.stub_object(Time, :now) { at(now.to_i) } + simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + simple_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 + yield + ensure + travel_back + 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+. + # + # 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(&block) + travel_to Time.now, &block + end + + private + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/time.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/time.rb new file mode 100644 index 0000000000..51854675bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/time_with_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/time_with_zone.rb new file mode 100644 index 0000000000..03d49d597f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/time_with_zone.rb @@ -0,0 +1,585 @@ +# frozen_string_literal: true + +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 + # 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_s(: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 + # Report class name as 'Time' to thwart type checking. + def self.name + "Time" + end + + 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 # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00" + def inspect + "#{time.strftime('%a, %d %b %Y %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").to_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").to_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.tag = "!ruby/object:ActiveSupport::TimeWithZone" + 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_s(:rfc822) + end + alias_method :rfc822, :rfc2822 + + # Returns a string of the object's date and time. + # Accepts an optional format: + # * :default - default value, mimics Ruby Time#to_s format. + # * :db - format outputs time in UTC :db time. See Time#to_formatted_s(:db). + # * Any key in Time::DATE_FORMATS can be used. See active_support/core_ext/time/conversions.rb. + def to_s(format = :default) + if format == :db + utc.to_s(format) + elsif formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + end + end + alias_method :to_formatted_s, :to_s + + # 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 + result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) + 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. Then it will return a Float of the difference + # between the two times that represents the difference between the current + # object's time and the +other+ time. + # + # 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) + to_time - other.to_time + elsif duration_of_variable_length?(other) + method_missing(:-, other) + else + result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(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.116992711 EST -05:00 + # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.116992711 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 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 + @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 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) + return false if sym.to_sym == :acts_like_date? + 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(sym, *args, &block) + wrap_with_time_zone time.__send__(sym, *args, &block) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, inspect), 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.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) } + 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 diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/values/time_zone.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/values/time_zone.rb new file mode 100644 index 0000000000..d4160eacbe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/values/time_zone.rb @@ -0,0 +1,582 @@ +# frozen_string_literal: true + +require "tzinfo" +require "concurrent/map" + +module ActiveSupport + # 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", + "Astana" => "Asia/Dhaka", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Colombo", + "Almaty" => "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/Melbourne", + "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 + + # 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 String + begin + @lazy_zones_map[arg] ||= create(arg) + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + 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 + + # 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). + def initialize(name, utc_offset = nil, tzinfo = nil) + @name = name + @utc_offset = utc_offset + @tzinfo = tzinfo || TimeZone.find_tzinfo(name) + end + + # 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) + raise ArgumentError, "invalid date" if str.nil? + + parts = Date._iso8601(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + 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 + 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) + 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 + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_utc(time) + tzinfo.period_for_utc(time) + end + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_local(time, dst = true) + tzinfo.period_for_local(time, dst) { |periods| periods.last } + end + + def periods_for_local(time) #:nodoc: + tzinfo.periods_for_local(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.0.0/gems/activesupport-6.1.7.2/lib/active_support/version.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/version.rb new file mode 100644 index 0000000000..928838c837 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveSupport + # Returns the version of the currently loaded ActiveSupport as a Gem::Version + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini.rb new file mode 100644 index 0000000000..f6ae08bb5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini.rb @@ -0,0 +1,201 @@ +# 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', '=0.9.7' + # 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", + "Array" => "array", + "Hash" => "hash" + } + end + + FORMATTING = { + "symbol" => Proc.new { |symbol| symbol.to_s }, + "date" => Proc.new { |date| date.to_s(:db) }, + "dateTime" => Proc.new { |time| time.xmlschema }, + "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 }, + "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) }, + "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 + + # TODO: Add support for other encodings + def _parse_binary(bin, entity) + case entity["encoding"] + when "base64" + ::Base64.decode64(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 current_thread_backend + Thread.current[:xml_mini_backend] + end + + def current_thread_backend=(name) + Thread.current[: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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/jdom.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/jdom.rb new file mode 100644 index 0000000000..12ca19a76f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/jdom.rb @@ -0,0 +1,182 @@ +# 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__" + + NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE + DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE + PROCESSING_INSTRUCTION_NODE TEXT_NODE} + + node_type_map = {} + NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type } + + # 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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/libxml.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/libxml.rb new file mode 100644 index 0000000000..c2e999ef6c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/libxmlsax.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/libxmlsax.rb new file mode 100644 index 0000000000..ac8acdfc3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogiri.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogiri.rb new file mode 100644 index 0000000000..f76513f48b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogiri.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogirisax.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogirisax.rb new file mode 100644 index 0000000000..55cd72e093 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/nokogirisax.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "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.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/rexml.rb b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.2/lib/active_support/xml_mini/rexml.rb new file mode 100644 index 0000000000..c700959f71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7.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 + $stderr.puts "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.0.0/gems/bundler-2.2.23/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/CHANGELOG.md new file mode 100644 index 0000000000..9fda3843a7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/CHANGELOG.md @@ -0,0 +1,3727 @@ +# 2.2.23 (July 9, 2021) + +## Enhancements: + + - Fix `bundle install` on truffleruby selecting incorrect variant for `sorbet-static` gem [#4625](https://github.com/rubygems/rubygems/pull/4625) + - Spare meaningless warning on read-only bundle invocations [#4724](https://github.com/rubygems/rubygems/pull/4724) + +## Bug fixes: + + - Fix incorrect warning about duplicated gems in the Gemfile [#4732](https://github.com/rubygems/rubygems/pull/4732) + - Fix `bundle plugin install foo` crashing [#4734](https://github.com/rubygems/rubygems/pull/4734) + +# 2.2.22 (July 6, 2021) + +## Enhancements: + + - Never downgrade indirect dependencies when running `bundle update` [#4713](https://github.com/rubygems/rubygems/pull/4713) + - Fix `getaddrinfo` errors not treated as fatal on non darwin platforms [#4703](https://github.com/rubygems/rubygems/pull/4703) + +## Bug fixes: + + - Fix `bundle update ` sometimes hanging and `bundle lock --update` not being able to update an insecure lockfile to the new format if it requires downgrades [#4652](https://github.com/rubygems/rubygems/pull/4652) + - Fix edge case combination of DSL methods and duplicated sources causing gems to not be found [#4711](https://github.com/rubygems/rubygems/pull/4711) + - Fix `bundle doctor` crashing when finding a broken symlink [#4707](https://github.com/rubygems/rubygems/pull/4707) + - Fix incorrect re-resolve edge case [#4700](https://github.com/rubygems/rubygems/pull/4700) + - Fix some gems being unintentionally locked under multiple lockfile sections [#4701](https://github.com/rubygems/rubygems/pull/4701) + - Fix `--conservative` flag unexpectedly updating indirect dependencies [#4692](https://github.com/rubygems/rubygems/pull/4692) + +# 2.2.21 (June 23, 2021) + +## Security fixes: + + - Auto-update insecure lockfile to split GEM source sections whenever possible [#4647](https://github.com/rubygems/rubygems/pull/4647) + +## Enhancements: + + - Use a more limited number of threads when fetching in parallel from the Compact Index API [#4670](https://github.com/rubygems/rubygems/pull/4670) + - Update TODO link in bundle gem template to https [#4671](https://github.com/rubygems/rubygems/pull/4671) + +## Bug fixes: + + - Fix `bundle install --local` hitting the network when `cache_all_platforms` configured [#4677](https://github.com/rubygems/rubygems/pull/4677) + +# 2.2.20 (June 11, 2021) + +## Enhancements: + + - Don't print bug report template on server side errors [#4663](https://github.com/rubygems/rubygems/pull/4663) + - Don't load `resolv` unnecessarily [#4640](https://github.com/rubygems/rubygems/pull/4640) + +## Bug fixes: + + - Fix `bundle outdated` edge case [#4648](https://github.com/rubygems/rubygems/pull/4648) + - Fix `bundle check` with scoped rubygems sources [#4639](https://github.com/rubygems/rubygems/pull/4639) + +## Performance: + + - Don't use `extra_rdoc_files` with md files in gemspec to make installing bundler with docs faster [#4628](https://github.com/rubygems/rubygems/pull/4628) + +# 2.2.19 (May 31, 2021) + +## Bug fixes: + + - Restore support for configuration keys with dashes [#4582](https://github.com/rubygems/rubygems/pull/4582) + - Fix some cached gems being unintentionally ignored when using rubygems 3.2.18 [#4623](https://github.com/rubygems/rubygems/pull/4623) + +# 2.2.18 (May 25, 2021) + +## Security fixes: + + - Fix dependency confusion issues with implicit dependencies [#4609](https://github.com/rubygems/rubygems/pull/4609) + +## Enhancements: + + - Use simpler notation for generated `required_ruby_version` [#4598](https://github.com/rubygems/rubygems/pull/4598) + - Undeprecate bundle show [#4586](https://github.com/rubygems/rubygems/pull/4586) + - Make sure link to new issue uses the proper template [#4592](https://github.com/rubygems/rubygems/pull/4592) + +## Bug fixes: + + - Fix platform specific gems being removed from the lockfile [#4580](https://github.com/rubygems/rubygems/pull/4580) + +# 2.2.17 (May 5, 2021) + +## Enhancements: + + - Improve authentication required error message to include an alternative using `ENV` [#4565](https://github.com/rubygems/rubygems/pull/4565) + - Discard partial range responses without etag [#4563](https://github.com/rubygems/rubygems/pull/4563) + - Fix configuring ENV for a gem server with a name including dashes [#4571](https://github.com/rubygems/rubygems/pull/4571) + - Redact credentials from `bundle env` and `bundle config` [#4566](https://github.com/rubygems/rubygems/pull/4566) + - Redact all sources in verbose mode [#4564](https://github.com/rubygems/rubygems/pull/4564) + - Improve `bundle pristine` error if `BUNDLE_GEMFILE` does not exist [#4536](https://github.com/rubygems/rubygems/pull/4536) + - [CurrentRuby] Add 3.0 as a known minor [#4535](https://github.com/rubygems/rubygems/pull/4535) + - Prefer File.read instead of IO.read [#4530](https://github.com/rubygems/rubygems/pull/4530) + - Add space after open curly bracket in Gemfile and gems.rb template [#4518](https://github.com/rubygems/rubygems/pull/4518) + +## Bug fixes: + + - Make sure specs are fetched from the right source when materializing [#4562](https://github.com/rubygems/rubygems/pull/4562) + - Fix `bundle cache` with an up-to-date lockfile and specs not already installed [#4554](https://github.com/rubygems/rubygems/pull/4554) + - Ignore `deployment` setting in inline mode [#4523](https://github.com/rubygems/rubygems/pull/4523) + +## Performance: + + - Don't materialize resolutions when not necessary [#4556](https://github.com/rubygems/rubygems/pull/4556) + +# 2.2.16 (April 8, 2021) + +## Enhancements: + + - Add `--github-username` option and config to `bundle gem` [#3687](https://github.com/rubygems/rubygems/pull/3687) + - Bump vendored `tmpdir` library copy [#4506](https://github.com/rubygems/rubygems/pull/4506) + - Add `rake build:checksum` task to build checksums for a gem package [#4156](https://github.com/rubygems/rubygems/pull/4156) + - Enable bundler-cache for GitHub Actions template [#4498](https://github.com/rubygems/rubygems/pull/4498) + - Improve `bundle info` error when gem is on a "disabled" group [#4492](https://github.com/rubygems/rubygems/pull/4492) + - Small tweak to yank message [#4494](https://github.com/rubygems/rubygems/pull/4494) + - Don't show duplicate entries in `bundle outdated` output [#4474](https://github.com/rubygems/rubygems/pull/4474) + - Never downgrade top level gems when running `bundle update` [#4473](https://github.com/rubygems/rubygems/pull/4473) + +## Bug fixes: + + - Fix incorrect logic for filtering metadata matching candidates [#4497](https://github.com/rubygems/rubygems/pull/4497) + +# 2.2.15 (March 19, 2021) + +## Enhancements: + + - Add a hint about bundler installing executables for path gems [#4461](https://github.com/rubygems/rubygems/pull/4461) + - Warn lockfiles with incorrect resolutions [#4459](https://github.com/rubygems/rubygems/pull/4459) + - Don't generate duplicate redundant sources in the lockfile [#4456](https://github.com/rubygems/rubygems/pull/4456) + +## Bug fixes: + + - Respect running ruby when resolving platforms [#4449](https://github.com/rubygems/rubygems/pull/4449) + +# 2.2.14 (March 8, 2021) + +## Security fixes: + + - Lock GEM sources separately and fix locally installed specs confusing bundler [#4381](https://github.com/rubygems/rubygems/pull/4381) + +## Bug fixes: + + - Make `rake` available to other gems' installers right after it's installed [#4428](https://github.com/rubygems/rubygems/pull/4428) + - Fix encoding issue on compact index updater [#4362](https://github.com/rubygems/rubygems/pull/4362) + +# 2.2.13 (March 3, 2021) + +## Enhancements: + + - Respect user configured default branch in README links in new generated gems [#4303](https://github.com/rubygems/rubygems/pull/4303) + +## Bug fixes: + + - Fix gems sometimes being pulled from irrelevant sources [#4418](https://github.com/rubygems/rubygems/pull/4418) + +# 2.2.12 (March 1, 2021) + +## Bug fixes: + + - Fix sporadic warnings about `nil` gemspec on install/update and make those faster [#4409](https://github.com/rubygems/rubygems/pull/4409) + - Fix deployment install with duplicate path gems added to Gemfile [#4410](https://github.com/rubygems/rubygems/pull/4410) + +# 2.2.11 (February 17, 2021) + +## Bug fixes: + + - Revert disable_multisource changes [#4385](https://github.com/rubygems/rubygems/pull/4385) + +# 2.2.10 (February 15, 2021) + +## Security fixes: + + - Fix source priority for transitive dependencies and split lockfile rubygems source sections [#3655](https://github.com/rubygems/rubygems/pull/3655) + +## Bug fixes: + + - Fix adding platforms to lockfile sometimes conflicting on ruby requirements [#4371](https://github.com/rubygems/rubygems/pull/4371) + - Fix bundler sometimes choosing ruby variants over java ones [#4367](https://github.com/rubygems/rubygems/pull/4367) + +## Documentation: + + - Update man pages to reflect to new default for bundle install jobs [#4188](https://github.com/rubygems/rubygems/pull/4188) + +# 2.2.9 (February 8, 2021) + +## Enhancements: + + - Stop removing existing platforms when force_ruby_platform is true [#4336](https://github.com/rubygems/rubygems/pull/4336) + +## Bug fixes: + + - Don't install platform specific gems on truffleruby [#4333](https://github.com/rubygems/rubygems/pull/4333) + +# 2.2.8 (February 2, 2021) + +## Enhancements: + + - Add a CHANGELOG.md file to gems generated by `bundle gem` [#4093](https://github.com/rubygems/rubygems/pull/4093) + - Support gemified `set` [#4297](https://github.com/rubygems/rubygems/pull/4297) + +## Bug fixes: + + - Fix standalone Kernel.require visibility [#4337](https://github.com/rubygems/rubygems/pull/4337) + +## Performance: + + - Fix resolver edge cases and speed up bundler [#4277](https://github.com/rubygems/rubygems/pull/4277) + +# 2.2.7 (January 26, 2021) + +## Enhancements: + + - Improve error messages when dependency on bundler conflicts with running version [#4308](https://github.com/rubygems/rubygems/pull/4308) + - Avoid showing platforms with requirements in error messages [#4310](https://github.com/rubygems/rubygems/pull/4310) + - Introduce disable_local_revision_check config [#4237](https://github.com/rubygems/rubygems/pull/4237) + - Reverse rubygems require mixin with bundler standalone [#4299](https://github.com/rubygems/rubygems/pull/4299) + +## Bug fixes: + + - Fix releasing from a not yet pushed branch [#4309](https://github.com/rubygems/rubygems/pull/4309) + - Install cache only once if it already exists [#4304](https://github.com/rubygems/rubygems/pull/4304) + - Fix `force_ruby_platform` no longer being respected [#4302](https://github.com/rubygems/rubygems/pull/4302) + +## Performance: + + - Fix resolver dependency comparison [#4289](https://github.com/rubygems/rubygems/pull/4289) + +# 2.2.6 (January 18, 2021) + +## Enhancements: + + - Improve resolver debugging [#4288](https://github.com/rubygems/rubygems/pull/4288) + +## Bug fixes: + + - Fix dependency locking for path source [#4293](https://github.com/rubygems/rubygems/pull/4293) + +## Performance: + + - Speed up complex dependency resolves by creating DepProxy factory and cache [#4216](https://github.com/rubygems/rubygems/pull/4216) + +# 2.2.5 (January 11, 2021) + +## Enhancements: + + - Improve rubocop setup in the new gem template [#4220](https://github.com/rubygems/rubygems/pull/4220) + - Support repositories with default branch not named master [#4224](https://github.com/rubygems/rubygems/pull/4224) + +## Bug fixes: + + - Let Net::HTTP decompress the index instead of doing it manually [#4081](https://github.com/rubygems/rubygems/pull/4081) + - Workaround for another jruby crash when autoloading a constant [#4252](https://github.com/rubygems/rubygems/pull/4252) + - Fix another performance regression in the resolver [#4243](https://github.com/rubygems/rubygems/pull/4243) + - Restore support for old git versions [#4233](https://github.com/rubygems/rubygems/pull/4233) + - Give a proper error if cache path does not have write access [#4215](https://github.com/rubygems/rubygems/pull/4215) + - Fix running `rake release` from an ambiguous ref [#4219](https://github.com/rubygems/rubygems/pull/4219) + +# 2.2.4 (December 31, 2020) + +## Bug fixes: + + - Fix bundle man pages display on truffleruby [#4209](https://github.com/rubygems/rubygems/pull/4209) + - Fix Windows + JRuby no longer being able to install git sources [#4196](https://github.com/rubygems/rubygems/pull/4196) + +# 2.2.3 (December 22, 2020) + +## Bug fixes: + + - Restore full compatibility with previous lockfiles [#4179](https://github.com/rubygems/rubygems/pull/4179) + - Add all matching variants with the same platform specificity to the lockfile [#4180](https://github.com/rubygems/rubygems/pull/4180) + - Fix bundler installing gems for a different platform when running in frozen mode and current platform not in the lockfile [#4172](https://github.com/rubygems/rubygems/pull/4172) + - Fix crash when `bundle exec`'ing to bundler [#4175](https://github.com/rubygems/rubygems/pull/4175) + +# 2.2.2 (December 17, 2020) + +## Bug fixes: + + - Fix resolver crash when a candidate has 0 matching platforms [#4163](https://github.com/rubygems/rubygems/pull/4163) + - Restore change to copy global with/without config locally upon `bundle install` [#4154](https://github.com/rubygems/rubygems/pull/4154) + +# 2.2.1 (December 14, 2020) + +## Bug fixes: + + - Ad-hoc fix for platform regression [#4127](https://github.com/rubygems/rubygems/pull/4127) + - Workaround JRuby + Windows issue with net-http-persistent vendored code [#4138](https://github.com/rubygems/rubygems/pull/4138) + - Reset also root when in a nested invocation [#4140](https://github.com/rubygems/rubygems/pull/4140) + - Restore 2.1.4 resolution times [#4134](https://github.com/rubygems/rubygems/pull/4134) + - Fix `bundle outdated --strict` crash [#4133](https://github.com/rubygems/rubygems/pull/4133) + - Autoload `Bundler::RemoteSpecification` to workaround crash on jruby [#4114](https://github.com/rubygems/rubygems/pull/4114) + +# 2.2.0 (December 7, 2020) + +## Enhancements: + + - New gem template: prefer `require_relative` to `require` [#4066](https://github.com/rubygems/rubygems/pull/4066) + - Always show underlying error when fetching specs fails [#4061](https://github.com/rubygems/rubygems/pull/4061) + - Add `--all-platforms` flag to `bundle binstubs` to generate binstubs for all platforms [#3886](https://github.com/rubygems/rubygems/pull/3886) + - Improve gem not found in source error messages [#4019](https://github.com/rubygems/rubygems/pull/4019) + - Revert resolving all Gemfile platforms automatically [#4052](https://github.com/rubygems/rubygems/pull/4052) + - Remove extra empty line from README template [#4041](https://github.com/rubygems/rubygems/pull/4041) + - Lazily load `erb` [#4011](https://github.com/rubygems/rubygems/pull/4011) + +## Bug fixes: + + - Fix `Bundler::Plugin::API::Source#to_s` having empty source type [#4084](https://github.com/rubygems/rubygems/pull/4084) + - Raise consistent errors with or without `bundle exec` [#4063](https://github.com/rubygems/rubygems/pull/4063) + - Fix edge case resulting in a crash when using `zeitwerk` inside a nested `bundle exec` invocation [#4062](https://github.com/rubygems/rubygems/pull/4062) + - Enable `specific_platform` by default [#4015](https://github.com/rubygems/rubygems/pull/4015) + - Prevent remove command from deleting gemfile lines that are comments [#4045](https://github.com/rubygems/rubygems/pull/4045) + - Fix issue with `cache_all_platforms` and `specific_platform` configured [#4042](https://github.com/rubygems/rubygems/pull/4042) + - Fix incorrect error message on Windows [#4039](https://github.com/rubygems/rubygems/pull/4039) + - Make printed drive letters consistent on Windows [#4038](https://github.com/rubygems/rubygems/pull/4038) + - Load rubygems plugins from RUBYLIB during `bundle install` and `bundle update` [#3534](https://github.com/rubygems/rubygems/pull/3534) + - Fix `specific_platform` and `cache_all` with `bundle cache --all-platforms` [#4022](https://github.com/rubygems/rubygems/pull/4022) + - Bring back the possibility to install a plugin from path [#4020](https://github.com/rubygems/rubygems/pull/4020) + - Move ronn pages to lib [#3997](https://github.com/rubygems/rubygems/pull/3997) + - Fix fileutils double load when using `bundler/inline` [#3991](https://github.com/rubygems/rubygems/pull/3991) + - Accept responses with no etag header [#3865](https://github.com/rubygems/rubygems/pull/3865) + +## Documentation: + + - Fix typo of `bundle-install.1` (v2.1) [#4079](https://github.com/rubygems/rubygems/pull/4079) + - Add commented out example and more information link to generated gemspec [#4034](https://github.com/rubygems/rubygems/pull/4034) + +# 2.2.0.rc.2 (October 6, 2020) + +## Features: + + - Add `bundle fund` command [#3390](https://github.com/rubygems/rubygems/pull/3390) + +## Enhancements: + + - Fix ls-files matching regexp [#3845](https://github.com/rubygems/rubygems/pull/3845) + - Remove redundant `bundler/setup` require from `spec_helper.rb` generated by `bundle gem` [#3791](https://github.com/rubygems/rubygems/pull/3791) + +## Bug fixes: + + - Deduplicate spec groups [#3965](https://github.com/rubygems/rubygems/pull/3965) + - Fix some cases of running `bundler` on a path including brackets [#3854](https://github.com/rubygems/rubygems/pull/3854) + - Don't warn when deinit'ing submodules [#3969](https://github.com/rubygems/rubygems/pull/3969) + - Make `bundle clean --force` leave default gem executables untouched [#3907](https://github.com/rubygems/rubygems/pull/3907) + - Prioritize `path.system` over `path` when it makes sense [#3933](https://github.com/rubygems/rubygems/pull/3933) + - Sort requirements in Gem::Requirement to succeed comparison with different order [#3889](https://github.com/rubygems/rubygems/pull/3889) + - Print bug report template to standard error [#3924](https://github.com/rubygems/rubygems/pull/3924) + - Restore `bundle cache --all` in all cases [#3914](https://github.com/rubygems/rubygems/pull/3914) + - Move shebang to the top of `bin/console` template [#3927](https://github.com/rubygems/rubygems/pull/3927) + - Fix platform issues when running under a frozen bundle [#3909](https://github.com/rubygems/rubygems/pull/3909) + - Fix deprecation messages for `bundle install` flags, the config should be --local as before [#3917](https://github.com/rubygems/rubygems/pull/3917) + - Look for absolute path when removing bundler/setup from RUBYOPT in Bundler.unbundled_env method [#3877](https://github.com/rubygems/rubygems/pull/3877) + - Fix incorrect re-resolution when path gem excluded and not available [#3902](https://github.com/rubygems/rubygems/pull/3902) + - Fix error when building error message in `bundler/inline` [#3901](https://github.com/rubygems/rubygems/pull/3901) + - Fix regression related to locked ruby [#3900](https://github.com/rubygems/rubygems/pull/3900) + - Expand load paths in standalone setup.rb file [#3522](https://github.com/rubygems/rubygems/pull/3522) + - Fix broken exception recovery code when installing plugins [#3487](https://github.com/rubygems/rubygems/pull/3487) + - Fix incorrect build info on development versions of bundler, and on bundler versions installed as a default gem [#3778](https://github.com/rubygems/rubygems/pull/3778) + - Avoid autoloading `openssl` to try help with jruby load service issues [#3809](https://github.com/rubygems/rubygems/pull/3809) + - Fix `rake release` pushing all local tags instead of only the release tag [#3785](https://github.com/rubygems/rubygems/pull/3785) + - Fix `rake release` aborting when credentials file is missing, even if properly configured through XDG [#3783](https://github.com/rubygems/rubygems/pull/3783) + +## Deprecations: + + - Deprecate `bundle cache --all` flag [#3932](https://github.com/rubygems/rubygems/pull/3932) + +## Documentation: + + - Correct grammar in Gemfile docs [#3990](https://github.com/rubygems/rubygems/pull/3990) + - Fix typo in `bundle pristine` warning message [#3959](https://github.com/rubygems/rubygems/pull/3959) + - Improve human readable fallback version of CLI help messages [#3921](https://github.com/rubygems/rubygems/pull/3921) + - Note CLI flag deprecations in documentation [#3915](https://github.com/rubygems/rubygems/pull/3915) + - Update man page and deprecation warning for binstubs --all [#3872](https://github.com/rubygems/rubygems/pull/3872) + +# 2.2.0.rc.1 (July 2, 2020) + +## Features: + + - Windows support. There's still gotchas and unimplemented features, but a Windows CI is now enforced. + - Full multiplatform support. Bundler should now seamlessly handle multiplatform `Gemfile` or `gems.rb` files. + +## Enhancements: + + - `bundle info` now includes gem metadata [#7376](https://github.com/rubygems/bundler/pull/7376) + - `bundle list --without-group` and `bundle list --only-group` now support space separated list of groups in addition to single groups [#7404](https://github.com/rubygems/bundler/pull/7404) + - `bundle gem` now supports a `--rubocop` flag that adds the `rubocop` gem to the new gem layout [#6455](https://github.com/rubygems/bundler/pull/6455) + - `bundle gem` now supports `--test-unit` in addition to `rspec` and `minitest` as a value for its `--test` option [#5521](https://github.com/rubygems/bundler/pull/5521) + - `bundle install` now uses the available number of processors automatically for concurrent gem install, except for Windows where it still uses a single thread by default [#3393](https://github.com/rubygems/rubygems/pull/3393) and [#3718](https://github.com/rubygems/rubygems/pull/3718) + - Report Gitlab CI within bundler user-agent string [#3432](https://github.com/rubygems/rubygems/pull/3432) + - Add `bundle plugin uninstall` [#3482](https://github.com/rubygems/rubygems/pull/3482) + - `bundle gem` now supports a `--ci` flag and a `gem.ci` configuration that adds CI config files for the main CI providers to the generated gem skeleton [#3667](https://github.com/rubygems/rubygems/pull/3667) + - Allow setting a tag prefix to be used by release tasks [#3766](https://github.com/rubygems/rubygems/pull/3766) + - `bundle outdated` now prints output in columns for better readability [#4474](https://github.com/rubygems/bundler/pull/4474) + - bundler's `release` rake task now prints a better message when not being logged in and trying to push a gem [#7513](https://github.com/rubygems/bundler/pull/7513) + - `BUNDLE_APP_CONFIG` environment variable is now documented [#7563](https://github.com/rubygems/bundler/pull/7563) + - Original exception is now reported when bundler fails to load OpenSSL [#7527](https://github.com/rubygems/bundler/pull/7527) + - RVM specific instructions for recompiling ruby is no longer recommended when bundler fails to load OpenSSL [#7597](https://github.com/rubygems/bundler/pull/7597) + - Improve resolver debugging out from resolver [#7589](https://github.com/rubygems/bundler/pull/7589) and [#7590](https://github.com/rubygems/bundler/pull/7590) + - Clarify `bundle config --local` docs [#3408](https://github.com/rubygems/rubygems/pull/3408) + - Make sure to not "leak" to a different bundler install under any circumstances [#3595](https://github.com/rubygems/rubygems/pull/3595) + - Make sure users messing with `$;` doesn't affect us [#3602](https://github.com/rubygems/rubygems/pull/3602) + - Remove explicit psych activation which could potentially lead to packaging-specific issues [#3638](https://github.com/rubygems/rubygems/pull/3638) + - Deprecate `--no-deployment` flag and never recommend it [#3657](https://github.com/rubygems/rubygems/pull/3657) + - `bundle gem` test framework defaults to the `gem.test` setting and asks for a value without overwriting configuration if `-t` without a value is passed explicitly [#3544](https://github.com/rubygems/rubygems/pull/3544) + - `bundle gem` now ships with a default `.rubocop.yml` file and an offense free initial gem skeleton [#3731](https://github.com/rubygems/rubygems/pull/3731), [#3740](https://github.com/rubygems/rubygems/pull/3740), [#3765](https://github.com/rubygems/rubygems/pull/3765) + - Remove some requires that might workaround some autoloading issues on jruby [#3771](https://github.com/rubygems/rubygems/pull/3771) + - Unswallow an error that should make some random crashes on jruby easier to troubleshoot [#3774](https://github.com/rubygems/rubygems/pull/3774) + +## Bug fixes: + + - Fix `bundle pristine` removing gems with local overrides. Be conservative by printing a warning and skipping the removal [#7423](https://github.com/rubygems/bundler/pull/7423) + - Fix multiplaform resolution edge cases [#7522](https://github.com/rubygems/bundler/pull/7522) and [#7578](https://github.com/rubygems/bundler/pull/7578) + - Fix ruby version conflicts not displaying the current ruby version [7559](https://github.com/rubygems/bundler/pull/7559) + - Fix `Gemfile` or `gems.rb` files containing `:path` gems using relative paths not working when the app is packaged as a `jar` with `warbler` [#7614](https://github.com/rubygems/bundler/pull/7614) + - Fix config location edge case where if `BUNDLE_APP_CONFIG` is set to an absolute path like in official ruby docker images, and there's no Gemfile up in the directory hierarchy, bundler would end up using the default config location instead of the customized one [#7622](https://github.com/rubygems/bundler/pull/7622) + - Fix error message about missing permissions recommending a deprecated command [#7633](https://github.com/rubygems/bundler/pull/7633) + - Fix `init_gems_rb` setting being ignored by `bundle gem` [#7629](https://github.com/rubygems/bundler/pull/7629) + - Fix "unresolvable warning" being printed on `bundle install` of multipliplatform `Gemfile` or `gems.rb` files without lockfiles, multiplatform is now managed automatically [#7580](https://github.com/rubygems/bundler/pull/7580) + - Fix setting the number of `--jobs` to be one unit less than specified to the CLI [#3393](https://github.com/rubygems/rubygems/pull/3393) + - Fix extension building when the same git source specifies several gems with extensions [#3475](https://github.com/rubygems/rubygems/pull/3475) + - Fix uninitialized instance variable warning under ruby-head (2.8-dev) [#3477](https://github.com/rubygems/rubygems/pull/3477) + - Fix double chdir warning while installing a git gem with extensions [#3479](https://github.com/rubygems/rubygems/pull/3479) + - Fix some deprecations not showing up when CLI flags passed as `--flag=value` [#3561](https://github.com/rubygems/rubygems/pull/3561) + - Fix man pages display when bundler installed as a default gem [#3562](https://github.com/rubygems/rubygems/pull/3562) + - Fix bundler gem tasks not handling relative paths [#3586](https://github.com/rubygems/rubygems/pull/3586) + - Fix deprecation warnings when options the dashed names are used, such as `--no-prune` [#3623](https://github.com/rubygems/rubygems/pull/3623) + - Fix crash related to bundler gem activation under old rubygems version (2.6.1 or older) [#3626](https://github.com/rubygems/rubygems/pull/3626) + - Avoid stack overflow inside `StubSpecification` on some edge cases [#3635](https://github.com/rubygems/rubygems/pull/3635) + - Fix `bundle remove` with multiline gem specifications [#3400](https://github.com/rubygems/rubygems/pull/3400) + - Fix `bundle info` not informing about deleted gems as opposed to old `bundle show` [#3509](https://github.com/rubygems/rubygems/pull/3509) + - The `--no-deployment` flag to `bundle install` was deprecated just like the other flags that rely on their value being remembered [#3657](https://github.com/rubygems/rubygems/pull/3657) + - Fix `bundle install` unintentionally copying `with` and `without` global config to local configuration [#3666](https://github.com/rubygems/rubygems/pull/3666). This PR also address the `BUNDLE_WITH` environment variable unintentionally being persisted to configuration in a similar way ([#3708](https://github.com/rubygems/rubygems/issues/3708)) + - Fix race condition in `bundle install` that could "empty" exceptions to be raised [#3669](https://github.com/rubygems/rubygems/pull/3669) + - Fix `--no-cache` to `bundle install` being unintentionally deprecated [#3688](https://github.com/rubygems/rubygems/pull/3688) + - Avoid calling `LoadError#message` to fix performance regression in future ruby 3.0 [#3762](https://github.com/rubygems/rubygems/pull/3762) + +# 2.1.4 (January 5, 2020) + +## Bug fixes: + + - Fix `net-http-pipeline` no longer being allowed in Gemfiles if already installed in the system due to our vendored version of `net-http-persistent` optionally requiring it [#7529](https://github.com/bundler/bundler/pull/7529) + - Fix inline gems no longer being requirable if no Gemfile is present in the directory hierarchy [#7537](https://github.com/bundler/bundler/pull/7537) + +# 2.1.3 (January 2, 2020) + +## Bug fixes: + + - Fix `rake build` when path has spaces on it [#7514](https://github.com/bundler/bundler/pull/7514) + - Fix `rake release` git push tasks when the running shell has `git` as an alias of another command (like `hub`) [#7510](https://github.com/bundler/bundler/pull/7510) + - Fix some circular require warnings [#7520](https://github.com/bundler/bundler/pull/7520) + - Fix `bundle config set deployment true` recommended alternative to `bundle config --deployment` to behave in the same way as the `--deployment` flag [#7519](https://github.com/bundler/bundler/pull/7519) + +# 2.1.2 (December 20, 2019) + +## Bug fixes: + + - Restore an explicit `require "rubygems"` on top `rubygems_integration.rb` to avoid some missing constant errors under some convoluted setups [#7505](https://github.com/rubygems/bundler/pull/7505) + +# 2.1.1 (December 17, 2019) + +## Bug fixes: + + - Fix some cases of shelling out to `rubygems` still being silent [#7493](https://github.com/rubygems/bundler/pull/7493) + - Restore compatibility with `rubygems-bundler` so that binstubs work under `RVM` [#7498](https://github.com/rubygems/bundler/pull/7498) + +# 2.1.0 (December 15, 2019) + +## Features: + + - Add support for new default gems. In particular, + + * `open3` [#7455](https://github.com/rubygems/bundler/pull/7455) + * `cgi`: [#7456](https://github.com/rubygems/bundler/pull/7456) + * `uri` [#7460](https://github.com/rubygems/bundler/pull/7460) + + plus other PRs removing or lazily loading usages of these gems from other places to not interfere with user's choice, such as [#7471](https://github.com/rubygems/bundler/pull/7471) or [#7473](https://github.com/bundler/bundler/pull/7473) + +## Bug fixes: + + - Fix `bundle exec rake install` failing [#7474](https://github.com/rubygems/bundler/pull/7474) + - Fix `bundle exec`'ing to rubygems being silent [#7442](https://github.com/rubygems/bundler/pull/7442) + - Restore previous `BUNDLE_GEMFILE` in `bundler/inline` [#7418](https://github.com/rubygems/bundler/pull/7418) + - Fix error when using `gem` DSL's `:glob` option for selecting gemspecs from a specific source [#7419](https://github.com/rubygems/bundler/pull/7419) + +## Enhancements: + + - `bundle config` no longer warns when using "old interface" (might be deprecated again in the future) [#7475](https://github.com/rubygems/bundler/pull/7475) + - `bundle update` no longer warns when used without arguments (might be deprecated again in the future) [#7475](https://github.com/rubygems/bundler/pull/7475) + +# 2.1.0.pre.3 (November 12, 2019) + +## Features: + + - Add caller information to some deprecation messages to make them easier to fix [#7361](https://github.com/rubygems/bundler/pull/7361) + - Reconcile `bundle cache` vs `bundle package` everywhere. Now in docs, CLI help and everywhere else `bundle cache` is the preferred version and `bundle package` remains as an alias [#7389](https://github.com/rubygems/bundler/pull/7389) + - Display some basic `bundler` documentation together with ruby's RDoc based documentation [#7394](https://github.com/rubygems/bundler/pull/7394) + +## Bug fixes: + + - Fix typos deprecation message and upgrading docs [#7374](https://github.com/rubygems/bundler/pull/7374) + - Deprecation warnings about `taint` usage on ruby 2.7 [#7385](https://github.com/rubygems/bundler/pull/7385) + - Fix `--help` flag not correctly delegating to `man` when used with command aliases [#7388](https://github.com/rubygems/bundler/pull/7388) + - `bundle add` should cache newly added gems if an application cache exists [#7393](https://github.com/rubygems/bundler/pull/7393) + - Stop using an insecure folder as a "fallback home" when user home is not defined [#7416](https://github.com/rubygems/bundler/pull/7416) + - Fix `bundler/inline` warning about `Bundler.root` redefinition [#7417](https://github.com/rubygems/bundler/pull/7417) + +# 2.1.0.pre.2 (September 15, 2019) + +## Bug fixes: + + - Fix `bundle clean` trying to delete non-existent directory ([#7340](https://github.com/rubygems/bundler/pull/7340)) + - Fix warnings about keyword argument separation on ruby 2.7 ([#7337](https://github.com/rubygems/bundler/pull/7337)) + +# 2.1.0.pre.1 (August 28, 2019) + + One of the biggest changes in bundler 2.1.0 is that deprecations for upcoming + breaking changes in bundler 3 will be turned on by default. We do this to grab + feedback and communicate early to our users the kind of changes we're intending + to ship with bundler 3. See + [#6965](https://github.com/rubygems/bundler/pull/6965). + + Another important improvement is a better coexistence between bundler + installations and the default copy of bundler that comes with ruby installed as + a default gem. Since bundler is shipped as a default gem with ruby, a number of + users have been affected by issues where bundler ends up failing due to version + mismatches, because at some point of the execution, bundler switches to run the + default copy instead of the expected version. A number of PRs have been focused + on minimizing (hopefully eliminating) this, such as + [#7100](https://github.com/rubygems/bundler/pull/7100), + [#7137](https://github.com/rubygems/bundler/pull/7137), + [#6996](https://github.com/rubygems/bundler/pull/6996), + [#7056](https://github.com/rubygems/bundler/pull/7056), + [#7062](https://github.com/rubygems/bundler/pull/7062), + [#7193](https://github.com/rubygems/bundler/pull/7193), + [#7216](https://github.com/rubygems/bundler/pull/7216), + [#7274](https://github.com/rubygems/bundler/pull/7274) + +## Deprecations: + + * See the [the upgrading document](UPGRADING.md) for a detailed explanation of + the deprecations that are getting enabled in bundler 2.1, and the future + breaking changes in bundler 3. + +## Features: + + - Reimplement `config` command using subcommands ([#5981](https://github.com/rubygems/bundler/pull/5981)) + - Add `bundle plugin list` command ([#6120](https://github.com/rubygems/bundler/pull/6120)) + - Introduce a `bundle lock --gemfile` flag ([#6748](https://github.com/rubygems/bundler/pull/6748)) + - Add local git repository source option (`--local_git`) to plugin installation ([#6749](https://github.com/rubygems/bundler/pull/6749)) + - Add `quiet` flag to inline bundler ([#6828](https://github.com/rubygems/bundler/pull/6828)) + - Introduce a `prefer_patch` configuration that makes `bundle update` behave like `bundle update --patch` ([#6931](https://github.com/rubygems/bundler/pull/6931)) + - Introduce `Bundler.original_system` and `Bundler.original_exec` to shell out or exec to external programs using the original environment before bundler was loaded ([#7052](https://github.com/rubygems/bundler/pull/7052)) + - Add feature parity to `bundle info GEM` with respect to the old deprecated command `bundle show GEM` [#7026](https://github.com/rubygems/bundler/pull/7026) + - Introduce `bundle list` to list groups of gems in your Gemfile. This command was actually documented, but was working as an alias to `bundle show` so this could also be considered a bug fix :) [#7072](https://github.com/rubygems/bundler/pull/7072) + - Introduce `bundle outdated --filter-strict` as an alias to `bundle outdated --strict` [#6030](https://github.com/rubygems/bundler/pull/6030) + - Add `:git` and `:branch` options to `bundle add` ([#7127](https://github.com/rubygems/bundler/pull/7127)) + - Add `:ruby_26` as a valid value to the `:platform(s)` dsl ([#7155](https://github.com/rubygems/bundler/pull/7155)) + - Let the `bundle cache` command include all features currently provided by `bundle package` ([#7249](https://github.com/rubygems/bundler/pull/7249)) + - Several improvements on new gem templates ([#6924](https://github.com/rubygems/bundler/pull/6924), [#6968](https://github.com/bundler/bundler/pull/6968), [#7209](https://github.com/bundler/bundler/pull/7209), [#7222](https://github.com/bundler/bundler/pull/7222), [#7238](https://github.com/bundler/bundler/pull/7238)) + - Add `--[no-]git` option to `bundle gem` to generate non source control gems. Useful for monorepos, for example ([#7263](https://github.com/rubygems/bundler/pull/7263)) + +## Bug fixes: + + - Raise when the same gem is available in multiple sources, and show a suggestion to solve it ([#5985](https://github.com/rubygems/bundler/pull/5985)) + - Validate that bundler has permissions to write to the tmp directory, and raise with a meaningful error otherwise ([#5954](https://github.com/rubygems/bundler/pull/5954)) + - Remove downloaded `.gem` file from the cache if it's corrupted ([#6010](https://github.com/rubygems/bundler/pull/6010)) + - Fix generated README in new gems to explicitly suggest running `bundle install`, so that the outcome is independent from the major version of bundler being run ([#6068](https://github.com/rubygems/bundler/pull/6068)) + - Fix `bundle outdated --group NAME` when the group is listed second in the Gemfile ([#6116](https://github.com/rubygems/bundler/pull/6116)) + - Improve conflict resolution messages by not calling "ruby" a gem when conflict happens in the `required_ruby_version`, and by filtering out requirements that didn't contribute to the conflict ([#6647](https://github.com/rubygems/bundler/pull/6647)) + - Avoid fetching and rebuilding git gems whenever any gem is changed in the Gemfile ([#6711](https://github.com/rubygems/bundler/pull/6711)) + - Include the exact bundler version in the lock file in the suggested command when bundler warns about version mismatches of itself [#6971](https://github.com/rubygems/bundler/pull/6971) + - Fix plugins being installed every time a command is run #[#6978](https://github.com/rubygems/bundler/pull/6978) + - Fallback to sequentially fetching specs on 429s [#6728](https://github.com/rubygems/bundler/pull/6728) + - Make `bundle clean` also clean native extensions for gems with a git source [#7058](https://github.com/rubygems/bundler/pull/7058) + - Fix `bundle info bundler` to show the correct path to the bundler gem [#7026](https://github.com/rubygems/bundler/pull/7026) + - Fix `bundle config build.` not sending multiple parameters to `extconf.rb` correctly [#7023](https://github.com/rubygems/bundler/pull/7023) + - Fix bad error message on Gemfile errors under ruby 2.7 (still unreleased, but it's a bugfix for beta testers after all) [#7038](https://github.com/rubygems/bundler/pull/7038) + - Warn about situations where multiple gems provide the same executable ([#7075](https://github.com/rubygems/bundler/pull/7075)) + - Ignore `frozen` setting in inline mode ([#7125](https://github.com/rubygems/bundler/pull/7125)) + - Fix incorrect "bundler attempted to update GEM but version stayed the same" message when updating git sourced gems ([#6325](https://github.com/rubygems/bundler/pull/6325)) + - Don't check for existence of a writable home directory if `BUNDLE_USER_HOME` is set ([#6885](https://github.com/rubygems/bundler/pull/6885)) + - Fix error message when server would respond to a bad username/password request with a 401 ([#6928](https://github.com/rubygems/bundler/pull/6928)) + - Fix `bundle outdated` pluralization when multiple groups are requested ([#7063](https://github.com/rubygems/bundler/pull/7063)) + - Fix `bundle install` not updating conservatively when gemspec is changed ([#7143](https://github.com/rubygems/bundler/pull/7143)) + - Fix `bundle exec` not respecting custom process titles inside scripts ([#7140](https://github.com/rubygems/bundler/pull/7140)) + - Fix `bundle update` message about exclude groups saying "installed" instead of "updated" ([#7150](https://github.com/rubygems/bundler/pull/7150)) + - Fix `bundle licenses` not showing correct information about bundler itself ([#7147](https://github.com/rubygems/bundler/pull/7147)) + - Fix installation path not including ruby scope when `BUNDLE_PATH` was set ([#7163](https://github.com/rubygems/bundler/pull/7163)) + - Fix `bundle clean` incorrectly removing git dependencies present in the Gemfile when rubygems 3.0+ was used and path involved a symlink ([#7211](https://github.com/rubygems/bundler/pull/7211)) + - Fix platform specific gems always being re-resolved when bundler was not running under that platform ([#7212](https://github.com/rubygems/bundler/pull/7212)) + - Fix `bundle package --all-platforms` causing `bundle install` to ignore `--with` and `--without` ([#6113](https://github.com/rubygems/bundler/pull/6113)) + - Fix `MissingRevision` git errors to include the specific `git` command that failed under the hood ([#7225](https://github.com/rubygems/bundler/pull/7225)) + - Fix using gemspec & `force_ruby_platform` on Windows ([#6809](https://github.com/rubygems/bundler/pull/6809)) + - Make bundler's binstub checks on bundler version consistent with rubygems `BundlerVersionFinder` ([#7259](https://github.com/rubygems/bundler/pull/7259)) + - Fix `bundle install` and `bundle update` generating different lockfiles when `path:` gems with relative paths starting with "./" were used ([#7264](https://github.com/rubygems/bundler/pull/7264)) + - Give a proper error when user tries to `bundle open` a default gem ([#7288](https://github.com/rubygems/bundler/pull/7288)) + - Fix `bundle doctor` command ([#7309](https://github.com/rubygems/bundler/pull/7309)) + - Fix bundler giving an unclear recommendation when duplicated gems are found in the Gemfile ([#7302](https://github.com/rubygems/bundler/pull/7302)) + +## Documentation: + + - Fix typo on a file extension in `bundle.ronn` [#7146](https://github.com/rubygems/bundler/pull/7146) + - Fix incorrect default value for `cache_path` configuration ([#7229](https://github.com/rubygems/bundler/pull/7229)) + - Binstubs documentation has been improved ([#5889](https://github.com/rubygems/bundler/pull/5889)) + - Fix incorrect sections when explaining `:git`, `:branch`, and `:ref` options ([#7265](https://github.com/rubygems/bundler/pull/7265)) + - Fix mentions to remembered options in docs to explain the current state ([#7242](https://github.com/rubygems/bundler/pull/7242)) + + Internally, there's also been a bunch of improvements in our development + environment, test suite, policies, contributing docs, and a bunch of cleanups of + old compatibility code. + +# 2.0.2 (June 13, 2019) + +## Enhancements: + + - Fixes for Bundler integration with ruby-src ([#6941](https://github.com/rubygems/bundler/pull/6941), [#6973](https://github.com/bundler/bundler/pull/6973), [#6977](https://github.com/bundler/bundler/pull/6977), [#6315](https://github.com/bundler/bundler/pull/6315), [#7061](https://github.com/bundler/bundler/pull/7061)) + - Use `__dir__` instead of `__FILE__` when generating a gem with `bundle gem` ([#6503](https://github.com/rubygems/bundler/pull/6503)) + - Use `https` on externals links in the Bundler gemspec ([#6721](https://github.com/rubygems/bundler/pull/6721)) + - Removed duplicate gem names from the suggested `did you mean` list for gem typos ([#6739](https://github.com/rubygems/bundler/pull/6739)) + - Removed Ruby 1.x compatibility code ([#6764](https://github.com/rubygems/bundler/pull/6764), [#6806](https://github.com/bundler/bundler/pull/6806)) + - Fixed an issue where `bundle remove` would crash with certain Gemfiles ([#6768](https://github.com/rubygems/bundler/pull/6769)) + - Fixed indentation in the Bundler executable template ([#6773](https://github.com/rubygems/bundler/pull/6773)) + - Fixed an issue where plugins could register for the same Bundler hook multiple times ([#6775](https://github.com/rubygems/bundler/pull/6775)) + - Changed the "multiple sources" message in `bundle install` to be a warning instead of an error ([#6790](https://github.com/rubygems/bundler/pull/6790)) + - Fixed a bug where path gems would break when using `only_update_to_newer_versions` ([#6774](https://github.com/rubygems/bundler/pull/6774)) + - Fixed a bug where installing plugins with the `--deployment` setting would fail ([#6805](https://github.com/rubygems/bundler/pull/6805)) + - Fixed an issue where `bundle update` couldn't update & install a gem when `no_install` was set (a `bundle package` config) ([#7078](https://github.com/rubygems/bundler/pull/7078)) + - Fixed an issue where users could not run `bundle exec` on default gems ([#6963](https://github.com/rubygems/bundler/pull/6963)) + - Updated vendor libraries to their latest version ([#7076](https://github.com/rubygems/bundler/pull/7067), [#7068](https://github.com/bundler/bundler/pull/7068)) + - Fixed an issue where the `github` source was not using `https` by default that we mentioned in the 2.0 release ([#7182](https://github.com/rubygems/bundler/pull/7182)) + - Fixed an issue where `rake release` was not outputting the message to users asking for a 2fa token ([#7199](https://github.com/rubygems/bundler/pull/7199)) + +## Documentation: + + - Fix incorrect documented `BUNDLE_PATH_RELATIVE_TO_CWD` env var ([#6751](https://github.com/rubygems/bundler/pull/6751)) + - Update URLs in Bundler's documentation to use `https` ([#6935](https://github.com/rubygems/bundler/pull/6935)) + +# 2.0.1 (January 4, 2019) + +## Bug fixes: + + - Relaxed RubyGems requirement to `>= 2.5.0` ([#6867](https://github.com/rubygems/bundler/pull/6867)) + +# 2.0.0 (January 3, 2019) + + No changes. + +# 2.0.0.pre.3 (December 30, 2018) + +## Breaking changes: + + - Bundler 2 now requires RubyGems 3.0.0 at minimum + +## Bug fixes: + + - Ruby 2.6 compatibility fixes (@segiddins) + +## Enhancements: + + - Import changes from Bundler 1.17.3 release + + Note: To upgrade your Gemfile to Bundler 2 you will need to run `bundle update --bundler` + +# 2.0.0.pre.2 (November 27, 2018) + +## Breaking changes: + + - `:github` source in the Gemfile now defaults to using HTTPS + +Changes + + - Add compatibility for Bundler merge into ruby-src + + Note: To upgrade your Gemfile to Bundler 2 you will need to run `bundle update --bundler` + +# 2.0.0.pre.1 (November 9, 2018) + +## Breaking changes: + + - Dropped support for versions of Ruby under 2.3 + - Dropped support for version of RubyGems under 2.5 + - Moved error messages from STDOUT to STDERR + + Note: To upgrade your Gemfile to Bundler 2 you will need to run `bundle update --bundler` + +# 1.17.3 (December 27, 2018) + +## Bug fixes: + + - Fix a Bundler error when installing gems on old versions of RubyGems ([#6839](https://github.com/rubygems/bundler/issues/6839), @colby-swandale) + - Fix a rare issue where Bundler was removing itself after a `bundle clean` ([#6829](https://github.com/rubygems/bundler/issues/6829), @colby-swandale) + +## Documentation: + + - Add entry for the `bundle remove` command to the main Bundler manual page + +# 1.17.2 (December 11, 2018) + + - Add compatibility for bundler merge with Ruby 2.6 + +# 1.17.1 (October 25, 2018) + + - Convert `Pathname`s to `String`s before sorting them, fixing #6760 and #6758 ([#6761](https://github.com/rubygems/bundler/pull/6761), @alexggordon) + +# 1.17.0 (October 25, 2018) + + No changes. + +# 1.17.0.pre.2 (October 13, 2018) + +## Features: + + - Configure Bundler home, cache, config and plugin directories with `BUNDLE_USER_HOME`, `BUNDLE_USER_CACHE`, `BUNDLE_USER_CONFIG` and `BUNDLE_USER_PLUGIN` env vars ([#4333](https://github.com/rubygems/bundler/issues/4333), @gwerbin) + - Add `--all` option to `bundle binstubs` that will generate an executable file for all gems with commands in the bundle + - Add `bundle remove` command to remove gems from the Gemfile via the CLI + - Improve checking file permissions and asking for `sudo` in Bundler when it doesn't need to + - Add error message to `bundle add` to check adding duplicate gems to the Gemfile + - When asking for `sudo`, Bundler will show a list of folders/files that require elevated permissions to write to. + + The following new features are available but are not enabled by default. These are intended to be tested by users for the upcoming release of Bundler 2. + + - Improve deprecation warning message for `bundle show` command + - Improve deprecation warning message for the `--force` option in `bundle install` + +# 1.17.0.pre.1 (September 24, 2018) + +## Features: + + - Check folder/file permissions of the Bundle home directory in the `bundle doctor` command ([#5786](https://github.com/rubygems/bundler/issues/5786), @ajwann) + - Remove compiled gem extensions when running `bundle clean` ([#5596](https://github.com/rubygems/bundler/issues/5596), @akhramov) + - Add `--paths` option to `bundle list` command ([#6172](https://github.com/rubygems/bundler/issues/6172), @colby-swandale) + - Add base error class to gems generated from `bundle gem` ([#6260](https://github.com/rubygems/bundler/issues/6260), @christhekeele) + - Correctly re-install gem extensions with a git source when running `bundle pristine` ([#6294](https://github.com/rubygems/bundler/issues/6294), @wagenet) + - Add config option to disable platform warnings ([#6124](https://github.com/rubygems/bundler/issues/6124), @agrim123) + - Add `--skip-install` option to `bundle add` command to add gems to the Gemfile without installation ([#6511](https://github.com/rubygems/bundler/issues/6511), @agrim123) + - Add `--only-explicit` option to `bundle outdated` to list only outdated gems in the Gemfile ([#5366](https://github.com/rubygems/bundler/issues/5366), @peret) + - Support adding multiple gems to the Gemfile with `bundle add` ([#6543](https://github.com/rubygems/bundler/issues/6543), @agrim123) + - Make registered plugin events easier to manage in the Plugin API (@jules2689) + - Add new gem install hooks to the Plugin API (@jules2689) + - Add `--optimistic` and `--strict` options to `bundle add` ([#6553](https://github.com/rubygems/bundler/issues/6553), @agrim123) + - Add `--without-group` and `--only-group` options to `bundle list` ([#6564](https://github.com/rubygems/bundler/issues/6564), @agrim123) + - Add `--gemfile` option to the `bundle exec` command ([#5924](https://github.com/rubygems/bundler/issues/5924), @ankitkataria) + + The following new features are available but are not enabled by default. These are intended to be tested by users for the upcoming release of Bundler 2. + + - Make `install --path` relative to the current working directory ([#2048](https://github.com/rubygems/bundler/issues/2048), @igorbozato) + - Auto-configure job count ([#5808](https://github.com/rubygems/bundler/issues/5808), @segiddins) + - Use the Gem Version Promoter for major gem updates ([#5993](https://github.com/rubygems/bundler/issues/5993), @segiddins) + - Add config option to add the Ruby scope to `bundle config path` when configured globally (@segiddins) + +# 1.16.6 (October 5, 2018) + +## Enhancements: + + - Add an error message when adding a gem with `bundle add` that's already in the bundle ([#6341](https://github.com/rubygems/bundler/issues/6341), @agrim123) + - Add Homepage, Source Code and Changelog URI metadata fields to the `bundle gem` gemspec template (@walf443) + +## Bug fixes: + + - Fix issue where updating a gem resulted in the gem's version being downgraded when `BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS` was set ([#6529](https://github.com/rubygems/bundler/issues/6529), @theflow) + - Fix some rescue calls that don't specify error type (@utilum) + - Fix an issue when the Lockfile would contain platform-specific gems that it didn't need ([#6491](https://github.com/rubygems/bundler/issues/6491), @segiddins) + - Improve handling of adding new gems with only a single group to the Gemfile in `bundle add` (@agrim123) + - Refactor check for OpenSSL in `bundle env` (@voxik) + - Remove an unnecessary assignment in Metadata (@voxik) + +## Documentation: + + - Update docs to reflect revised guidance to check in Gemfile.lock into version control for gems ([#5879](https://github.com/rubygems/bundler/issues/5879), @arbonap) + - Add documentation for the `--all` flag in `bundle update` (@agrim123) + - Update README to use `bundle add` in usage examples (@hdf1986) + +# 1.16.5 (September 18, 2018) + +## Enhancements: + + - Add support for TruffleRuby (@eregon) + +## Bug fixes: + + - Avoid printing git errors when checking the version on incorrectly packaged versions of Bundler ([#6453](https://github.com/rubygems/bundler/issues/6453), @greysteil) + - Fix issue where Bundler does not check the given class when comparing equality in DepProxy (@ChrisBr) + - Handle `RangeNotSatisfiable` error in Compact Index (@MaxLap) + - Check for initialized `search` variable in `LazySpecification` (@voxik) + - Fix LoadError occurring in nested bundle exec calls ([#6537](https://github.com/rubygems/bundler/issues/6537), @colby-swandale) + - Check that Bundler::Deprecate is not an autoload constant ([#6163](https://github.com/rubygems/bundler/issues/6163), @eregon) + - Prefer non-pre-release versions when performing a `bundle update --patch` ([#6684](https://github.com/rubygems/bundler/issues/6684), @segiddins) + +# 1.16.4 (August 17, 2018) + +## Enhancements: + + - Welcome new members to the Bundler core team (@indirect) + - Don't mutate original error trees when determining version_conflict_message (@greysteil) + - Update vendored Molinillo to 0.6.6 (@segiddins) + +## Bug fixes: + + - Reword bundle update regression message to be more clear to the user when a gem's version is downgraded ([#6584](https://github.com/rubygems/bundler/issues/6584), @ralphbolo) + - Respect --conservative flag when updating a dependency group ([#6560](https://github.com/rubygems/bundler/issues/6560), @greysteil) + - Fix issue where a pre-release version was not being selected when it's specified in the Gemfile ([#6449](https://github.com/rubygems/bundler/issues/6449), @akihiro17) + - Fix issue where `Etc` was not loaded when getting the user's home dir ([#6640](https://github.com/rubygems/bundler/issues/6640), @colby-swandale) + - Use UTF-8 for reading files including Gemfile ([#6660](https://github.com/rubygems/bundler/issues/6660), @eregon) + - Remove unnecessary `while` loop in path resolver helper (@ojab) + +## Documentation: + + - Document that `bundle show [--paths]` sorts results by name (@kemitchell) + +# 1.16.3 (July 17, 2018) + +## Features: + + - Support URI::File of Ruby 2.6 (@hsbt) + +## Bug fixes: + + - Expand symlinks during setup to allow Bundler to load correctly when using symlinks in $GEM_HOME ([#6465](https://github.com/rubygems/bundler/issues/6465), @ojab, @indirect) + - Dont let Bundler create temporary folders for gem installs which are owned by root ([#6258](https://github.com/rubygems/bundler/issues/6258), @colby-swandale) + - Don't fallback to using temporary directories when needed directories already exist ([#6546](https://github.com/rubygems/bundler/issues/6546), @brodock) + - Use SharedHelpers.filesystem_access when reading a Gemfile so friendly error messages can be given to the user ([#6541](https://github.com/rubygems/bundler/issues/6541), @segiddins) + - Check if source responds to `#remotes` before printing gem install error message ([#6211](https://github.com/rubygems/bundler/issues/6211), @colby-swandale) + - Handle Errno::ENOTSUP in the Bundler Process Lock to prevent exceptions when using NFS mounts ([#6566](https://github.com/rubygems/bundler/issues/6566), @colby-swandale) + - Respect encodings when reading gemspecs ([#6598](https://github.com/rubygems/bundler/issues/6598), @deivid-rodriguez) + +## Documentation: + + - Fix links between manual pages (@BanzaiMan) + - Add warning to Gemfile documentation for the use of the `source` option when declaring gems ([#6280](https://github.com/rubygems/bundler/issues/6280), @forestgagnon) + +# 1.16.2 (April 20, 2018) + +## Enhancements: + + - Include the gem's source in the gem install error message when available (@papanikge) + - Remove unnecessary executable bit from gem template (@voxik) + - Dont add the timestamp comment with gems added to the Gemfile via `bundle add` ([#6193](https://github.com/rubygems/bundler/issues/6193), @cpgo) + - Improve yanked gem error message (@alyssais) + - Use `Bundler.rubygems.inflate` instead of the Gem::Util method directly (@segiddins) + - Remove unused instance variable (@segiddins) + +## Bug fixes: + + - Only trap INT signal and have Ruby's signal default handler be invoked (@shayonj) + - Fix warning about the use of `__FILE__` in RubyGems integration testing (@MSP-Greg) + - Skip the outdated bundler check when MD5 is not available ([#6032](https://github.com/rubygems/bundler/issues/6032), @segiddins) + - Fallback to the original error if the friendly message raises (@segiddins) + - Rename Bundler.frozen? to avoid Object method conflict ([#6252](https://github.com/rubygems/bundler/issues/6252), @segiddins) + - Ensure the bindir exists before installing gems (@segiddins) + - Handle gzip corruption errors in the compact index client ([#6261](https://github.com/rubygems/bundler/issues/6261), @colby-swandale) + - Check if the current directory is writeable when writing files in `bundle gem` ([#6219](https://github.com/rubygems/bundler/issues/6219), @nilsding) + - Fix hang when gemspec has incompatible encoding (@deivid-rodriguez) + - Gracefully handle when the lockfile is missing spec entries for the current platform ([#6079](https://github.com/rubygems/bundler/issues/6079), @segiddins) + - Use Gem::Util.inflate instead of Gem.inflate (@hsbt) + - Update binstub generator to use new ERB.new arity in Ruby 2.6 (@koic) + - Fix `source_location` call in rubygems integration (@MSP-Greg) + - Use `filesystem_access` when copying files in Compact Index Updater ([#6289](https://github.com/rubygems/bundler/issues/6289), @segiddins) + - Fail gracefully when resetting git gems to the given revision fails ([#6324](https://github.com/rubygems/bundler/issues/6324), @segiddins) + - Handle exceptions that do not have a backtrace ([#6342](https://github.com/rubygems/bundler/issues/6342), @nesaulov) + - Check if stderr was closed before writing to it (@shime) + - Handle updating a specific gem for a non-local platform ([#6350](https://github.com/rubygems/bundler/issues/6350), @greysteil) + - Bump the `bundle_binstub` check-length to 300 characters (@tduffield) + - Fix specifying alterntive Lockfile with `bundle lock` when default gemfile is present ([#6460](https://github.com/rubygems/bundler/issues/6460), @agrim123) + - Allow installing dependencies when the path is set to `.` ([#6475](https://github.com/rubygems/bundler/issues/6475), @segiddins) + - Support Bundler installing on a readonly filesystem without a home directory ([#6461](https://github.com/rubygems/bundler/issues/6461), @grosser) + - Filter git uri credentials in source description (@segiddins) + +## Documentation: + + - Correct typos in `bundle binstubs` man page (@erikj, @samueloph) + - Update links in `bundle gem` command documentation to use https (@KrauseFx) + - Fix broken links between bundler man pages (@segiddins) + - Add man page for the `bundle doctor` command ([#6243](https://github.com/rubygems/bundler/issues/6243), @nholden) + - Document `# frozen_string_literal` in `bundle init` Gemfile (@315tky) + - Explain the gemspec files attribute in `bundle gem` template and print a link to bundler.io guides when running `bundle gem` ([#6246](https://github.com/rubygems/bundler/issues/6246), @nesaulov) + - Small copy tweaks & removed redundant phrasing in the bundler man page (@rubymorillo) + - Improve the documentation of the settings load order in Bundler (@rubymorillo) + - Added license info to main README (@rubymorillo) + - Document parameters and return value of Injector#inject (@tobias-grasse) + +# 1.16.1 (December 12, 2017) + +## Bug fixes: + + - avoid hanging on complex resolver errors ([#6114](https://github.com/rubygems/bundler/issues/6114), @halfbyte) + - avoid an error when running `bundle update --group` ([#6156](https://github.com/rubygems/bundler/issues/6156), @mattbrictson) + - ensure the resolver prefers non-pre-release gems when possible ([#6181](https://github.com/rubygems/bundler/issues/6181), @greysteil) + - include bundler's gemspec in the built gem ([#6165](https://github.com/rubygems/bundler/issues/6165), @dr-itz) + - ensure locally installed specs are not overridden by those in remote sources during dependency resolution ([#6072](https://github.com/rubygems/bundler/issues/6072), @indirect) + - ensure custom gemfiles are respected in generated binstubs (@pftg) + - fail gracefully when loading a bundler-generated binstub when `bin/bundle` was not generated by bundler ([#6149](https://github.com/rubygems/bundler/issues/6149), @hsbt) + - allow `bundle init` to be run even when a parent directory contains a gemfile ([#6205](https://github.com/rubygems/bundler/issues/6205), @colby-swandale) + +# 1.16.0 (October 31, 2017) + +## Bug fixes: + + - avoid new RubyGems warning about unsafe YAML loading (to keep output consistent) (@segiddins) + - load digest subclasses in a thread-safe manner (@segiddins, @colby-swandale) + - avoid unusued variable warnings under ruby 2.5 (@amatsuda) + - fix printing the same message twice in verbose mode ([#6028](https://github.com/rubygems/bundler/issues/6028), @akhramov) + - allow `SignalException`s to bubble up to the interpreter during `bundle exec` ([#6090](https://github.com/rubygems/bundler/issues/6090), @dekellum) + - avoid activating stdlib digest under Ruby 2.5 (@segiddins) + - prioritise explicitly requested gems in dependency resolution sort order (@segiddins) + - reduce memory usage during dependency resolution ([#6114](https://github.com/rubygems/bundler/issues/6114), @greysteil) + - ensure that the default bundler gem is not accidentally activated on ruby 2.5 when using local git overrides (@segiddins) + +# 1.16.0.pre.3 (October 4, 2017) + +## Features: + + - the output from `bundle env` includes more information, particularly both the compiled & loaded versions of OpenSSL (@indirect) + +## Bug fixes: + + - fix a bug where installing on FreeBSD would accidentally raise an error ([#6013](https://github.com/rubygems/bundler/issues/6013), @olleolleolle) + - fix a regression in 1.16 where pre-release gems could accidentally be resolved even when the gemfile contained no pre-release requirements (@greysteil) + - bundler will avoid making unnecessary network requests to fetch dependency data, fixing a regression introduced in 1.16 (@segiddins) + - the outdated bundler version message is disabled by default until the message has been fine-tuned ([#6004](https://github.com/rubygems/bundler/issues/6004), @segiddins) + +# 1.16.0.pre.2 (September 6, 2017) + +## Bug fixes: + + - handle when a connection is missing a socket when warning about OpenSSL version (@greysteil) + - the description for the `rake release` task now reflects `$RUBYGEMS_HOST` (@wadetandy) + - fix a bug where `bundle update` would regress transitive dependencies (@greysteil) + +# 1.16.0.pre.1 (September 4, 2017) + +## Features: + + - allow using non-branch symbolic refs in a git source ([#4845](https://github.com/rubygems/bundler/issues/4845), @segiddins) + - allow absolute paths in the `cache path` setting ([#5627](https://github.com/rubygems/bundler/issues/5627), @mal) + - gems created via `bundle gem` with rspec have `--require spec_helper` in their `.rspec` file (@koic) + - `bundle env` includes `Gem.ruby` and the `bundle` binstub shebang when they don't match ([#5616](https://github.com/rubygems/bundler/issues/5616), @segiddins) + - allow passing gem names to `bundle pristine` (@segiddins) + - `bundle version` and `bundle env` include the commit and build date for the bundler gem ([#5049](https://github.com/rubygems/bundler/issues/5049), @segiddins) + - add the `--shebang` option to `bundle binstubs` ([#4070](https://github.com/rubygems/bundler/issues/4070), @segiddins, @Zorbash) + - gemfiles are `eval`ed one fewer time when running `bundle install` ([#4952](https://github.com/rubygems/bundler/issues/4952), [#3096](https://github.com/bundler/bundler/issues/3096), [#4417](https://github.com/bundler/bundler/issues/4417), @segiddins) + - the `fileutils` gem is now vendored so different versions of the gem can be activated (@segiddins) + - speed up no-op installations ([#5842](https://github.com/rubygems/bundler/issues/5842), @segiddins) + - default to keeping the lockfile in the default gem template (@deivid-rodriguez) + - add a special bundler binstub that ensures the correct version of bundler is activated ([#5876](https://github.com/rubygems/bundler/issues/5876), @segiddins) + - speed up dependency resolution and ensure that all resolvable gemfiles can be installed (@segiddins, @greysteil) + - add a `bundle list` command that prints the gems in use ([#4754](https://github.com/rubygems/bundler/issues/4754), @colby-swandale) + - allow adding credentials to a gem source during deployment when `allow_deployment_source_credential_changes` is set (@adrian-gomez) + - making an outdated (and insecure) TLS connection to rubygems.org will print a warning (@segiddins) + +## Bug fixes: + + - allow configuring a mirror fallback timeout without a trailing slash ([#4830](https://github.com/rubygems/bundler/issues/4830), @segiddins) + - fix handling of mirrors for file: urls that contain upper-case characters (@segiddins) + - list the correct gem host for `rake release` when `allowed_push_host` has been set (@mdeering) + - ensure `Bundler.original_env` preserves all env keys that bundler sets ([#5700](https://github.com/rubygems/bundler/issues/5700), @segiddins) + - ensure `bundle pristine` removes files added to a git gem (@segiddins) + - load plugin files from path gems before gem installation ([#5429](https://github.com/rubygems/bundler/issues/5429), @segiddins) + - ensure gems containing manpages are properly set up ([#5730](https://github.com/rubygems/bundler/issues/5730), @segiddins) + - avoid fetching remote specs when all effected gems are in groups that are not being installed (@segiddins) + - allow `BUNDLE_GEMFILE` to be a relative path ([#5712](https://github.com/rubygems/bundler/issues/5712), @gxespino) + - show a more helpful error message when a gem fails to install due to a corrupted lockfile ([#5846](https://github.com/rubygems/bundler/issues/5846), @segiddins) + - add a process lock to allow multiple concurrent `bundle install`s ([#5851](https://github.com/rubygems/bundler/issues/5851), @stefansedich) + - ensure that specifications always return an array for `#extensions` (@greysteil) + - print a helpful error message when using a gem in the Gemfile with an empty name (@colby-swandale) + - ensure that all gemfiles are included in `bundle env` (@segiddins) + - use ssl client cert and ca cert settings from gem configuration as fallbacks (@stan3) + - avoid global namespace pollution when loading gems ([#5958](https://github.com/rubygems/bundler/issues/5958), @shyouhei) + - avoid running a complete re-resolve on `bundle update --bundler` (@segiddins) + - allow `bundle binstubs --standalone` to work without `path` being set (@colby-swandale) + - fix support for bundle paths that include jars or wars on jruby ([#5975](https://github.com/rubygems/bundler/issues/5975), @torcido) + +# 1.15.4 (August 19, 2017) + +## Bug fixes: + + - handle file conflicts gracefully in `bundle gem` (@rafaelfranca, @segiddins) + - bundler will fail gracefully when the bundle path contains the system path separator ([#5485](https://github.com/rubygems/bundler/issues/5485), ajwann) + - failed gem downloads will be retried consistently across different RubyGems versions (@shayonj) + - `bundle pristine` will respect build options while re-building native extensions (@NickLaMuro) + +# 1.15.3 (July 21, 2017) + +## Bug fixes: + + - ensure that empty strings passed to `bundle config` are serialized & parsed properly ([#5881](https://github.com/rubygems/bundler/issues/5881), @segiddins) + - avoid printing an outdated version warning when running a parseable command (@segiddins) + +# 1.15.2 (July 17, 2017) + +## Features: + + - new gemfiles created by bundler will include an explicit `github` git source that uses `https` (@segiddins) + +## Bug fixes: + + - inline gemfiles work when `BUNDLE_BIN` is set ([#5847](https://github.com/rubygems/bundler/issues/5847), @segiddins) + - avoid using the old dependency API when there are no changes to the compact index files ([#5373](https://github.com/rubygems/bundler/issues/5373), @greysteil) + - fail gracefully when the full index serves gemspecs with invalid dependencies ([#5797](https://github.com/rubygems/bundler/issues/5797), @segiddins) + - support installing gemfiles that use `eval_gemfile`, `:path` gems with relative paths, and `--deployment` simultaneously (@NickLaMuro) + - `bundle config` will print settings as the type they are interpreted as (@segiddins) + - respect the `no_proxy` environment variable when making network requests ([#5781](https://github.com/rubygems/bundler/issues/5781), @jakauppila) + - commands invoked with `--verbose` will not have default flags printed (@segiddins) + - allow `bundle viz` to work when another gem has a requirable `grapviz` file ([#5707](https://github.com/rubygems/bundler/issues/5707), @segiddins) + - ensure bundler puts activated gems on the `$LOAD_PATH` in a consistent order ([#5696](https://github.com/rubygems/bundler/issues/5696), @segiddins) + +# 1.15.1 (June 2, 2017) + +## Bug fixes: + + - `bundle lock --update GEM` will fail gracefully when the gem is not in the lockfile ([#5693](https://github.com/rubygems/bundler/issues/5693), @segiddins) + - `bundle init --gemspec` will fail gracefully when the gemspec is invalid (@colby-swandale) + - `bundle install --force` works when the gemfile contains git gems ([#5678](https://github.com/rubygems/bundler/issues/5678), @segiddins) + - `bundle env` will print well-formed markdown when there are no settings ([#5677](https://github.com/rubygems/bundler/issues/5677), @segiddins) + +# 1.15.0 (May 19, 2017) + + No changes. + +# 1.15.0.pre.4 (May 10, 2017) + +## Bug fixes: + + - avoid conflicts when `Gem.finish_resolve` is called after the bundle has been set up (@segiddins) + - ensure that `Gem::Specification.find_by_name` always returns an object that can have `#to_spec` called on it ([#5592](https://github.com/rubygems/bundler/issues/5592), @jules2689) + +# 1.15.0.pre.3 (April 30, 2017) + +## Bug fixes: + + - avoid redundant blank lines in the readme generated by `bundle gem` (@koic) + - ensure that `open-uri` is not loaded after `bundle exec` (@segiddins) + - print a helpful error message when an activated default gem conflicts with + a gem in the gemfile (@segiddins) + - only shorten `ref` option for git gems when it is a SHA ([#5620](https://github.com/rubygems/bundler/issues/5620), @segiddins) + +# 1.15.0.pre.2 (April 23, 2017) + +## Bug fixes: + + - ensure pre-existing fit caches are updated from remote sources ([#5423](https://github.com/rubygems/bundler/issues/5423), @alextaylor000) + - avoid duplicating specs in the lockfile after updating with the gem uninstalled ([#5599](https://github.com/rubygems/bundler/issues/5599), @segiddins) + - ensure git gems have their extensions available at runtime ([#5594](https://github.com/rubygems/bundler/issues/5594), @jules2689, @segiddins) + +# 1.15.0.pre.1 (April 16, 2017) + +## Features: + + - print a notification when a newer version of bundler is available ([#4683](https://github.com/rubygems/bundler/issues/4683), @segiddins) + - add man pages for all bundler commands ([#4988](https://github.com/rubygems/bundler/issues/4988), @feministy) + - add the `bundle info` command (@fredrb, @colby-swandale) + - all files created with `bundle gem` comply with the bundler style guide (@zachahn) + - if installing a gem fails, print out the reason the gem needed to be installed ([#5078](https://github.com/rubygems/bundler/issues/5078), @segiddins) + - allow setting `gem.push_key` to set the key used when running `rake release` (@DTrierweiler) + - print gem versions that are regressing during `bundle update` in yellow ([#5506](https://github.com/rubygems/bundler/issues/5506), @brchristian) + - avoid printing extraneous dependencies when the resolver encounters a conflict (@segiddins) + - add the `bundle issue` command that prints instructions for reporting issues ([#4871](https://github.com/rubygems/bundler/issues/4871), @jonathanpike) + - add `--source` and `--group` options to the `bundle inject` command ([#5452](https://github.com/rubygems/bundler/issues/5452), @Shekharrajak) + - add the `bundle add` command to add a gem to the gemfile (@denniss) + - add the `bundle pristine` command to re-install gems from cached `.gem` files ([#4509](https://github.com/rubygems/bundler/issues/4509), @denniss) + - add a `--parseable` option for `bundle config` (@JuanitoFatas, @colby-swandale) + +## Performance: + + - speed up gemfile initialization by storing locked dependencies as a hash (@jules2689) + - speed up gemfile initialization by making locked dependency comparison lazy, avoiding object allocation (@jules2689) + - only validate git gems when they are downloaded, instead of every time `Bundler.setup` is run (@segiddins) + - avoid regenerating the lockfile when nothing has changed (@segiddins) + - avoid diffing large arrays when no sources in the gemfile have changed (@segiddins) + - avoid evaluating full gemspecs when running with RubyGems 2.5+ (@segiddins) + +## Bug fixes: + + - fix cases where `bundle update` would print a resolver conflict instead of updating the selected gems ([#5031](https://github.com/rubygems/bundler/issues/5031), [#5095](https://github.com/bundler/bundler/issues/5095), @segiddins) + - print out a stack trace after an interrupt when running in debug mode (@segiddins) + - print out when bundler starts fetching a gem from a remote server (@segiddins) + - fix `bundle gem` failing when `git` is unavailable ([#5458](https://github.com/rubygems/bundler/issues/5458), @Shekharrajak, @colby-swandale) + - suggest the appropriate command to unfreeze a bundle ([#5009](https://github.com/rubygems/bundler/issues/5009), @denniss) + - ensure nested calls to `bundle exec` resolve default gems correctly ([#5500](https://github.com/rubygems/bundler/issues/5500), @segiddins) + - ensure that a plugin failing to install doesn't uninstall other plugins (@kerrizor, @roseaboveit) + - ensure `socket` is required before being referenced ([#5533](https://github.com/rubygems/bundler/issues/5533), @rafaelfranca) + - allow running `bundle outdated` when gems aren't installed locally ([#5553](https://github.com/rubygems/bundler/issues/5553), @segiddins) + - print a helpful error when `bundle exec`ing to a gem that isn't included in the bundle ([#5487](https://github.com/rubygems/bundler/issues/5487), @segiddins) + - print an error message when a non-git gem is given a `branch` option ([#5530](https://github.com/rubygems/bundler/issues/5530), @colby-swandale) + - allow interrupts to exit the process after gems have been installed (@segiddins) + - print the underlying error when downloading gem metadata fails ([#5579](https://github.com/rubygems/bundler/issues/5579), @segiddins) + - avoid deadlocking when installing with a lockfile that is missing dependencies ([#5378](https://github.com/rubygems/bundler/issues/5378), [#5480](https://github.com/bundler/bundler/issues/5480), [#5519](https://github.com/bundler/bundler/issues/5519), [#5526](https://github.com/bundler/bundler/issues/5526), [#5529](https://github.com/bundler/bundler/issues/5529), [#5549](https://github.com/bundler/bundler/issues/5549), [#5572](https://github.com/bundler/bundler/issues/5572), @segiddins) + +# 1.14.6 (March 3, 2017) + +## Bug fixes: + + - avoid undefined constant `Bundler::Plugin::API::Source` exception ([#5409](https://github.com/rubygems/bundler/issues/5409), @segiddins) + - avoid incorrect warnings about needing to enable `specific_platform` (@segiddins) + - fail gracefully when the compact index does not send an ETag ([#5463](https://github.com/rubygems/bundler/issues/5463), @olleolleolle) + - ensure `bundle outdated --local` shows all outdated gems ([#5430](https://github.com/rubygems/bundler/issues/5430), @denniss) + - fix a case where ruby version requirements could lead to incorrect resolver conflicts ([#5425](https://github.com/rubygems/bundler/issues/5425), @segiddins) + +# 1.14.5 (February 22, 2017) + +## Bug fixes: + + - avoid loading all unused gemspecs during `bundle exec` on RubyGems 2.3+ (@segiddins) + - improve resolver performance when dependencies have zero or one total possibilities ignoring requirements ([#5444](https://github.com/rubygems/bundler/issues/5444), [#5457](https://github.com/bundler/bundler/issues/5457), @segiddins) + - enable compact index when OpenSSL FIPS mode is enabled but not active ([#5433](https://github.com/rubygems/bundler/issues/5433), @wjordan) + - use github username instead of git name for the github url in `bundle gem` ([#5438](https://github.com/rubygems/bundler/issues/5438), @danielpclark) + - avoid a TypeError on RubyGems 2.6.8 when no build settings are set for native extensions (@okkez) + - fail gracefully when the dependency api is missing runtime dependencies for a gem (@segiddins) + - handle when a platform-specific gem has more dependencies than the ruby platform version ([#5339](https://github.com/rubygems/bundler/issues/5339), [#5426](https://github.com/bundler/bundler/issues/5426), @segiddins) + - allow running bundler on a machine with no home directory where the temporary directory is not writable ([#5371](https://github.com/rubygems/bundler/issues/5371), @segiddins) + - avoid gem version conflicts on openssl using Ruby 2.5 ([#5235](https://github.com/rubygems/bundler/issues/5235), @rhenium) + - fail when installing in frozen mode and the dependencies for `gemspec` gems have changed without the lockfile being updated ([#5264](https://github.com/rubygems/bundler/issues/5264), @segiddins) + +# 1.14.4 (February 12, 2017) + +## Bug fixes: + + - fail gracefully when attempting to overwrite an existing directory with `bundle gem` ([#5358](https://github.com/rubygems/bundler/issues/5358), @nodo) + - fix a resolver bug that would cause bundler to report conflicts that it could resolve ([#5359](https://github.com/rubygems/bundler/issues/5359), [#5362](https://github.com/bundler/bundler/issues/5362), @segiddins) + - set native extension build arguments for git gems ([#5401](https://github.com/rubygems/bundler/issues/5401), @segiddins) + - fix the suggested `bundle lock` command printed when a dependency is unused on any platform (@5t111111) + - ensure the version passed to `ruby` in the Gemfile is valid during Gemfile parsing ([#5380](https://github.com/rubygems/bundler/issues/5380), @segiddins) + - show `bundle inject` usage when too many arguments are passed ([#5384](https://github.com/rubygems/bundler/issues/5384), @Shekharrajak) + - stop `bundle show --outdated` from implicitly running `bundle update` ([#5375](https://github.com/rubygems/bundler/issues/5375), @colby-swandale) + - allow the temporary home directory fallback to work for multiple users (@svoop) + +# 1.14.3 (January 24, 2017) + +## Bug fixes: + + - fix the resolver attempting to activate ruby-platform gems when the bundle is only for other platforms ([#5349](https://github.com/rubygems/bundler/issues/5349), [#5356](https://github.com/bundler/bundler/issues/5356), @segiddins) + - avoid re-resolving a locked gemfile that uses `gemspec` and includes development dependencies ([#5349](https://github.com/rubygems/bundler/issues/5349), @segiddins) + +# 1.14.2 (January 22, 2017) + +## Bug fixes: + + - fix using `force_ruby_platform` on windows ([#5344](https://github.com/rubygems/bundler/issues/5344), @segiddins) + - fix an incorrect version conflict error when using `gemspec` on multiple platforms ([#5340](https://github.com/rubygems/bundler/issues/5340), @segiddins) + +# 1.14.1 (January 21, 2017) + +## Bug fixes: + + - work around a ruby 2.2.2 bug that caused a stack consistency error during installation ([#5342](https://github.com/rubygems/bundler/issues/5342), @segiddins) + +# 1.14.0 (January 20, 2017) + +## Bug fixes: + + - ensure `Settings::Mirror` is autoloaded under the `Settings` namespace + ([#5238](https://github.com/rubygems/bundler/issues/5238), @segiddins) + - fix `bundler/inline` when `BUNDLE_GEMFILE=""` ([#5079](https://github.com/rubygems/bundler/issues/5079), @segiddins) + +# 1.14.0.pre.2 (January 11, 2017) + +## Bug fixes: + + - allow not selecting a gem when running `bundle open` ([#5301](https://github.com/rubygems/bundler/issues/5301), @segiddins) + - support installing gems from git branches that contain shell metacharacters ([#5295](https://github.com/rubygems/bundler/issues/5295), @segiddins) + - fix a resolver error that could leave dependencies unresolved ([#5294](https://github.com/rubygems/bundler/issues/5294), @segiddins) + - fix a stack overflow error when invoking commands ([#5296](https://github.com/rubygems/bundler/issues/5296), @segiddins) + +# 1.14.0.pre.1 (December 29, 2016) + +## Features: + + - `bundle doctor` first runs `bundle check` (@segiddins) + - the bundler trampoline is automatically enabled when the target version is greater than bundler 2 (@segiddins) + - gem checksums returned by rubygems.org are validated when installing gems ([#4464](https://github.com/rubygems/bundler/issues/4464), @segiddins) + - use the git username as a github username when running `bundle gem` (@JuanitoFatas) + - show more context when the resolver conflicts on required ruby and rubygems versions (@segiddins) + - improve platform support by allowing bundler to pick the best platform match during dependency resolution, enabled with the `specific_platform` setting ([#4295](https://github.com/rubygems/bundler/issues/4295), [#4896](https://github.com/bundler/bundler/issues/4896), @segiddins) + - always prompt the user for a password when using `sudo` ([#3006](https://github.com/rubygems/bundler/issues/3006), @segiddins) + - support running without a home directory ([#4778](https://github.com/rubygems/bundler/issues/4778), @segiddins) + - print a warning when the gemfile uses a platform conditional that will exclude the gem from all lockfile platforms (@segiddins) + - add the `force_ruby_platform` setting to force bundler to install ruby-platform gems, even on other platforms ([#4813](https://github.com/rubygems/bundler/issues/4813), @segiddins) + - add conservative update options to `bundle lock` ([#4912](https://github.com/rubygems/bundler/issues/4912), @chrismo) + - improve `bundle outdated` output to group gems by group (@ryanfox1985) + - add conservative update options to `bundle update` ([#5065](https://github.com/rubygems/bundler/issues/5065), [#5076](https://github.com/bundler/bundler/issues/5076), @chrismo) + - print the output of `bundle env` as github-flavored markdown, making it easier to preserve formatting when copy-pasting into a new issue (@segiddins) + - configure the persistence file when using `bundle gem` with `rspec` (@segiddins) + - add support for the `ruby_25` gemfile filter (@amatsuda) + - when installing with a lockfile that is missing dependencies, allow installation to proceed (but without parallelism) (@segiddins) + +## Performance: + + - improve `require "bundler"` performance by ~5x (@segiddins) + - allow install gems in parallel when running on rubygems 2+ + +## Bug fixes: + + - config files with CRLF line endings can be read ([#4435](https://github.com/rubygems/bundler/issues/4435), @segiddins) + - `bundle lock` activates gems for the current platform even if they were activated under a different platform for a separate dependency ([#4896](https://github.com/rubygems/bundler/issues/4896), @segiddins) + - running `bundle env` in a directory without a gemfile no longer crashes (@segiddins) + - fail gracefully when attempting to use a source with an unknown URI scheme ([#4953](https://github.com/rubygems/bundler/issues/4953), @segiddins) + - store paths in the lockfile relative to the root gemfile directory when using `eval_gemfile` ([#4966](https://github.com/rubygems/bundler/issues/4966), @segiddins) + - `bundle lock` will not update without the `--update` flag ([#4957](https://github.com/rubygems/bundler/issues/4957), @segiddins) + - the `console` binstub generated by `bundle gem` will load `.irbrc` files (@mattbrictson) + - print friendly filesystem access errors in the new index (@segiddins) + - print a helpful error when running out of memory on jruby ([#4673](https://github.com/rubygems/bundler/issues/4673), @segiddins) + - load all rubygems plugins when installing gems ([#2824](https://github.com/rubygems/bundler/issues/2824), @segiddins) + - `bundle clean --dry-run` prints the list of gems without the `--force` option when no path is set ([#5027](https://github.com/rubygems/bundler/issues/5027), @hmistry) + - local installs no longer print "this gem may have been yanked" ([#5022](https://github.com/rubygems/bundler/issues/5022), @hmistry) + - avoid leaking `which` output when running `bundle doctor` (@colby-swandale) + - print a warning when attempting to `bundle exec` an empty program ([#5084](https://github.com/rubygems/bundler/issues/5084), @bronzdoc) + - ensure `bundle outdated` lists all outdated gems ([#4979](https://github.com/rubygems/bundler/issues/4979), @chrismo) + - fail gracefully when attempting to `bundle gem` with an invalid constant name ([#5185](https://github.com/rubygems/bundler/issues/5185), @segiddins) + - allow `bundler/inline` to work in a directory that contains a gemfile ([#5117](https://github.com/rubygems/bundler/issues/5117), @colby-swandale) + - ensure that the new index is thread-safe, allowing installation on rbx ([#5142](https://github.com/rubygems/bundler/issues/5142), @segiddins) + - remove deprecated `rspec` syntax in `bundle gem` output (@gearnode) + - fail gracefully when any system error is encountered when touching the filesystem ([#5134](https://github.com/rubygems/bundler/issues/5134), @segiddins) + - fix compatibility with a machine running with FIPS mode enabled ([#4989](https://github.com/rubygems/bundler/issues/4989), @segiddins) + - fix `bundle lock --add-platform ruby` ([#5230](https://github.com/rubygems/bundler/issues/5230), @segiddins) + - print gem post-install messages when running `bundle update` (@smathy) + - ensure errors due to a retries are all separated by a newline (@segiddins) + - print out the bundle path in gem not found errors ([#4854](https://github.com/rubygems/bundler/issues/4854), @diegosteiner) + - fail gracefully when creating threads fails (@segiddins) + - avoid downloading metadata for gems that are only development dependencies (@Paxa) + +# 1.13.7 (December 25, 2016) + +## Features: + + - add support for the `ruby_24` gemfile filter ([#5281](https://github.com/rubygems/bundler/issues/5281), @amatsuda) + +# 1.13.6 (October 22, 2016) + +## Bug fixes: + + - make the `gem` method public again, fixing a regression in 1.13.4 ([#5102](https://github.com/rubygems/bundler/issues/5102), @segiddins) + +# 1.13.5 (October 15, 2016) + +## Bug fixes: + + - Ensure a locked pre-release spec can always be re-resolved ([#5089](https://github.com/rubygems/bundler/issues/5089), @segiddins) + +# 1.13.4 (October 11, 2016) + +## Bug fixes: + + - stop printing warning when compact index versions file is rewritten ([#5064](https://github.com/rubygems/bundler/issues/5064), @indirect) + - fix `parent directory is world writable but not sticky` error on install ([#5043](https://github.com/rubygems/bundler/issues/5043), @indirect) + - fix for `uninitialized constant Bundler::Plugin::API::Source` error ([#5010](https://github.com/rubygems/bundler/issues/5010), @hsbt, @aycabta) + - make `update` options for major, minor, and patch updates consistent ([#4934](https://github.com/rubygems/bundler/issues/4934), @chrismo) + +# 1.13.3 (October 10, 2016) + +## Bug fixes: + + - add support for weak etags to the new index (@segiddins) + +# 1.13.2 (September 30, 2016) + +## Bug fixes: + + - allow `Settings` to be initialized without a root directory (@m1k3) + - allow specifying ruby engines in the gemfile as a symbol ([#4919](https://github.com/rubygems/bundler/issues/4919), @JuanitoFatas) + - avoid an exception when using `bundler/deployment` with Vlad (@srbaker) + - ensure redefined methods have the same visibility as the one they're replacing, fixing `Kernel.require` failing on JRuby ([#4975](https://github.com/rubygems/bundler/issues/4975), @segiddins) + - ensure that Bundler won't complain about a corrupt lockfile when no lockfile exists when using `gemspec` in the Gemfile ([#5006](https://github.com/rubygems/bundler/issues/5006), @segiddins) + - fail gracefully when parsing the metadata for a gemspec from the compact index fails (@segiddins) + - fix system gems not being copied to --path on bundle install (e.g. --deployment) ([#4974](https://github.com/rubygems/bundler/issues/4974), @chrismo) + +## Performance: + + - avoid parsing the lockfile twice when evaluating gemfiles (@segiddins) + +# 1.13.1 (September 13, 2016) + +## Bug fixes: + + - ensure that `Gem::Source` is available, fixing several exceptions ([#4944](https://github.com/rubygems/bundler/issues/4944), @dekellum) + - ensure that dependency resolution works when multiple gems have the same dependency ([#4961](https://github.com/rubygems/bundler/issues/4961), @segiddins) + +# 1.13.0 (September 5, 2016) + + No changes. + +# 1.13.0.rc.2 (August 21, 2016) + +## Features: + + - add setting `exec_disable_load` to force `exec` to spawn a new Ruby process (@segiddins) + - add `doctor` command to help with issues like unlinked compiled gems ([#4765](https://github.com/rubygems/bundler/issues/4765), @mistydemeo) + - rework the `update` command, providing fine-grained control via flags ([#4676](https://github.com/rubygems/bundler/issues/4676), @chrismo) + - add URI to http response output in debug mode ([#4808](https://github.com/rubygems/bundler/issues/4808), @NickLaMuro) + - add manpage for `binstubs` command ([#4847](https://github.com/rubygems/bundler/issues/4847), @Zorbash) + - support `mirror` settings for sources by hostname, not only full URL (@opiethehokie) + - print gem installation errors after other install output ([#4834](https://github.com/rubygems/bundler/issues/4834), @segiddins) + - add `lock --remove-platform` flag to remove platforms from the lock ([#4877](https://github.com/rubygems/bundler/issues/4877), @segiddins) + - add `only_update_to_newer_versions` setting to prevent downgrades during `update` (@segiddins) + - expanded experimental plugin support to include hooks and sources (@asutoshpalai) + +## Bug fixes: + + - retry gem downloads ([#4846](https://github.com/rubygems/bundler/issues/4846), @jkeiser) + - improve the CompactIndex to handle capitalized legacy gems ([#4867](https://github.com/rubygems/bundler/issues/4867), @segiddins) + - re-use persistent HTTP connections for CompactIndex (@NickLaMuro) + - respect `required_ruby_version` when Gemfile contains `ruby` version (@indirect) + - allow `rake release` to sign git tags ([#4743](https://github.com/rubygems/bundler/issues/4743), @eagletmt) + - set process titles when using `#load` during `exec` (@yob) + - recognize JRuby shebangs for using `#load` during `exec` (@ojab) + - handle world-writable home directories ([#4726](https://github.com/rubygems/bundler/issues/4726), @allenzhao) + - support multi-platform gems via the `gemspec` Gemfile method ([#4798](https://github.com/rubygems/bundler/issues/4798), @segiddins) + - print dots correctly for CompactIndex fetcher (@NickLaMuro) + - set an `open_timeout` when requesting gem data via HTTP (@NickLaMuro) + - rename the BUNDLE\_ORIG\_ENV variable so it no longer shows up in `config` (@indirect) + - show help only when `-h` or `--help` is passed to Bundler, not to `exec` ([#4801](https://github.com/rubygems/bundler/issues/4801), @segiddins) + - handle symlinks to binstubs created by `--standalone` ([#4782](https://github.com/rubygems/bundler/issues/4782), @terinjokes) + +# 1.13.0.rc.1 (June 27, 2016) + +## Features: + + - when `bundle config major_deprecations` or `BUNDLE_MAJOR_DEPRECATIONS` is set, deprecation warnings for bundler 2 will be printed (@segiddins) + - when running with `--verbose`, bundler will print the reason it is re-resolving a gemfile (@segiddins) + +## Bug fixes: + + - fix support for running RubyGems 1.x on Ruby 2.3 ([#4698](https://github.com/rubygems/bundler/issues/4698), @segiddins) + - fix bundle exec'ing to a ruby file when gems are installed into a path ([#4592](https://github.com/rubygems/bundler/issues/4592), @chrismo) + - when multiple specs in a bundle have the same executable, prefer activating the one from the requested gem ([#4705](https://github.com/rubygems/bundler/issues/4705), @segiddins) + - stop changing the load path to require the vendored postit when trampolining (@segiddins) + - ensure relative paths are properly printed after completing an installation (@jenseng) + - fix re-resolving when there are multiple unchanged path sources (@segiddins) + - de-init submodules when running git 2.9 and requesting a git gem without submodules (@segiddins) + +# 1.13.0.pre.1 (June 20, 2016) + +## Performance: + + - speed up gemfile resolution during `bundle install` by between 4x-100x ([#4376](https://github.com/rubygems/bundler/issues/4376), @segiddins) + - generally reduce object allocations when using bundler (@segiddins) + - speed up bin generation for path gems with many files ([#2846](https://github.com/rubygems/bundler/issues/2846), @segiddins) + - fix detecting path spec changes to avoid re-resolving unnecessarily (@jrafanie) + +## Features: + + - automatically trampoline to the bundler version locked in the lockfile, only updating to the running version on `bundle update --bundler` (@segiddins) + - laying the groundwork for plugin support, which is currently unsuppported, undocumented, disabled by default, and liable to change without notice (@asutoshpalai) + - allow `bundle viz --without` to accept multiple `:`-delimited groups (@mobilutz) + - support for RubyGems 2.6.4 ([#4368](https://github.com/rubygems/bundler/issues/4368), @segiddins, @RochesterinNYC) + - colorize updated gem versions ([#4334](https://github.com/rubygems/bundler/issues/4334), @bronzdoc) + - add the `--standalone` flag to `bundle binstubs` ([#4594](https://github.com/rubygems/bundler/issues/4594), @b-ggs) + - update the `bundle gem` CoC to contributor covenant v1.4 (@cllns) + - use a custom YAML serializer to make config file consistent (@segiddins) + - filter credentials from error messages (bundler/bundler-features[#111](https://github.com/rubygems/bundler/issues/111), @RochesterinNYC, @sandlerr) + - support relative paths used inside a nested invocation of `eval_gemfile` ([#4584](https://github.com/rubygems/bundler/issues/4584), @RochesterinNYC) + - fail gracefully when attempting to install a yanked gem ([#4344](https://github.com/rubygems/bundler/issues/4344), @allenzhao) + - automatically install an inline gemfile when gems are missing locally (@segiddins) + - allow conflicts for gems resolved via `gemspec` (@segiddins) + - add `--add-platform` option to `bundle lock` (@segiddins) + - fail gracefully when a resolved spec's `required_ruby_version` or `required_rubygems_version` is incompatible (@segiddins) + +## Bug fixes: + + - implicitly unlock the resolved ruby version when the declared requirements in the gemfile are incompatible with the locked version ([#4595](https://github.com/rubygems/bundler/issues/4595), [#4627](https://github.com/bundler/bundler/issues/4627), @segiddins) + - add support for quoted paths in `$PATH` ([#4323](https://github.com/rubygems/bundler/issues/4323), @segiddins) + - check out missing git repos that are not being installed ([#3981](https://github.com/rubygems/bundler/issues/3981), @asutoshpalai) + - write `bundler/setup.rb` to a consistent path (@glennpratt) + - open editor in `bundle open` with a clean environment (@sj26) + - resolve infinitely recursive copy when running `bundle package --all` with a `gemspec` in the gemfile ([#4392](https://github.com/rubygems/bundler/issues/4392), [#4430](https://github.com/bundler/bundler/issues/4430), @RochesterinNYC) + - fail gracefully when encountering an `Errno::ENOTSUP` ([#4394](https://github.com/rubygems/bundler/issues/4394), @segiddins) + - fail gracefully when encountering an `Errno::EHOSTUNREACH` ([#4642](https://github.com/rubygems/bundler/issues/4642), @allenzhao) + - fix loading config files with very long values ([#4370](https://github.com/rubygems/bundler/issues/4370), @segiddins) + - only show potential updates for gemfile platforms in `bundle outdated` ([#4450](https://github.com/rubygems/bundler/issues/4450), @RochesterinNYC) + - allow running `bundle install --deployment` after `bundle package --all` with path gems ([#2175](https://github.com/rubygems/bundler/issues/2175), @allenzhao) + - add support for patchlevels in ruby versions in the gemfile and gemspecs ([#4593](https://github.com/rubygems/bundler/issues/4593), @chalkos) + +# 1.12.6 (October 10, 2016) + +## Bug fixes: + - add support for weak etags to the new index (@segiddins) + +# 1.12.5 (May 25, 2016) + +## Bug fixes: + - only take over `--help` on `bundle exec` when the first two arguments are `exec` and `--help` ([#4596](https://github.com/rubygems/bundler/issues/4596), @segiddins) + - don't require `require: true` dependencies that are excluded via `env` or `install_if` (@BrianHawley) + - reduce the number of threads used simultaneously by bundler ([#4367](https://github.com/rubygems/bundler/issues/4367), @will-in-wi) + +# 1.12.4 (May 16, 2016) + +## Bug fixes: + - ensure concurrent use of the new index can't corrupt the cache ([#4519](https://github.com/rubygems/bundler/issues/4519), @domcleal) + - allow missing rubygems credentials when pushing a gem with a custom host ([#4437](https://github.com/rubygems/bundler/issues/4437), @Cohen-Carlisle) + - fix installing built-in specs with `--standalone` ([#4557](https://github.com/rubygems/bundler/issues/4557), @segiddins) + - fix `bundle show` when a gem has a prerelease version that includes a `-` ([#4385](https://github.com/rubygems/bundler/issues/4385), @segiddins) + +# 1.12.3 (May 6, 2016) + +## Bug fixes: + - fix uncoditionally writing `.bundle/config` when running `bundle install` (@segiddins) + - fall back to the dependency API and the full index when the home directory is not writable (@segiddins) + +# 1.12.2 (May 4, 2016) + +## Bug fixes: + - fix modifying a frozen string when the resolver conflicts on dependencies with requirements ([#4520](https://github.com/rubygems/bundler/issues/4520), @grzuy) + - fix `bundle exec foo --help` not showing the invoked command's help ([#4480](https://github.com/rubygems/bundler/issues/4480), @b-ggs) + +# 1.12.1 (April 30, 2016) + +## Bug fixes: + - automatically fallback when the new index has a checksum mismatch instead of erroring (@segiddins) + - fix computation of new index file local checksums on Windows ([#4472](https://github.com/rubygems/bundler/issues/4472), @mwrock) + - properly handle certain resolver backtracking cases without erroring (@segiddins, [#4484](https://github.com/rubygems/bundler/issues/4484)) + - ensure the `$LOAD_PATH` contains specs' load paths in the correct order (@segiddins, [#4482](https://github.com/rubygems/bundler/issues/4482)) + +# 1.12.0 (April 28, 2016) + + No changes. + +# 1.12.0.rc.4 (April 21, 2016) + +## Bug fixes: + + - don't fail when `bundle outdated` is run with flags and the lockfile contains non-semver versions ([#4438](https://github.com/rubygems/bundler/issues/4438), @RochesterinNYC) + +# 1.12.0.rc.3 (April 19, 2016) + +## Bug fixes: + + - don't allow new attributes to dirty a lockfile when running `bundle exec`, `-rbundler/setup`, or `bundle check` (@segiddins) + +# 1.12.0.rc.2 (April 15, 2016) + +## Features: + + - `bundle outdated` handles all combinations of `--major`, `--minor`, and `--patch` ([#4396](https://github.com/rubygems/bundler/issues/4396), @RochesterinNYC) + +## Bug fixes: + + - prevent endless recursive copy for `bundle package --all` ([#4392](https://github.com/rubygems/bundler/issues/4392), @RochesterinNYC) + - allow executables that are `load`ed to exit non-0 via an `at_exit` hook when invoked by `bundle exec` (@segiddins) + - nested invocations of `bundle exec` properly preserve the `$PATH` and `$GEM_PATH` environment variables (@segiddins) + +# 1.12.0.rc (March 13, 2016) + +## Performance: + + - Download gem metadata from globally distributed CDN endpoints ([#4358](https://github.com/rubygems/bundler/issues/4358), @segiddins) + +## Bug fixes: + + - handle Ruby pre-releases built from source ([#4324](https://github.com/rubygems/bundler/issues/4324), @RochesterinNYC) + - support binstubs from RubyGems 2.6 ([#4341](https://github.com/rubygems/bundler/issues/4341), @segiddins) + - handle quotes present in in PATH ([#4326](https://github.com/rubygems/bundler/issues/4326), @segiddins) + +# 1.12.0.pre.2 (February 26, 2016) + +## Performance: + + - speed up `bundle exec` by `load`ing the executable whenever possible, saving roughly .2 seconds per invocation (@segiddins) + +## Features: + + - add a `--patch` flag for `bundle outdated` (@RochesterinNYC) + - add `Bundler.clean_env` and `Bundler.original_env` ([#4232](https://github.com/rubygems/bundler/issues/4232), @njam) + - add `--frozen` support to `bundle package` ([#3356](https://github.com/rubygems/bundler/issues/3356), @RochesterinNYC) + +## Bug fixes: + + - place bundler loaded gems after `-I` and `RUBYLIB` (@Elffers) + - give a better error message when filesystem access raises an `EPROTO` error ([#3581](https://github.com/rubygems/bundler/issues/3581), [#3932](https://github.com/bundler/bundler/issues/3932), [#4163](https://github.com/bundler/bundler/issues/4163), @RochesterinNYC) + - give a better error message when both `--deployment` and `--system` are used together (@RochesterinNYC) + - fix `$PATH` being preserved for use in `Bundler.with_clean_env` ([#4251](https://github.com/rubygems/bundler/issues/4251), @segiddins, @njam) + - give a better error message when running `bundle outdated` in frozen mode ([#4287](https://github.com/rubygems/bundler/issues/4287), @RochesterinNYC) + - handle when `http_proxy` is set to `:no_proxy` in the rubygems configuration ([#4294](https://github.com/rubygems/bundler/issues/4294), @segiddins) + - give a better error message when authentication details aren't properly escaped ([#4288](https://github.com/rubygems/bundler/issues/4288), @RochesterinNYC) + - fix `bundle outdated --minor` to only report updates that match the current minor version (@RochesterinNYC) + - fix extra dependencies being resolved unnecessarily ([#4276](https://github.com/rubygems/bundler/issues/4276), @segiddins) + - give a better error message when missing specs due to platform mis-matches ([#4259](https://github.com/rubygems/bundler/issues/4259), @RochesterinNYC) + - skip rebuilding extensions for git gems if they are already built ([#4082](https://github.com/rubygems/bundler/issues/4082), @csfrancis, @indirect, @segiddins) + - fix `bundle install` not installing when the `no_install` setting is set ([#3966](https://github.com/rubygems/bundler/issues/3966), @chulkilee, @segiddins) + +# 1.12.0.pre.1 (February 9, 2016) + +## Performance: + + - speed up `bundle install` and `bundle update` by using the new compact gem index (@segiddins, @fotanus, @indirect) + - speed up `bundle exec` by avoiding loading the gemfile twice ([#2951](https://github.com/rubygems/bundler/issues/2951), [#2952](https://github.com/bundler/bundler/issues/2952), @segiddins) + +## Features: + + - add support for using version operators to specify ruby versions in the Gemfile (@jtarchie) + - redirect `--help` flag for plugins to that plugin's man page (@RochesterinNYC) + - support probing a mirror with a fallback timeout ([#4128](https://github.com/rubygems/bundler/issues/4128), @pcarranza) + - add `--full-index` option to `bundle lock` (@segiddins) + - support running with frozen string literals (@deepj, @segiddins) + - add `--major` and `--minor` options to `bundle outdated` ([#3805](https://github.com/rubygems/bundler/issues/3805), @cirdes) + - allow passing a custom `ui` to `bundler/inline` (@lamont-granquist) + - add support for ruby 2.4 ([#4266](https://github.com/rubygems/bundler/issues/4266), @segiddins) + - add `bundle outdated --parseable` for machine-readable output (@RochesterinNYC) + +## Bug fixes: + + - fix `bundle package --all` recursing endlessly ([#4158](https://github.com/rubygems/bundler/issues/4158), @RochesterinNYC) + - fail fast on more errors when fetching remote resources ([#4154](https://github.com/rubygems/bundler/issues/4154), @RochesterinNYC) + - give a better error message when a given git commit can't be found ([#4140](https://github.com/rubygems/bundler/issues/4140), @doy) + - give a better error message when `bundle clean` doesn't have sufficient permissions ([#4170](https://github.com/rubygems/bundler/issues/4170), @RochesterinNYC) + - give a better error message when reading a bundler config file fails (@segiddins) + - restrict platforms when referencing a `gemspec` in the `Gemfile` to those defined in the gemspec ([#4102](https://github.com/rubygems/bundler/issues/4102), [#4150](https://github.com/bundler/bundler/issues/4150), @smellsblue) + - fix `bundle gem` with minitest to use the correct rake task (@kotoshenya) + - give a better error message when ssl isn't available ([#4054](https://github.com/rubygems/bundler/issues/4054), @RochesterinNYC) + - print the original `require` error when `Bundler.require` fails ([#4182](https://github.com/rubygems/bundler/issues/4182), @RochesterinNYC) + - give a better error message when certain resources are temporarily unavailable ([#4183](https://github.com/rubygems/bundler/issues/4183), @RochesterinNYC) + - fix returning case-sensitive gem mirror URIs on ruby 2.3 (@segiddins) + - ignore colorized output from `git` when determining the current branch ([#4056](https://github.com/rubygems/bundler/issues/4056), @agis-) + - fix storing the shared gems config option as a boolean (@vassilevsky) + - add support for running `bundle gem --exe` instead of using the `--bin` option (@christhekeele) + - fix `exec`-ing with 0 args in a directory with spaces ([#4230](https://github.com/rubygems/bundler/issues/4230), @segiddins) + - avoid installing extraneous gems when resolving to an older version of a spec ([#4101](https://github.com/rubygems/bundler/issues/4101), [#4198](https://github.com/bundler/bundler/issues/4198), @segiddins) + - ensure paths resolved when parsing a gemfile are relative to that file ([#3349](https://github.com/rubygems/bundler/issues/3349), @dtognazzini) + - give a better error message when encountering an invalid gemspec ([#4248](https://github.com/rubygems/bundler/issues/4248), [#4275](https://github.com/bundler/bundler/issues/4275), @RochesterinNYC) + - preserve the original `PATH` in `Bundler.with_clean_env` ([#4251](https://github.com/rubygems/bundler/issues/4251), @segiddins) + - ensure standalone file paths are relative to the project root ([#4144](https://github.com/rubygems/bundler/issues/4144), @glennpratt) + +# 1.11.2 (December 15, 2015) + +## Bug fixes: + + - _really_ stop calling `required_ruby_version` on nil @specifications ([#4147](https://github.com/rubygems/bundler/issues/4147), @indirect) + +# 1.11.1 (December 15, 2015) + +## Bug fixes: + + - lazy-load Psych, again ([#4149](https://github.com/rubygems/bundler/issues/4149), @indirect) + - allow gemspec gems on other platforms ([#4150](https://github.com/rubygems/bundler/issues/4150), @indirect) + - fix --no-coc and --no-mit flags on `gem` ([#4148](https://github.com/rubygems/bundler/issues/4148), @RochesterinNYC) + - stop calling `required_ruby_version` on nil @specifications ([#4147](https://github.com/rubygems/bundler/issues/4147), @indirect) + +# 1.11.0 (December 12, 2015) + + No changes. + +# 1.11.0.pre.2 (December 6, 2015) + +## Bug fixes: + + - fail gracefully when trying to execute a non-executable file ([#4081](https://github.com/rubygems/bundler/issues/4081), @fotanus) + - fix a crash when pushing a gem via `rake release` (@segiddins) + +# 1.11.0.pre.1 (November 29, 2015) + +## Features: + + - actual Gemfile and lockfile filenames are used in messages ([#3672](https://github.com/rubygems/bundler/issues/3672), @segiddins) + - the git remote for `rake release` is now customizable (@skateman) + - file access permissions errors are now much more friendly ([#3703](https://github.com/rubygems/bundler/issues/3703), [#3735](https://github.com/bundler/bundler/issues/3735), [#3858](https://github.com/bundler/bundler/issues/3858), [#3988](https://github.com/bundler/bundler/issues/3988), [#4009](https://github.com/bundler/bundler/issues/4009) @repinel, @Elffers, @segiddins, @agis-) + - add support for showing help for plugin commands (@tf) + - send `X-Gemfile-Source` header to source mirrors (@agis-) + - show what version upstream dependencies were resolved to in conflict messages (@segiddins) + - add support for using bundler setting to add private access credentials for git sources (@frsyuki) + - take into consideration HTTP proxy settings in `.gemrc` (@PG-kura) + - allow specifying a gem host to push to in the `GEM_HOST` environment variable (@pmenglund) + - when gempec `required_ruby_version` is available and the Gemfile specifies a ruby version, resolve for the given ruby version (@segiddins) + - allow setting a `silence_root_warning` setting to silence the warning when `bundle install` is run as root (@blackxored) + - update the `bundle gem` code of conduct template to Contributor Covenant v1.3.0 (@CoralineAda) + - add support for specifying gems to update when running `bundle lock` via `--update gem1 gem2` (@JuanitoFatas) + - added support for MRI 2.3 (@amatsuda) + - show a helpful message when requiring a file in `bundler require` fails ([#3960](https://github.com/rubygems/bundler/issues/3960), @agis-) + - include git revision hash when printing a git source ([#3433](https://github.com/rubygems/bundler/issues/3433), @agis-) + - improve hint when a resolution conflict occurs (@seanlinsley) + - show a friendly error when a git ref is not found ([#3879](https://github.com/rubygems/bundler/issues/3879), @agis-) + - improve error message when sources are not absolute URIs ([#3925](https://github.com/rubygems/bundler/issues/3925), @agis-) + - add `pkg` to rake's clobber list ([#3676](https://github.com/rubygems/bundler/issues/3676), @jasonkarns) + - retry fetching specs when fetching version metadata fails (@jingweno) + +## Bug fixes: + + - avoid showing bundler version warning messages twice (@fotanus) + - fix running `bundle check` with `--path` when the gems are only installed globally (@akihiro17) + - fix `bin/setup` from `bundle gem` assuming `bash` is in `/bin` + - fail more gracefully when an HTTP remote is unreachable ([#3765](https://github.com/rubygems/bundler/issues/3765), @steverob) + - fix a warning running `bundle exec` on jruby 9.0.0.0 (@deivid-rodriguez, @mastfish) + - fix the `bundle gem` readme when no tests are generated (@roseweixel) + - the dependencies on test gems in `bundle gem` are now locked to major versions ([#3811](https://github.com/rubygems/bundler/issues/3811), @indirect) + - fix the paths for native extensions generated by `--standalone` ([#3813](https://github.com/rubygems/bundler/issues/3813), @AlexanderPavlenko) + - fix trying to cache a gem that has no source (@EduardoBautista) + - fix `--source` option to `bundle update` causing incorrect gem unlocking ([#3759](https://github.com/rubygems/bundler/issues/3759), [#3761](https://github.com/bundler/bundler/issues/3761), @neoeno) + - fix handling an empty `BUNDLE_GEMFILE` environment variables ([#3678](https://github.com/rubygems/bundler/issues/3678), @agis-) + - avoid cleaning up gem extension directory in `bundle clean` (@Sirupsen) + - fix the `ssl_verify_mode` setting not being treated as a number (@goughy000) + - fix not retrying on zlib errors ([#4047](https://github.com/rubygems/bundler/issues/4047), @andremedeiros) + - fix a warning being shown for using `URI.encode` (@EduardoBautista) + - fix handling of fatal HTTP errors ([#3830](https://github.com/rubygems/bundler/issues/3830), @indirect) + - ensure all `sudo` access is done in a thread-safe manner ([#3910](https://github.com/rubygems/bundler/issues/3910), @agis-) + - fix caching gems with a path with the same prefix as the bundled application (@indirect) + - fix showing gemspec validation errors on `bundle exec` ([#3895](https://github.com/rubygems/bundler/issues/3895), @agis-) + - distinguish Gemfile syntax and evaluation errors ([#3783](https://github.com/rubygems/bundler/issues/3783), @agis-) + - fix nested Gemfile sources not restoring the previous source ([#3974](https://github.com/rubygems/bundler/issues/3974), @agis-) + - fix the `RUBYLIB` environment variable not being cleaned ([#3982](https://github.com/rubygems/bundler/issues/3982), @agis-) + - fix handling a dependency missing from `Gemfile.lock` so parallel installation does not deadlock ([#4012](https://github.com/rubygems/bundler/issues/4012), @lukaso) + - also print gemspecs in `bundle env` output (@agis-) + - fix handling when a `path` source does not have a gemspec but a lockfile says there is ([#4004](https://github.com/rubygems/bundler/issues/4004), @segiddins) + - show a warning when the `RUBYGEMS_GEMDEPS` environment variable is set ([#3656](https://github.com/rubygems/bundler/issues/3656), @agis-) + - fix handling invalid RubyGems configuration files ([#4042](https://github.com/rubygems/bundler/issues/4042), @agis-) + - fix `bundle console` falling back to `irb` when the preferred console is unavailable (@felixbuenemann) + - restrict platforms when referencing a `gemspec` in the `Gemfile` to those defined in the gemspec ([#4102](https://github.com/rubygems/bundler/issues/4102), @smellsblue) + +## Performance: + + - speed up dependency resolution in pathological cases by 25x ([#3803](https://github.com/rubygems/bundler/issues/3803), @segiddins) + - drop string allocations when searching for gems (@jrafanie) + +# 1.10.6 (July 22, 2015) + +## Bug fixes: + + - only warn on invalid gemspecs (@indirect) + - fix installing dependencies in the correct order ([#3799](https://github.com/rubygems/bundler/issues/3799), @pducks32) + - fix sorting of mixed DependencyLists ([#3762](https://github.com/rubygems/bundler/issues/3762), @tony-spataro-rs) + - fix `install_if` conditionals when using the block form (@danieltdt) + +# 1.10.5 (June 24, 2015) + +## Bug fixes: + + - don't add or update BUNDLED WITH during `install` with no changes (@segiddins) + - fix sorting of mixed DependencyLists with RubyGems >= 2.23 ([#3762](https://github.com/rubygems/bundler/issues/3762), @tony-spataro-rs) + - speed up resolver for path and git gems (@segiddins) + - fix `install --force` to not reinstall Bundler ([#3743](https://github.com/rubygems/bundler/issues/3743), @karlo57) + +# 1.10.4 (June 16, 2015) + +## Bug fixes: + + - don't add BUNDLED WITH to the lock when Spring runs `check` over and over (@indirect) + - display "with native extensions" log output correctly (@ivantsepp) + - alias `i` to `install`, `c` to `check`, and `e` to `exec` (@indirect) + +# 1.10.3 (June 3, 2015) + +## Bug fixes: + + - allow missing gemspec files when validating path and git gems ([#3686](https://github.com/rubygems/bundler/issues/3686), [#3698](https://github.com/bundler/bundler/issues/3698), @segiddins) + - fix regression in `rake install` ([#3701](https://github.com/rubygems/bundler/issues/3701), [#3705](https://github.com/bundler/bundler/issues/3705), @segiddins) + - fix regression when calling `gem` with `bundle exec` or `-rbundler/setup` ([#3699](https://github.com/rubygems/bundler/issues/3699), @segiddins) + - fix `bundler/inline` requiring a newly-installed gem ([#3693](https://github.com/rubygems/bundler/issues/3693), @indirect, @segiddins) + +# 1.10.2 (May 29, 2015) + +## Bug fixes: + + - fix regression in `bundle update GEM` performance introduced in 1.10.0 ([#3687](https://github.com/rubygems/bundler/issues/3687), @segiddins) + +# 1.10.1 (May 28, 2015) + +## Bug fixes: + + - silence ruby warning when running CLI commands (@segiddins) + - validate gemspecs in non-packaging mode ([#3681](https://github.com/rubygems/bundler/issues/3681), @segiddins) + - ensure the same chdir mutex as RubyGems is used ([#3680](https://github.com/rubygems/bundler/issues/3680), @segiddins) + +# 1.10.0 (May 28, 2015) + + No changes. + +# 1.10.0.rc (May 16, 2015) + +## Features: + + - dramatically speed up resolving some slow Gemfiles ([#3635](https://github.com/rubygems/bundler/issues/3635), @segiddins) + - track CI platforms running Bundler ([#3646](https://github.com/rubygems/bundler/issues/3646), @fotanus) + +## Bug fixes: + + - allow `viz` to work with prereleases ([#3621](https://github.com/rubygems/bundler/issues/3621), [#3217](https://github.com/bundler/bundler/issues/3217), @aprescott) + - validate gemspecs used in path and git gems ([#3639](https://github.com/rubygems/bundler/issues/3639), @segiddins, @indirect) + - stop printing config warnings when config is unchanged ([#3649](https://github.com/rubygems/bundler/issues/3649), @fotanus, @indirect) + - Without groups saved via `config` are no longer ignored when the `--without` flag is used + +# 1.10.0.pre.2 (May 7, 2015) + +## Bug fixes: + + - make BUNDLED WITH backwards compatible ([#3623](https://github.com/rubygems/bundler/issues/3623), @segiddins) + +# 1.10.0.pre.1 (May 5, 2015) + +## Bug fixes: + + - always clean up tmp dirs ([#3277](https://github.com/rubygems/bundler/issues/3277), @hone, @indirect, @segiddins) + +# 1.10.0.pre (May 3, 2015) + +## Features: + + - support gem extensions built into any directory on RubyGems 2.2+ ([#3582](https://github.com/rubygems/bundler/issues/3582), @voxik) + - add 'bundler/inline' which provides a `gemfile` method ([#3440](https://github.com/rubygems/bundler/issues/3440), @segiddins) + - improved error reports for Gemfile errors ([#3480](https://github.com/rubygems/bundler/issues/3480), @segiddins) + - `lock` command ([#3437](https://github.com/rubygems/bundler/issues/3437), @segiddins) + - add `ignore_messages` config to suppress post-install text ([#3510](https://github.com/rubygems/bundler/issues/3510), @pducks32) + - improve `gem` minitest template ([#3513](https://github.com/rubygems/bundler/issues/3513), [#3515](https://github.com/bundler/bundler/issues/3515), @arthurnn) + - add `install --force` to re-install installed gems ([#3519](https://github.com/rubygems/bundler/issues/3519), @segiddins) + - show more `outdated` information, including groups (@smlance, @indirect) + - add optional groups to the Gemfile ([#3531](https://github.com/rubygems/bundler/issues/3531), @jhass) + - accept glob argument to `gemspec` in Gemfile ([#3464](https://github.com/rubygems/bundler/issues/3464), @pjump) + - make timeouts and retries configurable via `config` ([#3601](https://github.com/rubygems/bundler/issues/3601), @pducks32) + - add `install_if` Gemfile method for conditional installs ([#3611](https://github.com/rubygems/bundler/issues/3611), @segiddins) + +## Bug fixes: + + - standalone mode now uses builtin gems correctly ([#3610](https://github.com/rubygems/bundler/issues/3610), @segiddins) + - fix `rake spec:deps` on MinGW Ruby 2.0+ ([#3487](https://github.com/rubygems/bundler/issues/3487), @marutosi) + - remember all y/n answers when generating gems ([#3579](https://github.com/rubygems/bundler/issues/3579), @pducks32) + +## Performance: + + - use RubyGems stub specifications when possible ([#3580](https://github.com/rubygems/bundler/issues/3580), @segiddins) + +## Deprecations: + + - deprecated the (never enabled) `bundle_ruby` binary (@smlance) + +# 1.9.10 (June 22, 2015) + +## Features: + + - the `BUNDLED WITH` section of lockfiles generated by 1.10+ will be preserved (@segiddins) + +# 1.9.9 (May 16, 2015) + +## Bug fixes: + + - read mirror and credential settings from older versions ([#3557](https://github.com/rubygems/bundler/issues/3557), @Strech) + +# 1.9.8 (May 12, 2015) + +## Bug fixes: + + - fix regression in sudo mode introduced by 1.9.7 ([#3642](https://github.com/rubygems/bundler/issues/3642), @segiddins) + +# 1.9.7 (May 11, 2015) + +## Bug fixes: + + - always clean up tmp dirs ([#3277](https://github.com/rubygems/bundler/issues/3277), @hone, @indirect, @segiddins) + +# 1.9.6 (May 2, 2015) + +## Bug fixes: + + - use RubyGems spec stubs if available (@segiddins) + - allow creating gems with names containing two dashes ([#3483](https://github.com/rubygems/bundler/issues/3483), @janlelis) + - allow creating gems with names extending constants ([#3603](https://github.com/rubygems/bundler/issues/3603), @amatsuda) + +# 1.9.5 (April 29, 2015) + +## Bug fixes: + + - respect Gemfile sources when installing a gem present in two sources ([#3585](https://github.com/rubygems/bundler/issues/3585), @tmoore) + +# 1.9.4 (April 13, 2015) + +## Bug fixes: + + - fix regression in installing x86 and universal gems ([#3565](https://github.com/rubygems/bundler/issues/3565), @jdmundrawala) + - improve error when gems are missing ([#3564](https://github.com/rubygems/bundler/issues/3564), @sealocal) + +# 1.9.3 (April 12, 2015) + +## Bug fixes: + + - handle removal of `specs` from rubygems/rubygems@620910 ([#3558](https://github.com/rubygems/bundler/issues/3558), @indirect) + - install 'universal' gems on Windows ([#3066](https://github.com/rubygems/bundler/issues/3066), @jdmundrawala) + - stop passing --local during `rake install` task ([#3236](https://github.com/rubygems/bundler/issues/3236), @indirect) + - guard against all possible accidental public gem pushes ([#3533](https://github.com/rubygems/bundler/issues/3533), @indirect) + +# 1.9.2 (March 30, 2015) + +## Bug fixes: + + - ensure gem executables are executable ([#3517](https://github.com/rubygems/bundler/issues/3517), [#3511](https://github.com/bundler/bundler/issues/3511), @indirect) + - fix warnings in Molinillo ([#3516](https://github.com/rubygems/bundler/issues/3516), @segiddins) + - ensure duplicate dependencies do not propagate ([#3522](https://github.com/rubygems/bundler/issues/3522), @segiddins) + - keep gems locked when updating another gem from the same source ([#3520](https://github.com/rubygems/bundler/issues/3520), @indirect) + - resolve race that could build gems without saved arguments ([#3404](https://github.com/rubygems/bundler/issues/3404), @indirect) + +# 1.9.1 (March 21, 2015) + +## Bug fixes: + + - avoid exception in 'bundler/gem_tasks' ([#3492](https://github.com/rubygems/bundler/issues/3492), @segiddins) + +# 1.9.0 (March 20, 2015) + +# 1.9.0.rc (March 13, 2015) + +## Bug fixes: + + - make Bundler.which stop finding directories (@nohoho) + - handle Bundler prereleases correctly ([#3470](https://github.com/rubygems/bundler/issues/3470), @segiddins) + - add before_install to .travis.yml template for new gems (@kodnin) + +# 1.9.0.pre.1 (March 11, 2015) + +## Bug fixes: + + - make `gem` command work again (@arthurnn) + +# 1.9.0.pre (March 11, 2015) + +## Features: + + - prefer gemspecs closest to the directory root ([#3428](https://github.com/rubygems/bundler/issues/3428), @segiddins) + - debug log for API request limits ([#3452](https://github.com/rubygems/bundler/issues/3452), @neerfri) + +## Enhancements: + + - Molinillo resolver, shared with CocoaPods (@segiddins) + - updated Thor to v0.19.1 (@segiddins) + +# 1.8.9 (May 2, 2015) + +## Bug fixes: + + - Use RubyGems spec stubs if available (@segiddins) + +# 1.8.8 (April 29, 2015) + +## Bug fixes: + + - Respect Gemfile sources when installing a gem present in two sources ([#3585](https://github.com/rubygems/bundler/issues/3585), @tmoore) + +# 1.8.7 (April 7, 2015) + +## Bug fixes: + + - stop suppressing errors inside gems that get required ([#3549](https://github.com/rubygems/bundler/issues/3549), @indirect) + +# 1.8.6 (March 30, 2015) + +## Bug fixes: + + - keep gems locked when updating another gem from the same source ([#3250](https://github.com/rubygems/bundler/issues/3250), @indirect) + - resolve race that could build gems without saved arguments ([#3404](https://github.com/rubygems/bundler/issues/3404), @indirect) + +# 1.8.5 (March 11, 2015) + +## Bug fixes: + + - remove MIT license from gemspec when removing license file (@indirect) + - respect 'no' immediately as well as saving it in `gem` config (@kirs) + +# 1.8.4 (March 5, 2015) + +## Bug fixes: + + - document --all-platforms option ([#3449](https://github.com/rubygems/bundler/issues/3449), @moeffju) + - find gems from all sources on exec after install ([#3450](https://github.com/rubygems/bundler/issues/3450), @TimMoore) + +# 1.8.3 (February 24, 2015) + +## Bug fixes: + + - handle boolean values for gem settings (@EduardoBautista) + - stop always looking for updated `path` gems ([#3414](https://github.com/rubygems/bundler/issues/3414), [#3417](https://github.com/bundler/bundler/issues/3417), [#3429](https://github.com/bundler/bundler/issues/3429), @TimMoore) + +# 1.8.2 (February 14, 2015) + +## Bug fixes: + + - allow config settings for gems with 'http' in the name again ([#3398](https://github.com/rubygems/bundler/issues/3398), @TimMoore) + +# 1.8.1 (February 13, 2015) + +## Bug fixes: + + - synchronize building git gem native extensions ([#3385](https://github.com/rubygems/bundler/issues/3385), @antifuchs & @indirect) + - set gemspec bindir correctly ([#3392](https://github.com/rubygems/bundler/issues/3392), @TimMoore) + - request lockfile deletion when it is malformed ([#3396](https://github.com/rubygems/bundler/issues/3396), @indirect) + - explain problem when mirror config is missing ([#3386](https://github.com/rubygems/bundler/issues/3386), @indirect) + - explain problem when caching causes permission error ([#3390](https://github.com/rubygems/bundler/issues/3390), @indirect) + - normalize URLs in config keys ([#3391](https://github.com/rubygems/bundler/issues/3391), @indirect) + +# 1.8.0 (February 10, 2015) + +## Bug fixes: + + - gemfile `github` blocks now work ([#3379](https://github.com/rubygems/bundler/issues/3379), @indirect) + - look up installed gems in remote sources ([#3300](https://github.com/rubygems/bundler/issues/3300), [#3368](https://github.com/bundler/bundler/issues/3368), [#3377](https://github.com/bundler/bundler/issues/3377), [#3380](https://github.com/bundler/bundler/issues/3380), [#3381](https://github.com/bundler/bundler/issues/3381), @indirect) + - look up gems across all sources to satisfy dependencies ([#3365](https://github.com/rubygems/bundler/issues/3365), @keiths-osc) + - request dependencies for no more than 100 gems at a time ([#3367](https://github.com/rubygems/bundler/issues/3367), @segiddins) + +# 1.8.0.rc (January 26, 2015) + +## Features: + + - add `config disable_multisource` option to ensure sources can't compete (@indirect) + +## Bug fixes: + + - don't add extra quotes around long, quoted config values (@aroben, [#3338](https://github.com/rubygems/bundler/issues/3338)) + +## Security fixes: + + - warn when more than one top-level source is present (@indirect) + +# 1.8.0.pre (January 26, 2015) + +## Features: + + - add metadata allowed_push_host to new gem template ([#3002](https://github.com/rubygems/bundler/issues/3002), @juanitofatas) + - adds a `--no-install` flag to `bundle package` (@d-reinhold) + - add `bundle config auto_install true` to install automatically (@smashwilson) + - add `bundle viz --without` to exclude gem groups from resulting graph (@fnichol) + - prevent whitespace in gem declarations with clear messaging (@benlakey) + - tries to find a `bundler-` executable on your path for non-bundler commands (@andremedeiros) + - tries to find `gems.rb` and it's new counterpart, `gems.locked` (@andremedeiros) + - change the initial version of new gems from `0.0.1` to `0.1.0` (@petedmarsh) + - add `package --all-platforms` to cache gems for each known platform (@ccutrer) + - speed up `exec` when running commands on the $PATH (@kirs) + - add gem code of conduct file and option (@kirs) + - add config settings for gem license and tests (@kirs) + - add `bin/setup` and `bin/console` to new gems (@indirect) + - include configured user-agent in network requests (@indirect) + - support `github`, `gist`, and `bitbucket` options on git gems (@indirect) + - add `package --cache-path` and `config cache_path` for cache location (@jnraine) + - allow `config` to work even when a Gemfile is not present (@dholdren) + - add `config gemfile /path` for other Gemfile locations (@dholdren) + - add `github` method alonside the `git` method (@BenMorganIO) + +## Bug fixes: + + - reduce memory usage with threaded parallel workers (@Who828) + - support read-only git gems (@pmahoney) + - various resolver performance improvements (@dubek) + - untaint git gem paths for Rubygems compatibility (@tdtds) + +## Documentation: + + - add missing Gemfile global `path` explanation (@agenteo) + +# 1.7.15 (April 29, 2015) + +## Bug fixes: + + - Respect Gemfile sources when installing a gem present in two sources ([#3585](https://github.com/rubygems/bundler/issues/3585), @tmoore) + +# 1.7.14 (March 30, 2015) + +## Bug fixes: + + - Keep gems locked when updating another gem from the same source ([#3250](https://github.com/rubygems/bundler/issues/3250), @indirect) + - Don't add extra quotes around long, quoted config values (@aroben, [#3338](https://github.com/rubygems/bundler/issues/3338)) + +# 1.7.13 (February 7, 2015) + +## Bug fixes: + + - Look up installed gems in remote sources ([#3300](https://github.com/rubygems/bundler/issues/3300), [#3368](https://github.com/bundler/bundler/issues/3368), [#3377](https://github.com/bundler/bundler/issues/3377), [#3380](https://github.com/bundler/bundler/issues/3380), [#3381](https://github.com/bundler/bundler/issues/3381), @indirect) + - Look up gems across all sources to satisfy dependencies ([#3365](https://github.com/rubygems/bundler/issues/3365), @keiths-osc) + - Request dependencies for no more than 100 gems at a time ([#3367](https://github.com/rubygems/bundler/issues/3367), @segiddins) + +# 1.7.12 (January 8, 2015) + +## Bug fixes: + + - Always send credentials for sources, fixing private Gemfury gems ([#3342](https://github.com/rubygems/bundler/issues/3342), @TimMoore) + +# 1.7.11 (January 4, 2015) + +## Bug fixes: + + - Recognize `:mri_22` and `:mingw_22`, rather than just `:ruby_22` ([#3328](https://github.com/rubygems/bundler/issues/3328), @myabc) + +# 1.7.10 (December 29, 2014) + +## Bug fixes: + + - Fix source blocks sometimes causing deployment mode to fail wrongly ([#3298](https://github.com/rubygems/bundler/issues/3298), @TimMoore) + +## Features: + + - Support `platform :mri_22` and related version bits ([#3309](https://github.com/rubygems/bundler/issues/3309), @thomasfedb) + +# 1.7.9 (December 9, 2014) + +## Bug fixes: + + - Fix an issue where bundler sometime spams one gem in Gemfile.lock ([#3216](https://github.com/rubygems/bundler/issues/3216), @Who828) + - Ensure bundle update installs the newer version of the gem ([#3089](https://github.com/rubygems/bundler/issues/3089), @Who828) + - Fix an regression which stopped Bundler from resolving some Gemfiles ([#3059](https://github.com/rubygems/bundler/issues/3059), [#3248](https://github.com/bundler/bundler/issues/3248), @Who828) + +# 1.7.8 (December 6, 2014) + +## Bug fixes: + + - Hide credentials while warning about gems with ambiguous sources ([#3256](https://github.com/rubygems/bundler/issues/3256), @TimMoore) + +# 1.7.7 (November 19, 2014) + +## Bug fixes: + + - Ensure server credentials stored in config or ENV will be used ([#3180](https://github.com/rubygems/bundler/issues/3180), @arronmabrey) + - Fix race condition causing errors while installing git-based gems ([#3174](https://github.com/rubygems/bundler/issues/3174), @Who828) + - Use single quotes in config so YAML won't add more quotes ([#3261](https://github.com/rubygems/bundler/issues/3261), @indirect) + +# 1.7.6 (November 11, 2014) + +## Bug fixes: + + - CA certificates that work with all OpenSSLs (@luislavena, @indirect) + +# 1.7.5 (November 10, 2014) + +## Bug fixes: + + - Fix --deployment with source blocks and non-alphabetical gems ([#3224](https://github.com/rubygems/bundler/issues/3224), @TimMoore) + - Vendor CA chain to validate new rubygems.org HTTPS certificate (@indirect) + +# 1.7.4 (October 19, 2014) + +## Bug fixes: + + - Allow --deployment after `pack` while using source blocks ([#3167](https://github.com/rubygems/bundler/issues/3167), @TimMoore) + - Use dependency API even when HTTP credentials are in ENV ([#3191](https://github.com/rubygems/bundler/issues/3191), @fvaleur) + - Silence warnings (including root warning) in --quiet mode ([#3186](https://github.com/rubygems/bundler/issues/3186), @indirect) + - Stop asking gem servers for gems already found locally ([#2909](https://github.com/rubygems/bundler/issues/2909), @dubek) + +# 1.7.3 (September 14, 2014) + +## Bug fixes: + + - `extconf.rb` is now generated with the right path for `create_makefile` (@andremedeiros) + - Fix various Ruby warnings (@piotrsanarki, @indirect) + +# 1.7.2 (August 23, 2014) + +## Bug fixes: + + - Revert gem source sorting in lock files (@indirect) + +# 1.7.1 (August 20, 2014) + +## Bug fixes: + + - Install gems from one source needed by gems in another source (@indirect) + - Install the same gem versions even after some are installed (@TimMoore) + - Download specs only when installing from servers (@indirect) + +# 1.7.0 (August 13, 2014) + +## Security fixes: + + - Fix for CVE-2013-0334, installing gems from an unexpected source (@TimMoore) + +## Features: + + - Gemfile `source` calls now take a block containing gems from that source (@TimMoore) + - Added the `:source` option to `gem` to specify a source (@TimMoore) + +## Bug fixes: + + - Warn on ambiguous gems available from more than one source (@TimMoore) + +# 1.6.7 (October 19, 2014) + +## Features: + + - warn to upgrade when using useless source blocks (@danfinnie) + +## Documentation: + + - explain how to use gem server credentials via ENV (@hwartig) + +# 1.6.6 (August 23, 2014) + +## Bug fixes: + + - restore Gemfile credentials to Gemfile.lock (@indirect) + +# 1.6.5 (July 23, 2014) + +## Bug fixes: + + - require openssl explicitly to fix rare HTTPS request failures (@indirect, [#3107](https://github.com/rubygems/bundler/issues/3107)) + +# 1.6.4 (July 17, 2014) + +## Bug fixes: + + - fix undefined constant error when can't find gem during binstubs ([#3095](https://github.com/rubygems/bundler/issues/3095), @jetaggart) + - work when installed git gems are not writable ([#3092](https://github.com/rubygems/bundler/issues/3092), @pmahoney) + - don't store configured source credentials in Gemfile.lock ([#3045](https://github.com/rubygems/bundler/issues/3045), @lhz) + - don't include config source credentials in the lockfile (Lars Haugseth) + - use threads for jobs on Rubinius (@YorickPeterse) + - skip dependencies from other platforms (@mvz) + - work when Rubygems was built without SSL (@andremedeiros) + +# 1.6.3 (June 16, 2014) + +## Bug fixes: + + - fix regression when resolving many conflicts ([#2994](https://github.com/rubygems/bundler/issues/2994), @Who828) + - use local gemspec for builtin gems during install --local ([#3041](https://github.com/rubygems/bundler/issues/3041), @Who828) + - don't warn about sudo when installing on Windows ([#2984](https://github.com/rubygems/bundler/issues/2984), @indirect) + - shell escape `bundle open` arguments (@indirect) + +# 1.6.2 (April 13, 2014) + +## Bug fixes: + + - fix an exception when using builtin gems ([#2915](https://github.com/rubygems/bundler/issues/2915), [#2963](https://github.com/bundler/bundler/issues/2963), @gnufied) + - cache gems that are built in to the running ruby ([#2975](https://github.com/rubygems/bundler/issues/2975), @indirect) + - re-allow deploying cached git gems without git installed ([#2968](https://github.com/rubygems/bundler/issues/2968), @aughr) + - keep standalone working even with builtin gems (@indirect) + - don't update vendor/cache in deployment mode ([#2921](https://github.com/rubygems/bundler/issues/2921), @indirect) + +## Features: + + - warn informatively when `bundle install` is run as root ([#2936](https://github.com/rubygems/bundler/issues/2936), @1337807) + +# 1.6.1 (April 2, 2014) + +## Bug fixes: + + - update C extensions when git gem versions change ([#2948](https://github.com/rubygems/bundler/issues/2948), @dylanahsmith) + +## Features: + + - add support for C extensions in sudo mode on Rubygems 2.2 + +# 1.6.0 (March 28, 2014) + +## Bug fixes: + + - many Gemfiles that caused incorrect errors now resolve correctly (@Who828) + - redirects across hosts now work on rubies without OpenSSL ([#2686](https://github.com/rubygems/bundler/issues/2686), @grddev) + - gemspecs now handle filenames with newlines ([#2634](https://github.com/rubygems/bundler/issues/2634), @jasonmp85) + - support escaped characters in usernames and passwords (@punkie) + - no more exception on `update GEM` without lock file (@simi) + - allow long config values ([#2823](https://github.com/rubygems/bundler/issues/2823), @kgrz) + - cache successfully even locked to gems shipped with Ruby ([#2869](https://github.com/rubygems/bundler/issues/2869), @aughr) + - respect NO_PROXY even if a proxy is configured ([#2878](https://github.com/rubygems/bundler/issues/2878), @stlay) + - only retry git commands that hit the network ([#2899](https://github.com/rubygems/bundler/issues/2899), @timmoore) + - fix NameError regression when OpenSSL is not available ([#2898](https://github.com/rubygems/bundler/issues/2898), @timmoore) + - handle exception installing when build_info owned by root (@Who828) + - skip HTTP redirects from rubygems.org, huge speed boost (@Who828) + +## Features: + + - resolver rewritten to avoid recursion (@Who828) + - add `git_source` for custom options like :github and :gist (@strzalek) + - HTTP auth may now be stored in `bundle config` (@smashwilson) + - some complex Gemfiles are resolved up to 10x faster (@Who828) + - add support for IRB alternatives such as Pry and Ripl (@joallard, @postmodern) + - highlight installed or updated gems ([#2722](https://github.com/rubygems/bundler/issues/2722), [#2741](https://github.com/bundler/bundler/issues/2741), @yaotti, @simi) + - display the `post_install_message` for gems installed via :git (@phallstrom) + - `bundle outdated --strict` now only reports allowed updates (@davidblondeau) + - `bundle show --verbose` Add gem summary to the output (@lardcanoe) + - `bundle gem GEM --ext` now generates a skeleton for a C extension (@superdealloc) + - Avoid using threequals operator where possible (@as-cii) + - Add `bundle update --group` to update specific group ([#2731](https://github.com/rubygems/bundler/issues/2731) @banyan) + +## Documentation: + + - Add missing switches for bundle-install(1) and bundle-update(1) (@as-cii) + +# 1.5.3 (February 6, 2014) + +## Bug fixes: + + - find "missing" gems that are actually present ([#2780](https://github.com/rubygems/bundler/issues/2780), [#2818](https://github.com/bundler/bundler/issues/2818), [#2854](https://github.com/bundler/bundler/issues/2854)) + - use n-1 cores when given n jobs for parallel install (@jdickey) + +# 1.5.2 (January 10, 2014) + +## Bug fixes: + + - fix integration with Rubygems 1.8.0-1.8.19 + - handle ENETDOWN exception during network requests + - gracefully shut down after interrupt during parallel install (@Who828) + - allow Rails to run Thor without debug mode (@rafaelfranca) + - set git binstub permissions by umask (@v-yarotsky) + - remove parallel install debug log + +# 1.5.1 (December 28, 2013) + +## Bug fixes: + + - correctly find gems installed with Ruby by default + +# 1.5.0 (December 26, 2013) + +## Features: + + - install missing gems if their specs are present (@hone) + +## Bug fixes: + + - use print for "Installing…" so messages are thread-safe (@TimMoore) + +# 1.5.0.rc.2 (December 18, 2013) + +## Features: + + - Support threaded installation on Rubygems 2.0.7+ + - Debug installation logs in .bundle/install.log + +## Bug fixes: + + - Try to catch gem installation race conditions + +# 1.5.0.rc.1 (November 9, 2013) + +## Features: + + - bundle update also accepts --jobs ([#2692](https://github.com/rubygems/bundler/issues/2692), @mrkn) + - add fork URL to README for new `bundle gem` ([#2665](https://github.com/rubygems/bundler/issues/2665), @zzak) + - add `bundle outdated --strict` ([#2685](https://github.com/rubygems/bundler/issues/2685), @davidblondeau) + - warn if same gem/version is added twice ([#2679](https://github.com/rubygems/bundler/issues/2679), @jendiamond) + - don't redownload installed specs for `bundle install` ([#2680](https://github.com/rubygems/bundler/issues/2680), @cainlevy) + - override gem sources with mirrors ([#2650](https://github.com/rubygems/bundler/issues/2650), @danielsdeleo, @mkristian) + +## Bug fixes: + + - fix sharing same SSL socket when forking workers for parallel install ([#2632](https://github.com/rubygems/bundler/issues/2632)) + - fix msg typo in GitNotAllowedError ([#2654](https://github.com/rubygems/bundler/issues/2654), @joyicecloud) + - fix Bundler.which for directories ([#2697](https://github.com/rubygems/bundler/issues/2697), @rhysd) + - properly require `Capistrano::Version` ([#2690](https://github.com/rubygems/bundler/issues/2690), @steveklabnik) + - search for git.exe and git + - fix the bug that downloads every spec when API fetcher encounters an error + - only retry network requests + +# 1.4.0.rc.1 (September 29, 2013) + +## Features: + + - add support for the x64-mingw32 platform ([#2356](https://github.com/rubygems/bundler/issues/2356), [#2590](https://github.com/bundler/bundler/issues/2590), @larskanis) + - add :patchlevel option to ruby DSL + - add `bundler` bin ([#2598](https://github.com/rubygems/bundler/issues/2598), @kirs) + - friendly ambiguous error messages ([#2581](https://github.com/rubygems/bundler/issues/2581), [#2550](https://github.com/bundler/bundler/issues/2550), @jlsuttles, @jendiamond, @joyicecloud) + - add `:jruby_18` and `:jruby_19` platform options (@mcfiredrill) + - add X.509 client certificates for auth without passwords (@snackbandit) + - add `exec --keep-file-descriptors` for Ruby 1.9-like behavior on 2.0 (@steved555) + - print a better error when git is not installed (@joyicecloud) + - exit non-zero when `outdated` is run with an unknown gem (@joyicecloud) + - add `:ruby_21` platform option (@brandonblack) + - add `--retry` to retry failed network and git commands (@schneems) + - include command and versions in User-Agent (@indirect, @joyicecloud) + +## Bug fixes: + + - allow passwordless Basic Auth ([#2606](https://github.com/rubygems/bundler/issues/2606), @rykov) + - don't suggest `gem install foo` when `foo` is a git gem that fails (@kirs) + - revert [#2569](https://github.com/rubygems/bundler/issues/2569), staying compatible with git: instead of https: for :github gems + - handle exceptions while installing gems in parallel (@gnufied) + +# 1.4.0.pre.1 (August 4, 2013) + +## Features: + + - retry network requests while installing gems ([#2561](https://github.com/rubygems/bundler/issues/2561), @ascherger) + - faster installs using gemspecs from the local system cache ([#2497](https://github.com/rubygems/bundler/issues/2497), @mipearson) + - add `bundle install -jN` for N parallel gem installations ([#2481](https://github.com/rubygems/bundler/issues/2481), @eagletmt) + - add `ENV['DEBUG_RESOLVER_TREE']` outputs resolver tree (@dblock) + - set $MANPATH so `bundle exec man name` works ([#1624](https://github.com/rubygems/bundler/issues/1624), @sunaku) + - use `man` instead of `groff` ([#2579](https://github.com/rubygems/bundler/issues/2579), @ixti, @simi) + - add Gemfile dependency info to bundle outdated output ([#2487](https://github.com/rubygems/bundler/issues/2487), @rahearn) + - allow `require: true` as an alias for `require: ` ([#2538](https://github.com/rubygems/bundler/issues/2538), @ndbroadbent) + - rescue and report Thor errors ([#2478](https://github.com/rubygems/bundler/issues/2478), @pjvds) + - detect cyclic dependencies ([#2564](https://github.com/rubygems/bundler/issues/2564), @gnufied) + - support multiple gems in `binstubs` ([#2576](https://github.com/rubygems/bundler/issues/2576), @lucasmazza) + - use https instead of git for :github gems ([#2569](https://github.com/rubygems/bundler/issues/2569), @fuadsaud) + - add quiet option to `bundle package` ([#2573](https://github.com/rubygems/bundler/issues/2573), @shtirlic) + - use RUBYLIB instead of RUBYOPT for better Windows support ([#2536](https://github.com/rubygems/bundler/issues/2536), @equinux) + +## Bug fixes: + + - reduce stack size while resolving to fix JRuby overflow ([#2510](https://github.com/rubygems/bundler/issues/2510), @headius) + - display GitErrors while loading specs in --verbose mode ([#2461](https://github.com/rubygems/bundler/issues/2461)) + - allow the same options hash to be passed to multiple gems ([#2447](https://github.com/rubygems/bundler/issues/2447)) + - handle missing binaries without an exception ([#2019](https://github.com/rubygems/bundler/issues/2019), @luismreis) + +# 1.3.6 (January 8, 2014) + +## Bug fixes: + + - make gemspec path option preserve relative paths in lock file (@bwillis) + - use umask when creating binstubs ([#1618](https://github.com/rubygems/bundler/issues/1618), @v-yarotsky) + - warn if graphviz is not installed ([#2435](https://github.com/rubygems/bundler/issues/2435), @Agis-) + - show git errors while loading gemspecs + - don't mutate gem method options hash ([#2447](https://github.com/rubygems/bundler/issues/2447)) + - print Thor errors ([#2478](https://github.com/rubygems/bundler/issues/2478), @pjvds) + - print Rubygems system exit errors (James Cook) + - more Pathnames into Strings for MacRuby (@kml) + - preserve original gemspec path (@bwillis) + - remove warning about deps with :git ([#1651](https://github.com/rubygems/bundler/issues/1651), @ixti) + - split git files on null ([#2634](https://github.com/rubygems/bundler/issues/2634), @jasonmp85) + - handle cross-host redirects without SSL ([#2686](https://github.com/rubygems/bundler/issues/2686), @grddev) + - handle Rubygems 2 security exception (@zzak) + - reinstall gems if they are missing with spec present + - set binstub permissions using umask ([#1618](https://github.com/rubygems/bundler/issues/1618), @v-yarotsky) + +# 1.3.5 (April 3, 2013) + +## Features: + + - progress indicator while resolver is running (@chief) + +## Bug fixes: + + - update local overrides with orphaned revisions (@jamesferguson) + - revert to working quoting of RUBYOPT on Windows (@ogra) + - use basic auth even when SSL is not available (@jayniz) + - installing git gems without dependencies in deployment now works + +# 1.3.4 (March 15, 2013) + +## Bug fixes: + + - load YAML on Rubygems versions that define module YAML + - fix regression that broke --without on ruby 1.8.7 + +# 1.3.3 (March 13, 2013) + +## Features: + + - compatible with Rubygems 2.0.2 (higher and lower already work) + - mention skipped groups in bundle install and bundle update output (@simi) + - `gem` creates rake tasks for minitest (@coop) and rspec + +## Bug fixes: + + - require rbconfig for standalone mode + +# 1.3.2 (March 7, 2013) + +## Features: + + - include rubygems.org CA chain + +## Bug fixes: + + - don't store --dry-run as a Bundler setting + +# 1.3.1 (March 3, 2013) + +## Bug fixes: + + - include manpages in gem, restoring many help pages + - handle more SSL certificate verification failures + - check for the full version of SSL, which we need (@alup) + - gem rake task 'install' now depends on task 'build' (@sunaku) + +# 1.3.0 (February 24, 2013) + +## Features: + + - raise a useful error when the lockfile contains a merge conflict (@zofrex) + - ensure `rake release` checks for uncommitted as well as unstaged (@benmoss) + - allow environment variables to be negated with 'false' and '0' (@brettporter) + - set $MANPATH inside `exec` for gems with man pages (@sunaku) + - partial gem names for `open` and `update` now return a list (@takkanm) + +## Bug fixes: + + - `update` now (again) finds gems that aren't listed in the Gemfile + - `install` now (again) updates cached gems that aren't in the Gemfile + - install Gemfiles with HTTP sources even without OpenSSL present + - display CerficateFailureError message in full + +# 1.3.0.pre.8 (February 12, 2013) + +## Security fixes: + + - validate SSL certificate chain during HTTPS network requests + - don't send HTTP Basic Auth creds when redirected to other hosts (@perplexes) + - add `--trust-policy` to `install`, like `gem install -P` (@CosmicCat, [#2293](https://github.com/rubygems/bundler/issues/2293)) + +## Features: + + - optimize resolver when too new of a gem is already activated (@rykov, [#2248](https://github.com/rubygems/bundler/issues/2248)) + - update Net::HTTP::Persistent for SSL cert validation and no_proxy ENV + - explain SSL cert validation failures + - generate gemspecs when installing git repos, removing shellouts + - add pager selection (@csgui) + - add `licenses` command (@bryanwoods, [#1898](https://github.com/rubygems/bundler/issues/1898)) + - sort output from `outdated` (@richardkmichael, [#1896](https://github.com/rubygems/bundler/issues/1896)) + - add a .travis.yml to `gem -t` (@ndbroadbent, [#2143](https://github.com/rubygems/bundler/issues/2143)) + - inform users when the resolver starts + - disable reverse DNS to speed up API requests (@raggi) + +## Bug fixes: + + - raise errors while requiring dashed gems ([#1807](https://github.com/rubygems/bundler/issues/1807)) + - quote the Bundler path on Windows (@jgeiger, [#1862](https://github.com/rubygems/bundler/issues/1862), [#1856](https://github.com/bundler/bundler/issues/1856)) + - load gemspecs containing unicode (@gaffneyc, [#2301](https://github.com/rubygems/bundler/issues/2301)) + - support any ruby version in --standalone + - resolve some ruby -w warnings (@chastell, [#2193](https://github.com/rubygems/bundler/issues/2193)) + - don't scare users with an error message during API fallback + - `install --binstubs` is back to overwriting. thanks, SemVer. + +# 1.3.0.pre.7 (January 22, 2013) + +## Bug fixes: + + - stubs for gems with dev deps no longer cause exceptions ([#2272](https://github.com/rubygems/bundler/issues/2272)) + - don't suggest binstubs to --binstubs users + +# 1.3.0.pre.6 (January 22, 2013) + +## Features: + + - `binstubs` lists child gem bins if a gem has no binstubs + - `bundle gem --edit` will open the new gemspec (@ndbroadbent) + - `bundle gem --test rspec` now makes working tests (@tricknotes) + - `bundle env` prints info about bundler's environment (@peeja) + - add `BUNDLE_IGNORE_CONFIG` environment variable support (@richo) + +## Bug fixes: + + - don't overwrite custom binstubs during `install --binstubs` + - don't throw an exception if `binstubs` gem doesn't exist + - `bundle config` now works in directories without a Gemfile + +# 1.3.0.pre.5 (January 9, 2013) + +## Features: + + - make `--standalone` require lines ruby engine/version agnostic + - add `--dry-run` to `bundle clean` (@wfarr, [#2237](https://github.com/rubygems/bundler/issues/2237)) + +## Bug fixes: + + - don't skip writing binstubs when doing `bundle install` + - distinguish between ruby 1.9/2.0 when using :platforms (@spastorino) + +# 1.3.0.pre.4 (January 3, 2013) + +## Features: + + - `bundle binstubs ` to setup individual binstubs + - `bundle install --binstubs ""` will remove binstubs option + - `bundle clean --dry-run` will print out gems instead of removing them + +## Bug fixes: + + - Avoid stack traces when Ctrl+C during bundle command (@mitchellh) + - fix YAML parsing in in ruby-preview2 + +# 1.3.0.pre.3 (December 21, 2012) + +## Features: + + - pushing gems during `rake release` can be disabled (@trans) + - installing gems with `rake install` is much faster (@utkarshkukreti) + - added platforms :ruby_20 and :mri_20, since the ABI has changed + - added '--edit' option to open generated gemspec in editor + +## Bug fixes: + + - :git gems with extensions now work with Rubygems >= 2.0 (@jeremy) + - revert SemVer breaking change to :github + - `outdated` exits non-zero if outdated gems found (@rohit, [#2021](https://github.com/rubygems/bundler/issues/2021)) + - https Gist URLs for compatibility with Gist 2.0 (@NARKOZ) + - namespaced gems no longer generate a superfluous directory (@banyan) + +# 1.3.0.pre.2 (December 9, 2012) + +## Features: + + - `config` expands local overrides like `local.rack .` (@gkop, [#2205](https://github.com/rubygems/bundler/issues/2205)) + - `gem` generates files correctly for names like `jquery-rails` (@banyan, [#2201](https://github.com/rubygems/bundler/issues/2201)) + - use gems from gists with the :gist option in the Gemfile (@jgaskins) + +## Bug fixes: + + - Gemfile sources other than rubygems.org work even when .gemrc contains sources + - caching git gems now caches specs, fixing e.g. git ls-files (@bison, [#2039](https://github.com/rubygems/bundler/issues/2039)) + - `show GEM` now warns if the directory has been deleted (@rohit, [#2070](https://github.com/rubygems/bundler/issues/2070)) + - git output hidden when running in --quiet mode (@rohit) + +# 1.3.0.pre (November 29, 2012) + +## Features: + + - compatible with Ruby 2.0.0-preview2 + - compatible with Rubygems 2.0.0.preview2 (@drbrain, @evanphx) + - ruby 2.0 added to the `:ruby19` ABI-compatible platform + - lazy load YAML, allowing Psych to be specified in the Gemfile + - significant performance improvements (@cheald, [#2181](https://github.com/rubygems/bundler/issues/2181)) + - `inject` command for scripted Gemfile additions (Engine Yard) + - :github option uses slashless arguments as repo owner (@rking) + - `open` suggests gem names for typos (@jdelStrother) + - `update` reports non-existent gems (@jdelStrother) + - `gem` option --test can generate rspec stubs (@MafcoCinco) + - `gem` option --test can generate minitest stubs (@kcurtin) + - `gem` command generates MIT license (@BrentWheeldon) + - gem rake task 'release' resuses existing tags (@shtirlic) + +## Bug fixes: + + - JRuby new works with HTTPS gem sources (@davidcelis) + - `install` installs both rake rake-built gems at once (@crowbot, [#2107](https://github.com/rubygems/bundler/issues/2107)) + - handle Errno::ETIMEDOUT errors (@jmoses) + - handle Errno::EAGAIN errors on JRuby + - disable ANSI coloring when output is redirected (@tomykaira) + - raise LoadErrors correctly during Bundler.require (@Empact) + - do not swallow --verbose on `bundle exec` (@sol, [#2102](https://github.com/rubygems/bundler/issues/2102)) + - `gem` generates gemspecs that block double-requires + - `gem` generates gemspecs that admit they depend on rake + +# 1.2.5 (February 24, 2013) + +## Bug fixes: + + - install Gemfiles with HTTP sources even without OpenSSL present + - display CerficateFailureError message in full + +# 1.2.4 (February 12, 2013) + +## Features: + + - warn about Ruby 2.0 and Rubygems 2.0 + - inform users when the resolver starts + - disable reverse DNS to speed up API requests (@raggi) + +## Bug fixes: + + - don't send user/pass when redirected to another host (@perplexes) + - load gemspecs containing unicode (@gaffneyc, [#2301](https://github.com/rubygems/bundler/issues/2301)) + - support any ruby version in --standalone + - resolve some ruby -w warnings (@chastell, [#2193](https://github.com/rubygems/bundler/issues/2193)) + - don't scare users with an error message during API fallback + +# 1.2.3 (November 29, 2012) + +## Bug fixes: + + - fix exceptions while loading some gemspecs + +# 1.2.2 (November 14, 2012) + +## Bug fixes: + + - support new Psych::SyntaxError for Ruby 2.0.0 (@tenderlove, @sol) + - `bundle viz` works with git gems again (@hirochachacha) + - recognize more cases when OpenSSL is not present + +# 1.2.1 (September 19, 2012) + +## Bug fixes: + + - `bundle clean` now works with BUNDLE_WITHOUT groups again + - have a net/http read timeout around the Gemcutter API Endpoint + +# 1.2.0 (August 30, 2012) + +## Bug fixes: + + - raise original error message from LoadError's + +## Documentation: + + - `platform` man pages + +# 1.2.0.rc.2 (August 8, 2012) + +## Bug fixes: + + - `clean` doesn't remove gems that are included in the lockfile + +# 1.2.0.rc (July 17, 2012) + +## Features: + + - `check` now has a `--dry-run` option (@svenfuchs, [#1811](https://github.com/rubygems/bundler/issues/1811)) + - loosen ruby directive for engines + - prune git/path directories inside vendor/cache (@josevalim, [#1988](https://github.com/rubygems/bundler/issues/1988)) + - update vendored thor to 0.15.2 (@sferik) + - add .txt to LICENSE (@postmodern, [#2001](https://github.com/rubygems/bundler/issues/2001)) + - add `config disable_local_branch_check` (@josevalim, [#1985](https://github.com/rubygems/bundler/issues/1985)) + - fall back on the full index when experiencing syck errors ([#1419](https://github.com/rubygems/bundler/issues/1419)) + - handle syntax errors in Ruby gemspecs ([#1974](https://github.com/rubygems/bundler/issues/1974)) + +## Bug fixes: + + - fix `pack`/`cache` with `--all` (@josevalim, [#1989](https://github.com/rubygems/bundler/issues/1989)) + - don't display warning message when `cache_all` is set + - check for `nil` PATH ([#2006](https://github.com/rubygems/bundler/issues/2006)) + - Always try to keep original GEM_PATH (@drogus, [#1920](https://github.com/rubygems/bundler/issues/1920)) + +# 1.2.0.pre.1 (May 27, 2012) + +## Features: + + - Git gems import submodules of submodules recursively (@nwwatson, [#1935](https://github.com/rubygems/bundler/issues/1935)) + +## Bug fixes: + + - Exit from `check` with a non-zero status when frozen with no lock + - Use `latest_release` in Capistrano and Vlad integration ([#1264](https://github.com/rubygems/bundler/issues/1264)) + - Work around a Ruby 1.9.3p194 bug in Psych when config files are empty + +## Documentation: + + - Add instructions for local git repos to the `config` manpage + - Update the `Gemfile` manpage to include ruby versions (@stevenh512) + - When OpenSSL is missing, provide instructions for fixing ([#1776](https://github.com/rubygems/bundler/issues/1776) etc.) + - Unknown exceptions now link to ISSUES for help instead of a new ticket + - Correct inline help for `clean --force` (@dougbarth, [#1911](https://github.com/rubygems/bundler/issues/1911)) + +# 1.2.0.pre (May 4, 2012) + +## Features: + + - bundle package now accepts --all to package git and path dependencies + - bundle config now accepts --local, --global and --delete options + - It is possible to override a git repository via configuration. + For instance, if you have a git dependency on rack, you can force + it to use a local repo with `bundle config local.rack ~/path/to/rack` + - Cache gemspec loads for performance (@dekellum, [#1635](https://github.com/rubygems/bundler/issues/1635)) + - add --full-index flag to `bundle update` (@fluxx, [#1829](https://github.com/rubygems/bundler/issues/1829)) + - add --quiet flag to `bundle update` (@nashby, [#1654](https://github.com/rubygems/bundler/issues/1654)) + - Add Bundler::GemHelper.gemspec (@knu, [#1637](https://github.com/rubygems/bundler/issues/1637)) + - Graceful handling of Gemfile syntax errors (@koraktor, [#1661](https://github.com/rubygems/bundler/issues/1661)) + - `bundle platform` command + - add ruby to DSL, to specify version of ruby + - error out if the ruby version doesn't match + +## Performance: + + - bundle exec shouldn't run Bundler.setup just setting the right rubyopts options is enough (@spastorino, [#1598](https://github.com/rubygems/bundler/issues/1598)) + +## Bug fixes: + + - Avoid passing RUBYOPT changes in with_clean_env block (@eric1234, [#1604](https://github.com/rubygems/bundler/issues/1604)) + - Use the same ruby to run subprocesses as is running rake (@brixen) + +## Documentation: + + - Add :github documentation in DSL (@zofrex, [#1848](https://github.com/rubygems/bundler/issues/1848), [#1851](https://github.com/bundler/bundler/issues/1851), [#1852](https://github.com/bundler/bundler/issues/1852)) + - Add docs for the --no-cache option (@fluxx, [#1796](https://github.com/rubygems/bundler/issues/1796)) + - Add basic documentation for bin_path and bundle_path (@radar) + - Add documentation for the run method in Bundler::Installer + +# 1.1.5 (July 17, 2012) + +## Features: + + - Special case `ruby` directive from 1.2.0, so you can install Gemfiles that use it + +# 1.1.4 (May 27, 2012) + +## Bug fixes: + + - Use `latest_release` in Capistrano and Vlad integration ([#1264](https://github.com/rubygems/bundler/issues/1264)) + - Unknown exceptions now link to ISSUES for help instead of a new ticket + - When OpenSSL is missing, provide instructions for fixing ([#1776](https://github.com/rubygems/bundler/issues/1776) etc.) + - Correct inline help for `clean --force` (@dougbarth, [#1911](https://github.com/rubygems/bundler/issues/1911)) + - Work around a Ruby 1.9.3p194 bug in Psych when config files are empty + +# 1.1.3 (March 23, 2012) + +## Bug fixes: + + - escape the bundler root path (@tenderlove, [#1789](https://github.com/rubygems/bundler/issues/1789)) + +# 1.1.2 (March 20, 2012) + +## Bug fixes: + + - Fix --deployment for multiple PATH sections of the same source ([#1782](https://github.com/rubygems/bundler/issues/1782)) + +# 1.1.1 (March 14, 2012) + +## Bug fixes: + + - Rescue EAGAIN so the fetcher works on JRuby on Windows + - Stop asking users to report gem installation errors + - Clarify "no sources" message + - Use $\ so `bundle gem` gemspecs work on Windows (@postmodern) + - URI-encode gem names for dependency API (@rohit, [#1672](https://github.com/rubygems/bundler/issues/1672)) + - Fix `cache` edge case in rubygems 1.3.7 ([#1202](https://github.com/rubygems/bundler/issues/1202)) + +## Performance: + + - Reduce invocation of git ls-files in `bundle gem` gemspecs (@knu) + +# 1.1.0 (March 7, 2012) + +## Bug fixes: + + - Clean up corrupted lockfiles on bundle installs + - Prevent duplicate GIT sources + - Fix post_install_message when uing the endpoint API + +# 1.1.rc.8 (March 3, 2012) + +## Performance: + + - don't resolve if the Gemfile.lock and Gemfile haven't changed + +## Bug fixes: + + - Load gemspecs from git even when a released gem has the same version ([#1609](https://github.com/rubygems/bundler/issues/1609)) + - Declare an accurate Ruby version requirement of 1.8.7 or newer ([#1619](https://github.com/rubygems/bundler/issues/1619)) + - handle gemspec development dependencies correctly (@raggi, [#1639](https://github.com/rubygems/bundler/issues/1639)) + - Avoid passing RUBYOPT changes in with_clean_env block. (eric1234, [#1604](https://github.com/rubygems/bundler/issues/1604)) + +# 1.1.rc.7 (December 29, 2011) + +## Bug fixes: + + - Fix bug where `clean` would break when using :path with no gemspec + +# 1.1.rc.6 (December 22, 2011) + +## Bug fixes: + + - Fix performance regression from 1.0 (@spastorino, [#1511](https://github.com/rubygems/bundler/issues/1511), [#1591](https://github.com/bundler/bundler/issues/1591), [#1592](https://github.com/bundler/bundler/issues/1592)) + - Load gems correctly when GEM_HOME is blank + - Refresh gems so Bundler works from inside a bundle + - Handle empty .bundle/config files without an error + +# 1.1.rc.5 (December 14, 2011) + +## Bug fixes: + + - Fix ASCII encoding errors with gem (rerelease with ruby 1.8) + +# 1.1.rc.4 (December 14, 2011) + +## Features: + + - `bundle viz` has the option to output a DOT file instead of a PNG (@hirochachacha, [#683](https://github.com/rubygems/bundler/issues/683)) + +## Bug fixes: + + - Ensure binstubs generated when using --standalone point to the standalonde bundle (@cowboyd, [#1588](https://github.com/rubygems/bundler/issues/1588)) + - fix `bundle viz` (@hirochachacha, [#1586](https://github.com/rubygems/bundler/issues/1586)) + +# 1.1.rc.3 (December 8, 2011) + +## Bug fixes: + + - fix relative_path so it checks Bundler.root is actually in the beginning of the path ([#1582](https://github.com/rubygems/bundler/issues/1582)) + - fix bundle outdated doesn't list all gems (@joelmoss, [#1521](https://github.com/rubygems/bundler/issues/1521)) + +# 1.1.rc.2 (December 6, 2011) + +## Features: + + - Added README.md to `newgem` (@ognevsky, [#1574](https://github.com/rubygems/bundler/issues/1574)) + - Added LICENSE (MIT) to newgem (@ognevsky, [#1571](https://github.com/rubygems/bundler/issues/1571)) + +## Bug fixes: + + - only auto-namespace requires for implied requires ([#1531](https://github.com/rubygems/bundler/issues/1531)) + - fix bundle clean output for git repos ([#1473](https://github.com/rubygems/bundler/issues/1473)) + - use Gem.bindir for bundle clean ([#1544](https://github.com/rubygems/bundler/issues/1544), [#1532](https://github.com/bundler/bundler/issues/1532)) + - use `Gem.load_env_plugins` instead of `Gem.load_env_plugins` ([#1500](https://github.com/rubygems/bundler/issues/1500), [#1543](https://github.com/bundler/bundler/issues/1543)) + - differentiate Ruby 2.0 (trunk) from Ruby 1.9 (@tenderlove, [#1539](https://github.com/rubygems/bundler/issues/1539)) + - `bundle clean` handles 7 length git hash for bundle clean ([#1490](https://github.com/rubygems/bundler/issues/1490), [#1491](https://github.com/bundler/bundler/issues/1491)) + - fix Psych loading issues + - Search $PATH for a binary rather than shelling out to `which` (@tenderlove, [#1573](https://github.com/rubygems/bundler/issues/1573)) + - do not clear RG cache unless we actually modify GEM_PATH and GEM_HOME- use `Gem.load_env_plugins` instead of `Gem.load_env_plugins` ([#1500](https://github.com/rubygems/bundler/issues/1500), [#1543](https://github.com/bundler/bundler/issues/1543)) + - `newgem` now uses https://rubygems.org ([#1562](https://github.com/rubygems/bundler/issues/1562)) + - `bundle init` now uses https://rubygems.org (@jjb, [#1522](https://github.com/rubygems/bundler/issues/1522)) + - `bundle install/update` does not autoclean when using --path for semver + +## Documentation: + + - added documentation for --shebang option for `bundle install` (@lunks, [#1475](https://github.com/rubygems/bundler/issues/1475), [#1558](https://github.com/bundler/bundler/issues/1558)) + +# 1.1.rc (October 3, 2011) + +## Features: + + - add `--shebang` option to bundle install (@bensie, [#1467](https://github.com/rubygems/bundler/issues/1467)) + - build passes on ruby 1.9.3rc1 ([#1458](https://github.com/rubygems/bundler/issues/1458), [#1469](https://github.com/bundler/bundler/issues/1469)) + - hide basic auth credentials for custom sources ([#1440](https://github.com/rubygems/bundler/issues/1440), [#1463](https://github.com/bundler/bundler/issues/1463)) + +## Bug fixes: + + - fix index search result caching ([#1446](https://github.com/rubygems/bundler/issues/1446), [#1466](https://github.com/bundler/bundler/issues/1466)) + - fix fetcher prints multiple times during install ([#1445](https://github.com/rubygems/bundler/issues/1445), [#1462](https://github.com/bundler/bundler/issues/1462)) + - don't mention API errors from non-rubygems.org sources + - fix autoclean so it doesn't remove bins that are used ([#1459](https://github.com/rubygems/bundler/issues/1459), [#1460](https://github.com/bundler/bundler/issues/1460)) + +## Documentation: + + - add :require => [...] to the gemfile(5) manpage (@nono, [#1468](https://github.com/rubygems/bundler/issues/1468)) + +# 1.1.pre.10 (September 27, 2011) + +## Features: + + - `config system_bindir foo` added, works like "-n foo" in your .gemrc file + +# 1.1.pre.9 (September 18, 2011) + +## Features: + + - `clean` will now clean up all old .gem and .gemspec files, cleaning up older pres + - `clean` will be automatically run after bundle install and update when using `--path` ([#1420](https://github.com/rubygems/bundler/issues/1420), [#1425](https://github.com/bundler/bundler/issues/1425)) + - `clean` now takes a `--force` option ([#1247](https://github.com/rubygems/bundler/issues/1247), [#1426](https://github.com/bundler/bundler/issues/1426)) + - `clean` will clean up cached git dirs in bundle clean ([#1390](https://github.com/rubygems/bundler/issues/1390)) + - remove deprecations from DSL ([#1119](https://github.com/rubygems/bundler/issues/1119)) + - autorequire tries directories for gems with dashed names ([#1205](https://github.com/rubygems/bundler/issues/1205)) + - adds a `--paths` flag to `bundle show` to list all the paths of bundled gems (@tiegz, [#1360](https://github.com/rubygems/bundler/issues/1360)) + - load rubygems plugins in the bundle binary (@tpope, [#1364](https://github.com/rubygems/bundler/issues/1364)) + - make `--standalone` respect `--path` (@cowboyd, [#1361](https://github.com/rubygems/bundler/issues/1361)) + +## Bug fixes: + + - Fix `clean` to handle nested gems in a git repo ([#1329](https://github.com/rubygems/bundler/issues/1329)) + - Fix conflict from revert of benchmark tool (@boffbowsh, [#1355](https://github.com/rubygems/bundler/issues/1355)) + - Fix fatal error when unable to connect to gem source ([#1269](https://github.com/rubygems/bundler/issues/1269)) + - Fix `outdated` to find pre-release gems that are installed. ([#1359](https://github.com/rubygems/bundler/issues/1359)) + - Fix color for ui. ([#1374](https://github.com/rubygems/bundler/issues/1374)) + - Fix installing to user-owned system gems on OS X + - Fix caching issue in the resolver ([#1353](https://github.com/rubygems/bundler/issues/1353), [#1421](https://github.com/bundler/bundler/issues/1421)) + - Fix :github DSL option + +# 1.1.pre.8 (August 13, 2011) + +## Bug fixes: + + - Fix `bundle check` to not print fatal error message (@cldwalker, [#1347](https://github.com/rubygems/bundler/issues/1347)) + - Fix require_sudo when Gem.bindir isn't writeable ([#1352](https://github.com/rubygems/bundler/issues/1352)) + - Fix not asking Gemcutter API for dependency chain of git gems in --deployment ([#1254](https://github.com/rubygems/bundler/issues/1254)) + - Fix `install --binstubs` when using --path ([#1332](https://github.com/rubygems/bundler/issues/1332)) + +# 1.1.pre.7 (August 8, 2011) + +## Bug fixes: + + - Fixed invalid byte sequence error while installing gem on Ruby 1.9 ([#1341](https://github.com/rubygems/bundler/issues/1341)) + - Fixed exception when sudo was needed to install gems (@spastorino) + +# 1.1.pre.6 (August 8, 2011) + +## Bug fixes: + + - Fix cross repository dependencies ([#1138](https://github.com/rubygems/bundler/issues/1138)) + - Fix git dependency fetching from API endpoint ([#1254](https://github.com/rubygems/bundler/issues/1254)) + - Fixes for bundle outdated (@joelmoss, [#1238](https://github.com/rubygems/bundler/issues/1238)) + - Fix bundle standalone when using the endpoint ([#1240](https://github.com/rubygems/bundler/issues/1240)) + +## Features: + + - Implement `to_ary` to avoid calls to method_missing (@tenderlove, [#1274](https://github.com/rubygems/bundler/issues/1274)) + - bundle clean removes old .gem files (@cldwalker, [#1293](https://github.com/rubygems/bundler/issues/1293)) + - Correctly identify missing child dependency in error message + - Run pre-install, post-build, and post-install gem hooks for git gems (@warhammerkid, [#1120](https://github.com/rubygems/bundler/issues/1120)) + - create Gemfile.lock for empty Gemfile ([#1218](https://github.com/rubygems/bundler/issues/1218)) + +# 1.1.pre.5 (June 11, 2011) + +## Bug fixes: + + - Fix LazySpecification on Ruby 1.9 (@dpiddy, [#1232](https://github.com/rubygems/bundler/issues/1232)) + - Fix HTTP proxy support (@leobessa, [#878](https://github.com/rubygems/bundler/issues/878)) + +## Features: + + - Speed up `install --deployment` by using the API endpoint + - Support Basic HTTP Auth for the API endpoint (@dpiddy, [#1229](https://github.com/rubygems/bundler/issues/1229)) + - Add `install --full-index` to disable the API endpoint, just in case + - Significantly speed up install by removing unneeded gemspec fetches + - `outdated` command shows outdated gems (@joelmoss, [#1130](https://github.com/rubygems/bundler/issues/1130)) + - Print gem post install messages (@csquared, [#1155](https://github.com/rubygems/bundler/issues/1155)) + - Reduce memory use by removing Specification.new inside method_missing (@tenderlove, [#1222](https://github.com/rubygems/bundler/issues/1222)) + - Allow `check --path` + +# 1.1.pre.4 (May 5, 2011) + +## Bug fixes: + + - Fix bug that could prevent installing new gems + +# 1.1.pre.3 (May 4, 2011) + +## Features: + + - Add `bundle outdated` to show outdated gems (@joelmoss) + - Remove BUNDLE_* from `Bundler.with_clean_env` (@wuputah) + - Add Bundler.clean_system, and clean_exec (@wuputah) + - Use git config for gem author name and email (@krekoten) + +## Bug fixes: + + - Fix error calling Bundler.rubygems.gem_path + - Fix error when Gem.path returns Gem::FS instead of String + +# 1.1.pre.2 (April 28, 2011) + +## Features: + + - Add :github option to Gemfile DSL for easy git repos + - Merge all fixes from 1.0.12 and 1.0.13 + +# 1.1.pre.1 (February 2, 2011) + +## Bug fixes: + + - Compatibility with changes made by Rubygems 1.5 + +# 1.1.pre (January 21, 2011) + +## Features: + + - Add bundle clean. Removes unused gems from --path directory + - Initial Gemcutter Endpoint API work, BAI Fetching source index + - Added bundle install --standalone + - Ignore Gemfile.lock when building new gems + - Make it possible to override a .gemspec dependency's source in the + Gemfile + +## Breaking changes: + + - Removed bundle lock + - Removed bundle install + - Removed bundle install --production + - Removed bundle install --disable-shared-gems + +# 1.0.21 (September 30, 2011) + + No changes. + +# 1.0.21.rc (September 29, 2011) + +## Bug fixes: + + - Load Psych unless Syck is defined, because 1.9.2 defines YAML + +# 1.0.20 (September 27, 2011) + +## Features: + + - Add platform :maglev (@timfel, [#1444](https://github.com/rubygems/bundler/issues/1444)) + +## Bug fixes: + + - Ensure YAML is required even if Psych is found + - Handle directory names that contain invalid regex characters + +# 1.0.20.rc (September 18, 2011) + +## Features: + + - Rescue interrupts to `bundle` while loading bundler.rb ([#1395](https://github.com/rubygems/bundler/issues/1395)) + - Allow clearing without groups by passing `--without ''` ([#1259](https://github.com/rubygems/bundler/issues/1259)) + +## Bug fixes: + + - Manually sort requirements in the lockfile ([#1375](https://github.com/rubygems/bundler/issues/1375)) + - Remove several warnings generated by ruby -w (@stephencelis) + - Handle trailing slashes on names passed to `gem` ([#1372](https://github.com/rubygems/bundler/issues/1372)) + - Name modules for gems like 'test-foo_bar' correctly ([#1303](https://github.com/rubygems/bundler/issues/1303)) + - Don't require Psych if Syck is already loaded ([#1239](https://github.com/rubygems/bundler/issues/1239)) + +# 1.0.19.rc (September 13, 2011) + +## Features: + + - Compatibility with Rubygems 1.8.10 installer changes + - Report gem installation failures clearly (@rwilcox, [#1380](https://github.com/rubygems/bundler/issues/1380)) + - Useful error for cap and vlad on first deploy (@nexmat, @kirs) + +## Bug fixes: + + - `exec` now works when the command contains 'exec' + - Only touch lock after changes on Windows (@robertwahler, [#1358](https://github.com/rubygems/bundler/issues/1358)) + - Keep load paths when #setup is called multiple times (@radsaq, [#1379](https://github.com/rubygems/bundler/issues/1379)) + +# 1.0.18 (August 16, 2011) + +## Bug fixes: + + - Fix typo in DEBUG_RESOLVER (@geemus) + - Fixes rake 0.9.x warning (@mtylty, [#1333](https://github.com/rubygems/bundler/issues/1333)) + - Fix `bundle cache` again for rubygems 1.3.x + +## Features: + + - Run the bundle install earlier in a Capistrano deployment (@cgriego, [#1300](https://github.com/rubygems/bundler/issues/1300)) + - Support hidden gemspec (@trans, @cldwalker, [#827](https://github.com/rubygems/bundler/issues/827)) + - Make fetch_specs faster (@zeha, [#1294](https://github.com/rubygems/bundler/issues/1294)) + - Allow overriding development deps loaded by #gemspec (@lgierth, [#1245](https://github.com/rubygems/bundler/issues/1245)) + +# 1.0.17 (August 8, 2011) + +## Bug fixes: + + - Fix rake issues with rubygems 1.3.x ([#1342](https://github.com/rubygems/bundler/issues/1342)) + - Fixed invalid byte sequence error while installing gem on Ruby 1.9 ([#1341](https://github.com/rubygems/bundler/issues/1341)) + +# 1.0.16 (August 8, 2011) + +## Features: + + - Performance fix for MRI 1.9 (@efficientcloud, [#1288](https://github.com/rubygems/bundler/issues/1288)) + - Shortcuts (like `bundle i`) for all commands (@amatsuda) + - Correctly identify missing child dependency in error message + +## Bug fixes: + + - Allow Windows network share paths with forward slashes (@mtscout6, [#1253](https://github.com/rubygems/bundler/issues/1253)) + - Check for rubygems.org credentials so `rake release` doesn't hang ([#980](https://github.com/rubygems/bundler/issues/980)) + - Find cached prerelease gems on rubygems 1.3.x (@dburt, [#1202](https://github.com/rubygems/bundler/issues/1202)) + - Fix `bundle install --without` on kiji (@tmm1, [#1287](https://github.com/rubygems/bundler/issues/1287)) + - Get rid of warning in ruby 1.9.3 (@smartinez87, [#1231](https://github.com/rubygems/bundler/issues/1231)) + +## Documentation: + + - Documentation for `gem ..., :require => false` (@kmayer, [#1292](https://github.com/rubygems/bundler/issues/1292)) + - Gems provide "executables", they are rarely also binaries (@fxn, [#1242](https://github.com/rubygems/bundler/issues/1242)) + +# 1.0.15 (June 9, 2011) + +## Features: + + - Improved Rubygems integration, removed many deprecation notices + +## Bug fixes: + + - Escape URL arguments to git correctly on Windows (1.0.14 regression) + +# 1.0.14 (May 27, 2011) + +## Features: + + - Rubinius platform :rbx (@rkbodenner) + - Include gem rake tasks with "require 'bundler/gem_tasks" (@indirect) + - Include user name and email from git config in new gemspec (@ognevsky) + +## Bug fixes: + + - Set file permissions after checking out git repos (@tissak) + - Remove deprecated call to Gem::SourceIndex#all_gems (@mpj) + - Require the version file in new gemspecs (@rubiii) + - Allow relative paths from the Gemfile in gems with no gemspec (@mbirk) + - Install gems that contain 'bundler', e.g. guard-bundler (@hone) + - Display installed path correctly on Windows (@tadman) + - Escape quotes in git URIs (@mheffner) + - Improve Rake 0.9 support (@quix) + - Handle certain directories already existing (@raggi) + - Escape filenames containing regex characters (@indirect) + +# 1.0.13 (May 4, 2011) + +## Features: + + - Compatibility with Rubygems master (soon to be v1.8) (@evanphx) + - Informative error when --path points to a broken symlink + - Support Rake 0.9 and greater (@e2) + - Output full errors for non-TTYs e.g. pow (@josh) + +## Bug fixes: + + - Allow spaces in gem path names for gem tasks (@rslifka) + - Have cap run bundle install from release_path (@martinjagusch) + - Quote git refspec so zsh doesn't expand it (@goneflyin) + +# 1.0.12 (April 8, 2011) + +## Features: + + - Add --no-deployment option to `install` for disabling it on dev machines + - Better error message when git fails and cache is present (@parndt) + - Honor :bundle_cmd in cap `rake` command (@voidlock, @cgriego) + +## Bug fixes: + + - Compatibility with Rubygems 1.7 and Rails 2.3 and vendored gems (@evanphx) + - Fix changing gem order in lock (@gucki) + - Remove color escape sequences when displaying man pages (@bgreenlee) + - Fix creating GEM_HOME on both JRuby 1.5 and 1.6 (@nickseiger) + - Fix gems without a gemspec and directories in bin/ (@epall) + - Fix --no-prune option for `bundle install` (@cmeiklejohn) + +# 1.0.11 (April 1, 2011) + +## Features: + + - Compatibility with Rubygems 1.6 and 1.7 + - Better error messages when a git command fails + +## Bug fixes: + + - Don't always update gemspec gems (@carllerche) + - Remove ivar warnings (@jackdempsey) + - Fix occasional git failures in zsh (@jonah-carbonfive) + - Consistent lock for gems with double deps like Cap (@akahn) + +# 1.0.10 (February 1, 2011) + +## Bug fixes: + + - Fix a regression loading YAML gemspecs from :git and :path gems + - Requires, namespaces, etc. to work with changes in Rubygems 1.5 + +# 1.0.9 (January 19, 2011) + +## Bug fixes: + + - Fix a bug where Bundler.require could remove gems from the load + path. In Rails apps with a default application.rb, this removed + all gems in groups other than :default and Rails.env + +# 1.0.8 (January 18, 2011) + +## Features: + + - Allow overriding gemspec() deps with :git deps + - Add --local option to `bundle update` + - Ignore Gemfile.lock in newly generated gems + - Use `less` as help pager instead of `more` + - Run `bundle exec rake` instead of `rake` in Capistrano tasks + +## Bug fixes: + + - Fix --no-cache option for `bundle install` + - Allow Vlad deploys to work without Capistrano gem installed + - Fix group arguments to `bundle console` + - Allow groups to be loaded even if other groups were loaded + - Evaluate gemspec() gemspecs in their directory not the cwd + - Count on Rake to chdir to the right place in GemHelper + - Change Pathnames to Strings for MacRuby + - Check git process exit status correctly + - Fix some warnings in 1.9.3-trunk (thanks tenderlove) + +# 1.0.7 (November 17, 2010) + +## Bug fixes: + + - Remove Bundler version from the lockfile because it broke + backwards compatibility with 1.0.0-1.0.5. Sorry. :( + +# 1.0.6 (November 16, 2010) + +## Bug fixes: + + - Fix regression in `update` that caused long/wrong results + - Allow git gems on other platforms while installing ([#579](https://github.com/rubygems/bundler/issues/579)) + +## Features: + + - Speed up `install` command using various optimizations + - Significantly increase performance of resolver + - Use upcoming Rubygems performance improvements (@tmm1) + - Warn if the lockfile was generated by a newer version + - Set generated gems' homepage to "", so Rubygems will warn + +# 1.0.5 (November 13, 2010) + +## Bug fixes: + + - Fix regression disabling all operations that employ sudo + +# 1.0.4 (November 12, 2010) + +## Bug fixes: + + - Expand relative :paths from Bundler.root (eg ./foogem) + - Allow git gems in --without groups while --frozen + - Allow gem :ref to be a symbol as well as a string + - Fix exception when Gemfile needs a newer Bundler version + - Explanation when the current Bundler version conflicts + - Explicit error message if Gemfile needs newer Bundler + - Ignore an empty string BUNDLE_GEMFILE + - Skeleton gemspec now works with older versions of git + - Fix shell quoting and ref fetching in GemHelper + - Disable colored output in --deployment + - Preserve line endings in lock file + +## Features: + + - Add support for 'mingw32' platform (aka RubyInstaller) + - Large speed increase when Gemfile.lock is already present + - Huge speed increase when many (100+) system gems are present + - Significant expansion of ISSUES, man pages, and docs site + - Remove Open3 from GemHelper (now it works on Windows™®©) + - Allow setting roles in built-in cap and vlad tasks + +# 1.0.3 (October 15, 2010) + +## Bug fixes: + + - Use bitwise or in #hash to reduce the chance of overflow + - `bundle update` now works with :git + :tag updates + - Record relative :path options in the Gemfile.lock + - :groups option on gem method in Gemfile now works + - Add #platform method and :platform option to Gemfile DSL + - --without now accepts a quoted, space-separated list + - Installing after --deployment with no lock is now possible + - Binstubs can now be symlinked + - Print warning if cache for --local install is missing gems + - Improve output when installing to a path + - The tests all pass! Yay! + +# 1.0.2 (October 2, 2010) + +## Bug fixes: + + - Actually include the man pages in the gem, so help works + +# 1.0.1 (October 1, 2010) + +## Features: + + - Vlad deployment recipe, `require 'bundler/vlad'` + - Prettier bundle graphs + - Improved gem skeleton for `bundle gem` + - Prompt on file clashes when generating a gem + - Option to generate binary with gem skeleton + - Allow subclassing of GemHelper for custom tasks + - Chdir to gem directory during `bundle open` + +## Bug fixes: + + - Allow gemspec requirements with a list of versions + - Accept lockfiles with windows line endings + - Respect BUNDLE_WITHOUT env var + - Allow `gem "foo", :platform => :jruby` + - Specify loaded_from path in fake gemspec + - Flesh out gem_helper tasks, raise errors correctly + - Respect RBConfig::CONFIG['ruby_install_name'] in binstubs + +# 1.0.0 (August 29, 2010) + +## Features: + + - You can now define `:bundle_cmd` in the capistrano task + +## Bug fixes: + + - Various bugfixes to the built-in rake helpers + - Fix a bug where shortrefs weren't unique enough and were + therefore colliding + - Fix a small bug involving checking whether a local git + clone is up to date + - Correctly handle explicit '=' dependencies with gems + pinned to a git source + - Fix an issue with Windows-generated lockfiles by reading + and writing the lockfile in binary mode + - Fix an issue with shelling out to git in Windows by + using double quotes around paths + - Detect new Rubygems sources in the Gemfile and update + the lockfile + +# 1.0.0.rc.6 (August 23, 2010) + +## Features: + + - Much better documentation for most of the commands and Gemfile + format + +## Bug fixes: + + - Don't attempt to create directories if they already exist + - Fix the capistrano task so that it actually runs + - Update the Gemfile template to reference rubygems.org instead + of :gemcutter + - bundle exec should exit with a non zero exit code when the gem + binary does not exist or the file is not executable. + - Expand paths in Gemfile relative to the Gemfile and not the current + working directory. + +# 1.0.0.rc.5 (August 10, 2010) + +## Features: + + - Make the Capistrano task more concise. + +## Bug fixes: + + - Fix a regression with determining whether or not to use sudo + - Allow using the --gemfile flag with the --deployment flag + +# 1.0.0.rc.4 (August 9, 2010) + +## Features: + + - `bundle gem NAME` command to generate a new gem with Gemfile + - Bundle config file location can be specified by BUNDLE_APP_CONFIG + - Add --frozen to disable updating the Gemfile.lock at runtime + (default with --deployment) + - Basic Capistrano task now added as 'bundler/capistrano' + +## Bug fixes: + + - Multiple bundler process no longer share a tmp directory + - `bundle update GEM` always updates dependencies of GEM as well + - Deleting the cache directory no longer causes errors + - Moving the bundle after installation no longer causes git errors + - Bundle path is now correctly remembered on a read-only filesystem + - Gem binaries are installed to Gem.bindir, not #{Gem.dir}/bin + - Fetch gems from vendor/cache, even without --local + - Sort lockfile by platform as well as spec + +# 1.0.0.rc.3 (August 3, 2010) + +## Features: + + - Deprecate --production flag for --deployment, since the former + was causing confusion with the :production group + - Add --gemfile option to `bundle check` + - Reduce memory usage of `bundle install` by 2-4x + - Improve message from `bundle check` under various conditions + - Better error when a changed Gemfile conflicts with Gemfile.lock + +## Bug fixes: + + - Create bin/ directory if it is missing, then install binstubs + - Error nicely on the edge case of a pinned gem with no spec + - Do not require gems for other platforms + - Update git sources along with the gems they contain + +# 1.0.0.rc.2 (July 29, 2010) + + - `bundle install path` was causing confusion, so we now print + a clarifying warning. The preferred way to install to a path + (which will not print the warning) is + `bundle install --path path/to/install`. + - `bundle install --system` installs to the default system + location ($BUNDLE_PATH or $GEM_HOME) even if you previously + used `bundle install --path` + - completely remove `--disable-shared-gems`. If you install to + system, you will not be isolated, while if you install to + another path, you will be isolated from gems installed to + the system. This was mostly an internal option whose naming + and semantics were extremely confusing. + - Add a `--production` option to `bundle install`: + - by default, installs to `vendor/bundle`. This can be + overridden with the `--path` option + - uses `--local` if `vendor/cache` is found. This will + guarantee that Bundler does not attempt to connect to + Rubygems and will use the gems cached in `vendor/cache` + instead + - Raises an exception if a Gemfile.lock is not found + - Raises an exception if you modify your Gemfile in development + but do not check in an updated Gemfile.lock + - Fixes a bug where switching a source from Rubygems to git + would always say "the git source is not checked out" when + running `bundle install` + + NOTE: We received several reports of "the git source has not + been checked out. Please run bundle install". As far as we + can tell, these problems have two possible causes: + + 1. `bundle install ~/.bundle` in one user, but actually running + the application as another user. Never install gems to a + directory scoped to a user (`~` or `$HOME`) in deployment. + 2. A bug that happened when changing a gem to a git source. + + To mitigate several common causes of `(1)`, please use the + new `--production` flag. This flag is simply a roll-up of + the best practices we have been encouraging people to use + for deployment. + + If you want to share gems across deployments, and you use + Capistrano, symlink release_path/current/vendor/bundle to + release_path/shared/bundle. This will keep deployments + snappy while maintaining the benefits of clean, deploy-time + isolation. + +# 1.0.0.rc.1 (July 26, 2010) + + - Fixed a bug with `bundle install` on multiple machines and git + +# 1.0.0.beta.10 (July 25, 2010) + + - Last release before 1.0.0.rc.1 + - Added :mri as a valid platform (platforms :mri { gem "ruby-debug" }) + - Fix `bundle install` immediately after modifying the :submodule option + - Don't write to Gemfile.lock if nothing has changed, fixing situations + where bundle install was run with a different user than the app + itself + - Fix a bug where other platforms were being wiped on `bundle update` + - Don't ask for root password on `bundle install` if not needed + - Avoid setting `$GEM_HOME` where not needed + - First solid pass of `bundle config` + - Add build options + - `bundle config build.mysql --with-mysql-config=/path/to/config` + +# 1.0.0.beta.9 (July 21, 2010) + + - Fix install failure when switching from a path to git source + - Fix `bundle exec bundle *` in a bundle with --disable-shared-gems + - Fix `bundle *` from inside a bundle with --disable-shared-gem + - Shim Gem.refresh. This is used by Unicorn + - Fix install failure when a path's dependencies changed + +# 1.0.0.beta.8 (July 20, 2010) + + - Fix a Beta 7 bug involving Ruby 1.9 + +# 1.0.0.beta.7 (July 20, 2010, yanked) + + - Running `bundle install` twice in a row with a git source always crashed + +# 1.0.0.beta.6 (July 20, 2010, yanked) + + - Create executables with bundle install --binstubs + - You can customize the location (default is app/bin) with --binstubs other/location + - Fix a bug where the Gemfile.lock would be deleted even if the update was exited + - Fix a bug where cached gems for other platforms were sometimes deleted + - Clean up output when nothing was deleted from cache (it previously said + "Removing outdated gems ...") + - Improve performance of bundle install if the git gem was already checked out, + and the revision being used already exists locally + - Fix bundle show bundler in some cases + - Fix bugs with bundle update + - Don't ever run git commands at runtime (fixes a number of common passenger issues) + - Fixes an obscure bug where switching the source of a gem could fail to correctly + change the source of its dependencies + - Support multiple version dependencies in the Gemfile + (`gem "rails", ">= 3.0.0.beta1", "<= 3.0.0"`) + - Raise an exception for ambiguous uses of multiple declarations of the same gem + (for instance, with different versions or sources). + - Fix cases where the same dependency appeared several times in the Gemfile.lock + - Fix a bug where require errors were being swallowed during Bundler.require + +# 1.0.0.beta.1 + + - No `bundle lock` command. Locking happens automatically on install or update + - No .bundle/environment.rb. Require 'bundler/setup' instead. + - $BUNDLE_HOME defaults to $GEM_HOME instead of ~/.bundle + - Remove lockfiles generated by 0.9 + +# 0.9.26 + +## Features: + + - error nicely on incompatible 0.10 lockfiles + +# 0.9.25 (May 3, 2010) + +## Bug fixes: + + - explicitly coerce Pathname objects to Strings for Ruby 1.9 + - fix some newline weirdness in output from install command + +# 0.9.24 (April 22, 2010) + +## Features: + + - fetch submodules for git sources + - limit the bundled version of bundler to the same as the one installing + - force relative paths in git gemspecs to avoid raising Gem::NameTooLong + - serialize GemCache sources correctly, so locking works + - raise Bundler::GemNotFound instead of calling exit! inside library code + - Rubygems 1.3.5 compatibility for the adventurous, not supported by me :) + +## Bug fixes: + + - don't try to regenerate environment.rb if it is read-only + - prune outdated gems with the platform "ruby" + - prune cache without errors when there are directories or non-gem files + - don't re-write environment.rb if running after it has been loaded + - do not monkeypatch Specification#load_paths twice when inside a bundle + +# 0.9.23 (April 20, 2010) + +## Bug fixes: + + - cache command no longer prunes gems created by an older rubygems version + - cache command no longer prunes gems that are for other platforms + +# 0.9.22 (April 20, 2010) + +## Features: + + - cache command now prunes stale .gem files from vendor/cache + - init --gemspec command now generates development dependencies + - handle Polyglot's changes to Kernel#require with Bundler::ENV_LOADED ([#287](https://github.com/rubygems/bundler/issues/287)) + - remove .gem files generated after installing a gem from a :path ([#286](https://github.com/rubygems/bundler/issues/286)) + - improve install/lock messaging ([#284](https://github.com/rubygems/bundler/issues/284)) + +## Bug fixes: + + - ignore cached gems that are for another platform ([#288](https://github.com/rubygems/bundler/issues/288)) + - install Windows gems that have no architecture set, like rcov ([#277](https://github.com/rubygems/bundler/issues/277)) + - exec command while locked now includes the bundler lib in $LOAD_PATH ([#293](https://github.com/rubygems/bundler/issues/293)) + - fix the `rake install` task + - add GemspecError so it can be raised without (further) error ([#292](https://github.com/rubygems/bundler/issues/292)) + - create a parent directory before cloning for git 1.5 compatibility ([#285](https://github.com/rubygems/bundler/issues/285)) + +# 0.9.21 (April 16, 2010) + +## Bug fixes: + + - don't raise 'omg wtf' when lockfile is outdated + +# 0.9.20 (April 15, 2010) + +## Features: + + - load YAML format gemspecs + - no backtraces when calling Bundler.setup if gems are missing + - no backtraces when trying to exec a file without the executable bit + +## Bug fixes: + + - fix infinite recursion in Bundler.setup after loading a bundled Bundler gem + - request install instead of lock when env.rb is out of sync with Gemfile.lock + +# 0.9.19 (April 12, 2010) + +## Features: + + - suggest `bundle install --relock` when the Gemfile has changed ([#272](https://github.com/rubygems/bundler/issues/272)) + - source support for Rubygems servers without prerelease gem indexes ([#262](https://github.com/rubygems/bundler/issues/262)) + +## Bug fixes: + + - don't set up all groups every time Bundler.setup is called while locked ([#263](https://github.com/rubygems/bundler/issues/263)) + - fix #full_gem_path for git gems while locked ([#268](https://github.com/rubygems/bundler/issues/268)) + - eval gemspecs at the top level, not inside the Bundler class ([#269](https://github.com/rubygems/bundler/issues/269)) + + +# 0.9.18 (April 8, 2010) + +## Features: + + - console command that runs irb with bundle (and optional group) already loaded + +## Bug fixes: + + - Bundler.setup now fully disables system gems, even when unlocked ([#266](https://github.com/rubygems/bundler/issues/266), [#246](https://github.com/bundler/bundler/issues/246)) + - fixes Yard, which found plugins in Gem.source_index that it could not load + - makes behaviour of `Bundler.require` consistent between locked and unlocked loads + +# 0.9.17 (April 7, 2010) + +## Features: + + - Bundler.require now calls Bundler.setup automatically + - Gem::Specification#add_bundler_dependencies added for gemspecs + +## Bug fixes: + + - Gem paths are not longer duplicated while loading bundler + - exec no longer duplicates RUBYOPT if it is already set correctly + +# 0.9.16 (April 3, 2010) + +## Features: + + - exit gracefully on INT signal + - resolver output now indicates whether remote sources were checked + - print error instead of backtrace when exec cannot find a binary ([#241](https://github.com/rubygems/bundler/issues/241)) + +## Bug fixes: + + - show, check, and open commands work again while locked (oops) + - show command for git gems + - outputs branch names other than master + - gets the correct sha from the checkout + - doesn't print sha twice if :ref is set + - report errors from bundler/setup.rb without backtraces ([#243](https://github.com/rubygems/bundler/issues/243)) + - fix Gem::Spec#git_version to not error on unloaded specs + - improve deprecation, Gemfile, and command error messages ([#242](https://github.com/rubygems/bundler/issues/242)) + +# 0.9.15 (April 1, 2010) + +## Features: + + - use the env_file if possible instead of doing a runtime resolve + - huge speedup when calling Bundler.setup while locked + - ensures bundle exec is fast while locked + - regenerates env_file if it was generated by an older version + - update cached/packed gems when you update gems via bundle install + +## Bug fixes: + + - prep for Rubygems 1.3.7 changes + - install command now pulls git branches correctly ([#211](https://github.com/rubygems/bundler/issues/211)) + - raise errors on invalid options in the Gemfile + +# 0.9.14 (March 30, 2010) + +## Features: + + - install command output vastly improved + - installation message now accurate, with 'using' and 'installing' + - bundler gems no longer listed as 'system gems' + - show command output now includes sha and branch name for git gems + - init command now takes --gemspec option for bootstrapping gem Gemfiles + - Bundler.with_clean_env for shelling out to ruby scripts + - show command now aliased as 'list' + - VISUAL env var respected for GUI editors + +## Bug fixes: + + - exec command now finds binaries from gems with no gemspec + - note source of Gemfile resolver errors + - don't blow up if git urls are changed + +# 0.9.13 (March 23, 2010) + +## Bug fixes: + + - exec command now finds binaries from gems installed via :path + - gem dependencies are pulled in even if their type is nil + - paths with spaces have double-quotes to work on Windows + - set GEM_PATH in environment.rb so generators work with Rails 2 + +# 0.9.12 (March 17, 2010) + + - refactoring, internal cleanup, more solid specs + +## Features: + + - check command takes a --without option + - check command exits 1 if the check fails + +## Bug fixes: + + - perform a topological sort on resolved gems ([#191](https://github.com/rubygems/bundler/issues/191)) + - gems from git work even when paths or repos have spaces ([#196](https://github.com/rubygems/bundler/issues/196)) + - Specification#loaded_from returns a String, like Gem::Specification ([#197](https://github.com/rubygems/bundler/issues/197)) + - specs eval from inside the gem directory, even when locked + - virtual gemspecs are now saved in environment.rb for use when loading + - unify the Installer's local index and the runtime index ([#204](https://github.com/rubygems/bundler/issues/204)) + +# 0.9.11 (March 9, 2010) + + - added roadmap with future development plans + +## Features: + + - install command can take the path to the gemfile with --gemfile ([#125](https://github.com/rubygems/bundler/issues/125)) + - unknown command line options are now rejected ([#163](https://github.com/rubygems/bundler/issues/163)) + - exec command hugely sped up while locked ([#177](https://github.com/rubygems/bundler/issues/177)) + - show command prints the install path if you pass it a gem name ([#148](https://github.com/rubygems/bundler/issues/148)) + - open command edits an installed gem with $EDITOR ([#148](https://github.com/rubygems/bundler/issues/148)) + - Gemfile allows assigning an array of groups to a gem ([#114](https://github.com/rubygems/bundler/issues/114)) + - Gemfile allows :tag option on :git sources + - improve backtraces when a gemspec is invalid + - improve performance by installing gems from the cache if present + +## Bug fixes: + + - normalize parameters to Bundler.require ([#153](https://github.com/rubygems/bundler/issues/153)) + - check now checks installed gems rather than cached gems ([#162](https://github.com/rubygems/bundler/issues/162)) + - don't update the gem index when installing after locking ([#169](https://github.com/rubygems/bundler/issues/169)) + - bundle parenthesises arguments for 1.8.6 ([#179](https://github.com/rubygems/bundler/issues/179)) + - gems can now be assigned to multiple groups without problems ([#135](https://github.com/rubygems/bundler/issues/135)) + - fix the warning when building extensions for a gem from git with Rubygems 1.3.6 + - fix a Dependency.to_yaml error due to accidentally including sources and groups + - don't reinstall packed gems + - fix gems with git sources that are private repositories + +# 0.9.10 (March 1, 2010) + + - depends on Rubygems 1.3.6 + +## Bug fixes: + + - support locking after install --without + - don't reinstall gems from the cache if they're already in the bundle + - fixes for Ruby 1.8.7 and 1.9 + +# 0.9.9 (February 25, 2010) + +## Bug fixes: + + - don't die if GEM_HOME is an empty string + - fixes for Ruby 1.8.6 and 1.9 + +# 0.9.8 (February 23, 2010) + +## Features: + + - pack command which both caches and locks + - descriptive error if a cached gem is missing + - remember the --without option after installing + - expand paths given in the Gemfile via the :path option + - add block syntax to the git and group options in the Gemfile + - support gems with extensions that don't admit they depend on rake + - generate gems using gem build gemspec so git gems can have native extensions + - print a useful warning if building a gem fails + - allow manual configuration via BUNDLE_PATH + +## Bug fixes: + + - eval gemspecs in the gem directory so relative paths work + - make default spec for git sources valid + - don't reinstall gems that are already packed + +# 0.9.7 (February 17, 2010) + +## Bug fixes: + + - don't say that a gem from an excluded group is "installing" + - improve crippling rubygems in locked scenarios + +# 0.9.6 (February 16, 2010) + +## Features: + + - allow String group names + - a number of improvements in the documentation and error messages + +## Bug fixes: + + - set SourceIndex#spec_dirs to solve a problem involving Rails 2.3 in unlocked mode + - ensure Rubygems is fully loaded in Ruby 1.9 before patching it + - fix `bundle install` for a locked app without a .bundle directory + - require gems in the order that the resolver determines + - make the tests platform agnostic so we can confirm that they're green on JRuby + - fixes for Ruby 1.9 + +# 0.9.5 (February 12, 2010) + +## Features: + + - added support for :path => "relative/path" + - added support for older versions of git + - added `bundle install --disable-shared-gems` + - Bundler.require fails silently if a library does not have a file on the load path with its name + - Basic support for multiple rubies by namespacing the default bundle path using the version and engine + +## Bug fixes: + + - if the bundle is locked and .bundle/environment.rb is not present when Bundler.setup is called, generate it + - same if it's not present with `bundle check` + - same if it's not present with `bundle install` diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/LICENSE.md new file mode 100644 index 0000000000..52b5c2132f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License + +Portions copyright (c) 2010-2019 André Arko +Portions copyright (c) 2009 Engine Yard + +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.0.0/gems/bundler-2.2.23/README.md b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/README.md new file mode 100644 index 0000000000..9c65a8031b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/README.md @@ -0,0 +1,62 @@ +[![Version ](https://img.shields.io/gem/v/bundler.svg?style=flat)](https://rubygems.org/gems/bundler) +[![Slack ](https://bundler-slackin.herokuapp.com/badge.svg)](https://bundler-slackin.herokuapp.com) + +# Bundler: a gem to bundle gems + +Bundler makes sure Ruby applications run the same code on every machine. + +It does this by managing the gems that the application depends on. Given a list of gems, it can automatically download and install those gems, as well as any other gems needed by the gems that are listed. Before installing gems, it checks the versions of every gem to make sure that they are compatible, and can all be loaded at the same time. After the gems have been installed, Bundler can help you update some or all of them when new versions become available. Finally, it records the exact versions that have been installed, so that others can install the exact same gems. + +### Installation and usage + +To install (or update to the latest version): + +``` +gem install bundler +``` + +To install a prerelease version (if one is available), run `gem install bundler --pre`. To uninstall Bundler, run `gem uninstall bundler`. + +Bundler is most commonly used to manage your application's dependencies. For example, these commands will allow you to use Bundler to manage the `rspec` gem for your application: + +``` +bundle init +bundle add rspec +bundle install +bundle exec rspec +``` + +See [bundler.io](https://bundler.io) for the full documentation. + +### Troubleshooting + +For help with common problems, see [TROUBLESHOOTING](doc/TROUBLESHOOTING.md). + +Still stuck? Try [filing an issue](doc/contributing/ISSUES.md). + +### Other questions + +To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANGELOG.md). + +To get in touch with the Bundler core team and other Bundler users, please see [getting help](doc/contributing/GETTING_HELP.md). + +### Contributing + +If you'd like to contribute to Bundler, that's awesome, and we <3 you. We've put together [the Bundler contributor guide](https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/README.md) with all of the information you need to get started. + +If you'd like to request a substantial change to Bundler or its documentation, refer to the [Bundler RFC process](https://github.com/bundler/rfcs) for more information. + +While some Bundler contributors are compensated by Ruby Together, the project maintainers make decisions independent of Ruby Together. As a project, we welcome contributions regardless of the author's affiliation with Ruby Together. + +### Supporting + +
+Ruby Together pays some Bundler maintainers for their ongoing work. As a grassroots initiative committed to supporting the critical Ruby infrastructure you rely on, Ruby Together is funded entirely by the Ruby community. Contribute today as an individual or (better yet) as a company to ensure that Bundler, RubyGems, and other shared tooling is around for years to come. + +### Code of Conduct + +Everyone interacting in the Bundler project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [Bundler code of conduct](https://github.com/rubygems/rubygems/blob/master/CODE_OF_CONDUCT.md). + +### License + +Bundler is available under an [MIT License](https://github.com/rubygems/rubygems/blob/master/bundler/LICENSE.md). diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/bundler.gemspec b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/bundler.gemspec new file mode 100644 index 0000000000..f91798ab64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/bundler.gemspec @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +begin + require_relative "lib/bundler/version" +rescue LoadError + # for Ruby core repository + require_relative "version" +end + +Gem::Specification.new do |s| + s.name = "bundler" + s.version = Bundler::VERSION + s.license = "MIT" + s.authors = [ + "André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata", + "David Rodríguez", "Grey Baker", "Stephanie Morillo", "Chris Morris", "James Wen", "Tim Moore", + "André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche", + "Yehuda Katz" + ] + s.email = ["team@bundler.io"] + s.homepage = "https://bundler.io" + s.summary = "The best way to manage your application's dependencies" + s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" + + if s.respond_to?(:metadata=) + s.metadata = { + "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + "homepage_uri" => "https://bundler.io/", + "source_code_uri" => "https://github.com/rubygems/rubygems/", + } + end + + s.required_ruby_version = ">= 2.3.0" + s.required_rubygems_version = ">= 2.5.2" + + s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + + # include the gemspec itself because warbler breaks w/o it + s.files += %w[bundler.gemspec] + + s.files += %w[CHANGELOG.md LICENSE.md README.md] + s.bindir = "exe" + s.executables = %w[bundle bundler] + s.require_paths = ["lib"] +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundle b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundle new file mode 100755 index 0000000000..1168e5a361 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundle @@ -0,0 +1,50 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Exit cleanly from an early interrupt +Signal.trap("INT") do + Bundler.ui.debug("\n#{caller.join("\n")}") if defined?(Bundler) + exit 1 +end + +base_path = File.expand_path("../lib", __dir__) + +if File.exist?(base_path) + require_relative "../lib/bundler" +else + require "bundler" +end + +# Workaround for non-activated bundler spec due to missing https://github.com/rubygems/rubygems/commit/4e306d7bcdee924b8d80ca9db6125aa59ee4e5a3 +gem "bundler", Bundler::VERSION if Gem.rubygems_version < Gem::Version.new("2.6.2") + +# Check if an older version of bundler is installed +$LOAD_PATH.each do |path| + next unless path =~ %r{/bundler-0\.(\d+)} && $1.to_i < 9 + err = String.new + err << "Looks like you have a version of bundler that's older than 0.9.\n" + err << "Please remove your old versions.\n" + err << "An easy way to do this is by running `gem cleanup bundler`." + abort(err) +end + +if File.exist?(base_path) + require_relative "../lib/bundler/friendly_errors" +else + require "bundler/friendly_errors" +end + +Bundler.with_friendly_errors do + if File.exist?(base_path) + require_relative "../lib/bundler/cli" + else + require "bundler/cli" + end + + # Allow any command to use --help flag to show help for that command + help_flags = %w[--help -h] + help_flag_used = ARGV.any? {|a| help_flags.include? a } + args = help_flag_used ? Bundler::CLI.reformatted_help_args(ARGV) : ARGV + + Bundler::CLI.start(args, :debug => true) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundler b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundler new file mode 100755 index 0000000000..d9131fe834 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/exe/bundler @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +load File.expand_path("../bundle", __FILE__) diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler.rb new file mode 100644 index 0000000000..e3127adb6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler.rb @@ -0,0 +1,706 @@ +# frozen_string_literal: true + +require_relative "bundler/vendored_fileutils" +require "pathname" +require "rbconfig" + +require_relative "bundler/errors" +require_relative "bundler/environment_preserver" +require_relative "bundler/plugin" +require_relative "bundler/rubygems_ext" +require_relative "bundler/rubygems_integration" +require_relative "bundler/version" +require_relative "bundler/constants" +require_relative "bundler/current_ruby" +require_relative "bundler/build_metadata" + +# Bundler provides a consistent environment for Ruby projects by +# tracking and installing the exact gems and versions that are needed. +# +# Since Ruby 2.6, Bundler is a part of Ruby's standard library. +# +# Bunder is used by creating _gemfiles_ listing all the project dependencies +# and (optionally) their versions and then using +# +# require 'bundler/setup' +# +# or Bundler.setup to setup environment where only specified gems and their +# specified versions could be used. +# +# See {Bundler website}[https://bundler.io/docs.html] for extensive documentation +# on gemfiles creation and Bundler usage. +# +# As a standard library inside project, Bundler could be used for introspection +# of loaded and required modules. +# +module Bundler + environment_preserver = EnvironmentPreserver.from_env + ORIGINAL_ENV = environment_preserver.restore + environment_preserver.replace_with_backup + SUDO_MUTEX = Mutex.new + + autoload :Definition, File.expand_path("bundler/definition", __dir__) + autoload :Dependency, File.expand_path("bundler/dependency", __dir__) + autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__) + autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__) + autoload :Dsl, File.expand_path("bundler/dsl", __dir__) + autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__) + autoload :Env, File.expand_path("bundler/env", __dir__) + autoload :Fetcher, File.expand_path("bundler/fetcher", __dir__) + autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__) + autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__) + autoload :GemHelpers, File.expand_path("bundler/gem_helpers", __dir__) + autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__) + autoload :Graph, File.expand_path("bundler/graph", __dir__) + autoload :Index, File.expand_path("bundler/index", __dir__) + autoload :Injector, File.expand_path("bundler/injector", __dir__) + autoload :Installer, File.expand_path("bundler/installer", __dir__) + autoload :LazySpecification, File.expand_path("bundler/lazy_specification", __dir__) + autoload :LockfileParser, File.expand_path("bundler/lockfile_parser", __dir__) + autoload :MatchPlatform, File.expand_path("bundler/match_platform", __dir__) + autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__) + autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__) + autoload :Resolver, File.expand_path("bundler/resolver", __dir__) + autoload :Retry, File.expand_path("bundler/retry", __dir__) + autoload :RubyDsl, File.expand_path("bundler/ruby_dsl", __dir__) + autoload :RubyVersion, File.expand_path("bundler/ruby_version", __dir__) + autoload :Runtime, File.expand_path("bundler/runtime", __dir__) + autoload :Settings, File.expand_path("bundler/settings", __dir__) + autoload :SharedHelpers, File.expand_path("bundler/shared_helpers", __dir__) + autoload :Source, File.expand_path("bundler/source", __dir__) + autoload :SourceList, File.expand_path("bundler/source_list", __dir__) + autoload :SourceMap, File.expand_path("bundler/source_map", __dir__) + autoload :SpecSet, File.expand_path("bundler/spec_set", __dir__) + autoload :StubSpecification, File.expand_path("bundler/stub_specification", __dir__) + autoload :UI, File.expand_path("bundler/ui", __dir__) + autoload :URICredentialsFilter, File.expand_path("bundler/uri_credentials_filter", __dir__) + autoload :VersionRanges, File.expand_path("bundler/version_ranges", __dir__) + + class << self + def configure + @configured ||= configure_gem_home_and_path + end + + def ui + (defined?(@ui) && @ui) || (self.ui = UI::Shell.new) + end + + def ui=(ui) + Bundler.rubygems.ui = UI::RGProxy.new(ui) + @ui = ui + end + + # Returns absolute path of where gems are installed on the filesystem. + def bundle_path + @bundle_path ||= Pathname.new(configured_bundle_path.path).expand_path(root) + end + + def configured_bundle_path + @configured_bundle_path ||= settings.path.tap(&:validate!) + end + + # Returns absolute location of where binstubs are installed to. + def bin_path + @bin_path ||= begin + path = settings[:bin] || "bin" + path = Pathname.new(path).expand_path(root).expand_path + SharedHelpers.filesystem_access(path) {|p| FileUtils.mkdir_p(p) } + path + end + end + + # Turns on the Bundler runtime. After +Bundler.setup+ call, all +load+ or + # +require+ of the gems would be allowed only if they are part of + # the Gemfile or Ruby's standard library. If the versions specified + # in Gemfile, only those versions would be loaded. + # + # Assuming Gemfile + # + # gem 'first_gem', '= 1.0' + # group :test do + # gem 'second_gem', '= 1.0' + # end + # + # The code using Bundler.setup works as follows: + # + # require 'third_gem' # allowed, required from global gems + # require 'first_gem' # allowed, loads the last installed version + # Bundler.setup + # require 'fourth_gem' # fails with LoadError + # require 'second_gem' # loads exactly version 1.0 + # + # +Bundler.setup+ can be called only once, all subsequent calls are no-op. + # + # If _groups_ list is provided, only gems from specified groups would + # be allowed (gems specified outside groups belong to special +:default+ group). + # + # To require all gems from Gemfile (or only some groups), see Bundler.require. + # + def setup(*groups) + # Return if all groups are already loaded + return @setup if defined?(@setup) && @setup + + definition.validate_runtime! + + SharedHelpers.print_major_deprecations! + + if groups.empty? + # Load all groups, but only once + @setup = load.setup + else + load.setup(*groups) + end + end + + # Setups Bundler environment (see Bundler.setup) if it is not already set, + # and loads all gems from groups specified. Unlike ::setup, can be called + # multiple times with different groups (if they were allowed by setup). + # + # Assuming Gemfile + # + # gem 'first_gem', '= 1.0' + # group :test do + # gem 'second_gem', '= 1.0' + # end + # + # The code will work as follows: + # + # Bundler.setup # allow all groups + # Bundler.require(:default) # requires only first_gem + # # ...later + # Bundler.require(:test) # requires second_gem + # + def require(*groups) + setup(*groups).require(*groups) + end + + def load + @load ||= Runtime.new(root, definition) + end + + def environment + SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load", :print_caller_location => true + load + end + + # Returns an instance of Bundler::Definition for given Gemfile and lockfile + # + # @param unlock [Hash, Boolean, nil] Gems that have been requested + # to be updated or true if all gems should be updated + # @return [Bundler::Definition] + def definition(unlock = nil) + @definition = nil if unlock + @definition ||= begin + configure + Definition.build(default_gemfile, default_lockfile, unlock) + end + end + + def frozen_bundle? + frozen = settings[:deployment] + frozen ||= settings[:frozen] + frozen + end + + def locked_gems + @locked_gems ||= + if defined?(@definition) && @definition + definition.locked_gems + elsif Bundler.default_lockfile.file? + lock = Bundler.read_file(Bundler.default_lockfile) + LockfileParser.new(lock) + end + end + + def most_specific_locked_platform?(platform) + return false unless defined?(@definition) && @definition + + definition.most_specific_locked_platform == platform + end + + def ruby_scope + "#{Bundler.rubygems.ruby_engine}/#{RbConfig::CONFIG["ruby_version"]}" + end + + def user_home + @user_home ||= begin + home = Bundler.rubygems.user_home + bundle_home = home ? File.join(home, ".bundle") : nil + + warning = if home.nil? + "Your home directory is not set." + elsif !File.directory?(home) + "`#{home}` is not a directory." + elsif !File.writable?(home) && (!File.directory?(bundle_home) || !File.writable?(bundle_home)) + "`#{home}` is not writable." + end + + if warning + Bundler.ui.warn "#{warning}\n" + user_home = tmp_home_path + Bundler.ui.warn "Bundler will use `#{user_home}' as your home directory temporarily.\n" + user_home + else + Pathname.new(home) + end + end + end + + def user_bundle_path(dir = "home") + env_var, fallback = case dir + when "home" + ["BUNDLE_USER_HOME", proc { Pathname.new(user_home).join(".bundle") }] + when "cache" + ["BUNDLE_USER_CACHE", proc { user_bundle_path.join("cache") }] + when "config" + ["BUNDLE_USER_CONFIG", proc { user_bundle_path.join("config") }] + when "plugin" + ["BUNDLE_USER_PLUGIN", proc { user_bundle_path.join("plugin") }] + else + raise BundlerError, "Unknown user path requested: #{dir}" + end + # `fallback` will already be a Pathname, but Pathname.new() is + # idempotent so it's OK + Pathname.new(ENV.fetch(env_var, &fallback)) + end + + def user_cache + user_bundle_path("cache") + end + + def home + bundle_path.join("bundler") + end + + def install_path + home.join("gems") + end + + def specs_path + bundle_path.join("specifications") + end + + def root + @root ||= begin + SharedHelpers.root + rescue GemfileNotFound + bundle_dir = default_bundle_dir + raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir + Pathname.new(File.expand_path("..", bundle_dir)) + end + end + + def app_config_path + if app_config = ENV["BUNDLE_APP_CONFIG"] + app_config_pathname = Pathname.new(app_config) + + if app_config_pathname.absolute? + app_config_pathname + else + app_config_pathname.expand_path(root) + end + else + root.join(".bundle") + end + end + + def app_cache(custom_path = nil) + path = custom_path || root + Pathname.new(path).join(settings.app_cache_path) + end + + def tmp(name = Process.pid.to_s) + Kernel.send(:require, "tmpdir") + Pathname.new(Dir.mktmpdir(["bundler", name])) + end + + def rm_rf(path) + FileUtils.remove_entry_secure(path) if path && File.exist?(path) + rescue ArgumentError + message = < true + ) + + unbundled_env + end + + # @return [Hash] Environment with all bundler-related variables removed + def unbundled_env + env = original_env + + if env.key?("BUNDLER_ORIG_MANPATH") + env["MANPATH"] = env["BUNDLER_ORIG_MANPATH"] + end + + env.delete_if {|k, _| k[0, 7] == "BUNDLE_" } + + if env.key?("RUBYOPT") + rubyopt = env["RUBYOPT"].split(" ") + rubyopt.delete("-r#{File.expand_path("bundler/setup", __dir__)}") + rubyopt.delete("-rbundler/setup") + env["RUBYOPT"] = rubyopt.join(" ") + end + + if env.key?("RUBYLIB") + rubylib = env["RUBYLIB"].split(File::PATH_SEPARATOR) + rubylib.delete(File.expand_path("..", __FILE__)) + env["RUBYLIB"] = rubylib.join(File::PATH_SEPARATOR) + end + + env + end + + # Run block with environment present before Bundler was activated + def with_original_env + with_env(original_env) { yield } + end + + # @deprecated Use `with_unbundled_env` instead + def with_clean_env + Bundler::SharedHelpers.major_deprecation( + 2, + "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ + "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`", + :print_caller_location => true + ) + + with_env(unbundled_env) { yield } + end + + # Run block with all bundler-related variables removed + def with_unbundled_env + with_env(unbundled_env) { yield } + end + + # Run subcommand with the environment present before Bundler was activated + def original_system(*args) + with_original_env { Kernel.system(*args) } + end + + # @deprecated Use `unbundled_system` instead + def clean_system(*args) + Bundler::SharedHelpers.major_deprecation( + 2, + "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ + "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`", + :print_caller_location => true + ) + + with_env(unbundled_env) { Kernel.system(*args) } + end + + # Run subcommand in an environment with all bundler related variables removed + def unbundled_system(*args) + with_unbundled_env { Kernel.system(*args) } + end + + # Run a `Kernel.exec` to a subcommand with the environment present before Bundler was activated + def original_exec(*args) + with_original_env { Kernel.exec(*args) } + end + + # @deprecated Use `unbundled_exec` instead + def clean_exec(*args) + Bundler::SharedHelpers.major_deprecation( + 2, + "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ + "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`", + :print_caller_location => true + ) + + with_env(unbundled_env) { Kernel.exec(*args) } + end + + # Run a `Kernel.exec` to a subcommand in an environment with all bundler related variables removed + def unbundled_exec(*args) + with_env(unbundled_env) { Kernel.exec(*args) } + end + + def local_platform + return Gem::Platform::RUBY if settings[:force_ruby_platform] || Gem.platforms == [Gem::Platform::RUBY] + Gem::Platform.local + end + + def default_gemfile + SharedHelpers.default_gemfile + end + + def default_lockfile + SharedHelpers.default_lockfile + end + + def default_bundle_dir + SharedHelpers.default_bundle_dir + end + + def system_bindir + # Gem.bindir doesn't always return the location that RubyGems will install + # system binaries. If you put '-n foo' in your .gemrc, RubyGems will + # install binstubs there instead. Unfortunately, RubyGems doesn't expose + # that directory at all, so rather than parse .gemrc ourselves, we allow + # the directory to be set as well, via `bundle config set --local bindir foo`. + Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir + end + + def preferred_gemfile_name + Bundler.settings[:init_gems_rb] ? "gems.rb" : "Gemfile" + end + + def use_system_gems? + configured_bundle_path.use_system_gems? + end + + def requires_sudo? + return @requires_sudo if defined?(@requires_sudo_ran) + + sudo_present = which "sudo" if settings.allow_sudo? + + if sudo_present + # the bundle path and subdirectories need to be writable for RubyGems + # to be able to unpack and install gems without exploding + path = bundle_path + path = path.parent until path.exist? + + # bins are written to a different location on OS X + bin_dir = Pathname.new(Bundler.system_bindir) + bin_dir = bin_dir.parent until bin_dir.exist? + + # if any directory is not writable, we need sudo + files = [path, bin_dir] | Dir[bundle_path.join("build_info/*").to_s] | Dir[bundle_path.join("*").to_s] + unwritable_files = files.reject {|f| File.writable?(f) } + sudo_needed = !unwritable_files.empty? + if sudo_needed + Bundler.ui.warn "Following files may not be writable, so sudo is needed:\n #{unwritable_files.map(&:to_s).sort.join("\n ")}" + end + end + + @requires_sudo_ran = true + @requires_sudo = settings.allow_sudo? && sudo_present && sudo_needed + end + + def mkdir_p(path, options = {}) + if requires_sudo? && !options[:no_sudo] + sudo "mkdir -p '#{path}'" unless File.exist?(path) + else + SharedHelpers.filesystem_access(path, :write) do |p| + FileUtils.mkdir_p(p) + end + end + end + + def which(executable) + if File.file?(executable) && File.executable?(executable) + executable + elsif paths = ENV["PATH"] + quote = '"'.freeze + paths.split(File::PATH_SEPARATOR).find do |path| + path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote) + executable_path = File.expand_path(executable, path) + return executable_path if File.file?(executable_path) && File.executable?(executable_path) + end + end + end + + def sudo(str) + SUDO_MUTEX.synchronize do + prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " " + Your user account isn't allowed to install to the system RubyGems. + You can cancel this installation and run: + + bundle config set --local path 'vendor/bundle' + bundle install + + to install the gems into ./vendor/bundle/, or you can enter your password + and install the bundled gems to RubyGems using sudo. + + Password: + PROMPT + + unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true)) + raise SudoNotPermittedError, + "Bundler requires sudo access to install at the moment. " \ + "Try installing again, granting Bundler sudo access when prompted, or installing into a different path." + end + + `sudo -p "#{prompt}" #{str}` + end + end + + def read_file(file) + SharedHelpers.filesystem_access(file, :read) do + File.open(file, "r:UTF-8", &:read) + end + end + + def load_marshal(data) + Marshal.load(data) + rescue StandardError => e + raise MarshalError, "#{e.class}: #{e.message}" + end + + def load_gemspec(file, validate = false) + @gemspec_cache ||= {} + key = File.expand_path(file) + @gemspec_cache[key] ||= load_gemspec_uncached(file, validate) + # Protect against caching side-effected gemspecs by returning a + # new instance each time. + @gemspec_cache[key].dup if @gemspec_cache[key] + end + + def load_gemspec_uncached(file, validate = false) + path = Pathname.new(file) + contents = read_file(file) + spec = if contents.start_with?("---") # YAML header + eval_yaml_gemspec(path, contents) + else + # Eval the gemspec from its parent directory, because some gemspecs + # depend on "./" relative paths. + SharedHelpers.chdir(path.dirname.to_s) do + eval_gemspec(path, contents) + end + end + return unless spec + spec.loaded_from = path.expand_path.to_s + Bundler.rubygems.validate(spec) if validate + spec + end + + def clear_gemspec_cache + @gemspec_cache = {} + end + + def git_present? + return @git_present if defined?(@git_present) + @git_present = Bundler.which("git") || Bundler.which("git.exe") + end + + def feature_flag + @feature_flag ||= FeatureFlag.new(VERSION) + end + + def reset! + reset_paths! + Plugin.reset! + reset_rubygems! + end + + def reset_settings_and_root! + @settings = nil + @root = nil + end + + def reset_paths! + @bin_path = nil + @bundler_major_version = nil + @bundle_path = nil + @configured = nil + @configured_bundle_path = nil + @definition = nil + @load = nil + @locked_gems = nil + @root = nil + @settings = nil + @setup = nil + @user_home = nil + end + + def reset_rubygems! + return unless defined?(@rubygems) && @rubygems + rubygems.undo_replacements + rubygems.reset + @rubygems = nil + end + + private + + def eval_yaml_gemspec(path, contents) + require_relative "bundler/psyched_yaml" + + # If the YAML is invalid, Syck raises an ArgumentError, and Psych + # raises a Psych::SyntaxError. See psyched_yaml.rb for more info. + Gem::Specification.from_yaml(contents) + rescue YamlLibrarySyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception + eval_gemspec(path, contents) + end + + def eval_gemspec(path, contents) + eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s) + rescue ScriptError, StandardError => e + msg = "There was an error while loading `#{path.basename}`: #{e.message}" + + if e.is_a?(LoadError) + msg += "\nDoes it try to require a relative path? That's been removed in Ruby 1.9" + end + + raise GemspecError, Dsl::DSLError.new(msg, path, e.backtrace, contents) + end + + def configure_gem_home_and_path + configure_gem_path + configure_gem_home + bundle_path + end + + def configure_gem_path(env = ENV) + blank_home = env["GEM_HOME"].nil? || env["GEM_HOME"].empty? + if !use_system_gems? + # this needs to be empty string to cause + # PathSupport.split_gem_path to only load up the + # Bundler --path setting as the GEM_PATH. + env["GEM_PATH"] = "" + elsif blank_home + possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path] + paths = possibles.flatten.compact.uniq.reject(&:empty?) + env["GEM_PATH"] = paths.join(File::PATH_SEPARATOR) + end + end + + def configure_gem_home + Bundler::SharedHelpers.set_env "GEM_HOME", File.expand_path(bundle_path, root) + Bundler.rubygems.clear_paths + end + + def tmp_home_path + Kernel.send(:require, "tmpdir") + SharedHelpers.filesystem_access(Dir.tmpdir) do + path = Bundler.tmp + at_exit { Bundler.rm_rf(path) } + path + end + end + + # @param env [Hash] + def with_env(env) + backup = ENV.to_hash + ENV.replace(env) + yield + ensure + ENV.replace(backup) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/build_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/build_metadata.rb new file mode 100644 index 0000000000..8002e86908 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/build_metadata.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Bundler + # Represents metadata from when the Bundler gem was built. + module BuildMetadata + # begin ivars + @built_at = "2021-07-09".freeze + @git_commit_sha = "e863a3905d".freeze + @release = true + # end ivars + + # A hash representation of the build metadata. + def self.to_h + { + "Built At" => built_at, + "Git SHA" => git_commit_sha, + "Released Version" => release?, + } + end + + # A string representing the date the bundler gem was built. + def self.built_at + @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + end + + # The SHA for the git commit the bundler gem was built from. + def self.git_commit_sha + return @git_commit_sha if instance_variable_defined? :@git_commit_sha + + # If Bundler has been installed without its .git directory and without a + # commit instance variable then we can't determine its commits SHA. + git_dir = File.join(File.expand_path("../../../..", __FILE__), ".git") + if File.directory?(git_dir) + return @git_commit_sha = Dir.chdir(git_dir) { `git rev-parse --short HEAD`.strip.freeze } + end + + @git_commit_sha ||= "unknown" + end + + # Whether this is an official release build of Bundler. + def self.release? + @release + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/capistrano.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/capistrano.rb new file mode 100644 index 0000000000..1f3712d48e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/capistrano.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "shared_helpers" +Bundler::SharedHelpers.major_deprecation 2, + "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler" + +# Capistrano task for Bundler. +# +# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and +# Bundler will be activated after each new deployment. +require_relative "deployment" +require "capistrano/version" + +if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0") + raise "For Capistrano 3.x integration, please use https://github.com/capistrano/bundler" +end + +Capistrano::Configuration.instance(:must_exist).load do + before "deploy:finalize_update", "bundle:install" + Bundler::Deployment.define_task(self, :task, :except => { :no_release => true }) + set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" } +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli.rb new file mode 100644 index 0000000000..45d4e62481 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli.rb @@ -0,0 +1,844 @@ +# frozen_string_literal: true + +require_relative "vendored_thor" + +module Bundler + class CLI < Thor + require_relative "cli/common" + + package_name "Bundler" + + AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze + PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze + + COMMAND_ALIASES = { + "check" => "c", + "install" => "i", + "list" => "ls", + "exec" => ["e", "ex", "exe"], + "cache" => ["package", "pack"], + "version" => ["-v", "--version"], + }.freeze + + def self.start(*) + super + ensure + Bundler::SharedHelpers.print_major_deprecations! + end + + def self.dispatch(*) + super do |i| + i.send(:print_command) + i.send(:warn_on_outdated_bundler) + end + end + + def self.all_aliases + @all_aliases ||= begin + command_aliases = {} + + COMMAND_ALIASES.each do |name, aliases| + Array(aliases).each do |one_alias| + command_aliases[one_alias] = name + end + end + + command_aliases + end + end + + def self.aliases_for(command_name) + COMMAND_ALIASES.select {|k, _| k == command_name }.invert + end + + def initialize(*args) + super + + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] + if custom_gemfile && !custom_gemfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) + Bundler.reset_settings_and_root! + end + + Bundler.settings.set_command_option_if_given :retry, options[:retry] + + current_cmd = args.last[:current_command].name + auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) + rescue UnknownArgumentError => e + raise InvalidOption, e.message + ensure + self.options ||= {} + unprinted_warnings = Bundler.ui.unprinted_warnings + Bundler.ui = UI::Shell.new(options) + Bundler.ui.level = "debug" if options["verbose"] + unprinted_warnings.each {|w| Bundler.ui.warn(w) } + + if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty? + Bundler.ui.warn( + "The RUBYGEMS_GEMDEPS environment variable is set. This enables RubyGems' " \ + "experimental Gemfile mode, which may conflict with Bundler and cause unexpected errors. " \ + "To remove this warning, unset RUBYGEMS_GEMDEPS.", :wrap => true + ) + end + end + + check_unknown_options!(:except => [:config, :exec]) + stop_on_unknown_option! :exec + + desc "cli_help", "Prints a summary of bundler commands", :hide => true + def cli_help + version + Bundler.ui.info "\n" + + primary_commands = ["install", "update", "cache", "exec", "config", "help"] + + list = self.class.printable_commands(true) + by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] } + utilities = by_name.keys.sort - primary_commands + primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } + utilities.map! {|name| by_name[name].first } + + shell.say "Bundler commands:\n\n" + + shell.say " Primary commands:\n" + shell.print_table(primary_commands, :indent => 4, :truncate => true) + shell.say + shell.say " Utilities:\n" + shell.print_table(utilities, :indent => 4, :truncate => true) + shell.say + self.class.send(:class_options_help, shell) + end + default_task(Bundler.feature_flag.default_cli_command) + + class_option "no-color", :type => :boolean, :desc => "Disable colorization in output" + class_option "retry", :type => :numeric, :aliases => "-r", :banner => "NUM", + :desc => "Specify the number of times you wish to attempt network commands" + class_option "verbose", :type => :boolean, :desc => "Enable verbose output mode", :aliases => "-V" + + def help(cli = nil) + case cli + when "gemfile" then command = "gemfile" + when nil then command = "bundle" + else command = "bundle-#{cli}" + end + + man_path = File.expand_path("man", __dir__) + man_pages = Hash[Dir.glob(File.join(man_path, "**", "*")).grep(/.*\.\d*\Z/).collect do |f| + [File.basename(f, ".*"), f] + end] + + if man_pages.include?(command) + man_page = man_pages[command] + if Bundler.which("man") && man_path !~ %r{^file:/.+!/META-INF/jruby.home/.+} + Kernel.exec "man #{man_page}" + else + puts File.read("#{man_path}/#{File.basename(man_page)}.ronn") + end + elsif command_path = Bundler.which("bundler-#{cli}") + Kernel.exec(command_path, "--help") + else + super + end + end + + def self.handle_no_command_error(command, has_namespace = $thor_runner) + if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) + return Bundler::Plugin.exec_command(command, ARGV[1..-1]) + end + + return super unless command_path = Bundler.which("bundler-#{command}") + + Kernel.exec(command_path, *ARGV[1..-1]) + end + + desc "init [OPTIONS]", "Generates a Gemfile into the current working directory" + long_desc <<-D + Init generates a default Gemfile in the current working directory. When adding a + Gemfile to a gem with a gemspec, the --gemspec option will automatically add each + dependency listed in the gemspec file to the newly created Gemfile. + D + method_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile" + def init + require_relative "cli/init" + Init.new(options.dup).run + end + + desc "check [OPTIONS]", "Checks if the dependencies listed in Gemfile are satisfied by currently installed gems" + long_desc <<-D + Check searches the local machine for each of the gems requested in the Gemfile. If + all gems are found, Bundler prints a success message and exits with a status of 0. + If not, the first missing gem is listed and Bundler exits status 1. + D + method_option "dry-run", :type => :boolean, :default => false, :banner => + "Lock the Gemfile" + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "path", :type => :string, :banner => + "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + def check + remembered_flag_deprecation("path") + + require_relative "cli/check" + Check.new(options).run + end + + map aliases_for("check") + + desc "remove [GEM [GEM ...]]", "Removes gems from the Gemfile" + long_desc <<-D + Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning. + D + method_option "install", :type => :boolean, :banner => + "Runs 'bundle install' after removing the gems from the Gemfile" + def remove(*gems) + require_relative "cli/remove" + Remove.new(gems, options).run + end + + desc "install [OPTIONS]", "Install the current environment to the system" + long_desc <<-D + Install will install all of the gems in the current bundle, making them available + for use. In a freshly checked out repository, this command will give you the same + gem versions as the last person who updated the Gemfile and ran `bundle update`. + + Passing [DIR] to install (e.g. vendor) will cause the unpacked gems to be installed + into the [DIR] directory rather than into system gems. + + If the bundle has already been installed, bundler will tell you so and then exit. + D + method_option "binstubs", :type => :string, :lazy_default => "bin", :banner => + "Generate bin stubs for bundled gems to ./bin" + method_option "clean", :type => :boolean, :banner => + "Run bundle clean automatically after install" + method_option "deployment", :type => :boolean, :banner => + "Install using defaults tuned for deployment environments" + method_option "frozen", :type => :boolean, :banner => + "Do not allow the Gemfile.lock to be updated after this install" + method_option "full-index", :type => :boolean, :banner => + "Fall back to using the single-file index of all gems" + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" + method_option "local", :type => :boolean, :banner => + "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "no-cache", :type => :boolean, :banner => + "Don't update the existing gem cache." + method_option "redownload", :type => :boolean, :aliases => "--force", :banner => + "Force downloading every gem." + method_option "no-prune", :type => :boolean, :banner => + "Don't remove stale gems from the cache." + method_option "path", :type => :string, :banner => + "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "quiet", :type => :boolean, :banner => + "Only output warnings and errors." + method_option "shebang", :type => :string, :banner => + "Specify a different shebang executable name than the default (usually 'ruby')" + method_option "standalone", :type => :array, :lazy_default => [], :banner => + "Make a bundle that can work without the Bundler runtime" + method_option "system", :type => :boolean, :banner => + "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" + method_option "trust-policy", :alias => "P", :type => :string, :banner => + "Gem trust policy (like gem install -P). Must be one of " + + Bundler.rubygems.security_policy_keys.join("|") + method_option "without", :type => :array, :banner => + "Exclude gems that are part of the specified named group." + method_option "with", :type => :array, :banner => + "Include gems that are part of the specified named group." + def install + SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") + + %w[clean deployment frozen no-prune path shebang system without with].each do |option| + remembered_flag_deprecation(option) + end + + remembered_negative_flag_deprecation("no-deployment") + + require_relative "cli/install" + Bundler.settings.temporary(:no_install => false) do + Install.new(options.dup).run + end + end + + map aliases_for("install") + + desc "update [OPTIONS]", "Update the current environment" + long_desc <<-D + Update will install the newest versions of the gems listed in the Gemfile. Use + update when you have changed the Gemfile, or if you want to get the newest + possible versions of the gems in the bundle. + D + method_option "full-index", :type => :boolean, :banner => + "Fall back to using the single-file index of all gems" + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "group", :aliases => "-g", :type => :array, :banner => + "Update a specific group" + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" + method_option "local", :type => :boolean, :banner => + "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "quiet", :type => :boolean, :banner => + "Only output warnings and errors." + method_option "source", :type => :array, :banner => + "Update a specific source (and all gems associated with it)" + method_option "redownload", :type => :boolean, :aliases => "--force", :banner => + "Force downloading every gem." + method_option "ruby", :type => :boolean, :banner => + "Update ruby specified in Gemfile.lock" + method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner => + "Update the locked version of bundler" + method_option "patch", :type => :boolean, :banner => + "Prefer updating only to next patch version" + method_option "minor", :type => :boolean, :banner => + "Prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => + "Prefer updating to next major version (default)" + method_option "strict", :type => :boolean, :banner => + "Do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", :type => :boolean, :banner => + "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." + method_option "all", :type => :boolean, :banner => + "Update everything." + def update(*gems) + SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") + require_relative "cli/update" + Bundler.settings.temporary(:no_install => false) do + Update.new(options, gems).run + end + end + + desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem" + long_desc <<-D + Show lists the names and versions of all gems that are required by your Gemfile. + Calling show with [GEM] will list the exact location of that gem on your machine. + D + method_option "paths", :type => :boolean, + :banner => "List the paths of all gems that are required by your Gemfile." + method_option "outdated", :type => :boolean, + :banner => "Show verbose output including whether gems are outdated." + def show(gem_name = nil) + SharedHelpers.major_deprecation(2, "the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") if ARGV.include?("--outdated") + require_relative "cli/show" + Show.new(options, gem_name).run + end + + desc "list", "List all gems in the bundle" + method_option "name-only", :type => :boolean, :banner => "print only the gem names" + method_option "only-group", :type => :array, :default => [], :banner => "print gems from a given set of groups" + method_option "without-group", :type => :array, :default => [], :banner => "print all gems except from a given set of groups" + method_option "paths", :type => :boolean, :banner => "print the path to each gem in the bundle" + def list + require_relative "cli/list" + List.new(options).run + end + + map aliases_for("list") + + desc "info GEM [OPTIONS]", "Show information for the given gem" + method_option "path", :type => :boolean, :banner => "Print full path to gem" + def info(gem_name) + require_relative "cli/info" + Info.new(options, gem_name).run + end + + desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem" + long_desc <<-D + Generate binstubs for executables in [GEM]. Binstubs are put into bin, + or the --binstubs directory if one has been set. Calling binstubs with [GEM [GEM]] + will create binstubs for all given gems. + D + method_option "force", :type => :boolean, :default => false, :banner => + "Overwrite existing binstubs if they exist" + method_option "path", :type => :string, :lazy_default => "bin", :banner => + "Binstub destination directory (default bin)" + method_option "shebang", :type => :string, :banner => + "Specify a different shebang executable name than the default (usually 'ruby')" + method_option "standalone", :type => :boolean, :banner => + "Make binstubs that can work without the Bundler runtime" + method_option "all", :type => :boolean, :banner => + "Install binstubs for all gems" + method_option "all-platforms", :type => :boolean, :default => false, :banner => + "Install binstubs for all platforms" + def binstubs(*gems) + require_relative "cli/binstubs" + Binstubs.new(options, gems).run + end + + desc "add GEM VERSION", "Add gem to Gemfile and run bundle install" + long_desc <<-D + Adds the specified gem to Gemfile (if valid) and run 'bundle install' in one step. + D + method_option "version", :aliases => "-v", :type => :string + method_option "group", :aliases => "-g", :type => :string + method_option "source", :aliases => "-s", :type => :string + method_option "git", :type => :string + method_option "branch", :type => :string + method_option "skip-install", :type => :boolean, :banner => + "Adds gem to the Gemfile but does not install it" + method_option "optimistic", :type => :boolean, :banner => "Adds optimistic declaration of version to gem" + method_option "strict", :type => :boolean, :banner => "Adds strict declaration of version to gem" + def add(*gems) + require_relative "cli/add" + Add.new(options.dup, gems).run + end + + desc "outdated GEM [OPTIONS]", "List installed gems with newer versions available" + long_desc <<-D + Outdated lists the names and versions of gems that have a newer version available + in the given source. Calling outdated with [GEM [GEM]] will only check for newer + versions of the given gems. Prerelease gems are ignored by default. If your gems + are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1. + + For more information on patch level options (--major, --minor, --patch, + --update-strict) see documentation on the same options on the update command. + D + method_option "group", :type => :string, :banner => "List gems from a specific group" + method_option "groups", :type => :boolean, :banner => "List gems organized by groups" + method_option "local", :type => :boolean, :banner => + "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems" + method_option "source", :type => :array, :banner => "Check against a specific source" + strict_is_update = Bundler.feature_flag.forget_cli_options? + method_option "filter-strict", :type => :boolean, :aliases => strict_is_update ? [] : %w[--strict], :banner => + "Only list newer versions allowed by your Gemfile requirements" + method_option "update-strict", :type => :boolean, :aliases => strict_is_update ? %w[--strict] : [], :banner => + "Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)" + method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version" + method_option "filter-major", :type => :boolean, :banner => "Only list major newer versions" + method_option "filter-minor", :type => :boolean, :banner => "Only list minor newer versions" + method_option "filter-patch", :type => :boolean, :banner => "Only list patch newer versions" + method_option "parseable", :aliases => "--porcelain", :type => :boolean, :banner => + "Use minimal formatting for more parseable output" + method_option "only-explicit", :type => :boolean, :banner => + "Only list gems specified in your Gemfile, not their dependencies" + def outdated(*gems) + require_relative "cli/outdated" + Outdated.new(options, gems).run + end + + desc "fund [OPTIONS]", "Lists information about gems seeking funding assistance" + method_option "group", :aliases => "-g", :type => :array, :banner => + "Fetch funding information for a specific group" + def fund + require_relative "cli/fund" + Fund.new(options).run + end + + desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" + method_option "all", :type => :boolean, + :default => Bundler.feature_flag.cache_all?, + :banner => "Include all sources (including path and git)." + method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one" + method_option "cache-path", :type => :string, :banner => + "Specify a different cache path than the default (vendor/cache)." + method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" + method_option "no-install", :type => :boolean, :banner => "Don't install the gems, only update the cache." + method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." + method_option "path", :type => :string, :banner => + "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." + method_option "frozen", :type => :boolean, :banner => + "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install" + long_desc <<-D + The cache command will copy the .gem files for every gem in the bundle into the + directory ./vendor/cache. If you then check that directory into your source + control repository, others who check out your source will be able to install the + bundle without having to download any additional gems. + D + def cache + SharedHelpers.major_deprecation 2, + "The `--all` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no longer " \ + "do in future versions. Instead please use `bundle config set cache_all true`, " \ + "and stop using this flag" if ARGV.include?("--all") + + require_relative "cli/cache" + Cache.new(options).run + end + + map aliases_for("cache") + + desc "exec [OPTIONS]", "Run the command in context of the bundle" + method_option :keep_file_descriptors, :type => :boolean, :default => false + method_option :gemfile, :type => :string, :required => false + long_desc <<-D + Exec runs a command, providing it access to the gems in the bundle. While using + bundle exec you can require and call the bundled gems as if they were installed + into the system wide RubyGems repository. + D + def exec(*args) + require_relative "cli/exec" + Exec.new(options, args).run + end + + map aliases_for("exec") + + desc "config NAME [VALUE]", "Retrieve or set a configuration value" + long_desc <<-D + Retrieves or sets a configuration value. If only one parameter is provided, retrieve the value. If two parameters are provided, replace the + existing value with the newly provided one. + + By default, setting a configuration value sets it for all projects + on the machine. + + If a global setting is superseded by local configuration, this command + will show the current value, as well as any superseded values and + where they were specified. + D + require_relative "cli/config" + subcommand "config", Config + + desc "open GEM", "Opens the source directory of the given bundled gem" + def open(name) + require_relative "cli/open" + Open.new(options, name).run + end + + unless Bundler.feature_flag.bundler_3_mode? + desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" + def console(group = nil) + require_relative "cli/console" + Console.new(options, group).run + end + end + + desc "version", "Prints the bundler's version information" + def version + cli_help = current_command.name == "cli_help" + if cli_help || ARGV.include?("version") + build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + end + + if !cli_help && Bundler.feature_flag.print_only_version_number? + Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + else + Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + end + end + + map aliases_for("version") + + desc "licenses", "Prints the license of all gems in the bundle" + def licenses + Bundler.load.specs.sort_by {|s| s.license.to_s }.reverse_each do |s| + gem_name = s.name + license = s.license || s.licenses + + if license.empty? + Bundler.ui.warn "#{gem_name}: Unknown" + else + Bundler.ui.info "#{gem_name}: #{license}" + end + end + end + + unless Bundler.feature_flag.bundler_3_mode? + desc "viz [OPTIONS]", "Generates a visual dependency graph", :hide => true + long_desc <<-D + Viz generates a PNG file of the current Gemfile as a dependency graph. + Viz requires the ruby-graphviz gem (and its dependencies). + The associated gems must also be installed via 'bundle install'. + D + method_option :file, :type => :string, :default => "gem_graph", :aliases => "-f", :desc => "The name to use for the generated file. see format option" + method_option :format, :type => :string, :default => "png", :aliases => "-F", :desc => "This is output format option. Supported format is png, jpg, svg, dot ..." + method_option :requirements, :type => :boolean, :default => false, :aliases => "-R", :desc => "Set to show the version of each required dependency." + method_option :version, :type => :boolean, :default => false, :aliases => "-v", :desc => "Set to show each gem version." + method_option :without, :type => :array, :default => [], :aliases => "-W", :banner => "GROUP[ GROUP...]", :desc => "Exclude gems that are part of the specified named group." + def viz + SharedHelpers.major_deprecation 2, "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz" + require_relative "cli/viz" + Viz.new(options.dup).run + end + end + + old_gem = instance_method(:gem) + + desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem" + method_option :exe, :type => :boolean, :default => false, :aliases => ["--bin", "-b"], :desc => "Generate a binary executable for your library." + method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." + method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR", + :lazy_default => [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, + :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code" + method_option :git, :type => :boolean, :default => true, :desc => "Initialize a git repo inside your library." + method_option :mit, :type => :boolean, :desc => "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." + method_option :rubocop, :type => :boolean, :desc => "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`." + method_option :changelog, :type => :boolean, :desc => "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." + method_option :test, :type => :string, :lazy_default => Bundler.settings["gem.test"] || "", :aliases => "-t", :banner => "Use the specified test framework for your library", + :desc => "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." + method_option :ci, :type => :string, :lazy_default => Bundler.settings["gem.ci"] || "", + :desc => "Generate CI configuration, either GitHub Actions, Travis CI, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|travis|gitlab|circle)`" + method_option :github_username, :type => :string, :default => Bundler.settings["gem.github_username"], :banner => "Set your username on GitHub", :desc => "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username `." + + def gem(name) + end + + commands["gem"].tap do |gem_command| + def gem_command.run(instance, args = []) + arity = 1 # name + + require_relative "cli/gem" + cmd_args = args + [instance] + cmd_args.unshift(instance.options) + + cmd = begin + Gem.new(*cmd_args) + rescue ArgumentError => e + instance.class.handle_argument_error(self, e, args, arity) + end + + cmd.run + end + end + + undef_method(:gem) + define_method(:gem, old_gem) + private :gem + + def self.source_root + File.expand_path(File.join(File.dirname(__FILE__), "templates")) + end + + desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", :hide => true + method_option "dry-run", :type => :boolean, :default => false, :banner => + "Only print out changes, do not clean gems" + method_option "force", :type => :boolean, :default => false, :banner => + "Forces clean even if --path is not set" + def clean + require_relative "cli/clean" + Clean.new(options.dup).run + end + + desc "platform [OPTIONS]", "Displays platform compatibility information" + method_option "ruby", :type => :boolean, :default => false, :banner => + "only display ruby related platform information" + def platform + require_relative "cli/platform" + Platform.new(options).run + end + + desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", :hide => true + method_option "source", :type => :string, :banner => + "Install gem from the given source" + method_option "group", :type => :string, :banner => + "Install gem into a bundler group" + def inject(name, version) + SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command" + require_relative "cli/inject" + Inject.new(options.dup, name, version).run + end + + desc "lock", "Creates a lockfile without installing" + method_option "update", :type => :array, :lazy_default => true, :banner => + "ignore the existing lockfile, update all gems by default, or update list of given gems" + method_option "local", :type => :boolean, :default => false, :banner => + "do not attempt to fetch remote gemspecs and use the local gem cache only" + method_option "print", :type => :boolean, :default => false, :banner => + "print the lockfile to STDOUT instead of writing to the file system" + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "lockfile", :type => :string, :default => nil, :banner => + "the path the lockfile should be written to" + method_option "full-index", :type => :boolean, :default => false, :banner => + "Fall back to using the single-file index of all gems" + method_option "add-platform", :type => :array, :default => [], :banner => + "Add a new platform to the lockfile" + method_option "remove-platform", :type => :array, :default => [], :banner => + "Remove a platform from the lockfile" + method_option "patch", :type => :boolean, :banner => + "If updating, prefer updating only to next patch version" + method_option "minor", :type => :boolean, :banner => + "If updating, prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => + "If updating, prefer updating to next major version (default)" + method_option "strict", :type => :boolean, :banner => + "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", :type => :boolean, :banner => + "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" + def lock + require_relative "cli/lock" + Lock.new(options).run + end + + desc "env", "Print information about the environment Bundler is running under" + def env + Env.write($stdout) + end + + desc "doctor [OPTIONS]", "Checks the bundle for common problems" + long_desc <<-D + Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If + missing dependencies are detected, Bundler prints them and exits status 1. + Otherwise, Bundler prints a success message and exits with a status of 0. + D + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "quiet", :type => :boolean, :banner => + "Only output warnings and errors." + def doctor + require_relative "cli/doctor" + Doctor.new(options).run + end + + desc "issue", "Learn how to report an issue in Bundler" + def issue + require_relative "cli/issue" + Issue.new.run + end + + desc "pristine [GEMS...]", "Restores installed gems to pristine condition" + long_desc <<-D + Restores installed gems to pristine condition from files located in the + gem cache. Gems installed from a git repository will be issued `git + checkout --force`. + D + def pristine(*gems) + require_relative "cli/pristine" + Pristine.new(gems).run + end + + if Bundler.feature_flag.plugins? + require_relative "cli/plugin" + desc "plugin", "Manage the bundler plugins" + subcommand "plugin", Plugin + end + + # Reformat the arguments passed to bundle that include a --help flag + # into the corresponding `bundle help #{command}` call + def self.reformatted_help_args(args) + bundler_commands = (COMMAND_ALIASES.keys + COMMAND_ALIASES.values).flatten + + help_flags = %w[--help -h] + exec_commands = ["exec"] + COMMAND_ALIASES["exec"] + + help_used = args.index {|a| help_flags.include? a } + exec_used = args.index {|a| exec_commands.include? a } + + command = args.find {|a| bundler_commands.include? a } + command = all_aliases[command] if all_aliases[command] + + if exec_used && help_used + if exec_used + help_used == 1 + %w[help exec] + else + args + end + elsif help_used + args = args.dup + args.delete_at(help_used) + ["help", command || args].flatten.compact + else + args + end + end + + private + + # Automatically invoke `bundle install` and resume if + # Bundler.settings[:auto_install] exists. This is set through config cmd + # `bundle config set --global auto_install 1`. + # + # Note that this method `nil`s out the global Definition object, so it + # should be called first, before you instantiate anything like an + # `Installer` that'll keep a reference to the old one instead. + def auto_install + return unless Bundler.settings[:auto_install] + + begin + Bundler.definition.specs + rescue GemNotFound + Bundler.ui.info "Automatically installing missing gems." + Bundler.reset! + invoke :install, [] + Bundler.reset! + end + end + + def current_command + _, _, config = @_initializer + config[:current_command] + end + + def print_command + return unless Bundler.ui.debug? + cmd = current_command + command_name = cmd.name + return if PARSEABLE_COMMANDS.include?(command_name) + command = ["bundle", command_name] + args + options_to_print = options.dup + options_to_print.delete_if do |k, v| + next unless o = cmd.options[k] + o.default == v + end + command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip + command.reject!(&:empty?) + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + end + + def warn_on_outdated_bundler + return if Bundler.settings[:disable_version_check] + + command_name = current_command.name + return if PARSEABLE_COMMANDS.include?(command_name) + + return unless SharedHelpers.md5_available? + + latest = Fetcher::CompactIndex. + new(nil, Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")), nil). + send(:compact_index_client). + instance_variable_get(:@cache). + dependencies("bundler"). + map {|d| Gem::Version.new(d.first) }. + max + return unless latest + + current = Gem::Version.new(VERSION) + return if current >= latest + latest_installed = Bundler.rubygems.find_name("bundler").map(&:version).max + + installation = "To install the latest version, run `gem install bundler#{" --pre" if latest.prerelease?}`" + if latest_installed && latest_installed > current + suggestion = "To update to the most recent installed version (#{latest_installed}), run `bundle update --bundler`" + suggestion = "#{installation}\n#{suggestion}" if latest_installed < latest + else + suggestion = installation + end + + Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\n#{suggestion}" + rescue RuntimeError + nil + end + + def remembered_negative_flag_deprecation(name) + positive_name = name.gsub(/\Ano-/, "") + option = current_command.options[positive_name] + flag_name = "--no-" + option.switch_name.gsub(/\A--/, "") + + flag_deprecation(positive_name, flag_name, option) + end + + def remembered_flag_deprecation(name) + option = current_command.options[name] + flag_name = option.switch_name + + flag_deprecation(name, flag_name, option) + end + + def flag_deprecation(name, flag_name, option) + name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } + return unless name_index + + value = options[name] + value = value.join(" ").to_s if option.type == :array + + Bundler::SharedHelpers.major_deprecation 2, + "The `#{flag_name}` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no longer " \ + "do in future versions. Instead please use `bundle config set --local #{name.tr("-", "_")} " \ + "'#{value}'`, and stop using this flag" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/add.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/add.rb new file mode 100644 index 0000000000..5bcf30d82d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/add.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Add + attr_reader :gems, :options, :version + + def initialize(options, gems) + @gems = gems + @options = options + @options[:group] = options[:group].split(",").map(&:strip) unless options[:group].nil? + @version = options[:version].split(",").map(&:strip) unless options[:version].nil? + end + + def run + validate_options! + inject_dependencies + perform_bundle_install unless options["skip-install"] + end + + private + + def perform_bundle_install + Installer.install(Bundler.root, Bundler.definition) + Bundler.load.cache if Bundler.app_cache.exist? + end + + def inject_dependencies + dependencies = gems.map {|g| Bundler::Dependency.new(g, version, options) } + + Injector.inject(dependencies, + :conservative_versioning => options[:version].nil?, # Perform conservative versioning only when version is not specified + :optimistic => options[:optimistic], + :strict => options[:strict]) + end + + def validate_options! + raise InvalidOption, "You can not specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic] + + # raise error when no gems are specified + raise InvalidOption, "Please specify gems to add." if gems.empty? + + version.to_a.each do |v| + raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN =~ v.to_s + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/binstubs.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/binstubs.rb new file mode 100644 index 0000000000..639c01ff39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/binstubs.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Binstubs + attr_reader :options, :gems + def initialize(options, gems) + @options = options + @gems = gems + end + + def run + Bundler.definition.validate_runtime! + path_option = options["path"] + path_option = nil if path_option && path_option.empty? + Bundler.settings.set_command_option :bin, path_option if options["path"] + Bundler.settings.set_command_option_if_given :shebang, options["shebang"] + installer = Installer.new(Bundler.root, Bundler.definition) + + installer_opts = { + :force => options[:force], + :binstubs_cmd => true, + :all_platforms => options["all-platforms"], + } + + if options[:all] + raise InvalidOption, "Cannot specify --all with specific gems" unless gems.empty? + @gems = Bundler.definition.specs.map(&:name) + installer_opts.delete(:binstubs_cmd) + elsif gems.empty? + Bundler.ui.error "`bundle binstubs` needs at least one gem to run." + exit 1 + end + + gems.each do |gem_name| + spec = Bundler.definition.specs.find {|s| s.name == gem_name } + unless spec + raise GemNotFound, Bundler::CLI::Common.gem_not_found_message( + gem_name, Bundler.definition.specs + ) + end + + if options[:standalone] + next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler" + Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do + installer.generate_standalone_bundler_executable_stubs(spec, installer_opts) + end + else + installer.generate_bundler_executable_stubs(spec, installer_opts) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/cache.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/cache.rb new file mode 100644 index 0000000000..9cd6133879 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/cache.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Cache + attr_reader :options + + def initialize(options) + @options = options + end + + def run + Bundler.ui.level = "error" if options[:quiet] + Bundler.settings.set_command_option_if_given :path, options[:path] + Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"] + + setup_cache_all + install + + # TODO: move cache contents here now that all bundles are locked + custom_path = Bundler.settings[:path] if options[:path] + + Bundler.settings.temporary(:cache_all_platforms => options["all-platforms"]) do + Bundler.load.cache(custom_path) + end + end + + private + + def install + require_relative "install" + options = self.options.dup + options["local"] = false if Bundler.settings[:cache_all_platforms] + options["no-cache"] = true + Bundler::CLI::Install.new(options).run + end + + def setup_cache_all + all = options.fetch(:all, Bundler.feature_flag.cache_all? || nil) + + Bundler.settings.set_command_option_if_given :cache_all, all + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/check.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/check.rb new file mode 100644 index 0000000000..65c51337d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/check.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Check + attr_reader :options + + def initialize(options) + @options = options + end + + def run + Bundler.settings.set_command_option_if_given :path, options[:path] + + definition = Bundler.definition + definition.validate_runtime! + + begin + definition.resolve_only_locally! + not_installed = definition.missing_specs + rescue GemNotFound, VersionConflict + Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies." + Bundler.ui.warn "Install missing gems with `bundle install`." + exit 1 + end + + if not_installed.any? + Bundler.ui.error "The following gems are missing" + not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" } + Bundler.ui.warn "Install missing gems with `bundle install`" + exit 1 + elsif !Bundler.default_lockfile.file? && Bundler.frozen_bundle? + Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present" + exit 1 + else + Bundler.load.lock(:preserve_unknown_sections => true) unless options[:"dry-run"] + Bundler.ui.info "The Gemfile's dependencies are satisfied" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/clean.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/clean.rb new file mode 100644 index 0000000000..c6b0968e3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/clean.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Clean + attr_reader :options + + def initialize(options) + @options = options + end + + def run + require_path_or_force unless options[:"dry-run"] + Bundler.load.clean(options[:"dry-run"]) + end + + protected + + def require_path_or_force + return unless Bundler.use_system_gems? && !options[:force] + raise InvalidOption, "Cleaning all the gems on your system is dangerous! " \ + "If you're sure you want to remove every system gem not in this " \ + "bundle, run `bundle clean --force`." + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/common.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/common.rb new file mode 100644 index 0000000000..ba259143b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/common.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +module Bundler + module CLI::Common + def self.output_post_install_messages(messages) + return if Bundler.settings["ignore_messages"] + messages.to_a.each do |name, msg| + print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"] + end + end + + def self.print_post_install_message(name, msg) + Bundler.ui.confirm "Post-install message from #{name}:" + Bundler.ui.info msg + end + + def self.output_fund_metadata_summary + definition = Bundler.definition + current_dependencies = definition.requested_dependencies + current_specs = definition.specs + + count = current_dependencies.count {|dep| current_specs[dep.name].first.metadata.key?("funding_uri") } + + return if count.zero? + + intro = count > 1 ? "#{count} installed gems you directly depend on are" : "#{count} installed gem you directly depend on is" + message = "#{intro} looking for funding.\n Run `bundle fund` for details" + Bundler.ui.info message + end + + def self.output_without_groups_message(command) + return if Bundler.settings[:without].empty? + Bundler.ui.confirm without_groups_message(command) + end + + def self.without_groups_message(command) + command_in_past_tense = command == :install ? "installed" : "updated" + groups = Bundler.settings[:without] + "Gems in the #{verbalize_groups(groups)} were not #{command_in_past_tense}." + end + + def self.verbalize_groups(groups) + groups.map!{|g| "'#{g}'" } + group_list = [groups[0...-1].join(", "), groups[-1..-1]]. + reject {|s| s.to_s.empty? }.join(" and ") + group_str = groups.size == 1 ? "group" : "groups" + "#{group_str} #{group_list}" + end + + def self.select_spec(name, regex_match = nil) + specs = [] + regexp = Regexp.new(name) if regex_match + + Bundler.definition.specs.each do |spec| + return spec if spec.name == name + specs << spec if regexp && spec.name =~ regexp + end + + case specs.count + when 0 + dep_in_other_group = Bundler.definition.current_dependencies.find {|dep|dep.name == name } + + if dep_in_other_group + raise GemNotFound, "Could not find gem '#{name}', because it's in the #{verbalize_groups(dep_in_other_group.groups)}, configured to be ignored." + else + raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) + end + when 1 + specs.first + else + ask_for_spec_from(specs) + end + rescue RegexpError + raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) + end + + def self.ask_for_spec_from(specs) + specs.each_with_index do |spec, index| + Bundler.ui.info "#{index.succ} : #{spec.name}", true + end + Bundler.ui.info "0 : - exit -", true + + num = Bundler.ui.ask("> ").to_i + num > 0 ? specs[num - 1] : nil + end + + def self.gem_not_found_message(missing_gem_name, alternatives) + require_relative "../similarity_detector" + message = "Could not find gem '#{missing_gem_name}'." + alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } + suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name) + message += "\nDid you mean #{suggestions}?" if suggestions + message + end + + def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems) + return unless locked_gems + + locked_names = locked_gems.specs.map(&:name).uniq + names.-(locked_names).each do |g| + raise GemNotFound, gem_not_found_message(g, locked_names) + end + end + + def self.configure_gem_version_promoter(definition, options) + patch_level = patch_level_options(options) + patch_level << :patch if patch_level.empty? && Bundler.settings[:prefer_patch] + raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1 + + definition.gem_version_promoter.tap do |gvp| + gvp.level = patch_level.first || :major + gvp.strict = options[:strict] || options["update-strict"] || options["filter-strict"] + end + end + + def self.patch_level_options(options) + [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) } + end + + def self.clean_after_install? + clean = Bundler.settings[:clean] + return clean unless clean.nil? + clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil? + clean &&= !Bundler.use_system_gems? + clean + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/config.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/config.rb new file mode 100644 index 0000000000..8d2aba0916 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/config.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Config < Thor + class_option :parseable, :type => :boolean, :banner => "Use minimal formatting for more parseable output" + + def self.scope_options + method_option :global, :type => :boolean, :banner => "Only change the global config" + method_option :local, :type => :boolean, :banner => "Only change the local config" + end + private_class_method :scope_options + + desc "base NAME [VALUE]", "The Bundler 1 config interface", :hide => true + scope_options + method_option :delete, :type => :boolean, :banner => "delete" + def base(name = nil, *value) + new_args = + if ARGV.size == 1 + ["config", "list"] + elsif ARGV.include?("--delete") + ARGV.map {|arg| arg == "--delete" ? "unset" : arg } + elsif ARGV.include?("--global") || ARGV.include?("--local") || ARGV.size == 3 + ["config", "set", *ARGV[1..-1]] + else + ["config", "get", ARGV[1]] + end + + SharedHelpers.major_deprecation 3, + "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." + + Base.new(options, name, value, self).run + end + + desc "list", "List out all configured settings" + def list + Base.new(options, nil, nil, self).run + end + + desc "get NAME", "Returns the value for the given key" + def get(name) + Base.new(options, name, nil, self).run + end + + desc "set NAME VALUE", "Sets the given value for the given key" + scope_options + def set(name, value, *value_) + Base.new(options, name, value_.unshift(value), self).run + end + + desc "unset NAME", "Unsets the value for the given key" + scope_options + def unset(name) + options[:delete] = true + Base.new(options, name, nil, self).run + end + + default_task :base + + class Base + attr_reader :name, :value, :options, :scope, :thor + + def initialize(options, name, value, thor) + @options = options + @name = name + value = Array(value) + @value = value.empty? ? nil : value.join(" ") + @thor = thor + validate_scope! + end + + def run + unless name + warn_unused_scope "Ignoring --#{scope}" + confirm_all + return + end + + if options[:delete] + if !explicit_scope? || scope != "global" + Bundler.settings.set_local(name, nil) + end + if !explicit_scope? || scope != "local" + Bundler.settings.set_global(name, nil) + end + return + end + + if value.nil? + warn_unused_scope "Ignoring --#{scope} since no value to set was given" + + if options[:parseable] + if value = Bundler.settings[name] + Bundler.ui.info("#{name}=#{value}") + end + return + end + + confirm(name) + return + end + + Bundler.ui.info(message) if message + Bundler.settings.send("set_#{scope}", name, new_value) + end + + def confirm_all + if @options[:parseable] + thor.with_padding do + Bundler.settings.all.each do |setting| + val = Bundler.settings[setting] + Bundler.ui.info "#{setting}=#{val}" + end + end + else + Bundler.ui.confirm "Settings are listed in order of priority. The top value will be used.\n" + Bundler.settings.all.each do |setting| + Bundler.ui.confirm setting + show_pretty_values_for(setting) + Bundler.ui.confirm "" + end + end + end + + def confirm(name) + Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used" + show_pretty_values_for(name) + end + + def new_value + pathname = Pathname.new(value) + if name.start_with?("local.") && pathname.directory? + pathname.expand_path.to_s + else + value + end + end + + def message + locations = Bundler.settings.locations(name) + if @options[:parseable] + "#{name}=#{new_value}" if new_value + elsif scope == "global" + if !locations[:local].nil? + "Your application has set #{name} to #{locations[:local].inspect}. " \ + "This will override the global value you are currently setting" + elsif locations[:env] + "You have a bundler environment variable for #{name} set to " \ + "#{locations[:env].inspect}. This will take precedence over the global value you are setting" + elsif !locations[:global].nil? && locations[:global] != value + "You are replacing the current global value of #{name}, which is currently " \ + "#{locations[:global].inspect}" + end + elsif scope == "local" && !locations[:local].nil? && locations[:local] != value + "You are replacing the current local value of #{name}, which is currently " \ + "#{locations[:local].inspect}" + end + end + + def show_pretty_values_for(setting) + thor.with_padding do + Bundler.settings.pretty_values_for(setting).each do |line| + Bundler.ui.info line + end + end + end + + def explicit_scope? + @explicit_scope + end + + def warn_unused_scope(msg) + return unless explicit_scope? + return if options[:parseable] + + Bundler.ui.warn(msg) + end + + def validate_scope! + @explicit_scope = true + scopes = %w[global local].select {|s| options[s] } + case scopes.size + when 0 + @scope = "global" + @explicit_scope = false + when 1 + @scope = scopes.first + else + raise InvalidOption, + "The options #{scopes.join " and "} were specified. Please only use one of the switches at a time." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/console.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/console.rb new file mode 100644 index 0000000000..97b8dc0663 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/console.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Console + attr_reader :options, :group + def initialize(options, group) + @options = options + @group = group + end + + def run + Bundler::SharedHelpers.major_deprecation 2, "bundle console will be replaced " \ + "by `bin/console` generated by `bundle gem `" + + group ? Bundler.require(:default, *group.split(" ").map!(&:to_sym)) : Bundler.require + ARGV.clear + + console = get_console(Bundler.settings[:console] || "irb") + console.start + end + + def get_console(name) + require name + get_constant(name) + rescue LoadError + Bundler.ui.error "Couldn't load console #{name}, falling back to irb" + require "irb" + get_constant("irb") + end + + def get_constant(name) + const_name = { + "pry" => :Pry, + "ripl" => :Ripl, + "irb" => :IRB, + }[name] + Object.const_get(const_name) + rescue NameError + Bundler.ui.error "Could not find constant #{const_name}" + exit 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/doctor.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/doctor.rb new file mode 100644 index 0000000000..959b1b5e04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/doctor.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Bundler + class CLI::Doctor + DARWIN_REGEX = /\s+(.+) \(compatibility /.freeze + LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/.freeze + + attr_reader :options + + def initialize(options) + @options = options + end + + def otool_available? + Bundler.which("otool") + end + + def ldd_available? + Bundler.which("ldd") + end + + def dylibs_darwin(path) + output = `/usr/bin/otool -L "#{path}"`.chomp + dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq + # ignore @rpath and friends + dylibs.reject {|dylib| dylib.start_with? "@" } + end + + def dylibs_ldd(path) + output = `/usr/bin/ldd "#{path}"`.chomp + output.split("\n").map do |l| + match = l.match(LDD_REGEX) + next if match.nil? + match.captures[0] + end.compact + end + + def dylibs(path) + case RbConfig::CONFIG["host_os"] + when /darwin/ + return [] unless otool_available? + dylibs_darwin(path) + when /(linux|solaris|bsd)/ + return [] unless ldd_available? + dylibs_ldd(path) + else # Windows, etc. + Bundler.ui.warn("Dynamic library check not supported on this platform.") + [] + end + end + + def bundles_for_gem(spec) + Dir.glob("#{spec.full_gem_path}/**/*.bundle") + end + + def check! + require_relative "check" + Bundler::CLI::Check.new({}).run + end + + def run + Bundler.ui.level = "error" if options[:quiet] + Bundler.settings.validate! + check! + + definition = Bundler.definition + broken_links = {} + + definition.specs.each do |spec| + bundles_for_gem(spec).each do |bundle| + bad_paths = dylibs(bundle).select {|f| !File.exist?(f) } + if bad_paths.any? + broken_links[spec] ||= [] + broken_links[spec].concat(bad_paths) + end + end + end + + permissions_valid = check_home_permissions + + if broken_links.any? + message = "The following gems are missing OS dependencies:" + broken_links.map do |spec, paths| + paths.uniq.map do |path| + "\n * #{spec.name}: #{path}" + end + end.flatten.sort.each {|m| message += m } + raise ProductionError, message + elsif !permissions_valid + Bundler.ui.info "No issues found with the installed bundle" + end + end + + private + + def check_home_permissions + require "find" + files_not_readable_or_writable = [] + files_not_rw_and_owned_by_different_user = [] + files_not_owned_by_current_user_but_still_rw = [] + broken_symlinks = [] + Find.find(Bundler.bundle_path.to_s).each do |f| + if !File.exist?(f) + broken_symlinks << f + elsif !File.writable?(f) || !File.readable?(f) + if File.stat(f).uid != Process.uid + files_not_rw_and_owned_by_different_user << f + else + files_not_readable_or_writable << f + end + elsif File.stat(f).uid != Process.uid + files_not_owned_by_current_user_but_still_rw << f + end + end + + ok = true + + if broken_symlinks.any? + Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}" + + ok = false + end + + if files_not_owned_by_current_user_but_still_rw.any? + Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ + "user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}" + + ok = false + end + + if files_not_rw_and_owned_by_different_user.any? + Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ + "user, and are not readable/writable. These files are:\n - #{files_not_rw_and_owned_by_different_user.join("\n - ")}" + + ok = false + end + + if files_not_readable_or_writable.any? + Bundler.ui.warn "Files exist in the Bundler home that are not " \ + "readable/writable by the current user. These files are:\n - #{files_not_readable_or_writable.join("\n - ")}" + + ok = false + end + + ok + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/exec.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/exec.rb new file mode 100644 index 0000000000..318d57fb06 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/exec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require_relative "../current_ruby" + +module Bundler + class CLI::Exec + attr_reader :options, :args, :cmd + + TRAPPED_SIGNALS = %w[INT].freeze + + def initialize(options, args) + @options = options + @cmd = args.shift + @args = args + + if !Bundler.current_ruby.jruby? + @args << { :close_others => !options.keep_file_descriptors? } + elsif options.keep_file_descriptors? + Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec." + end + end + + def run + validate_cmd! + SharedHelpers.set_bundle_environment + if bin_path = Bundler.which(cmd) + if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path) + return kernel_load(bin_path, *args) + end + kernel_exec(bin_path, *args) + else + # exec using the given command + kernel_exec(cmd, *args) + end + end + + private + + def validate_cmd! + return unless cmd.nil? + Bundler.ui.error "bundler: exec needs a command to run" + exit 128 + end + + def kernel_exec(*args) + Kernel.exec(*args) + rescue Errno::EACCES, Errno::ENOEXEC + Bundler.ui.error "bundler: not executable: #{cmd}" + exit 126 + rescue Errno::ENOENT + Bundler.ui.error "bundler: command not found: #{cmd}" + Bundler.ui.warn "Install missing gem executables with `bundle install`" + exit 127 + end + + def kernel_load(file, *args) + args.pop if args.last.is_a?(Hash) + ARGV.replace(args) + $0 = file + Process.setproctitle(process_title(file, args)) if Process.respond_to?(:setproctitle) + require_relative "../setup" + TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") } + Kernel.load(file) + rescue SystemExit, SignalException + raise + rescue Exception # rubocop:disable Lint/RescueException + Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})" + Bundler::FriendlyErrors.disable! + raise + end + + def process_title(file, args) + "#{file} #{args.join(" ")}".strip + end + + def ruby_shebang?(file) + possibilities = [ + "#!/usr/bin/env ruby\n", + "#!/usr/bin/env jruby\n", + "#!/usr/bin/env truffleruby\n", + "#!#{Gem.ruby}\n", + ] + + if File.zero?(file) + Bundler.ui.warn "#{file} is empty" + return false + end + + first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) } + possibilities.any? {|shebang| first_line.start_with?(shebang) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/fund.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/fund.rb new file mode 100644 index 0000000000..52db5aef68 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/fund.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Fund + attr_reader :options + + def initialize(options) + @options = options + end + + def run + Bundler.definition.validate_runtime! + + groups = Array(options[:group]).map(&:to_sym) + + deps = if groups.any? + Bundler.definition.dependencies_for(groups) + else + Bundler.definition.current_dependencies + end + + fund_info = deps.each_with_object([]) do |dep, arr| + spec = Bundler.definition.specs[dep.name].first + if spec.metadata.key?("funding_uri") + arr << "* #{spec.name} (#{spec.version})\n Funding: #{spec.metadata["funding_uri"]}" + end + end + + if fund_info.empty? + Bundler.ui.info "None of the installed gems you directly depend on are looking for funding." + else + Bundler.ui.info fund_info.join("\n") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/gem.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/gem.rb new file mode 100644 index 0000000000..eb1119859c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/gem.rb @@ -0,0 +1,353 @@ +# frozen_string_literal: true + +require "pathname" + +module Bundler + class CLI + Bundler.require_thor_actions + include Thor::Actions + end + + class CLI::Gem + TEST_FRAMEWORK_VERSIONS = { + "rspec" => "3.0", + "minitest" => "5.0", + "test-unit" => "3.0", + }.freeze + + attr_reader :options, :gem_name, :thor, :name, :target + + def initialize(options, gem_name, thor) + @options = options + @gem_name = resolve_name(gem_name) + + @thor = thor + thor.behavior = :invoke + thor.destination_root = nil + + @name = @gem_name + @target = SharedHelpers.pwd.join(gem_name) + + validate_ext_name if options[:ext] + end + + def run + Bundler.ui.confirm "Creating gem '#{name}'..." + + underscored_name = name.tr("-", "_") + namespaced_path = name.tr("-", "/") + constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase } + constant_array = constant_name.split("::") + + use_git = Bundler.git_present? && options[:git] + + git_author_name = use_git ? `git config user.name`.chomp : "" + git_username = use_git ? `git config github.user`.chomp : "" + git_user_email = use_git ? `git config user.email`.chomp : "" + + github_username = if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] + end + + config = { + :name => name, + :underscored_name => underscored_name, + :namespaced_path => namespaced_path, + :makefile_path => "#{underscored_name}/#{underscored_name}", + :constant_name => constant_name, + :constant_array => constant_array, + :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name, + :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, + :test => options[:test], + :ext => options[:ext], + :exe => options[:exe], + :bundler_version => bundler_dependency_version, + :git => use_git, + :github_username => github_username.empty? ? "[USERNAME]" : github_username, + :required_ruby_version => Gem.ruby_version < Gem::Version.new("2.4.a") ? "2.3.0" : "2.4.0", + } + ensure_safe_gem_name(name, constant_array) + + templates = { + "#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name, + "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb", + "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb", + "newgem.gemspec.tt" => "#{name}.gemspec", + "Rakefile.tt" => "Rakefile", + "README.md.tt" => "README.md", + "bin/console.tt" => "bin/console", + "bin/setup.tt" => "bin/setup", + } + + executables = %w[ + bin/console + bin/setup + ] + + templates.merge!("gitignore.tt" => ".gitignore") if use_git + + if test_framework = ask_and_set_test_framework + config[:test] = test_framework + config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework] + + case test_framework + when "rspec" + templates.merge!( + "rspec.tt" => ".rspec", + "spec/spec_helper.rb.tt" => "spec/spec_helper.rb", + "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" + ) + config[:test_task] = :spec + when "minitest" + templates.merge!( + "test/minitest/test_helper.rb.tt" => "test/test_helper.rb", + "test/minitest/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb" + ) + config[:test_task] = :test + when "test-unit" + templates.merge!( + "test/test-unit/test_helper.rb.tt" => "test/test_helper.rb", + "test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb" + ) + config[:test_task] = :test + end + end + + config[:ci] = ask_and_set_ci + case config[:ci] + when "github" + templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") + when "travis" + templates.merge!("travis.yml.tt" => ".travis.yml") + when "gitlab" + templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") + when "circle" + templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml") + end + + if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?", + "This means that any other developer or company will be legally allowed to use your code " \ + "for free as long as they admit you created it. You can read more about the MIT license " \ + "at https://choosealicense.com/licenses/mit.") + config[:mit] = true + Bundler.ui.info "MIT License enabled in config" + templates.merge!("LICENSE.txt.tt" => "LICENSE.txt") + end + + if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?", + "Codes of conduct can increase contributions to your project by contributors who " \ + "prefer collaborative, safe spaces. You can read more about the code of conduct at " \ + "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \ + "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \ + "address is specified as a contact in the generated code of conduct so that people know " \ + "who to contact in case of a violation. For suggestions about " \ + "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.") + config[:coc] = true + Bundler.ui.info "Code of conduct enabled in config" + templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md") + end + + if ask_and_set(:changelog, "Do you want to include a changelog?", + "A changelog is a file which contains a curated, chronologically ordered list of notable " \ + "changes for each version of a project. To make it easier for users and contributors to" \ + " see precisely what notable changes have been made between each release (or version) of" \ + " the project. Whether consumers or developers, the end users of software are" \ + " human beings who care about what's in the software. When the software changes, people " \ + "want to know why and how. see https://keepachangelog.com") + config[:changelog] = true + Bundler.ui.info "Changelog enabled in config" + templates.merge!("CHANGELOG.md.tt" => "CHANGELOG.md") + end + + if ask_and_set(:rubocop, "Do you want to add rubocop as a dependency for gems you generate?", + "RuboCop is a static code analyzer that has out-of-the-box rules for many " \ + "of the guidelines in the community style guide. " \ + "For more information, see the RuboCop docs (https://docs.rubocop.org/en/stable/) " \ + "and the Ruby Style Guides (https://github.com/rubocop-hq/ruby-style-guide).") + config[:rubocop] = true + config[:rubocop_version] = Gem.ruby_version < Gem::Version.new("2.4.a") ? "0.81.0" : "1.7" + Bundler.ui.info "RuboCop enabled in config" + templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + end + + templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe] + + if options[:ext] + templates.merge!( + "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c" + ) + end + + if File.exist?(target) && !File.directory?(target) + Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." + exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] + end + + if use_git + Bundler.ui.info "Initializing git repo in #{target}" + `git init #{target}` + + config[:git_default_branch] = File.read("#{target}/.git/HEAD").split("/").last.chomp + end + + templates.each do |src, dst| + destination = target.join(dst) + thor.template("newgem/#{src}", destination, config) + end + + executables.each do |file| + path = target.join(file) + executable = (path.stat.mode | 0o111) + path.chmod(executable) + end + + if use_git + Dir.chdir(target) do + `git add .` + end + end + + # Open gemspec in editor + open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit] + + Bundler.ui.info "Gem '#{name}' was successfully created. " \ + "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" + end + + private + + def resolve_name(name) + SharedHelpers.pwd.join(name).basename.to_s + end + + def ask_and_set(key, header, message) + choice = options[key] + choice = Bundler.settings["gem.#{key}"] if choice.nil? + + if choice.nil? + Bundler.ui.confirm header + choice = Bundler.ui.yes? "#{message} y/(n):" + Bundler.settings.set_global("gem.#{key}", choice) + end + + choice + end + + def validate_ext_name + return unless gem_name.index("-") + + Bundler.ui.error "You have specified a gem name which does not conform to the \n" \ + "naming guidelines for C extensions. For more information, \n" \ + "see the 'Extension Naming' section at the following URL:\n" \ + "https://guides.rubygems.org/gems-with-extensions/\n" + exit 1 + end + + def ask_and_set_test_framework + test_framework = options[:test] || Bundler.settings["gem.test"] + + if test_framework.to_s.empty? + Bundler.ui.confirm "Do you want to generate tests with your gem?" + Bundler.ui.info hint_text("test") + + result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):" + if result =~ /rspec|minitest|test-unit/ + test_framework = result + else + test_framework = false + end + end + + if Bundler.settings["gem.test"].nil? + Bundler.settings.set_global("gem.test", test_framework) + end + + if options[:test] == Bundler.settings["gem.test"] + Bundler.ui.info "#{options[:test]} is already configured, ignoring --test flag." + end + + test_framework + end + + def hint_text(setting) + if Bundler.settings["gem.#{setting}"] == false + "Your choice will only be applied to this gem." + else + "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.#{setting}`." + end + end + + def ask_and_set_ci + ci_template = options[:ci] || Bundler.settings["gem.ci"] + + if ci_template.to_s.empty? + Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \ + "Supported services:\n" \ + "* CircleCI: https://circleci.com/\n" \ + "* GitHub Actions: https://github.com/features/actions\n" \ + "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \ + "* Travis CI: https://travis-ci.org/\n" \ + "\n" + Bundler.ui.info hint_text("ci") + + result = Bundler.ui.ask "Enter a CI service. github/travis/gitlab/circle/(none):" + if result =~ /github|travis|gitlab|circle/ + ci_template = result + else + ci_template = false + end + end + + if Bundler.settings["gem.ci"].nil? + Bundler.settings.set_global("gem.ci", ci_template) + end + + if options[:ci] == Bundler.settings["gem.ci"] + Bundler.ui.info "#{options[:ci]} is already configured, ignoring --ci flag." + end + + ci_template + end + + def bundler_dependency_version + v = Gem::Version.new(Bundler::VERSION) + req = v.segments[0..1] + req << "a" if v.prerelease? + req.join(".") + end + + def ensure_safe_gem_name(name, constant_array) + if name =~ /^\d/ + Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers." + exit 1 + end + + constant_name = constant_array.join("::") + + existing_constant = constant_array.inject(Object) do |c, s| + defined = begin + c.const_defined?(s) + rescue NameError + Bundler.ui.error "Invalid gem name #{name} -- `#{constant_name}` is an invalid constant name" + exit 1 + end + (defined && c.const_get(s)) || break + end + + return unless existing_constant + Bundler.ui.error "Invalid gem name #{name} constant #{constant_name} is already in use. Please choose another gem name." + exit 1 + end + + def open_editor(editor, file) + thor.run(%(#{editor} "#{file}")) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/info.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/info.rb new file mode 100644 index 0000000000..3111b64a33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/info.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Info + attr_reader :gem_name, :options + def initialize(options, gem_name) + @options = options + @gem_name = gem_name + end + + def run + Bundler.ui.silence do + Bundler.definition.validate_runtime! + Bundler.load.lock + end + + spec = spec_for_gem(gem_name) + + if spec + return print_gem_path(spec) if @options[:path] + print_gem_info(spec) + end + end + + private + + def spec_for_gem(gem_name) + spec = Bundler.definition.specs.find {|s| s.name == gem_name } + spec || default_gem_spec(gem_name) || Bundler::CLI::Common.select_spec(gem_name, :regex_match) + end + + def default_gem_spec(gem_name) + return unless Gem::Specification.respond_to?(:find_all_by_name) + gem_spec = Gem::Specification.find_all_by_name(gem_name).last + return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem? + end + + def spec_not_found(gem_name) + raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies) + end + + def print_gem_path(spec) + if spec.name == "bundler" + path = File.expand_path("../../../..", __FILE__) + else + path = spec.full_gem_path + unless File.directory?(path) + return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}" + end + end + + Bundler.ui.info path + end + + def print_gem_info(spec) + metadata = spec.metadata + gem_info = String.new + gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n" + gem_info << "\tSummary: #{spec.summary}\n" if spec.summary + gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage + gem_info << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri") + gem_info << "\tSource Code: #{metadata["source_code_uri"]}\n" if metadata.key?("source_code_uri") + gem_info << "\tFunding: #{metadata["funding_uri"]}\n" if metadata.key?("funding_uri") + gem_info << "\tWiki: #{metadata["wiki_uri"]}\n" if metadata.key?("wiki_uri") + gem_info << "\tChangelog: #{metadata["changelog_uri"]}\n" if metadata.key?("changelog_uri") + gem_info << "\tBug Tracker: #{metadata["bug_tracker_uri"]}\n" if metadata.key?("bug_tracker_uri") + gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri") + gem_info << "\tPath: #{spec.full_gem_path}\n" + gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem? + Bundler.ui.info gem_info + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/init.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/init.rb new file mode 100644 index 0000000000..d851d02d42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/init.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Init + attr_reader :options + def initialize(options) + @options = options + end + + def run + if File.exist?(gemfile) + Bundler.ui.error "#{gemfile} already exists at #{File.expand_path(gemfile)}" + exit 1 + end + + unless File.writable?(Dir.pwd) + Bundler.ui.error "Can not create #{gemfile} as the current directory is not writable." + exit 1 + end + + if options[:gemspec] + gemspec = File.expand_path(options[:gemspec]) + unless File.exist?(gemspec) + Bundler.ui.error "Gem specification #{gemspec} doesn't exist" + exit 1 + end + + spec = Bundler.load_gemspec_uncached(gemspec) + + File.open(gemfile, "wb") do |file| + file << "# Generated from #{gemspec}\n" + file << spec.to_gemfile + end + else + FileUtils.cp(File.expand_path("../../templates/#{gemfile}", __FILE__), gemfile) + end + + puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}" + end + + private + + def gemfile + @gemfile ||= Bundler.preferred_gemfile_name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/inject.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/inject.rb new file mode 100644 index 0000000000..8093a85283 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/inject.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Inject + attr_reader :options, :name, :version, :group, :source, :gems + def initialize(options, name, version) + @options = options + @name = name + @version = version || last_version_number + @group = options[:group].split(",") unless options[:group].nil? + @source = options[:source] + @gems = [] + end + + def run + # The required arguments allow Thor to give useful feedback when the arguments + # are incorrect. This adds those first two arguments onto the list as a whole. + gems.unshift(source).unshift(group).unshift(version).unshift(name) + + # Build an array of Dependency objects out of the arguments + deps = [] + # when `inject` support addition of more than one gem, then this loop will + # help. Currently this loop is running once. + gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source| + ops = Gem::Requirement::OPS.map {|key, _val| key } + has_op = ops.any? {|op| gem_version.start_with? op } + gem_version = "~> #{gem_version}" unless has_op + deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source) + end + + added = Injector.inject(deps, options) + + if added.any? + Bundler.ui.confirm "Added to Gemfile:" + Bundler.ui.confirm(added.map do |d| + name = "'#{d.name}'" + requirement = ", '#{d.requirement}'" + group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default) + source = ", :source => '#{d.source}'" unless d.source.nil? + %(gem #{name}#{requirement}#{group}#{source}) + end.join("\n")) + else + Bundler.ui.confirm "All gems were already present in the Gemfile" + end + end + + private + + def last_version_number + definition = Bundler.definition(true) + definition.resolve_remotely! + specs = definition.index[name].sort_by(&:version) + unless options[:pre] + specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } + end + spec = specs.last + spec.version.to_s + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/install.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/install.rb new file mode 100644 index 0000000000..5e39e2a36d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/install.rb @@ -0,0 +1,220 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Install + attr_reader :options + def initialize(options) + @options = options + end + + def run + Bundler.ui.level = "error" if options[:quiet] + + warn_if_root + + Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Bundler::FREEBSD + + # Disable color in deployment mode + Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment] + + check_for_options_conflicts + + check_trust_policy + + if options[:deployment] || options[:frozen] || Bundler.frozen_bundle? + unless Bundler.default_lockfile.exist? + flag = "--deployment flag" if options[:deployment] + flag ||= "--frozen flag" if options[:frozen] + flag ||= "deployment setting" + raise ProductionError, "The #{flag} requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \ + "sure you have checked your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} into version control " \ + "before deploying." + end + + options[:local] = true if Bundler.app_cache.exist? + + Bundler.settings.set_command_option :deployment, true if options[:deployment] + Bundler.settings.set_command_option :frozen, true if options[:frozen] + end + + # When install is called with --no-deployment, disable deployment mode + if options[:deployment] == false + Bundler.settings.set_command_option :frozen, nil + options[:system] = true + end + + normalize_settings + + Bundler::Fetcher.disable_endpoint = options["full-index"] + + if options["binstubs"] + Bundler::SharedHelpers.major_deprecation 2, + "The --binstubs option will be removed in favor of `bundle binstubs --all`" + end + + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + + definition = Bundler.definition + definition.validate_runtime! + + installer = Installer.install(Bundler.root, definition, options) + + Bundler.settings.temporary(:cache_all_platforms => options[:local] ? false : Bundler.settings[:cache_all_platforms]) do + Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle? + end + + Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}." + Bundler::CLI::Common.output_without_groups_message(:install) + + if Bundler.use_system_gems? + Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed." + else + relative_path = Bundler.configured_bundle_path.base_path_relative_to_pwd + Bundler.ui.confirm "Bundled gems are installed into `#{relative_path}`" + end + + Bundler::CLI::Common.output_post_install_messages installer.post_install_messages + + warn_ambiguous_gems + + if CLI::Common.clean_after_install? + require_relative "clean" + Bundler::CLI::Clean.new(options).run + end + + Bundler::CLI::Common.output_fund_metadata_summary + rescue GemNotFound, VersionConflict => e + if options[:local] && Bundler.app_cache.exist? + Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory." + end + + unless Bundler.definition.has_rubygems_remotes? + Bundler.ui.warn <<-WARN, :wrap => true + Your Gemfile has no gem server sources. If you need gems that are \ + not already on your machine, add a line like this to your Gemfile: + source 'https://rubygems.org' + WARN + end + raise e + rescue Gem::InvalidSpecificationException => e + Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed." + raise e + end + + private + + def warn_if_root + return if Bundler.settings[:silence_root_warning] || Gem.win_platform? || !Process.uid.zero? + Bundler.ui.warn "Don't run Bundler as root. Bundler can ask for sudo " \ + "if it is needed, and installing your bundle as root will break this " \ + "application for all non-root users on this machine.", :wrap => true + end + + def dependencies_count_for(definition) + count = definition.dependencies.count + "#{count} Gemfile #{count == 1 ? "dependency" : "dependencies"}" + end + + def gems_installed_for(definition) + count = definition.specs.count + "#{count} #{count == 1 ? "gem" : "gems"} now installed" + end + + def check_for_group_conflicts_in_cli_options + conflicting_groups = Array(options[:without]) & Array(options[:with]) + return if conflicting_groups.empty? + raise InvalidOption, "You can't list a group in both with and without." \ + " The offending groups are: #{conflicting_groups.join(", ")}." + end + + def check_for_options_conflicts + if (options[:path] || options[:deployment]) && options[:system] + error_message = String.new + error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path] + error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment] + raise InvalidOption.new(error_message) + end + end + + def check_trust_policy + trust_policy = options["trust-policy"] + unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy) + raise InvalidOption, "RubyGems doesn't know about trust policy '#{trust_policy}'. " \ + "The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}." + end + Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy + end + + def normalize_groups + options[:with] &&= options[:with].join(":").tr(" ", ":").split(":") + options[:without] &&= options[:without].join(":").tr(" ", ":").split(":") + + check_for_group_conflicts_in_cli_options + + Bundler.settings.set_command_option :with, nil if options[:with] == [] + Bundler.settings.set_command_option :without, nil if options[:without] == [] + + with = options.fetch(:with, []) + with |= Bundler.settings[:with].map(&:to_s) + with -= options[:without] if options[:without] + + without = options.fetch(:without, []) + without |= Bundler.settings[:without].map(&:to_s) + without -= options[:with] if options[:with] + + options[:with] = with + options[:without] = without + + unless Bundler.settings[:without] == options[:without] && Bundler.settings[:with] == options[:with] + # need to nil them out first to get around validation for backwards compatibility + Bundler.settings.set_command_option :without, nil + Bundler.settings.set_command_option :with, nil + Bundler.settings.set_command_option :without, options[:without] - options[:with] + Bundler.settings.set_command_option :with, options[:with] + end + end + + def normalize_settings + Bundler.settings.set_command_option :path, nil if options[:system] + Bundler.settings.temporary(:path_relative_to_cwd => false) do + Bundler.settings.set_command_option :path, "vendor/bundle" if Bundler.settings[:deployment] && Bundler.settings[:path].nil? + end + Bundler.settings.set_command_option_if_given :path, options[:path] + Bundler.settings.temporary(:path_relative_to_cwd => false) do + Bundler.settings.set_command_option :path, "bundle" if options["standalone"] && Bundler.settings[:path].nil? + end + + bin_option = options["binstubs"] + bin_option = nil if bin_option && bin_option.empty? + Bundler.settings.set_command_option :bin, bin_option if options["binstubs"] + + Bundler.settings.set_command_option_if_given :shebang, options["shebang"] + + Bundler.settings.set_command_option_if_given :jobs, options["jobs"] + + Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"] + + Bundler.settings.set_command_option_if_given :no_install, options["no-install"] + + Bundler.settings.set_command_option_if_given :clean, options["clean"] + + normalize_groups + + options[:force] = options[:redownload] + end + + def warn_ambiguous_gems + # TODO: remove this when we drop Bundler 1.x support + Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris| + Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources." + Bundler.ui.warn "Installed from: #{installed_from_uri}" + Bundler.ui.warn "Also found in:" + also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" } + Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source." + Bundler.ui.warn "For example:" + Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'" + Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again." + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/issue.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/issue.rb new file mode 100644 index 0000000000..f4cd5ac4df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/issue.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Bundler + class CLI::Issue + def run + Bundler.ui.info <<-EOS.gsub(/^ {8}/, "") + Did you find an issue with Bundler? Before filing a new issue, + be sure to check out these resources: + + 1. Check out our troubleshooting guide for quick fixes to common issues: + https://github.com/rubygems/rubygems/blob/master/bundler/doc/TROUBLESHOOTING.md + + 2. Instructions for common Bundler uses can be found on the documentation + site: https://bundler.io/ + + 3. Information about each Bundler command can be found in the Bundler + man pages: https://bundler.io/man/bundle.1.html + + Hopefully the troubleshooting steps above resolved your problem! If things + still aren't working the way you expect them to, please let us know so + that we can diagnose and help fix the problem you're having. Please + view the Filing Issues guide for more information: + https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md + + EOS + + Bundler.ui.info Bundler::Env.report + + Bundler.ui.info "\n## Bundle Doctor" + doctor + end + + def doctor + require_relative "doctor" + Bundler::CLI::Doctor.new({}).run + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/list.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/list.rb new file mode 100644 index 0000000000..66abd32650 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/list.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Bundler + class CLI::List + def initialize(options) + @options = options + @without_group = options["without-group"].map(&:to_sym) + @only_group = options["only-group"].map(&:to_sym) + end + + def run + raise InvalidOption, "The `--only-group` and `--without-group` options cannot be used together" if @only_group.any? && @without_group.any? + + raise InvalidOption, "The `--name-only` and `--paths` options cannot be used together" if @options["name-only"] && @options[:paths] + + specs = if @only_group.any? || @without_group.any? + filtered_specs_by_groups + else + Bundler.load.specs + end.reject {|s| s.name == "bundler" }.sort_by(&:name) + + return Bundler.ui.info "No gems in the Gemfile" if specs.empty? + + return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"] + return specs.each {|s| Bundler.ui.info s.full_gem_path } if @options["paths"] + + Bundler.ui.info "Gems included by the bundle:" + + specs.each {|s| Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})" } + + Bundler.ui.info "Use `bundle info` to print more detailed information about a gem" + end + + private + + def verify_group_exists(groups) + (@without_group + @only_group).each do |group| + raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group) + end + end + + def filtered_specs_by_groups + definition = Bundler.definition + groups = definition.groups + + verify_group_exists(groups) + + show_groups = + if @without_group.any? + groups.reject {|g| @without_group.include?(g) } + elsif @only_group.any? + groups.select {|g| @only_group.include?(g) } + else + groups + end.map(&:to_sym) + + definition.specs_for(show_groups) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/lock.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/lock.rb new file mode 100644 index 0000000000..7d613a6644 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/lock.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Lock + attr_reader :options + + def initialize(options) + @options = options + end + + def run + unless Bundler.default_gemfile + Bundler.ui.error "Unable to find a Gemfile to lock" + exit 1 + end + + print = options[:print] + ui = Bundler.ui + Bundler.ui = UI::Silent.new if print + + Bundler::Fetcher.disable_endpoint = options["full-index"] + + update = options[:update] + conservative = options[:conservative] + + if update.is_a?(Array) # unlocking specific gems + Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update) + update = { :gems => update, :conservative => conservative } + elsif update + update = { :conservative => conservative } if conservative + end + definition = Bundler.definition(update) + + Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) if options[:update] + + options["remove-platform"].each do |platform| + definition.remove_platform(platform) + end + + options["add-platform"].each do |platform_string| + platform = Gem::Platform.new(platform_string) + if platform.to_s == "unknown" + Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \ + "and adding it will likely lead to resolution errors" + end + definition.add_platform(platform) + end + + if definition.platforms.empty? + raise InvalidOption, "Removing all platforms from the bundle is not allowed" + end + + definition.resolve_remotely! unless options[:local] + + if print + puts definition.to_lock + else + file = options[:lockfile] + file = file ? File.expand_path(file) : Bundler.default_lockfile + puts "Writing lockfile to #{file}" + definition.lock(file) + end + + Bundler.ui = ui + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/open.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/open.rb new file mode 100644 index 0000000000..df32e2f38b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/open.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "shellwords" + +module Bundler + class CLI::Open + attr_reader :options, :name + def initialize(options, name) + @options = options + @name = name + end + + def run + editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? } + return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor + return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match) + if spec.default_gem? + Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist." + else + path = spec.full_gem_path + Dir.chdir(path) do + command = Shellwords.split(editor) + [path] + Bundler.with_original_env do + system(*command) + end || Bundler.ui.info("Could not run '#{command.join(" ")}'") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/outdated.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/outdated.rb new file mode 100644 index 0000000000..d5183b060b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/outdated.rb @@ -0,0 +1,290 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Outdated + attr_reader :options, :gems, :options_include_groups, :filter_options_patch, :sources, :strict + attr_accessor :outdated_gems + + def initialize(options, gems) + @options = options + @gems = gems + @sources = Array(options[:source]) + + @filter_options_patch = options.keys & %w[filter-major filter-minor filter-patch] + + @outdated_gems = [] + + @options_include_groups = [:group, :groups].any? do |v| + options.keys.include?(v.to_s) + end + + # the patch level options imply strict is also true. It wouldn't make + # sense otherwise. + @strict = options["filter-strict"] || Bundler::CLI::Common.patch_level_options(options).any? + end + + def run + check_for_deployment_mode! + + gems.each do |gem_name| + Bundler::CLI::Common.select_spec(gem_name) + end + + Bundler.definition.validate_runtime! + current_specs = Bundler.ui.silence { Bundler.definition.resolve } + + current_dependencies = Bundler.ui.silence do + Bundler.load.dependencies.map {|dep| [dep.name, dep] }.to_h + end + + definition = if gems.empty? && sources.empty? + # We're doing a full update + Bundler.definition(true) + else + Bundler.definition(:gems => gems, :sources => sources) + end + + Bundler::CLI::Common.configure_gem_version_promoter( + Bundler.definition, + options + ) + + definition_resolution = proc do + options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely! + end + + if options[:parseable] + Bundler.ui.silence(&definition_resolution) + else + definition_resolution.call + end + + Bundler.ui.info "" + + # Loop through the current specs + gemfile_specs, dependency_specs = current_specs.partition do |spec| + current_dependencies.key? spec.name + end + + specs = if options["only-explicit"] + gemfile_specs + else + gemfile_specs + dependency_specs + end + + specs.sort_by(&:name).uniq(&:name).each do |current_spec| + next unless gems.empty? || gems.include?(current_spec.name) + + active_spec = retrieve_active_spec(definition, current_spec) + next unless active_spec + + next unless filter_options_patch.empty? || update_present_via_semver_portions(current_spec, active_spec, options) + + gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version) + next unless gem_outdated || (current_spec.git_version != active_spec.git_version) + + dependency = current_dependencies[current_spec.name] + groups = "" + if dependency && !options[:parseable] + groups = dependency.groups.join(", ") + end + + outdated_gems << { + :active_spec => active_spec, + :current_spec => current_spec, + :dependency => dependency, + :groups => groups, + } + end + + if outdated_gems.empty? + unless options[:parseable] + Bundler.ui.info(nothing_outdated_message) + end + else + if options_include_groups + relevant_outdated_gems = outdated_gems.group_by {|g| g[:groups] }.sort.flat_map do |groups, gems| + contains_group = groups.split(", ").include?(options[:group]) + next unless options[:groups] || contains_group + + gems + end.compact + + if options[:parseable] + relevant_outdated_gems.each do |gems| + print_gems(gems) + end + else + print_gems_table(relevant_outdated_gems) + end + elsif options[:parseable] + print_gems(outdated_gems) + else + print_gems_table(outdated_gems) + end + + exit 1 + end + end + + private + + def groups_text(group_text, groups) + "#{group_text}#{groups.split(",").size > 1 ? "s" : ""} \"#{groups}\"" + end + + def nothing_outdated_message + if filter_options_patch.any? + display = filter_options_patch.map do |o| + o.sub("filter-", "") + end.join(" or ") + + "No #{display} updates to display.\n" + else + "Bundle up to date!\n" + end + end + + def retrieve_active_spec(definition, current_spec) + active_spec = definition.resolve.find_by_name_and_platform(current_spec.name, current_spec.platform) + return unless active_spec + + return active_spec if strict + + active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 + active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } + end + active_specs.last + end + + def print_gems(gems_list) + gems_list.each do |gem| + print_gem( + gem[:current_spec], + gem[:active_spec], + gem[:dependency], + gem[:groups], + ) + end + end + + def print_gems_table(gems_list) + data = gems_list.map do |gem| + gem_column_for( + gem[:current_spec], + gem[:active_spec], + gem[:dependency], + gem[:groups], + ) + end + + print_indented([table_header] + data) + end + + def print_gem(current_spec, active_spec, dependency, groups) + spec_version = "#{active_spec.version}#{active_spec.git_version}" + spec_version += " (from #{active_spec.loaded_from})" if Bundler.ui.debug? && active_spec.loaded_from + current_version = "#{current_spec.version}#{current_spec.git_version}" + + if dependency && dependency.specific? + dependency_version = %(, requested #{dependency.requirement}) + end + + spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \ + "installed #{current_version}#{dependency_version})" + + output_message = if options[:parseable] + spec_outdated_info.to_s + elsif options_include_groups || groups.empty? + " * #{spec_outdated_info}" + else + " * #{spec_outdated_info} in #{groups_text("group", groups)}" + end + + Bundler.ui.info output_message.rstrip + end + + def gem_column_for(current_spec, active_spec, dependency, groups) + current_version = "#{current_spec.version}#{current_spec.git_version}" + spec_version = "#{active_spec.version}#{active_spec.git_version}" + dependency = dependency.requirement if dependency + + ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s] + ret_val << active_spec.loaded_from.to_s if Bundler.ui.debug? + ret_val + end + + def check_for_deployment_mode! + return unless Bundler.frozen_bundle? + suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any? + "bundle config unset frozen" + elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any? + "bundle config unset deployment" + end + raise ProductionError, "You are trying to check outdated gems in " \ + "deployment mode. Run `bundle outdated` elsewhere.\n" \ + "\nIf this is a development machine, remove the " \ + "#{Bundler.default_gemfile} freeze" \ + "\nby running `#{suggested_command}`." + end + + def update_present_via_semver_portions(current_spec, active_spec, options) + current_major = current_spec.version.segments.first + active_major = active_spec.version.segments.first + + update_present = false + update_present = active_major > current_major if options["filter-major"] + + if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major + current_minor = get_version_semver_portion_value(current_spec, 1) + active_minor = get_version_semver_portion_value(active_spec, 1) + + update_present = active_minor > current_minor if options["filter-minor"] + + if !update_present && options["filter-patch"] && current_minor == active_minor + current_patch = get_version_semver_portion_value(current_spec, 2) + active_patch = get_version_semver_portion_value(active_spec, 2) + + update_present = active_patch > current_patch + end + end + + update_present + end + + def get_version_semver_portion_value(spec, version_portion_index) + version_section = spec.version.segments[version_portion_index, 1] + version_section.to_a[0].to_i + end + + def print_indented(matrix) + header = matrix[0] + data = matrix[1..-1] + + column_sizes = Array.new(header.size) do |index| + matrix.max_by {|row| row[index].length }[index].length + end + + Bundler.ui.info justify(header, column_sizes) + + data.sort_by! {|row| row[0] } + + data.each do |row| + Bundler.ui.info justify(row, column_sizes) + end + end + + def table_header + header = ["Gem", "Current", "Latest", "Requested", "Groups"] + header << "Path" if Bundler.ui.debug? + header + end + + def justify(row, sizes) + row.each_with_index.map do |element, index| + element.ljust(sizes[index]) + end.join(" ").strip + "\n" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/platform.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/platform.rb new file mode 100644 index 0000000000..e97cad49a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/platform.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Platform + attr_reader :options + def initialize(options) + @options = options + end + + def run + platforms, ruby_version = Bundler.ui.silence do + locked_ruby_version = Bundler.locked_gems && Bundler.locked_gems.ruby_version + gemfile_ruby_version = Bundler.definition.ruby_version && Bundler.definition.ruby_version.single_version_string + [Bundler.definition.platforms.map {|p| "* #{p}" }, + locked_ruby_version || gemfile_ruby_version] + end + output = [] + + if options[:ruby] + if ruby_version + output << ruby_version + else + output << "No ruby version specified" + end + else + output << "Your platform is: #{RUBY_PLATFORM}" + output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}" + + if ruby_version + output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}" + + begin + Bundler.definition.validate_runtime! + output << "Your current platform satisfies the Ruby version requirement." + rescue RubyVersionMismatch => e + output << e.message + end + else + output << "Your Gemfile does not specify a Ruby version requirement." + end + end + + Bundler.ui.info output.join("\n\n") + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/plugin.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/plugin.rb new file mode 100644 index 0000000000..fe3f4412fa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/plugin.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require_relative "../vendored_thor" +module Bundler + class CLI::Plugin < Thor + desc "install PLUGINS", "Install the plugin from the source" + long_desc <<-D + Install plugins either from the rubygems source provided (with --source option) or from a git source provided with --git (for remote repos) or --local_git (for local repos). If no sources are provided, it uses Gem.sources + D + method_option "source", :type => :string, :default => nil, :banner => + "URL of the RubyGems source to fetch the plugin from" + method_option "version", :type => :string, :default => nil, :banner => + "The version of the plugin to fetch" + method_option "git", :type => :string, :default => nil, :banner => + "URL of the git repo to fetch from" + method_option "local_git", :type => :string, :default => nil, :banner => + "Path of the local git repo to fetch from" + method_option "branch", :type => :string, :default => nil, :banner => + "The git branch to checkout" + method_option "ref", :type => :string, :default => nil, :banner => + "The git revision to check out" + def install(*plugins) + Bundler::Plugin.install(plugins, options) + end + + desc "uninstall PLUGINS", "Uninstall the plugins" + long_desc <<-D + Uninstall given list of plugins. To uninstall all the plugins, use -all option. + D + method_option "all", :type => :boolean, :default => nil, :banner => + "Uninstall all the installed plugins. If no plugin is installed, then it does nothing." + def uninstall(*plugins) + Bundler::Plugin.uninstall(plugins, options) + end + + desc "list", "List the installed plugins and available commands" + def list + Bundler::Plugin.list + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/pristine.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/pristine.rb new file mode 100644 index 0000000000..d6654f8053 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/pristine.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Pristine + def initialize(gems) + @gems = gems + end + + def run + CLI::Common.ensure_all_gems_in_lockfile!(@gems) + definition = Bundler.definition + definition.validate_runtime! + installer = Bundler::Installer.new(Bundler.root, definition) + + Bundler.load.specs.each do |spec| + next if spec.name == "bundler" # Source::Rubygems doesn't install bundler + next if !@gems.empty? && !@gems.include?(spec.name) + + gem_name = "#{spec.name} (#{spec.version}#{spec.git_version})" + gem_name += " (#{spec.platform})" if !spec.platform.nil? && spec.platform != Gem::Platform::RUBY + + case source = spec.source + when Source::Rubygems + cached_gem = spec.cache_file + unless File.exist?(cached_gem) + Bundler.ui.error("Failed to pristine #{gem_name}. Cached gem #{cached_gem} does not exist.") + next + end + + FileUtils.rm_rf spec.full_gem_path + when Source::Git + if source.local? + Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is locally overridden.") + next + end + + source.remote! + if extension_cache_path = source.extension_cache_path(spec) + FileUtils.rm_rf extension_cache_path + end + FileUtils.rm_rf spec.extension_dir + FileUtils.rm_rf spec.full_gem_path + else + Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.") + next + end + + Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/remove.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/remove.rb new file mode 100644 index 0000000000..cd6a2cec28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/remove.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Remove + def initialize(gems, options) + @gems = gems + @options = options + end + + def run + raise InvalidOption, "Please specify gems to remove." if @gems.empty? + + Injector.remove(@gems, {}) + + Installer.install(Bundler.root, Bundler.definition) if @options["install"] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/show.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/show.rb new file mode 100644 index 0000000000..5eaaba1ef7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/show.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Show + attr_reader :options, :gem_name, :latest_specs + def initialize(options, gem_name) + @options = options + @gem_name = gem_name + @verbose = options[:verbose] || options[:outdated] + @latest_specs = fetch_latest_specs if @verbose + end + + def run + Bundler.ui.silence do + Bundler.definition.validate_runtime! + Bundler.load.lock + end + + if gem_name + if gem_name == "bundler" + path = File.expand_path("../../../..", __FILE__) + else + spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match) + return unless spec + path = spec.full_gem_path + unless File.directory?(path) + return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}" + end + end + return Bundler.ui.info(path) + end + + if options[:paths] + Bundler.load.specs.sort_by(&:name).map do |s| + Bundler.ui.info s.full_gem_path + end + else + Bundler.ui.info "Gems included by the bundle:" + Bundler.load.specs.sort_by(&:name).each do |s| + desc = " * #{s.name} (#{s.version}#{s.git_version})" + if @verbose + latest = latest_specs.find {|l| l.name == s.name } + Bundler.ui.info <<-END.gsub(/^ +/, "") + #{desc} + \tSummary: #{s.summary || "No description available."} + \tHomepage: #{s.homepage || "No website available."} + \tStatus: #{outdated?(s, latest) ? "Outdated - #{s.version} < #{latest.version}" : "Up to date"} + END + else + Bundler.ui.info desc + end + end + end + end + + private + + def fetch_latest_specs + definition = Bundler.definition(true) + if options[:outdated] + Bundler.ui.info "Fetching remote specs for outdated check...\n\n" + Bundler.ui.silence { definition.resolve_remotely! } + else + definition.resolve_with_cache! + end + Bundler.reset! + definition.specs + end + + def outdated?(current, latest) + return false unless latest + Gem::Version.new(current.version) < Gem::Version.new(latest.version) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/update.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/update.rb new file mode 100644 index 0000000000..cf6a5b26d3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/update.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Update + attr_reader :options, :gems + def initialize(options, gems) + @options = options + @gems = gems + end + + def run + Bundler.ui.level = "error" if options[:quiet] + + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + + sources = Array(options[:source]) + groups = Array(options[:group]).map(&:to_sym) + + full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler] + + if full_update && !options[:all] + if Bundler.feature_flag.update_requires_all_flag? + raise InvalidOption, "To update everything, pass the `--all` flag." + end + SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything" + elsif !full_update && options[:all] + raise InvalidOption, "Cannot specify --all along with specific options." + end + + conservative = options[:conservative] + + if full_update + if conservative + Bundler.definition(:conservative => conservative) + else + Bundler.definition(true) + end + else + unless Bundler.default_lockfile.exist? + raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \ + "Run `bundle install` to update and install the bundled gems." + end + Bundler::CLI::Common.ensure_all_gems_in_lockfile!(gems) + + if groups.any? + deps = Bundler.definition.dependencies.select {|d| (d.groups & groups).any? } + gems.concat(deps.map(&:name)) + end + + Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby], + :conservative => conservative, + :bundler => options[:bundler]) + end + + Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) + + Bundler::Fetcher.disable_endpoint = options["full-index"] + + opts = options.dup + opts["update"] = true + opts["local"] = options[:local] + + Bundler.settings.set_command_option_if_given :jobs, opts["jobs"] + + Bundler.definition.validate_runtime! + + if locked_gems = Bundler.definition.locked_gems + previous_locked_info = locked_gems.specs.reduce({}) do |h, s| + h[s.name] = { :spec => s, :version => s.version, :source => s.source.to_s } + h + end + end + + installer = Installer.install Bundler.root, Bundler.definition, opts + Bundler.load.cache if Bundler.app_cache.exist? + + if CLI::Common.clean_after_install? + require_relative "clean" + Bundler::CLI::Clean.new(options).run + end + + if locked_gems + gems.each do |name| + locked_info = previous_locked_info[name] + next unless locked_info + + locked_spec = locked_info[:spec] + new_spec = Bundler.definition.specs[name].first + unless new_spec + unless locked_spec.match_platform(Bundler.local_platform) + Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one" + end + + next + end + + locked_source = locked_info[:source] + new_source = new_spec.source.to_s + next if locked_source != new_source + + new_version = new_spec.version + locked_version = locked_info[:version] + if new_version < locked_version + Bundler.ui.warn "Note: #{name} version regressed from #{locked_version} to #{new_version}" + elsif new_version == locked_version + Bundler.ui.warn "Bundler attempted to update #{name} but its version stayed the same" + end + end + end + + Bundler.ui.confirm "Bundle updated!" + Bundler::CLI::Common.output_without_groups_message(:update) + Bundler::CLI::Common.output_post_install_messages installer.post_install_messages + + Bundler::CLI::Common.output_fund_metadata_summary + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/viz.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/viz.rb new file mode 100644 index 0000000000..644f9b25cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/cli/viz.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Bundler + class CLI::Viz + attr_reader :options, :gem_name + def initialize(options) + @options = options + end + + def run + # make sure we get the right `graphviz`. There is also a `graphviz` + # gem we're not built to support + gem "ruby-graphviz" + require "graphviz" + + options[:without] = options[:without].join(":").tr(" ", ":").split(":") + output_file = File.expand_path(options[:file]) + + graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) + graph.viz + rescue LoadError => e + Bundler.ui.error e.inspect + Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" + Bundler.ui.warn "`gem install ruby-graphviz`" + rescue StandardError => e + raise unless e.message =~ /GraphViz not installed or dot not in PATH/ + Bundler.ui.error e.message + Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`." + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client.rb new file mode 100644 index 0000000000..cf67f0e7a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require "pathname" +require "set" + +module Bundler + class CompactIndexClient + DEBUG_MUTEX = Mutex.new + def self.debug + return unless ENV["DEBUG_COMPACT_INDEX"] + DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") } + end + + class Error < StandardError; end + + require_relative "compact_index_client/cache" + require_relative "compact_index_client/updater" + + attr_reader :directory + + def initialize(directory, fetcher) + @directory = Pathname.new(directory) + @updater = Updater.new(fetcher) + @cache = Cache.new(@directory) + @endpoints = Set.new + @info_checksums_by_name = {} + @parsed_checksums = false + @mutex = Mutex.new + end + + def execution_mode=(block) + Bundler::CompactIndexClient.debug { "execution_mode=" } + @endpoints = Set.new + + @execution_mode = block + end + + # @return [Lambda] A lambda that takes an array of inputs and a block, and + # maps the inputs with the block in parallel. + # + def execution_mode + @execution_mode || sequentially + end + + def sequential_execution_mode! + self.execution_mode = sequentially + end + + def sequentially + @sequentially ||= lambda do |inputs, &blk| + inputs.map(&blk) + end + end + + def names + Bundler::CompactIndexClient.debug { "/names" } + update(@cache.names_path, "names") + @cache.names + end + + def versions + Bundler::CompactIndexClient.debug { "/versions" } + update(@cache.versions_path, "versions") + versions, @info_checksums_by_name = @cache.versions + versions + end + + def dependencies(names) + Bundler::CompactIndexClient.debug { "dependencies(#{names})" } + execution_mode.call(names) do |name| + update_info(name) + @cache.dependencies(name).map {|d| d.unshift(name) } + end.flatten(1) + end + + def spec(name, version, platform = nil) + Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" } + update_info(name) + @cache.specific_dependency(name, version, platform) + end + + def update_and_parse_checksums! + Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" } + return @info_checksums_by_name if @parsed_checksums + update(@cache.versions_path, "versions") + @info_checksums_by_name = @cache.checksums + @parsed_checksums = true + end + + private + + def update(local_path, remote_path) + Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" } + unless synchronize { @endpoints.add?(remote_path) } + Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" } + return + end + @updater.update(local_path, url(remote_path)) + end + + def update_info(name) + Bundler::CompactIndexClient.debug { "update_info(#{name})" } + path = @cache.info_path(name) + checksum = @updater.checksum_for_file(path) + unless existing = @info_checksums_by_name[name] + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" } + return + end + if checksum == existing + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" } + return + end + Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" } + update(path, "info/#{name}") + end + + def url(path) + path + end + + def synchronize + @mutex.synchronize { yield } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/cache.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/cache.rb new file mode 100644 index 0000000000..c2cd069ec1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/cache.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require_relative "gem_parser" + +module Bundler + class CompactIndexClient + class Cache + attr_reader :directory + + def initialize(directory) + @directory = Pathname.new(directory).expand_path + info_roots.each do |dir| + SharedHelpers.filesystem_access(dir) do + FileUtils.mkdir_p(dir) + end + end + end + + def names + lines(names_path) + end + + def names_path + directory.join("names") + end + + def versions + versions_by_name = Hash.new {|hash, key| hash[key] = [] } + info_checksums_by_name = {} + + lines(versions_path).each do |line| + name, versions_string, info_checksum = line.split(" ", 3) + info_checksums_by_name[name] = info_checksum || "" + versions_string.split(",").each do |version| + if version.start_with?("-") + version = version[1..-1].split("-", 2).unshift(name) + versions_by_name[name].delete(version) + else + version = version.split("-", 2).unshift(name) + versions_by_name[name] << version + end + end + end + + [versions_by_name, info_checksums_by_name] + end + + def versions_path + directory.join("versions") + end + + def checksums + checksums = {} + + lines(versions_path).each do |line| + name, _, checksum = line.split(" ", 3) + checksums[name] = checksum + end + + checksums + end + + def dependencies(name) + lines(info_path(name)).map do |line| + parse_gem(line) + end + end + + def info_path(name) + name = name.to_s + if name =~ /[^a-z0-9_-]/ + name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}" + info_roots.last.join(name) + else + info_roots.first.join(name) + end + end + + def specific_dependency(name, version, platform) + pattern = [version, platform].compact.join("-") + return nil if pattern.empty? + + gem_lines = info_path(name).read + gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0] + gem_line ? parse_gem(gem_line) : nil + end + + private + + def lines(path) + return [] unless path.file? + lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n") + header = lines.index("---") + header ? lines[header + 1..-1] : lines + end + + def parse_gem(line) + @dependency_parser ||= GemParser.new + @dependency_parser.parse(line) + end + + def info_roots + [ + directory.join("info"), + directory.join("info-special-characters"), + ] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/gem_parser.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/gem_parser.rb new file mode 100644 index 0000000000..e7bf4c6001 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/gem_parser.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Bundler + class CompactIndexClient + if defined?(Gem::Resolver::APISet::GemParser) + GemParser = Gem::Resolver::APISet::GemParser + else + class GemParser + def parse(line) + version_and_platform, rest = line.split(" ", 2) + version, platform = version_and_platform.split("-", 2) + dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest + dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : [] + requirements = requirements ? requirements.map {|d| parse_dependency(d) } : [] + [version, platform, dependencies, requirements] + end + + private + + def parse_dependency(string) + dependency = string.split(":") + dependency[-1] = dependency[-1].split("&") if dependency.size > 1 + dependency + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/updater.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/updater.rb new file mode 100644 index 0000000000..06486f98cb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/compact_index_client/updater.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require_relative "../vendored_fileutils" + +module Bundler + class CompactIndexClient + class Updater + class MisMatchedChecksumError < Error + def initialize(path, server_checksum, local_checksum) + @path = path + @server_checksum = server_checksum + @local_checksum = local_checksum + end + + def message + "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \ + "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})." + end + end + + def initialize(fetcher) + @fetcher = fetcher + require_relative "../vendored_tmpdir" + end + + def update(local_path, remote_path, retrying = nil) + headers = {} + + Bundler::Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| + local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) + + # first try to fetch any new bytes on the existing file + if retrying.nil? && local_path.file? + SharedHelpers.filesystem_access(local_temp_path) do + FileUtils.cp local_path, local_temp_path + end + headers["If-None-Match"] = etag_for(local_temp_path) + headers["Range"] = + if local_temp_path.size.nonzero? + # Subtract a byte to ensure the range won't be empty. + # Avoids 416 (Range Not Satisfiable) responses. + "bytes=#{local_temp_path.size - 1}-" + else + "bytes=#{local_temp_path.size}-" + end + end + + response = @fetcher.call(remote_path, headers) + return nil if response.is_a?(Net::HTTPNotModified) + + content = response.body + + etag = (response["ETag"] || "").gsub(%r{\AW/}, "") + correct_response = SharedHelpers.filesystem_access(local_temp_path) do + if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? + local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } + + etag_for(local_temp_path) == etag + else + local_temp_path.open("wb") {|f| f << content } + + etag.length.zero? || etag_for(local_temp_path) == etag + end + end + + if correct_response + SharedHelpers.filesystem_access(local_path) do + FileUtils.mv(local_temp_path, local_path) + end + return nil + end + + if retrying + raise MisMatchedChecksumError.new(remote_path, etag, etag_for(local_temp_path)) + end + + update(local_path, remote_path, :retrying) + end + rescue Errno::EACCES + raise Bundler::PermissionError, + "Bundler does not have write access to create a temp directory " \ + "within #{Dir.tmpdir}. Bundler must have write access to your " \ + "systems temp directory to function properly. " + rescue Zlib::GzipFile::Error + raise Bundler::HTTPError + end + + def etag_for(path) + sum = checksum_for_file(path) + sum ? %("#{sum}") : nil + end + + def slice_body(body, range) + body.byteslice(range) + end + + def checksum_for_file(path) + return nil unless path.file? + # This must use File.read instead of Digest.file().hexdigest + # because we need to preserve \n line endings on windows when calculating + # the checksum + SharedHelpers.filesystem_access(path, :read) do + SharedHelpers.digest(:MD5).hexdigest(File.read(path)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/constants.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/constants.rb new file mode 100644 index 0000000000..2e4ebb37ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/constants.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Bundler + WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/ + FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/ + NULL = WINDOWS ? "NUL" : "/dev/null" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/current_ruby.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/current_ruby.rb new file mode 100644 index 0000000000..f84d68e262 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/current_ruby.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Bundler + # Returns current version of Ruby + # + # @return [CurrentRuby] Current version of Ruby + def self.current_ruby + @current_ruby ||= CurrentRuby.new + end + + class CurrentRuby + KNOWN_MINOR_VERSIONS = %w[ + 1.8 + 1.9 + 2.0 + 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 3.0 + ].freeze + + KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze + + KNOWN_PLATFORMS = %w[ + jruby + maglev + mingw + mri + mswin + mswin64 + rbx + ruby + truffleruby + x64_mingw + ].freeze + + def ruby? + return true if Bundler::GemHelpers.generic_local_platform == Gem::Platform::RUBY + + !mswin? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby") + end + + def mri? + !mswin? && RUBY_ENGINE == "ruby" + end + + def rbx? + ruby? && RUBY_ENGINE == "rbx" + end + + def jruby? + RUBY_ENGINE == "jruby" + end + + def maglev? + RUBY_ENGINE == "maglev" + end + + def truffleruby? + RUBY_ENGINE == "truffleruby" + end + + def mswin? + Gem.win_platform? + end + + def mswin64? + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64" + end + + def mingw? + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64" + end + + def x64_mingw? + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64" + end + + (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version| + trimmed_version = version.tr(".", "") + define_method(:"on_#{trimmed_version}?") do + RUBY_VERSION.start_with?("#{version}.") + end + + KNOWN_PLATFORMS.each do |platform| + define_method(:"#{platform}_#{trimmed_version}?") do + send(:"#{platform}?") && send(:"on_#{trimmed_version}?") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/definition.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/definition.rb new file mode 100644 index 0000000000..e043b5c713 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/definition.rb @@ -0,0 +1,903 @@ +# frozen_string_literal: true + +require_relative "lockfile_parser" + +module Bundler + class Definition + include GemHelpers + + attr_reader( + :dependencies, + :locked_deps, + :locked_gems, + :platforms, + :requires, + :ruby_version, + :lockfile, + :gemfiles + ) + + # Given a gemfile and lockfile creates a Bundler definition + # + # @param gemfile [Pathname] Path to Gemfile + # @param lockfile [Pathname,nil] Path to Gemfile.lock + # @param unlock [Hash, Boolean, nil] Gems that have been requested + # to be updated or true if all gems should be updated + # @return [Bundler::Definition] + def self.build(gemfile, lockfile, unlock) + unlock ||= {} + gemfile = Pathname.new(gemfile).expand_path + + raise GemfileNotFound, "#{gemfile} not found" unless gemfile.file? + + Dsl.evaluate(gemfile, lockfile, unlock) + end + + # + # How does the new system work? + # + # * Load information from Gemfile and Lockfile + # * Invalidate stale locked specs + # * All specs from stale source are stale + # * All specs that are reachable only through a stale + # dependency are stale. + # * If all fresh dependencies are satisfied by the locked + # specs, then we can try to resolve locally. + # + # @param lockfile [Pathname] Path to Gemfile.lock + # @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile + # @param sources [Bundler::SourceList] + # @param unlock [Hash, Boolean, nil] Gems that have been requested + # to be updated or true if all gems should be updated + # @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version + # @param optional_groups [Array(String)] A list of optional groups + def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = []) + if [true, false].include?(unlock) + @unlocking_bundler = false + @unlocking = unlock + else + @unlocking_bundler = unlock.delete(:bundler) + @unlocking = unlock.any? {|_k, v| !Array(v).empty? } + end + + @dependencies = dependencies + @sources = sources + @unlock = unlock + @optional_groups = optional_groups + @remote = false + @specs = nil + @ruby_version = ruby_version + @gemfiles = gemfiles + + @lockfile = lockfile + @lockfile_contents = String.new + @locked_bundler_version = nil + @locked_ruby_version = nil + @locked_specs_incomplete_for_platform = false + @new_platform = nil + + if lockfile && File.exist?(lockfile) + @lockfile_contents = Bundler.read_file(lockfile) + @locked_gems = LockfileParser.new(@lockfile_contents) + @locked_platforms = @locked_gems.platforms + @platforms = @locked_platforms.dup + @locked_bundler_version = @locked_gems.bundler_version + @locked_ruby_version = @locked_gems.ruby_version + + if unlock != true + @locked_deps = @locked_gems.dependencies + @locked_specs = SpecSet.new(@locked_gems.specs) + @locked_sources = @locked_gems.sources + else + @unlock = {} + @locked_deps = {} + @locked_specs = SpecSet.new([]) + @locked_sources = [] + end + else + @unlock = {} + @platforms = [] + @locked_gems = nil + @locked_deps = {} + @locked_specs = SpecSet.new([]) + @locked_sources = [] + @locked_platforms = [] + end + + locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } + @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? + + if @multisource_allowed + unless sources.aggregate_global_source? + msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + + Bundler::SharedHelpers.major_deprecation 2, msg + end + + @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + end + + @unlock[:sources] ||= [] + @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object + @ruby_version.diff(locked_ruby_version_object) + end + @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) + + add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle? + + converge_path_sources_to_gemspec_sources + @path_changes = converge_paths + @source_changes = converge_sources + + if @unlock[:conservative] + @unlock[:gems] ||= @dependencies.map(&:name) + else + eager_unlock = expand_dependencies(@unlock[:gems] || [], true) + @unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name) + end + + @dependency_changes = converge_dependencies + @local_changes = converge_locals + + @requires = compute_requires + end + + def gem_version_promoter + @gem_version_promoter ||= begin + locked_specs = + if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty? + # Definition uses an empty set of locked_specs to indicate all gems + # are unlocked, but GemVersionPromoter needs the locked_specs + # for conservative comparison. + Bundler::SpecSet.new(@locked_gems.specs) + else + @locked_specs + end + GemVersionPromoter.new(locked_specs, @unlock[:gems]) + end + end + + def multisource_allowed? + @multisource_allowed + end + + def resolve_only_locally! + @remote = false + sources.local_only! + resolve + end + + def resolve_with_cache! + sources.cached! + resolve + end + + def resolve_remotely! + @remote = true + sources.remote! + resolve + end + + # For given dependency list returns a SpecSet with Gemspec of all the required + # dependencies. + # 1. The method first resolves the dependencies specified in Gemfile + # 2. After that it tries and fetches gemspec of resolved dependencies + # + # @return [Bundler::SpecSet] + def specs + @specs ||= begin + begin + specs = resolve.materialize(requested_dependencies) + rescue GemNotFound => e # Handle yanked gem + gem_name, gem_version = extract_gem_info(e) + locked_gem = @locked_specs[gem_name].last + raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote + raise GemNotFound, "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \ + "no longer be found in that source. That means the author of #{locked_gem} has removed it. " \ + "You'll need to update your bundle to a version other than #{locked_gem} that hasn't been " \ + "removed in order to install." + end + unless specs["bundler"].any? + bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last + specs["bundler"] = bundler + end + + specs + end + end + + def new_specs + specs - @locked_specs + end + + def removed_specs + @locked_specs - specs + end + + def missing_specs + missing = [] + resolve.materialize(requested_dependencies, missing) + missing + end + + def missing_specs? + missing = missing_specs + return false if missing.empty? + Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}" + true + rescue BundlerError => e + @resolve = nil + @specs = nil + @gem_version_promoter = nil + + Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})" + true + end + + def requested_specs + @requested_specs ||= begin + groups = requested_groups + groups.map!(&:to_sym) + specs_for(groups) + end + end + + def requested_dependencies + groups = requested_groups + groups.map!(&:to_sym) + dependencies_for(groups) + end + + def current_dependencies + dependencies.select do |d| + d.should_include? && !d.gem_platforms(@platforms).empty? + end + end + + def specs_for(groups) + deps = dependencies_for(groups) + SpecSet.new(specs.for(expand_dependencies(deps))) + end + + def dependencies_for(groups) + current_dependencies.reject do |d| + (d.groups & groups).empty? + end + end + + # Resolve all the dependencies specified in Gemfile. It ensures that + # dependencies that have been already resolved via locked file and are fresh + # are reused when resolving dependencies + # + # @return [SpecSet] resolved dependencies + def resolve + @resolve ||= begin + last_resolve = converge_locked_specs + if Bundler.frozen_bundle? + Bundler.ui.debug "Frozen, using resolution from the lockfile" + last_resolve + elsif !unlocking? && nothing_changed? + Bundler.ui.debug("Found no changes, using resolution from the lockfile") + last_resolve + else + # Run a resolve against the locally available gems + Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") + expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote) + Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms) + end + end + end + + def has_rubygems_remotes? + sources.rubygems_sources.any? {|s| s.remotes.any? } + end + + def spec_git_paths + sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact + end + + def groups + dependencies.map(&:groups).flatten.uniq + end + + def lock(file, preserve_unknown_sections = false) + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = Gem::Version.create(Bundler::VERSION).segments.first + + if updating_major = locked_major < current_major + Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \ + "after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}." + end + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + return if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end + end + + def locked_bundler_version + if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION) + new_version = Bundler::VERSION + end + + new_version || @locked_bundler_version || Bundler::VERSION + end + + def locked_ruby_version + return unless ruby_version + if @unlock[:ruby] || !@locked_ruby_version + Bundler::RubyVersion.system + else + @locked_ruby_version + end + end + + def locked_ruby_version_object + return unless @locked_ruby_version + @locked_ruby_version_object ||= begin + unless version = RubyVersion.from_string(@locked_ruby_version) + raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \ + "#{@lockfile} could not be parsed. " \ + "Try running bundle update --ruby to resolve this." + end + version + end + end + + def to_lock + require_relative "lockfile_generator" + LockfileGenerator.generate(self) + end + + def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) + msg = String.new + msg << "You are trying to install in deployment mode after changing\n" \ + "your Gemfile. Run `bundle install` elsewhere and add the\n" \ + "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control." + + unless explicit_flag + suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any? + "bundle config unset frozen" + elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any? + "bundle config unset deployment" + end + msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \ + "freeze \nby running `#{suggested_command}`." + end + + added = [] + deleted = [] + changed = [] + + new_platforms = @platforms - @locked_platforms + deleted_platforms = @locked_platforms - @platforms + added.concat new_platforms.map {|p| "* platform: #{p}" } + deleted.concat deleted_platforms.map {|p| "* platform: #{p}" } + + gemfile_sources = sources.lock_sources + + new_sources = gemfile_sources - @locked_sources + deleted_sources = @locked_sources - gemfile_sources + + new_deps = @dependencies - @locked_deps.values + deleted_deps = @locked_deps.values - @dependencies + + # Check if it is possible that the source is only changed thing + if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?) + new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } + deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } + end + + if @locked_sources != gemfile_sources + if new_sources.any? + added.concat new_sources.map {|source| "* source: #{source}" } + end + + if deleted_sources.any? + deleted.concat deleted_sources.map {|source| "* source: #{source}" } + end + end + + added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? + if deleted_deps.any? + deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } + end + + both_sources = Hash.new {|h, k| h[k] = [] } + @dependencies.each {|d| both_sources[d.name][0] = d } + @locked_deps.each {|name, d| both_sources[name][1] = d.source } + + both_sources.each do |name, (dep, lock_source)| + next if lock_source.nil? || (dep && lock_source.can_lock?(dep)) + gemfile_source_name = (dep && dep.source) || "no specified source" + lockfile_source_name = lock_source + changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`" + end + + reason = change_reason + msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty? + msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? + msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? + msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? + msg << "\n" + + raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed? + end + + def validate_runtime! + validate_ruby! + validate_platforms! + end + + def validate_ruby! + return unless ruby_version + + if diff = ruby_version.diff(Bundler::RubyVersion.system) + problem, expected, actual = diff + + msg = case problem + when :engine + "Your Ruby engine is #{actual}, but your Gemfile specified #{expected}" + when :version + "Your Ruby version is #{actual}, but your Gemfile specified #{expected}" + when :engine_version + "Your #{Bundler::RubyVersion.system.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}" + when :patchlevel + if !expected.is_a?(String) + "The Ruby patchlevel in your Gemfile must be a string" + else + "Your Ruby patchlevel is #{actual}, but your Gemfile specified #{expected}" + end + end + + raise RubyVersionMismatch, msg + end + end + + def validate_platforms! + return if current_platform_locked? + + raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \ + "but your local platform is #{Bundler.local_platform}. " \ + "Add the current platform to the lockfile with `bundle lock --add-platform #{Bundler.local_platform}` and try again." + end + + def add_platform(platform) + @new_platform ||= !@platforms.include?(platform) + @platforms |= [platform] + end + + def remove_platform(platform) + return if @platforms.delete(Gem::Platform.new(platform)) + raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" + end + + def most_specific_locked_platform + @platforms.min_by do |bundle_platform| + platform_specificity_match(bundle_platform, local_platform) + end + end + + attr_reader :sources + private :sources + + def nothing_changed? + !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform + end + + def unlocking? + @unlocking + end + + private + + def precompute_source_requirements_for_indirect_dependencies? + sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + end + + def current_ruby_platform_locked? + return false unless generic_local_platform == Gem::Platform::RUBY + + current_platform_locked? + end + + def current_platform_locked? + @platforms.any? do |bundle_platform| + MatchPlatform.platforms_match?(bundle_platform, Bundler.local_platform) + end + end + + def add_current_platform + add_platform(local_platform) + end + + def change_reason + if unlocking? + unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v| + if v == true + k.to_s + else + v = Array(v) + "#{k}: (#{v.join(", ")})" + end + end.join(", ") + return "bundler is unlocking #{unlock_reason}" + end + [ + [@source_changes, "the list of sources changed"], + [@dependency_changes, "the dependencies in your gemfile changed"], + [@new_platform, "you added a new platform to your gemfile"], + [@path_changes, "the gemspecs for path gems changed"], + [@local_changes, "the gemspecs for git local gems changed"], + [@locked_specs_incomplete_for_platform, "the lockfile does not have all gems needed for the current platform"], + ].select(&:first).map(&:last).join(", ") + end + + def pretty_dep(dep, source = false) + SharedHelpers.pretty_dependency(dep, source) + end + + # Check if the specs of the given source changed + # according to the locked source. + def specs_changed?(source) + locked = @locked_sources.find {|s| s == source } + + !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source) + end + + def dependencies_for_source_changed?(source, locked_source = source) + deps_for_source = @dependencies.select {|s| s.source == source } + locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source } + + deps_for_source.uniq.sort != locked_deps_for_source.sort + end + + def specs_for_source_changed?(source) + locked_index = Index.new + locked_index.use(@locked_specs.select {|s| source.can_lock?(s) }) + + # order here matters, since Index#== is checking source.specs.include?(locked_index) + locked_index != source.specs + rescue PathError, GitError => e + Bundler.ui.debug "Assuming that #{source} has not changed since fetching its specs errored (#{e})" + false + end + + # Get all locals and override their matching sources. + # Return true if any of the locals changed (for example, + # they point to a new revision) or depend on new specs. + def converge_locals + locals = [] + + Bundler.settings.local_overrides.map do |k, v| + spec = @dependencies.find {|s| s.name == k } + source = spec && spec.source + if source && source.respond_to?(:local_override!) + source.unlock! if @unlock[:gems].include?(spec.name) + locals << [source, source.local_override!(v)] + end + end + + sources_with_changes = locals.select do |source, changed| + changed || specs_changed?(source) + end.map(&:first) + !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty? + end + + def converge_paths + sources.path_sources.any? do |source| + specs_changed?(source) + end + end + + def converge_path_source_to_gemspec_source(source) + return source unless source.instance_of?(Source::Path) + gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source } + gemspec_source || source + end + + def converge_path_sources_to_gemspec_sources + @locked_sources.map! do |source| + converge_path_source_to_gemspec_source(source) + end + @locked_specs.each do |spec| + spec.source &&= converge_path_source_to_gemspec_source(spec.source) + end + @locked_deps.each do |_, dep| + dep.source &&= converge_path_source_to_gemspec_source(dep.source) + end + end + + def converge_sources + # Replace the sources from the Gemfile with the sources from the Gemfile.lock, + # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent + # source in the Gemfile.lock, use the one from the Gemfile. + changes = sources.replace_sources!(@locked_sources) + + sources.all_sources.each do |source| + # If the source is unlockable and the current command allows an unlock of + # the source (for example, you are doing a `bundle update ` of a git-pinned + # gem), unlock it. For git sources, this means to unlock the revision, which + # will cause the `ref` used to be the most recent for the branch (or master) if + # an explicit `ref` is not used. + if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name) + source.unlock! + changes = true + end + end + + changes + end + + def converge_dependencies + frozen = Bundler.frozen_bundle? + (@dependencies + @locked_deps.values).each do |dep| + locked_source = @locked_deps[dep.name] + # This is to make sure that if bundler is installing in deployment mode and + # after locked_source and sources don't match, we still use locked_source. + if frozen && !locked_source.nil? && + locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist? + dep.source = locked_source.source + elsif dep.source + dep.source = sources.get(dep.source) + end + end + + changes = false + # We want to know if all match, but don't want to check all entries + # This means we need to return false if any dependency doesn't match + # the lock or doesn't exist in the lock. + @dependencies.each do |dependency| + unless locked_dep = @locked_deps[dependency.name] + changes = true + next + end + + # Gem::Dependency#== matches Gem::Dependency#type. As the lockfile + # doesn't carry a notion of the dependency type, if you use + # add_development_dependency in a gemspec that's loaded with the gemspec + # directive, the lockfile dependencies and resolved dependencies end up + # with a mismatch on #type. Work around that by setting the type on the + # dep from the lockfile. + locked_dep.instance_variable_set(:@type, dependency.type) + + # We already know the name matches from the hash lookup + # so we only need to check the requirement now + changes ||= dependency.requirement != locked_dep.requirement + end + + changes + end + + # Remove elements from the locked specs that are expired. This will most + # commonly happen if the Gemfile has changed since the lockfile was last + # generated + def converge_locked_specs + deps = [] + + # Build a list of dependencies that are the same in the Gemfile + # and Gemfile.lock. If the Gemfile modified a dependency, but + # the gem in the Gemfile.lock still satisfies it, this is fine + # too. + @dependencies.each do |dep| + locked_dep = @locked_deps[dep.name] + + # If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep + locked_dep = nil unless locked_dep == dep + + if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep) + deps << dep + elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source) + @locked_specs.each do |s| + @unlock[:gems] << s.name if s.source == dep.source + end + + dep.source.unlock! if dep.source.respond_to?(:unlock!) + dep.source.specs.each {|s| @unlock[:gems] << s.name } + end + end + + converged = [] + @locked_specs.each do |s| + # Replace the locked dependency's source with the equivalent source from the Gemfile + dep = @dependencies.find {|d| s.satisfies?(d) } + s.source = (dep && dep.source) || sources.get(s.source) + + # Don't add a spec to the list if its source is expired. For example, + # if you change a Git gem to RubyGems. + next if s.source.nil? + next if @unlock[:sources].include?(s.source.name) + + # If the spec is from a path source and it doesn't exist anymore + # then we unlock it. + + # Path sources have special logic + if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec) + new_specs = begin + s.source.specs + rescue PathError, GitError + # if we won't need the source (according to the lockfile), + # don't error if the path/git source isn't available + next if @locked_specs. + for(requested_dependencies, [], false, true, false). + none? {|locked_spec| locked_spec.source == s.source } + + raise + end + + new_spec = new_specs[s].first + + # If the spec is no longer in the path source, unlock it. This + # commonly happens if the version changed in the gemspec + next unless new_spec + + s.dependencies.replace(new_spec.dependencies) + end + + converged << s + end + + resolve = SpecSet.new(converged) + @locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(requested_dependencies & deps), @unlock[:gems], true, true) + resolve = SpecSet.new(resolve.for(expand_dependencies(deps, true), [], false, false, false).reject{|s| @unlock[:gems].include?(s.name) }) + diff = nil + + # Now, we unlock any sources that do not have anymore gems pinned to it + sources.all_sources.each do |source| + next unless source.respond_to?(:unlock!) + + unless resolve.any? {|s| s.source == source } + diff ||= @locked_specs.to_a - resolve.to_a + source.unlock! if diff.any? {|s| s.source == source } + end + end + + resolve + end + + def in_locked_deps?(dep, locked_dep) + # Because the lockfile can't link a dep to a specific remote, we need to + # treat sources as equivalent anytime the locked dep has all the remotes + # that the Gemfile dep does. + locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source) + end + + def satisfies_locked_spec?(dep) + @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) } + end + + def metadata_dependencies + @metadata_dependencies ||= begin + ruby_versions = ruby_version_requirements(@ruby_version) + [ + Dependency.new("Ruby\0", ruby_versions), + Dependency.new("RubyGems\0", Gem::VERSION), + ] + end + end + + def ruby_version_requirements(ruby_version) + return [] unless ruby_version + if ruby_version.patchlevel + [ruby_version.to_gem_version_with_patchlevel] + else + ruby_version.versions.map do |version| + requirement = Gem::Requirement.new(version) + if requirement.exact? + "~> #{version}.0" + else + requirement + end + end + end + end + + def expand_dependencies(dependencies, remote = false) + deps = [] + dependencies.each do |dep| + dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name) + next unless remote || dep.current_platform? + target_platforms = dep.gem_platforms(remote ? @platforms : [generic_local_platform]) + deps += expand_dependency_with_platforms(dep, target_platforms) + end + deps + end + + def expand_dependency_with_platforms(dep, platforms) + platforms.map do |p| + DepProxy.get_proxy(dep, p) + end + end + + def source_requirements + # Record the specs available in each gem's source, so that those + # specs will be available later when the resolver knows where to + # look for that gemspec (or its dependencies) + source_requirements = if precompute_source_requirements_for_indirect_dependencies? + { :default => sources.default_source }.merge(source_map.all_requirements) + else + { :default => Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements) + end + metadata_dependencies.each do |dep| + source_requirements[dep.name] = sources.metadata_source + end + source_requirements[:default_bundler] = source_requirements["bundler"] || sources.default_source + source_requirements["bundler"] = sources.metadata_source # needs to come last to override + source_requirements + end + + def requested_groups + groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with] + end + + def lockfiles_equal?(current, proposed, preserve_unknown_sections) + if preserve_unknown_sections + sections_to_ignore = LockfileParser.sections_to_ignore(@locked_bundler_version) + sections_to_ignore += LockfileParser.unknown_sections_in_lockfile(current) + sections_to_ignore += LockfileParser::ENVIRONMENT_VERSION_SECTIONS + pattern = /#{Regexp.union(sections_to_ignore)}\n(\s{2,}.*\n)+/ + whitespace_cleanup = /\n{2,}/ + current = current.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip + proposed = proposed.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip + end + current == proposed + end + + def extract_gem_info(error) + # This method will extract the error message like "Could not find foo-1.2.3 in any of the sources" + # to an array. The first element will be the gem name (e.g. foo), the second will be the version number. + error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten + end + + def compute_requires + dependencies.reduce({}) do |requires, dep| + next requires unless dep.should_include? + requires[dep.name] = Array(dep.autorequire || dep.name).map do |file| + # Allow `require: true` as an alias for `require: ` + file == true ? dep.name : file + end + requires + end + end + + def additional_base_requirements_for_resolve + return [] unless @locked_gems && unlocking? && !sources.expired_sources?(@locked_gems.sources) + dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) } + @locked_gems.specs.reduce({}) do |requirements, locked_spec| + name = locked_spec.name + dependency = dependencies_by_name[name] + next requirements if @locked_gems.dependencies[name] != dependency + next requirements if dependency && dependency.source.is_a?(Source::Path) + dep = Gem::Dependency.new(name, ">= #{locked_spec.version}") + requirements[name] = DepProxy.get_proxy(dep, locked_spec.platform) + requirements + end.values + end + + def equivalent_rubygems_remotes?(source) + return false unless source.is_a?(Source::Rubygems) + + Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes) + end + + def source_map + @source_map ||= SourceMap.new(sources, dependencies) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dep_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dep_proxy.rb new file mode 100644 index 0000000000..a32dc37b49 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dep_proxy.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Bundler + class DepProxy + attr_reader :__platform, :dep + + @proxies = {} + + def self.get_proxy(dep, platform) + @proxies[[dep, platform]] ||= new(dep, platform).freeze + end + + def initialize(dep, platform) + @dep = dep + @__platform = platform + end + + private_class_method :new + + alias_method :eql?, :== + + def type + @dep.type + end + + def name + @dep.name + end + + def requirement + @dep.requirement + end + + def to_s + s = name.dup + s << " (#{requirement})" unless requirement == Gem::Requirement.default + s << " #{__platform}" unless __platform == Gem::Platform::RUBY + s + end + + def dup + raise NoMethodError.new("DepProxy cannot be duplicated") + end + + def clone + raise NoMethodError.new("DepProxy cannot be cloned") + end + + private + + def method_missing(*args, &blk) + @dep.send(*args, &blk) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dependency.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dependency.rb new file mode 100644 index 0000000000..af07e8bc36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dependency.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require "rubygems/dependency" +require_relative "shared_helpers" +require_relative "rubygems_ext" + +module Bundler + class Dependency < Gem::Dependency + attr_reader :autorequire + attr_reader :groups, :platforms, :gemfile, :git, :branch + + PLATFORM_MAP = { + :ruby => Gem::Platform::RUBY, + :ruby_18 => Gem::Platform::RUBY, + :ruby_19 => Gem::Platform::RUBY, + :ruby_20 => Gem::Platform::RUBY, + :ruby_21 => Gem::Platform::RUBY, + :ruby_22 => Gem::Platform::RUBY, + :ruby_23 => Gem::Platform::RUBY, + :ruby_24 => Gem::Platform::RUBY, + :ruby_25 => Gem::Platform::RUBY, + :ruby_26 => Gem::Platform::RUBY, + :mri => Gem::Platform::RUBY, + :mri_18 => Gem::Platform::RUBY, + :mri_19 => Gem::Platform::RUBY, + :mri_20 => Gem::Platform::RUBY, + :mri_21 => Gem::Platform::RUBY, + :mri_22 => Gem::Platform::RUBY, + :mri_23 => Gem::Platform::RUBY, + :mri_24 => Gem::Platform::RUBY, + :mri_25 => Gem::Platform::RUBY, + :mri_26 => Gem::Platform::RUBY, + :rbx => Gem::Platform::RUBY, + :truffleruby => Gem::Platform::RUBY, + :jruby => Gem::Platform::JAVA, + :jruby_18 => Gem::Platform::JAVA, + :jruby_19 => Gem::Platform::JAVA, + :mswin => Gem::Platform::MSWIN, + :mswin_18 => Gem::Platform::MSWIN, + :mswin_19 => Gem::Platform::MSWIN, + :mswin_20 => Gem::Platform::MSWIN, + :mswin_21 => Gem::Platform::MSWIN, + :mswin_22 => Gem::Platform::MSWIN, + :mswin_23 => Gem::Platform::MSWIN, + :mswin_24 => Gem::Platform::MSWIN, + :mswin_25 => Gem::Platform::MSWIN, + :mswin_26 => Gem::Platform::MSWIN, + :mswin64 => Gem::Platform::MSWIN64, + :mswin64_19 => Gem::Platform::MSWIN64, + :mswin64_20 => Gem::Platform::MSWIN64, + :mswin64_21 => Gem::Platform::MSWIN64, + :mswin64_22 => Gem::Platform::MSWIN64, + :mswin64_23 => Gem::Platform::MSWIN64, + :mswin64_24 => Gem::Platform::MSWIN64, + :mswin64_25 => Gem::Platform::MSWIN64, + :mswin64_26 => Gem::Platform::MSWIN64, + :mingw => Gem::Platform::MINGW, + :mingw_18 => Gem::Platform::MINGW, + :mingw_19 => Gem::Platform::MINGW, + :mingw_20 => Gem::Platform::MINGW, + :mingw_21 => Gem::Platform::MINGW, + :mingw_22 => Gem::Platform::MINGW, + :mingw_23 => Gem::Platform::MINGW, + :mingw_24 => Gem::Platform::MINGW, + :mingw_25 => Gem::Platform::MINGW, + :mingw_26 => Gem::Platform::MINGW, + :x64_mingw => Gem::Platform::X64_MINGW, + :x64_mingw_20 => Gem::Platform::X64_MINGW, + :x64_mingw_21 => Gem::Platform::X64_MINGW, + :x64_mingw_22 => Gem::Platform::X64_MINGW, + :x64_mingw_23 => Gem::Platform::X64_MINGW, + :x64_mingw_24 => Gem::Platform::X64_MINGW, + :x64_mingw_25 => Gem::Platform::X64_MINGW, + :x64_mingw_26 => Gem::Platform::X64_MINGW, + }.freeze + + def initialize(name, version, options = {}, &blk) + type = options["type"] || :runtime + super(name, version, type) + + @autorequire = nil + @groups = Array(options["group"] || :default).map(&:to_sym) + @source = options["source"] + @git = options["git"] + @branch = options["branch"] + @platforms = Array(options["platforms"]) + @env = options["env"] + @should_include = options.fetch("should_include", true) + @gemfile = options["gemfile"] + + @autorequire = Array(options["require"] || []) if options.key?("require") + end + + # Returns the platforms this dependency is valid for, in the same order as + # passed in the `valid_platforms` parameter + def gem_platforms(valid_platforms) + return valid_platforms if @platforms.empty? + + valid_generic_platforms = valid_platforms.map {|p| [p, GemHelpers.generic(p)] }.to_h + @gem_platforms ||= expanded_platforms.compact.uniq + + filtered_generic_platforms = valid_generic_platforms.values & @gem_platforms + valid_generic_platforms.select {|_, v| filtered_generic_platforms.include?(v) }.keys + end + + def expanded_platforms + @platforms.map {|pl| PLATFORM_MAP[pl] } + end + + def should_include? + @should_include && current_env? && current_platform? + end + + def current_env? + return true unless @env + if @env.is_a?(Hash) + @env.all? do |key, val| + ENV[key.to_s] && (val.is_a?(String) ? ENV[key.to_s] == val : ENV[key.to_s] =~ val) + end + else + ENV[@env.to_s] + end + end + + def current_platform? + return true if @platforms.empty? + @platforms.any? do |p| + Bundler.current_ruby.send("#{p}?") + end + end + + def to_lock + out = super + out << "!" if source + out << "\n" + end + + def specific? + super + rescue NoMethodError + requirement != ">= 0" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deployment.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deployment.rb new file mode 100644 index 0000000000..b432ae6ae1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deployment.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require_relative "shared_helpers" +Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \ + "Capistrano, but Capistrano provides its own integration with " \ + "Bundler via the capistrano-bundler gem. Use it instead." + +module Bundler + class Deployment + def self.define_task(context, task_method = :task, opts = {}) + if defined?(Capistrano) && context.is_a?(Capistrano::Configuration) + context_name = "capistrano" + role_default = "{:except => {:no_release => true}}" + error_type = ::Capistrano::CommandError + else + context_name = "vlad" + role_default = "[:app]" + error_type = ::Rake::CommandFailedError + end + + roles = context.fetch(:bundle_roles, false) + opts[:roles] = roles if roles + + context.send :namespace, :bundle do + send :desc, <<-DESC + Install the current Bundler environment. By default, gems will be \ + installed to the shared/bundle path. Gems in the development and \ + test group will not be installed. The install command is executed \ + with the --deployment and --quiet flags. If the bundle cmd cannot \ + be found then you can override the bundle_cmd variable to specify \ + which one it should use. The base path to the app is fetched from \ + the :latest_release variable. Set it for custom deploy layouts. + + You can override any of these defaults by setting the variables shown below. + + N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \ + in your deploy.rb file. + + set :bundle_gemfile, "Gemfile" + set :bundle_dir, File.join(fetch(:shared_path), 'bundle') + set :bundle_flags, "--deployment --quiet" + set :bundle_without, [:development, :test] + set :bundle_with, [:mysql] + set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle" + set :bundle_roles, #{role_default} # e.g. [:app, :batch] + DESC + send task_method, :install, opts do + bundle_cmd = context.fetch(:bundle_cmd, "bundle") + bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet") + bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle")) + bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile") + bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact + bundle_with = [*context.fetch(:bundle_with, [])].compact + app_path = context.fetch(:latest_release) + if app_path.to_s.empty? + raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.") + end + args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"] + args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty? + args << bundle_flags.to_s + args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty? + args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty? + + run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deprecate.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deprecate.rb new file mode 100644 index 0000000000..f59533630e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/deprecate.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +begin + require "rubygems/deprecate" +rescue LoadError + # it's fine if it doesn't exist on the current RubyGems... + nil +end + +module Bundler + # If Bundler::Deprecate is an autoload constant, we need to define it + if defined?(Bundler::Deprecate) && !autoload?(:Deprecate) + # nothing to do! + elsif defined? ::Deprecate + Deprecate = ::Deprecate + elsif defined? Gem::Deprecate + Deprecate = Gem::Deprecate + else + class Deprecate + end + end + + unless Deprecate.respond_to?(:skip_during) + def Deprecate.skip_during + original = skip + self.skip = true + yield + ensure + self.skip = original + end + end + + unless Deprecate.respond_to?(:skip) + def Deprecate.skip + @skip ||= false + end + end + + unless Deprecate.respond_to?(:skip=) + def Deprecate.skip=(skip) + @skip = skip + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dsl.rb new file mode 100644 index 0000000000..1605210e55 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/dsl.rb @@ -0,0 +1,586 @@ +# frozen_string_literal: true + +require_relative "dependency" +require_relative "ruby_dsl" + +module Bundler + class Dsl + include RubyDsl + + def self.evaluate(gemfile, lockfile, unlock) + builder = new + builder.eval_gemfile(gemfile) + builder.to_definition(lockfile, unlock) + end + + VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze + + VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules + platform platforms type source install_if gemfile].freeze + + attr_reader :gemspecs + attr_accessor :dependencies + + def initialize + @source = nil + @sources = SourceList.new + @git_sources = {} + @dependencies = [] + @groups = [] + @install_conditionals = [] + @optional_groups = [] + @platforms = [] + @env = nil + @ruby_version = nil + @gemspecs = [] + @gemfile = nil + @gemfiles = [] + add_git_sources + end + + def eval_gemfile(gemfile, contents = nil) + expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent) + original_gemfile = @gemfile + @gemfile = expanded_gemfile_path + @gemfiles << expanded_gemfile_path + contents ||= Bundler.read_file(@gemfile.to_s) + instance_eval(contents.dup.tap{|x| x.untaint if RUBY_VERSION < "2.7" }, gemfile.to_s, 1) + rescue Exception => e # rubocop:disable Lint/RescueException + message = "There was an error " \ + "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \ + "`#{File.basename gemfile.to_s}`: #{e.message}" + + raise DSLError.new(message, gemfile, e.backtrace, contents) + ensure + @gemfile = original_gemfile + end + + def gemspec(opts = nil) + opts ||= {} + path = opts[:path] || "." + glob = opts[:glob] + name = opts[:name] + development_group = opts[:development_group] || :development + expanded_path = gemfile_root.join(path) + + gemspecs = Gem::Util.glob_files_in_dir("{,*}.gemspec", expanded_path).map {|g| Bundler.load_gemspec(g) }.compact + gemspecs.reject! {|s| s.name != name } if name + Index.sort_specs(gemspecs) + specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] } + + case specs_by_name_and_version.size + when 1 + specs = specs_by_name_and_version.values.first + spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first + + @gemspecs << spec + + gem spec.name, :name => spec.name, :path => path, :glob => glob + + group(development_group) do + spec.development_dependencies.each do |dep| + gem dep.name, *(dep.requirement.as_list + [:type => :development]) + end + end + when 0 + raise InvalidOption, "There are no gemspecs at #{expanded_path}" + else + raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. " \ + "Please use the :name option to specify which one should be used" + end + end + + def gem(name, *args) + options = args.last.is_a?(Hash) ? args.pop.dup : {} + options["gemfile"] = @gemfile + version = args || [">= 0"] + + normalize_options(name, version, options) + + dep = Dependency.new(name, version, options) + + # if there's already a dependency with this name we try to prefer one + if current = @dependencies.find {|d| d.name == dep.name } + deleted_dep = @dependencies.delete(current) if current.type == :development + return if deleted_dep + + if current.requirement != dep.requirement + return if dep.type == :development + + update_prompt = "" + + if File.basename(@gemfile) == Injector::INJECTED_GEMS + if dep.requirements_list.include?(">= 0") && !current.requirements_list.include?(">= 0") + update_prompt = ". Gem already added" + else + update_prompt = ". If you want to update the gem version, run `bundle update #{current.name}`" + + update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current.requirements_list.include?(">= 0") + end + end + + raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \ + "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \ + "#{update_prompt}" + else + Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \ + "You should probably keep only one of them.\n" \ + "Remove any duplicate entries and specify the gem only once.\n" \ + "While it's not a problem now, it could cause errors if you change the version of one of them later." + end + + if current.source != dep.source + return if dep.type == :development + raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ + "You specified that #{dep.name} (#{dep.requirement}) should come from " \ + "#{current.source || "an unspecified source"} and #{dep.source}\n" + end + end + + @dependencies << dep + end + + def source(source, *args, &blk) + options = args.last.is_a?(Hash) ? args.pop.dup : {} + options = normalize_hash(options) + source = normalize_source(source) + + if options.key?("type") + options["type"] = options["type"].to_s + unless Plugin.source?(options["type"]) + raise InvalidOption, "No plugin sources available for #{options["type"]}" + end + + unless block_given? + raise InvalidOption, "You need to pass a block to #source with :type option" + end + + source_opts = options.merge("uri" => source) + with_source(@sources.add_plugin_source(options["type"], source_opts), &blk) + elsif block_given? + with_source(@sources.add_rubygems_source("remotes" => source), &blk) + else + @sources.add_global_rubygems_remote(source) + end + end + + def git_source(name, &block) + unless block_given? + raise InvalidOption, "You need to pass a block to #git_source" + end + + if valid_keys.include?(name.to_s) + raise InvalidOption, "You cannot use #{name} as a git source. It " \ + "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}" + end + + @git_sources[name.to_s] = block + end + + def path(path, options = {}, &blk) + source_options = normalize_hash(options).merge( + "path" => Pathname.new(path), + "root_path" => gemfile_root, + "gemspec" => gemspecs.find {|g| g.name == options["name"] } + ) + + source_options["global"] = true unless block_given? + + source = @sources.add_path_source(source_options) + with_source(source, &blk) + end + + def git(uri, options = {}, &blk) + unless block_given? + msg = "You can no longer specify a git source by itself. Instead, \n" \ + "either use the :git option on a gem, or specify the gems that \n" \ + "bundler should find in the git source by passing a block to \n" \ + "the git method, like: \n\n" \ + " git 'git://github.com/rails/rails.git' do\n" \ + " gem 'rails'\n" \ + " end" + raise DeprecatedError, msg + end + + with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk) + end + + def github(repo, options = {}) + raise ArgumentError, "GitHub sources require a block" unless block_given? + github_uri = @git_sources["github"].call(repo) + git_options = normalize_hash(options).merge("uri" => github_uri) + git_source = @sources.add_git_source(git_options) + with_source(git_source) { yield } + end + + def to_definition(lockfile, unlock) + check_primary_source_safety + Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles) + end + + def group(*args, &blk) + options = args.last.is_a?(Hash) ? args.pop.dup : {} + normalize_group_options(options, args) + + @groups.concat args + + if options["optional"] + optional_groups = args - @optional_groups + @optional_groups.concat optional_groups + end + + yield + ensure + args.each { @groups.pop } + end + + def install_if(*args) + @install_conditionals.concat args + yield + ensure + args.each { @install_conditionals.pop } + end + + def platforms(*platforms) + @platforms.concat platforms + yield + ensure + platforms.each { @platforms.pop } + end + alias_method :platform, :platforms + + def env(name) + old = @env + @env = name + yield + ensure + @env = old + end + + def plugin(*args) + # Pass on + end + + def method_missing(name, *args) + raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile" + end + + def check_primary_source_safety + check_path_source_safety + check_rubygems_source_safety + end + + private + + def add_git_sources + git_source(:github) do |repo_name| + warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".') +"https://github.com/#{repo_name}.git" + RUBY + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" + end + + git_source(:gist) do |repo_name| + warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"') + + "https://gist.github.com/#{repo_name}.git" + end + + git_source(:bitbucket) do |repo_name| + warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip) +user_name, repo_name = repo_name.split("/") +repo_name ||= user_name +"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git" + RUBY + + user_name, repo_name = repo_name.split("/") + repo_name ||= user_name + "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git" + end + end + + def with_source(source) + old_source = @source + if block_given? + @source = source + yield + end + source + ensure + @source = old_source + end + + def normalize_hash(opts) + opts.keys.each do |k| + opts[k.to_s] = opts.delete(k) unless k.is_a?(String) + end + opts + end + + def valid_keys + @valid_keys ||= VALID_KEYS + end + + def normalize_options(name, version, opts) + if name.is_a?(Symbol) + raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead) + end + if name =~ /\s/ + raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace) + end + raise GemfileError, %(an empty gem name is not valid) if name.empty? + + normalize_hash(opts) + + git_names = @git_sources.keys.map(&:to_s) + validate_keys("gem '#{name}'", opts, valid_keys + git_names) + + groups = @groups.dup + opts["group"] = opts.delete("groups") || opts["group"] + groups.concat Array(opts.delete("group")) + groups = [:default] if groups.empty? + + install_if = @install_conditionals.dup + install_if.concat Array(opts.delete("install_if")) + install_if = install_if.reduce(true) do |memo, val| + memo && (val.respond_to?(:call) ? val.call : val) + end + + platforms = @platforms.dup + opts["platforms"] = opts["platform"] || opts["platforms"] + platforms.concat Array(opts.delete("platforms")) + platforms.map!(&:to_sym) + platforms.each do |p| + next if VALID_PLATFORMS.include?(p) + raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}" + end + + # Save sources passed in a key + if opts.key?("source") + source = normalize_source(opts["source"]) + opts["source"] = @sources.add_rubygems_source("remotes" => source) + end + + git_name = (git_names & opts.keys).last + if @git_sources[git_name] + opts["git"] = @git_sources[git_name].call(opts[git_name]) + end + + %w[git path].each do |type| + next unless param = opts[type] + if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/ + options = opts.merge("name" => name, "version" => $1) + else + options = opts.dup + end + source = send(type, param, options) {} + opts["source"] = source + end + + opts["source"] ||= @source + opts["env"] ||= @env + opts["platforms"] = platforms.dup + opts["group"] = groups + opts["should_include"] = install_if + end + + def normalize_group_options(opts, groups) + normalize_hash(opts) + + groups = groups.map {|group| ":#{group}" }.join(", ") + validate_keys("group #{groups}", opts, %w[optional]) + + opts["optional"] ||= false + end + + def validate_keys(command, opts, valid_keys) + invalid_keys = opts.keys - valid_keys + + git_source = opts.keys & @git_sources.keys.map(&:to_s) + if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?) + raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch) + end + + return true unless invalid_keys.any? + + message = String.new + message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} " + message << if invalid_keys.size > 1 + "as options for #{command}, but they are invalid." + else + "as an option for #{command}, but it is invalid." + end + + message << " Valid options are: #{valid_keys.join(", ")}." + message << " You may be able to resolve this by upgrading Bundler to the newest version." + raise InvalidOption, message + end + + def normalize_source(source) + case source + when :gemcutter, :rubygems, :rubyforge + Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \ + "requests are insecure.\nPlease change your source to 'https://" \ + "rubygems.org' if possible, or 'http://rubygems.org' if not." + "http://rubygems.org" + when String + source + else + raise GemfileError, "Unknown source '#{source}'" + end + end + + def check_path_source_safety + return if @sources.global_path_source.nil? + + msg = "You can no longer specify a path source by itself. Instead, \n" \ + "either use the :path option on a gem, or specify the gems that \n" \ + "bundler should find in the path source by passing a block to \n" \ + "the path method, like: \n\n" \ + " path 'dir/containing/rails' do\n" \ + " gem 'rails'\n" \ + " end\n\n" + + SharedHelpers.major_deprecation(2, msg.strip) + end + + def check_rubygems_source_safety + return unless @sources.aggregate_global_source? + + if Bundler.feature_flag.bundler_3_mode? + msg = "This Gemfile contains multiple primary sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" + raise GemfileEvalError, msg + else + Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \ + "Using `source` more than once without a block is a security risk, and " \ + "may result in installing unexpected gems. To resolve this warning, use " \ + "a block to indicate which gems should come from the secondary source." + end + end + + def warn_deprecated_git_source(name, replacement, additional_message = nil) + additional_message &&= " #{additional_message}" + replacement = if replacement.count("\n").zero? + "{|repo_name| #{replacement} }" + else + "do |repo_name|\n#{replacement.to_s.gsub(/^/, " ")}\n end" + end + + Bundler::SharedHelpers.major_deprecation 3, <<-EOS +The :#{name} git source is deprecated, and will be removed in the future.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work: + + git_source(:#{name}) #{replacement} + + EOS + end + + class DSLError < GemfileError + # @return [String] the description that should be presented to the user. + # + attr_reader :description + + # @return [String] the path of the dsl file that raised the exception. + # + attr_reader :dsl_path + + # @return [Exception] the backtrace of the exception raised by the + # evaluation of the dsl file. + # + attr_reader :backtrace + + # @param [Exception] backtrace @see backtrace + # @param [String] dsl_path @see dsl_path + # + def initialize(description, dsl_path, backtrace, contents = nil) + @status_code = $!.respond_to?(:status_code) && $!.status_code + + @description = description + @dsl_path = dsl_path + @backtrace = backtrace + @contents = contents + end + + def status_code + @status_code || super + end + + # @return [String] the contents of the DSL that cause the exception to + # be raised. + # + def contents + @contents ||= begin + dsl_path && File.exist?(dsl_path) && File.read(dsl_path) + end + end + + # The message of the exception reports the content of podspec for the + # line that generated the original exception. + # + # @example Output + # + # Invalid podspec at `RestKit.podspec` - undefined method + # `exclude_header_search_paths=' for # + # + # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36 + # ------------------------------------------- + # # because it would break: #import + # > ns.exclude_header_search_paths = 'Code/RestKit.h' + # end + # ------------------------------------------- + # + # @return [String] the message of the exception. + # + def to_s + @to_s ||= begin + trace_line, description = parse_line_number_from_description + + m = String.new("\n[!] ") + m << description + m << ". Bundler cannot continue.\n" + + return m unless backtrace && dsl_path && contents + + trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line + return m unless trace_line + line_numer = trace_line.split(":")[1].to_i - 1 + return m unless line_numer + + lines = contents.lines.to_a + indent = " # " + indicator = indent.tr("#", ">") + first_line = line_numer.zero? + last_line = (line_numer == (lines.count - 1)) + + m << "\n" + m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n" + m << "#{indent}-------------------------------------------\n" + m << "#{indent}#{lines[line_numer - 1]}" unless first_line + m << "#{indicator}#{lines[line_numer]}" + m << "#{indent}#{lines[line_numer + 1]}" unless last_line + m << "\n" unless m.end_with?("\n") + m << "#{indent}-------------------------------------------\n" + end + end + + private + + def parse_line_number_from_description + description = self.description + if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/ + trace_line = Regexp.last_match[1] + description = description.sub(/\n.*\n(\.\.\.)? *\^~+$/, "").sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ") + end + [trace_line, description] + end + end + + def gemfile_root + @gemfile ||= Bundler.default_gemfile + @gemfile.dirname + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/endpoint_specification.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/endpoint_specification.rb new file mode 100644 index 0000000000..476151ae56 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/endpoint_specification.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module Bundler + # used for Creating Specifications from the Gemcutter Endpoint + class EndpointSpecification < Gem::Specification + ILLFORMED_MESSAGE = 'Ill-formed requirement ["# e + raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}" + end + + def build_dependency(name, requirements) + Gem::Dependency.new(name, requirements) + rescue ArgumentError => e + raise unless e.message.include?(ILLFORMED_MESSAGE) + puts # we shouldn't print the error message on the "fetching info" status line + raise GemspecError, + "Unfortunately, the gem #{name} (#{version}) has an invalid " \ + "gemspec.\nPlease ask the gem author to yank the bad version to fix " \ + "this issue. For more information, see http://bit.ly/syck-defaultkey." + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/env.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/env.rb new file mode 100644 index 0000000000..00d4ef2196 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/env.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require_relative "rubygems_integration" +require_relative "source/git/git_proxy" + +module Bundler + class Env + def self.write(io) + io.write report + end + + def self.report(options = {}) + print_gemfile = options.delete(:print_gemfile) { true } + print_gemspecs = options.delete(:print_gemspecs) { true } + + out = String.new + append_formatted_table("Environment", environment, out) + append_formatted_table("Bundler Build Metadata", BuildMetadata.to_h, out) + + unless Bundler.settings.all.empty? + out << "\n## Bundler settings\n\n```\n" + Bundler.settings.all.each do |setting| + out << setting << "\n" + Bundler.settings.pretty_values_for(setting).each do |line| + out << " " << line << "\n" + end + end + out << "```\n" + end + + return out unless SharedHelpers.in_bundle? + + if print_gemfile + gemfiles = [Bundler.default_gemfile] + begin + gemfiles = Bundler.definition.gemfiles + rescue GemfileNotFound + nil + end + + out << "\n## Gemfile\n" + gemfiles.each do |gemfile| + out << "\n### #{Pathname.new(gemfile).relative_path_from(SharedHelpers.pwd)}\n\n" + out << "```ruby\n" << read_file(gemfile).chomp << "\n```\n" + end + + out << "\n### #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n" + out << "```\n" << read_file(Bundler.default_lockfile).chomp << "\n```\n" + end + + if print_gemspecs + dsl = Dsl.new.tap {|d| d.eval_gemfile(Bundler.default_gemfile) } + out << "\n## Gemspecs\n" unless dsl.gemspecs.empty? + dsl.gemspecs.each do |gs| + out << "\n### #{File.basename(gs.loaded_from)}" + out << "\n\n```ruby\n" << read_file(gs.loaded_from).chomp << "\n```\n" + end + end + + out + end + + def self.read_file(filename) + Bundler.read_file(filename.to_s).strip + rescue Errno::ENOENT + "" + rescue RuntimeError => e + "#{e.class}: #{e.message}" + end + + def self.ruby_version + str = String.new(RUBY_VERSION) + str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]" + end + + def self.git_version + Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version + rescue Bundler::Source::Git::GitNotInstalledError + "not installed" + end + + def self.version_of(script) + return "not installed" unless Bundler.which(script) + `#{script} --version`.chomp + end + + def self.chruby_version + return "not installed" unless Bundler.which("chruby-exec") + `chruby-exec -- chruby --version`. + sub(/.*^chruby: (#{Gem::Version::VERSION_PATTERN}).*/m, '\1') + end + + def self.environment + out = [] + + out << ["Bundler", Bundler::VERSION] + out << [" Platforms", Gem.platforms.join(", ")] + out << ["Ruby", ruby_version] + out << [" Full Path", Gem.ruby] + out << [" Config Dir", Pathname.new(Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE).dirname] + out << ["RubyGems", Gem::VERSION] + out << [" Gem Home", Gem.dir] + out << [" Gem Path", Gem.path.join(File::PATH_SEPARATOR)] + out << [" User Home", Gem.user_home] + out << [" User Path", Gem.user_dir] + out << [" Bin Dir", Gem.bindir] + if defined?(OpenSSL::SSL) + out << ["OpenSSL"] + out << [" Compiled", OpenSSL::OPENSSL_VERSION] if defined?(OpenSSL::OPENSSL_VERSION) + out << [" Loaded", OpenSSL::OPENSSL_LIBRARY_VERSION] if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION) + out << [" Cert File", OpenSSL::X509::DEFAULT_CERT_FILE] if defined?(OpenSSL::X509::DEFAULT_CERT_FILE) + out << [" Cert Dir", OpenSSL::X509::DEFAULT_CERT_DIR] if defined?(OpenSSL::X509::DEFAULT_CERT_DIR) + end + out << ["Tools"] + out << [" Git", git_version] + out << [" RVM", ENV.fetch("rvm_version") { version_of("rvm") }] + out << [" rbenv", version_of("rbenv")] + out << [" chruby", chruby_version] + + %w[rubygems-bundler open_gem].each do |name| + specs = Bundler.rubygems.find_name(name) + out << [" #{name}", "(#{specs.map(&:version).join(",")})"] unless specs.empty? + end + if (exe = caller.last.split(":").first) && exe =~ %r{(exe|bin)/bundler?\z} + shebang = File.read(exe).lines.first + shebang.sub!(/^#!\s*/, "") + unless shebang.start_with?(Gem.ruby, "/usr/bin/env ruby") + out << ["Gem.ruby", Gem.ruby] + out << ["bundle #!", shebang] + end + end + + out + end + + def self.append_formatted_table(title, pairs, out) + return if pairs.empty? + out << "\n" unless out.empty? + out << "## #{title}\n\n```\n" + ljust = pairs.map {|k, _v| k.to_s.length }.max + pairs.each do |k, v| + out << "#{k.to_s.ljust(ljust)} #{v}\n" + end + out << "```\n" + end + + private_class_method :read_file, :ruby_version, :git_version, :append_formatted_table, :version_of, :chruby_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/environment_preserver.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/environment_preserver.rb new file mode 100644 index 0000000000..a77f7e0816 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/environment_preserver.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Bundler + class EnvironmentPreserver + INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL".freeze + BUNDLER_KEYS = %w[ + BUNDLE_BIN_PATH + BUNDLE_GEMFILE + BUNDLER_VERSION + GEM_HOME + GEM_PATH + MANPATH + PATH + RB_USER_INSTALL + RUBYLIB + RUBYOPT + ].map(&:freeze).freeze + BUNDLER_PREFIX = "BUNDLER_ORIG_".freeze + + def self.from_env + new(env_to_hash(ENV), BUNDLER_KEYS) + end + + def self.env_to_hash(env) + to_hash = env.to_hash + return to_hash unless Gem.win_platform? + + to_hash.each_with_object({}) {|(k,v), a| a[k.upcase] = v } + end + + # @param env [Hash] + # @param keys [Array] + def initialize(env, keys) + @original = env + @keys = keys + @prefix = BUNDLER_PREFIX + end + + # Replaces `ENV` with the bundler environment variables backed up + def replace_with_backup + ENV.replace(backup) unless Gem.win_platform? + + # Fallback logic for Windows below to workaround + # https://bugs.ruby-lang.org/issues/16798. Can be dropped once all + # supported rubies include the fix for that. + + ENV.clear + + backup.each {|k, v| ENV[k] = v } + end + + # @return [Hash] + def backup + env = @original.clone + @keys.each do |key| + value = env[key] + if !value.nil? && !value.empty? + env[@prefix + key] ||= value + elsif value.nil? + env[@prefix + key] ||= INTENTIONALLY_NIL + end + end + env + end + + # @return [Hash] + def restore + env = @original.clone + @keys.each do |key| + value_original = env[@prefix + key] + next if value_original.nil? || value_original.empty? + if value_original == INTENTIONALLY_NIL + env.delete(key) + else + env[key] = value_original + end + env.delete(@prefix + key) + end + env + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/errors.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/errors.rb new file mode 100644 index 0000000000..11763b4e88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/errors.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +module Bundler + class BundlerError < StandardError + def self.status_code(code) + define_method(:status_code) { code } + if match = BundlerError.all_errors.find {|_k, v| v == code } + error, _ = match + raise ArgumentError, + "Trying to register #{self} for status code #{code} but #{error} is already registered" + end + BundlerError.all_errors[self] = code + end + + def self.all_errors + @all_errors ||= {} + end + end + + class GemfileError < BundlerError; status_code(4); end + class InstallError < BundlerError; status_code(5); end + + # Internal error, should be rescued + class VersionConflict < BundlerError + attr_reader :conflicts + + def initialize(conflicts, msg = nil) + super(msg) + @conflicts = conflicts + end + + status_code(6) + end + + class GemNotFound < BundlerError; status_code(7); end + class InstallHookError < BundlerError; status_code(8); end + class GemfileNotFound < BundlerError; status_code(10); end + class GitError < BundlerError; status_code(11); end + class DeprecatedError < BundlerError; status_code(12); end + class PathError < BundlerError; status_code(13); end + class GemspecError < BundlerError; status_code(14); end + class InvalidOption < BundlerError; status_code(15); end + class ProductionError < BundlerError; status_code(16); end + class HTTPError < BundlerError + status_code(17) + def filter_uri(uri) + URICredentialsFilter.credential_filtered_uri(uri) + end + end + class RubyVersionMismatch < BundlerError; status_code(18); end + class SecurityError < BundlerError; status_code(19); end + class LockfileError < BundlerError; status_code(20); end + class CyclicDependencyError < BundlerError; status_code(21); end + class GemfileLockNotFound < BundlerError; status_code(22); end + class PluginError < BundlerError; status_code(29); end + class SudoNotPermittedError < BundlerError; status_code(30); end + class ThreadCreationError < BundlerError; status_code(33); end + class APIResponseMismatchError < BundlerError; status_code(34); end + class APIResponseInvalidDependenciesError < BundlerError; status_code(35); end + class GemfileEvalError < GemfileError; end + class MarshalError < StandardError; end + + class PermissionError < BundlerError + def initialize(path, permission_type = :write) + @path = path + @permission_type = permission_type + end + + def action + case @permission_type + when :read then "read from" + when :write then "write to" + when :executable, :exec then "execute" + else @permission_type.to_s + end + end + + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "It is likely that you need to grant #{@permission_type} permissions " \ + "for that path." + end + + status_code(23) + end + + class GemRequireError < BundlerError + attr_reader :orig_exception + + def initialize(orig_exception, msg) + full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\ + "Backtrace for gem load error is:\n"\ + "#{orig_exception.backtrace.join("\n")}\n"\ + "Bundler Error Backtrace:\n" + super(full_message) + @orig_exception = orig_exception + end + + status_code(24) + end + + class YamlSyntaxError < BundlerError + attr_reader :orig_exception + + def initialize(orig_exception, msg) + super(msg) + @orig_exception = orig_exception + end + + status_code(25) + end + + class TemporaryResourceError < PermissionError + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "Some resource was temporarily unavailable. It's suggested that you try" \ + "the operation again." + end + + status_code(26) + end + + class VirtualProtocolError < BundlerError + def message + "There was an error relating to virtualization and file access." \ + "It is likely that you need to grant access to or mount some file system correctly." + end + + status_code(27) + end + + class OperationNotSupportedError < PermissionError + def message + "Attempting to #{action} `#{@path}` is unsupported by your OS." + end + + status_code(28) + end + + class NoSpaceOnDeviceError < PermissionError + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "There was insufficient space remaining on the device." + end + + status_code(31) + end + + class GenericSystemCallError < BundlerError + attr_reader :underlying_error + + def initialize(underlying_error, message) + @underlying_error = underlying_error + super("#{message}\nThe underlying system error is #{@underlying_error.class}: #{@underlying_error}") + end + + status_code(32) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/feature_flag.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/feature_flag.rb new file mode 100644 index 0000000000..e441b941c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/feature_flag.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Bundler + class FeatureFlag + def self.settings_flag(flag, &default) + unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) + raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" + end + + settings_method("#{flag}?", flag, &default) + end + private_class_method :settings_flag + + def self.settings_option(key, &default) + settings_method(key, key, &default) + end + private_class_method :settings_option + + def self.settings_method(name, key, &default) + define_method(name) do + value = Bundler.settings[key] + value = instance_eval(&default) if value.nil? + value + end + end + private_class_method :settings_method + + (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } } + + settings_flag(:allow_offline_install) { bundler_3_mode? } + settings_flag(:auto_clean_without_path) { bundler_3_mode? } + settings_flag(:cache_all) { bundler_3_mode? } + settings_flag(:default_install_uses_path) { bundler_3_mode? } + settings_flag(:forget_cli_options) { bundler_3_mode? } + settings_flag(:global_gem_cache) { bundler_3_mode? } + settings_flag(:path_relative_to_cwd) { bundler_3_mode? } + settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } + settings_flag(:print_only_version_number) { bundler_3_mode? } + settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? } + settings_flag(:suppress_install_using_messages) { bundler_3_mode? } + settings_flag(:update_requires_all_flag) { bundler_4_mode? } + settings_flag(:use_gem_version_promoter_for_major_updates) { bundler_3_mode? } + + settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install } + + def initialize(bundler_version) + @bundler_version = Gem::Version.create(bundler_version) + end + + def major_version + @bundler_version.segments.first + end + private :major_version + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher.rb new file mode 100644 index 0000000000..2bbe53aa25 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true + +require_relative "vendored_persistent" +require "cgi" +require "securerandom" +require "zlib" +require "rubygems/request" + +module Bundler + # Handles all the fetching with the rubygems server + class Fetcher + autoload :CompactIndex, File.expand_path("fetcher/compact_index", __dir__) + autoload :Downloader, File.expand_path("fetcher/downloader", __dir__) + autoload :Dependency, File.expand_path("fetcher/dependency", __dir__) + autoload :Index, File.expand_path("fetcher/index", __dir__) + + # This error is raised when it looks like the network is down + class NetworkDownError < HTTPError; end + # This error is raised if we should rate limit our requests to the API + class TooManyRequestsError < HTTPError; end + # This error is raised if the API returns a 413 (only printed in verbose) + class FallbackError < HTTPError; end + # This is the error raised if OpenSSL fails the cert verification + class CertificateFailureError < HTTPError + def initialize(remote_uri) + remote_uri = filter_uri(remote_uri) + super "Could not verify the SSL certificate for #{remote_uri}.\nThere" \ + " is a chance you are experiencing a man-in-the-middle attack, but" \ + " most likely your system doesn't have the CA certificates needed" \ + " for verification. For information about OpenSSL certificates, see" \ + " http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \ + " sources and change 'https' to 'http'." + end + end + # This is the error raised when a source is HTTPS and OpenSSL didn't load + class SSLError < HTTPError + def initialize(msg = nil) + super msg || "Could not load OpenSSL.\n" \ + "You must recompile Ruby with OpenSSL support or change the sources in your " \ + "Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \ + "using RVM are available at rvm.io/packages/openssl." + end + end + # This error is raised if HTTP authentication is required, but not provided. + class AuthenticationRequiredError < HTTPError + def initialize(remote_uri) + remote_uri = filter_uri(remote_uri) + super "Authentication is required for #{remote_uri}.\n" \ + "Please supply credentials for this source. You can do this by running:\n" \ + "`bundle config set --global #{remote_uri} username:password`\n" \ + "or by storing the credentials in the `#{Settings.key_for(remote_uri)}` environment variable" + end + end + # This error is raised if HTTP authentication is provided, but incorrect. + class BadAuthenticationError < HTTPError + def initialize(remote_uri) + remote_uri = filter_uri(remote_uri) + super "Bad username or password for #{remote_uri}.\n" \ + "Please double-check your credentials and correct them." + end + end + + # Exceptions classes that should bypass retry attempts. If your password didn't work the + # first time, it's not going to the third time. + NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency, + :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed, + :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound, + :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze + FAIL_ERRORS = begin + fail_errors = [AuthenticationRequiredError, BadAuthenticationError, FallbackError] + fail_errors << Gem::Requirement::BadRequirementError if defined?(Gem::Requirement::BadRequirementError) + fail_errors.concat(NET_ERRORS.map {|e| SharedHelpers.const_get_safely(e, Net) }.compact) + end.freeze + + class << self + attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries + end + + self.redirect_limit = Bundler.settings[:redirect] # How many redirects to allow in one request + self.api_timeout = Bundler.settings[:timeout] # How long to wait for each API call + self.max_retries = Bundler.settings[:retry] # How many retries for the API call + + def initialize(remote) + @remote = remote + + Socket.do_not_reverse_lookup = true + connection # create persistent connection + end + + def uri + @remote.anonymized_uri + end + + # fetch a gem specification + def fetch_spec(spec) + spec -= [nil, "ruby", ""] + spec_file_name = "#{spec.join "-"}.gemspec" + + uri = Bundler::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz") + if uri.scheme == "file" + path = Bundler.rubygems.correct_for_windows_path(uri.path) + Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(path)) + elsif cached_spec_path = gemspec_cached_path(spec_file_name) + Bundler.load_gemspec(cached_spec_path) + else + Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body) + end + rescue MarshalError + raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \ + "Your network or your gem server is probably having issues right now." + end + + # return the specs in the bundler format as an index with retries + def specs_with_retry(gem_names, source) + Bundler::Retry.new("fetcher", FAIL_ERRORS).attempts do + specs(gem_names, source) + end + end + + # return the specs in the bundler format as an index + def specs(gem_names, source) + old = Bundler.rubygems.sources + index = Bundler::Index.new + + if Bundler::Fetcher.disable_endpoint + @use_api = false + specs = fetchers.last.specs(gem_names) + else + specs = [] + fetchers.shift until fetchers.first.available? || fetchers.empty? + fetchers.dup.each do |f| + break unless f.api_fetcher? && !gem_names || !specs = f.specs(gem_names) + fetchers.delete(f) + end + @use_api = false if fetchers.none?(&:api_fetcher?) + end + + specs.each do |name, version, platform, dependencies, metadata| + spec = if dependencies + EndpointSpecification.new(name, version, platform, dependencies, metadata) + else + RemoteSpecification.new(name, version, platform, self) + end + spec.source = source + spec.remote = @remote + index << spec + end + + index + rescue CertificateFailureError + Bundler.ui.info "" if gem_names && use_api # newline after dots + raise + ensure + Bundler.rubygems.sources = old + end + + def use_api + return @use_api if defined?(@use_api) + + fetchers.shift until fetchers.first.available? + + @use_api = if remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint + false + else + fetchers.first.api_fetcher? + end + end + + def user_agent + @user_agent ||= begin + ruby = Bundler::RubyVersion.system + + agent = String.new("bundler/#{Bundler::VERSION}") + agent << " rubygems/#{Gem::VERSION}" + agent << " ruby/#{ruby.versions_string(ruby.versions)}" + agent << " (#{ruby.host})" + agent << " command/#{ARGV.first}" + + if ruby.engine != "ruby" + # engine_version raises on unknown engines + engine_version = begin + ruby.engine_versions + rescue RuntimeError + "???" + end + agent << " #{ruby.engine}/#{ruby.versions_string(engine_version)}" + end + + agent << " options/#{Bundler.settings.all.join(",")}" + + agent << " ci/#{cis.join(",")}" if cis.any? + + # add a random ID so we can consolidate runs server-side + agent << " " << SecureRandom.hex(8) + + # add any user agent strings set in the config + extra_ua = Bundler.settings[:user_agent] + agent << " " << extra_ua if extra_ua + + agent + end + end + + def fetchers + @fetchers ||= FETCHERS.map {|f| f.new(downloader, @remote, uri) } + end + + def http_proxy + return unless uri = connection.proxy_uri + uri.to_s + end + + def inspect + "#<#{self.class}:0x#{object_id} uri=#{uri}>" + end + + private + + FETCHERS = [CompactIndex, Dependency, Index].freeze + + def cis + env_cis = { + "TRAVIS" => "travis", + "CIRCLECI" => "circle", + "SEMAPHORE" => "semaphore", + "JENKINS_URL" => "jenkins", + "BUILDBOX" => "buildbox", + "GO_SERVER_URL" => "go", + "SNAP_CI" => "snap", + "GITLAB_CI" => "gitlab", + "CI_NAME" => ENV["CI_NAME"], + "CI" => "ci", + } + env_cis.find_all {|env, _| ENV[env] }.map {|_, ci| ci } + end + + def connection + @connection ||= begin + needs_ssl = remote_uri.scheme == "https" || + Bundler.settings[:ssl_verify_mode] || + Bundler.settings[:ssl_client_cert] + raise SSLError if needs_ssl && !defined?(OpenSSL::SSL) + + con = PersistentHTTP.new :name => "bundler", :proxy => :ENV + if gem_proxy = Bundler.rubygems.configuration[:http_proxy] + con.proxy = Bundler::URI.parse(gem_proxy) if gem_proxy != :no_proxy + end + + if remote_uri.scheme == "https" + con.verify_mode = (Bundler.settings[:ssl_verify_mode] || + OpenSSL::SSL::VERIFY_PEER) + con.cert_store = bundler_cert_store + end + + ssl_client_cert = Bundler.settings[:ssl_client_cert] || + (Bundler.rubygems.configuration.ssl_client_cert if + Bundler.rubygems.configuration.respond_to?(:ssl_client_cert)) + if ssl_client_cert + pem = File.read(ssl_client_cert) + con.cert = OpenSSL::X509::Certificate.new(pem) + con.key = OpenSSL::PKey::RSA.new(pem) + end + + con.read_timeout = Fetcher.api_timeout + con.open_timeout = Fetcher.api_timeout + con.override_headers["User-Agent"] = user_agent + con.override_headers["X-Gemfile-Source"] = @remote.original_uri.to_s if @remote.original_uri + con + end + end + + # cached gem specification path, if one exists + def gemspec_cached_path(spec_file_name) + paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) } + paths = paths.select {|path| File.file? path } + paths.first + end + + HTTP_ERRORS = [ + Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH, + Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN, + Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, + PersistentHTTP::Error, Zlib::BufError, Errno::EHOSTUNREACH + ].freeze + + def bundler_cert_store + store = OpenSSL::X509::Store.new + ssl_ca_cert = Bundler.settings[:ssl_ca_cert] || + (Bundler.rubygems.configuration.ssl_ca_cert if + Bundler.rubygems.configuration.respond_to?(:ssl_ca_cert)) + if ssl_ca_cert + if File.directory? ssl_ca_cert + store.add_path ssl_ca_cert + else + store.add_file ssl_ca_cert + end + else + store.set_default_paths + Gem::Request.get_cert_files.each {|c| store.add_file c } + end + store + end + + private + + def remote_uri + @remote.uri + end + + def downloader + @downloader ||= Downloader.new(connection, self.class.redirect_limit) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/base.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/base.rb new file mode 100644 index 0000000000..16cc98273a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/base.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Bundler + class Fetcher + class Base + attr_reader :downloader + attr_reader :display_uri + attr_reader :remote + + def initialize(downloader, remote, display_uri) + raise "Abstract class" if self.class == Base + @downloader = downloader + @remote = remote + @display_uri = display_uri + end + + def remote_uri + @remote.uri + end + + def fetch_uri + @fetch_uri ||= begin + if remote_uri.host == "rubygems.org" + uri = remote_uri.dup + uri.host = "index.rubygems.org" + uri + else + remote_uri + end + end + end + + def available? + true + end + + def api_fetcher? + false + end + + private + + def log_specs(debug_msg) + if Bundler.ui.debug? + Bundler.ui.debug debug_msg + else + Bundler.ui.info ".", false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/compact_index.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/compact_index.rb new file mode 100644 index 0000000000..bc69b884ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/compact_index.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require_relative "base" +require_relative "../worker" + +module Bundler + autoload :CompactIndexClient, File.expand_path("../compact_index_client", __dir__) + + class Fetcher + class CompactIndex < Base + def self.compact_index_request(method_name) + method = instance_method(method_name) + undef_method(method_name) + define_method(method_name) do |*args, &blk| + begin + method.bind(self).call(*args, &blk) + rescue NetworkDownError, CompactIndexClient::Updater::MisMatchedChecksumError => e + raise HTTPError, e.message + rescue AuthenticationRequiredError + # Fail since we got a 401 from the server. + raise + rescue HTTPError => e + Bundler.ui.trace(e) + nil + end + end + end + + def specs(gem_names) + specs_for_names(gem_names) + end + compact_index_request :specs + + def specs_for_names(gem_names) + gem_info = [] + complete_gems = [] + remaining_gems = gem_names.dup + + until remaining_gems.empty? + log_specs "Looking up gems #{remaining_gems.inspect}" + + deps = begin + parallel_compact_index_client.dependencies(remaining_gems) + rescue TooManyRequestsError + @bundle_worker.stop if @bundle_worker + @bundle_worker = nil # reset it. Not sure if necessary + serial_compact_index_client.dependencies(remaining_gems) + end + next_gems = deps.map {|d| d[3].map(&:first).flatten(1) }.flatten(1).uniq + deps.each {|dep| gem_info << dep } + complete_gems.concat(deps.map(&:first)).uniq! + remaining_gems = next_gems - complete_gems + end + @bundle_worker.stop if @bundle_worker + @bundle_worker = nil # reset it. Not sure if necessary + + gem_info + end + + def fetch_spec(spec) + spec -= [nil, "ruby", ""] + contents = compact_index_client.spec(*spec) + return nil if contents.nil? + contents.unshift(spec.first) + contents[3].map! {|d| Gem::Dependency.new(*d) } + EndpointSpecification.new(*contents) + end + compact_index_request :fetch_spec + + def available? + return nil unless SharedHelpers.md5_available? + user_home = Bundler.user_home + return nil unless user_home.directory? && user_home.writable? + # Read info file checksums out of /versions, so we can know if gems are up to date + fetch_uri.scheme != "file" && compact_index_client.update_and_parse_checksums! + rescue CompactIndexClient::Updater::MisMatchedChecksumError => e + Bundler.ui.debug(e.message) + nil + end + compact_index_request :available? + + def api_fetcher? + true + end + + private + + def compact_index_client + @compact_index_client ||= + SharedHelpers.filesystem_access(cache_path) do + CompactIndexClient.new(cache_path, client_fetcher) + end + end + + def parallel_compact_index_client + compact_index_client.execution_mode = lambda do |inputs, &blk| + func = lambda {|object, _index| blk.call(object) } + worker = bundle_worker(func) + inputs.each {|input| worker.enq(input) } + inputs.map { worker.deq } + end + + compact_index_client + end + + def serial_compact_index_client + compact_index_client.sequential_execution_mode! + compact_index_client + end + + def bundle_worker(func = nil) + @bundle_worker ||= begin + worker_name = "Compact Index (#{display_uri.host})" + Bundler::Worker.new(Bundler.settings.processor_count, worker_name, func) + end + @bundle_worker.tap do |worker| + worker.instance_variable_set(:@func, func) if func + end + end + + def cache_path + Bundler.user_cache.join("compact_index", remote.cache_slug) + end + + def client_fetcher + ClientFetcher.new(self, Bundler.ui) + end + + ClientFetcher = Struct.new(:fetcher, :ui) do + def call(path, headers) + fetcher.downloader.fetch(fetcher.fetch_uri + path, headers) + rescue NetworkDownError => e + raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] + ui.warn "Using the cached data for the new index because of a network error: #{e}" + Net::HTTPNotModified.new(nil, nil, nil) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/dependency.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/dependency.rb new file mode 100644 index 0000000000..c52c32fb5b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/dependency.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require_relative "base" +require "cgi" + +module Bundler + class Fetcher + class Dependency < Base + def available? + @available ||= fetch_uri.scheme != "file" && downloader.fetch(dependency_api_uri) + rescue NetworkDownError => e + raise HTTPError, e.message + rescue AuthenticationRequiredError + # Fail since we got a 401 from the server. + raise + rescue HTTPError + false + end + + def api_fetcher? + true + end + + def specs(gem_names, full_dependency_list = [], last_spec_list = []) + query_list = gem_names.uniq - full_dependency_list + + log_specs "Query List: #{query_list.inspect}" + + return last_spec_list if query_list.empty? + + spec_list, deps_list = Bundler::Retry.new("dependency api", FAIL_ERRORS).attempts do + dependency_specs(query_list) + end + + returned_gems = spec_list.map(&:first).uniq + specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list) + rescue MarshalError + Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over + Bundler.ui.debug "could not fetch from the dependency API, trying the full index" + nil + rescue HTTPError, GemspecError + Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over + Bundler.ui.debug "could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`" + nil + end + + def dependency_specs(gem_names) + Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(",")}" + + gem_list = unmarshalled_dep_gems(gem_names) + get_formatted_specs_and_deps(gem_list) + end + + def unmarshalled_dep_gems(gem_names) + gem_list = [] + gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| + marshalled_deps = downloader.fetch(dependency_api_uri(names)).body + gem_list.concat(Bundler.load_marshal(marshalled_deps)) + end + gem_list + end + + def get_formatted_specs_and_deps(gem_list) + deps_list = [] + spec_list = [] + + gem_list.each do |s| + deps_list.concat(s[:dependencies].map(&:first)) + deps = s[:dependencies].map {|n, d| [n, d.split(", ")] } + spec_list.push([s[:name], s[:number], s[:platform], deps]) + end + [spec_list, deps_list] + end + + def dependency_api_uri(gem_names = []) + uri = fetch_uri + "api/v1/dependencies" + uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any? + uri + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/downloader.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/downloader.rb new file mode 100644 index 0000000000..f2aad3a500 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/downloader.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module Bundler + class Fetcher + class Downloader + attr_reader :connection + attr_reader :redirect_limit + + def initialize(connection, redirect_limit) + @connection = connection + @redirect_limit = redirect_limit + end + + def fetch(uri, headers = {}, counter = 0) + raise HTTPError, "Too many redirects" if counter >= redirect_limit + + filtered_uri = URICredentialsFilter.credential_filtered_uri(uri) + + response = request(uri, headers) + Bundler.ui.debug("HTTP #{response.code} #{response.message} #{filtered_uri}") + + case response + when Net::HTTPSuccess, Net::HTTPNotModified + response + when Net::HTTPRedirection + new_uri = Bundler::URI.parse(response["location"]) + if new_uri.host == uri.host + new_uri.user = uri.user + new_uri.password = uri.password + end + fetch(new_uri, headers, counter + 1) + when Net::HTTPRequestedRangeNotSatisfiable + new_headers = headers.dup + new_headers.delete("Range") + new_headers["Accept-Encoding"] = "gzip" + fetch(uri, new_headers) + when Net::HTTPRequestEntityTooLarge + raise FallbackError, response.body + when Net::HTTPTooManyRequests + raise TooManyRequestsError, response.body + when Net::HTTPUnauthorized + raise BadAuthenticationError, uri.host if uri.userinfo + raise AuthenticationRequiredError, uri.host + when Net::HTTPNotFound + raise FallbackError, "Net::HTTPNotFound: #{filtered_uri}" + else + raise HTTPError, "#{response.class}#{": #{response.body}" unless response.body.empty?}" + end + end + + def request(uri, headers) + validate_uri_scheme!(uri) + + filtered_uri = URICredentialsFilter.credential_filtered_uri(uri) + + Bundler.ui.debug "HTTP GET #{filtered_uri}" + req = Net::HTTP::Get.new uri.request_uri, headers + if uri.user + user = CGI.unescape(uri.user) + password = uri.password ? CGI.unescape(uri.password) : nil + req.basic_auth(user, password) + end + connection.request(uri, req) + rescue NoMethodError => e + raise unless ["undefined method", "use_ssl="].all? {|snippet| e.message.include? snippet } + raise LoadError.new("cannot load such file -- openssl") + rescue OpenSSL::SSL::SSLError + raise CertificateFailureError.new(uri) + rescue *HTTP_ERRORS => e + Bundler.ui.trace e + if e.is_a?(SocketError) || e.message =~ /host down:/ + raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ + "connection and try again." + else + raise HTTPError, "Network error while fetching #{filtered_uri}" \ + " (#{e})" + end + end + + private + + def validate_uri_scheme!(uri) + return if uri.scheme =~ /\Ahttps?\z/ + raise InvalidOption, + "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \ + "Did you mean `http` or `https`?" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/index.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/index.rb new file mode 100644 index 0000000000..0d14c47aa7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/fetcher/index.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative "base" + +module Bundler + class Fetcher + class Index < Base + def specs(_gem_names) + Bundler.rubygems.fetch_all_remote_specs(remote) + rescue Gem::RemoteFetcher::FetchError => e + case e.message + when /certificate verify failed/ + raise CertificateFailureError.new(display_uri) + when /401/ + raise BadAuthenticationError, remote_uri if remote_uri.userinfo + raise AuthenticationRequiredError, remote_uri + when /403/ + raise BadAuthenticationError, remote_uri if remote_uri.userinfo + raise AuthenticationRequiredError, remote_uri + else + raise HTTPError, "Could not fetch specs from #{display_uri} due to underlying error <#{e.message}>" + end + end + + def fetch_spec(spec) + spec -= [nil, "ruby", ""] + spec_file_name = "#{spec.join "-"}.gemspec" + + uri = Bundler::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz") + if uri.scheme == "file" + path = Bundler.rubygems.correct_for_windows_path(uri.path) + Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(path)) + elsif cached_spec_path = gemspec_cached_path(spec_file_name) + Bundler.load_gemspec(cached_spec_path) + else + Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body) + end + rescue MarshalError + raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \ + "Your network or your gem server is probably having issues right now." + end + + private + + # cached gem specification path, if one exists + def gemspec_cached_path(spec_file_name) + paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) } + paths.find {|path| File.file? path } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/friendly_errors.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/friendly_errors.rb new file mode 100644 index 0000000000..db43e0f654 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/friendly_errors.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require_relative "vendored_thor" + +module Bundler + module FriendlyErrors + module_function + + def enable! + @disabled = false + end + + def disabled? + @disabled + end + + def disable! + @disabled = true + end + + def log_error(error) + case error + when YamlSyntaxError + Bundler.ui.error error.message + Bundler.ui.trace error.orig_exception + when Dsl::DSLError, GemspecError + Bundler.ui.error error.message + when GemRequireError + Bundler.ui.error error.message + Bundler.ui.trace error.orig_exception + when BundlerError + Bundler.ui.error error.message, :wrap => true + Bundler.ui.trace error + when Thor::Error + Bundler.ui.error error.message + when LoadError + raise error unless error.message =~ /cannot load such file -- openssl|openssl.so|libcrypto.so/ + Bundler.ui.error "\nCould not load OpenSSL. #{error.class}: #{error}\n#{error.backtrace.join("\n ")}" + when Interrupt + Bundler.ui.error "\nQuitting..." + Bundler.ui.trace error + when Gem::InvalidSpecificationException + Bundler.ui.error error.message, :wrap => true + when SystemExit + when *[defined?(Java::JavaLang::OutOfMemoryError) && Java::JavaLang::OutOfMemoryError].compact + Bundler.ui.error "\nYour JVM has run out of memory, and Bundler cannot continue. " \ + "You can decrease the amount of memory Bundler needs by removing gems from your Gemfile, " \ + "especially large gems. (Gems can be as large as hundreds of megabytes, and Bundler has to read those files!). " \ + "Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)." + else request_issue_report_for(error) + end + end + + def exit_status(error) + case error + when BundlerError then error.status_code + when Thor::Error then 15 + when SystemExit then error.status + else 1 + end + end + + def request_issue_report_for(e) + Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, nil + --- ERROR REPORT TEMPLATE ------------------------------------------------------- + # Error Report + + ## Questions + + Please fill out answers to these questions, it'll help us figure out + why things are going wrong. + + - **What did you do?** + + I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}` + + - **What did you expect to happen?** + + I expected Bundler to... + + - **What happened instead?** + + Instead, what happened was... + + - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?** + + I tried... + + - **Have you read our issues document, https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md?** + + ... + + ## Backtrace + + ``` + #{e.class}: #{e.message} + #{e.backtrace && e.backtrace.join("\n ").chomp} + ``` + + #{Bundler::Env.report} + --- TEMPLATE END ---------------------------------------------------------------- + + EOS + + Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue." + + Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, :yellow + + First, try this link to see if there are any existing issue reports for this error: + #{issues_url(e)} + + If there aren't any reports for this error yet, please copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at: + https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md + EOS + end + + def issues_url(exception) + message = exception.message.lines.first.tr(":", " ").chomp + message = message.split("-").first if exception.is_a?(Errno) + require "cgi" + "https://github.com/rubygems/rubygems/search?q=" \ + "#{CGI.escape(message)}&type=Issues" + end + end + + def self.with_friendly_errors + FriendlyErrors.enable! + yield + rescue SignalException + raise + rescue Exception => e # rubocop:disable Lint/RescueException + raise if FriendlyErrors.disabled? + + FriendlyErrors.log_error(e) + exit FriendlyErrors.exit_status(e) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helper.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helper.rb new file mode 100644 index 0000000000..6096adfa27 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helper.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require_relative "../bundler" +require "shellwords" + +module Bundler + class GemHelper + include Rake::DSL if defined? Rake::DSL + + class << self + # set when install'd. + attr_accessor :instance + + def install_tasks(opts = {}) + new(opts[:dir], opts[:name]).install + end + + def tag_prefix=(prefix) + instance.tag_prefix = prefix + end + + def gemspec(&block) + gemspec = instance.gemspec + block.call(gemspec) if block + gemspec + end + end + + attr_reader :spec_path, :base, :gemspec + + attr_writer :tag_prefix + + def initialize(base = nil, name = nil) + @base = File.expand_path(base || SharedHelpers.pwd) + gemspecs = name ? [File.join(@base, "#{name}.gemspec")] : Gem::Util.glob_files_in_dir("{,*}.gemspec", @base) + raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1 + @spec_path = gemspecs.first + @gemspec = Bundler.load_gemspec(@spec_path) + @tag_prefix = "" + end + + def install + built_gem_path = nil + + desc "Build #{name}-#{version}.gem into the pkg directory." + task "build" do + built_gem_path = build_gem + end + + desc "Generate SHA512 checksum if #{name}-#{version}.gem into the checksums directory." + task "build:checksum" => "build" do + build_checksum(built_gem_path) + end + + desc "Build and install #{name}-#{version}.gem into system gems." + task "install" => "build" do + install_gem(built_gem_path) + end + + desc "Build and install #{name}-#{version}.gem into system gems without network access." + task "install:local" => "build" do + install_gem(built_gem_path, :local) + end + + desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to #{gem_push_host}\n" \ + "To prevent publishing in RubyGems use `gem_push=no rake release`" + task "release", [:remote] => ["build", "release:guard_clean", + "release:source_control_push", "release:rubygem_push"] do + end + + task "release:guard_clean" do + guard_clean + end + + task "release:source_control_push", [:remote] do |_, args| + tag_version { git_push(args[:remote]) } unless already_tagged? + end + + task "release:rubygem_push" do + rubygem_push(built_gem_path) if gem_push? + end + + GemHelper.instance = self + end + + def build_gem + file_name = nil + sh([*gem_command, "build", "-V", spec_path]) do + file_name = File.basename(built_gem_path) + SharedHelpers.filesystem_access(File.join(base, "pkg")) {|p| FileUtils.mkdir_p(p) } + FileUtils.mv(built_gem_path, "pkg") + Bundler.ui.confirm "#{name} #{version} built to pkg/#{file_name}." + end + File.join(base, "pkg", file_name) + end + + def install_gem(built_gem_path = nil, local = false) + built_gem_path ||= build_gem + cmd = [*gem_command, "install", built_gem_path.to_s] + cmd << "--local" if local + _, status = sh_with_status(cmd) + unless status.success? + raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" + end + Bundler.ui.confirm "#{name} (#{version}) installed." + end + + def build_checksum(built_gem_path = nil) + built_gem_path ||= build_gem + SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) } + file_name = "#{File.basename(built_gem_path)}.sha512" + require "digest/sha2" + checksum = Digest::SHA512.new.hexdigest(built_gem_path.to_s) + target = File.join(base, "checksums", file_name) + File.write(target, checksum) + Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}." + end + + protected + + def rubygem_push(path) + cmd = [*gem_command, "push", path] + cmd << "--key" << gem_key if gem_key + cmd << "--host" << allowed_push_host if allowed_push_host + sh_with_input(cmd) + Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_push_host}" + end + + def built_gem_path + Gem::Util.glob_files_in_dir("#{name}-*.gem", base).sort_by {|f| File.mtime(f) }.last + end + + def git_push(remote = nil) + remote ||= default_remote + perform_git_push "#{remote} refs/heads/#{current_branch}" + perform_git_push "#{remote} refs/tags/#{version_tag}" + Bundler.ui.confirm "Pushed git commits and release tag." + end + + def default_remote + remote_for_branch, status = sh_with_status(%W[git config --get branch.#{current_branch}.remote]) + return "origin" unless status.success? + + remote_for_branch.strip + end + + def current_branch + # We can replace this with `git branch --show-current` once we drop support for git < 2.22.0 + sh(%w[git rev-parse --abbrev-ref HEAD]).gsub(%r{\Aheads/}, "").strip + end + + def allowed_push_host + @gemspec.metadata["allowed_push_host"] if @gemspec.respond_to?(:metadata) + end + + def gem_push_host + env_rubygems_host = ENV["RUBYGEMS_HOST"] + env_rubygems_host = nil if + env_rubygems_host && env_rubygems_host.empty? + + allowed_push_host || env_rubygems_host || "rubygems.org" + end + + def perform_git_push(options = "") + cmd = "git push #{options}" + out, status = sh_with_status(cmd.shellsplit) + return if status.success? + raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" + end + + def already_tagged? + return false unless sh(%w[git tag]).split(/\n/).include?(version_tag) + Bundler.ui.confirm "Tag #{version_tag} has already been created." + true + end + + def guard_clean + clean? && committed? || raise("There are files that need to be committed first.") + end + + def clean? + sh_with_status(%w[git diff --exit-code])[1].success? + end + + def committed? + sh_with_status(%w[git diff-index --quiet --cached HEAD])[1].success? + end + + def tag_version + sh %W[git tag -m Version\ #{version} #{version_tag}] + Bundler.ui.confirm "Tagged #{version_tag}." + yield if block_given? + rescue RuntimeError + Bundler.ui.error "Untagging #{version_tag} due to error." + sh_with_status %W[git tag -d #{version_tag}] + raise + end + + def version + gemspec.version + end + + def version_tag + "#{@tag_prefix}v#{version}" + end + + def name + gemspec.name + end + + def sh_with_input(cmd) + Bundler.ui.debug(cmd) + SharedHelpers.chdir(base) do + abort unless Kernel.system(*cmd) + end + end + + def sh(cmd, &block) + out, status = sh_with_status(cmd, &block) + unless status.success? + cmd = cmd.shelljoin if cmd.respond_to?(:shelljoin) + raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out) + end + out + end + + def sh_with_status(cmd, &block) + Bundler.ui.debug(cmd) + SharedHelpers.chdir(base) do + outbuf = IO.popen(cmd, :err => [:child, :out], &:read) + status = $? + block.call(outbuf) if status.success? && block + [outbuf, status] + end + end + + def gem_key + Bundler.settings["gem.push_key"].to_s.downcase if Bundler.settings["gem.push_key"] + end + + def gem_push? + !%w[n no nil false off 0].include?(ENV["gem_push"].to_s.downcase) + end + + def gem_command + ENV["GEM_COMMAND"]&.shellsplit || ["gem"] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helpers.rb new file mode 100644 index 0000000000..b271b8d229 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_helpers.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module Bundler + module GemHelpers + GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant + GENERICS = [ + [Gem::Platform.new("java"), Gem::Platform.new("java")], + [Gem::Platform.new("mswin32"), Gem::Platform.new("mswin32")], + [Gem::Platform.new("mswin64"), Gem::Platform.new("mswin64")], + [Gem::Platform.new("universal-mingw32"), Gem::Platform.new("universal-mingw32")], + [Gem::Platform.new("x64-mingw32"), Gem::Platform.new("x64-mingw32")], + [Gem::Platform.new("x86_64-mingw32"), Gem::Platform.new("x64-mingw32")], + [Gem::Platform.new("mingw32"), Gem::Platform.new("x86-mingw32")], + ].freeze + + def generic(p) + GENERIC_CACHE[p] ||= begin + _, found = GENERICS.find do |match, _generic| + p.os == match.os && (!match.cpu || p.cpu == match.cpu) + end + found || Gem::Platform::RUBY + end + end + module_function :generic + + def generic_local_platform + generic(local_platform) + end + module_function :generic_local_platform + + def local_platform + Bundler.local_platform + end + module_function :local_platform + + def platform_specificity_match(spec_platform, user_platform) + spec_platform = Gem::Platform.new(spec_platform) + + PlatformMatch.specificity_score(spec_platform, user_platform) + end + module_function :platform_specificity_match + + def select_best_platform_match(specs, platform) + matching = specs.select {|spec| spec.match_platform(platform) } + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? + + sorted_matching = matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) } + exemplary_spec = sorted_matching.first + + sorted_matching.take_while{|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) } + end + module_function :select_best_platform_match + + class PlatformMatch + def self.specificity_score(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + platform_version_match(spec_platform, user_platform) * 100 + end + + def self.os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def self.cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def self.platform_version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end + + def same_specificity(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + module_function :same_specificity + + def same_deps(spec, exemplary_spec) + same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort + return same_runtime_deps unless spec.is_a?(Gem::Specification) && exemplary_spec.is_a?(Gem::Specification) + + same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version + same_runtime_deps && same_metadata_deps + end + module_function :same_deps + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_tasks.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_tasks.rb new file mode 100644 index 0000000000..bc725d3602 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_tasks.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "rake/clean" +CLOBBER.include "pkg" + +require_relative "gem_helper" +Bundler::GemHelper.install_tasks diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_version_promoter.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_version_promoter.rb new file mode 100644 index 0000000000..3cce3f2139 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gem_version_promoter.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +module Bundler + # This class contains all of the logic for determining the next version of a + # Gem to update to based on the requested level (patch, minor, major). + # Primarily designed to work with Resolver which will provide it the list of + # available dependency versions as found in its index, before returning it to + # to the resolution engine to select the best version. + class GemVersionPromoter + DEBUG = ENV["BUNDLER_DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER"] + + attr_reader :level, :locked_specs, :unlock_gems + + # By default, strict is false, meaning every available version of a gem + # is returned from sort_versions. The order gives preference to the + # requested level (:patch, :minor, :major) but in complicated requirement + # cases some gems will by necessity by promoted past the requested level, + # or even reverted to older versions. + # + # If strict is set to true, the results from sort_versions will be + # truncated, eliminating any version outside the current level scope. + # This can lead to unexpected outcomes or even VersionConflict exceptions + # that report a version of a gem not existing for versions that indeed do + # existing in the referenced source. + attr_accessor :strict + + attr_accessor :prerelease_specified + + # Given a list of locked_specs and a list of gems to unlock creates a + # GemVersionPromoter instance. + # + # @param locked_specs [SpecSet] All current locked specs. Unlike Definition + # where this list is empty if all gems are being updated, this should + # always be populated for all gems so this class can properly function. + # @param unlock_gems [String] List of gem names being unlocked. If empty, + # all gems will be considered unlocked. + # @return [GemVersionPromoter] + def initialize(locked_specs = SpecSet.new([]), unlock_gems = []) + @level = :major + @strict = false + @locked_specs = locked_specs + @unlock_gems = unlock_gems + @sort_versions = {} + @prerelease_specified = {} + end + + # @param value [Symbol] One of three Symbols: :major, :minor or :patch. + def level=(value) + v = case value + when String, Symbol + value.to_sym + end + + raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v) + @level = v + end + + # Given a Dependency and an Array of SpecGroups of available versions for a + # gem, this method will return the Array of SpecGroups sorted (and possibly + # truncated if strict is true) in an order to give preference to the current + # level (:major, :minor or :patch) when resolution is deciding what versions + # best resolve all dependencies in the bundle. + # @param dep [Dependency] The Dependency of the gem. + # @param spec_groups [SpecGroup] An array of SpecGroups for the same gem + # named in the @dep param. + # @return [SpecGroup] A new instance of the SpecGroup Array sorted and + # possibly filtered. + def sort_versions(dep, spec_groups) + before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if DEBUG + + @sort_versions[dep] ||= begin + gem_name = dep.name + + # An Array per version returned, different entries for different platforms. + # We only need the version here so it's ok to hard code this to the first instance. + locked_spec = locked_specs[gem_name].first + + if strict + filter_dep_specs(spec_groups, locked_spec) + else + sort_dep_specs(spec_groups, locked_spec) + end.tap do |specs| + if DEBUG + puts before_result + puts " after sort_versions: #{debug_format_result(dep, specs).inspect}" + end + end + end + end + + # @return [bool] Convenience method for testing value of level variable. + def major? + level == :major + end + + # @return [bool] Convenience method for testing value of level variable. + def minor? + level == :minor + end + + private + + def filter_dep_specs(spec_groups, locked_spec) + res = spec_groups.select do |spec_group| + if locked_spec && !major? + gsv = spec_group.version + lsv = locked_spec.version + + must_match = minor? ? [0] : [0, 1] + + matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] } + matches.uniq == [true] ? (gsv >= lsv) : false + else + true + end + end + + sort_dep_specs(res, locked_spec) + end + + def sort_dep_specs(spec_groups, locked_spec) + return spec_groups unless locked_spec + @gem_name = locked_spec.name + @locked_version = locked_spec.version + + result = spec_groups.sort do |a, b| + @a_ver = a.version + @b_ver = b.version + + unless @prerelease_specified[@gem_name] + a_pre = @a_ver.prerelease? + b_pre = @b_ver.prerelease? + + next -1 if a_pre && !b_pre + next 1 if b_pre && !a_pre + end + + if major? + @a_ver <=> @b_ver + elsif either_version_older_than_locked + @a_ver <=> @b_ver + elsif segments_do_not_match(:major) + @b_ver <=> @a_ver + elsif !minor? && segments_do_not_match(:minor) + @b_ver <=> @a_ver + else + @a_ver <=> @b_ver + end + end + post_sort(result) + end + + def either_version_older_than_locked + @a_ver < @locked_version || @b_ver < @locked_version + end + + def segments_do_not_match(level) + index = [:major, :minor].index(level) + @a_ver.segments[index] != @b_ver.segments[index] + end + + def unlocking_gem? + unlock_gems.empty? || unlock_gems.include?(@gem_name) + end + + # Specific version moves can't always reliably be done during sorting + # as not all elements are compared against each other. + def post_sort(result) + # default :major behavior in Bundler does not do this + return result if major? + if unlocking_gem? + result + else + move_version_to_end(result, @locked_version) + end + end + + def move_version_to_end(result, version) + move, keep = result.partition {|s| s.version.to_s == version.to_s } + keep.concat(move) + end + + def debug_format_result(dep, spec_groups) + a = [dep.to_s, + spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }] + last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] } + [a.first, last_map, level, strict ? :strict : :not_strict] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gemdeps.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gemdeps.rb new file mode 100644 index 0000000000..cd4b25d0e6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/gemdeps.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Bundler + class Gemdeps + def initialize(runtime) + @runtime = runtime + end + + def requested_specs + @runtime.requested_specs + end + + def specs + @runtime.specs + end + + def dependencies + @runtime.dependencies + end + + def current_dependencies + @runtime.current_dependencies + end + + def requires + @runtime.requires + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/graph.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/graph.rb new file mode 100644 index 0000000000..8f52e2f0f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/graph.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require "set" +module Bundler + class Graph + GRAPH_NAME = :Gemfile + + def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) + @env = env + @output_file = output_file + @show_version = show_version + @show_requirements = show_requirements + @output_format = output_format + @without_groups = without.map(&:to_sym) + + @groups = [] + @relations = Hash.new {|h, k| h[k] = Set.new } + @node_options = {} + @edge_options = {} + + _populate_relations + end + + attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format + + def viz + GraphVizClient.new(self).run + end + + private + + def _populate_relations + parent_dependencies = _groups.values.to_set.flatten + loop do + break if parent_dependencies.empty? + + tmp = Set.new + parent_dependencies.each do |dependency| + child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set + @relations[dependency.name] += child_dependencies.map(&:name).to_set + tmp += child_dependencies + + @node_options[dependency.name] = _make_label(dependency, :node) + child_dependencies.each do |c_dependency| + @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge) + end + end + parent_dependencies = tmp + end + end + + def _groups + relations = Hash.new {|h, k| h[k] = Set.new } + @env.current_dependencies.each do |dependency| + dependency.groups.each do |group| + next if @without_groups.include?(group) + + relations[group.to_s].add(dependency) + @relations[group.to_s].add(dependency.name) + + @node_options[group.to_s] ||= _make_label(group, :node) + @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge) + end + end + @groups = relations.keys + relations + end + + def _make_label(symbol_or_string_or_dependency, element_type) + case element_type.to_sym + when :node + if symbol_or_string_or_dependency.is_a?(Gem::Dependency) + label = symbol_or_string_or_dependency.name.dup + label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version + else + label = symbol_or_string_or_dependency.to_s + end + when :edge + label = nil + if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements + tmp = symbol_or_string_or_dependency.requirements_list.join(", ") + label = tmp if tmp != ">= 0" + end + else + raise ArgumentError, "2nd argument is invalid" + end + label.nil? ? {} : { :label => label } + end + + def spec_for_dependency(dependency) + @env.requested_specs.find {|s| s.name == dependency.name } + end + + class GraphVizClient + def initialize(graph_instance) + @graph_name = graph_instance.class::GRAPH_NAME + @groups = graph_instance.groups + @relations = graph_instance.relations + @node_options = graph_instance.node_options + @edge_options = graph_instance.edge_options + @output_file = graph_instance.output_file + @output_format = graph_instance.output_format + end + + def g + @g ||= ::GraphViz.digraph(@graph_name, :concentrate => true, :normalize => true, :nodesep => 0.55) do |g| + g.edge[:weight] = 2 + g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif" + g.edge[:fontsize] = 12 + end + end + + def run + @groups.each do |group| + g.add_nodes( + group, { + :style => "filled", + :fillcolor => "#B9B9D5", + :shape => "box3d", + :fontsize => 16, + }.merge(@node_options[group]) + ) + end + + @relations.each do |parent, children| + children.each do |child| + if @groups.include?(parent) + g.add_nodes(child, { :style => "filled", :fillcolor => "#B9B9D5" }.merge(@node_options[child])) + g.add_edges(parent, child, { :constraint => false }.merge(@edge_options["#{parent}_#{child}"])) + else + g.add_nodes(child, @node_options[child]) + g.add_edges(parent, child, @edge_options["#{parent}_#{child}"]) + end + end + end + + if @output_format.to_s == "debug" + $stdout.puts g.output :none => String + Bundler.ui.info "debugging bundle viz..." + else + begin + g.output @output_format.to_sym => "#{@output_file}.#{@output_format}" + Bundler.ui.info "#{@output_file}.#{@output_format}" + rescue ArgumentError => e + warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb" + raise e + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/index.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/index.rb new file mode 100644 index 0000000000..8930fca6d0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/index.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +module Bundler + class Index + include Enumerable + + def self.build + i = new + yield i + i + end + + attr_reader :specs, :all_specs, :sources + protected :specs, :all_specs + + RUBY = "ruby".freeze + NULL = "\0".freeze + + def initialize + @sources = [] + @cache = {} + @specs = Hash.new {|h, k| h[k] = {} } + @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH } + end + + def initialize_copy(o) + @sources = o.sources.dup + @cache = {} + @specs = Hash.new {|h, k| h[k] = {} } + @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH } + + o.specs.each do |name, hash| + @specs[name] = hash.dup + end + o.all_specs.each do |name, array| + @all_specs[name] = array.dup + end + end + + def inspect + "#<#{self.class}:0x#{object_id} sources=#{sources.map(&:inspect)} specs.size=#{specs.size}>" + end + + def empty? + each { return false } + true + end + + def search_all(name) + all_matches = local_search(name) + @all_specs[name] + @sources.each do |source| + all_matches.concat(source.search_all(name)) + end + all_matches + end + + # Search this index's specs, and any source indexes that this index knows + # about, returning all of the results. + def search(query, base = nil) + sort_specs(unsorted_search(query, base)) + end + + def unsorted_search(query, base) + results = local_search(query, base) + + seen = results.map(&:full_name).uniq unless @sources.empty? + + @sources.each do |source| + source.unsorted_search(query, base).each do |spec| + next if seen.include?(spec.full_name) + + seen << spec.full_name + results << spec + end + end + + results + end + protected :unsorted_search + + def self.sort_specs(specs) + specs.sort_by do |s| + platform_string = s.platform.to_s + [s.version, platform_string == RUBY ? NULL : platform_string] + end + end + + def sort_specs(specs) + self.class.sort_specs(specs) + end + + def local_search(query, base = nil) + case query + when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query) + when String then specs_by_name(query) + when Gem::Dependency then search_by_dependency(query, base) + when DepProxy then search_by_dependency(query.dep, base) + else + raise "You can't search for a #{query.inspect}." + end + end + + alias_method :[], :search + + def <<(spec) + @specs[spec.name][spec.full_name] = spec + spec + end + + def each(&blk) + return enum_for(:each) unless blk + specs.values.each do |spec_sets| + spec_sets.values.each(&blk) + end + sources.each {|s| s.each(&blk) } + self + end + + def spec_names + names = specs.keys + sources.map(&:spec_names) + names.uniq! + names + end + + def unmet_dependency_names + dependency_names.select do |name| + search(name).empty? + end + end + + def dependency_names + names = [] + each do |spec| + spec.dependencies.each do |dep| + next if dep.type == :development + names << dep.name + end + end + names.uniq + end + + def use(other, override_dupes = false) + return unless other + other.each do |s| + if (dupes = search_by_spec(s)) && !dupes.empty? + # safe to << since it's a new array when it has contents + @all_specs[s.name] = dupes << s + next unless override_dupes + end + self << s + end + self + end + + def size + @sources.inject(@specs.size) do |size, source| + size += source.size + end + end + + # Whether all the specs in self are in other + # TODO: rename to #include? + def ==(other) + all? do |spec| + other_spec = other[spec].first + other_spec && dependencies_eql?(spec, other_spec) && spec.source == other_spec.source + end + end + + def dependencies_eql?(spec, other_spec) + deps = spec.dependencies.select {|d| d.type != :development } + other_deps = other_spec.dependencies.select {|d| d.type != :development } + deps.sort == other_deps.sort + end + + def add_source(index) + raise ArgumentError, "Source must be an index, not #{index.class}" unless index.is_a?(Index) + @sources << index + @sources.uniq! # need to use uniq! here instead of checking for the item before adding + end + + private + + def specs_by_name(name) + @specs[name].values + end + + def search_by_dependency(dependency, base = nil) + @cache[base || false] ||= {} + @cache[base || false][dependency] ||= begin + specs = specs_by_name(dependency.name) + specs += base if base + found = specs.select do |spec| + next true if spec.source.is_a?(Source::Gemspec) + if base # allow all platforms when searching from a lockfile + dependency.matches_spec?(spec) + else + dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) + end + end + + found + end + end + + EMPTY_SEARCH = [].freeze + + def search_by_spec(spec) + spec = @specs[spec.name][spec.full_name] + spec ? [spec] : EMPTY_SEARCH + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/injector.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/injector.rb new file mode 100644 index 0000000000..613bda4f84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/injector.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +module Bundler + class Injector + INJECTED_GEMS = "injected gems".freeze + + def self.inject(new_deps, options = {}) + injector = new(new_deps, options) + injector.inject(Bundler.default_gemfile, Bundler.default_lockfile) + end + + def self.remove(gems, options = {}) + injector = new(gems, options) + injector.remove(Bundler.default_gemfile, Bundler.default_lockfile) + end + + def initialize(deps, options = {}) + @deps = deps + @options = options + end + + # @param [Pathname] gemfile_path The Gemfile in which to inject the new dependency. + # @param [Pathname] lockfile_path The lockfile in which to inject the new dependency. + # @return [Array] + def inject(gemfile_path, lockfile_path) + if Bundler.frozen_bundle? + # ensure the lock and Gemfile are synced + Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true) + end + + # temporarily unfreeze + Bundler.settings.temporary(:deployment => false, :frozen => false) do + # evaluate the Gemfile we have now + builder = Dsl.new + builder.eval_gemfile(gemfile_path) + + # don't inject any gems that are already in the Gemfile + @deps -= builder.dependencies + + # add new deps to the end of the in-memory Gemfile + # Set conservative versioning to false because + # we want to let the resolver resolve the version first + builder.eval_gemfile(INJECTED_GEMS, build_gem_lines(false)) if @deps.any? + + # resolve to see if the new deps broke anything + @definition = builder.to_definition(lockfile_path, {}) + @definition.resolve_remotely! + + # since nothing broke, we can add those gems to the gemfile + append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any? + + # since we resolved successfully, write out the lockfile + @definition.lock(Bundler.default_lockfile) + + # invalidate the cached Bundler.definition + Bundler.reset_paths! + + # return an array of the deps that we added + @deps + end + end + + # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies. + # @param [Pathname] lockfile_path The lockfile from which to remove dependencies. + # @return [Array] + def remove(gemfile_path, lockfile_path) + # remove gems from each gemfiles we have + Bundler.definition.gemfiles.each do |path| + deps = remove_deps(path) + + show_warning("No gems were removed from the gemfile.") if deps.empty? + + deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep, false)} was removed." } + end + end + + private + + def conservative_version(spec) + version = spec.version + return ">= 0" if version.nil? + segments = version.segments + seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2 + + prerelease_suffix = version.to_s.gsub(version.release.to_s, "") if version.prerelease? + "#{version_prefix}#{segments[0..seg_end_index].join(".")}#{prerelease_suffix}" + end + + def version_prefix + if @options[:strict] + "= " + elsif @options[:optimistic] + ">= " + else + "~> " + end + end + + def build_gem_lines(conservative_versioning) + @deps.map do |d| + name = d.name.dump + + requirement = if conservative_versioning + ", \"#{conservative_version(@definition.specs[d.name][0])}\"" + else + ", #{d.requirement.as_list.map(&:dump).join(", ")}" + end + + if d.groups != Array(:default) + group = d.groups.size == 1 ? ", :group => #{d.groups.first.inspect}" : ", :groups => #{d.groups.inspect}" + end + + source = ", :source => \"#{d.source}\"" unless d.source.nil? + git = ", :git => \"#{d.git}\"" unless d.git.nil? + branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil? + + %(gem #{name}#{requirement}#{group}#{source}#{git}#{branch}) + end.join("\n") + end + + def append_to(gemfile_path, new_gem_lines) + gemfile_path.open("a") do |f| + f.puts + f.puts new_gem_lines + end + end + + # evaluates a gemfile to remove the specified gem + # from it. + def remove_deps(gemfile_path) + initial_gemfile = File.readlines(gemfile_path) + + Bundler.ui.info "Removing gems from #{gemfile_path}" + + # evaluate the Gemfile we have + builder = Dsl.new + builder.eval_gemfile(gemfile_path) + + removed_deps = remove_gems_from_dependencies(builder, @deps, gemfile_path) + + # abort the operation if no gems were removed + # no need to operate on gemfile further + return [] if removed_deps.empty? + + cleaned_gemfile = remove_gems_from_gemfile(@deps, gemfile_path) + + SharedHelpers.write_to_gemfile(gemfile_path, cleaned_gemfile) + + # check for errors + # including extra gems being removed + # or some gems not being removed + # and return the actual removed deps + cross_check_for_errors(gemfile_path, builder.dependencies, removed_deps, initial_gemfile) + end + + # @param [Dsl] builder Dsl object of current Gemfile. + # @param [Array] gems Array of names of gems to be removed. + # @param [Pathname] gemfile_path Path of the Gemfile. + # @return [Array] Array of removed dependencies. + def remove_gems_from_dependencies(builder, gems, gemfile_path) + removed_deps = [] + + gems.each do |gem_name| + deleted_dep = builder.dependencies.find {|d| d.name == gem_name } + + if deleted_dep.nil? + raise GemfileError, "`#{gem_name}` is not specified in #{gemfile_path} so it could not be removed." + end + + builder.dependencies.delete(deleted_dep) + + removed_deps << deleted_dep + end + + removed_deps + end + + # @param [Array] gems Array of names of gems to be removed. + # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies. + def remove_gems_from_gemfile(gems, gemfile_path) + patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/ + new_gemfile = [] + multiline_removal = false + File.readlines(gemfile_path).each do |line| + match_data = line.match(patterns) + if match_data && is_not_within_comment?(line, match_data) + multiline_removal = line.rstrip.end_with?(",") + # skip lines which match the regex + next + end + + # skip followup lines until line does not end with ',' + new_gemfile << line unless multiline_removal + multiline_removal = line.rstrip.end_with?(",") if multiline_removal + end + + # remove line \n and append them with other strings + new_gemfile.each_with_index do |_line, index| + if new_gemfile[index + 1] == "\n" + new_gemfile[index] += new_gemfile[index + 1] + new_gemfile.delete_at(index + 1) + end + end + + %w[group source env install_if].each {|block| remove_nested_blocks(new_gemfile, block) } + + new_gemfile.join.chomp + end + + # @param [String] line Individual line of gemfile content. + # @param [MatchData] match_data Data about Regex match. + def is_not_within_comment?(line, match_data) + match_start_index = match_data.offset(0).first + !line[0..match_start_index].include?("#") + end + + # @param [Array] gemfile Array of gemfile contents. + # @param [String] block_name Name of block name to look for. + def remove_nested_blocks(gemfile, block_name) + nested_blocks = 0 + + # count number of nested blocks + gemfile.each_with_index {|line, index| nested_blocks += 1 if !gemfile[index + 1].nil? && gemfile[index + 1].include?(block_name) && line.include?(block_name) } + + while nested_blocks >= 0 + nested_blocks -= 1 + + gemfile.each_with_index do |line, index| + next unless !line.nil? && line.strip.start_with?(block_name) + if gemfile[index + 1] =~ /^\s*end\s*$/ + gemfile[index] = nil + gemfile[index + 1] = nil + end + end + + gemfile.compact! + end + end + + # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies. + # @param [Array] original_deps Array of original dependencies. + # @param [Array] removed_deps Array of removed dependencies. + # @param [Array] initial_gemfile Contents of original Gemfile before any operation. + def cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile) + # evaluate the new gemfile to look for any failure cases + builder = Dsl.new + builder.eval_gemfile(gemfile_path) + + # record gems which were removed but not requested + extra_removed_gems = original_deps - builder.dependencies + + # if some extra gems were removed then raise error + # and revert Gemfile to original + unless extra_removed_gems.empty? + SharedHelpers.write_to_gemfile(gemfile_path, initial_gemfile.join) + + raise InvalidOption, "Gems could not be removed. #{extra_removed_gems.join(", ")} would also have been removed. Bundler cannot continue." + end + + # record gems which could not be removed due to some reasons + errored_deps = builder.dependencies.select {|d| d.gemfile == gemfile_path } & removed_deps.select {|d| d.gemfile == gemfile_path } + + show_warning "#{errored_deps.map(&:name).join(", ")} could not be removed." unless errored_deps.empty? + + # return actual removed dependencies + removed_deps - errored_deps + end + + def show_warning(message) + Bundler.ui.info Bundler.ui.add_color(message, :yellow) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/inline.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/inline.rb new file mode 100644 index 0000000000..a718418fce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/inline.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# Allows for declaring a Gemfile inline in a ruby script, optionally installing +# any gems that aren't already installed on the user's system. +# +# @note Every gem that is specified in this 'Gemfile' will be `require`d, as if +# the user had manually called `Bundler.require`. To avoid a requested gem +# being automatically required, add the `:require => false` option to the +# `gem` dependency declaration. +# +# @param install [Boolean] whether gems that aren't already installed on the +# user's system should be installed. +# Defaults to `false`. +# +# @param gemfile [Proc] a block that is evaluated as a `Gemfile`. +# +# @example Using an inline Gemfile +# +# #!/usr/bin/env ruby +# +# require 'bundler/inline' +# +# gemfile do +# source 'https://rubygems.org' +# gem 'json', require: false +# gem 'nap', require: 'rest' +# gem 'cocoapods', '~> 0.34.1' +# end +# +# puts Pod::VERSION # => "0.34.4" +# +def gemfile(install = false, options = {}, &gemfile) + require_relative "../bundler" + + opts = options.dup + ui = opts.delete(:ui) { Bundler::UI::Shell.new } + ui.level = "silent" if opts.delete(:quiet) + raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? + + begin + old_root = Bundler.method(:root) + bundler_module = class << Bundler; self; end + bundler_module.send(:remove_method, :root) + def Bundler.root + Bundler::SharedHelpers.pwd.expand_path + end + old_gemfile = ENV["BUNDLE_GEMFILE"] + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + + Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? + builder = Bundler::Dsl.new + builder.instance_eval(&gemfile) + builder.check_primary_source_safety + + Bundler.settings.temporary(:deployment => false, :frozen => false) do + definition = builder.to_definition(nil, true) + def definition.lock(*); end + definition.validate_runtime! + + Bundler.ui = install ? ui : Bundler::UI::Silent.new + if install || definition.missing_specs? + Bundler.settings.temporary(:inline => true) do + installer = Bundler::Installer.install(Bundler.root, definition, :system => true) + installer.post_install_messages.each do |name, message| + Bundler.ui.info "Post-install message from #{name}:\n#{message}" + end + end + end + + runtime = Bundler::Runtime.new(nil, definition) + runtime.setup.require + end + ensure + if bundler_module + bundler_module.send(:remove_method, :root) + bundler_module.send(:define_method, :root, old_root) + end + + if old_gemfile + ENV["BUNDLE_GEMFILE"] = old_gemfile + else + ENV["BUNDLE_GEMFILE"] = "" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer.rb new file mode 100644 index 0000000000..2624ac4b18 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +require "rubygems/dependency_installer" +require_relative "worker" +require_relative "installer/parallel_installer" +require_relative "installer/standalone" +require_relative "installer/gem_installer" + +module Bundler + class Installer + class << self + attr_accessor :ambiguous_gems + + Installer.ambiguous_gems = [] + end + + attr_reader :post_install_messages + + # Begins the installation process for Bundler. + # For more information see the #run method on this class. + def self.install(root, definition, options = {}) + installer = new(root, definition) + Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL_ALL, definition.dependencies) + installer.run(options) + Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL_ALL, definition.dependencies) + installer + end + + def initialize(root, definition) + @root = root + @definition = definition + @post_install_messages = {} + end + + # Runs the install procedures for a specific Gemfile. + # + # Firstly, this method will check to see if `Bundler.bundle_path` exists + # and if not then Bundler will create the directory. This is usually the same + # location as RubyGems which typically is the `~/.gem` directory + # unless other specified. + # + # Secondly, it checks if Bundler has been configured to be "frozen". + # Frozen ensures that the Gemfile and the Gemfile.lock file are matching. + # This stops a situation where a developer may update the Gemfile but may not run + # `bundle install`, which leads to the Gemfile.lock file not being correctly updated. + # If this file is not correctly updated then any other developer running + # `bundle install` will potentially not install the correct gems. + # + # Thirdly, Bundler checks if there are any dependencies specified in the Gemfile. + # If there are no dependencies specified then Bundler returns a warning message stating + # so and this method returns. + # + # Fourthly, Bundler checks if the Gemfile.lock exists, and if so + # then proceeds to set up a definition based on the Gemfile and the Gemfile.lock. + # During this step Bundler will also download information about any new gems + # that are not in the Gemfile.lock and resolve any dependencies if needed. + # + # Fifthly, Bundler resolves the dependencies either through a cache of gems or by remote. + # This then leads into the gems being installed, along with stubs for their executables, + # but only if the --binstubs option has been passed or Bundler.options[:bin] has been set + # earlier. + # + # Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time + # that a user runs `bundle install` they will receive any updates from this process. + # + # Finally, if the user has specified the standalone flag, Bundler will generate the needed + # require paths and save them in a `setup.rb` file. See `bundle standalone --help` for more + # information. + def run(options) + create_bundle_path + + ProcessLock.lock do + if Bundler.frozen_bundle? + @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment]) + end + + if @definition.dependencies.empty? + Bundler.ui.warn "The Gemfile specifies no dependencies" + lock + return + end + + if resolve_if_needed(options) + ensure_specs_are_compatible! + load_plugins + options.delete(:jobs) + else + options[:jobs] = 1 # to avoid the overhead of Bundler::Worker + end + install(options) + + Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available + + lock unless Bundler.frozen_bundle? + Standalone.new(options[:standalone], @definition).generate if options[:standalone] + end + end + + def generate_bundler_executable_stubs(spec, options = {}) + if options[:binstubs_cmd] && spec.executables.empty? + options = {} + spec.runtime_dependencies.each do |dep| + bins = @definition.specs[dep].first.executables + options[dep.name] = bins unless bins.empty? + end + if options.any? + Bundler.ui.warn "#{spec.name} has no executables, but you may want " \ + "one from a gem it depends on." + options.each {|name, bins| Bundler.ui.warn " #{name} has: #{bins.join(", ")}" } + else + Bundler.ui.warn "There are no executables for the gem #{spec.name}." + end + return + end + + # double-assignment to avoid warnings about variables that will be used by ERB + bin_path = Bundler.bin_path + bin_path = bin_path + relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path) + relative_gemfile_path = relative_gemfile_path + ruby_command = Thor::Util.ruby_command + ruby_command = ruby_command + template_path = File.expand_path("../templates/Executable", __FILE__) + if spec.name == "bundler" + template_path += ".bundler" + spec.executables = %(bundle) + end + template = File.read(template_path) + + exists = [] + spec.executables.each do |executable| + binstub_path = "#{bin_path}/#{executable}" + if File.exist?(binstub_path) && !options[:force] + exists << executable + next + end + + mode = Gem.win_platform? ? "wb:UTF-8" : "w" + require "erb" + content = if RUBY_VERSION >= "2.6" + ERB.new(template, :trim_mode => "-").result(binding) + else + ERB.new(template, nil, "-").result(binding) + end + + File.write(binstub_path, content, :mode => mode, :perm => 0o777 & ~File.umask) + if Gem.win_platform? || options[:all_platforms] + prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n" + File.write("#{binstub_path}.cmd", prefix + content, :mode => mode) + end + end + + if options[:binstubs_cmd] && exists.any? + case exists.size + when 1 + Bundler.ui.warn "Skipped #{exists[0]} since it already exists." + when 2 + Bundler.ui.warn "Skipped #{exists.join(" and ")} since they already exist." + else + items = exists[0...-1].empty? ? nil : exists[0...-1].join(", ") + skipped = [items, exists[-1]].compact.join(" and ") + Bundler.ui.warn "Skipped #{skipped} since they already exist." + end + Bundler.ui.warn "If you want to overwrite skipped stubs, use --force." + end + end + + def generate_standalone_bundler_executable_stubs(spec, options = {}) + # double-assignment to avoid warnings about variables that will be used by ERB + bin_path = Bundler.bin_path + unless path = Bundler.settings[:path] + raise "Can't standalone without an explicit path set" + end + standalone_path = Bundler.root.join(path).relative_path_from(bin_path) + standalone_path = standalone_path + template = File.read(File.expand_path("../templates/Executable.standalone", __FILE__)) + ruby_command = Thor::Util.ruby_command + ruby_command = ruby_command + + spec.executables.each do |executable| + next if executable == "bundle" + executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path) + executable_path = executable_path + + mode = Gem.win_platform? ? "wb:UTF-8" : "w" + require "erb" + content = if RUBY_VERSION >= "2.6" + ERB.new(template, :trim_mode => "-").result(binding) + else + ERB.new(template, nil, "-").result(binding) + end + + File.write("#{bin_path}/#{executable}", content, :mode => mode, :perm => 0o755) + if Gem.win_platform? || options[:all_platforms] + prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n" + File.write("#{bin_path}/#{executable}.cmd", prefix + content, :mode => mode) + end + end + end + + private + + # the order that the resolver provides is significant, since + # dependencies might affect the installation of a gem. + # that said, it's a rare situation (other than rake), and parallel + # installation is SO MUCH FASTER. so we let people opt in. + def install(options) + force = options["force"] + jobs = installation_parallelization(options) + install_in_parallel jobs, options[:standalone], force + end + + def installation_parallelization(options) + if jobs = options.delete(:jobs) + return jobs + end + + if jobs = Bundler.settings[:jobs] + return jobs + end + + # Parallelization has some issues on Windows, so it's not yet the default + return 1 if Gem.win_platform? + + Bundler.settings.processor_count + end + + def load_plugins + Bundler.rubygems.load_plugins + + requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) } + path_plugin_files = requested_path_gems.map do |spec| + begin + Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}") + rescue TypeError + error_message = "#{spec.name} #{spec.version} has an invalid gemspec" + raise Gem::InvalidSpecificationException, error_message + end + end.flatten + Bundler.rubygems.load_plugin_files(path_plugin_files) + Bundler.rubygems.load_env_plugins + end + + def ensure_specs_are_compatible! + system_ruby = Bundler::RubyVersion.system + rubygems_version = Gem::Version.create(Gem::VERSION) + @definition.specs.each do |spec| + if required_ruby_version = spec.required_ruby_version + unless required_ruby_version.satisfied_by?(system_ruby.gem_version) + raise InstallError, "#{spec.full_name} requires ruby version #{required_ruby_version}, " \ + "which is incompatible with the current version, #{system_ruby}" + end + end + next unless required_rubygems_version = spec.required_rubygems_version + unless required_rubygems_version.satisfied_by?(rubygems_version) + raise InstallError, "#{spec.full_name} requires rubygems version #{required_rubygems_version}, " \ + "which is incompatible with the current version, #{rubygems_version}" + end + end + end + + def install_in_parallel(size, standalone, force = false) + spec_installations = ParallelInstaller.call(self, @definition.specs, size, standalone, force) + spec_installations.each do |installation| + post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message? + end + end + + def create_bundle_path + SharedHelpers.filesystem_access(Bundler.bundle_path.to_s) do |p| + Bundler.mkdir_p(p) + end unless Bundler.bundle_path.exist? + rescue Errno::EEXIST + raise PathError, "Could not install to path `#{Bundler.bundle_path}` " \ + "because a file already exists at that path. Either remove or rename the file so the directory can be created." + end + + # returns whether or not a re-resolve was needed + def resolve_if_needed(options) + if !@definition.unlocking? && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file? + return false if @definition.nothing_changed? && !@definition.missing_specs? + end + + options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely! + true + end + + def lock(opts = {}) + @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/gem_installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/gem_installer.rb new file mode 100644 index 0000000000..507fd1802c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/gem_installer.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "shellwords" + +module Bundler + class GemInstaller + attr_reader :spec, :standalone, :worker, :force, :installer + + def initialize(spec, installer, standalone = false, worker = 0, force = false) + @spec = spec + @installer = installer + @standalone = standalone + @worker = worker + @force = force + end + + def install_from_spec + post_install_message = spec_settings ? install_with_settings : install + Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" + generate_executable_stubs + return true, post_install_message + rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError + raise + rescue Errno::ENOSPC + return false, out_of_space_message + rescue Bundler::BundlerError, Gem::InstallError, Bundler::APIResponseInvalidDependenciesError => e + return false, specific_failure_message(e) + end + + private + + def specific_failure_message(e) + message = "#{e.class}: #{e.message}\n" + message += " " + e.backtrace.join("\n ") + "\n\n" if Bundler.ui.debug? + message = message.lines.first + Bundler.ui.add_color(message.lines.drop(1).join, :clear) + message + Bundler.ui.add_color(failure_message, :red) + end + + def failure_message + return install_error_message if spec.source.options["git"] + "#{install_error_message}\n#{gem_install_message}" + end + + def install_error_message + "An error occurred while installing #{spec.name} (#{spec.version}), and Bundler cannot continue." + end + + def gem_install_message + source = spec.source + return unless source.respond_to?(:remotes) + + if source.remotes.size == 1 + "Make sure that `gem install #{spec.name} -v '#{spec.version}' --source '#{source.remotes.first}'` succeeds before bundling." + else + "Make sure that `gem install #{spec.name} -v '#{spec.version}'` succeeds before bundling." + end + end + + def spec_settings + # Fetch the build settings, if there are any + if settings = Bundler.settings["build.#{spec.name}"] + Shellwords.shellsplit(settings) + end + end + + def install + spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone, :build_args => Array(spec_settings)) + end + + def install_with_settings + # Build arguments are global, so this is mutexed + Bundler.rubygems.install_with_build_args([spec_settings]) { install } + end + + def out_of_space_message + "#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle." + end + + def generate_executable_stubs + return if Bundler.feature_flag.forget_cli_options? + return if Bundler.settings[:inline] + if Bundler.settings[:bin] && standalone + installer.generate_standalone_bundler_executable_stubs(spec) + elsif Bundler.settings[:bin] + installer.generate_bundler_executable_stubs(spec, :force => true) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/parallel_installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/parallel_installer.rb new file mode 100644 index 0000000000..5b6680e5e1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/parallel_installer.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require_relative "../worker" +require_relative "gem_installer" + +module Bundler + class ParallelInstaller + class SpecInstallation + attr_accessor :spec, :name, :full_name, :post_install_message, :state, :error + def initialize(spec) + @spec = spec + @name = spec.name + @full_name = spec.full_name + @state = :none + @post_install_message = "" + @error = nil + end + + def installed? + state == :installed + end + + def enqueued? + state == :enqueued + end + + def failed? + state == :failed + end + + def ready_to_enqueue? + state == :none + end + + def has_post_install_message? + !post_install_message.empty? + end + + def ignorable_dependency?(dep) + dep.type == :development || dep.name == @name + end + + # Checks installed dependencies against spec's dependencies to make + # sure needed dependencies have been installed. + def dependencies_installed?(all_specs) + installed_specs = all_specs.select(&:installed?).map(&:name) + dependencies.all? {|d| installed_specs.include? d.name } + end + + # Represents only the non-development dependencies, the ones that are + # itself and are in the total list. + def dependencies + @dependencies ||= all_dependencies.reject {|dep| ignorable_dependency? dep } + end + + def missing_lockfile_dependencies(all_spec_names) + dependencies.reject {|dep| all_spec_names.include? dep.name } + end + + # Represents all dependencies + def all_dependencies + @spec.dependencies + end + + def to_s + "#<#{self.class} #{full_name} (#{state})>" + end + end + + def self.call(*args) + new(*args).call + end + + attr_reader :size + + def initialize(installer, all_specs, size, standalone, force) + @installer = installer + @size = size + @standalone = standalone + @force = force + @specs = all_specs.map {|s| SpecInstallation.new(s) } + @spec_set = all_specs + @rake = @specs.find {|s| s.name == "rake" } + end + + def call + check_for_corrupt_lockfile + + if @rake + do_install(@rake, 0) + Gem::Specification.reset + end + + if @size > 1 + install_with_worker + else + install_serially + end + + check_for_unmet_dependencies + + handle_error if failed_specs.any? + @specs + ensure + worker_pool && worker_pool.stop + end + + def check_for_unmet_dependencies + unmet_dependencies = @specs.map do |s| + [ + s, + s.dependencies.reject {|dep| @specs.any? {|spec| dep.matches_spec?(spec.spec) } }, + ] + end.reject {|a| a.last.empty? } + return if unmet_dependencies.empty? + + warning = [] + warning << "Your lockfile doesn't include a valid resolution." + warning << "You can fix this by regenerating your lockfile or trying to manually editing the bad locked gems to a version that satisfies all dependencies." + warning << "The unmet dependencies are:" + + unmet_dependencies.each do |spec, unmet_spec_dependencies| + unmet_spec_dependencies.each do |unmet_spec_dependency| + warning << "* #{unmet_spec_dependency}, depended upon #{spec.full_name}, unsatisfied by #{@specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) }.full_name}" + end + end + + Bundler.ui.warn(warning.join("\n")) + end + + def check_for_corrupt_lockfile + missing_dependencies = @specs.map do |s| + [ + s, + s.missing_lockfile_dependencies(@specs.map(&:name)), + ] + end.reject {|a| a.last.empty? } + return if missing_dependencies.empty? + + warning = [] + warning << "Your lockfile was created by an old Bundler that left some things out." + if @size != 1 + warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time." + @size = 1 + end + warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile." + warning << "The missing gems are:" + + missing_dependencies.each do |spec, missing| + warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}" + end + + Bundler.ui.warn(warning.join("\n")) + end + + private + + def failed_specs + @specs.select(&:failed?) + end + + def install_with_worker + enqueue_specs + process_specs until finished_installing? + end + + def install_serially + until finished_installing? + raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?) + spec_install.state = :enqueued + do_install(spec_install, 0) + end + end + + def worker_pool + @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num| + do_install(spec_install, worker_num) + } + end + + def do_install(spec_install, worker_num) + Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL, spec_install) + gem_installer = Bundler::GemInstaller.new( + spec_install.spec, @installer, @standalone, worker_num, @force + ) + success, message = gem_installer.install_from_spec + if success + spec_install.state = :installed + spec_install.post_install_message = message unless message.nil? + else + spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}" + spec_install.state = :failed + end + Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL, spec_install) + spec_install + end + + # Dequeue a spec and save its post-install message and then enqueue the + # remaining specs. + # Some specs might've had to wait til this spec was installed to be + # processed so the call to `enqueue_specs` is important after every + # dequeue. + def process_specs + worker_pool.deq + enqueue_specs + end + + def finished_installing? + @specs.all? do |spec| + return true if spec.failed? + spec.installed? + end + end + + def handle_error + errors = failed_specs.map(&:error) + if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) } + raise exception + end + raise Bundler::InstallError, errors.join("\n\n") + end + + def require_tree_for_spec(spec) + tree = @spec_set.what_required(spec) + t = String.new("In #{File.basename(SharedHelpers.default_gemfile)}:\n") + tree.each_with_index do |s, depth| + t << " " * depth.succ << s.name + unless tree.last == s + t << %( was resolved to #{s.version}, which depends on) + end + t << %(\n) + end + t + end + + # Keys in the remains hash represent uninstalled gems specs. + # We enqueue all gem specs that do not have any dependencies. + # Later we call this lambda again to install specs that depended on + # previously installed specifications. We continue until all specs + # are installed. + def enqueue_specs + @specs.select(&:ready_to_enqueue?).each do |spec| + if spec.dependencies_installed? @specs + spec.state = :enqueued + worker_pool.enq spec + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/standalone.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/standalone.rb new file mode 100644 index 0000000000..2a3f5cfe35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/installer/standalone.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Bundler + class Standalone + def initialize(groups, definition) + @specs = groups.empty? ? definition.requested_specs : definition.specs_for(groups.map(&:to_sym)) + end + + def generate + SharedHelpers.filesystem_access(bundler_path) do |p| + FileUtils.mkdir_p(p) + end + File.open File.join(bundler_path, "setup.rb"), "w" do |file| + file.puts "require 'rbconfig'" + file.puts "ruby_engine = RUBY_ENGINE" + file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]" + file.puts "path = File.expand_path('..', __FILE__)" + file.puts reverse_rubygems_kernel_mixin + paths.each do |path| + file.puts %($:.unshift File.expand_path("\#{path}/#{path}")) + end + end + end + + private + + def paths + @specs.map do |spec| + next if spec.name == "bundler" + Array(spec.require_paths).map do |path| + gem_path(path, spec).sub(version_dir, '#{ruby_engine}/#{ruby_version}') + # This is a static string intentionally. It's interpolated at a later time. + end + end.flatten + end + + def version_dir + "#{Bundler::RubyVersion.system.engine}/#{RbConfig::CONFIG["ruby_version"]}" + end + + def bundler_path + Bundler.root.join(Bundler.settings[:path], "bundler") + end + + def gem_path(path, spec) + full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path) + Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s + rescue TypeError + error_message = "#{spec.name} #{spec.version} has an invalid gemspec" + raise Gem::InvalidSpecificationException.new(error_message) + end + + def reverse_rubygems_kernel_mixin + <<~END + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + private_require = k.private_method_defined?(:require) + k.send(:remove_method, :require) + k.send(:define_method, :require, k.instance_method(:gem_original_require)) + k.send(:private, :require) if private_require + end + end + END + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lazy_specification.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lazy_specification.rb new file mode 100644 index 0000000000..6760edba42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lazy_specification.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require_relative "match_platform" + +module Bundler + class LazySpecification + include MatchPlatform + + attr_reader :name, :version, :dependencies, :platform + attr_accessor :source, :remote + + def initialize(name, version, platform, source = nil) + @name = name + @version = version + @dependencies = [] + @platform = platform || Gem::Platform::RUBY + @source = source + @specification = nil + end + + def full_name + if platform == Gem::Platform::RUBY || platform.nil? + "#{@name}-#{@version}" + else + "#{@name}-#{@version}-#{platform}" + end + end + + def ==(other) + identifier == other.identifier + end + + def eql?(other) + identifier.eql?(other.identifier) + end + + def hash + identifier.hash + end + + def satisfies?(dependency) + @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version)) + end + + def to_lock + out = String.new + + if platform == Gem::Platform::RUBY || platform.nil? + out << " #{name} (#{version})\n" + else + out << " #{name} (#{version}-#{platform})\n" + end + + dependencies.sort_by(&:to_s).uniq.each do |dep| + next if dep.type == :development + out << " #{dep.to_lock}\n" + end + + out + end + + def __materialize__ + @specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name + source.gemspec.tap {|s| s.source = source } + else + search_object = if source.is_a?(Source::Path) + Dependency.new(name, version) + else + ruby_platform_materializes_to_ruby_platform? ? self : Dependency.new(name, version) + end + platform_object = Gem::Platform.new(platform) + candidates = source.specs.search(search_object) + same_platform_candidates = candidates.select do |spec| + MatchPlatform.platforms_match?(spec.platform, platform_object) + end + installable_candidates = same_platform_candidates.select do |spec| + !spec.is_a?(EndpointSpecification) || + (spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && + spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version)) + end + search = installable_candidates.last || same_platform_candidates.last + search.dependencies = dependencies if search && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification)) + search + end + end + + def respond_to?(*args) + super || @specification ? @specification.respond_to?(*args) : nil + end + + def to_s + @__to_s ||= if platform == Gem::Platform::RUBY || platform.nil? + "#{name} (#{version})" + else + "#{name} (#{version}-#{platform})" + end + end + + def identifier + @__identifier ||= [name, version, platform_string] + end + + def git_version + return unless source.is_a?(Bundler::Source::Git) + " #{source.revision[0..6]}" + end + + protected + + def platform_string + platform_string = platform.to_s + platform_string == Index::RUBY ? Index::NULL : platform_string + end + + private + + def to_ary + nil + end + + def method_missing(method, *args, &blk) + raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification + + return super unless respond_to?(method) + + @specification.send(method, *args, &blk) + end + + # + # For backwards compatibility with existing lockfiles, if the most specific + # locked platform is RUBY, we keep the previous behaviour of resolving the + # best platform variant at materiliazation time. For previous bundler + # versions (before 2.2.0) this was always the case (except when the lockfile + # only included non-ruby platforms), but we're also keeping this behaviour + # on newer bundlers unless users generate the lockfile from scratch or + # explicitly add a more specific platform. + # + def ruby_platform_materializes_to_ruby_platform? + !Bundler.most_specific_locked_platform?(Gem::Platform::RUBY) || Bundler.settings[:force_ruby_platform] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_generator.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_generator.rb new file mode 100644 index 0000000000..3bc6bd7339 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_generator.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module Bundler + class LockfileGenerator + attr_reader :definition + attr_reader :out + + # @private + def initialize(definition) + @definition = definition + @out = String.new + end + + def self.generate(definition) + new(definition).generate! + end + + def generate! + add_sources + add_platforms + add_dependencies + add_locked_ruby_version + add_bundled_with + + out + end + + private + + def add_sources + definition.send(:sources).lock_sources.each_with_index do |source, idx| + out << "\n" unless idx.zero? + + # Add the source header + out << source.to_lock + + # Find all specs for this source + specs = definition.resolve.select {|s| source.can_lock?(s) } + add_specs(specs) + end + end + + def add_specs(specs) + # This needs to be sorted by full name so that + # gems with the same name, but different platform + # are ordered consistently + specs.sort_by(&:full_name).each do |spec| + next if spec.name == "bundler".freeze + out << spec.to_lock + end + end + + def add_platforms + add_section("PLATFORMS", definition.platforms) + end + + def add_dependencies + out << "\nDEPENDENCIES\n" + + handled = [] + definition.dependencies.sort_by(&:to_s).each do |dep| + next if handled.include?(dep.name) + out << dep.to_lock + handled << dep.name + end + end + + def add_locked_ruby_version + return unless locked_ruby_version = definition.locked_ruby_version + add_section("RUBY VERSION", locked_ruby_version.to_s) + end + + def add_bundled_with + add_section("BUNDLED WITH", definition.locked_bundler_version.to_s) + end + + def add_section(name, value) + out << "\n#{name}\n" + case value + when Array + value.map(&:to_s).sort.each do |val| + out << " #{val}\n" + end + when Hash + value.to_a.sort_by {|k, _| k.to_s }.each do |key, val| + out << " #{key}: #{val}\n" + end + when String + out << " #{value}\n" + else + raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_parser.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_parser.rb new file mode 100644 index 0000000000..afc21fd006 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/lockfile_parser.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +module Bundler + class LockfileParser + attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version + + BUNDLED = "BUNDLED WITH".freeze + DEPENDENCIES = "DEPENDENCIES".freeze + PLATFORMS = "PLATFORMS".freeze + RUBY = "RUBY VERSION".freeze + GIT = "GIT".freeze + GEM = "GEM".freeze + PATH = "PATH".freeze + PLUGIN = "PLUGIN SOURCE".freeze + SPECS = " specs:".freeze + OPTIONS = /^ ([a-z]+): (.*)$/i.freeze + SOURCE = [GIT, GEM, PATH, PLUGIN].freeze + + SECTIONS_BY_VERSION_INTRODUCED = { + Gem::Version.create("1.0") => [DEPENDENCIES, PLATFORMS, GIT, GEM, PATH].freeze, + Gem::Version.create("1.10") => [BUNDLED].freeze, + Gem::Version.create("1.12") => [RUBY].freeze, + Gem::Version.create("1.13") => [PLUGIN].freeze, + }.freeze + + KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten.freeze + + ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze + + def self.sections_in_lockfile(lockfile_contents) + lockfile_contents.scan(/^\w[\w ]*$/).uniq + end + + def self.unknown_sections_in_lockfile(lockfile_contents) + sections_in_lockfile(lockfile_contents) - KNOWN_SECTIONS + end + + def self.sections_to_ignore(base_version = nil) + base_version &&= base_version.release + base_version ||= Gem::Version.create("1.0".dup) + attributes = [] + SECTIONS_BY_VERSION_INTRODUCED.each do |version, introduced| + next if version <= base_version + attributes += introduced + end + attributes + end + + def initialize(lockfile) + @platforms = [] + @sources = [] + @dependencies = {} + @state = nil + @specs = {} + + if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) + raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \ + "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock." + end + + lockfile.split(/(?:\r?\n)+/).each do |line| + if SOURCE.include?(line) + @state = :source + parse_source(line) + elsif line == DEPENDENCIES + @state = :dependency + elsif line == PLATFORMS + @state = :platform + elsif line == RUBY + @state = :ruby + elsif line == BUNDLED + @state = :bundled_with + elsif line =~ /^[^\s]/ + @state = nil + elsif @state + send("parse_#{@state}", line) + end + end + @specs = @specs.values.sort_by(&:identifier) + warn_for_outdated_bundler_version + rescue ArgumentError => e + Bundler.ui.debug(e) + raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \ + "and then `bundle install` to generate a new lockfile." + end + + def warn_for_outdated_bundler_version + return unless bundler_version + prerelease_text = bundler_version.prerelease? ? " --pre" : "" + current_version = Gem::Version.create(Bundler::VERSION) + return unless current_version < bundler_version + Bundler.ui.warn "Warning: the running version of Bundler (#{current_version}) is older " \ + "than the version that created the lockfile (#{bundler_version}). We suggest you to " \ + "upgrade to the version that created the lockfile by running `gem install " \ + "bundler:#{bundler_version}#{prerelease_text}`.\n" + end + + private + + TYPES = { + GIT => Bundler::Source::Git, + GEM => Bundler::Source::Rubygems, + PATH => Bundler::Source::Path, + PLUGIN => Bundler::Plugin, + }.freeze + + def parse_source(line) + case line + when SPECS + case @type + when PATH + @current_source = TYPES[@type].from_lock(@opts) + @sources << @current_source + when GIT + @current_source = TYPES[@type].from_lock(@opts) + @sources << @current_source + when GEM + @opts["remotes"] = Array(@opts.delete("remote")).reverse + @current_source = TYPES[@type].from_lock(@opts) + @sources << @current_source + when PLUGIN + @current_source = Plugin.source_from_lock(@opts) + @sources << @current_source + end + when OPTIONS + value = $2 + value = true if value == "true" + value = false if value == "false" + + key = $1 + + if @opts[key] + @opts[key] = Array(@opts[key]) + @opts[key] << value + else + @opts[key] = value + end + when *SOURCE + @current_source = nil + @opts = {} + @type = line + else + parse_spec(line) + end + end + + space = / / + NAME_VERSION = / + ^(#{space}{2}|#{space}{4}|#{space}{6})(?!#{space}) # Exactly 2, 4, or 6 spaces at the start of the line + (.*?) # Name + (?:#{space}\(([^-]*) # Space, followed by version + (?:-(.*))?\))? # Optional platform + (!)? # Optional pinned marker + $ # Line end + /xo.freeze + + def parse_dependency(line) + return unless line =~ NAME_VERSION + spaces = $1 + return unless spaces.size == 2 + name = $2 + version = $3 + pinned = $5 + + version = version.split(",").map(&:strip) if version + + dep = Bundler::Dependency.new(name, version) + + if pinned && dep.name != "bundler" + spec = @specs.find {|_, v| v.name == dep.name } + dep.source = spec.last.source if spec + + # Path sources need to know what the default name / version + # to use in the case that there are no gemspecs present. A fake + # gemspec is created based on the version set on the dependency + # TODO: Use the version from the spec instead of from the dependency + if version && version.size == 1 && version.first =~ /^\s*= (.+)\s*$/ && dep.source.is_a?(Bundler::Source::Path) + dep.source.name = name + dep.source.version = $1 + end + end + + @dependencies[dep.name] = dep + end + + def parse_spec(line) + return unless line =~ NAME_VERSION + spaces = $1 + name = $2 + version = $3 + platform = $4 + + if spaces.size == 4 + version = Gem::Version.new(version) + platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY + @current_spec = LazySpecification.new(name, version, platform) + @current_spec.source = @current_source + + @specs[@current_spec.identifier] = @current_spec + elsif spaces.size == 6 + version = version.split(",").map(&:strip) if version + dep = Gem::Dependency.new(name, version) + @current_spec.dependencies << dep + end + end + + def parse_platform(line) + @platforms << Gem::Platform.new($1) if line =~ /^ (.*)$/ + end + + def parse_bundled_with(line) + line = line.strip + return unless Gem::Version.correct?(line) + @bundler_version = Gem::Version.create(line) + end + + def parse_ruby(line) + @ruby_version = line.strip + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/.document b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/.document new file mode 100644 index 0000000000..fb66f13c33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/.document @@ -0,0 +1 @@ +# Ignore all files in this directory diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1 new file mode 100644 index 0000000000..4945747f42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1 @@ -0,0 +1,66 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-ADD" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install +. +.SH "SYNOPSIS" +\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-git=GIT] [\-\-branch=BRANCH] [\-\-skip\-install] [\-\-strict] [\-\-optimistic] +. +.SH "DESCRIPTION" +Adds the named gem to the Gemfile and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\. +. +.P +Example: +. +.P +bundle add rails +. +.P +bundle add rails \-\-version "< 3\.0, > 1\.1" +. +.P +bundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development" +. +.P +bundle add rails \-\-skip\-install +. +.P +bundle add rails \-\-group "development, test" +. +.SH "OPTIONS" +. +.TP +\fB\-\-version\fR, \fB\-v\fR +Specify version requirements(s) for the added gem\. +. +.TP +\fB\-\-group\fR, \fB\-g\fR +Specify the group(s) for the added gem\. Multiple groups should be separated by commas\. +. +.TP +\fB\-\-source\fR, , \fB\-s\fR +Specify the source for the added gem\. +. +.TP +\fB\-\-git\fR +Specify the git source for the added gem\. +. +.TP +\fB\-\-branch\fR +Specify the git branch for the added gem\. +. +.TP +\fB\-\-skip\-install\fR +Adds the gem to the Gemfile but does not install it\. +. +.TP +\fB\-\-optimistic\fR +Adds optimistic declaration of version +. +.TP +\fB\-\-strict\fR +Adds strict declaration of version + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1.ronn new file mode 100644 index 0000000000..26cbe55647 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-add.1.ronn @@ -0,0 +1,46 @@ +bundle-add(1) -- Add gem to the Gemfile and run bundle install +================================================================ + +## SYNOPSIS + +`bundle add` [--group=GROUP] [--version=VERSION] [--source=SOURCE] [--git=GIT] [--branch=BRANCH] [--skip-install] [--strict] [--optimistic] + +## DESCRIPTION +Adds the named gem to the Gemfile and run `bundle install`. `bundle install` can be avoided by using the flag `--skip-install`. + +Example: + +bundle add rails + +bundle add rails --version "< 3.0, > 1.1" + +bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development" + +bundle add rails --skip-install + +bundle add rails --group "development, test" + +## OPTIONS +* `--version`, `-v`: + Specify version requirements(s) for the added gem. + +* `--group`, `-g`: + Specify the group(s) for the added gem. Multiple groups should be separated by commas. + +* `--source`, , `-s`: + Specify the source for the added gem. + +* `--git`: + Specify the git source for the added gem. + +* `--branch`: + Specify the git branch for the added gem. + +* `--skip-install`: + Adds the gem to the Gemfile but does not install it. + +* `--optimistic`: + Adds optimistic declaration of version + +* `--strict`: + Adds strict declaration of version diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1 new file mode 100644 index 0000000000..dd43180b19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1 @@ -0,0 +1,42 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-BINSTUBS" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems +. +.SH "SYNOPSIS" +\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone] +. +.SH "DESCRIPTION" +Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\. +. +.P +For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\. +. +.P +This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-force\fR +Overwrite existing binstubs if they exist\. +. +.TP +\fB\-\-path\fR +The location to install the specified binstubs to\. This defaults to \fBbin\fR\. +. +.TP +\fB\-\-standalone\fR +Makes binstubs that can work without depending on Rubygems or Bundler at runtime\. +. +.TP +\fB\-\-shebang\fR +Specify a different shebang executable name than the default (default \'ruby\') +. +.TP +\fB\-\-all\fR +Create binstubs for all gems in the bundle\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1.ronn new file mode 100644 index 0000000000..a96186929f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-binstubs.1.ronn @@ -0,0 +1,41 @@ +bundle-binstubs(1) -- Install the binstubs of the listed gems +============================================================= + +## SYNOPSIS + +`bundle binstubs` [--force] [--path PATH] [--standalone] + +## DESCRIPTION + +Binstubs are scripts that wrap around executables. Bundler creates a +small Ruby file (a binstub) that loads Bundler, runs the command, +and puts it into `bin/`. Binstubs are a shortcut-or alternative- +to always using `bundle exec`. This gives you a file that can be run +directly, and one that will always run the correct gem version +used by the application. + +For example, if you run `bundle binstubs rspec-core`, Bundler will create +the file `bin/rspec`. That file will contain enough code to load Bundler, +tell it to load the bundled gems, and then run rspec. + +This command generates binstubs for executables in `GEM_NAME`. +Binstubs are put into `bin`, or the `--path` directory if one has been set. +Calling binstubs with [GEM [GEM]] will create binstubs for all given gems. + +## OPTIONS + +* `--force`: + Overwrite existing binstubs if they exist. + +* `--path`: + The location to install the specified binstubs to. This defaults to `bin`. + +* `--standalone`: + Makes binstubs that can work without depending on Rubygems or Bundler at + runtime. + +* `--shebang`: + Specify a different shebang executable name than the default (default 'ruby') + +* `--all`: + Create binstubs for all gems in the bundle. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1 new file mode 100644 index 0000000000..22ce1dfc76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1 @@ -0,0 +1,55 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-CACHE" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application +. +.SH "SYNOPSIS" +\fBbundle cache\fR +. +.SH "DESCRIPTION" +Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running [bundle install(1)][bundle\-install], use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\. +. +.SH "GIT AND PATH GEMS" +The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\. +. +.SH "SUPPORT FOR MULTIPLE PLATFORMS" +When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\. +. +.SH "REMOTE FETCHING" +By default, if you run \fBbundle install(1)\fR](bundle\-install\.1\.html) after running bundle cache(1) \fIbundle\-cache\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\. +. +.P +For instance, consider this Gemfile(5): +. +.IP "" 4 +. +.nf + +source "https://rubygems\.org" + +gem "nokogiri" +. +.fi +. +.IP "" 0 +. +.P +If you run \fBbundle cache\fR under C Ruby, bundler will retrieve the version of \fBnokogiri\fR for the \fB"ruby"\fR platform\. If you deploy to JRuby and run \fBbundle install\fR, bundler is forced to check to see whether a \fB"java"\fR platformed \fBnokogiri\fR exists\. +. +.P +Even though the \fBnokogiri\fR gem for the Ruby platform is \fItechnically\fR acceptable on JRuby, it has a C extension that does not run on JRuby\. As a result, bundler will, by default, still connect to \fBrubygems\.org\fR to check whether it has a version of one of your gems more specific to your platform\. +. +.P +This problem is also not limited to the \fB"java"\fR platform\. A similar (common) problem can happen when developing on Windows and deploying to Linux, or even when developing on OSX and deploying to Linux\. +. +.P +If you know for sure that the gems packaged in \fBvendor/cache\fR are appropriate for the platform you are on, you can run \fBbundle install \-\-local\fR to skip checking for more appropriate gems, and use the ones in \fBvendor/cache\fR\. +. +.P +One way to be sure that you have the right platformed versions of all your gems is to run \fBbundle cache\fR on an identical machine and check in the gems\. For instance, you can run \fBbundle cache\fR on an identical staging box during your staging process, and check in the \fBvendor/cache\fR before deploying to production\. +. +.P +By default, bundle cache(1) \fIbundle\-cache\.1\.html\fR fetches and also installs the gems to the default location\. To package the dependencies to \fBvendor/cache\fR without installing them to the local install location, you can run \fBbundle cache \-\-no\-install\fR\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1.ronn new file mode 100644 index 0000000000..383adb2ba3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-cache.1.ronn @@ -0,0 +1,72 @@ +bundle-cache(1) -- Package your needed `.gem` files into your application +=========================================================================== + +## SYNOPSIS + +`bundle cache` + +## DESCRIPTION + +Copy all of the `.gem` files needed to run the application into the +`vendor/cache` directory. In the future, when running [bundle install(1)][bundle-install], +use the gems in the cache in preference to the ones on `rubygems.org`. + +## GIT AND PATH GEMS + +The `bundle cache` command can also package `:git` and `:path` dependencies +besides .gem files. This needs to be explicitly enabled via the `--all` option. +Once used, the `--all` option will be remembered. + +## SUPPORT FOR MULTIPLE PLATFORMS + +When using gems that have different packages for different platforms, Bundler +supports caching of gems for other platforms where the Gemfile has been resolved +(i.e. present in the lockfile) in `vendor/cache`. This needs to be enabled via +the `--all-platforms` option. This setting will be remembered in your local +bundler configuration. + +## REMOTE FETCHING + +By default, if you run `bundle install(1)`](bundle-install.1.html) after running +[bundle cache(1)](bundle-cache.1.html), bundler will still connect to `rubygems.org` +to check whether a platform-specific gem exists for any of the gems +in `vendor/cache`. + +For instance, consider this Gemfile(5): + + source "https://rubygems.org" + + gem "nokogiri" + +If you run `bundle cache` under C Ruby, bundler will retrieve +the version of `nokogiri` for the `"ruby"` platform. If you deploy +to JRuby and run `bundle install`, bundler is forced to check to +see whether a `"java"` platformed `nokogiri` exists. + +Even though the `nokogiri` gem for the Ruby platform is +_technically_ acceptable on JRuby, it has a C extension +that does not run on JRuby. As a result, bundler will, by default, +still connect to `rubygems.org` to check whether it has a version +of one of your gems more specific to your platform. + +This problem is also not limited to the `"java"` platform. +A similar (common) problem can happen when developing on Windows +and deploying to Linux, or even when developing on OSX and +deploying to Linux. + +If you know for sure that the gems packaged in `vendor/cache` +are appropriate for the platform you are on, you can run +`bundle install --local` to skip checking for more appropriate +gems, and use the ones in `vendor/cache`. + +One way to be sure that you have the right platformed versions +of all your gems is to run `bundle cache` on an identical +machine and check in the gems. For instance, you can run +`bundle cache` on an identical staging box during your +staging process, and check in the `vendor/cache` before +deploying to production. + +By default, [bundle cache(1)](bundle-cache.1.html) fetches and also +installs the gems to the default location. To package the +dependencies to `vendor/cache` without installing them to the +local install location, you can run `bundle cache --no-install`. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1 new file mode 100644 index 0000000000..6e1b38ab05 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1 @@ -0,0 +1,31 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-CHECK" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems +. +.SH "SYNOPSIS" +\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH] +. +.SH "DESCRIPTION" +\fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\. +. +.P +If not, the first missing gem is listed and Bundler exits status 1\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-dry\-run\fR +Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\. +. +.TP +\fB\-\-gemfile\fR +Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\. +. +.TP +\fB\-\-path\fR +Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1.ronn new file mode 100644 index 0000000000..f2846b8ff2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-check.1.ronn @@ -0,0 +1,26 @@ +bundle-check(1) -- Verifies if dependencies are satisfied by installed gems +=========================================================================== + +## SYNOPSIS + +`bundle check` [--dry-run] + [--gemfile=FILE] + [--path=PATH] + +## DESCRIPTION + +`check` searches the local machine for each of the gems requested in the +Gemfile. If all gems are found, Bundler prints a success message and exits with +a status of 0. + +If not, the first missing gem is listed and Bundler exits status 1. + +## OPTIONS + +* `--dry-run`: + Locks the [`Gemfile(5)`][Gemfile(5)] before running the command. +* `--gemfile`: + Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)]. +* `--path`: + Specify a different path than the system default (`$BUNDLE_PATH` or `$GEM_HOME`). + Bundler will remember this value for future installs on this machine. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1 new file mode 100644 index 0000000000..eb658e1c73 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1 @@ -0,0 +1,24 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-CLEAN" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory +. +.SH "SYNOPSIS" +\fBbundle clean\fR [\-\-dry\-run] [\-\-force] +. +.SH "DESCRIPTION" +This command will remove all unused gems in your bundler directory\. This is useful when you have made many changes to your gem dependencies\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-dry\-run\fR +Print the changes, but do not clean the unused gems\. +. +.TP +\fB\-\-force\fR +Force a clean even if \fB\-\-path\fR is not set\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1.ronn new file mode 100644 index 0000000000..de23991782 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-clean.1.ronn @@ -0,0 +1,18 @@ +bundle-clean(1) -- Cleans up unused gems in your bundler directory +================================================================== + +## SYNOPSIS + +`bundle clean` [--dry-run] [--force] + +## DESCRIPTION + +This command will remove all unused gems in your bundler directory. This is +useful when you have made many changes to your gem dependencies. + +## OPTIONS + +* `--dry-run`: + Print the changes, but do not clean the unused gems. +* `--force`: + Force a clean even if `--path` is not set. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1 new file mode 100644 index 0000000000..ffd9d27cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1 @@ -0,0 +1,496 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-CONFIG" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-config\fR \- Set bundler configuration options +. +.SH "SYNOPSIS" +\fBbundle config\fR [list|get|set|unset] [\fIname\fR [\fIvalue\fR]] +. +.SH "DESCRIPTION" +This command allows you to interact with Bundler\'s configuration system\. +. +.P +Bundler loads configuration settings in this order: +. +.IP "1." 4 +Local config (\fB/\.bundle/config\fR or \fB$BUNDLE_APP_CONFIG/config\fR) +. +.IP "2." 4 +Environmental variables (\fBENV\fR) +. +.IP "3." 4 +Global config (\fB~/\.bundle/config\fR) +. +.IP "4." 4 +Bundler default config +. +.IP "" 0 +. +.P +Executing \fBbundle config list\fR with will print a list of all bundler configuration for the current bundle, and where that configuration was set\. +. +.P +Executing \fBbundle config get \fR will print the value of that configuration setting, and where it was set\. +. +.P +Executing \fBbundle config set \fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\. +. +.P +Executing \fBbundle config set \-\-global \fR works the same as above\. +. +.P +Executing \fBbundle config set \-\-local \fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\. +. +.P +Executing \fBbundle config unset \fR will delete the configuration in both local and global sources\. +. +.P +Executing \fBbundle config unset \-\-global \fR will delete the configuration only from the user configuration\. +. +.P +Executing \fBbundle config unset \-\-local \fR will delete the configuration only from the local application\. +. +.P +Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. +. +.SH "REMEMBERING OPTIONS" +Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\. +. +.P +However, this will be changed in bundler 3, so it\'s better not to rely on this behavior\. If these options must be remembered, it\'s better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\. +. +.P +The options that can be configured are: +. +.TP +\fBbin\fR +Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +. +.TP +\fBdeployment\fR +In deployment mode, Bundler will \'roll\-out\' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\. +. +.TP +\fBpath\fR +The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. +. +.TP +\fBwithout\fR +A space\-separated list of groups referencing gems to skip during installation\. +. +.TP +\fBwith\fR +A space\-separated list of groups referencing gems to include during installation\. +. +.SH "BUILD OPTIONS" +You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\. +. +.P +A very common example, the \fBmysql\fR gem, requires Snow Leopard users to pass configuration flags to \fBgem install\fR to specify where to find the \fBmysql_config\fR executable\. +. +.IP "" 4 +. +.nf + +gem install mysql \-\- \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config +. +.fi +. +.IP "" 0 +. +.P +Since the specific location of that executable can change from machine to machine, you can specify these flags on a per\-machine basis\. +. +.IP "" 4 +. +.nf + +bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config +. +.fi +. +.IP "" 0 +. +.P +After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\. +. +.SH "CONFIGURATION KEYS" +Configuration keys in bundler have two forms: the canonical form and the environment variable form\. +. +.P +For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn\'t install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. +. +.P +The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. +. +.P +Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. +. +.SH "LIST OF AVAILABLE KEYS" +The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. +. +.IP "\(bu" 4 +\fBallow_deployment_source_credential_changes\fR (\fBBUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES\fR): When in deployment mode, allow changing the credentials to a gem\'s source\. Ex: \fBhttps://some\.host\.com/gems/path/\fR \-> \fBhttps://user_name:password@some\.host\.com/gems/path\fR +. +.IP "\(bu" 4 +\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\. +. +.IP "\(bu" 4 +\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\. +. +.IP "\(bu" 4 +\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\. +. +.IP "\(bu" 4 +\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\. +. +.IP "\(bu" 4 +\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\. +. +.IP "\(bu" 4 +\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\. +. +.IP "\(bu" 4 +\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. +. +.IP "\(bu" 4 +\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. +. +.IP "\(bu" 4 +\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. +. +.IP "\(bu" 4 +\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\. +. +.IP "\(bu" 4 +\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. +. +.IP "\(bu" 4 +\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\. +. +.IP "\(bu" 4 +\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. +. +.IP "\(bu" 4 +\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\. +. +.IP "\(bu" 4 +\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. +. +.IP "\(bu" 4 +\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\. +. +.IP "\(bu" 4 +\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. +. +.IP "\(bu" 4 +\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine\'s platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. +. +.IP "\(bu" 4 +\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\. +. +.IP "\(bu" 4 +\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. +. +.IP "\(bu" 4 +\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. +. +.IP "\(bu" 4 +\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. +. +.IP "\(bu" 4 +\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\. +. +.IP "\(bu" 4 +\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. +. +.IP "\(bu" 4 +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. +. +.IP "\(bu" 4 +\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1 on Windows, and to the the number of processors on other platforms\. +. +.IP "\(bu" 4 +\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\. +. +.IP "\(bu" 4 +\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\. +. +.IP "\(bu" 4 +\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\. +. +.IP "\(bu" 4 +\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. +. +.IP "\(bu" 4 +\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\. +. +.IP "\(bu" 4 +\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler\'s experimental plugin system\. +. +.IP "\(bu" 4 +\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. +. +.IP "\(bu" 4 +\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\. +. +.IP "\(bu" 4 +\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\. +. +.IP "\(bu" 4 +\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\. +. +.IP "\(bu" 4 +\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\. +. +.IP "\(bu" 4 +\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. +. +.IP "\(bu" 4 +\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. +. +.IP "\(bu" 4 +\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\. +. +.IP "\(bu" 4 +\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. +. +.IP "\(bu" 4 +\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\. +. +.IP "\(bu" 4 +\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. +. +.IP "\(bu" 4 +\fBsuppress_install_using_messages\fR (\fBBUNDLE_SUPPRESS_INSTALL_USING_MESSAGES\fR): Avoid printing \fBUsing \.\.\.\fR messages during installation when the version of a gem has not changed\. +. +.IP "\(bu" 4 +\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. +. +.IP "\(bu" 4 +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. +. +.IP "\(bu" 4 +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. +. +.IP "\(bu" 4 +\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\. +. +.IP "\(bu" 4 +\fBwith\fR (\fBBUNDLE_WITH\fR): A \fB:\fR\-separated list of groups whose gems bundler should install\. +. +.IP "\(bu" 4 +\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A \fB:\fR\-separated list of groups whose gems bundler should not install\. +. +.IP "" 0 +. +.P +In general, you should set these settings per\-application by using the applicable flag to the bundle install(1) \fIbundle\-install\.1\.html\fR or bundle package(1) \fIbundle\-package\.1\.html\fR command\. +. +.P +You can set them globally either via environment variables or \fBbundle config\fR, whichever is preferable for your setup\. If you use both, environment variables will take preference over global settings\. +. +.SH "LOCAL GIT REPOS" +Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override: +. +.IP "" 4 +. +.nf + +bundle config set \-\-local local\.GEM_NAME /path/to/local/git/repository +. +.fi +. +.IP "" 0 +. +.P +For example, in order to use a local Rack repository, a developer could call: +. +.IP "" 4 +. +.nf + +bundle config set \-\-local local\.rack ~/Work/git/rack +. +.fi +. +.IP "" 0 +. +.P +Now instead of checking out the remote git repository, the local override will be used\. Similar to a path source, every time the local git repository change, changes will be automatically picked up by Bundler\. This means a commit in the local git repo will update the revision in the \fBGemfile\.lock\fR to the local git repo revision\. This requires the same attention as git submodules\. Before pushing to the remote, you need to ensure the local override was pushed, otherwise you may point to a commit that only exists in your local machine\. You\'ll also need to CGI escape your usernames and passwords as well\. +. +.P +Bundler does many checks to ensure a developer won\'t work with invalid references\. Particularly, we force a developer to specify a branch in the \fBGemfile\fR in order to use this feature\. If the branch specified in the \fBGemfile\fR and the current branch in the local git repository do not match, Bundler will abort\. This ensures that a developer is always working against the correct branches, and prevents accidental locking to a different branch\. +. +.P +Finally, Bundler also ensures that the current revision in the \fBGemfile\.lock\fR exists in the local git repository\. By doing this, Bundler forces you to fetch the latest changes in the remotes\. +. +.SH "MIRRORS OF GEM SOURCES" +Bundler supports overriding gem sources with mirrors\. This allows you to configure rubygems\.org as the gem source in your Gemfile while still using your mirror to fetch gems\. +. +.IP "" 4 +. +.nf + +bundle config set \-\-global mirror\.SOURCE_URL MIRROR_URL +. +.fi +. +.IP "" 0 +. +.P +For example, to use a mirror of rubygems\.org hosted at rubygems\-mirror\.org: +. +.IP "" 4 +. +.nf + +bundle config set \-\-global mirror\.http://rubygems\.org http://rubygems\-mirror\.org +. +.fi +. +.IP "" 0 +. +.P +Each mirror also provides a fallback timeout setting\. If the mirror does not respond within the fallback timeout, Bundler will try to use the original server instead of the mirror\. +. +.IP "" 4 +. +.nf + +bundle config set \-\-global mirror\.SOURCE_URL\.fallback_timeout TIMEOUT +. +.fi +. +.IP "" 0 +. +.P +For example, to fall back to rubygems\.org after 3 seconds: +. +.IP "" 4 +. +.nf + +bundle config set \-\-global mirror\.https://rubygems\.org\.fallback_timeout 3 +. +.fi +. +.IP "" 0 +. +.P +The default fallback timeout is 0\.1 seconds, but the setting can currently only accept whole seconds (for example, 1, 15, or 30)\. +. +.SH "CREDENTIALS FOR GEM SOURCES" +Bundler allows you to configure credentials for any gem source, which allows you to avoid putting secrets into your Gemfile\. +. +.IP "" 4 +. +.nf + +bundle config set \-\-global SOURCE_HOSTNAME USERNAME:PASSWORD +. +.fi +. +.IP "" 0 +. +.P +For example, to save the credentials of user \fBclaudette\fR for the gem source at \fBgems\.longerous\.com\fR, you would run: +. +.IP "" 4 +. +.nf + +bundle config set \-\-global gems\.longerous\.com claudette:s00pers3krit +. +.fi +. +.IP "" 0 +. +.P +Or you can set the credentials as an environment variable like this: +. +.IP "" 4 +. +.nf + +export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" +. +.fi +. +.IP "" 0 +. +.P +For gems with a git source with HTTP(S) URL you can specify credentials like so: +. +.IP "" 4 +. +.nf + +bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password +. +.fi +. +.IP "" 0 +. +.P +Or you can set the credentials as an environment variable like so: +. +.IP "" 4 +. +.nf + +export BUNDLE_GITHUB__COM=username:password +. +.fi +. +.IP "" 0 +. +.P +This is especially useful for private repositories on hosts such as Github, where you can use personal OAuth tokens: +. +.IP "" 4 +. +.nf + +export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic +. +.fi +. +.IP "" 0 +. +.P +Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentially leaking credentials when copy\-pasting bundler output\. +. +.P +Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations: +. +.IP "\(bu" 4 +Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding enviroment variable\. +. +.IP "\(bu" 4 +Any \fB\.\fR characters in a host name are mapped to a double dash (\fB__\fR) in the corresponding environment variable\. +. +.IP "" 0 +. +.P +This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you\'ll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\. +. +.SH "CONFIGURE BUNDLER DIRECTORIES" +Bundler\'s home, config, cache and plugin directories are able to be configured through environment variables\. The default location for Bundler\'s home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values +. +.IP "" 4 +. +.nf + +BUNDLE_USER_HOME : $HOME/\.bundle +BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache +BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config +BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin +. +.fi +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1.ronn new file mode 100644 index 0000000000..2e47774497 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-config.1.ronn @@ -0,0 +1,396 @@ +bundle-config(1) -- Set bundler configuration options +===================================================== + +## SYNOPSIS + +`bundle config` [list|get|set|unset] [ []] + +## DESCRIPTION + +This command allows you to interact with Bundler's configuration system. + +Bundler loads configuration settings in this order: + +1. Local config (`/.bundle/config` or `$BUNDLE_APP_CONFIG/config`) +2. Environmental variables (`ENV`) +3. Global config (`~/.bundle/config`) +4. Bundler default config + +Executing `bundle config list` with will print a list of all bundler +configuration for the current bundle, and where that configuration +was set. + +Executing `bundle config get ` will print the value of that configuration +setting, and where it was set. + +Executing `bundle config set ` will set that configuration to the +value specified for all bundles executed as the current user. The configuration +will be stored in `~/.bundle/config`. If already is set, will be +overridden and user will be warned. + +Executing `bundle config set --global ` works the same as above. + +Executing `bundle config set --local ` will set that configuration +in the directory for the local application. The configuration will be stored in +`/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration +will be stored in `$BUNDLE_APP_CONFIG/config`. + +Executing `bundle config unset ` will delete the configuration in both +local and global sources. + +Executing `bundle config unset --global ` will delete the configuration +only from the user configuration. + +Executing `bundle config unset --local ` will delete the +configuration only from the local application. + +Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will +cause it to ignore all configuration. + +## REMEMBERING OPTIONS + +Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or +`--without production`, are remembered between commands and saved to your local +application's configuration (normally, `./.bundle/config`). + +However, this will be changed in bundler 3, so it's better not to rely on this +behavior. If these options must be remembered, it's better to set them using +`bundle config` (e.g., `bundle config set --local path foo`). + +The options that can be configured are: + +* `bin`: + Creates a directory (defaults to `~/bin`) and place any executables from the + gem there. These executables run in Bundler's context. If used, you might add + this directory to your environment's `PATH` variable. For instance, if the + `rails` gem comes with a `rails` executable, this flag will create a + `bin/rails` executable that ensures that all referred dependencies will be + resolved using the bundled gems. + +* `deployment`: + In deployment mode, Bundler will 'roll-out' the bundle for + `production` use. Please check carefully if you want to have this option + enabled in `development` or `test` environments. + +* `path`: + The location to install the specified gems to. This defaults to Rubygems' + setting. Bundler shares this location with Rubygems, `gem install ...` will + have gem installed there, too. Therefore, gems installed without a + `--path ...` setting will show up by calling `gem list`. Accordingly, gems + installed to other locations will not get listed. + +* `without`: + A space-separated list of groups referencing gems to skip during installation. + +* `with`: + A space-separated list of groups referencing gems to include during installation. + +## BUILD OPTIONS + +You can use `bundle config` to give Bundler the flags to pass to the gem +installer every time bundler tries to install a particular gem. + +A very common example, the `mysql` gem, requires Snow Leopard users to +pass configuration flags to `gem install` to specify where to find the +`mysql_config` executable. + + gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config + +Since the specific location of that executable can change from machine +to machine, you can specify these flags on a per-machine basis. + + bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config + +After running this command, every time bundler needs to install the +`mysql` gem, it will pass along the flags you specified. + +## CONFIGURATION KEYS + +Configuration keys in bundler have two forms: the canonical form and the +environment variable form. + +For instance, passing the `--without` flag to [bundle install(1)](bundle-install.1.html) +prevents Bundler from installing certain groups specified in the Gemfile(5). Bundler +persists this value in `app/.bundle/config` so that calls to `Bundler.setup` +do not try to find gems from the `Gemfile` that you didn't install. Additionally, +subsequent calls to [bundle install(1)](bundle-install.1.html) remember this setting +and skip those groups. + +The canonical form of this configuration is `"without"`. To convert the canonical +form to the environment variable form, capitalize it, and prepend `BUNDLE_`. The +environment variable form of `"without"` is `BUNDLE_WITHOUT`. + +Any periods in the configuration keys must be replaced with two underscores when +setting it via environment variables. The configuration key `local.rack` becomes +the environment variable `BUNDLE_LOCAL__RACK`. + +## LIST OF AVAILABLE KEYS + +The following is a list of all configuration keys and their purpose. You can +learn more about their operation in [bundle install(1)](bundle-install.1.html). + +* `allow_deployment_source_credential_changes` (`BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES`): + When in deployment mode, allow changing the credentials to a gem's source. + Ex: `https://some.host.com/gems/path/` -> `https://user_name:password@some.host.com/gems/path` +* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): + Allow Bundler to use cached data when installing without network access. +* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`): + Automatically run `bundle clean` after installing when an explicit `path` + has not been set and Bundler is not installing into the system gems. +* `auto_install` (`BUNDLE_AUTO_INSTALL`): + Automatically run `bundle install` when gems are missing. +* `bin` (`BUNDLE_BIN`): + Install executables from gems in the bundle to the specified directory. + Defaults to `false`. +* `cache_all` (`BUNDLE_CACHE_ALL`): + Cache all gems, including path and git gems. This needs to be explicitly + configured on bundler 1 and bundler 2, but will be the default on bundler 3. +* `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`): + Cache gems for all platforms. +* `cache_path` (`BUNDLE_CACHE_PATH`): + The directory that bundler will place cached gems in when running + bundle package, and that bundler will look in when installing gems. + Defaults to `vendor/cache`. +* `clean` (`BUNDLE_CLEAN`): + Whether Bundler should run `bundle clean` automatically after + `bundle install`. +* `console` (`BUNDLE_CONSOLE`): + The console that `bundle console` starts. Defaults to `irb`. +* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`): + Whether a `bundle install` without an explicit `--path` argument defaults + to installing gems in `.bundle`. +* `deployment` (`BUNDLE_DEPLOYMENT`): + Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the + lockfile has not been updated, running Bundler commands will be blocked. +* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`): + Allow installing gems even if they do not match the checksum provided by + RubyGems. +* `disable_exec_load` (`BUNDLE_DISABLE_EXEC_LOAD`): + Stop Bundler from using `load` to launch an executable in-process in + `bundle exec`. +* `disable_local_branch_check` (`BUNDLE_DISABLE_LOCAL_BRANCH_CHECK`): + Allow Bundler to use a local git override without a branch specified in the + Gemfile. +* `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`): + Allow Bundler to use a local git override without checking if the revision + present in the lockfile is present in the repository. +* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`): + Stop Bundler from accessing gems installed to RubyGems' normal location. +* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`): + Stop Bundler from checking if a newer Bundler version is available on + rubygems.org. +* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`): + Ignore the current machine's platform and install only `ruby` platform gems. + As a result, gems with native extensions will be compiled from source. +* `frozen` (`BUNDLE_FROZEN`): + Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the + lockfile has not been updated, running Bundler commands will be blocked. + Defaults to `true` when `--deployment` is used. +* `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`): + Sets a GitHub username or organization to be used in `README` file when you + create a new gem via `bundle gem` command. It can be overridden by passing an + explicit `--github-username` flag to `bundle gem`. +* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): + Sets the `--key` parameter for `gem push` when using the `rake release` + command with a private gemstash server. +* `gemfile` (`BUNDLE_GEMFILE`): + The name of the file that bundler should use as the `Gemfile`. This location + of this file also sets the root of the project, which is used to resolve + relative paths in the `Gemfile`, among other things. By default, bundler + will search up from the current working directory until it finds a + `Gemfile`. +* `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`): + Whether Bundler should cache all gems globally, rather than locally to the + installing Ruby installation. +* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): + When set, no post install messages will be printed. To silence a single gem, + use dot notation like `ignore_messages.httparty true`. +* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`): + Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`. +* `jobs` (`BUNDLE_JOBS`): + The number of gems Bundler can install in parallel. Defaults to 1 on Windows, + and to the the number of processors on other platforms. +* `no_install` (`BUNDLE_NO_INSTALL`): + Whether `bundle package` should skip installing gems. +* `no_prune` (`BUNDLE_NO_PRUNE`): + Whether Bundler should leave outdated gems unpruned when caching. +* `path` (`BUNDLE_PATH`): + The location on disk where all gems in your bundle will be located regardless + of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location + will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment + is used, defaults to vendor/bundle. +* `path.system` (`BUNDLE_PATH__SYSTEM`): + Whether Bundler will install gems into the default system path (`Gem.dir`). +* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`) + Makes `--path` relative to the CWD instead of the `Gemfile`. +* `plugins` (`BUNDLE_PLUGINS`): + Enable Bundler's experimental plugin system. +* `prefer_patch` (BUNDLE_PREFER_PATCH): + Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. +* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): + Print only version number from `bundler --version`. +* `redirect` (`BUNDLE_REDIRECT`): + The number of redirects allowed for network requests. Defaults to `5`. +* `retry` (`BUNDLE_RETRY`): + The number of times to retry failed network requests. Defaults to `3`. +* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`): + Have `Bundler.setup` make the `Kernel#gem` method public, even though + RubyGems declares it as private. +* `shebang` (`BUNDLE_SHEBANG`): + The program name that should be invoked for generated binstubs. Defaults to + the ruby install name used to generate the binstub. +* `silence_deprecations` (`BUNDLE_SILENCE_DEPRECATIONS`): + Whether Bundler should silence deprecation warnings for behavior that will + be changed in the next major version. +* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): + Silence the warning Bundler prints when installing gems as root. +* `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): + Path to a designated CA certificate file or folder containing multiple + certificates for trusted CAs in PEM format. +* `ssl_client_cert` (`BUNDLE_SSL_CLIENT_CERT`): + Path to a designated file containing a X.509 client certificate + and key in PEM format. +* `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`): + The SSL verification mode Bundler uses when making HTTPS requests. + Defaults to verify peer. +* `suppress_install_using_messages` (`BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES`): + Avoid printing `Using ...` messages during installation when the version of + a gem has not changed. +* `system_bindir` (`BUNDLE_SYSTEM_BINDIR`): + The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. +* `timeout` (`BUNDLE_TIMEOUT`): + The seconds allowed before timing out for network requests. Defaults to `10`. +* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`): + Require passing `--all` to `bundle update` when everything should be updated, + and disallow passing no options to `bundle update`. +* `user_agent` (`BUNDLE_USER_AGENT`): + The custom user agent fragment Bundler includes in API requests. +* `with` (`BUNDLE_WITH`): + A `:`-separated list of groups whose gems bundler should install. +* `without` (`BUNDLE_WITHOUT`): + A `:`-separated list of groups whose gems bundler should not install. + +In general, you should set these settings per-application by using the applicable +flag to the [bundle install(1)](bundle-install.1.html) or [bundle package(1)](bundle-package.1.html) command. + +You can set them globally either via environment variables or `bundle config`, +whichever is preferable for your setup. If you use both, environment variables +will take preference over global settings. + +## LOCAL GIT REPOS + +Bundler also allows you to work against a git repository locally +instead of using the remote version. This can be achieved by setting +up a local override: + + bundle config set --local local.GEM_NAME /path/to/local/git/repository + +For example, in order to use a local Rack repository, a developer could call: + + bundle config set --local local.rack ~/Work/git/rack + +Now instead of checking out the remote git repository, the local +override will be used. Similar to a path source, every time the local +git repository change, changes will be automatically picked up by +Bundler. This means a commit in the local git repo will update the +revision in the `Gemfile.lock` to the local git repo revision. This +requires the same attention as git submodules. Before pushing to +the remote, you need to ensure the local override was pushed, otherwise +you may point to a commit that only exists in your local machine. +You'll also need to CGI escape your usernames and passwords as well. + +Bundler does many checks to ensure a developer won't work with +invalid references. Particularly, we force a developer to specify +a branch in the `Gemfile` in order to use this feature. If the branch +specified in the `Gemfile` and the current branch in the local git +repository do not match, Bundler will abort. This ensures that +a developer is always working against the correct branches, and prevents +accidental locking to a different branch. + +Finally, Bundler also ensures that the current revision in the +`Gemfile.lock` exists in the local git repository. By doing this, Bundler +forces you to fetch the latest changes in the remotes. + +## MIRRORS OF GEM SOURCES + +Bundler supports overriding gem sources with mirrors. This allows you to +configure rubygems.org as the gem source in your Gemfile while still using your +mirror to fetch gems. + + bundle config set --global mirror.SOURCE_URL MIRROR_URL + +For example, to use a mirror of rubygems.org hosted at rubygems-mirror.org: + + bundle config set --global mirror.http://rubygems.org http://rubygems-mirror.org + +Each mirror also provides a fallback timeout setting. If the mirror does not +respond within the fallback timeout, Bundler will try to use the original +server instead of the mirror. + + bundle config set --global mirror.SOURCE_URL.fallback_timeout TIMEOUT + +For example, to fall back to rubygems.org after 3 seconds: + + bundle config set --global mirror.https://rubygems.org.fallback_timeout 3 + +The default fallback timeout is 0.1 seconds, but the setting can currently +only accept whole seconds (for example, 1, 15, or 30). + +## CREDENTIALS FOR GEM SOURCES + +Bundler allows you to configure credentials for any gem source, which allows +you to avoid putting secrets into your Gemfile. + + bundle config set --global SOURCE_HOSTNAME USERNAME:PASSWORD + +For example, to save the credentials of user `claudette` for the gem source at +`gems.longerous.com`, you would run: + + bundle config set --global gems.longerous.com claudette:s00pers3krit + +Or you can set the credentials as an environment variable like this: + + export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" + +For gems with a git source with HTTP(S) URL you can specify credentials like so: + + bundle config set --global https://github.com/rubygems/rubygems.git username:password + +Or you can set the credentials as an environment variable like so: + + export BUNDLE_GITHUB__COM=username:password + +This is especially useful for private repositories on hosts such as Github, +where you can use personal OAuth tokens: + + export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic + +Note that any configured credentials will be redacted by informative commands +such as `bundle config list` or `bundle config get`, unless you use the +`--parseable` flag. This is to avoid unintentially leaking credentials when +copy-pasting bundler output. + +Also note that to guarantee a sane mapping between valid environment variable +names and valid host names, bundler makes the following transformations: + +* Any `-` characters in a host name are mapped to a triple dash (`___`) in the + corresponding enviroment variable. + +* Any `.` characters in a host name are mapped to a double dash (`__`) in the + corresponding environment variable. + +This means that if you have a gem server named `my.gem-host.com`, you'll need to +use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it +through ENV. + +## CONFIGURE BUNDLER DIRECTORIES + +Bundler's home, config, cache and plugin directories are able to be configured +through environment variables. The default location for Bundler's home directory is +`~/.bundle`, which all directories inherit from by default. The following +outlines the available environment variables and their default values + + BUNDLE_USER_HOME : $HOME/.bundle + BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache + BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config + BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1 new file mode 100644 index 0000000000..84d1f4c9df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1 @@ -0,0 +1,44 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-DOCTOR" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-doctor\fR \- Checks the bundle for common problems +. +.SH "SYNOPSIS" +\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +. +.SH "DESCRIPTION" +Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. +. +.P +Examples of common problems caught by bundle\-doctor include: +. +.IP "\(bu" 4 +Invalid Bundler settings +. +.IP "\(bu" 4 +Mismatched Ruby versions +. +.IP "\(bu" 4 +Mismatched platforms +. +.IP "\(bu" 4 +Uninstalled gems +. +.IP "\(bu" 4 +Missing dependencies +. +.IP "" 0 +. +.SH "OPTIONS" +. +.TP +\fB\-\-quiet\fR +Only output warnings and errors\. +. +.TP +\fB\-\-gemfile=\fR +The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1.ronn new file mode 100644 index 0000000000..271ee800ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-doctor.1.ronn @@ -0,0 +1,33 @@ +bundle-doctor(1) -- Checks the bundle for common problems +========================================================= + +## SYNOPSIS + +`bundle doctor` [--quiet] + [--gemfile=GEMFILE] + +## DESCRIPTION + +Checks your Gemfile and gem environment for common problems. If issues +are detected, Bundler prints them and exits status 1. Otherwise, +Bundler prints a success message and exits status 0. + +Examples of common problems caught by bundle-doctor include: + +* Invalid Bundler settings +* Mismatched Ruby versions +* Mismatched platforms +* Uninstalled gems +* Missing dependencies + +## OPTIONS + +* `--quiet`: + Only output warnings and errors. + +* `--gemfile=`: + The location of the Gemfile(5) which Bundler should use. This defaults + to a Gemfile(5) in the current working directory. In general, Bundler + will assume that the location of the Gemfile(5) is also the project's + root and will try to find `Gemfile.lock` and `vendor/cache` relative + to this location. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1 new file mode 100644 index 0000000000..b3c60fbbfd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1 @@ -0,0 +1,165 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-EXEC" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-exec\fR \- Execute a command in the context of the bundle +. +.SH "SYNOPSIS" +\fBbundle exec\fR [\-\-keep\-file\-descriptors] \fIcommand\fR +. +.SH "DESCRIPTION" +This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\. +. +.P +Essentially, if you would normally have run something like \fBrspec spec/my_spec\.rb\fR, and you want to use the gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] and installed via bundle install(1) \fIbundle\-install\.1\.html\fR, you should run \fBbundle exec rspec spec/my_spec\.rb\fR\. +. +.P +Note that \fBbundle exec\fR does not require that an executable is available on your shell\'s \fB$PATH\fR\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-keep\-file\-descriptors\fR +Exec in Ruby 2\.0 began discarding non\-standard file descriptors\. When this flag is passed, exec will revert to the 1\.9 behaviour of passing all file descriptors to the new process\. +. +.SH "BUNDLE INSTALL \-\-BINSTUBS" +If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\. +. +.P +After using \fB\-\-binstubs\fR, \fBbin/rspec spec/my_spec\.rb\fR is identical to \fBbundle exec rspec spec/my_spec\.rb\fR\. +. +.SH "ENVIRONMENT MODIFICATIONS" +\fBbundle exec\fR makes a number of changes to the shell environment, then executes the command you specify in full\. +. +.IP "\(bu" 4 +make sure that it\'s still possible to shell out to \fBbundle\fR from inside a command invoked by \fBbundle exec\fR (using \fB$BUNDLE_BIN_PATH\fR) +. +.IP "\(bu" 4 +put the directory containing executables (like \fBrails\fR, \fBrspec\fR, \fBrackup\fR) for your bundle on \fB$PATH\fR +. +.IP "\(bu" 4 +make sure that if bundler is invoked in the subshell, it uses the same \fBGemfile\fR (by setting \fBBUNDLE_GEMFILE\fR) +. +.IP "\(bu" 4 +add \fB\-rbundler/setup\fR to \fB$RUBYOPT\fR, which makes sure that Ruby programs invoked in the subshell can see the gems in the bundle +. +.IP "" 0 +. +.P +It also modifies Rubygems: +. +.IP "\(bu" 4 +disallow loading additional gems not in the bundle +. +.IP "\(bu" 4 +modify the \fBgem\fR method to be a no\-op if a gem matching the requirements is in the bundle, and to raise a \fBGem::LoadError\fR if it\'s not +. +.IP "\(bu" 4 +Define \fBGem\.refresh\fR to be a no\-op, since the source index is always frozen when using bundler, and to prevent gems from the system leaking into the environment +. +.IP "\(bu" 4 +Override \fBGem\.bin_path\fR to use the gems in the bundle, making system executables work +. +.IP "\(bu" 4 +Add all gems in the bundle into Gem\.loaded_specs +. +.IP "" 0 +. +.P +Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the lockfile and the Gemfile do not match\. Bundler needs the Gemfile to determine things such as a gem\'s groups, \fBautorequire\fR, and platforms, etc\., and that information isn\'t stored in the lockfile\. The Gemfile and lockfile must be synced in order to \fBbundle exec\fR successfully, so \fBbundle exec\fR updates the lockfile beforehand\. +. +.SS "Loading" +By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\. +. +.SS "Shelling out" +Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_clean_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don\'t work inside a bundle: +. +.IP "" 4 +. +.nf + +Bundler\.with_clean_env do + `brew install wget` +end +. +.fi +. +.IP "" 0 +. +.P +Using \fBwith_clean_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_clean_env\fR\. +. +.IP "" 4 +. +.nf + +Bundler\.with_clean_env do + Dir\.chdir "/other/bundler/project" do + `bundle exec \./script` + end +end +. +.fi +. +.IP "" 0 +. +.P +Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this: +. +.IP "" 4 +. +.nf + +Bundler\.clean_system(\'brew install wget\') +Bundler\.clean_exec(\'brew install wget\') +. +.fi +. +.IP "" 0 +. +.SH "RUBYGEMS PLUGINS" +At present, the Rubygems plugin system requires all files named \fBrubygems_plugin\.rb\fR on the load path of \fIany\fR installed gem when any Ruby code requires \fBrubygems\.rb\fR\. This includes executables installed into the system, like \fBrails\fR, \fBrackup\fR, and \fBrspec\fR\. +. +.P +Since Rubygems plugins can contain arbitrary Ruby code, they commonly end up activating themselves or their dependencies\. +. +.P +For instance, the \fBgemcutter 0\.5\fR gem depended on \fBjson_pure\fR\. If you had that version of gemcutter installed (even if you \fIalso\fR had a newer version without this problem), Rubygems would activate \fBgemcutter 0\.5\fR and \fBjson_pure \fR\. +. +.P +If your Gemfile(5) also contained \fBjson_pure\fR (or a gem with a dependency on \fBjson_pure\fR), the latest version on your system might conflict with the version in your Gemfile(5), or the snapshot version in your \fBGemfile\.lock\fR\. +. +.P +If this happens, bundler will say: +. +.IP "" 4 +. +.nf + +You have already activated json_pure 1\.4\.6 but your Gemfile +requires json_pure 1\.4\.3\. Consider using bundle exec\. +. +.fi +. +.IP "" 0 +. +.P +In this situation, you almost certainly want to remove the underlying gem with the problematic gem plugin\. In general, the authors of these plugins (in this case, the \fBgemcutter\fR gem) have released newer versions that are more careful in their plugins\. +. +.P +You can find a list of all the gems containing gem plugins by running +. +.IP "" 4 +. +.nf + +ruby \-rrubygems \-e "puts Gem\.find_files(\'rubygems_plugin\.rb\')" +. +.fi +. +.IP "" 0 +. +.P +At the very least, you should remove all but the newest version of each gem plugin, and also remove all gem plugins that you aren\'t using (\fBgem uninstall gem_name\fR)\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1.ronn new file mode 100644 index 0000000000..dec3c7cb82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-exec.1.ronn @@ -0,0 +1,152 @@ +bundle-exec(1) -- Execute a command in the context of the bundle +================================================================ + +## SYNOPSIS + +`bundle exec` [--keep-file-descriptors] + +## DESCRIPTION + +This command executes the command, making all gems specified in the +[`Gemfile(5)`][Gemfile(5)] available to `require` in Ruby programs. + +Essentially, if you would normally have run something like +`rspec spec/my_spec.rb`, and you want to use the gems specified +in the [`Gemfile(5)`][Gemfile(5)] and installed via [bundle install(1)](bundle-install.1.html), you +should run `bundle exec rspec spec/my_spec.rb`. + +Note that `bundle exec` does not require that an executable is +available on your shell's `$PATH`. + +## OPTIONS + +* `--keep-file-descriptors`: + Exec in Ruby 2.0 began discarding non-standard file descriptors. When this + flag is passed, exec will revert to the 1.9 behaviour of passing all file + descriptors to the new process. + +## BUNDLE INSTALL --BINSTUBS + +If you use the `--binstubs` flag in [bundle install(1)](bundle-install.1.html), Bundler will +automatically create a directory (which defaults to `app_root/bin`) +containing all of the executables available from gems in the bundle. + +After using `--binstubs`, `bin/rspec spec/my_spec.rb` is identical +to `bundle exec rspec spec/my_spec.rb`. + +## ENVIRONMENT MODIFICATIONS + +`bundle exec` makes a number of changes to the shell environment, +then executes the command you specify in full. + +* make sure that it's still possible to shell out to `bundle` + from inside a command invoked by `bundle exec` (using + `$BUNDLE_BIN_PATH`) +* put the directory containing executables (like `rails`, `rspec`, + `rackup`) for your bundle on `$PATH` +* make sure that if bundler is invoked in the subshell, it uses + the same `Gemfile` (by setting `BUNDLE_GEMFILE`) +* add `-rbundler/setup` to `$RUBYOPT`, which makes sure that + Ruby programs invoked in the subshell can see the gems in + the bundle + +It also modifies Rubygems: + +* disallow loading additional gems not in the bundle +* modify the `gem` method to be a no-op if a gem matching + the requirements is in the bundle, and to raise a + `Gem::LoadError` if it's not +* Define `Gem.refresh` to be a no-op, since the source + index is always frozen when using bundler, and to + prevent gems from the system leaking into the environment +* Override `Gem.bin_path` to use the gems in the bundle, + making system executables work +* Add all gems in the bundle into Gem.loaded_specs + +Finally, `bundle exec` also implicitly modifies `Gemfile.lock` if the lockfile +and the Gemfile do not match. Bundler needs the Gemfile to determine things +such as a gem's groups, `autorequire`, and platforms, etc., and that +information isn't stored in the lockfile. The Gemfile and lockfile must be +synced in order to `bundle exec` successfully, so `bundle exec` +updates the lockfile beforehand. + +### Loading + +By default, when attempting to `bundle exec` to a file with a ruby shebang, +Bundler will `Kernel.load` that file instead of using `Kernel.exec`. For the +vast majority of cases, this is a performance improvement. In a rare few cases, +this could cause some subtle side-effects (such as dependence on the exact +contents of `$0` or `__FILE__`) and the optimization can be disabled by enabling +the `disable_exec_load` setting. + +### Shelling out + +Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will +automatically use the current Bundler environment. If you need to shell out to +a Ruby command that is not part of your current bundle, use the +`with_clean_env` method with a block. Any subshells created inside the block +will be given the environment present before Bundler was activated. For +example, Homebrew commands run Ruby, but don't work inside a bundle: + + Bundler.with_clean_env do + `brew install wget` + end + +Using `with_clean_env` is also necessary if you are shelling out to a different +bundle. Any Bundler commands run in a subshell will inherit the current +Gemfile, so commands that need to run in the context of a different bundle also +need to use `with_clean_env`. + + Bundler.with_clean_env do + Dir.chdir "/other/bundler/project" do + `bundle exec ./script` + end + end + +Bundler provides convenience helpers that wrap `system` and `exec`, and they +can be used like this: + + Bundler.clean_system('brew install wget') + Bundler.clean_exec('brew install wget') + + +## RUBYGEMS PLUGINS + +At present, the Rubygems plugin system requires all files +named `rubygems_plugin.rb` on the load path of _any_ installed +gem when any Ruby code requires `rubygems.rb`. This includes +executables installed into the system, like `rails`, `rackup`, +and `rspec`. + +Since Rubygems plugins can contain arbitrary Ruby code, they +commonly end up activating themselves or their dependencies. + +For instance, the `gemcutter 0.5` gem depended on `json_pure`. +If you had that version of gemcutter installed (even if +you _also_ had a newer version without this problem), Rubygems +would activate `gemcutter 0.5` and `json_pure `. + +If your Gemfile(5) also contained `json_pure` (or a gem +with a dependency on `json_pure`), the latest version on +your system might conflict with the version in your +Gemfile(5), or the snapshot version in your `Gemfile.lock`. + +If this happens, bundler will say: + + You have already activated json_pure 1.4.6 but your Gemfile + requires json_pure 1.4.3. Consider using bundle exec. + +In this situation, you almost certainly want to remove the +underlying gem with the problematic gem plugin. In general, +the authors of these plugins (in this case, the `gemcutter` +gem) have released newer versions that are more careful in +their plugins. + +You can find a list of all the gems containing gem plugins +by running + + ruby -rrubygems -e "puts Gem.find_files('rubygems_plugin.rb')" + +At the very least, you should remove all but the newest +version of each gem plugin, and also remove all gem plugins +that you aren't using (`gem uninstall gem_name`). diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1 new file mode 100644 index 0000000000..617454e503 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1 @@ -0,0 +1,102 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-GEM" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem +. +.SH "SYNOPSIS" +\fBbundle gem\fR \fIGEM_NAME\fR \fIOPTIONS\fR +. +.SH "DESCRIPTION" +Generates a directory named \fBGEM_NAME\fR with a \fBRakefile\fR, \fBGEM_NAME\.gemspec\fR, and other supporting files and directories that can be used to develop a rubygem with that name\. +. +.P +Run \fBrake \-T\fR in the resulting project for a list of Rake tasks that can be used to test and publish the gem to rubygems\.org\. +. +.P +The generated project skeleton can be customized with OPTIONS, as explained below\. Note that these options can also be specified via Bundler\'s global configuration file using the following names: +. +.IP "\(bu" 4 +\fBgem\.coc\fR +. +.IP "\(bu" 4 +\fBgem\.mit\fR +. +.IP "\(bu" 4 +\fBgem\.test\fR +. +.IP "" 0 +. +.SH "OPTIONS" +. +.TP +\fB\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\fR +Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. +. +.TP +\fB\-\-no\-exe\fR +Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. +. +.TP +\fB\-\-coc\fR +Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. +. +.TP +\fB\-\-no\-coc\fR +Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. +. +.TP +\fB\-\-ext\fR +Add boilerplate for C extension code to the generated project\. This behavior is disabled by default\. +. +.TP +\fB\-\-no\-ext\fR +Do not add C extension code (overrides \fB\-\-ext\fR specified in the global config)\. +. +.TP +\fB\-\-mit\fR +Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. +. +.TP +\fB\-\-no\-mit\fR +Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. +. +.TP +\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR +Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: +. +.IP +When Bundler is configured to generate tests, this defaults to Bundler\'s global config setting \fBgem\.test\fR\. +. +.IP +When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. +. +.IP +When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. +. +.TP +\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=travis\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\fR +Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBtravis\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +. +.IP +When Bundler is configured to generate CI files, this defaults to Bundler\'s global config setting \fBgem\.ci\fR\. +. +.IP +When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. +. +.IP +When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. +. +.TP +\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR +Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +. +.SH "SEE ALSO" +. +.IP "\(bu" 4 +bundle config(1) \fIbundle\-config\.1\.html\fR +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1.ronn new file mode 100644 index 0000000000..a997cb907a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-gem.1.ronn @@ -0,0 +1,101 @@ +bundle-gem(1) -- Generate a project skeleton for creating a rubygem +==================================================================== + +## SYNOPSIS + +`bundle gem` [OPTIONS] + +## DESCRIPTION + +Generates a directory named `GEM_NAME` with a `Rakefile`, `GEM_NAME.gemspec`, +and other supporting files and directories that can be used to develop a +rubygem with that name. + +Run `rake -T` in the resulting project for a list of Rake tasks that can be used +to test and publish the gem to rubygems.org. + +The generated project skeleton can be customized with OPTIONS, as explained +below. Note that these options can also be specified via Bundler's global +configuration file using the following names: + +* `gem.coc` +* `gem.mit` +* `gem.test` + +## OPTIONS + +* `--exe` or `-b` or `--bin`: + Specify that Bundler should create a binary executable (as `exe/GEM_NAME`) + in the generated rubygem project. This binary will also be added to the + `GEM_NAME.gemspec` manifest. This behavior is disabled by default. + +* `--no-exe`: + Do not create a binary (overrides `--exe` specified in the global config). + +* `--coc`: + Add a `CODE_OF_CONDUCT.md` file to the root of the generated project. If + this option is unspecified, an interactive prompt will be displayed and the + answer will be saved in Bundler's global config for future `bundle gem` use. + +* `--no-coc`: + Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the + global config). + +* `--ext`: + Add boilerplate for C extension code to the generated project. This behavior + is disabled by default. + +* `--no-ext`: + Do not add C extension code (overrides `--ext` specified in the global + config). + +* `--mit`: + Add an MIT license to a `LICENSE.txt` file in the root of the generated + project. Your name from the global git config is used for the copyright + statement. If this option is unspecified, an interactive prompt will be + displayed and the answer will be saved in Bundler's global config for future + `bundle gem` use. + +* `--no-mit`: + Do not create a `LICENSE.txt` (overrides `--mit` specified in the global + config). + +* `-t`, `--test=minitest`, `--test=rspec`, `--test=test-unit`: + Specify the test framework that Bundler should use when generating the + project. Acceptable values are `minitest`, `rspec` and `test-unit`. The + `GEM_NAME.gemspec` will be configured and a skeleton test/spec directory will + be created based on this option. Given no option is specified: + + When Bundler is configured to generate tests, this defaults to Bundler's + global config setting `gem.test`. + + When Bundler is configured to not generate tests, an interactive prompt will + be displayed and the answer will be used for the current rubygem project. + + When Bundler is unconfigured, an interactive prompt will be displayed and + the answer will be saved in Bundler's global config for future `bundle gem` + use. + +* `--ci`, `--ci=github`, `--ci=travis`, `--ci=gitlab`, `--ci=circle`: + Specify the continuous integration service that Bundler should use when + generating the project. Acceptable values are `github`, `travis`, `gitlab` + and `circle`. A configuration file will be generated in the project directory. + Given no option is specified: + + When Bundler is configured to generate CI files, this defaults to Bundler's + global config setting `gem.ci`. + + When Bundler is configured to not generate CI files, an interactive prompt + will be displayed and the answer will be used for the current rubygem project. + + When Bundler is unconfigured, an interactive prompt will be displayed and + the answer will be saved in Bundler's global config for future `bundle gem` + use. + +* `-e`, `--edit[=EDITOR]`: + Open the resulting GEM_NAME.gemspec in EDITOR, or the default editor if not + specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. + +## SEE ALSO + +* [bundle config(1)](bundle-config.1.html) diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1 new file mode 100644 index 0000000000..337e7bdd38 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1 @@ -0,0 +1,20 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-INFO" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-info\fR \- Show information for the given gem in your bundle +. +.SH "SYNOPSIS" +\fBbundle info\fR [GEM] [\-\-path] +. +.SH "DESCRIPTION" +Print the basic information about the provided GEM such as homepage, version, path and summary\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-path\fR +Print the path of the given gem + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1.ronn new file mode 100644 index 0000000000..47e457aa3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-info.1.ronn @@ -0,0 +1,17 @@ +bundle-info(1) -- Show information for the given gem in your bundle +========================================================================= + +## SYNOPSIS + +`bundle info` [GEM] + [--path] + +## DESCRIPTION + +Print the basic information about the provided GEM such as homepage, version, +path and summary. + +## OPTIONS + +* `--path`: +Print the path of the given gem diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1 new file mode 100644 index 0000000000..0c5b8c7a39 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1 @@ -0,0 +1,25 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-INIT" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-init\fR \- Generates a Gemfile into the current working directory +. +.SH "SYNOPSIS" +\fBbundle init\fR [\-\-gemspec=FILE] +. +.SH "DESCRIPTION" +Init generates a default [\fBGemfile(5)\fR][Gemfile(5)] in the current working directory\. When adding a [\fBGemfile(5)\fR][Gemfile(5)] to a gem with a gemspec, the \fB\-\-gemspec\fR option will automatically add each dependency listed in the gemspec file to the newly created [\fBGemfile(5)\fR][Gemfile(5)]\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-gemspec\fR +Use the specified \.gemspec to create the [\fBGemfile(5)\fR][Gemfile(5)] +. +.SH "FILES" +Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\. +. +.SH "SEE ALSO" +Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1.ronn new file mode 100644 index 0000000000..9d3d97deea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-init.1.ronn @@ -0,0 +1,29 @@ +bundle-init(1) -- Generates a Gemfile into the current working directory +======================================================================== + +## SYNOPSIS + +`bundle init` [--gemspec=FILE] + +## DESCRIPTION + +Init generates a default [`Gemfile(5)`][Gemfile(5)] in the current working directory. When +adding a [`Gemfile(5)`][Gemfile(5)] to a gem with a gemspec, the `--gemspec` option will +automatically add each dependency listed in the gemspec file to the newly +created [`Gemfile(5)`][Gemfile(5)]. + +## OPTIONS + +* `--gemspec`: + Use the specified .gemspec to create the [`Gemfile(5)`][Gemfile(5)] + +## FILES + +Included in the default [`Gemfile(5)`][Gemfile(5)] +generated is the line `# frozen_string_literal: true`. This is a magic comment +supported for the first time in Ruby 2.3. The presence of this line +results in all string literals in the file being implicitly frozen. + +## SEE ALSO + +[Gemfile(5)](https://bundler.io/man/gemfile.5.html) diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1 new file mode 100644 index 0000000000..f228ed247f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1 @@ -0,0 +1,33 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-INJECT" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile +. +.SH "SYNOPSIS" +\fBbundle inject\fR [GEM] [VERSION] +. +.SH "DESCRIPTION" +Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\. +. +.P +This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn\'t listed yet\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +bundle install +bundle inject \'rack\' \'> 0\' +. +.fi +. +.IP "" 0 +. +.P +This will inject the \'rack\' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1.ronn new file mode 100644 index 0000000000..f454341896 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-inject.1.ronn @@ -0,0 +1,22 @@ +bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile +========================================================================= + +## SYNOPSIS + +`bundle inject` [GEM] [VERSION] + +## DESCRIPTION + +Adds the named gem(s) with their version requirements to the resolved +[`Gemfile(5)`][Gemfile(5)]. + +This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it +isn't listed yet. + +Example: + + bundle install + bundle inject 'rack' '> 0' + +This will inject the 'rack' gem with a version greater than 0 in your +[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1 new file mode 100644 index 0000000000..ae9747bd83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1 @@ -0,0 +1,338 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-INSTALL" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile +. +.SH "SYNOPSIS" +\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang] [\-\-standalone[=GROUP[ GROUP\.\.\.]]] [\-\-system] [\-\-trust\-policy=POLICY] [\-\-with=GROUP[ GROUP\.\.\.]] [\-\-without=GROUP[ GROUP\.\.\.]] +. +.SH "DESCRIPTION" +Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. +. +.P +If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), Bundler will fetch all remote sources, but use the dependencies specified in the \fBGemfile\.lock\fR instead of resolving dependencies\. +. +.P +If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\. +. +.SH "OPTIONS" +The \fB\-\-clean\fR, \fB\-\-deployment\fR, \fB\-\-frozen\fR, \fB\-\-no\-prune\fR, \fB\-\-path\fR, \fB\-\-shebang\fR, \fB\-\-system\fR, \fB\-\-without\fR and \fB\-\-with\fR options are deprecated because they only make sense if they are applied to every subsequent \fBbundle install\fR run automatically and that requires \fBbundler\fR to silently remember them\. Since \fBbundler\fR will no longer remember CLI flags in future versions, \fBbundle config\fR (see bundle\-config(1)) should be used to apply them permanently\. +. +.TP +\fB\-\-binstubs[=]\fR +Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\. +. +.IP +Creates a directory (defaults to \fB~/bin\fR) and places any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +. +.TP +\fB\-\-clean\fR +On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don\'t worry, gems currently in use will not be removed\. +. +.IP +This option is deprecated in favor of the \fBclean\fR setting\. +. +.TP +\fB\-\-deployment\fR +In \fIdeployment mode\fR, Bundler will \'roll\-out\' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\. +. +.IP +This option is deprecated in favor of the \fBdeployment\fR setting\. +. +.TP +\fB\-\-redownload\fR +Force download every gem, even if the required versions are already available locally\. +. +.TP +\fB\-\-frozen\fR +Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\. +. +.IP +This option is deprecated in favor of the \fBfrozen\fR setting\. +. +.TP +\fB\-\-full\-index\fR +Bundler will not call Rubygems\' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\. +. +.TP +\fB\-\-gemfile=\fR +The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +. +.TP +\fB\-\-jobs=[]\fR, \fB\-j[]\fR +The maximum number of parallel download and install jobs\. The default is \fB1\fR\. +. +.TP +\fB\-\-local\fR +Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. +. +.TP +\fB\-\-no\-cache\fR +Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\. +. +.TP +\fB\-\-no\-prune\fR +Don\'t remove stale gems from the cache when the installation finishes\. +. +.IP +This option is deprecated in favor of the \fBno_prune\fR setting\. +. +.TP +\fB\-\-path=\fR +The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. +. +.IP +This option is deprecated in favor of the \fBpath\fR setting\. +. +.TP +\fB\-\-quiet\fR +Do not print progress information to the standard output\. Instead, Bundler will exit using a status code (\fB$?\fR)\. +. +.TP +\fB\-\-retry=[]\fR +Retry failed network or git requests for \fInumber\fR times\. +. +.TP +\fB\-\-shebang=\fR +Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\. +. +.IP +This option is deprecated in favor of the \fBshebang\fR setting\. +. +.TP +\fB\-\-standalone[=]\fR +Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install has to be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler\'s own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\. +. +.TP +\fB\-\-system\fR +Installs the gems specified in the bundle to the system\'s Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\. +. +.IP +This option is deprecated in favor of the \fBsystem\fR setting\. +. +.TP +\fB\-\-trust\-policy=[]\fR +Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\. +. +.TP +\fB\-\-with=\fR +A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\. +. +.IP +This option is deprecated in favor of the \fBwith\fR setting\. +. +.TP +\fB\-\-without=\fR +A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\. +. +.IP +This option is deprecated in favor of the \fBwithout\fR setting\. +. +.SH "DEPLOYMENT MODE" +Bundler\'s defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\. +. +.IP "1." 4 +A \fBGemfile\.lock\fR is required\. +. +.IP +To ensure that the same versions of the gems you developed with and tested with are also used in deployments, a \fBGemfile\.lock\fR is required\. +. +.IP +This is mainly to ensure that you remember to check your \fBGemfile\.lock\fR into version control\. +. +.IP "2." 4 +The \fBGemfile\.lock\fR must be up to date +. +.IP +In development, you can modify your Gemfile(5) and re\-run \fBbundle install\fR to \fIconservatively update\fR your \fBGemfile\.lock\fR snapshot\. +. +.IP +In deployment, your \fBGemfile\.lock\fR should be up\-to\-date with changes made in your Gemfile(5)\. +. +.IP "3." 4 +Gems are installed to \fBvendor/bundle\fR not your default system location +. +.IP +In development, it\'s convenient to share the gems used in your application with other applications and other scripts that run on the system\. +. +.IP +In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\. +. +.IP +As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\. +. +.IP "" 0 +. +.SH "SUDO USAGE" +By default, Bundler installs gems to the same location as \fBgem install\fR\. +. +.P +In some cases, that location may not be writable by your Unix user\. In that case, Bundler will stage everything in a temporary directory, then ask you for your \fBsudo\fR password in order to copy the gems into their system location\. +. +.P +From your perspective, this is identical to installing the gems directly into the system\. +. +.P +You should never use \fBsudo bundle install\fR\. This is because several other steps in \fBbundle install\fR must be performed as the current user: +. +.IP "\(bu" 4 +Updating your \fBGemfile\.lock\fR +. +.IP "\(bu" 4 +Updating your \fBvendor/cache\fR, if necessary +. +.IP "\(bu" 4 +Checking out private git repositories using your user\'s SSH keys +. +.IP "" 0 +. +.P +Of these three, the first two could theoretically be performed by \fBchown\fRing the resulting files to \fB$SUDO_USER\fR\. The third, however, can only be performed by invoking the \fBgit\fR command as the current user\. Therefore, git gems are downloaded and installed into \fB~/\.bundle\fR rather than $GEM_HOME or $BUNDLE_PATH\. +. +.P +As a result, you should run \fBbundle install\fR as the current user, and Bundler will ask for your password if it is needed to put the gems into their final location\. +. +.SH "INSTALLING GROUPS" +By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\. +. +.P +However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\. +. +.P +While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\. +. +.P +This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\. +. +.P +\fBBundler offers a rock\-solid guarantee that the third\-party code you are running in development and testing is also the third\-party code you are running in production\. You can choose to exclude some of that code in different environments, but you will never be caught flat\-footed by different versions of third\-party code being used in different environments\.\fR +. +.P +For a simple illustration, consider the following Gemfile(5): +. +.IP "" 4 +. +.nf + +source \'https://rubygems\.org\' + +gem \'sinatra\' + +group :production do + gem \'rack\-perftools\-profiler\' +end +. +.fi +. +.IP "" 0 +. +.P +In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\. +. +.P +When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\. +. +.P +This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\. +. +.P +This also means that you cannot include different versions of the same gem in different groups, because doing so would result in different sets of dependencies used in development and production\. Because of the vagaries of the dependency resolution process, this usually affects more than the gems you list in your Gemfile(5), and can (surprisingly) radically change the gems you are using\. +. +.SH "THE GEMFILE\.LOCK" +When you run \fBbundle install\fR, Bundler will persist the full names and versions of all gems that you used (including dependencies of the gems specified in the Gemfile(5)) into a file called \fBGemfile\.lock\fR\. +. +.P +Bundler uses this file in all subsequent calls to \fBbundle install\fR, which guarantees that you always use the same exact code, even as your application moves across machines\. +. +.P +Because of the way dependency resolution works, even a seemingly small change (for instance, an update to a point\-release of a dependency of a gem in your Gemfile(5)) can result in radically different gems being needed to satisfy all dependencies\. +. +.P +As a result, you \fBSHOULD\fR check your \fBGemfile\.lock\fR into version control, in both applications and gems\. If you do not, every machine that checks out your repository (including your production server) will resolve all dependencies again, which will result in different versions of third\-party code being used if \fBany\fR of the gems in the Gemfile(5) or any of their dependencies have been updated\. +. +.P +When Bundler first shipped, the \fBGemfile\.lock\fR was included in the \fB\.gitignore\fR file included with generated gems\. Over time, however, it became clear that this practice forces the pain of broken dependencies onto new contributors, while leaving existing contributors potentially unaware of the problem\. Since \fBbundle install\fR is usually the first step towards a contribution, the pain of broken dependencies would discourage new contributors from contributing\. As a result, we have revised our guidance for gem authors to now recommend checking in the lock for gems\. +. +.SH "CONSERVATIVE UPDATING" +When you make a change to the Gemfile(5) and then run \fBbundle install\fR, Bundler will update only the gems that you modified\. +. +.P +In other words, if a gem that you \fBdid not modify\fR worked before you called \fBbundle install\fR, it will continue to use the exact same versions of all dependencies as it used before the update\. +. +.P +Let\'s take a look at an example\. Here\'s your original Gemfile(5): +. +.IP "" 4 +. +.nf + +source \'https://rubygems\.org\' + +gem \'actionpack\', \'2\.3\.8\' +gem \'activemerchant\' +. +.fi +. +.IP "" 0 +. +.P +In this case, both \fBactionpack\fR and \fBactivemerchant\fR depend on \fBactivesupport\fR\. The \fBactionpack\fR gem depends on \fBactivesupport 2\.3\.8\fR and \fBrack ~> 1\.1\.0\fR, while the \fBactivemerchant\fR gem depends on \fBactivesupport >= 2\.3\.2\fR, \fBbraintree >= 2\.0\.0\fR, and \fBbuilder >= 2\.0\.0\fR\. +. +.P +When the dependencies are first resolved, Bundler will select \fBactivesupport 2\.3\.8\fR, which satisfies the requirements of both gems in your Gemfile(5)\. +. +.P +Next, you modify your Gemfile(5) to: +. +.IP "" 4 +. +.nf + +source \'https://rubygems\.org\' + +gem \'actionpack\', \'3\.0\.0\.rc\' +gem \'activemerchant\' +. +.fi +. +.IP "" 0 +. +.P +The \fBactionpack 3\.0\.0\.rc\fR gem has a number of new dependencies, and updates the \fBactivesupport\fR dependency to \fB= 3\.0\.0\.rc\fR and the \fBrack\fR dependency to \fB~> 1\.2\.1\fR\. +. +.P +When you run \fBbundle install\fR, Bundler notices that you changed the \fBactionpack\fR gem, but not the \fBactivemerchant\fR gem\. It evaluates the gems currently being used to satisfy its requirements: +. +.TP +\fBactivesupport 2\.3\.8\fR +also used to satisfy a dependency in \fBactivemerchant\fR, which is not being updated +. +.TP +\fBrack ~> 1\.1\.0\fR +not currently being used to satisfy another dependency +. +.P +Because you did not explicitly ask to update \fBactivemerchant\fR, you would not expect it to suddenly stop working after updating \fBactionpack\fR\. However, satisfying the new \fBactivesupport 3\.0\.0\.rc\fR dependency of actionpack requires updating one of its dependencies\. +. +.P +Even though \fBactivemerchant\fR declares a very loose dependency that theoretically matches \fBactivesupport 3\.0\.0\.rc\fR, Bundler treats gems in your Gemfile(5) that have not changed as an atomic unit together with their dependencies\. In this case, the \fBactivemerchant\fR dependency is treated as \fBactivemerchant 1\.7\.1 + activesupport 2\.3\.8\fR, so \fBbundle install\fR will report that it cannot update \fBactionpack\fR\. +. +.P +To explicitly update \fBactionpack\fR, including its dependencies which other gems in the Gemfile(5) still depend on, run \fBbundle update actionpack\fR (see \fBbundle update(1)\fR)\. +. +.P +\fBSummary\fR: In general, after making a change to the Gemfile(5) , you should first try to run \fBbundle install\fR, which will guarantee that no other gem in the Gemfile(5) is impacted by the change\. If that does not work, run bundle update(1) \fIbundle\-update\.1\.html\fR\. +. +.SH "SEE ALSO" +. +.IP "\(bu" 4 +Gem install docs \fIhttp://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR +. +.IP "\(bu" 4 +Rubygems signing docs \fIhttp://guides\.rubygems\.org/security/\fR +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1.ronn new file mode 100644 index 0000000000..5ea777f1d4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-install.1.ronn @@ -0,0 +1,405 @@ +bundle-install(1) -- Install the dependencies specified in your Gemfile +======================================================================= + +## SYNOPSIS + +`bundle install` [--binstubs[=DIRECTORY]] + [--clean] + [--deployment] + [--frozen] + [--full-index] + [--gemfile=GEMFILE] + [--jobs=NUMBER] + [--local] + [--no-cache] + [--no-prune] + [--path PATH] + [--quiet] + [--redownload] + [--retry=NUMBER] + [--shebang] + [--standalone[=GROUP[ GROUP...]]] + [--system] + [--trust-policy=POLICY] + [--with=GROUP[ GROUP...]] + [--without=GROUP[ GROUP...]] + +## DESCRIPTION + +Install the gems specified in your Gemfile(5). If this is the first +time you run bundle install (and a `Gemfile.lock` does not exist), +Bundler will fetch all remote sources, resolve dependencies and +install all needed gems. + +If a `Gemfile.lock` does exist, and you have not updated your Gemfile(5), +Bundler will fetch all remote sources, but use the dependencies +specified in the `Gemfile.lock` instead of resolving dependencies. + +If a `Gemfile.lock` does exist, and you have updated your Gemfile(5), +Bundler will use the dependencies in the `Gemfile.lock` for all gems +that you did not update, but will re-resolve the dependencies of +gems that you did update. You can find more information about this +update process below under [CONSERVATIVE UPDATING][]. + +## OPTIONS + +The `--clean`, `--deployment`, `--frozen`, `--no-prune`, `--path`, `--shebang`, +`--system`, `--without` and `--with` options are deprecated because they only +make sense if they are applied to every subsequent `bundle install` run +automatically and that requires `bundler` to silently remember them. Since +`bundler` will no longer remember CLI flags in future versions, `bundle config` +(see bundle-config(1)) should be used to apply them permanently. + +* `--binstubs[=]`: + Binstubs are scripts that wrap around executables. Bundler creates a small Ruby + file (a binstub) that loads Bundler, runs the command, and puts it in `bin/`. + This lets you link the binstub inside of an application to the exact gem + version the application needs. + + Creates a directory (defaults to `~/bin`) and places any executables from the + gem there. These executables run in Bundler's context. If used, you might add + this directory to your environment's `PATH` variable. For instance, if the + `rails` gem comes with a `rails` executable, this flag will create a + `bin/rails` executable that ensures that all referred dependencies will be + resolved using the bundled gems. + +* `--clean`: + On finishing the installation Bundler is going to remove any gems not present + in the current Gemfile(5). Don't worry, gems currently in use will not be + removed. + + This option is deprecated in favor of the `clean` setting. + +* `--deployment`: + In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for + production or CI use. Please check carefully if you want to have this option + enabled in your development environment. + + This option is deprecated in favor of the `deployment` setting. + +* `--redownload`: + Force download every gem, even if the required versions are already available + locally. + +* `--frozen`: + Do not allow the Gemfile.lock to be updated after this install. Exits + non-zero if there are going to be changes to the Gemfile.lock. + + This option is deprecated in favor of the `frozen` setting. + +* `--full-index`: + Bundler will not call Rubygems' API endpoint (default) but download and cache + a (currently big) index file of all gems. Performance can be improved for + large bundles that seldom change by enabling this option. + +* `--gemfile=`: + The location of the Gemfile(5) which Bundler should use. This defaults + to a Gemfile(5) in the current working directory. In general, Bundler + will assume that the location of the Gemfile(5) is also the project's + root and will try to find `Gemfile.lock` and `vendor/cache` relative + to this location. + +* `--jobs=[]`, `-j[]`: + The maximum number of parallel download and install jobs. The default + is `1`. + +* `--local`: + Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the + gems already present in Rubygems' cache or in `vendor/cache`. Note that if an + appropriate platform-specific gem exists on `rubygems.org` it will not be + found. + +* `--no-cache`: + Do not update the cache in `vendor/cache` with the newly bundled gems. This + does not remove any gems in the cache but keeps the newly bundled gems from + being cached during the install. + +* `--no-prune`: + Don't remove stale gems from the cache when the installation finishes. + + This option is deprecated in favor of the `no_prune` setting. + +* `--path=`: + The location to install the specified gems to. This defaults to Rubygems' + setting. Bundler shares this location with Rubygems, `gem install ...` will + have gem installed there, too. Therefore, gems installed without a + `--path ...` setting will show up by calling `gem list`. Accordingly, gems + installed to other locations will not get listed. + + This option is deprecated in favor of the `path` setting. + +* `--quiet`: + Do not print progress information to the standard output. Instead, Bundler + will exit using a status code (`$?`). + +* `--retry=[]`: + Retry failed network or git requests for times. + +* `--shebang=`: + Uses the specified ruby executable (usually `ruby`) to execute the scripts + created with `--binstubs`. In addition, if you use `--binstubs` together with + `--shebang jruby` these executables will be changed to execute `jruby` + instead. + + This option is deprecated in favor of the `shebang` setting. + +* `--standalone[=]`: + Makes a bundle that can work without depending on Rubygems or Bundler at + runtime. A space separated list of groups to install has to be specified. + Bundler creates a directory named `bundle` and installs the bundle there. It + also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup + in the manner required. Using this option implicitly sets `path`, which is a + [remembered option][REMEMBERED OPTIONS]. + +* `--system`: + Installs the gems specified in the bundle to the system's Rubygems location. + This overrides any previous configuration of `--path`. + + This option is deprecated in favor of the `system` setting. + +* `--trust-policy=[]`: + Apply the Rubygems security policy , where policy is one of + `HighSecurity`, `MediumSecurity`, `LowSecurity`, `AlmostNoSecurity`, or + `NoSecurity`. For more details, please see the Rubygems signing documentation + linked below in [SEE ALSO][]. + +* `--with=`: + A space-separated list of groups referencing gems to install. If an + optional group is given it is installed. If a group is given that is + in the remembered list of groups given to --without, it is removed + from that list. + + This option is deprecated in favor of the `with` setting. + +* `--without=`: + A space-separated list of groups referencing gems to skip during installation. + If a group is given that is in the remembered list of groups given + to --with, it is removed from that list. + + This option is deprecated in favor of the `without` setting. + +## DEPLOYMENT MODE + +Bundler's defaults are optimized for development. To switch to +defaults optimized for deployment and for CI, use the `--deployment` +flag. Do not activate deployment mode on development machines, as it +will cause an error when the Gemfile(5) is modified. + +1. A `Gemfile.lock` is required. + + To ensure that the same versions of the gems you developed with + and tested with are also used in deployments, a `Gemfile.lock` + is required. + + This is mainly to ensure that you remember to check your + `Gemfile.lock` into version control. + +2. The `Gemfile.lock` must be up to date + + In development, you can modify your Gemfile(5) and re-run + `bundle install` to [conservatively update][CONSERVATIVE UPDATING] + your `Gemfile.lock` snapshot. + + In deployment, your `Gemfile.lock` should be up-to-date with + changes made in your Gemfile(5). + +3. Gems are installed to `vendor/bundle` not your default system location + + In development, it's convenient to share the gems used in your + application with other applications and other scripts that run on + the system. + + In deployment, isolation is a more important default. In addition, + the user deploying the application may not have permission to install + gems to the system, or the web server may not have permission to + read them. + + As a result, `bundle install --deployment` installs gems to + the `vendor/bundle` directory in the application. This may be + overridden using the `--path` option. + +## SUDO USAGE + +By default, Bundler installs gems to the same location as `gem install`. + +In some cases, that location may not be writable by your Unix user. In +that case, Bundler will stage everything in a temporary directory, +then ask you for your `sudo` password in order to copy the gems into +their system location. + +From your perspective, this is identical to installing the gems +directly into the system. + +You should never use `sudo bundle install`. This is because several +other steps in `bundle install` must be performed as the current user: + +* Updating your `Gemfile.lock` +* Updating your `vendor/cache`, if necessary +* Checking out private git repositories using your user's SSH keys + +Of these three, the first two could theoretically be performed by +`chown`ing the resulting files to `$SUDO_USER`. The third, however, +can only be performed by invoking the `git` command as +the current user. Therefore, git gems are downloaded and installed +into `~/.bundle` rather than $GEM_HOME or $BUNDLE_PATH. + +As a result, you should run `bundle install` as the current user, +and Bundler will ask for your password if it is needed to put the +gems into their final location. + +## INSTALLING GROUPS + +By default, `bundle install` will install all gems in all groups +in your Gemfile(5), except those declared for a different platform. + +However, you can explicitly tell Bundler to skip installing +certain groups with the `--without` option. This option takes +a space-separated list of groups. + +While the `--without` option will skip _installing_ the gems in the +specified groups, it will still _download_ those gems and use them to +resolve the dependencies of every gem in your Gemfile(5). + +This is so that installing a different set of groups on another + machine (such as a production server) will not change the +gems and versions that you have already developed and tested against. + +`Bundler offers a rock-solid guarantee that the third-party +code you are running in development and testing is also the +third-party code you are running in production. You can choose +to exclude some of that code in different environments, but you +will never be caught flat-footed by different versions of +third-party code being used in different environments.` + +For a simple illustration, consider the following Gemfile(5): + + source 'https://rubygems.org' + + gem 'sinatra' + + group :production do + gem 'rack-perftools-profiler' + end + +In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while +`rack-perftools-profiler` depends on 1.x (`~> 1.0`). + +When you run `bundle install --without production` in development, we +look at the dependencies of `rack-perftools-profiler` as well. That way, +you do not spend all your time developing against Rack 2.0, using new +APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2 +when the `production` group _is_ used. + +This should not cause any problems in practice, because we do not +attempt to `install` the gems in the excluded groups, and only evaluate +as part of the dependency resolution process. + +This also means that you cannot include different versions of the same +gem in different groups, because doing so would result in different +sets of dependencies used in development and production. Because of +the vagaries of the dependency resolution process, this usually +affects more than the gems you list in your Gemfile(5), and can +(surprisingly) radically change the gems you are using. + +## THE GEMFILE.LOCK + +When you run `bundle install`, Bundler will persist the full names +and versions of all gems that you used (including dependencies of +the gems specified in the Gemfile(5)) into a file called `Gemfile.lock`. + +Bundler uses this file in all subsequent calls to `bundle install`, +which guarantees that you always use the same exact code, even +as your application moves across machines. + +Because of the way dependency resolution works, even a +seemingly small change (for instance, an update to a point-release +of a dependency of a gem in your Gemfile(5)) can result in radically +different gems being needed to satisfy all dependencies. + +As a result, you `SHOULD` check your `Gemfile.lock` into version +control, in both applications and gems. If you do not, every machine that +checks out your repository (including your production server) will resolve all +dependencies again, which will result in different versions of +third-party code being used if `any` of the gems in the Gemfile(5) +or any of their dependencies have been updated. + +When Bundler first shipped, the `Gemfile.lock` was included in the `.gitignore` +file included with generated gems. Over time, however, it became clear that +this practice forces the pain of broken dependencies onto new contributors, +while leaving existing contributors potentially unaware of the problem. Since +`bundle install` is usually the first step towards a contribution, the pain of +broken dependencies would discourage new contributors from contributing. As a +result, we have revised our guidance for gem authors to now recommend checking +in the lock for gems. + +## CONSERVATIVE UPDATING + +When you make a change to the Gemfile(5) and then run `bundle install`, +Bundler will update only the gems that you modified. + +In other words, if a gem that you `did not modify` worked before +you called `bundle install`, it will continue to use the exact +same versions of all dependencies as it used before the update. + +Let's take a look at an example. Here's your original Gemfile(5): + + source 'https://rubygems.org' + + gem 'actionpack', '2.3.8' + gem 'activemerchant' + +In this case, both `actionpack` and `activemerchant` depend on +`activesupport`. The `actionpack` gem depends on `activesupport 2.3.8` +and `rack ~> 1.1.0`, while the `activemerchant` gem depends on +`activesupport >= 2.3.2`, `braintree >= 2.0.0`, and `builder >= 2.0.0`. + +When the dependencies are first resolved, Bundler will select +`activesupport 2.3.8`, which satisfies the requirements of both +gems in your Gemfile(5). + +Next, you modify your Gemfile(5) to: + + source 'https://rubygems.org' + + gem 'actionpack', '3.0.0.rc' + gem 'activemerchant' + +The `actionpack 3.0.0.rc` gem has a number of new dependencies, +and updates the `activesupport` dependency to `= 3.0.0.rc` and +the `rack` dependency to `~> 1.2.1`. + +When you run `bundle install`, Bundler notices that you changed +the `actionpack` gem, but not the `activemerchant` gem. It +evaluates the gems currently being used to satisfy its requirements: + + * `activesupport 2.3.8`: + also used to satisfy a dependency in `activemerchant`, + which is not being updated + * `rack ~> 1.1.0`: + not currently being used to satisfy another dependency + +Because you did not explicitly ask to update `activemerchant`, +you would not expect it to suddenly stop working after updating +`actionpack`. However, satisfying the new `activesupport 3.0.0.rc` +dependency of actionpack requires updating one of its dependencies. + +Even though `activemerchant` declares a very loose dependency +that theoretically matches `activesupport 3.0.0.rc`, Bundler treats +gems in your Gemfile(5) that have not changed as an atomic unit +together with their dependencies. In this case, the `activemerchant` +dependency is treated as `activemerchant 1.7.1 + activesupport 2.3.8`, +so `bundle install` will report that it cannot update `actionpack`. + +To explicitly update `actionpack`, including its dependencies +which other gems in the Gemfile(5) still depend on, run +`bundle update actionpack` (see `bundle update(1)`). + +`Summary`: In general, after making a change to the Gemfile(5) , you +should first try to run `bundle install`, which will guarantee that no +other gem in the Gemfile(5) is impacted by the change. If that +does not work, run [bundle update(1)](bundle-update.1.html). + +## SEE ALSO + +* [Gem install docs](http://guides.rubygems.org/rubygems-basics/#installing-gems) +* [Rubygems signing docs](http://guides.rubygems.org/security/) diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1 new file mode 100644 index 0000000000..cce8881059 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1 @@ -0,0 +1,50 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-LIST" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-list\fR \- List all the gems in the bundle +. +.SH "SYNOPSIS" +\fBbundle list\fR [\-\-name\-only] [\-\-paths] [\-\-without\-group=GROUP[ GROUP\.\.\.]] [\-\-only\-group=GROUP[ GROUP\.\.\.]] +. +.SH "DESCRIPTION" +Prints a list of all the gems in the bundle including their version\. +. +.P +Example: +. +.P +bundle list \-\-name\-only +. +.P +bundle list \-\-paths +. +.P +bundle list \-\-without\-group test +. +.P +bundle list \-\-only\-group dev +. +.P +bundle list \-\-only\-group dev test \-\-paths +. +.SH "OPTIONS" +. +.TP +\fB\-\-name\-only\fR +Print only the name of each gem\. +. +.TP +\fB\-\-paths\fR +Print the path to each gem in the bundle\. +. +.TP +\fB\-\-without\-group=\fR +A space\-separated list of groups of gems to skip during printing\. +. +.TP +\fB\-\-only\-group=\fR +A space\-separated list of groups of gems to print\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1.ronn new file mode 100644 index 0000000000..dc058ecd5f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-list.1.ronn @@ -0,0 +1,33 @@ +bundle-list(1) -- List all the gems in the bundle +========================================================================= + +## SYNOPSIS + +`bundle list` [--name-only] [--paths] [--without-group=GROUP[ GROUP...]] [--only-group=GROUP[ GROUP...]] + +## DESCRIPTION + +Prints a list of all the gems in the bundle including their version. + +Example: + +bundle list --name-only + +bundle list --paths + +bundle list --without-group test + +bundle list --only-group dev + +bundle list --only-group dev test --paths + +## OPTIONS + +* `--name-only`: + Print only the name of each gem. +* `--paths`: + Print the path to each gem in the bundle. +* `--without-group=`: + A space-separated list of groups of gems to skip during printing. +* `--only-group=`: + A space-separated list of groups of gems to print. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1 new file mode 100644 index 0000000000..23388daaee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1 @@ -0,0 +1,84 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-LOCK" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing +. +.SH "SYNOPSIS" +\fBbundle lock\fR [\-\-update] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-add\-platform] [\-\-remove\-platform] [\-\-patch] [\-\-minor] [\-\-major] [\-\-strict] [\-\-conservative] +. +.SH "DESCRIPTION" +Lock the gems specified in Gemfile\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-update=<*gems>\fR +Ignores the existing lockfile\. Resolve then updates lockfile\. Taking a list of gems or updating all gems if no list is given\. +. +.TP +\fB\-\-local\fR +Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. +. +.TP +\fB\-\-print\fR +Prints the lockfile to STDOUT instead of writing to the file system\. +. +.TP +\fB\-\-lockfile=\fR +The path where the lockfile should be written to\. +. +.TP +\fB\-\-full\-index\fR +Fall back to using the single\-file index of all gems\. +. +.TP +\fB\-\-add\-platform\fR +Add a new platform to the lockfile, re\-resolving for the addition of that platform\. +. +.TP +\fB\-\-remove\-platform\fR +Remove a platform from the lockfile\. +. +.TP +\fB\-\-patch\fR +If updating, prefer updating only to next patch version\. +. +.TP +\fB\-\-minor\fR +If updating, prefer updating only to next minor version\. +. +.TP +\fB\-\-major\fR +If updating, prefer updating to next major version (default)\. +. +.TP +\fB\-\-strict\fR +If updating, do not allow any gem to be updated past latest \-\-patch | \-\-minor | \-\-major\. +. +.TP +\fB\-\-conservative\fR +If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated\. +. +.SH "UPDATING ALL GEMS" +If you run \fBbundle lock\fR with \fB\-\-update\fR option without list of gems, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\. +. +.SH "UPDATING A LIST OF GEMS" +Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\. +. +.P +For instance, you only want to update \fBnokogiri\fR, run \fBbundle lock \-\-update nokogiri\fR\. +. +.P +Bundler will update \fBnokogiri\fR and any of its dependencies, but leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\. +. +.SH "SUPPORTING OTHER PLATFORMS" +If you want your bundle to support platforms other than the one you\'re running locally, you can run \fBbundle lock \-\-add\-platform PLATFORM\fR to add PLATFORM to the lockfile, force bundler to re\-resolve and consider the new platform when picking gems, all without needing to have a machine that matches PLATFORM handy to install those platform\-specific gems on\. +. +.P +For a full explanation of gem platforms, see \fBgem help platform\fR\. +. +.SH "PATCH LEVEL OPTIONS" +See bundle update(1) \fIbundle\-update\.1\.html\fR for details\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1.ronn new file mode 100644 index 0000000000..3aa5920f5a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-lock.1.ronn @@ -0,0 +1,94 @@ +bundle-lock(1) -- Creates / Updates a lockfile without installing +================================================================= + +## SYNOPSIS + +`bundle lock` [--update] + [--local] + [--print] + [--lockfile=PATH] + [--full-index] + [--add-platform] + [--remove-platform] + [--patch] + [--minor] + [--major] + [--strict] + [--conservative] + +## DESCRIPTION + +Lock the gems specified in Gemfile. + +## OPTIONS + +* `--update=<*gems>`: + Ignores the existing lockfile. Resolve then updates lockfile. Taking a list + of gems or updating all gems if no list is given. + +* `--local`: + Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the + gems already present in Rubygems' cache or in `vendor/cache`. Note that if a + appropriate platform-specific gem exists on `rubygems.org` it will not be + found. + +* `--print`: + Prints the lockfile to STDOUT instead of writing to the file system. + +* `--lockfile=`: + The path where the lockfile should be written to. + +* `--full-index`: + Fall back to using the single-file index of all gems. + +* `--add-platform`: + Add a new platform to the lockfile, re-resolving for the addition of that + platform. + +* `--remove-platform`: + Remove a platform from the lockfile. + +* `--patch`: + If updating, prefer updating only to next patch version. + +* `--minor`: + If updating, prefer updating only to next minor version. + +* `--major`: + If updating, prefer updating to next major version (default). + +* `--strict`: + If updating, do not allow any gem to be updated past latest --patch | --minor | --major. + +* `--conservative`: + If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated. + +## UPDATING ALL GEMS + +If you run `bundle lock` with `--update` option without list of gems, bundler will +ignore any previously installed gems and resolve all dependencies again based +on the latest versions of all gems available in the sources. + +## UPDATING A LIST OF GEMS + +Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of +the gems that you specified locked to the versions in the `Gemfile.lock`. + +For instance, you only want to update `nokogiri`, run `bundle lock --update nokogiri`. + +Bundler will update `nokogiri` and any of its dependencies, but leave the rest of the +gems that you specified locked to the versions in the `Gemfile.lock`. + +## SUPPORTING OTHER PLATFORMS + +If you want your bundle to support platforms other than the one you're running +locally, you can run `bundle lock --add-platform PLATFORM` to add PLATFORM to +the lockfile, force bundler to re-resolve and consider the new platform when +picking gems, all without needing to have a machine that matches PLATFORM handy +to install those platform-specific gems on. + +For a full explanation of gem platforms, see `gem help platform`. + +## PATCH LEVEL OPTIONS + +See [bundle update(1)](bundle-update.1.html) for details. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1 new file mode 100644 index 0000000000..8cc104b81d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1 @@ -0,0 +1,32 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-OPEN" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle +. +.SH "SYNOPSIS" +\fBbundle open\fR [GEM] +. +.SH "DESCRIPTION" +Opens the source directory of the provided GEM in your editor\. +. +.P +For this to work the \fBEDITOR\fR or \fBBUNDLER_EDITOR\fR environment variable has to be set\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +bundle open \'rack\' +. +.fi +. +.IP "" 0 +. +.P +Will open the source directory for the \'rack\' gem in your bundle\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1.ronn new file mode 100644 index 0000000000..497beac93f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-open.1.ronn @@ -0,0 +1,19 @@ +bundle-open(1) -- Opens the source directory for a gem in your bundle +===================================================================== + +## SYNOPSIS + +`bundle open` [GEM] + +## DESCRIPTION + +Opens the source directory of the provided GEM in your editor. + +For this to work the `EDITOR` or `BUNDLER_EDITOR` environment variable has to +be set. + +Example: + + bundle open 'rack' + +Will open the source directory for the 'rack' gem in your bundle. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1 new file mode 100644 index 0000000000..bdd81a66af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1 @@ -0,0 +1,155 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-OUTDATED" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-outdated\fR \- List installed gems with newer versions available +. +.SH "SYNOPSIS" +\fBbundle outdated\fR [GEM] [\-\-local] [\-\-pre] [\-\-source] [\-\-strict] [\-\-parseable | \-\-porcelain] [\-\-group=GROUP] [\-\-groups] [\-\-update\-strict] [\-\-patch|\-\-minor|\-\-major] [\-\-filter\-major] [\-\-filter\-minor] [\-\-filter\-patch] [\-\-only\-explicit] +. +.SH "DESCRIPTION" +Outdated lists the names and versions of gems that have a newer version available in the given source\. Calling outdated with [GEM [GEM]] will only check for newer versions of the given gems\. Prerelease gems are ignored by default\. If your gems are up to date, Bundler will exit with a status of 0\. Otherwise, it will exit 1\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-local\fR +Do not attempt to fetch gems remotely and use the gem cache instead\. +. +.TP +\fB\-\-pre\fR +Check for newer pre\-release gems\. +. +.TP +\fB\-\-source\fR +Check against a specific source\. +. +.TP +\fB\-\-strict\fR +Only list newer versions allowed by your Gemfile requirements\. +. +.TP +\fB\-\-parseable\fR, \fB\-\-porcelain\fR +Use minimal formatting for more parseable output\. +. +.TP +\fB\-\-group\fR +List gems from a specific group\. +. +.TP +\fB\-\-groups\fR +List gems organized by groups\. +. +.TP +\fB\-\-update\-strict\fR +Strict conservative resolution, do not allow any gem to be updated past latest \-\-patch | \-\-minor| \-\-major\. +. +.TP +\fB\-\-minor\fR +Prefer updating only to next minor version\. +. +.TP +\fB\-\-major\fR +Prefer updating to next major version (default)\. +. +.TP +\fB\-\-patch\fR +Prefer updating only to next patch version\. +. +.TP +\fB\-\-filter\-major\fR +Only list major newer versions\. +. +.TP +\fB\-\-filter\-minor\fR +Only list minor newer versions\. +. +.TP +\fB\-\-filter\-patch\fR +Only list patch newer versions\. +. +.TP +\fB\-\-only\-explicit\fR +Only list gems specified in your Gemfile, not their dependencies\. +. +.SH "PATCH LEVEL OPTIONS" +See bundle update(1) \fIbundle\-update\.1\.html\fR for details\. +. +.P +One difference between the patch level options in \fBbundle update\fR and here is the \fB\-\-strict\fR option\. \fB\-\-strict\fR was already an option on outdated before the patch level options were added\. \fB\-\-strict\fR wasn\'t altered, and the \fB\-\-update\-strict\fR option on \fBoutdated\fR reflects what \fB\-\-strict\fR does on \fBbundle update\fR\. +. +.SH "FILTERING OUTPUT" +The 3 filtering options do not affect the resolution of versions, merely what versions are shown in the output\. +. +.P +If the regular output shows the following: +. +.IP "" 4 +. +.nf + +* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" +* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default" +* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +. +.fi +. +.IP "" 0 +. +.P +\fB\-\-filter\-major\fR would only show: +. +.IP "" 4 +. +.nf + +* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default" +. +.fi +. +.IP "" 0 +. +.P +\fB\-\-filter\-minor\fR would only show: +. +.IP "" 4 +. +.nf + +* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +. +.fi +. +.IP "" 0 +. +.P +\fB\-\-filter\-patch\fR would only show: +. +.IP "" 4 +. +.nf + +* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" +. +.fi +. +.IP "" 0 +. +.P +Filter options can be combined\. \fB\-\-filter\-minor\fR and \fB\-\-filter\-patch\fR would show: +. +.IP "" 4 +. +.nf + +* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" +* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +. +.fi +. +.IP "" 0 +. +.P +Combining all three \fBfilter\fR options would be the same result as providing none of them\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1.ronn new file mode 100644 index 0000000000..a991d23789 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-outdated.1.ronn @@ -0,0 +1,111 @@ +bundle-outdated(1) -- List installed gems with newer versions available +======================================================================= + +## SYNOPSIS + +`bundle outdated` [GEM] [--local] + [--pre] + [--source] + [--strict] + [--parseable | --porcelain] + [--group=GROUP] + [--groups] + [--update-strict] + [--patch|--minor|--major] + [--filter-major] + [--filter-minor] + [--filter-patch] + [--only-explicit] + +## DESCRIPTION + +Outdated lists the names and versions of gems that have a newer version available +in the given source. Calling outdated with [GEM [GEM]] will only check for newer +versions of the given gems. Prerelease gems are ignored by default. If your gems +are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1. + +## OPTIONS + +* `--local`: + Do not attempt to fetch gems remotely and use the gem cache instead. + +* `--pre`: + Check for newer pre-release gems. + +* `--source`: + Check against a specific source. + +* `--strict`: + Only list newer versions allowed by your Gemfile requirements. + +* `--parseable`, `--porcelain`: + Use minimal formatting for more parseable output. + +* `--group`: + List gems from a specific group. + +* `--groups`: + List gems organized by groups. + +* `--update-strict`: + Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor| --major. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--patch`: + Prefer updating only to next patch version. + +* `--filter-major`: + Only list major newer versions. + +* `--filter-minor`: + Only list minor newer versions. + +* `--filter-patch`: + Only list patch newer versions. + +* `--only-explicit`: + Only list gems specified in your Gemfile, not their dependencies. + +## PATCH LEVEL OPTIONS + +See [bundle update(1)](bundle-update.1.html) for details. + +One difference between the patch level options in `bundle update` and here is the `--strict` option. +`--strict` was already an option on outdated before the patch level options were added. `--strict` +wasn't altered, and the `--update-strict` option on `outdated` reflects what `--strict` does on +`bundle update`. + +## FILTERING OUTPUT + +The 3 filtering options do not affect the resolution of versions, merely what versions are shown +in the output. + +If the regular output shows the following: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +`--filter-major` would only show: + + * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" + +`--filter-minor` would only show: + + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +`--filter-patch` would only show: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + +Filter options can be combined. `--filter-minor` and `--filter-patch` would show: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +Combining all three `filter` options would be the same result as providing none of them. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1 new file mode 100644 index 0000000000..dab5269c04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1 @@ -0,0 +1,61 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-PLATFORM" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-platform\fR \- Displays platform compatibility information +. +.SH "SYNOPSIS" +\fBbundle platform\fR [\-\-ruby] +. +.SH "DESCRIPTION" +\fBplatform\fR will display information from your Gemfile, Gemfile\.lock, and Ruby VM about your platform\. +. +.P +For instance, using this Gemfile(5): +. +.IP "" 4 +. +.nf + +source "https://rubygems\.org" + +ruby "1\.9\.3" + +gem "rack" +. +.fi +. +.IP "" 0 +. +.P +If you run \fBbundle platform\fR on Ruby 1\.9\.3, it will display the following output: +. +.IP "" 4 +. +.nf + +Your platform is: x86_64\-linux + +Your app has gems that work on these platforms: +* ruby + +Your Gemfile specifies a Ruby version requirement: +* ruby 1\.9\.3 + +Your current platform satisfies the Ruby version requirement\. +. +.fi +. +.IP "" 0 +. +.P +\fBplatform\fR will list all the platforms in your \fBGemfile\.lock\fR as well as the \fBruby\fR directive if applicable from your Gemfile(5)\. It will also let you know if the \fBruby\fR directive requirement has been met\. If \fBruby\fR directive doesn\'t match the running Ruby VM, it will tell you what part does not\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-ruby\fR +It will display the ruby directive information, so you don\'t have to parse it from the Gemfile(5)\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1.ronn new file mode 100644 index 0000000000..b5d3283fb6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-platform.1.ronn @@ -0,0 +1,42 @@ +bundle-platform(1) -- Displays platform compatibility information +================================================================= + +## SYNOPSIS + +`bundle platform` [--ruby] + +## DESCRIPTION + +`platform` will display information from your Gemfile, Gemfile.lock, and Ruby +VM about your platform. + +For instance, using this Gemfile(5): + + source "https://rubygems.org" + + ruby "1.9.3" + + gem "rack" + +If you run `bundle platform` on Ruby 1.9.3, it will display the following output: + + Your platform is: x86_64-linux + + Your app has gems that work on these platforms: + * ruby + + Your Gemfile specifies a Ruby version requirement: + * ruby 1.9.3 + + Your current platform satisfies the Ruby version requirement. + +`platform` will list all the platforms in your `Gemfile.lock` as well as the +`ruby` directive if applicable from your Gemfile(5). It will also let you know +if the `ruby` directive requirement has been met. If `ruby` directive doesn't +match the running Ruby VM, it will tell you what part does not. + +## OPTIONS + +* `--ruby`: + It will display the ruby directive information, so you don't have to + parse it from the Gemfile(5). diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1 new file mode 100644 index 0000000000..e5b0dab334 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1 @@ -0,0 +1,34 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-PRISTINE" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition +. +.SH "SYNOPSIS" +\fBbundle pristine\fR +. +.SH "DESCRIPTION" +\fBpristine\fR restores the installed gems in the bundle to their pristine condition using the local gem cache from RubyGems\. For git gems, a forced checkout will be performed\. +. +.P +For further explanation, \fBbundle pristine\fR ignores unpacked files on disk\. In other words, this command utilizes the local \fB\.gem\fR cache or the gem\'s git repository as if one were installing from scratch\. +. +.P +Note: the Bundler gem cannot be restored to its original state with \fBpristine\fR\. One also cannot use \fBbundle pristine\fR on gems with a \'path\' option in the Gemfile, because bundler has no original copy it can restore from\. +. +.P +When is it practical to use \fBbundle pristine\fR? +. +.P +It comes in handy when a developer is debugging a gem\. \fBbundle pristine\fR is a great way to get rid of experimental changes to a gem that one may not want\. +. +.P +Why use \fBbundle pristine\fR over \fBgem pristine \-\-all\fR? +. +.P +Both commands are very similar\. For context: \fBbundle pristine\fR, without arguments, cleans all gems from the lockfile\. Meanwhile, \fBgem pristine \-\-all\fR cleans all installed gems for that Ruby version\. +. +.P +If a developer forgets which gems in their project they might have been debugging, the Rubygems \fBgem pristine [GEMNAME]\fR command may be inconvenient\. One can avoid waiting for \fBgem pristine \-\-all\fR, and instead run \fBbundle pristine\fR\. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1.ronn new file mode 100644 index 0000000000..e2d6b6a348 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-pristine.1.ronn @@ -0,0 +1,34 @@ +bundle-pristine(1) -- Restores installed gems to their pristine condition +=========================================================================== + +## SYNOPSIS + +`bundle pristine` + +## DESCRIPTION + +`pristine` restores the installed gems in the bundle to their pristine condition +using the local gem cache from RubyGems. For git gems, a forced checkout will be performed. + +For further explanation, `bundle pristine` ignores unpacked files on disk. In other +words, this command utilizes the local `.gem` cache or the gem's git repository +as if one were installing from scratch. + +Note: the Bundler gem cannot be restored to its original state with `pristine`. +One also cannot use `bundle pristine` on gems with a 'path' option in the Gemfile, +because bundler has no original copy it can restore from. + +When is it practical to use `bundle pristine`? + +It comes in handy when a developer is debugging a gem. `bundle pristine` is a +great way to get rid of experimental changes to a gem that one may not want. + +Why use `bundle pristine` over `gem pristine --all`? + +Both commands are very similar. +For context: `bundle pristine`, without arguments, cleans all gems from the lockfile. +Meanwhile, `gem pristine --all` cleans all installed gems for that Ruby version. + +If a developer forgets which gems in their project they might +have been debugging, the Rubygems `gem pristine [GEMNAME]` command may be inconvenient. +One can avoid waiting for `gem pristine --all`, and instead run `bundle pristine`. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1 new file mode 100644 index 0000000000..8af691283d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1 @@ -0,0 +1,31 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-REMOVE" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-remove\fR \- Removes gems from the Gemfile +. +.SH "SYNOPSIS" +\fBbundle remove [GEM [GEM \.\.\.]] [\-\-install]\fR +. +.SH "DESCRIPTION" +Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-install\fR +Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\. +. +.P +Example: +. +.P +bundle remove rails +. +.P +bundle remove rails rack +. +.P +bundle remove rails rack \-\-install diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1.ronn new file mode 100644 index 0000000000..40a239b4a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-remove.1.ronn @@ -0,0 +1,23 @@ +bundle-remove(1) -- Removes gems from the Gemfile +=========================================================================== + +## SYNOPSIS + +`bundle remove [GEM [GEM ...]] [--install]` + +## DESCRIPTION + +Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised. + +## OPTIONS + +* `--install`: + Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s). + +Example: + +bundle remove rails + +bundle remove rails rack + +bundle remove rails rack --install diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1 new file mode 100644 index 0000000000..c08c7205aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1 @@ -0,0 +1,23 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-SHOW" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem +. +.SH "SYNOPSIS" +\fBbundle show\fR [GEM] [\-\-paths] +. +.SH "DESCRIPTION" +Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\. +. +.P +Calling show with [GEM] will list the exact location of that gem on your machine\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-paths\fR +List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1.ronn new file mode 100644 index 0000000000..a6a59a1445 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-show.1.ronn @@ -0,0 +1,21 @@ +bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem +========================================================================= + +## SYNOPSIS + +`bundle show` [GEM] + [--paths] + +## DESCRIPTION + +Without the [GEM] option, `show` will print a list of the names and versions of +all gems that are required by your [`Gemfile(5)`][Gemfile(5)], sorted by name. + +Calling show with [GEM] will list the exact location of that gem on your +machine. + +## OPTIONS + +* `--paths`: + List the paths of all gems that are required by your [`Gemfile(5)`][Gemfile(5)], + sorted by gem name. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1 new file mode 100644 index 0000000000..7da2ce3438 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1 @@ -0,0 +1,394 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-UPDATE" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-update\fR \- Update your gems to the latest available versions +. +.SH "SYNOPSIS" +\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-jobs=JOBS] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-redownload] [\-\-strict] [\-\-conservative] +. +.SH "DESCRIPTION" +Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\. +. +.P +You would use \fBbundle update\fR to explicitly update the version of a gem\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-all\fR +Update all gems specified in Gemfile\. +. +.TP +\fB\-\-group=\fR, \fB\-g=[]\fR +Only update the gems in the specified group\. For instance, you can update all gems in the development group with \fBbundle update \-\-group development\fR\. You can also call \fBbundle update rails \-\-group test\fR to update the rails gem and all gems in the test group, for example\. +. +.TP +\fB\-\-source=\fR +The name of a \fB:git\fR or \fB:path\fR source used in the Gemfile(5)\. For instance, with a \fB:git\fR source of \fBhttp://github\.com/rails/rails\.git\fR, you would call \fBbundle update \-\-source rails\fR +. +.TP +\fB\-\-local\fR +Do not attempt to fetch gems remotely and use the gem cache instead\. +. +.TP +\fB\-\-ruby\fR +Update the locked version of Ruby to the current version of Ruby\. +. +.TP +\fB\-\-bundler\fR +Update the locked version of bundler to the invoked bundler version\. +. +.TP +\fB\-\-full\-index\fR +Fall back to using the single\-file index of all gems\. +. +.TP +\fB\-\-jobs=[]\fR, \fB\-j[]\fR +Specify the number of jobs to run in parallel\. The default is \fB1\fR\. +. +.TP +\fB\-\-retry=[]\fR +Retry failed network or git requests for \fInumber\fR times\. +. +.TP +\fB\-\-quiet\fR +Only output warnings and errors\. +. +.TP +\fB\-\-redownload\fR +Force downloading every gem\. +. +.TP +\fB\-\-patch\fR +Prefer updating only to next patch version\. +. +.TP +\fB\-\-minor\fR +Prefer updating only to next minor version\. +. +.TP +\fB\-\-major\fR +Prefer updating to next major version (default)\. +. +.TP +\fB\-\-strict\fR +Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\. +. +.TP +\fB\-\-conservative\fR +Use bundle install conservative update behavior and do not allow indirect dependencies to be updated\. +. +.SH "UPDATING ALL GEMS" +If you run \fBbundle update \-\-all\fR, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\. +. +.P +Consider the following Gemfile(5): +. +.IP "" 4 +. +.nf + +source "https://rubygems\.org" + +gem "rails", "3\.0\.0\.rc" +gem "nokogiri" +. +.fi +. +.IP "" 0 +. +.P +When you run bundle install(1) \fIbundle\-install\.1\.html\fR the first time, bundler will resolve all of the dependencies, all the way down, and install what you need: +. +.IP "" 4 +. +.nf + +Fetching gem metadata from https://rubygems\.org/\.\.\.\.\.\.\.\.\. +Resolving dependencies\.\.\. +Installing builder 2\.1\.2 +Installing abstract 1\.0\.0 +Installing rack 1\.2\.8 +Using bundler 1\.7\.6 +Installing rake 10\.4\.0 +Installing polyglot 0\.3\.5 +Installing mime\-types 1\.25\.1 +Installing i18n 0\.4\.2 +Installing mini_portile 0\.6\.1 +Installing tzinfo 0\.3\.42 +Installing rack\-mount 0\.6\.14 +Installing rack\-test 0\.5\.7 +Installing treetop 1\.4\.15 +Installing thor 0\.14\.6 +Installing activesupport 3\.0\.0\.rc +Installing erubis 2\.6\.6 +Installing activemodel 3\.0\.0\.rc +Installing arel 0\.4\.0 +Installing mail 2\.2\.20 +Installing activeresource 3\.0\.0\.rc +Installing actionpack 3\.0\.0\.rc +Installing activerecord 3\.0\.0\.rc +Installing actionmailer 3\.0\.0\.rc +Installing railties 3\.0\.0\.rc +Installing rails 3\.0\.0\.rc +Installing nokogiri 1\.6\.5 + +Bundle complete! 2 Gemfile dependencies, 26 gems total\. +Use `bundle show [gemname]` to see where a bundled gem is installed\. +. +.fi +. +.IP "" 0 +. +.P +As you can see, even though you have two gems in the Gemfile(5), your application needs 26 different gems in order to run\. Bundler remembers the exact versions it installed in \fBGemfile\.lock\fR\. The next time you run bundle install(1) \fIbundle\-install\.1\.html\fR, bundler skips the dependency resolution and installs the same gems as it installed last time\. +. +.P +After checking in the \fBGemfile\.lock\fR into version control and cloning it on another machine, running bundle install(1) \fIbundle\-install\.1\.html\fR will \fIstill\fR install the gems that you installed last time\. You don\'t need to worry that a new release of \fBerubis\fR or \fBmail\fR changes the gems you use\. +. +.P +However, from time to time, you might want to update the gems you are using to the newest versions that still match the gems in your Gemfile(5)\. +. +.P +To do this, run \fBbundle update \-\-all\fR, which will ignore the \fBGemfile\.lock\fR, and resolve all the dependencies again\. Keep in mind that this process can result in a significantly different set of the 25 gems, based on the requirements of new gems that the gem authors released since the last time you ran \fBbundle update \-\-all\fR\. +. +.SH "UPDATING A LIST OF GEMS" +Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\. +. +.P +For instance, in the scenario above, imagine that \fBnokogiri\fR releases version \fB1\.4\.4\fR, and you want to update it \fIwithout\fR updating Rails and all of its dependencies\. To do this, run \fBbundle update nokogiri\fR\. +. +.P +Bundler will update \fBnokogiri\fR and any of its dependencies, but leave alone Rails and its dependencies\. +. +.SH "OVERLAPPING DEPENDENCIES" +Sometimes, multiple gems declared in your Gemfile(5) are satisfied by the same second\-level dependency\. For instance, consider the case of \fBthin\fR and \fBrack\-perftools\-profiler\fR\. +. +.IP "" 4 +. +.nf + +source "https://rubygems\.org" + +gem "thin" +gem "rack\-perftools\-profiler" +. +.fi +. +.IP "" 0 +. +.P +The \fBthin\fR gem depends on \fBrack >= 1\.0\fR, while \fBrack\-perftools\-profiler\fR depends on \fBrack ~> 1\.0\fR\. If you run bundle install, you get: +. +.IP "" 4 +. +.nf + +Fetching source index for https://rubygems\.org/ +Installing daemons (1\.1\.0) +Installing eventmachine (0\.12\.10) with native extensions +Installing open4 (1\.0\.1) +Installing perftools\.rb (0\.4\.7) with native extensions +Installing rack (1\.2\.1) +Installing rack\-perftools_profiler (0\.0\.2) +Installing thin (1\.2\.7) with native extensions +Using bundler (1\.0\.0\.rc\.3) +. +.fi +. +.IP "" 0 +. +.P +In this case, the two gems have their own set of dependencies, but they share \fBrack\fR in common\. If you run \fBbundle update thin\fR, bundler will update \fBdaemons\fR, \fBeventmachine\fR and \fBrack\fR, which are dependencies of \fBthin\fR, but not \fBopen4\fR or \fBperftools\.rb\fR, which are dependencies of \fBrack\-perftools_profiler\fR\. Note that \fBbundle update thin\fR will update \fBrack\fR even though it\'s \fIalso\fR a dependency of \fBrack\-perftools_profiler\fR\. +. +.P +In short, by default, when you update a gem using \fBbundle update\fR, bundler will update all dependencies of that gem, including those that are also dependencies of another gem\. +. +.P +To prevent updating indirect dependencies, prior to version 1\.14 the only option was the \fBCONSERVATIVE UPDATING\fR behavior in bundle install(1) \fIbundle\-install\.1\.html\fR: +. +.P +In this scenario, updating the \fBthin\fR version manually in the Gemfile(5), and then running bundle install(1) \fIbundle\-install\.1\.html\fR will only update \fBdaemons\fR and \fBeventmachine\fR, but not \fBrack\fR\. For more information, see the \fBCONSERVATIVE UPDATING\fR section of bundle install(1) \fIbundle\-install\.1\.html\fR\. +. +.P +Starting with 1\.14, specifying the \fB\-\-conservative\fR option will also prevent indirect dependencies from being updated\. +. +.SH "PATCH LEVEL OPTIONS" +Version 1\.14 introduced 4 patch\-level options that will influence how gem versions are resolved\. One of the following options can be used: \fB\-\-patch\fR, \fB\-\-minor\fR or \fB\-\-major\fR\. \fB\-\-strict\fR can be added to further influence resolution\. +. +.TP +\fB\-\-patch\fR +Prefer updating only to next patch version\. +. +.TP +\fB\-\-minor\fR +Prefer updating only to next minor version\. +. +.TP +\fB\-\-major\fR +Prefer updating to next major version (default)\. +. +.TP +\fB\-\-strict\fR +Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\. +. +.P +When Bundler is resolving what versions to use to satisfy declared requirements in the Gemfile or in parent gems, it looks up all available versions, filters out any versions that don\'t satisfy the requirement, and then, by default, sorts them from newest to oldest, considering them in that order\. +. +.P +Providing one of the patch level options (e\.g\. \fB\-\-patch\fR) changes the sort order of the satisfying versions, causing Bundler to consider the latest \fB\-\-patch\fR or \fB\-\-minor\fR version available before other versions\. Note that versions outside the stated patch level could still be resolved to if necessary to find a suitable dependency graph\. +. +.P +For example, if gem \'foo\' is locked at 1\.0\.2, with no gem requirement defined in the Gemfile, and versions 1\.0\.3, 1\.0\.4, 1\.1\.0, 1\.1\.1, 2\.0\.0 all exist, the default order of preference by default (\fB\-\-major\fR) will be "2\.0\.0, 1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\. +. +.P +If the \fB\-\-patch\fR option is used, the order of preference will change to "1\.0\.4, 1\.0\.3, 1\.0\.2, 1\.1\.1, 1\.1\.0, 2\.0\.0"\. +. +.P +If the \fB\-\-minor\fR option is used, the order of preference will change to "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2, 2\.0\.0"\. +. +.P +Combining the \fB\-\-strict\fR option with any of the patch level options will remove any versions beyond the scope of the patch level option, to ensure that no gem is updated that far\. +. +.P +To continue the previous example, if both \fB\-\-patch\fR and \fB\-\-strict\fR options are used, the available versions for resolution would be "1\.0\.4, 1\.0\.3, 1\.0\.2"\. If \fB\-\-minor\fR and \fB\-\-strict\fR are used, it would be "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\. +. +.P +Gem requirements as defined in the Gemfile will still be the first determining factor for what versions are available\. If the gem requirement for \fBfoo\fR in the Gemfile is \'~> 1\.0\', that will accomplish the same thing as providing the \fB\-\-minor\fR and \fB\-\-strict\fR options\. +. +.SH "PATCH LEVEL EXAMPLES" +Given the following gem specifications: +. +.IP "" 4 +. +.nf + +foo 1\.4\.3, requires: ~> bar 2\.0 +foo 1\.4\.4, requires: ~> bar 2\.0 +foo 1\.4\.5, requires: ~> bar 2\.1 +foo 1\.5\.0, requires: ~> bar 2\.1 +foo 1\.5\.1, requires: ~> bar 3\.0 +bar with versions 2\.0\.3, 2\.0\.4, 2\.1\.0, 2\.1\.1, 3\.0\.0 +. +.fi +. +.IP "" 0 +. +.P +Gemfile: +. +.IP "" 4 +. +.nf + +gem \'foo\' +. +.fi +. +.IP "" 0 +. +.P +Gemfile\.lock: +. +.IP "" 4 +. +.nf + +foo (1\.4\.3) + bar (~> 2\.0) +bar (2\.0\.3) +. +.fi +. +.IP "" 0 +. +.P +Cases: +. +.IP "" 4 +. +.nf + +# Command Line Result +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- +1 bundle update \-\-patch \'foo 1\.4\.5\', \'bar 2\.1\.1\' +2 bundle update \-\-patch foo \'foo 1\.4\.5\', \'bar 2\.1\.1\' +3 bundle update \-\-minor \'foo 1\.5\.1\', \'bar 3\.0\.0\' +4 bundle update \-\-minor \-\-strict \'foo 1\.5\.0\', \'bar 2\.1\.1\' +5 bundle update \-\-patch \-\-strict \'foo 1\.4\.4\', \'bar 2\.0\.4\' +. +.fi +. +.IP "" 0 +. +.P +In case 1, bar is upgraded to 2\.1\.1, a minor version increase, because the dependency from foo 1\.4\.5 required it\. +. +.P +In case 2, only foo is requested to be unlocked, but bar is also allowed to move because it\'s not a declared dependency in the Gemfile\. +. +.P +In case 3, bar goes up a whole major release, because a minor increase is preferred now for foo, and when it goes to 1\.5\.1, it requires 3\.0\.0 of bar\. +. +.P +In case 4, foo is preferred up to a minor version, but 1\.5\.1 won\'t work because the \-\-strict flag removes bar 3\.0\.0 from consideration since it\'s a major increment\. +. +.P +In case 5, both foo and bar have any minor or major increments removed from consideration because of the \-\-strict flag, so the most they can move is up to 1\.4\.4 and 2\.0\.4\. +. +.SH "RECOMMENDED WORKFLOW" +In general, when working with an application managed with bundler, you should use the following workflow: +. +.IP "\(bu" 4 +After you create your Gemfile(5) for the first time, run +. +.IP +$ bundle install +. +.IP "\(bu" 4 +Check the resulting \fBGemfile\.lock\fR into version control +. +.IP +$ git add Gemfile\.lock +. +.IP "\(bu" 4 +When checking out this repository on another development machine, run +. +.IP +$ bundle install +. +.IP "\(bu" 4 +When checking out this repository on a deployment machine, run +. +.IP +$ bundle install \-\-deployment +. +.IP "\(bu" 4 +After changing the Gemfile(5) to reflect a new or update dependency, run +. +.IP +$ bundle install +. +.IP "\(bu" 4 +Make sure to check the updated \fBGemfile\.lock\fR into version control +. +.IP +$ git add Gemfile\.lock +. +.IP "\(bu" 4 +If bundle install(1) \fIbundle\-install\.1\.html\fR reports a conflict, manually update the specific gems that you changed in the Gemfile(5) +. +.IP +$ bundle update rails thin +. +.IP "\(bu" 4 +If you want to update all the gems to the latest possible versions that still match the gems listed in the Gemfile(5), run +. +.IP +$ bundle update \-\-all +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1.ronn new file mode 100644 index 0000000000..3a16f29149 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-update.1.ronn @@ -0,0 +1,350 @@ +bundle-update(1) -- Update your gems to the latest available versions +===================================================================== + +## SYNOPSIS + +`bundle update` <*gems> [--all] + [--group=NAME] + [--source=NAME] + [--local] + [--ruby] + [--bundler[=VERSION]] + [--full-index] + [--jobs=JOBS] + [--quiet] + [--patch|--minor|--major] + [--redownload] + [--strict] + [--conservative] + +## DESCRIPTION + +Update the gems specified (all gems, if `--all` flag is used), ignoring +the previously installed gems specified in the `Gemfile.lock`. In +general, you should use [bundle install(1)](bundle-install.1.html) to install the same exact +gems and versions across machines. + +You would use `bundle update` to explicitly update the version of a +gem. + +## OPTIONS + +* `--all`: + Update all gems specified in Gemfile. + +* `--group=`, `-g=[]`: + Only update the gems in the specified group. For instance, you can update all gems + in the development group with `bundle update --group development`. You can also + call `bundle update rails --group test` to update the rails gem and all gems in + the test group, for example. + +* `--source=`: + The name of a `:git` or `:path` source used in the Gemfile(5). For + instance, with a `:git` source of `http://github.com/rails/rails.git`, + you would call `bundle update --source rails` + +* `--local`: + Do not attempt to fetch gems remotely and use the gem cache instead. + +* `--ruby`: + Update the locked version of Ruby to the current version of Ruby. + +* `--bundler`: + Update the locked version of bundler to the invoked bundler version. + +* `--full-index`: + Fall back to using the single-file index of all gems. + +* `--jobs=[]`, `-j[]`: + Specify the number of jobs to run in parallel. The default is `1`. + +* `--retry=[]`: + Retry failed network or git requests for times. + +* `--quiet`: + Only output warnings and errors. + +* `--redownload`: + Force downloading every gem. + +* `--patch`: + Prefer updating only to next patch version. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--strict`: + Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. + +* `--conservative`: + Use bundle install conservative update behavior and do not allow indirect dependencies to be updated. + +## UPDATING ALL GEMS + +If you run `bundle update --all`, bundler will ignore +any previously installed gems and resolve all dependencies again +based on the latest versions of all gems available in the sources. + +Consider the following Gemfile(5): + + source "https://rubygems.org" + + gem "rails", "3.0.0.rc" + gem "nokogiri" + +When you run [bundle install(1)](bundle-install.1.html) the first time, bundler will resolve +all of the dependencies, all the way down, and install what you need: + + Fetching gem metadata from https://rubygems.org/......... + Resolving dependencies... + Installing builder 2.1.2 + Installing abstract 1.0.0 + Installing rack 1.2.8 + Using bundler 1.7.6 + Installing rake 10.4.0 + Installing polyglot 0.3.5 + Installing mime-types 1.25.1 + Installing i18n 0.4.2 + Installing mini_portile 0.6.1 + Installing tzinfo 0.3.42 + Installing rack-mount 0.6.14 + Installing rack-test 0.5.7 + Installing treetop 1.4.15 + Installing thor 0.14.6 + Installing activesupport 3.0.0.rc + Installing erubis 2.6.6 + Installing activemodel 3.0.0.rc + Installing arel 0.4.0 + Installing mail 2.2.20 + Installing activeresource 3.0.0.rc + Installing actionpack 3.0.0.rc + Installing activerecord 3.0.0.rc + Installing actionmailer 3.0.0.rc + Installing railties 3.0.0.rc + Installing rails 3.0.0.rc + Installing nokogiri 1.6.5 + + Bundle complete! 2 Gemfile dependencies, 26 gems total. + Use `bundle show [gemname]` to see where a bundled gem is installed. + +As you can see, even though you have two gems in the Gemfile(5), your application +needs 26 different gems in order to run. Bundler remembers the exact versions +it installed in `Gemfile.lock`. The next time you run [bundle install(1)](bundle-install.1.html), bundler skips +the dependency resolution and installs the same gems as it installed last time. + +After checking in the `Gemfile.lock` into version control and cloning it on another +machine, running [bundle install(1)](bundle-install.1.html) will _still_ install the gems that you installed +last time. You don't need to worry that a new release of `erubis` or `mail` changes +the gems you use. + +However, from time to time, you might want to update the gems you are using to the +newest versions that still match the gems in your Gemfile(5). + +To do this, run `bundle update --all`, which will ignore the `Gemfile.lock`, and resolve +all the dependencies again. Keep in mind that this process can result in a significantly +different set of the 25 gems, based on the requirements of new gems that the gem +authors released since the last time you ran `bundle update --all`. + +## UPDATING A LIST OF GEMS + +Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the +gems that you specified locked to the versions in the `Gemfile.lock`. + +For instance, in the scenario above, imagine that `nokogiri` releases version `1.4.4`, and +you want to update it _without_ updating Rails and all of its dependencies. To do this, +run `bundle update nokogiri`. + +Bundler will update `nokogiri` and any of its dependencies, but leave alone Rails and +its dependencies. + +## OVERLAPPING DEPENDENCIES + +Sometimes, multiple gems declared in your Gemfile(5) are satisfied by the same +second-level dependency. For instance, consider the case of `thin` and +`rack-perftools-profiler`. + + source "https://rubygems.org" + + gem "thin" + gem "rack-perftools-profiler" + +The `thin` gem depends on `rack >= 1.0`, while `rack-perftools-profiler` depends +on `rack ~> 1.0`. If you run bundle install, you get: + + Fetching source index for https://rubygems.org/ + Installing daemons (1.1.0) + Installing eventmachine (0.12.10) with native extensions + Installing open4 (1.0.1) + Installing perftools.rb (0.4.7) with native extensions + Installing rack (1.2.1) + Installing rack-perftools_profiler (0.0.2) + Installing thin (1.2.7) with native extensions + Using bundler (1.0.0.rc.3) + +In this case, the two gems have their own set of dependencies, but they share +`rack` in common. If you run `bundle update thin`, bundler will update `daemons`, +`eventmachine` and `rack`, which are dependencies of `thin`, but not `open4` or +`perftools.rb`, which are dependencies of `rack-perftools_profiler`. Note that +`bundle update thin` will update `rack` even though it's _also_ a dependency of +`rack-perftools_profiler`. + +In short, by default, when you update a gem using `bundle update`, bundler will +update all dependencies of that gem, including those that are also dependencies +of another gem. + +To prevent updating indirect dependencies, prior to version 1.14 the only option +was the `CONSERVATIVE UPDATING` behavior in [bundle install(1)](bundle-install.1.html): + +In this scenario, updating the `thin` version manually in the Gemfile(5), +and then running [bundle install(1)](bundle-install.1.html) will only update `daemons` and `eventmachine`, +but not `rack`. For more information, see the `CONSERVATIVE UPDATING` section +of [bundle install(1)](bundle-install.1.html). + +Starting with 1.14, specifying the `--conservative` option will also prevent indirect +dependencies from being updated. + +## PATCH LEVEL OPTIONS + +Version 1.14 introduced 4 patch-level options that will influence how gem +versions are resolved. One of the following options can be used: `--patch`, +`--minor` or `--major`. `--strict` can be added to further influence resolution. + +* `--patch`: + Prefer updating only to next patch version. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--strict`: + Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. + +When Bundler is resolving what versions to use to satisfy declared +requirements in the Gemfile or in parent gems, it looks up all +available versions, filters out any versions that don't satisfy +the requirement, and then, by default, sorts them from newest to +oldest, considering them in that order. + +Providing one of the patch level options (e.g. `--patch`) changes the +sort order of the satisfying versions, causing Bundler to consider the +latest `--patch` or `--minor` version available before other versions. +Note that versions outside the stated patch level could still be +resolved to if necessary to find a suitable dependency graph. + +For example, if gem 'foo' is locked at 1.0.2, with no gem requirement +defined in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0 +all exist, the default order of preference by default (`--major`) will +be "2.0.0, 1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2". + +If the `--patch` option is used, the order of preference will change to +"1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0, 2.0.0". + +If the `--minor` option is used, the order of preference will change to +"1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2, 2.0.0". + +Combining the `--strict` option with any of the patch level options +will remove any versions beyond the scope of the patch level option, +to ensure that no gem is updated that far. + +To continue the previous example, if both `--patch` and `--strict` +options are used, the available versions for resolution would be +"1.0.4, 1.0.3, 1.0.2". If `--minor` and `--strict` are used, it would +be "1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2". + +Gem requirements as defined in the Gemfile will still be the first +determining factor for what versions are available. If the gem +requirement for `foo` in the Gemfile is '~> 1.0', that will accomplish +the same thing as providing the `--minor` and `--strict` options. + +## PATCH LEVEL EXAMPLES + +Given the following gem specifications: + + foo 1.4.3, requires: ~> bar 2.0 + foo 1.4.4, requires: ~> bar 2.0 + foo 1.4.5, requires: ~> bar 2.1 + foo 1.5.0, requires: ~> bar 2.1 + foo 1.5.1, requires: ~> bar 3.0 + bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0 + +Gemfile: + + gem 'foo' + +Gemfile.lock: + + foo (1.4.3) + bar (~> 2.0) + bar (2.0.3) + +Cases: + + # Command Line Result + ------------------------------------------------------------ + 1 bundle update --patch 'foo 1.4.5', 'bar 2.1.1' + 2 bundle update --patch foo 'foo 1.4.5', 'bar 2.1.1' + 3 bundle update --minor 'foo 1.5.1', 'bar 3.0.0' + 4 bundle update --minor --strict 'foo 1.5.0', 'bar 2.1.1' + 5 bundle update --patch --strict 'foo 1.4.4', 'bar 2.0.4' + +In case 1, bar is upgraded to 2.1.1, a minor version increase, because +the dependency from foo 1.4.5 required it. + +In case 2, only foo is requested to be unlocked, but bar is also +allowed to move because it's not a declared dependency in the Gemfile. + +In case 3, bar goes up a whole major release, because a minor increase +is preferred now for foo, and when it goes to 1.5.1, it requires 3.0.0 +of bar. + +In case 4, foo is preferred up to a minor version, but 1.5.1 won't work +because the --strict flag removes bar 3.0.0 from consideration since +it's a major increment. + +In case 5, both foo and bar have any minor or major increments removed +from consideration because of the --strict flag, so the most they can +move is up to 1.4.4 and 2.0.4. + +## RECOMMENDED WORKFLOW + +In general, when working with an application managed with bundler, you should +use the following workflow: + +* After you create your Gemfile(5) for the first time, run + + $ bundle install + +* Check the resulting `Gemfile.lock` into version control + + $ git add Gemfile.lock + +* When checking out this repository on another development machine, run + + $ bundle install + +* When checking out this repository on a deployment machine, run + + $ bundle install --deployment + +* After changing the Gemfile(5) to reflect a new or update dependency, run + + $ bundle install + +* Make sure to check the updated `Gemfile.lock` into version control + + $ git add Gemfile.lock + +* If [bundle install(1)](bundle-install.1.html) reports a conflict, manually update the specific + gems that you changed in the Gemfile(5) + + $ bundle update rails thin + +* If you want to update all the gems to the latest possible versions that + still match the gems listed in the Gemfile(5), run + + $ bundle update --all diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1 new file mode 100644 index 0000000000..9683f0678b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1 @@ -0,0 +1,39 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE\-VIZ" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile +. +.SH "SYNOPSIS" +\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP] +. +.SH "DESCRIPTION" +\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\. +. +.P +The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-file\fR, \fB\-f\fR +The name to use for the generated file\. See \fB\-\-format\fR option +. +.TP +\fB\-\-format\fR, \fB\-F\fR +This is output format option\. Supported format is png, jpg, svg, dot \.\.\. +. +.TP +\fB\-\-requirements\fR, \fB\-R\fR +Set to show the version of each required dependency\. +. +.TP +\fB\-\-version\fR, \fB\-v\fR +Set to show each gem version\. +. +.TP +\fB\-\-without\fR, \fB\-W\fR +Exclude gems that are part of the specified named group\. + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1.ronn new file mode 100644 index 0000000000..701df5415e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle-viz.1.ronn @@ -0,0 +1,30 @@ +bundle-viz(1) -- Generates a visual dependency graph for your Gemfile +===================================================================== + +## SYNOPSIS + +`bundle viz` [--file=FILE] + [--format=FORMAT] + [--requirements] + [--version] + [--without=GROUP GROUP] + +## DESCRIPTION + +`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph. +`viz` requires the ruby-graphviz gem (and its dependencies). + +The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html). + +## OPTIONS + +* `--file`, `-f`: + The name to use for the generated file. See `--format` option +* `--format`, `-F`: + This is output format option. Supported format is png, jpg, svg, dot ... +* `--requirements`, `-R`: + Set to show the version of each required dependency. +* `--version`, `-v`: + Set to show each gem version. +* `--without`, `-W`: + Exclude gems that are part of the specified named group. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1 new file mode 100644 index 0000000000..b741f7cf6a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1 @@ -0,0 +1,136 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "BUNDLE" "1" "June 2021" "" "" +. +.SH "NAME" +\fBbundle\fR \- Ruby Dependency Management +. +.SH "SYNOPSIS" +\fBbundle\fR COMMAND [\-\-no\-color] [\-\-verbose] [ARGS] +. +.SH "DESCRIPTION" +Bundler manages an \fBapplication\'s dependencies\fR through its entire life across many machines systematically and repeatably\. +. +.P +See the bundler website \fIhttps://bundler\.io\fR for information on getting started, and Gemfile(5) for more information on the \fBGemfile\fR format\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-no\-color\fR +Print all output without color +. +.TP +\fB\-\-retry\fR, \fB\-r\fR +Specify the number of times you wish to attempt network commands +. +.TP +\fB\-\-verbose\fR, \fB\-V\fR +Print out additional logging information +. +.SH "BUNDLE COMMANDS" +We divide \fBbundle\fR subcommands into primary commands and utilities: +. +.SH "PRIMARY COMMANDS" +. +.TP +\fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR +Install the gems specified by the \fBGemfile\fR or \fBGemfile\.lock\fR +. +.TP +\fBbundle update(1)\fR \fIbundle\-update\.1\.html\fR +Update dependencies to their latest versions +. +.TP +\fBbundle package(1)\fR \fIbundle\-package\.1\.html\fR +Package the \.gem files required by your application into the \fBvendor/cache\fR directory +. +.TP +\fBbundle exec(1)\fR \fIbundle\-exec\.1\.html\fR +Execute a script in the current bundle +. +.TP +\fBbundle config(1)\fR \fIbundle\-config\.1\.html\fR +Specify and read configuration options for Bundler +. +.TP +\fBbundle help(1)\fR +Display detailed help for each subcommand +. +.SH "UTILITIES" +. +.TP +\fBbundle add(1)\fR \fIbundle\-add\.1\.html\fR +Add the named gem to the Gemfile and run \fBbundle install\fR +. +.TP +\fBbundle binstubs(1)\fR \fIbundle\-binstubs\.1\.html\fR +Generate binstubs for executables in a gem +. +.TP +\fBbundle check(1)\fR \fIbundle\-check\.1\.html\fR +Determine whether the requirements for your application are installed and available to Bundler +. +.TP +\fBbundle show(1)\fR \fIbundle\-show\.1\.html\fR +Show the source location of a particular gem in the bundle +. +.TP +\fBbundle outdated(1)\fR \fIbundle\-outdated\.1\.html\fR +Show all of the outdated gems in the current bundle +. +.TP +\fBbundle console(1)\fR +Start an IRB session in the current bundle +. +.TP +\fBbundle open(1)\fR \fIbundle\-open\.1\.html\fR +Open an installed gem in the editor +. +.TP +\fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR +Generate a lockfile for your dependencies +. +.TP +\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR +Generate a visual representation of your dependencies +. +.TP +\fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR +Generate a simple \fBGemfile\fR, placed in the current directory +. +.TP +\fBbundle gem(1)\fR \fIbundle\-gem\.1\.html\fR +Create a simple gem, suitable for development with Bundler +. +.TP +\fBbundle platform(1)\fR \fIbundle\-platform\.1\.html\fR +Display platform compatibility information +. +.TP +\fBbundle clean(1)\fR \fIbundle\-clean\.1\.html\fR +Clean up unused gems in your Bundler directory +. +.TP +\fBbundle doctor(1)\fR \fIbundle\-doctor\.1\.html\fR +Display warnings about common problems +. +.TP +\fBbundle remove(1)\fR \fIbundle\-remove\.1\.html\fR +Removes gems from the Gemfile +. +.SH "PLUGINS" +When running a command that isn\'t listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-\fR and execute it, passing down any extra arguments to it\. +. +.SH "OBSOLETE" +These commands are obsolete and should no longer be used: +. +.IP "\(bu" 4 +\fBbundle cache(1)\fR +. +.IP "\(bu" 4 +\fBbundle show(1)\fR +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1.ronn new file mode 100644 index 0000000000..5b1712394a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/bundle.1.ronn @@ -0,0 +1,111 @@ +bundle(1) -- Ruby Dependency Management +======================================= + +## SYNOPSIS + +`bundle` COMMAND [--no-color] [--verbose] [ARGS] + +## DESCRIPTION + +Bundler manages an `application's dependencies` through its entire life +across many machines systematically and repeatably. + +See [the bundler website](https://bundler.io) for information on getting +started, and Gemfile(5) for more information on the `Gemfile` format. + +## OPTIONS + +* `--no-color`: + Print all output without color + +* `--retry`, `-r`: + Specify the number of times you wish to attempt network commands + +* `--verbose`, `-V`: + Print out additional logging information + +## BUNDLE COMMANDS + +We divide `bundle` subcommands into primary commands and utilities: + +## PRIMARY COMMANDS + +* [`bundle install(1)`](bundle-install.1.html): + Install the gems specified by the `Gemfile` or `Gemfile.lock` + +* [`bundle update(1)`](bundle-update.1.html): + Update dependencies to their latest versions + +* [`bundle package(1)`](bundle-package.1.html): + Package the .gem files required by your application into the + `vendor/cache` directory + +* [`bundle exec(1)`](bundle-exec.1.html): + Execute a script in the current bundle + +* [`bundle config(1)`](bundle-config.1.html): + Specify and read configuration options for Bundler + +* `bundle help(1)`: + Display detailed help for each subcommand + +## UTILITIES + +* [`bundle add(1)`](bundle-add.1.html): + Add the named gem to the Gemfile and run `bundle install` + +* [`bundle binstubs(1)`](bundle-binstubs.1.html): + Generate binstubs for executables in a gem + +* [`bundle check(1)`](bundle-check.1.html): + Determine whether the requirements for your application are installed + and available to Bundler + +* [`bundle show(1)`](bundle-show.1.html): + Show the source location of a particular gem in the bundle + +* [`bundle outdated(1)`](bundle-outdated.1.html): + Show all of the outdated gems in the current bundle + +* `bundle console(1)`: + Start an IRB session in the current bundle + +* [`bundle open(1)`](bundle-open.1.html): + Open an installed gem in the editor + +* [`bundle lock(1)`](bundle-lock.1.html): + Generate a lockfile for your dependencies + +* [`bundle viz(1)`](bundle-viz.1.html): + Generate a visual representation of your dependencies + +* [`bundle init(1)`](bundle-init.1.html): + Generate a simple `Gemfile`, placed in the current directory + +* [`bundle gem(1)`](bundle-gem.1.html): + Create a simple gem, suitable for development with Bundler + +* [`bundle platform(1)`](bundle-platform.1.html): + Display platform compatibility information + +* [`bundle clean(1)`](bundle-clean.1.html): + Clean up unused gems in your Bundler directory + +* [`bundle doctor(1)`](bundle-doctor.1.html): + Display warnings about common problems + +* [`bundle remove(1)`](bundle-remove.1.html): + Removes gems from the Gemfile + +## PLUGINS + +When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, +Bundler will try to find an executable on your path named `bundler-` +and execute it, passing down any extra arguments to it. + +## OBSOLETE + +These commands are obsolete and should no longer be used: + +* `bundle cache(1)` +* `bundle show(1)` diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5 b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5 new file mode 100644 index 0000000000..af714c1718 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5 @@ -0,0 +1,686 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "GEMFILE" "5" "June 2021" "" "" +. +.SH "NAME" +\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs +. +.SH "SYNOPSIS" +A \fBGemfile\fR describes the gem dependencies required to execute associated Ruby code\. +. +.P +Place the \fBGemfile\fR in the root of the directory containing the associated code\. For instance, in a Rails application, place the \fBGemfile\fR in the same directory as the \fBRakefile\fR\. +. +.SH "SYNTAX" +A \fBGemfile\fR is evaluated as Ruby code, in a context which makes available a number of methods used to describe the gem requirements\. +. +.SH "GLOBAL SOURCES" +At the top of the \fBGemfile\fR, add a line for the \fBRubygems\fR source that contains the gems listed in the \fBGemfile\fR\. +. +.IP "" 4 +. +.nf + +source "https://rubygems\.org" +. +.fi +. +.IP "" 0 +. +.P +It is possible, but not recommended as of Bundler 1\.7, to add multiple global \fBsource\fR lines\. Each of these \fBsource\fRs \fBMUST\fR be a valid Rubygems repository\. +. +.P +Sources are checked for gems following the heuristics described in \fISOURCE PRIORITY\fR\. If a gem is found in more than one global source, Bundler will print a warning after installing the gem indicating which source was used, and listing the other sources where the gem is available\. A specific source can be selected for gems that need to use a non\-standard repository, suppressing this warning, by using the \fI\fB:source\fR option\fR or a \fI\fBsource\fR block\fR\. +. +.SS "CREDENTIALS" +Some gem sources require a username and password\. Use bundle config(1) \fIbundle\-config\.1\.html\fR to set the username and password for any of the sources that need it\. The command must be run once on each computer that will install the Gemfile, but this keeps the credentials from being stored in plain text in version control\. +. +.IP "" 4 +. +.nf + +bundle config gems\.example\.com user:password +. +.fi +. +.IP "" 0 +. +.P +For some sources, like a company Gemfury account, it may be easier to include the credentials in the Gemfile as part of the source URL\. +. +.IP "" 4 +. +.nf + +source "https://user:password@gems\.example\.com" +. +.fi +. +.IP "" 0 +. +.P +Credentials in the source URL will take precedence over credentials set using \fBconfig\fR\. +. +.SH "RUBY" +If your application requires a specific Ruby version or engine, specify your requirements using the \fBruby\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\. +. +.SS "VERSION (required)" +The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this should be the Ruby version that the engine is compatible with\. +. +.IP "" 4 +. +.nf + +ruby "1\.9\.3" +. +.fi +. +.IP "" 0 +. +.SS "ENGINE" +Each application \fImay\fR specify a Ruby engine\. If an engine is specified, an engine version \fImust\fR also be specified\. +. +.P +What exactly is an Engine? \- A Ruby engine is an implementation of the Ruby language\. +. +.IP "\(bu" 4 +For background: the reference or original implementation of the Ruby programming language is called Matz\'s Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\. +. +.IP "\(bu" 4 +Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include Rubinius \fIhttps://rubinius\.com/\fR, and JRuby \fIhttp://jruby\.org/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. +. +.IP "" 0 +. +.SS "ENGINE VERSION" +Each application \fImay\fR specify a Ruby engine version\. If an engine version is specified, an engine \fImust\fR also be specified\. If the engine is "ruby" the engine version specified \fImust\fR match the Ruby version\. +. +.IP "" 4 +. +.nf + +ruby "1\.8\.7", :engine => "jruby", :engine_version => "1\.6\.7" +. +.fi +. +.IP "" 0 +. +.SS "PATCHLEVEL" +Each application \fImay\fR specify a Ruby patchlevel\. +. +.IP "" 4 +. +.nf + +ruby "2\.0\.0", :patchlevel => "247" +. +.fi +. +.IP "" 0 +. +.SH "GEMS" +Specify gem requirements using the \fBgem\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\. +. +.SS "NAME (required)" +For each gem requirement, list a single \fIgem\fR line\. +. +.IP "" 4 +. +.nf + +gem "nokogiri" +. +.fi +. +.IP "" 0 +. +.SS "VERSION" +Each \fIgem\fR \fBMAY\fR have one or more version specifiers\. +. +.IP "" 4 +. +.nf + +gem "nokogiri", ">= 1\.4\.2" +gem "RedCloth", ">= 4\.1\.0", "< 4\.2\.0" +. +.fi +. +.IP "" 0 +. +.SS "REQUIRE AS" +Each \fIgem\fR \fBMAY\fR specify files that should be used when autorequiring via \fBBundler\.require\fR\. You may pass an array with multiple files or \fBtrue\fR if the file you want \fBrequired\fR has the same name as \fIgem\fR or \fBfalse\fR to prevent any file from being autorequired\. +. +.IP "" 4 +. +.nf + +gem "redis", :require => ["redis/connection/hiredis", "redis"] +gem "webmock", :require => false +gem "byebug", :require => true +. +.fi +. +.IP "" 0 +. +.P +The argument defaults to the name of the gem\. For example, these are identical: +. +.IP "" 4 +. +.nf + +gem "nokogiri" +gem "nokogiri", :require => "nokogiri" +gem "nokogiri", :require => true +. +.fi +. +.IP "" 0 +. +.SS "GROUPS" +Each \fIgem\fR \fBMAY\fR specify membership in one or more groups\. Any \fIgem\fR that does not specify membership in any group is placed in the \fBdefault\fR group\. +. +.IP "" 4 +. +.nf + +gem "rspec", :group => :test +gem "wirble", :groups => [:development, :test] +. +.fi +. +.IP "" 0 +. +.P +The Bundler runtime allows its two main methods, \fBBundler\.setup\fR and \fBBundler\.require\fR, to limit their impact to particular groups\. +. +.IP "" 4 +. +.nf + +# setup adds gems to Ruby\'s load path +Bundler\.setup # defaults to all groups +require "bundler/setup" # same as Bundler\.setup +Bundler\.setup(:default) # only set up the _default_ group +Bundler\.setup(:test) # only set up the _test_ group (but `not` _default_) +Bundler\.setup(:default, :test) # set up the _default_ and _test_ groups, but no others + +# require requires all of the gems in the specified groups +Bundler\.require # defaults to the _default_ group +Bundler\.require(:default) # identical +Bundler\.require(:default, :test) # requires the _default_ and _test_ groups +Bundler\.require(:test) # requires the _test_ group +. +.fi +. +.IP "" 0 +. +.P +The Bundler CLI allows you to specify a list of groups whose gems \fBbundle install\fR should not install with the \fBwithout\fR configuration\. +. +.P +To specify multiple groups to ignore, specify a list of groups separated by spaces\. +. +.IP "" 4 +. +.nf + +bundle config set \-\-local without test +bundle config set \-\-local without development test +. +.fi +. +.IP "" 0 +. +.P +Also, calling \fBBundler\.setup\fR with no parameters, or calling \fBrequire "bundler/setup"\fR will setup all groups except for the ones you excluded via \fB\-\-without\fR (since they are not available)\. +. +.P +Note that on \fBbundle install\fR, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies\. This means that you cannot list different versions of the same gems in different groups\. For more details, see Understanding Bundler \fIhttps://bundler\.io/rationale\.html\fR\. +. +.SS "PLATFORMS" +If a gem should only be used in a particular platform or set of platforms, you can specify them\. Platforms are essentially identical to groups, except that you do not need to use the \fB\-\-without\fR install\-time flag to exclude groups of gems for other platforms\. +. +.P +There are a number of \fBGemfile\fR platforms: +. +.TP +\fBruby\fR +C Ruby (MRI), Rubinius or TruffleRuby, but \fBNOT\fR Windows +. +.TP +\fBmri\fR +Same as \fIruby\fR, but only C Ruby (MRI) +. +.TP +\fBmingw\fR +Windows 32 bit \'mingw32\' platform (aka RubyInstaller) +. +.TP +\fBx64_mingw\fR +Windows 64 bit \'mingw32\' platform (aka RubyInstaller x64) +. +.TP +\fBrbx\fR +Rubinius +. +.TP +\fBjruby\fR +JRuby +. +.TP +\fBtruffleruby\fR +TruffleRuby +. +.TP +\fBmswin\fR +Windows +. +.P +You can restrict further by platform and version for all platforms \fIexcept\fR for \fBrbx\fR, \fBjruby\fR, \fBtruffleruby\fR and \fBmswin\fR\. +. +.P +To specify a version in addition to a platform, append the version number without the delimiter to the platform\. For example, to specify that a gem should only be used on platforms with Ruby 2\.3, use: +. +.IP "" 4 +. +.nf + +ruby_23 +. +.fi +. +.IP "" 0 +. +.P +The full list of platforms and supported versions includes: +. +.TP +\fBruby\fR +1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5, 2\.6 +. +.TP +\fBmri\fR +1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5, 2\.6 +. +.TP +\fBmingw\fR +1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5, 2\.6 +. +.TP +\fBx64_mingw\fR +2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5, 2\.6 +. +.P +As with groups, you can specify one or more platforms: +. +.IP "" 4 +. +.nf + +gem "weakling", :platforms => :jruby +gem "ruby\-debug", :platforms => :mri_18 +gem "nokogiri", :platforms => [:mri_18, :jruby] +. +.fi +. +.IP "" 0 +. +.P +All operations involving groups (\fBbundle install\fR \fIbundle\-install\.1\.html\fR, \fBBundler\.setup\fR, \fBBundler\.require\fR) behave exactly the same as if any groups not matching the current platform were explicitly excluded\. +. +.SS "SOURCE" +You can select an alternate Rubygems repository for a gem using the \':source\' option\. +. +.IP "" 4 +. +.nf + +gem "some_internal_gem", :source => "https://gems\.example\.com" +. +.fi +. +.IP "" 0 +. +.P +This forces the gem to be loaded from this source and ignores any global sources declared at the top level of the file\. If the gem does not exist in this source, it will not be installed\. +. +.P +Bundler will search for child dependencies of this gem by first looking in the source selected for the parent, but if they are not found there, it will fall back on global sources using the ordering described in \fISOURCE PRIORITY\fR\. +. +.P +Selecting a specific source repository this way also suppresses the ambiguous gem warning described above in \fIGLOBAL SOURCES (#source)\fR\. +. +.P +Using the \fB:source\fR option for an individual gem will also make that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when adding gems with explicit sources, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources\. +. +.SS "GIT" +If necessary, you can specify that a gem is located at a particular git repository using the \fB:git\fR parameter\. The repository can be accessed via several protocols: +. +.TP +\fBHTTP(S)\fR +gem "rails", :git => "https://github\.com/rails/rails\.git" +. +.TP +\fBSSH\fR +gem "rails", :git => "git@github\.com:rails/rails\.git" +. +.TP +\fBgit\fR +gem "rails", :git => "git://github\.com/rails/rails\.git" +. +.P +If using SSH, the user that you use to run \fBbundle install\fR \fBMUST\fR have the appropriate keys available in their \fB$HOME/\.ssh\fR\. +. +.P +\fBNOTE\fR: \fBhttp://\fR and \fBgit://\fR URLs should be avoided if at all possible\. These protocols are unauthenticated, so a man\-in\-the\-middle attacker can deliver malicious code and compromise your system\. HTTPS and SSH are strongly preferred\. +. +.P +The \fBgroup\fR, \fBplatforms\fR, and \fBrequire\fR options are available and behave exactly the same as they would for a normal gem\. +. +.P +A git repository \fBSHOULD\fR have at least one file, at the root of the directory containing the gem, with the extension \fB\.gemspec\fR\. This file \fBMUST\fR contain a valid gem specification, as expected by the \fBgem build\fR command\. +. +.P +If a git repository does not have a \fB\.gemspec\fR, bundler will attempt to create one, but it will not contain any dependencies, executables, or C extension compilation instructions\. As a result, it may fail to properly integrate into your application\. +. +.P +If a git repository does have a \fB\.gemspec\fR for the gem you attached it to, a version specifier, if provided, means that the git repository is only valid if the \fB\.gemspec\fR specifies a version matching the version specifier\. If not, bundler will print a warning\. +. +.IP "" 4 +. +.nf + +gem "rails", "2\.3\.8", :git => "https://github\.com/rails/rails\.git" +# bundle install will fail, because the \.gemspec in the rails +# repository\'s master branch specifies version 3\.0\.0 +. +.fi +. +.IP "" 0 +. +.P +If a git repository does \fBnot\fR have a \fB\.gemspec\fR for the gem you attached it to, a version specifier \fBMUST\fR be provided\. Bundler will use this version in the simple \fB\.gemspec\fR it creates\. +. +.P +Git repositories support a number of additional options\. +. +.TP +\fBbranch\fR, \fBtag\fR, and \fBref\fR +You \fBMUST\fR only specify at most one of these options\. The default is \fB:branch => "master"\fR\. For example: +. +.IP +gem "rails", :git => "https://github\.com/rails/rails\.git", :branch => "5\-0\-stable" +. +.IP +gem "rails", :git => "https://github\.com/rails/rails\.git", :tag => "v5\.0\.0" +. +.IP +gem "rails", :git => "https://github\.com/rails/rails\.git", :ref => "4aded" +. +.TP +\fBsubmodules\fR +For reference, a git submodule \fIhttps://git\-scm\.com/book/en/v2/Git\-Tools\-Submodules\fR lets you have another git repository within a subfolder of your repository\. Specify \fB:submodules => true\fR to cause bundler to expand any submodules included in the git repository +. +.P +If a git repository contains multiple \fB\.gemspecs\fR, each \fB\.gemspec\fR represents a gem located at the same place in the file system as the \fB\.gemspec\fR\. +. +.IP "" 4 +. +.nf + +|~rails [git root] +| |\-rails\.gemspec [rails gem located here] +|~actionpack +| |\-actionpack\.gemspec [actionpack gem located here] +|~activesupport +| |\-activesupport\.gemspec [activesupport gem located here] +|\.\.\. +. +.fi +. +.IP "" 0 +. +.P +To install a gem located in a git repository, bundler changes to the directory containing the gemspec, runs \fBgem build name\.gemspec\fR and then installs the resulting gem\. The \fBgem build\fR command, which comes standard with Rubygems, evaluates the \fB\.gemspec\fR in the context of the directory in which it is located\. +. +.SS "GIT SOURCE" +A custom git source can be defined via the \fBgit_source\fR method\. Provide the source\'s name as an argument, and a block which receives a single argument and interpolates it into a string to return the full repo address: +. +.IP "" 4 +. +.nf + +git_source(:stash){ |repo_name| "https://stash\.corp\.acme\.pl/#{repo_name}\.git" } +gem \'rails\', :stash => \'forks/rails\' +. +.fi +. +.IP "" 0 +. +.P +In addition, if you wish to choose a specific branch: +. +.IP "" 4 +. +.nf + +gem "rails", :stash => "forks/rails", :branch => "branch_name" +. +.fi +. +.IP "" 0 +. +.SS "GITHUB" +\fBNOTE\fR: This shorthand should be avoided until Bundler 2\.0, since it currently expands to an insecure \fBgit://\fR URL\. This allows a man\-in\-the\-middle attacker to compromise your system\. +. +.P +If the git repository you want to use is hosted on GitHub and is public, you can use the :github shorthand to specify the github username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\. +. +.IP "" 4 +. +.nf + +gem "rails", :github => "rails/rails" +gem "rails", :github => "rails" +. +.fi +. +.IP "" 0 +. +.P +Are both equivalent to +. +.IP "" 4 +. +.nf + +gem "rails", :git => "git://github\.com/rails/rails\.git" +. +.fi +. +.IP "" 0 +. +.P +Since the \fBgithub\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\. +. +.SS "GIST" +If the git repository you want to use is hosted as a Github Gist and is public, you can use the :gist shorthand to specify the gist identifier (without the trailing "\.git")\. +. +.IP "" 4 +. +.nf + +gem "the_hatch", :gist => "4815162342" +. +.fi +. +.IP "" 0 +. +.P +Is equivalent to: +. +.IP "" 4 +. +.nf + +gem "the_hatch", :git => "https://gist\.github\.com/4815162342\.git" +. +.fi +. +.IP "" 0 +. +.P +Since the \fBgist\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\. +. +.SS "BITBUCKET" +If the git repository you want to use is hosted on Bitbucket and is public, you can use the :bitbucket shorthand to specify the bitbucket username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\. +. +.IP "" 4 +. +.nf + +gem "rails", :bitbucket => "rails/rails" +gem "rails", :bitbucket => "rails" +. +.fi +. +.IP "" 0 +. +.P +Are both equivalent to +. +.IP "" 4 +. +.nf + +gem "rails", :git => "https://rails@bitbucket\.org/rails/rails\.git" +. +.fi +. +.IP "" 0 +. +.P +Since the \fBbitbucket\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\. +. +.SS "PATH" +You can specify that a gem is located in a particular location on the file system\. Relative paths are resolved relative to the directory containing the \fBGemfile\fR\. +. +.P +Similar to the semantics of the \fB:git\fR option, the \fB:path\fR option requires that the directory in question either contains a \fB\.gemspec\fR for the gem, or that you specify an explicit version that bundler should use\. +. +.P +Unlike \fB:git\fR, bundler does not compile C extensions for gems specified as paths\. +. +.IP "" 4 +. +.nf + +gem "rails", :path => "vendor/rails" +. +.fi +. +.IP "" 0 +. +.P +If you would like to use multiple local gems directly from the filesystem, you can set a global \fBpath\fR option to the path containing the gem\'s files\. This will automatically load gemspec files from subdirectories\. +. +.IP "" 4 +. +.nf + +path \'components\' do + gem \'admin_ui\' + gem \'public_ui\' +end +. +.fi +. +.IP "" 0 +. +.SH "BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS" +The \fB:source\fR, \fB:git\fR, \fB:path\fR, \fB:group\fR, and \fB:platforms\fR options may be applied to a group of gems by using block form\. +. +.IP "" 4 +. +.nf + +source "https://gems\.example\.com" do + gem "some_internal_gem" + gem "another_internal_gem" +end + +git "https://github\.com/rails/rails\.git" do + gem "activesupport" + gem "actionpack" +end + +platforms :ruby do + gem "ruby\-debug" + gem "sqlite3" +end + +group :development, :optional => true do + gem "wirble" + gem "faker" +end +. +.fi +. +.IP "" 0 +. +.P +In the case of the group block form the :optional option can be given to prevent a group from being installed unless listed in the \fB\-\-with\fR option given to the \fBbundle install\fR command\. +. +.P +In the case of the \fBgit\fR block form, the \fB:ref\fR, \fB:branch\fR, \fB:tag\fR, and \fB:submodules\fR options may be passed to the \fBgit\fR method, and all gems in the block will inherit those options\. +. +.P +The presence of a \fBsource\fR block in a Gemfile also makes that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when defining source blocks, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources, either via source blocks or \fB:source\fR directives on individual gems\. +. +.SH "INSTALL_IF" +The \fBinstall_if\fR method allows gems to be installed based on a proc or lambda\. This is especially useful for optional gems that can only be used if certain software is installed or some other conditions are met\. +. +.IP "" 4 +. +.nf + +install_if \-> { RUBY_PLATFORM =~ /darwin/ } do + gem "pasteboard" +end +. +.fi +. +.IP "" 0 +. +.SH "GEMSPEC" +The \fB\.gemspec\fR \fIhttp://guides\.rubygems\.org/specification\-reference/\fR file is where you provide metadata about your gem to Rubygems\. Some required Gemspec attributes include the name, description, and homepage of your gem\. This is also where you specify the dependencies your gem needs to run\. +. +.P +If you wish to use Bundler to help install dependencies for a gem while it is being developed, use the \fBgemspec\fR method to pull in the dependencies listed in the \fB\.gemspec\fR file\. +. +.P +The \fBgemspec\fR method adds any runtime dependencies as gem requirements in the default group\. It also adds development dependencies as gem requirements in the \fBdevelopment\fR group\. Finally, it adds a gem requirement on your project (\fB:path => \'\.\'\fR)\. In conjunction with \fBBundler\.setup\fR, this allows you to require project files in your test code as you would if the project were installed as a gem; you need not manipulate the load path manually or require project files via relative paths\. +. +.P +The \fBgemspec\fR method supports optional \fB:path\fR, \fB:glob\fR, \fB:name\fR, and \fB:development_group\fR options, which control where bundler looks for the \fB\.gemspec\fR, the glob it uses to look for the gemspec (defaults to: "{,\fI,\fR/*}\.gemspec"), what named \fB\.gemspec\fR it uses (if more than one is present), and which group development dependencies are included in\. +. +.P +When a \fBgemspec\fR dependency encounters version conflicts during resolution, the local version under development will always be selected \-\- even if there are remote versions that better match other requirements for the \fBgemspec\fR gem\. +. +.SH "SOURCE PRIORITY" +When attempting to locate a gem to satisfy a gem requirement, bundler uses the following priority order: +. +.IP "1." 4 +The source explicitly attached to the gem (using \fB:source\fR, \fB:path\fR, or \fB:git\fR) +. +.IP "2." 4 +For implicit gems (dependencies of explicit gems), any source, git, or path repository declared on the parent\. This results in bundler prioritizing the ActiveSupport gem from the Rails git repository over ones from \fBrubygems\.org\fR +. +.IP "3." 4 +The sources specified via global \fBsource\fR lines, searching each source in your \fBGemfile\fR from last added to first added\. +. +.IP "" 0 + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5.ronn b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5.ronn new file mode 100644 index 0000000000..994f0d66bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/gemfile.5.ronn @@ -0,0 +1,517 @@ +Gemfile(5) -- A format for describing gem dependencies for Ruby programs +======================================================================== + +## SYNOPSIS + +A `Gemfile` describes the gem dependencies required to execute associated +Ruby code. + +Place the `Gemfile` in the root of the directory containing the associated +code. For instance, in a Rails application, place the `Gemfile` in the same +directory as the `Rakefile`. + +## SYNTAX + +A `Gemfile` is evaluated as Ruby code, in a context which makes available +a number of methods used to describe the gem requirements. + +## GLOBAL SOURCES + +At the top of the `Gemfile`, add a line for the `Rubygems` source that contains +the gems listed in the `Gemfile`. + + source "https://rubygems.org" + +It is possible, but not recommended as of Bundler 1.7, to add multiple global +`source` lines. Each of these `source`s `MUST` be a valid Rubygems repository. + +Sources are checked for gems following the heuristics described in +[SOURCE PRIORITY][]. If a gem is found in more than one global source, Bundler +will print a warning after installing the gem indicating which source was used, +and listing the other sources where the gem is available. A specific source can +be selected for gems that need to use a non-standard repository, suppressing +this warning, by using the [`:source` option](#SOURCE) or a +[`source` block](#BLOCK-FORM-OF-SOURCE-GIT-PATH-GROUP-and-PLATFORMS). + +### CREDENTIALS + +Some gem sources require a username and password. Use [bundle config(1)](bundle-config.1.html) to set +the username and password for any of the sources that need it. The command must +be run once on each computer that will install the Gemfile, but this keeps the +credentials from being stored in plain text in version control. + + bundle config gems.example.com user:password + +For some sources, like a company Gemfury account, it may be easier to +include the credentials in the Gemfile as part of the source URL. + + source "https://user:password@gems.example.com" + +Credentials in the source URL will take precedence over credentials set using +`config`. + +## RUBY + +If your application requires a specific Ruby version or engine, specify your +requirements using the `ruby` method, with the following arguments. +All parameters are `OPTIONAL` unless otherwise specified. + +### VERSION (required) + +The version of Ruby that your application requires. If your application +requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this +should be the Ruby version that the engine is compatible with. + + ruby "1.9.3" + +### ENGINE + +Each application _may_ specify a Ruby engine. If an engine is specified, an +engine version _must_ also be specified. + +What exactly is an Engine? + - A Ruby engine is an implementation of the Ruby language. + + - For background: the reference or original implementation of the Ruby + programming language is called + [Matz's Ruby Interpreter](https://en.wikipedia.org/wiki/Ruby_MRI), or MRI + for short. This is named after Ruby creator Yukihiro Matsumoto, + also known as Matz. MRI is also known as CRuby, because it is written in C. + MRI is the most widely used Ruby engine. + + - [Other implementations](https://www.ruby-lang.org/en/about/) of Ruby exist. + Some of the more well-known implementations include + [Rubinius](https://rubinius.com/), and [JRuby](http://jruby.org/). + Rubinius is an alternative implementation of Ruby written in Ruby. + JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine. + +### ENGINE VERSION + +Each application _may_ specify a Ruby engine version. If an engine version is +specified, an engine _must_ also be specified. If the engine is "ruby" the +engine version specified _must_ match the Ruby version. + + ruby "1.8.7", :engine => "jruby", :engine_version => "1.6.7" + +### PATCHLEVEL + +Each application _may_ specify a Ruby patchlevel. + + ruby "2.0.0", :patchlevel => "247" + +## GEMS + +Specify gem requirements using the `gem` method, with the following arguments. +All parameters are `OPTIONAL` unless otherwise specified. + +### NAME (required) + +For each gem requirement, list a single _gem_ line. + + gem "nokogiri" + +### VERSION + +Each _gem_ `MAY` have one or more version specifiers. + + gem "nokogiri", ">= 1.4.2" + gem "RedCloth", ">= 4.1.0", "< 4.2.0" + +### REQUIRE AS + +Each _gem_ `MAY` specify files that should be used when autorequiring via +`Bundler.require`. You may pass an array with multiple files or `true` if the file +you want `required` has the same name as _gem_ or `false` to +prevent any file from being autorequired. + + gem "redis", :require => ["redis/connection/hiredis", "redis"] + gem "webmock", :require => false + gem "byebug", :require => true + +The argument defaults to the name of the gem. For example, these are identical: + + gem "nokogiri" + gem "nokogiri", :require => "nokogiri" + gem "nokogiri", :require => true + +### GROUPS + +Each _gem_ `MAY` specify membership in one or more groups. Any _gem_ that does +not specify membership in any group is placed in the `default` group. + + gem "rspec", :group => :test + gem "wirble", :groups => [:development, :test] + +The Bundler runtime allows its two main methods, `Bundler.setup` and +`Bundler.require`, to limit their impact to particular groups. + + # setup adds gems to Ruby's load path + Bundler.setup # defaults to all groups + require "bundler/setup" # same as Bundler.setup + Bundler.setup(:default) # only set up the _default_ group + Bundler.setup(:test) # only set up the _test_ group (but `not` _default_) + Bundler.setup(:default, :test) # set up the _default_ and _test_ groups, but no others + + # require requires all of the gems in the specified groups + Bundler.require # defaults to the _default_ group + Bundler.require(:default) # identical + Bundler.require(:default, :test) # requires the _default_ and _test_ groups + Bundler.require(:test) # requires the _test_ group + +The Bundler CLI allows you to specify a list of groups whose gems `bundle install` should +not install with the `without` configuration. + +To specify multiple groups to ignore, specify a list of groups separated by spaces. + + bundle config set --local without test + bundle config set --local without development test + +Also, calling `Bundler.setup` with no parameters, or calling `require "bundler/setup"` +will setup all groups except for the ones you excluded via `--without` (since they +are not available). + +Note that on `bundle install`, bundler downloads and evaluates all gems, in order to +create a single canonical list of all of the required gems and their dependencies. +This means that you cannot list different versions of the same gems in different +groups. For more details, see [Understanding Bundler](https://bundler.io/rationale.html). + +### PLATFORMS + +If a gem should only be used in a particular platform or set of platforms, you can +specify them. Platforms are essentially identical to groups, except that you do not +need to use the `--without` install-time flag to exclude groups of gems for other +platforms. + +There are a number of `Gemfile` platforms: + + * `ruby`: + C Ruby (MRI), Rubinius or TruffleRuby, but `NOT` Windows + * `mri`: + Same as _ruby_, but only C Ruby (MRI) + * `mingw`: + Windows 32 bit 'mingw32' platform (aka RubyInstaller) + * `x64_mingw`: + Windows 64 bit 'mingw32' platform (aka RubyInstaller x64) + * `rbx`: + Rubinius + * `jruby`: + JRuby + * `truffleruby`: + TruffleRuby + * `mswin`: + Windows + +You can restrict further by platform and version for all platforms *except* for +`rbx`, `jruby`, `truffleruby` and `mswin`. + +To specify a version in addition to a platform, append the version number without +the delimiter to the platform. For example, to specify that a gem should only be +used on platforms with Ruby 2.3, use: + + ruby_23 + +The full list of platforms and supported versions includes: + + * `ruby`: + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 + * `mri`: + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 + * `mingw`: + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 + * `x64_mingw`: + 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 + +As with groups, you can specify one or more platforms: + + gem "weakling", :platforms => :jruby + gem "ruby-debug", :platforms => :mri_18 + gem "nokogiri", :platforms => [:mri_18, :jruby] + +All operations involving groups ([`bundle install`](bundle-install.1.html), `Bundler.setup`, +`Bundler.require`) behave exactly the same as if any groups not +matching the current platform were explicitly excluded. + +### SOURCE + +You can select an alternate Rubygems repository for a gem using the ':source' +option. + + gem "some_internal_gem", :source => "https://gems.example.com" + +This forces the gem to be loaded from this source and ignores any global sources +declared at the top level of the file. If the gem does not exist in this source, +it will not be installed. + +Bundler will search for child dependencies of this gem by first looking in the +source selected for the parent, but if they are not found there, it will fall +back on global sources using the ordering described in [SOURCE PRIORITY][]. + +Selecting a specific source repository this way also suppresses the ambiguous +gem warning described above in +[GLOBAL SOURCES (#source)](#GLOBAL-SOURCES). + +Using the `:source` option for an individual gem will also make that source +available as a possible global source for any other gems which do not specify +explicit sources. Thus, when adding gems with explicit sources, it is +recommended that you also ensure all other gems in the Gemfile are using +explicit sources. + +### GIT + +If necessary, you can specify that a gem is located at a particular +git repository using the `:git` parameter. The repository can be accessed via +several protocols: + + * `HTTP(S)`: + gem "rails", :git => "https://github.com/rails/rails.git" + * `SSH`: + gem "rails", :git => "git@github.com:rails/rails.git" + * `git`: + gem "rails", :git => "git://github.com/rails/rails.git" + +If using SSH, the user that you use to run `bundle install` `MUST` have the +appropriate keys available in their `$HOME/.ssh`. + +`NOTE`: `http://` and `git://` URLs should be avoided if at all possible. These +protocols are unauthenticated, so a man-in-the-middle attacker can deliver +malicious code and compromise your system. HTTPS and SSH are strongly +preferred. + +The `group`, `platforms`, and `require` options are available and behave +exactly the same as they would for a normal gem. + +A git repository `SHOULD` have at least one file, at the root of the +directory containing the gem, with the extension `.gemspec`. This file +`MUST` contain a valid gem specification, as expected by the `gem build` +command. + +If a git repository does not have a `.gemspec`, bundler will attempt to +create one, but it will not contain any dependencies, executables, or +C extension compilation instructions. As a result, it may fail to properly +integrate into your application. + +If a git repository does have a `.gemspec` for the gem you attached it +to, a version specifier, if provided, means that the git repository is +only valid if the `.gemspec` specifies a version matching the version +specifier. If not, bundler will print a warning. + + gem "rails", "2.3.8", :git => "https://github.com/rails/rails.git" + # bundle install will fail, because the .gemspec in the rails + # repository's master branch specifies version 3.0.0 + +If a git repository does `not` have a `.gemspec` for the gem you attached +it to, a version specifier `MUST` be provided. Bundler will use this +version in the simple `.gemspec` it creates. + +Git repositories support a number of additional options. + + * `branch`, `tag`, and `ref`: + You `MUST` only specify at most one of these options. The default + is `:branch => "master"`. For example: + + gem "rails", :git => "https://github.com/rails/rails.git", :branch => "5-0-stable" + + gem "rails", :git => "https://github.com/rails/rails.git", :tag => "v5.0.0" + + gem "rails", :git => "https://github.com/rails/rails.git", :ref => "4aded" + + * `submodules`: + For reference, a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) + lets you have another git repository within a subfolder of your repository. + Specify `:submodules => true` to cause bundler to expand any + submodules included in the git repository + +If a git repository contains multiple `.gemspecs`, each `.gemspec` +represents a gem located at the same place in the file system as +the `.gemspec`. + + |~rails [git root] + | |-rails.gemspec [rails gem located here] + |~actionpack + | |-actionpack.gemspec [actionpack gem located here] + |~activesupport + | |-activesupport.gemspec [activesupport gem located here] + |... + +To install a gem located in a git repository, bundler changes to +the directory containing the gemspec, runs `gem build name.gemspec` +and then installs the resulting gem. The `gem build` command, +which comes standard with Rubygems, evaluates the `.gemspec` in +the context of the directory in which it is located. + +### GIT SOURCE + +A custom git source can be defined via the `git_source` method. Provide the source's name +as an argument, and a block which receives a single argument and interpolates it into a +string to return the full repo address: + + git_source(:stash){ |repo_name| "https://stash.corp.acme.pl/#{repo_name}.git" } + gem 'rails', :stash => 'forks/rails' + +In addition, if you wish to choose a specific branch: + + gem "rails", :stash => "forks/rails", :branch => "branch_name" + +### GITHUB + +`NOTE`: This shorthand should be avoided until Bundler 2.0, since it +currently expands to an insecure `git://` URL. This allows a +man-in-the-middle attacker to compromise your system. + +If the git repository you want to use is hosted on GitHub and is public, you can use the +:github shorthand to specify the github username and repository name (without the +trailing ".git"), separated by a slash. If both the username and repository name are the +same, you can omit one. + + gem "rails", :github => "rails/rails" + gem "rails", :github => "rails" + +Are both equivalent to + + gem "rails", :git => "git://github.com/rails/rails.git" + +Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument. + +### GIST + +If the git repository you want to use is hosted as a Github Gist and is public, you can use +the :gist shorthand to specify the gist identifier (without the trailing ".git"). + + gem "the_hatch", :gist => "4815162342" + +Is equivalent to: + + gem "the_hatch", :git => "https://gist.github.com/4815162342.git" + +Since the `gist` method is a specialization of `git_source`, it accepts a `:branch` named argument. + +### BITBUCKET + +If the git repository you want to use is hosted on Bitbucket and is public, you can use the +:bitbucket shorthand to specify the bitbucket username and repository name (without the +trailing ".git"), separated by a slash. If both the username and repository name are the +same, you can omit one. + + gem "rails", :bitbucket => "rails/rails" + gem "rails", :bitbucket => "rails" + +Are both equivalent to + + gem "rails", :git => "https://rails@bitbucket.org/rails/rails.git" + +Since the `bitbucket` method is a specialization of `git_source`, it accepts a `:branch` named argument. + +### PATH + +You can specify that a gem is located in a particular location +on the file system. Relative paths are resolved relative to the +directory containing the `Gemfile`. + +Similar to the semantics of the `:git` option, the `:path` +option requires that the directory in question either contains +a `.gemspec` for the gem, or that you specify an explicit +version that bundler should use. + +Unlike `:git`, bundler does not compile C extensions for +gems specified as paths. + + gem "rails", :path => "vendor/rails" + +If you would like to use multiple local gems directly from the filesystem, you can set a global `path` option to the path containing the gem's files. This will automatically load gemspec files from subdirectories. + + path 'components' do + gem 'admin_ui' + gem 'public_ui' + end + +## BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS + +The `:source`, `:git`, `:path`, `:group`, and `:platforms` options may be +applied to a group of gems by using block form. + + source "https://gems.example.com" do + gem "some_internal_gem" + gem "another_internal_gem" + end + + git "https://github.com/rails/rails.git" do + gem "activesupport" + gem "actionpack" + end + + platforms :ruby do + gem "ruby-debug" + gem "sqlite3" + end + + group :development, :optional => true do + gem "wirble" + gem "faker" + end + +In the case of the group block form the :optional option can be given +to prevent a group from being installed unless listed in the `--with` +option given to the `bundle install` command. + +In the case of the `git` block form, the `:ref`, `:branch`, `:tag`, +and `:submodules` options may be passed to the `git` method, and +all gems in the block will inherit those options. + +The presence of a `source` block in a Gemfile also makes that source +available as a possible global source for any other gems which do not specify +explicit sources. Thus, when defining source blocks, it is +recommended that you also ensure all other gems in the Gemfile are using +explicit sources, either via source blocks or `:source` directives on +individual gems. + +## INSTALL_IF + +The `install_if` method allows gems to be installed based on a proc or lambda. +This is especially useful for optional gems that can only be used if certain +software is installed or some other conditions are met. + + install_if -> { RUBY_PLATFORM =~ /darwin/ } do + gem "pasteboard" + end + +## GEMSPEC + +The [`.gemspec`](http://guides.rubygems.org/specification-reference/) file is where + you provide metadata about your gem to Rubygems. Some required Gemspec + attributes include the name, description, and homepage of your gem. This is + also where you specify the dependencies your gem needs to run. + +If you wish to use Bundler to help install dependencies for a gem while it is +being developed, use the `gemspec` method to pull in the dependencies listed in +the `.gemspec` file. + +The `gemspec` method adds any runtime dependencies as gem requirements in the +default group. It also adds development dependencies as gem requirements in the +`development` group. Finally, it adds a gem requirement on your project (`:path +=> '.'`). In conjunction with `Bundler.setup`, this allows you to require project +files in your test code as you would if the project were installed as a gem; you +need not manipulate the load path manually or require project files via relative +paths. + +The `gemspec` method supports optional `:path`, `:glob`, `:name`, and `:development_group` +options, which control where bundler looks for the `.gemspec`, the glob it uses to look +for the gemspec (defaults to: "{,*,*/*}.gemspec"), what named `.gemspec` it uses +(if more than one is present), and which group development dependencies are included in. + +When a `gemspec` dependency encounters version conflicts during resolution, the +local version under development will always be selected -- even if there are +remote versions that better match other requirements for the `gemspec` gem. + +## SOURCE PRIORITY + +When attempting to locate a gem to satisfy a gem requirement, +bundler uses the following priority order: + + 1. The source explicitly attached to the gem (using `:source`, `:path`, or + `:git`) + 2. For implicit gems (dependencies of explicit gems), any source, git, or path + repository declared on the parent. This results in bundler prioritizing the + ActiveSupport gem from the Rails git repository over ones from + `rubygems.org` + 3. The sources specified via global `source` lines, searching each source in + your `Gemfile` from last added to first added. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/index.txt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/index.txt new file mode 100644 index 0000000000..ef2956b2f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/man/index.txt @@ -0,0 +1,25 @@ +Gemfile(5) gemfile.5 +bundle(1) bundle.1 +bundle-add(1) bundle-add.1 +bundle-binstubs(1) bundle-binstubs.1 +bundle-cache(1) bundle-cache.1 +bundle-check(1) bundle-check.1 +bundle-clean(1) bundle-clean.1 +bundle-config(1) bundle-config.1 +bundle-doctor(1) bundle-doctor.1 +bundle-exec(1) bundle-exec.1 +bundle-gem(1) bundle-gem.1 +bundle-info(1) bundle-info.1 +bundle-init(1) bundle-init.1 +bundle-inject(1) bundle-inject.1 +bundle-install(1) bundle-install.1 +bundle-list(1) bundle-list.1 +bundle-lock(1) bundle-lock.1 +bundle-open(1) bundle-open.1 +bundle-outdated(1) bundle-outdated.1 +bundle-platform(1) bundle-platform.1 +bundle-pristine(1) bundle-pristine.1 +bundle-remove(1) bundle-remove.1 +bundle-show(1) bundle-show.1 +bundle-update(1) bundle-update.1 +bundle-viz(1) bundle-viz.1 diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/match_platform.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/match_platform.rb new file mode 100644 index 0000000000..69074925a6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/match_platform.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative "gem_helpers" + +module Bundler + module MatchPlatform + include GemHelpers + + def match_platform(p) + MatchPlatform.platforms_match?(platform, p) + end + + def self.platforms_match?(gemspec_platform, local_platform) + return true if gemspec_platform.nil? + return true if Gem::Platform::RUBY == gemspec_platform + return true if local_platform == gemspec_platform + gemspec_platform = Gem::Platform.new(gemspec_platform) + return true if GemHelpers.generic(gemspec_platform) === local_platform + return true if gemspec_platform === local_platform + + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/mirror.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/mirror.rb new file mode 100644 index 0000000000..a63b45b47d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/mirror.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +require "socket" + +module Bundler + class Settings + # Class used to build the mirror set and then find a mirror for a given URI + # + # @param prober [Prober object, nil] by default a TCPSocketProbe, this object + # will be used to probe the mirror address to validate that the mirror replies. + class Mirrors + def initialize(prober = nil) + @all = Mirror.new + @prober = prober || TCPSocketProbe.new + @mirrors = {} + end + + # Returns a mirror for the given uri. + # + # Depending on the uri having a valid mirror or not, it may be a + # mirror that points to the provided uri + def for(uri) + if @all.validate!(@prober).valid? + @all + else + fetch_valid_mirror_for(Settings.normalize_uri(uri)) + end + end + + def each + @mirrors.each do |k, v| + yield k, v.uri.to_s + end + end + + def parse(key, value) + config = MirrorConfig.new(key, value) + mirror = if config.all? + @all + else + @mirrors[config.uri] ||= Mirror.new + end + config.update_mirror(mirror) + end + + private + + def fetch_valid_mirror_for(uri) + downcased = uri.to_s.downcase + mirror = @mirrors[downcased] || @mirrors[Bundler::URI(downcased).host] || Mirror.new(uri) + mirror.validate!(@prober) + mirror = Mirror.new(uri) unless mirror.valid? + mirror + end + end + + # A mirror + # + # Contains both the uri that should be used as a mirror and the + # fallback timeout which will be used for probing if the mirror + # replies on time or not. + class Mirror + DEFAULT_FALLBACK_TIMEOUT = 0.1 + + attr_reader :uri, :fallback_timeout + + def initialize(uri = nil, fallback_timeout = 0) + self.uri = uri + self.fallback_timeout = fallback_timeout + @valid = nil + end + + def uri=(uri) + @uri = if uri.nil? + nil + else + Bundler::URI(uri.to_s) + end + @valid = nil + end + + def fallback_timeout=(timeout) + case timeout + when true, "true" + @fallback_timeout = DEFAULT_FALLBACK_TIMEOUT + when false, "false" + @fallback_timeout = 0 + else + @fallback_timeout = timeout.to_i + end + @valid = nil + end + + def ==(other) + !other.nil? && uri == other.uri && fallback_timeout == other.fallback_timeout + end + + def valid? + return false if @uri.nil? + return @valid unless @valid.nil? + false + end + + def validate!(probe = nil) + @valid = false if uri.nil? + if @valid.nil? + @valid = fallback_timeout == 0 || (probe || TCPSocketProbe.new).replies?(self) + end + self + end + end + + # Class used to parse one configuration line + # + # Gets the configuration line and the value. + # This object provides a `update_mirror` method + # used to setup the given mirror value. + class MirrorConfig + attr_accessor :uri, :value + + def initialize(config_line, value) + uri, fallback = + config_line.match(%r{\Amirror\.(all|.+?)(\.fallback_timeout)?\/?\z}).captures + @fallback = !fallback.nil? + @all = false + if uri == "all" + @all = true + else + @uri = Bundler::URI(uri).absolute? ? Settings.normalize_uri(uri) : uri + end + @value = value + end + + def all? + @all + end + + def update_mirror(mirror) + if @fallback + mirror.fallback_timeout = @value + else + mirror.uri = Settings.normalize_uri(@value) + end + end + end + + # Class used for probing TCP availability for a given mirror. + class TCPSocketProbe + def replies?(mirror) + MirrorSockets.new(mirror).any? do |socket, address, timeout| + begin + socket.connect_nonblock(address) + rescue Errno::EINPROGRESS + wait_for_writtable_socket(socket, address, timeout) + rescue RuntimeError # Connection failed somehow, again + false + end + end + end + + private + + def wait_for_writtable_socket(socket, address, timeout) + if IO.select(nil, [socket], nil, timeout) + probe_writtable_socket(socket, address) + else # TCP Handshake timed out, or there is something dropping packets + false + end + end + + def probe_writtable_socket(socket, address) + socket.connect_nonblock(address) + rescue Errno::EISCONN + true + rescue StandardError # Connection failed + false + end + end + end + + # Class used to build the list of sockets that correspond to + # a given mirror. + # + # One mirror may correspond to many different addresses, both + # because of it having many dns entries or because + # the network interface is both ipv4 and ipv5 + class MirrorSockets + def initialize(mirror) + @timeout = mirror.fallback_timeout + @addresses = Socket.getaddrinfo(mirror.uri.host, mirror.uri.port).map do |address| + SocketAddress.new(address[0], address[3], address[1]) + end + end + + def any? + @addresses.any? do |address| + socket = Socket.new(Socket.const_get(address.type), Socket::SOCK_STREAM, 0) + socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + value = yield socket, address.to_socket_address, @timeout + socket.close unless socket.closed? + value + end + end + end + + # Socket address builder. + # + # Given a socket type, a host and a port, + # provides a method to build sockaddr string + class SocketAddress + attr_reader :type, :host, :port + + def initialize(type, host, port) + @type = type + @host = host + @port = port + end + + def to_socket_address + Socket.pack_sockaddr_in(@port, @host) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin.rb new file mode 100644 index 0000000000..023c25b33e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin.rb @@ -0,0 +1,331 @@ +# frozen_string_literal: true + +require_relative "plugin/api" + +module Bundler + module Plugin + autoload :DSL, File.expand_path("plugin/dsl", __dir__) + autoload :Events, File.expand_path("plugin/events", __dir__) + autoload :Index, File.expand_path("plugin/index", __dir__) + autoload :Installer, File.expand_path("plugin/installer", __dir__) + autoload :SourceList, File.expand_path("plugin/source_list", __dir__) + + class MalformattedPlugin < PluginError; end + class UndefinedCommandError < PluginError; end + class UnknownSourceError < PluginError; end + + PLUGIN_FILE_NAME = "plugins.rb".freeze + + module_function + + def reset! + instance_variables.each {|i| remove_instance_variable(i) } + + @sources = {} + @commands = {} + @hooks_by_event = Hash.new {|h, k| h[k] = [] } + @loaded_plugin_names = [] + end + + reset! + + # Installs a new plugin by the given name + # + # @param [Array] names the name of plugin to be installed + # @param [Hash] options various parameters as described in description. + # Refer to cli/plugin for available options + def install(names, options) + specs = Installer.new.install(names, options) + + save_plugins names, specs + rescue PluginError => e + specs_to_delete = specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) } + specs_to_delete.each_value {|spec| Bundler.rm_rf(spec.full_gem_path) } + + names_list = names.map {|name| "`#{name}`" }.join(", ") + Bundler.ui.error "Failed to install the following plugins: #{names_list}. The underlying error was: #{e.message}.\n #{e.backtrace.join("\n ")}" + end + + # Uninstalls plugins by the given names + # + # @param [Array] names the names of plugins to be uninstalled + def uninstall(names, options) + if names.empty? && !options[:all] + Bundler.ui.error "No plugins to uninstall. Specify at least 1 plugin to uninstall.\n"\ + "Use --all option to uninstall all the installed plugins." + return + end + + names = index.installed_plugins if options[:all] + if names.any? + names.each do |name| + if index.installed?(name) + Bundler.rm_rf(index.plugin_path(name)) + index.unregister_plugin(name) + Bundler.ui.info "Uninstalled plugin #{name}" + else + Bundler.ui.error "Plugin #{name} is not installed \n" + end + end + else + Bundler.ui.info "No plugins installed" + end + end + + # List installed plugins and commands + # + def list + installed_plugins = index.installed_plugins + if installed_plugins.any? + output = String.new + installed_plugins.each do |plugin| + output << "#{plugin}\n" + output << "-----\n" + index.plugin_commands(plugin).each do |command| + output << " #{command}\n" + end + output << "\n" + end + else + output = "No plugins installed" + end + Bundler.ui.info output + end + + # Evaluates the Gemfile with a limited DSL and installs the plugins + # specified by plugin method + # + # @param [Pathname] gemfile path + # @param [Proc] block that can be evaluated for (inline) Gemfile + def gemfile_install(gemfile = nil, &inline) + Bundler.settings.temporary(:frozen => false, :deployment => false) do + builder = DSL.new + if block_given? + builder.instance_eval(&inline) + else + builder.eval_gemfile(gemfile) + end + builder.check_primary_source_safety + definition = builder.to_definition(nil, true) + + return if definition.dependencies.empty? + + plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p } + installed_specs = Installer.new.install_definition(definition) + + save_plugins plugins, installed_specs, builder.inferred_plugins + end + rescue RuntimeError => e + unless e.is_a?(GemfileError) + Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}" + end + raise + end + + # The index object used to store the details about the plugin + def index + @index ||= Index.new + end + + # The directory root for all plugin related data + # + # If run in an app, points to local root, in app_config_path + # Otherwise, points to global root, in Bundler.user_bundle_path("plugin") + def root + @root ||= if SharedHelpers.in_bundle? + local_root + else + global_root + end + end + + def local_root + Bundler.app_config_path.join("plugin") + end + + # The global directory root for all plugin related data + def global_root + Bundler.user_bundle_path("plugin") + end + + # The cache directory for plugin stuffs + def cache + @cache ||= root.join("cache") + end + + # To be called via the API to register to handle a command + def add_command(command, cls) + @commands[command] = cls + end + + # Checks if any plugin handles the command + def command?(command) + !index.command_plugin(command).nil? + end + + # To be called from Cli class to pass the command and argument to + # appropriate plugin class + def exec_command(command, args) + raise UndefinedCommandError, "Command `#{command}` not found" unless command? command + + load_plugin index.command_plugin(command) unless @commands.key? command + + @commands[command].new.exec(command, args) + end + + # To be called via the API to register to handle a source plugin + def add_source(source, cls) + @sources[source] = cls + end + + # Checks if any plugin declares the source + def source?(name) + !index.source_plugin(name.to_s).nil? + end + + # @return [Class] that handles the source. The class includes API::Source + def source(name) + raise UnknownSourceError, "Source #{name} not found" unless source? name + + load_plugin(index.source_plugin(name)) unless @sources.key? name + + @sources[name] + end + + # @param [Hash] The options that are present in the lock file + # @return [API::Source] the instance of the class that handles the source + # type passed in locked_opts + def source_from_lock(locked_opts) + src = source(locked_opts["type"]) + + src.new(locked_opts.merge("uri" => locked_opts["remote"])) + end + + # To be called via the API to register a hooks and corresponding block that + # will be called to handle the hook + def add_hook(event, &block) + unless Events.defined_event?(event) + raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" + end + @hooks_by_event[event.to_s] << block + end + + # Runs all the hooks that are registered for the passed event + # + # It passes the passed arguments and block to the block registered with + # the api. + # + # @param [String] event + def hook(event, *args, &arg_blk) + return unless Bundler.feature_flag.plugins? + unless Events.defined_event?(event) + raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" + end + + plugins = index.hook_plugins(event) + return unless plugins.any? + + (plugins - @loaded_plugin_names).each {|name| load_plugin(name) } + + @hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) } + end + + # currently only intended for specs + # + # @return [String, nil] installed path + def installed?(plugin) + Index.new.installed?(plugin) + end + + # Post installation processing and registering with index + # + # @param [Array] plugins list to be installed + # @param [Hash] specs of plugins mapped to installation path (currently they + # contain all the installed specs, including plugins) + # @param [Array] names of inferred source plugins that can be ignored + def save_plugins(plugins, specs, optional_plugins = []) + plugins.each do |name| + spec = specs[name] + validate_plugin! Pathname.new(spec.full_gem_path) + installed = register_plugin(name, spec, optional_plugins.include?(name)) + Bundler.ui.info "Installed plugin #{name}" if installed + end + end + + # Checks if the gem is good to be a plugin + # + # At present it only checks whether it contains plugins.rb file + # + # @param [Pathname] plugin_path the path plugin is installed at + # @raise [MalformattedPlugin] if plugins.rb file is not found + def validate_plugin!(plugin_path) + plugin_file = plugin_path.join(PLUGIN_FILE_NAME) + raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file? + end + + # Runs the plugins.rb file in an isolated namespace, records the plugin + # actions it registers for and then passes the data to index to be stored. + # + # @param [String] name the name of the plugin + # @param [Specification] spec of installed plugin + # @param [Boolean] optional_plugin, removed if there is conflict with any + # other plugin (used for default source plugins) + # + # @raise [MalformattedPlugin] if plugins.rb raises any error + def register_plugin(name, spec, optional_plugin = false) + commands = @commands + sources = @sources + hooks = @hooks_by_event + + @commands = {} + @sources = {} + @hooks_by_event = Hash.new {|h, k| h[k] = [] } + + load_paths = spec.load_paths + Bundler.rubygems.add_to_load_path(load_paths) + path = Pathname.new spec.full_gem_path + + begin + load path.join(PLUGIN_FILE_NAME), true + rescue StandardError => e + raise MalformattedPlugin, "#{e.class}: #{e.message}" + end + + if optional_plugin && @sources.keys.any? {|s| source? s } + Bundler.rm_rf(path) + false + else + index.register_plugin(name, path.to_s, load_paths, @commands.keys, + @sources.keys, @hooks_by_event.keys) + true + end + ensure + @commands = commands + @sources = sources + @hooks_by_event = hooks + end + + # Executes the plugins.rb file + # + # @param [String] name of the plugin + def load_plugin(name) + # Need to ensure before this that plugin root where the rest of gems + # are installed to be on load path to support plugin deps. Currently not + # done to avoid conflicts + path = index.plugin_path(name) + + Bundler.rubygems.add_to_load_path(index.load_paths(name)) + + load path.join(PLUGIN_FILE_NAME) + + @loaded_plugin_names << name + rescue RuntimeError => e + Bundler.ui.error "Failed loading plugin #{name}: #{e.message}" + raise + end + + class << self + private :load_plugin, :register_plugin, :save_plugins, :validate_plugin! + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api.rb new file mode 100644 index 0000000000..ee2bffe3ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Bundler + # This is the interfacing class represents the API that we intend to provide + # the plugins to use. + # + # For plugins to be independent of the Bundler internals they shall limit their + # interactions to methods of this class only. This will save them from breaking + # when some internal change. + # + # Currently we are delegating the methods defined in Bundler class to + # itself. So, this class acts as a buffer. + # + # If there is some change in the Bundler class that is incompatible to its + # previous behavior or if otherwise desired, we can reimplement(or implement) + # the method to preserve compatibility. + # + # To use this, either the class can inherit this class or use it directly. + # For example of both types of use, refer the file `spec/plugins/command.rb` + # + # To use it without inheriting, you will have to create an object of this + # to use the functions (except for declaration functions like command, source, + # and hooks). + module Plugin + class API + autoload :Source, File.expand_path("api/source", __dir__) + + # The plugins should declare that they handle a command through this helper. + # + # @param [String] command being handled by them + # @param [Class] (optional) class that handles the command. If not + # provided, the `self` class will be used. + def self.command(command, cls = self) + Plugin.add_command command, cls + end + + # The plugins should declare that they provide a installation source + # through this helper. + # + # @param [String] the source type they provide + # @param [Class] (optional) class that handles the source. If not + # provided, the `self` class will be used. + def self.source(source, cls = self) + cls.send :include, Bundler::Plugin::API::Source + Plugin.add_source source, cls + end + + def self.hook(event, &block) + Plugin.add_hook(event, &block) + end + + # The cache dir to be used by the plugins for storage + # + # @return [Pathname] path of the cache dir + def cache_dir + Plugin.cache.join("plugins") + end + + # A tmp dir to be used by plugins + # Accepts names that get concatenated as suffix + # + # @return [Pathname] object for the new directory created + def tmp(*names) + Bundler.tmp(["plugin", *names].join("-")) + end + + def method_missing(name, *args, &blk) + return Bundler.send(name, *args, &blk) if Bundler.respond_to?(name) + + return SharedHelpers.send(name, *args, &blk) if SharedHelpers.respond_to?(name) + + super + end + + def respond_to_missing?(name, include_private = false) + SharedHelpers.respond_to?(name, include_private) || + Bundler.respond_to?(name, include_private) || super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api/source.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api/source.rb new file mode 100644 index 0000000000..f6f4ac4f0a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/api/source.rb @@ -0,0 +1,325 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + class API + # This class provides the base to build source plugins + # All the method here are required to build a source plugin (except + # `uri_hash`, `gem_install_dir`; they are helpers). + # + # Defaults for methods, where ever possible are provided which is + # expected to work. But, all source plugins have to override + # `fetch_gemspec_files` and `install`. Defaults are also not provided for + # `remote!`, `cache!` and `unlock!`. + # + # The defaults shall work for most situations but nevertheless they can + # be (preferably should be) overridden as per the plugins' needs safely + # (as long as they behave as expected). + # On overriding `initialize` you should call super first. + # + # If required plugin should override `hash`, `==` and `eql?` methods to be + # able to match objects representing same sources, but may be created in + # different situation (like form gemfile and lockfile). The default ones + # checks only for class and uri, but elaborate source plugins may need + # more comparisons (e.g. git checking on branch or tag). + # + # @!attribute [r] uri + # @return [String] the remote specified with `source` block in Gemfile + # + # @!attribute [r] options + # @return [String] options passed during initialization (either from + # lockfile or Gemfile) + # + # @!attribute [r] name + # @return [String] name that can be used to uniquely identify a source + # + # @!attribute [rw] dependency_names + # @return [Array] Names of dependencies that the source should + # try to resolve. It is not necessary to use this list internally. This + # is present to be compatible with `Definition` and is used by + # rubygems source. + module Source + attr_reader :uri, :options, :name + attr_accessor :dependency_names + + def initialize(opts) + @options = opts + @dependency_names = [] + @uri = opts["uri"] + @type = opts["type"] + @name = opts["name"] || "#{@type} at #{@uri}" + end + + # This is used by the default `spec` method to constructs the + # Specification objects for the gems and versions that can be installed + # by this source plugin. + # + # Note: If the spec method is overridden, this function is not necessary + # + # @return [Array] paths of the gemspec files for gems that can + # be installed + def fetch_gemspec_files + [] + end + + # Options to be saved in the lockfile so that the source plugin is able + # to check out same version of gem later. + # + # There options are passed when the source plugin is created from the + # lock file. + # + # @return [Hash] + def options_to_lock + {} + end + + # Install the gem specified by the spec at appropriate path. + # `install_path` provides a sufficient default, if the source can only + # satisfy one gem, but is not binding. + # + # @return [String] post installation message (if any) + def install(spec, opts) + raise MalformattedPlugin, "Source plugins need to override the install method." + end + + # It builds extensions, generates bins and installs them for the spec + # provided. + # + # It depends on `spec.loaded_from` to get full_gem_path. The source + # plugins should set that. + # + # It should be called in `install` after the plugin is done placing the + # gem at correct install location. + # + # It also runs Gem hooks `pre_install`, `post_build` and `post_install` + # + # Note: Do not override if you don't know what you are doing. + def post_install(spec, disable_exts = false) + opts = { :env_shebang => false, :disable_extensions => disable_exts } + installer = Bundler::Source::Path::Installer.new(spec, opts) + installer.post_install + end + + # A default installation path to install a single gem. If the source + # servers multiple gems, it's not of much use and the source should one + # of its own. + def install_path + @install_path ||= + begin + base_name = File.basename(Bundler::URI.parse(uri).normalize.path) + + gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}") + end + end + + # Parses the gemspec files to find the specs for the gems that can be + # satisfied by the source. + # + # Few important points to keep in mind: + # - If the gems are not installed then it shall return specs for all + # the gems it can satisfy + # - If gem is installed (that is to be detected by the plugin itself) + # then it shall return at least the specs that are installed. + # - The `loaded_from` for each of the specs shall be correct (it is + # used to find the load path) + # + # @return [Bundler::Index] index containing the specs + def specs + files = fetch_gemspec_files + + Bundler::Index.build do |index| + files.each do |file| + next unless spec = Bundler.load_gemspec(file) + Bundler.rubygems.set_installed_by_version(spec) + + spec.source = self + Bundler.rubygems.validate(spec) + + index << spec + end + end + end + + # Set internal representation to fetch the gems/specs locally. + # + # When this is called, the source should try to fetch the specs and + # install from the local system. + def local! + end + + # Set internal representation to fetch the gems/specs from remote. + # + # When this is called, the source should try to fetch the specs and + # install from remote path. + def remote! + end + + # Set internal representation to fetch the gems/specs from app cache. + # + # When this is called, the source should try to fetch the specs and + # install from the path provided by `app_cache_path`. + def cached! + end + + # This is called to update the spec and installation. + # + # If the source plugin is loaded from lockfile or otherwise, it shall + # refresh the cache/specs (e.g. git sources can make a fresh clone). + def unlock! + end + + # Name of directory where plugin the is expected to cache the gems when + # #cache is called. + # + # Also this name is matched against the directories in cache for pruning + # + # This is used by `app_cache_path` + def app_cache_dirname + base_name = File.basename(Bundler::URI.parse(uri).normalize.path) + "#{base_name}-#{uri_hash}" + end + + # This method is called while caching to save copy of the gems that the + # source can resolve to path provided by `app_cache_app`so that they can + # be reinstalled from the cache without querying the remote (i.e. an + # alternative to remote) + # + # This is stored with the app and source plugins should try to provide + # specs and install only from this cache when `cached!` is called. + # + # This cache is different from the internal caching that can be done + # at sub paths of `cache_path` (from API). This can be though as caching + # by bundler. + def cache(spec, custom_path = nil) + new_cache_path = app_cache_path(custom_path) + + FileUtils.rm_rf(new_cache_path) + FileUtils.cp_r(install_path, new_cache_path) + FileUtils.touch(app_cache_path.join(".bundlecache")) + end + + # This shall check if two source object represent the same source. + # + # The comparison shall take place only on the attribute that can be + # inferred from the options passed from Gemfile and not on attributes + # that are used to pin down the gem to specific version (e.g. Git + # sources should compare on branch and tag but not on commit hash) + # + # The sources objects are constructed from Gemfile as well as from + # lockfile. To converge the sources, it is necessary that they match. + # + # The same applies for `eql?` and `hash` + def ==(other) + other.is_a?(self.class) && uri == other.uri + end + + # When overriding `eql?` please preserve the behaviour as mentioned in + # docstring for `==` method. + alias_method :eql?, :== + + # When overriding `hash` please preserve the behaviour as mentioned in + # docstring for `==` method, i.e. two methods equal by above comparison + # should have same hash. + def hash + [self.class, uri].hash + end + + # A helper method, not necessary if not used internally. + def installed? + File.directory?(install_path) + end + + # The full path where the plugin should cache the gem so that it can be + # installed latter. + # + # Note: Do not override if you don't know what you are doing. + def app_cache_path(custom_path = nil) + @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname) + end + + # Used by definition. + # + # Note: Do not override if you don't know what you are doing. + def unmet_deps + specs.unmet_dependency_names + end + + # Used by definition. + # + # Note: Do not override if you don't know what you are doing. + def spec_names + specs.spec_names + end + + # Used by definition. + # + # Note: Do not override if you don't know what you are doing. + def add_dependency_names(names) + @dependencies |= Array(names) + end + + # Note: Do not override if you don't know what you are doing. + def can_lock?(spec) + spec.source == self + end + + # Generates the content to be entered into the lockfile. + # Saves type and remote and also calls to `options_to_lock`. + # + # Plugin should use `options_to_lock` to save information in lockfile + # and not override this. + # + # Note: Do not override if you don't know what you are doing. + def to_lock + out = String.new("#{LockfileParser::PLUGIN}\n") + out << " remote: #{@uri}\n" + out << " type: #{@type}\n" + options_to_lock.each do |opt, value| + out << " #{opt}: #{value}\n" + end + out << " specs:\n" + end + + def to_s + "plugin source for #{@type} with uri #{@uri}" + end + + # Note: Do not override if you don't know what you are doing. + def include?(other) + other == self + end + + def uri_hash + SharedHelpers.digest(:SHA1).hexdigest(uri) + end + + # Note: Do not override if you don't know what you are doing. + def gem_install_dir + Bundler.install_path + end + + # It is used to obtain the full_gem_path. + # + # spec's loaded_from path is expanded against this to get full_gem_path + # + # Note: Do not override if you don't know what you are doing. + def root + Bundler.root + end + + # @private + # Returns true + def bundler_plugin_api_source? + true + end + + # @private + # This API on source might not be stable, and for now we expect plugins + # to download all specs in `#specs`, so we implement the method for + # compatibility purposes and leave it undocumented (and don't support) + # overriding it) + def double_check_for(*); end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/dsl.rb new file mode 100644 index 0000000000..da751d1774 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/dsl.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + # Dsl to parse the Gemfile looking for plugins to install + class DSL < Bundler::Dsl + class PluginGemfileError < PluginError; end + alias_method :_gem, :gem # To use for plugin installation as gem + + # So that we don't have to override all there methods to dummy ones + # explicitly. + # They will be handled by method_missing + [:gemspec, :gem, :install_if, :platforms, :env].each {|m| undef_method m } + + # This lists the plugins that was added automatically and not specified by + # the user. + # + # When we encounter :type attribute with a source block, we add a plugin + # by name bundler-source- to list of plugins to be installed. + # + # These plugins are optional and are not installed when there is conflict + # with any other plugin. + attr_reader :inferred_plugins + + def initialize + super + @sources = Plugin::SourceList.new + @inferred_plugins = [] # The source plugins inferred from :type + end + + def plugin(name, *args) + _gem(name, *args) + end + + def method_missing(name, *args) + raise PluginGemfileError, "Undefined local variable or method `#{name}' for Gemfile" unless Bundler::Dsl.method_defined? name + end + + def source(source, *args, &blk) + options = args.last.is_a?(Hash) ? args.pop.dup : {} + options = normalize_hash(options) + return super unless options.key?("type") + + plugin_name = "bundler-source-#{options["type"]}" + + return if @dependencies.any? {|d| d.name == plugin_name } + + plugin(plugin_name) + @inferred_plugins << plugin_name + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/events.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/events.rb new file mode 100644 index 0000000000..bc037d1af5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/events.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + module Events + def self.define(const, event) + const = const.to_sym.freeze + if const_defined?(const) && const_get(const) != event + raise ArgumentError, "Attempting to reassign #{const} to a different value" + end + const_set(const, event) unless const_defined?(const) + @events ||= {} + @events[event] = const + end + private_class_method :define + + def self.reset + @events.each_value do |const| + remove_const(const) + end + @events = nil + end + private_class_method :reset + + # Check if an event has been defined + # @param event [String] An event to check + # @return [Boolean] A boolean indicating if the event has been defined + def self.defined_event?(event) + @events ||= {} + @events.key?(event) + end + + # @!parse + # A hook called before each individual gem is installed + # Includes a Bundler::ParallelInstaller::SpecInstallation. + # No state, error, post_install_message will be present as nothing has installed yet + # GEM_BEFORE_INSTALL = "before-install" + define :GEM_BEFORE_INSTALL, "before-install" + + # @!parse + # A hook called after each individual gem is installed + # Includes a Bundler::ParallelInstaller::SpecInstallation. + # - If state is failed, an error will be present. + # - If state is success, a post_install_message may be present. + # GEM_AFTER_INSTALL = "after-install" + define :GEM_AFTER_INSTALL, "after-install" + + # @!parse + # A hook called before any gems install + # Includes an Array of Bundler::Dependency objects + # GEM_BEFORE_INSTALL_ALL = "before-install-all" + define :GEM_BEFORE_INSTALL_ALL, "before-install-all" + + # @!parse + # A hook called after any gems install + # Includes an Array of Bundler::Dependency objects + # GEM_AFTER_INSTALL_ALL = "after-install-all" + define :GEM_AFTER_INSTALL_ALL, "after-install-all" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/index.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/index.rb new file mode 100644 index 0000000000..819bc60588 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/index.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +module Bundler + # Manages which plugins are installed and their sources. This also is supposed to map + # which plugin does what (currently the features are not implemented so this class is + # now a stub class). + module Plugin + class Index + class CommandConflict < PluginError + def initialize(plugin, commands) + msg = "Command(s) `#{commands.join("`, `")}` declared by #{plugin} are already registered." + super msg + end + end + + class SourceConflict < PluginError + def initialize(plugin, sources) + msg = "Source(s) `#{sources.join("`, `")}` declared by #{plugin} are already registered." + super msg + end + end + + attr_reader :commands + + def initialize + @plugin_paths = {} + @commands = {} + @sources = {} + @hooks = {} + @load_paths = {} + + begin + load_index(global_index_file, true) + rescue GenericSystemCallError + # no need to fail when on a read-only FS, for example + nil + end + load_index(local_index_file) if SharedHelpers.in_bundle? + end + + # This function is to be called when a new plugin is installed. This + # function shall add the functions of the plugin to existing maps and also + # the name to source location. + # + # @param [String] name of the plugin to be registered + # @param [String] path where the plugin is installed + # @param [Array] load_paths for the plugin + # @param [Array] commands that are handled by the plugin + # @param [Array] sources that are handled by the plugin + def register_plugin(name, path, load_paths, commands, sources, hooks) + old_commands = @commands.dup + + common = commands & @commands.keys + raise CommandConflict.new(name, common) unless common.empty? + commands.each {|c| @commands[c] = name } + + common = sources & @sources.keys + raise SourceConflict.new(name, common) unless common.empty? + sources.each {|k| @sources[k] = name } + + hooks.each do |event| + event_hooks = (@hooks[event] ||= []) << name + event_hooks.uniq! + end + + @plugin_paths[name] = path + @load_paths[name] = load_paths + save_index + rescue StandardError + @commands = old_commands + raise + end + + def unregister_plugin(name) + @commands.delete_if {|_, v| v == name } + @sources.delete_if {|_, v| v == name } + @hooks.each {|_, plugin_names| plugin_names.delete(name) } + @plugin_paths.delete(name) + @load_paths.delete(name) + save_index + end + + # Path of default index file + def index_file + Plugin.root.join("index") + end + + # Path where the global index file is stored + def global_index_file + Plugin.global_root.join("index") + end + + # Path where the local index file is stored + def local_index_file + Plugin.local_root.join("index") + end + + def plugin_path(name) + Pathname.new @plugin_paths[name] + end + + def load_paths(name) + @load_paths[name] + end + + # Fetch the name of plugin handling the command + def command_plugin(command) + @commands[command] + end + + def installed?(name) + @plugin_paths[name] + end + + def installed_plugins + @plugin_paths.keys + end + + def plugin_commands(plugin) + @commands.find_all {|_, n| n == plugin }.map(&:first) + end + + def source?(source) + @sources.key? source + end + + def source_plugin(name) + @sources[name] + end + + # Returns the list of plugin names handling the passed event + def hook_plugins(event) + @hooks[event] || [] + end + + private + + # Reads the index file from the directory and initializes the instance + # variables. + # + # It skips the sources if the second param is true + # @param [Pathname] index file path + # @param [Boolean] is the index file global index + def load_index(index_file, global = false) + SharedHelpers.filesystem_access(index_file, :read) do |index_f| + valid_file = index_f && index_f.exist? && !index_f.size.zero? + break unless valid_file + + data = index_f.read + + require_relative "../yaml_serializer" + index = YAMLSerializer.load(data) + + @commands.merge!(index["commands"]) + @hooks.merge!(index["hooks"]) + @load_paths.merge!(index["load_paths"]) + @plugin_paths.merge!(index["plugin_paths"]) + @sources.merge!(index["sources"]) unless global + end + end + + # Should be called when any of the instance variables change. Stores the + # instance variables in YAML format. (The instance variables are supposed + # to be only String key value pairs) + def save_index + index = { + "commands" => @commands, + "hooks" => @hooks, + "load_paths" => @load_paths, + "plugin_paths" => @plugin_paths, + "sources" => @sources, + } + + require_relative "../yaml_serializer" + SharedHelpers.filesystem_access(index_file) do |index_f| + FileUtils.mkdir_p(index_f.dirname) + File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer.rb new file mode 100644 index 0000000000..54ce8528cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module Bundler + # Handles the installation of plugin in appropriate directories. + # + # This class is supposed to be wrapper over the existing gem installation infra + # but currently it itself handles everything as the Source's subclasses (e.g. Source::RubyGems) + # are heavily dependent on the Gemfile. + module Plugin + class Installer + autoload :Rubygems, File.expand_path("installer/rubygems", __dir__) + autoload :Git, File.expand_path("installer/git", __dir__) + + def install(names, options) + check_sources_consistency!(options) + + version = options[:version] || [">= 0"] + + if options[:git] + install_git(names, version, options) + elsif options[:local_git] + install_local_git(names, version, options) + else + sources = options[:source] || Bundler.rubygems.sources + install_rubygems(names, version, sources) + end + end + + # Installs the plugin from Definition object created by limited parsing of + # Gemfile searching for plugins to be installed + # + # @param [Definition] definition object + # @return [Hash] map of names to their specs they are installed with + def install_definition(definition) + def definition.lock(*); end + definition.resolve_remotely! + specs = definition.specs + + install_from_specs specs + end + + private + + def check_sources_consistency!(options) + if options.key?(:git) && options.key?(:local_git) + raise InvalidOption, "Remote and local plugin git sources can't be both specified" + end + end + + def install_git(names, version, options) + uri = options.delete(:git) + options["uri"] = uri + + install_all_sources(names, version, options, options[:source]) + end + + def install_local_git(names, version, options) + uri = options.delete(:local_git) + options["uri"] = uri + + install_all_sources(names, version, options, options[:source]) + end + + # Installs the plugin from rubygems source and returns the path where the + # plugin was installed + # + # @param [String] name of the plugin gem to search in the source + # @param [Array] version of the gem to install + # @param [String, Array] source(s) to resolve the gem + # + # @return [Hash] map of names to the specs of plugins installed + def install_rubygems(names, version, sources) + install_all_sources(names, version, nil, sources) + end + + def install_all_sources(names, version, git_source_options, rubygems_source) + source_list = SourceList.new + + source_list.add_git_source(git_source_options) if git_source_options + Array(rubygems_source).each {|remote| source_list.add_global_rubygems_remote(remote) } if rubygems_source + + deps = names.map {|name| Dependency.new name, version } + + definition = Definition.new(nil, deps, source_list, true) + install_definition(definition) + end + + # Installs the plugins and deps from the provided specs and returns map of + # gems to their paths + # + # @param specs to install + # + # @return [Hash] map of names to the specs + def install_from_specs(specs) + paths = {} + + specs.each do |spec| + spec.source.install spec + + paths[spec.name] = spec + end + + paths + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/git.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/git.rb new file mode 100644 index 0000000000..fbb6c5e40e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/git.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + class Installer + class Git < Bundler::Source::Git + def cache_path + @cache_path ||= begin + git_scope = "#{base_name}-#{uri_hash}" + + Plugin.cache.join("bundler", "git", git_scope) + end + end + + def install_path + @install_path ||= begin + git_scope = "#{base_name}-#{shortref_for_path(revision)}" + + Plugin.root.join("bundler", "gems", git_scope) + end + end + + def version_message(spec) + "#{spec.name} #{spec.version}" + end + + def root + Plugin.root + end + + def generate_bin(spec, disable_extensions = false) + # Need to find a way without code duplication + # For now, we can ignore this + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/rubygems.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/rubygems.rb new file mode 100644 index 0000000000..e144c14b24 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/installer/rubygems.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + class Installer + class Rubygems < Bundler::Source::Rubygems + def version_message(spec) + "#{spec.name} #{spec.version}" + end + + private + + def requires_sudo? + false # Will change on implementation of project level plugins + end + + def rubygems_dir + Plugin.root + end + + def cache_path + Plugin.cache + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/source_list.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/source_list.rb new file mode 100644 index 0000000000..547661cf2f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/plugin/source_list.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Bundler + # SourceList object to be used while parsing the Gemfile, setting the + # approptiate options to be used with Source classes for plugin installation + module Plugin + class SourceList < Bundler::SourceList + def add_git_source(options = {}) + add_source_to_list Plugin::Installer::Git.new(options), git_sources + end + + def add_rubygems_source(options = {}) + add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources + end + + def all_sources + path_sources + git_sources + rubygems_sources + [metadata_source] + end + + def default_source + git_sources.first || global_rubygems_source + end + + private + + def rubygems_aggregate_class + Plugin::Installer::Rubygems + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/process_lock.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/process_lock.rb new file mode 100644 index 0000000000..cba4fcdba5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/process_lock.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Bundler + class ProcessLock + def self.lock(bundle_path = Bundler.bundle_path) + lock_file_path = File.join(bundle_path, "bundler.lock") + has_lock = false + + File.open(lock_file_path, "w") do |f| + f.flock(File::LOCK_EX) + has_lock = true + yield + f.flock(File::LOCK_UN) + end + rescue Errno::EACCES, Errno::ENOLCK, *[SharedHelpers.const_get_safely(:ENOTSUP, Errno)].compact + # In the case the user does not have access to + # create the lock file or is using NFS where + # locks are not available we skip locking. + yield + ensure + FileUtils.rm_f(lock_file_path) if has_lock + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/psyched_yaml.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/psyched_yaml.rb new file mode 100644 index 0000000000..463d52dc4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/psyched_yaml.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Psych could be in the stdlib +# but it's too late if Syck is already loaded +begin + require "psych" unless defined?(Syck) +rescue LoadError + # Apparently Psych wasn't available. Oh well. +end + +# At least load the YAML stdlib, whatever that may be +require "yaml" unless defined?(YAML.dump) + +module Bundler + # On encountering invalid YAML, + # Psych raises Psych::SyntaxError + if defined?(::Psych::SyntaxError) + YamlLibrarySyntaxError = ::Psych::SyntaxError + else # Syck raises ArgumentError + YamlLibrarySyntaxError = ::ArgumentError + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/remote_specification.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/remote_specification.rb new file mode 100644 index 0000000000..89b69e1045 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/remote_specification.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module Bundler + # Represents a lazily loaded gem specification, where the full specification + # is on the source server in rubygems' "quick" index. The proxy object is to + # be seeded with what we're given from the source's abbreviated index - the + # full specification will only be fetched when necessary. + class RemoteSpecification + include MatchPlatform + include Comparable + + attr_reader :name, :version, :platform + attr_writer :dependencies + attr_accessor :source, :remote + + def initialize(name, version, platform, spec_fetcher) + @name = name + @version = Gem::Version.create version + @platform = platform + @spec_fetcher = spec_fetcher + @dependencies = nil + end + + # Needed before installs, since the arch matters then and quick + # specs don't bother to include the arch in the platform string + def fetch_platform + @platform = _remote_specification.platform + end + + def full_name + if platform == Gem::Platform::RUBY || platform.nil? + "#{@name}-#{@version}" + else + "#{@name}-#{@version}-#{platform}" + end + end + + # Compare this specification against another object. Using sort_obj + # is compatible with Gem::Specification and other Bundler or RubyGems + # objects. Otherwise, use the default Object comparison. + def <=>(other) + if other.respond_to?(:sort_obj) + sort_obj <=> other.sort_obj + else + super + end + end + + # Because Rubyforge cannot be trusted to provide valid specifications + # once the remote gem is downloaded, the backend specification will + # be swapped out. + def __swap__(spec) + raise APIResponseInvalidDependenciesError unless spec.dependencies.all? {|d| d.is_a?(Gem::Dependency) } + + SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies) + @_remote_specification = spec + end + + # Create a delegate used for sorting. This strategy is copied from + # RubyGems 2.23 and ensures that Bundler's specifications can be + # compared and sorted with RubyGems' own specifications. + # + # @see #<=> + # @see Gem::Specification#sort_obj + # + # @return [Array] an object you can use to compare and sort this + # specification against other specifications + def sort_obj + [@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1] + end + + def to_s + "#<#{self.class} name=#{name} version=#{version} platform=#{platform}>" + end + + def dependencies + @dependencies ||= begin + deps = method_missing(:dependencies) + + # allow us to handle when the specs dependencies are an array of array of string + # in order to delay the crash to `#__swap__` where it results in a friendlier error + # see https://github.com/rubygems/bundler/issues/5797 + deps = deps.map {|d| d.is_a?(Gem::Dependency) ? d : Gem::Dependency.new(*d) } + + deps + end + end + + def git_version + return unless loaded_from && source.is_a?(Bundler::Source::Git) + " #{source.revision[0..6]}" + end + + private + + def to_ary + nil + end + + def _remote_specification + @_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform]) + @_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \ + " missing from the server! Try installing with `--full-index` as a workaround.") + end + + def method_missing(method, *args, &blk) + _remote_specification.send(method, *args, &blk) + end + + def respond_to?(method, include_all = false) + super || _remote_specification.respond_to?(method, include_all) + end + public :respond_to? + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver.rb new file mode 100644 index 0000000000..a78b2db157 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver.rb @@ -0,0 +1,396 @@ +# frozen_string_literal: true + +module Bundler + class Resolver + require_relative "vendored_molinillo" + require_relative "resolver/spec_group" + + include GemHelpers + + # Figures out the best possible configuration of gems that satisfies + # the list of passed dependencies and any child dependencies without + # causing any gem activation errors. + # + # ==== Parameters + # *dependencies:: The list of dependencies to resolve + # + # ==== Returns + # ,nil:: If the list of dependencies can be resolved, a + # collection of gemspecs is returned. Otherwise, nil is returned. + def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) + base = SpecSet.new(base) unless base.is_a?(SpecSet) + resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) + result = resolver.start(requirements) + SpecSet.new(SpecSet.new(result).for(requirements.reject{|dep| dep.name.end_with?("\0") })) + end + + def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) + @source_requirements = source_requirements + @base = base + @resolver = Molinillo::Resolver.new(self, self) + @search_for = {} + @base_dg = Molinillo::DependencyGraph.new + aggregate_global_source = @source_requirements[:default].is_a?(Source::RubygemsAggregate) + @base.each do |ls| + dep = Dependency.new(ls.name, ls.version) + ls.source = source_for(ls.name) unless aggregate_global_source + @base_dg.add_vertex(ls.name, DepProxy.get_proxy(dep, ls.platform), true) + end + additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } + @platforms = platforms.reject {|p| p != Gem::Platform::RUBY && (platforms - [p]).any? {|pl| generic(pl) == p } } + @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY] + @gem_version_promoter = gem_version_promoter + @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major? + end + + def start(requirements) + @gem_version_promoter.prerelease_specified = @prerelease_specified = {} + requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? } + + verify_gemfile_dependencies_are_found!(requirements) + dg = @resolver.resolve(requirements, @base_dg) + dg. + map(&:payload). + reject {|sg| sg.name.end_with?("\0") }. + map(&:to_specs). + flatten + rescue Molinillo::VersionConflict => e + message = version_conflict_message(e) + raise VersionConflict.new(e.conflicts.keys.uniq, message) + rescue Molinillo::CircularDependencyError => e + names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" } + raise CyclicDependencyError, "Your bundle requires gems that depend" \ + " on each other, creating an infinite loop. Please remove" \ + " #{names.count > 1 ? "either " : ""}#{names.join(" or ")}" \ + " and try again." + end + + include Molinillo::UI + + # Conveys debug information to the user. + # + # @param [Integer] depth the current depth of the resolution process. + # @return [void] + def debug(depth = 0) + return unless debug? + debug_info = yield + debug_info = debug_info.inspect unless debug_info.is_a?(String) + puts debug_info.split("\n").map {|s| depth == 0 ? "BUNDLER: #{s}" : "BUNDLER(#{depth}): #{s}" } + end + + def debug? + return @debug_mode if defined?(@debug_mode) + @debug_mode = + ENV["BUNDLER_DEBUG_RESOLVER"] || + ENV["BUNDLER_DEBUG_RESOLVER_TREE"] || + ENV["DEBUG_RESOLVER"] || + ENV["DEBUG_RESOLVER_TREE"] || + false + end + + def before_resolution + Bundler.ui.info "Resolving dependencies...", debug? + end + + def after_resolution + Bundler.ui.info "" + end + + def indicate_progress + Bundler.ui.info ".", false unless debug? + end + + include Molinillo::SpecificationProvider + + def dependencies_for(specification) + specification.dependencies_for_activated_platforms + end + + def search_for(dependency_proxy) + platform = dependency_proxy.__platform + dependency = dependency_proxy.dep + name = dependency.name + @search_for[dependency_proxy] ||= begin + results = results_for(dependency, @base[name]) + + if vertex = @base_dg.vertex_named(name) + locked_requirement = vertex.payload.requirement + end + + if !@prerelease_specified[name] && (!@use_gvp || locked_requirement.nil?) + # Move prereleases to the beginning of the list, so they're considered + # last during resolution. + pre, results = results.partition {|spec| spec.version.prerelease? } + results = pre + results + end + + spec_groups = if results.any? + nested = [] + results.each do |spec| + version, specs = nested.last + if version == spec.version + specs << spec + else + nested << [spec.version, [spec]] + end + end + nested.reduce([]) do |groups, (version, specs)| + next groups if locked_requirement && !locked_requirement.satisfied_by?(version) + + specs_by_platform = Hash.new do |current_specs, current_platform| + current_specs[current_platform] = select_best_platform_match(specs, current_platform) + end + + spec_group_ruby = SpecGroup.create_for(specs_by_platform, [Gem::Platform::RUBY], Gem::Platform::RUBY) + groups << spec_group_ruby if spec_group_ruby + + next groups if @resolving_only_for_ruby + + spec_group = SpecGroup.create_for(specs_by_platform, @platforms, platform) + groups << spec_group if spec_group + + groups + end + else + [] + end + # GVP handles major itself, but it's still a bit risky to trust it with it + # until we get it settled with new behavior. For 2.x it can take over all cases. + if !@use_gvp + spec_groups + else + @gem_version_promoter.sort_versions(dependency, spec_groups) + end + end + end + + def index_for(dependency) + source_for(dependency.name).specs + end + + def source_for(name) + @source_requirements[name] || @source_requirements[:default] + end + + def results_for(dependency, base) + index_for(dependency).search(dependency, base) + end + + def name_for(dependency) + dependency.name + end + + def name_for_explicit_dependency_source + Bundler.default_gemfile.basename.to_s + rescue StandardError + "Gemfile" + end + + def name_for_locking_dependency_source + Bundler.default_lockfile.basename.to_s + rescue StandardError + "Gemfile.lock" + end + + def requirement_satisfied_by?(requirement, activated, spec) + requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) + end + + def dependencies_equal?(dependencies, other_dependencies) + dependencies.map(&:dep) == other_dependencies.map(&:dep) + end + + def sort_dependencies(dependencies, activated, conflicts) + dependencies.sort_by do |dependency| + name = name_for(dependency) + vertex = activated.vertex_named(name) + [ + @base_dg.vertex_named(name) ? 0 : 1, + vertex.payload ? 0 : 1, + vertex.root? ? 0 : 1, + amount_constrained(dependency), + conflicts[name] ? 0 : 1, + vertex.payload ? 0 : search_for(dependency).count, + self.class.platform_sort_key(dependency.__platform), + ] + end + end + + def self.platform_sort_key(platform) + # Prefer specific platform to not specific platform + return ["99-LAST", "", "", ""] if Gem::Platform::RUBY == platform + ["00", *platform.to_a.map {|part| part || "" }] + end + + private + + # returns an integer \in (-\infty, 0] + # a number closer to 0 means the dependency is less constraining + # + # dependencies w/ 0 or 1 possibilities (ignoring version requirements) + # are given very negative values, so they _always_ sort first, + # before dependencies that are unconstrained + def amount_constrained(dependency) + @amount_constrained ||= {} + @amount_constrained[dependency.name] ||= begin + if (base = @base[dependency.name]) && !base.empty? + dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1 + else + all = index_for(dependency).search(dependency.name).size + + if all <= 1 + all - 1_000_000 + else + search = search_for(dependency) + search = @prerelease_specified[dependency.name] ? search.count : search.count {|s| !s.version.prerelease? } + search - all + end + end + end + end + + def verify_gemfile_dependencies_are_found!(requirements) + requirements.each do |requirement| + name = requirement.name + next if name == "bundler" + next unless search_for(requirement).empty? + + cache_message = begin + " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? + rescue GemfileNotFound + nil + end + + if (base = @base[name]) && !base.empty? + version = base.first.version + message = "You have requested:\n" \ + " #{name} #{requirement.requirement}\n\n" \ + "The bundle currently has #{name} locked at #{version}.\n" \ + "Try running `bundle update #{name}`\n\n" \ + "If you are updating multiple gems in your Gemfile at once,\n" \ + "try passing them all to `bundle update`" + elsif source = @source_requirements[name] + specs = source.specs.search(name) + versions_with_platforms = specs.map {|s| [s.version, s.platform] } + message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n") + message << if versions_with_platforms.any? + "The source contains the following versions of '#{name}': #{formatted_versions_with_platforms(versions_with_platforms)}" + else + "The source does not contain any versions of '#{name}'" + end + else + message = "Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in any of the gem sources " \ + "listed in your Gemfile#{cache_message}." + end + raise GemNotFound, message + end + end + + def formatted_versions_with_platforms(versions_with_platforms) + version_platform_strs = versions_with_platforms.map do |vwp| + version = vwp.first + platform = vwp.last + version_platform_str = String.new(version.to_s) + version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY + version_platform_str + end + version_platform_strs.join(", ") + end + + def version_conflict_message(e) + # only show essential conflicts, if possible + conflicts = e.conflicts.dup + + if conflicts["bundler"] + conflicts.replace("bundler" => conflicts["bundler"]) + else + conflicts.delete_if do |_name, conflict| + deps = conflict.requirement_trees.map(&:last).flatten(1) + !Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) + end + end + + e = Molinillo::VersionConflict.new(conflicts, e.specification_provider) unless conflicts.empty? + + solver_name = "Bundler" + possibility_type = "gem" + e.message_with_trees( + :solver_name => solver_name, + :possibility_type => possibility_type, + :reduce_trees => lambda do |trees| + # called first, because we want to reduce the amount of work required to find maximal empty sets + trees = trees.uniq {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } } + + # bail out if tree size is too big for Array#combination to make any sense + return trees if trees.size > 15 + maximal = 1.upto(trees.size).map do |size| + trees.map(&:last).flatten(1).combination(size).to_a + end.flatten(1).select do |deps| + Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) + end.min_by(&:size) + + trees.reject! {|t| !maximal.include?(t.last) } if maximal + + trees.sort_by {|t| t.reverse.map(&:name) } + end, + :printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) }, + :additional_message_for_conflict => lambda do |o, name, conflict| + if name == "bundler" + o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION})) + + conflict_dependency = conflict.requirement + conflict_requirement = conflict_dependency.requirement + other_bundler_required = !conflict_requirement.satisfied_by?(Gem::Version.new(Bundler::VERSION)) + + if other_bundler_required + o << "\n\n" + + candidate_specs = source_for(:default_bundler).specs.search(conflict_dependency) + if candidate_specs.any? + target_version = candidate_specs.last.version + new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ") + o << "Your bundle requires a different version of Bundler than the one you're running.\n" + o << "Install the necessary version with `gem install bundler:#{target_version}` and rerun bundler using `#{new_command}`\n" + else + o << "Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.\n" + end + end + elsif conflict.locked_requirement + o << "\n" + o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n) + o << %(the gems in your Gemfile, which may resolve the conflict.\n) + elsif !conflict.existing + o << "\n" + + relevant_source = conflict.requirement.source || source_for(name) + + metadata_requirement = name.end_with?("\0") + + o << "Could not find gem '" unless metadata_requirement + o << SharedHelpers.pretty_dependency(conflict.requirement) + o << "'" unless metadata_requirement + if conflict.requirement_trees.first.size > 1 + o << ", which is required by " + o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}'," + end + o << " " + + o << if metadata_requirement + "is not available in #{relevant_source}" + else + "in #{relevant_source}.\n" + end + end + end, + :version_for_spec => lambda {|spec| spec.version }, + :incompatible_version_message_for_conflict => lambda do |name, _conflict| + if name.end_with?("\0") + %(#{solver_name} found conflicting requirements for the #{name} version:) + else + %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) + end + end + ) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver/spec_group.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver/spec_group.rb new file mode 100644 index 0000000000..8f4fd18c46 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/resolver/spec_group.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module Bundler + class Resolver + class SpecGroup + attr_accessor :name, :version, :source + attr_accessor :activated_platforms + + def self.create_for(specs, all_platforms, specific_platform) + specific_platform_specs = specs[specific_platform] + return unless specific_platform_specs.any? + + platforms = all_platforms.select {|p| specs[p].any? } + + new(specific_platform_specs.first, specs, platforms) + end + + def initialize(exemplary_spec, specs, relevant_platforms) + @exemplary_spec = exemplary_spec + @name = exemplary_spec.name + @version = exemplary_spec.version + @source = exemplary_spec.source + + @activated_platforms = relevant_platforms + @dependencies = Hash.new do |dependencies, platforms| + dependencies[platforms] = dependencies_for(platforms) + end + @specs = specs + end + + def to_specs + activated_platforms.map do |p| + specs = @specs[p] + next unless specs.any? + + specs.map do |s| + lazy_spec = LazySpecification.new(name, version, s.platform, source) + lazy_spec.dependencies.replace s.dependencies + lazy_spec + end + end.flatten.compact.uniq + end + + def to_s + activated_platforms_string = sorted_activated_platforms.join(", ") + "#{name} (#{version}) (#{activated_platforms_string})" + end + + def dependencies_for_activated_platforms + @dependencies[activated_platforms] + end + + def ==(other) + return unless other.is_a?(SpecGroup) + name == other.name && + version == other.version && + sorted_activated_platforms == other.sorted_activated_platforms && + source == other.source + end + + def eql?(other) + return unless other.is_a?(SpecGroup) + name.eql?(other.name) && + version.eql?(other.version) && + sorted_activated_platforms.eql?(other.sorted_activated_platforms) && + source.eql?(other.source) + end + + def hash + name.hash ^ version.hash ^ sorted_activated_platforms.hash ^ source.hash + end + + protected + + def sorted_activated_platforms + activated_platforms.sort_by(&:to_s) + end + + private + + def dependencies_for(platforms) + platforms.map do |platform| + __dependencies(platform) + metadata_dependencies(platform) + end.flatten + end + + def __dependencies(platform) + dependencies = [] + @specs[platform].first.dependencies.each do |dep| + next if dep.type == :development + dependencies << DepProxy.get_proxy(dep, platform) + end + dependencies + end + + def metadata_dependencies(platform) + spec = @specs[platform].first + return [] unless spec.is_a?(Gem::Specification) + dependencies = [] + if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none? + dependencies << DepProxy.get_proxy(Gem::Dependency.new("Ruby\0", spec.required_ruby_version), platform) + end + if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none? + dependencies << DepProxy.get_proxy(Gem::Dependency.new("RubyGems\0", spec.required_rubygems_version), platform) + end + dependencies + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/retry.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/retry.rb new file mode 100644 index 0000000000..2415ade200 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/retry.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Bundler + # General purpose class for retrying code that may fail + class Retry + attr_accessor :name, :total_runs, :current_run + + class << self + def default_attempts + default_retries + 1 + end + alias_method :attempts, :default_attempts + + def default_retries + Bundler.settings[:retry] + end + end + + def initialize(name, exceptions = nil, retries = self.class.default_retries) + @name = name + @retries = retries + @exceptions = Array(exceptions) || [] + @total_runs = @retries + 1 # will run once, then upto attempts.times + end + + def attempt(&block) + @current_run = 0 + @failed = false + @error = nil + run(&block) while keep_trying? + @result + end + alias_method :attempts, :attempt + + private + + def run(&block) + @failed = false + @current_run += 1 + @result = block.call + rescue StandardError => e + fail_attempt(e) + end + + def fail_attempt(e) + @failed = true + if last_attempt? || @exceptions.any? {|k| e.is_a?(k) } + Bundler.ui.info "" unless Bundler.ui.debug? + raise e + end + return true unless name + Bundler.ui.info "" unless Bundler.ui.debug? # Add new line in case dots preceded this + Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", Bundler.ui.debug? + end + + def keep_trying? + return true if current_run.zero? + return false if last_attempt? + return true if @failed + end + + def last_attempt? + current_run >= total_runs + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_dsl.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_dsl.rb new file mode 100644 index 0000000000..f6ba220cd5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_dsl.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + module RubyDsl + def ruby(*ruby_version) + options = ruby_version.last.is_a?(Hash) ? ruby_version.pop : {} + ruby_version.flatten! + raise GemfileError, "Please define :engine_version" if options[:engine] && options[:engine_version].nil? + raise GemfileError, "Please define :engine" if options[:engine_version] && options[:engine].nil? + + if options[:engine] == "ruby" && options[:engine_version] && + ruby_version != Array(options[:engine_version]) + raise GemfileEvalError, "ruby_version must match the :engine_version for MRI" + end + @ruby_version = RubyVersion.new(ruby_version, options[:patchlevel], options[:engine], options[:engine_version]) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_version.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_version.rb new file mode 100644 index 0000000000..491f8c55a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ruby_version.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module Bundler + class RubyVersion + attr_reader :versions, + :patchlevel, + :engine, + :engine_versions, + :gem_version, + :engine_gem_version + + def initialize(versions, patchlevel, engine, engine_version) + # The parameters to this method must satisfy the + # following constraints, which are verified in + # the DSL: + # + # * If an engine is specified, an engine version + # must also be specified + # * If an engine version is specified, an engine + # must also be specified + # * If the engine is "ruby", the engine version + # must not be specified, or the engine version + # specified must match the version. + + @versions = Array(versions).map do |v| + op, v = Gem::Requirement.parse(v) + op == "=" ? v.to_s : "#{op} #{v}" + end + + @gem_version = Gem::Requirement.create(@versions.first).requirements.first.last + @input_engine = engine && engine.to_s + @engine = engine && engine.to_s || "ruby" + @engine_versions = (engine_version && Array(engine_version)) || @versions + @engine_gem_version = Gem::Requirement.create(@engine_versions.first).requirements.first.last + @patchlevel = patchlevel + end + + def to_s(versions = self.versions) + output = String.new("ruby #{versions_string(versions)}") + output << "p#{patchlevel}" if patchlevel + output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby" + + output + end + + # @private + PATTERN = / + ruby\s + ([\d.]+) # ruby version + (?:p(-?\d+))? # optional patchlevel + (?:\s\((\S+)\s(.+)\))? # optional engine info + /xo.freeze + + # Returns a RubyVersion from the given string. + # @param [String] the version string to match. + # @return [RubyVersion,Nil] The version if the string is a valid RubyVersion + # description, and nil otherwise. + def self.from_string(string) + new($1, $2, $3, $4) if string =~ PATTERN + end + + def single_version_string + to_s(gem_version) + end + + def ==(other) + versions == other.versions && + engine == other.engine && + engine_versions == other.engine_versions && + patchlevel == other.patchlevel + end + + def host + @host ||= [ + RbConfig::CONFIG["host_cpu"], + RbConfig::CONFIG["host_vendor"], + RbConfig::CONFIG["host_os"], + ].join("-") + end + + # Returns a tuple of these things: + # [diff, this, other] + # The priority of attributes are + # 1. engine + # 2. ruby_version + # 3. engine_version + def diff(other) + raise ArgumentError, "Can only diff with a RubyVersion, not a #{other.class}" unless other.is_a?(RubyVersion) + if engine != other.engine && @input_engine + [:engine, engine, other.engine] + elsif versions.empty? || !matches?(versions, other.gem_version) + [:version, versions_string(versions), versions_string(other.versions)] + elsif @input_engine && !matches?(engine_versions, other.engine_gem_version) + [:engine_version, versions_string(engine_versions), versions_string(other.engine_versions)] + elsif patchlevel && (!patchlevel.is_a?(String) || !other.patchlevel.is_a?(String) || !matches?(patchlevel, other.patchlevel)) + [:patchlevel, patchlevel, other.patchlevel] + end + end + + def versions_string(versions) + Array(versions).join(", ") + end + + def self.system + ruby_engine = RUBY_ENGINE.dup + ruby_version = ENV.fetch("BUNDLER_SPEC_RUBY_VERSION") { RUBY_VERSION }.dup + ruby_engine_version = RUBY_ENGINE_VERSION.dup + patchlevel = RUBY_PATCHLEVEL.to_s + + @ruby_version ||= RubyVersion.new(ruby_version, patchlevel, ruby_engine, ruby_engine_version) + end + + def to_gem_version_with_patchlevel + @gem_version_with_patch ||= begin + Gem::Version.create("#{@gem_version}.#{@patchlevel}") + rescue ArgumentError + @gem_version + end + end + + def exact? + return @exact if defined?(@exact) + @exact = versions.all? {|v| Gem::Requirement.create(v).exact? } + end + + private + + def matches?(requirements, version) + # Handles RUBY_PATCHLEVEL of -1 for instances like ruby-head + return requirements == version if requirements.to_s == "-1" || version.to_s == "-1" + + Array(requirements).all? do |requirement| + Gem::Requirement.create(requirement).satisfied_by?(Gem::Version.create(version)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_ext.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_ext.rb new file mode 100644 index 0000000000..a59ffe2dec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_ext.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +require "pathname" + +require "rubygems/specification" + +# Possible use in Gem::Specification#source below and require +# shouldn't be deferred. +require "rubygems/source" + +require_relative "match_platform" + +module Gem + class Specification + attr_accessor :remote, :location, :relative_loaded_from + + remove_method :source + attr_writer :source + def source + (defined?(@source) && @source) || Gem::Source::Installed.new + end + + alias_method :rg_full_gem_path, :full_gem_path + alias_method :rg_loaded_from, :loaded_from + + def full_gem_path + # this cannot check source.is_a?(Bundler::Plugin::API::Source) + # because that _could_ trip the autoload, and if there are unresolved + # gems at that time, this method could be called inside another require, + # thus raising with that constant being undefined. Better to check a method + if source.respond_to?(:path) || (source.respond_to?(:bundler_plugin_api_source?) && source.bundler_plugin_api_source?) + Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.tap{|x| x.untaint if RUBY_VERSION < "2.7" } + else + rg_full_gem_path + end + end + + def loaded_from + if relative_loaded_from + source.path.join(relative_loaded_from).to_s + else + rg_loaded_from + end + end + + def load_paths + full_require_paths + end + + alias_method :rg_extension_dir, :extension_dir + def extension_dir + @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name) + unique_extension_dir = [source.extension_dir_name, File.basename(full_gem_path)].uniq.join("-") + File.expand_path(File.join(extensions_dir, unique_extension_dir)) + else + rg_extension_dir + end + end + + remove_method :gem_dir if instance_methods(false).include?(:gem_dir) + def gem_dir + full_gem_path + end + + def groups + @groups ||= [] + end + + def git_version + return unless loaded_from && source.is_a?(Bundler::Source::Git) + " #{source.revision[0..6]}" + end + + def to_gemfile(path = nil) + gemfile = String.new("source 'https://rubygems.org'\n") + gemfile << dependencies_to_gemfile(nondevelopment_dependencies) + unless development_dependencies.empty? + gemfile << "\n" + gemfile << dependencies_to_gemfile(development_dependencies, :development) + end + gemfile + end + + def nondevelopment_dependencies + dependencies - development_dependencies + end + + private + + def dependencies_to_gemfile(dependencies, group = nil) + gemfile = String.new + if dependencies.any? + gemfile << "group :#{group} do\n" if group + dependencies.each do |dependency| + gemfile << " " if group + gemfile << %(gem "#{dependency.name}") + req = dependency.requirements_list.first + gemfile << %(, "#{req}") if req + gemfile << "\n" + end + gemfile << "end\n" if group + end + gemfile + end + end + + class Dependency + attr_accessor :source, :groups + + alias_method :eql?, :== + + def encode_with(coder) + to_yaml_properties.each do |ivar| + coder[ivar.to_s.sub(/^@/, "")] = instance_variable_get(ivar) + end + end + + def to_yaml_properties + instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) } + end + + def to_lock + out = String.new(" #{name}") + unless requirement.none? + reqs = requirement.requirements.map {|o, v| "#{o} #{v}" }.sort.reverse + out << " (#{reqs.join(", ")})" + end + out + end + end + + # comparison is done order independently since rubygems 3.2.0.rc.2 + unless Gem::Requirement.new("> 1", "< 2") == Gem::Requirement.new("< 2", "> 1") + class Requirement + module OrderIndependentComparison + def ==(other) + if _requirements_sorted? && other._requirements_sorted? + super + else + _with_sorted_requirements == other._with_sorted_requirements + end + end + + protected + + def _requirements_sorted? + return @_are_requirements_sorted if defined?(@_are_requirements_sorted) + strings = as_list + @_are_requirements_sorted = strings == strings.sort + end + + def _with_sorted_requirements + @_with_sorted_requirements ||= _requirements_sorted? ? self : self.class.new(as_list.sort) + end + end + + prepend OrderIndependentComparison + end + end + + if Gem::Requirement.new("~> 2.0").hash == Gem::Requirement.new("~> 2.0.0").hash + class Requirement + module CorrectHashForLambdaOperator + def hash + if requirements.any? {|r| r.first == "~>" } + requirements.map {|r| r.first == "~>" ? [r[0], r[1].to_s] : r }.sort.hash + else + super + end + end + end + + prepend CorrectHashForLambdaOperator + end + end + + require "rubygems/platform" + + class Platform + JAVA = Gem::Platform.new("java") unless defined?(JAVA) + MSWIN = Gem::Platform.new("mswin32") unless defined?(MSWIN) + MSWIN64 = Gem::Platform.new("mswin64") unless defined?(MSWIN64) + MINGW = Gem::Platform.new("x86-mingw32") unless defined?(MINGW) + X64_MINGW = Gem::Platform.new("x64-mingw32") unless defined?(X64_MINGW) + end + + Platform.singleton_class.module_eval do + unless Platform.singleton_methods.include?(:match_spec?) + def match_spec?(spec) + match_gem?(spec.platform, spec.name) + end + + def match_gem?(platform, gem_name) + match_platforms?(platform, Gem.platforms) + end + + private + + def match_platforms?(platform, platforms) + platforms.any? do |local_platform| + platform.nil? || + local_platform == platform || + (local_platform != Gem::Platform::RUBY && local_platform =~ platform) + end + end + end + end + + require "rubygems/util" + + Util.singleton_class.module_eval do + if Util.singleton_methods.include?(:glob_files_in_dir) # since 3.0.0.beta.2 + remove_method :glob_files_in_dir + end + + def glob_files_in_dir(glob, base_path) + if RUBY_VERSION >= "2.5" + Dir.glob(glob, :base => base_path).map! {|f| File.expand_path(f, base_path) } + else + Dir.glob(File.join(base_path.to_s.gsub(/[\[\]]/, '\\\\\\&'), glob)).map! {|f| File.expand_path(f) } + end + end + end +end + +module Gem + class Specification + include ::Bundler::MatchPlatform + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_gem_installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_gem_installer.rb new file mode 100644 index 0000000000..f5f3c53309 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_gem_installer.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "rubygems/installer" + +module Bundler + class RubyGemsGemInstaller < Gem::Installer + def check_executable_overwrite(filename) + # Bundler needs to install gems regardless of binstub overwriting + end + + def install + pre_install_checks + + run_pre_install_hooks + + spec.loaded_from = spec_file + + # Completely remove any previous gem files + FileUtils.rm_rf gem_dir + FileUtils.rm_rf spec.extension_dir + + FileUtils.mkdir_p gem_dir, :mode => 0o755 + + extract_files + + build_extensions + write_build_info_file + run_post_build_hooks + + generate_bin + generate_plugins + + write_spec + write_cache_file + + say spec.post_install_message unless spec.post_install_message.nil? + + run_post_install_hooks + + spec + end + + def generate_plugins + return unless Gem::Installer.instance_methods(false).include?(:generate_plugins) + + latest = Gem::Specification.stubs_for(spec.name).first + return if latest && latest.version > spec.version + + ensure_writable_dir @plugins_dir + + if spec.plugins.empty? + remove_plugins_for(spec, @plugins_dir) + else + regenerate_plugins_for(spec, @plugins_dir) + end + end + + def pre_install_checks + super && validate_bundler_checksum(options[:bundler_expected_checksum]) + end + + def build_extensions + extension_cache_path = options[:bundler_extension_cache_path] + return super unless extension_cache_path && extension_dir = spec.extension_dir + + extension_dir = Pathname.new(extension_dir) + build_complete = SharedHelpers.filesystem_access(extension_cache_path.join("gem.build_complete"), :read, &:file?) + if build_complete && !options[:force] + SharedHelpers.filesystem_access(extension_dir.parent, &:mkpath) + SharedHelpers.filesystem_access(extension_cache_path) do + FileUtils.cp_r extension_cache_path, spec.extension_dir + end + else + super + if extension_dir.directory? # not made for gems without extensions + SharedHelpers.filesystem_access(extension_cache_path.parent, &:mkpath) + SharedHelpers.filesystem_access(extension_cache_path) do + FileUtils.cp_r extension_dir, extension_cache_path + end + end + end + end + + private + + def validate_bundler_checksum(checksum) + return true if Bundler.settings[:disable_checksum_validation] + return true unless checksum + return true unless source = @package.instance_variable_get(:@gem) + return true unless source.respond_to?(:with_read_io) + digest = source.with_read_io do |io| + digest = SharedHelpers.digest(:SHA256).new + digest << io.read(16_384) until io.eof? + io.rewind + send(checksum_type(checksum), digest) + end + unless digest == checksum + raise SecurityError, <<-MESSAGE + Bundler cannot continue installing #{spec.name} (#{spec.version}). + The checksum for the downloaded `#{spec.full_name}.gem` does not match \ + the checksum given by the server. This means the contents of the downloaded \ + gem is different from what was uploaded to the server, and could be a potential security issue. + + To resolve this issue: + 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem` + 2. run `bundle install` + + If you wish to continue installing the downloaded gem, and are certain it does not pose a \ + security issue despite the mismatching checksum, do the following: + 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification + 2. run `bundle install` + + (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \ + checksum for the downloaded gem was #{digest.inspect}.) + MESSAGE + end + true + end + + def checksum_type(checksum) + case checksum.length + when 64 then :hexdigest! + when 44 then :base64digest! + else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest" + end + end + + def hexdigest!(digest) + digest.hexdigest! + end + + def base64digest!(digest) + if digest.respond_to?(:base64digest!) + digest.base64digest! + else + [digest.digest!].pack("m0") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_integration.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_integration.rb new file mode 100644 index 0000000000..21ce12ecda --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/rubygems_integration.rb @@ -0,0 +1,614 @@ +# frozen_string_literal: true + +require "rubygems" unless defined?(Gem) + +module Bundler + class RubygemsIntegration + if defined?(Gem::Ext::Builder::CHDIR_MONITOR) + EXT_LOCK = Gem::Ext::Builder::CHDIR_MONITOR + else + require "monitor" + + EXT_LOCK = Monitor.new + end + + def self.version + @version ||= Gem::Version.new(Gem::VERSION) + end + + def self.provides?(req_str) + Gem::Requirement.new(req_str).satisfied_by?(version) + end + + def initialize + @replaced_methods = {} + backport_ext_builder_monitor + end + + def version + self.class.version + end + + def provides?(req_str) + self.class.provides?(req_str) + end + + def build_args + Gem::Command.build_args + end + + def build_args=(args) + Gem::Command.build_args = args + end + + def loaded_specs(name) + Gem.loaded_specs[name] + end + + def add_to_load_path(paths) + return Gem.add_to_load_path(*paths) if Gem.respond_to?(:add_to_load_path) + + if insert_index = Gem.load_path_insert_index + # Gem directories must come after -I and ENV['RUBYLIB'] + $LOAD_PATH.insert(insert_index, *paths) + else + # We are probably testing in core, -I and RUBYLIB don't apply + $LOAD_PATH.unshift(*paths) + end + end + + def mark_loaded(spec) + if spec.respond_to?(:activated=) + current = Gem.loaded_specs[spec.name] + current.activated = false if current + spec.activated = true + end + Gem.loaded_specs[spec.name] = spec + end + + def validate(spec) + Bundler.ui.silence { spec.validate(false) } + rescue Gem::InvalidSpecificationException => e + error_message = "The gemspec at #{spec.loaded_from} is not valid. Please fix this gemspec.\n" \ + "The validation error was '#{e.message}'\n" + raise Gem::InvalidSpecificationException.new(error_message) + rescue Errno::ENOENT + nil + end + + def set_installed_by_version(spec, installed_by_version = Gem::VERSION) + return unless spec.respond_to?(:installed_by_version=) + spec.installed_by_version = Gem::Version.create(installed_by_version) + end + + def spec_missing_extensions?(spec, default = true) + return spec.missing_extensions? if spec.respond_to?(:missing_extensions?) + + return false if spec_default_gem?(spec) + return false if spec.extensions.empty? + + default + end + + def spec_default_gem?(spec) + spec.respond_to?(:default_gem?) && spec.default_gem? + end + + def spec_matches_for_glob(spec, glob) + return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob) + + spec.load_paths.map do |lp| + Dir["#{lp}/#{glob}#{suffix_pattern}"] + end.flatten(1) + end + + def stub_set_spec(stub, spec) + stub.instance_variable_set(:@spec, spec) + end + + def path(obj) + obj.to_s + end + + def configuration + require_relative "psyched_yaml" + Gem.configuration + rescue Gem::SystemExitException, LoadError => e + Bundler.ui.error "#{e.class}: #{e.message}" + Bundler.ui.trace e + raise + rescue YamlLibrarySyntaxError => e + raise YamlSyntaxError.new(e, "Your RubyGems configuration, which is " \ + "usually located in ~/.gemrc, contains invalid YAML syntax.") + end + + def ruby_engine + Gem.ruby_engine + end + + def read_binary(path) + Gem.read_binary(path) + end + + def inflate(obj) + Gem::Util.inflate(obj) + end + + def correct_for_windows_path(path) + if Gem::Util.respond_to?(:correct_for_windows_path) + Gem::Util.correct_for_windows_path(path) + elsif path[0].chr == "/" && path[1].chr =~ /[a-z]/i && path[2].chr == ":" + path[1..-1] + else + path + end + end + + def sources=(val) + # Gem.configuration creates a new Gem::ConfigFile, which by default will read ~/.gemrc + # If that file exists, its settings (including sources) will overwrite the values we + # are about to set here. In order to avoid that, we force memoizing the config file now. + configuration + + Gem.sources = val + end + + def sources + Gem.sources + end + + def gem_dir + Gem.dir + end + + def gem_bindir + Gem.bindir + end + + def user_home + Gem.user_home + end + + def gem_path + Gem.path + end + + def reset + Gem::Specification.reset + end + + def post_reset_hooks + Gem.post_reset_hooks + end + + def suffix_pattern + Gem.suffix_pattern + end + + def gem_cache + gem_path.map {|p| File.expand_path("cache", p) } + end + + def spec_cache_dirs + @spec_cache_dirs ||= begin + dirs = gem_path.map {|dir| File.join(dir, "specifications") } + dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in RubyGems 2.0.3 or earlier + dirs.uniq.select {|dir| File.directory? dir } + end + end + + def marshal_spec_dir + Gem::MARSHAL_SPEC_DIR + end + + def clear_paths + Gem.clear_paths + end + + def bin_path(gem, bin, ver) + Gem.bin_path(gem, bin, ver) + end + + def loaded_gem_paths + loaded_gem_paths = Gem.loaded_specs.map {|_, s| s.full_require_paths } + loaded_gem_paths.flatten + end + + def load_plugins + Gem.load_plugins if Gem.respond_to?(:load_plugins) + end + + def load_plugin_files(files) + Gem.load_plugin_files(files) if Gem.respond_to?(:load_plugin_files) + end + + def load_env_plugins + Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) + end + + def ui=(obj) + Gem::DefaultUserInteraction.ui = obj + end + + def ext_lock + EXT_LOCK + end + + def with_build_args(args) + ext_lock.synchronize do + old_args = build_args + begin + self.build_args = args + yield + ensure + self.build_args = old_args + end + end + end + + def spec_from_gem(path, policy = nil) + require "rubygems/security" + require_relative "psyched_yaml" + gem_from_path(path, security_policies[policy]).spec + rescue Exception, Gem::Exception, Gem::Security::Exception => e # rubocop:disable Lint/RescueException + if e.is_a?(Gem::Security::Exception) || + e.message =~ /unknown trust policy|unsigned gem/i || + e.message =~ /couldn't verify (meta)?data signature/i + raise SecurityError, + "The gem #{File.basename(path, ".gem")} can't be installed because " \ + "the security policy didn't allow it, with the message: #{e.message}" + else + raise e + end + end + + def build_gem(gem_dir, spec) + build(spec) + end + + def security_policy_keys + %w[High Medium Low AlmostNo No].map {|level| "#{level}Security" } + end + + def security_policies + @security_policies ||= begin + require "rubygems/security" + Gem::Security::Policies + rescue LoadError, NameError + {} + end + end + + def reverse_rubygems_kernel_mixin + # Disable rubygems' gem activation system + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + redefine_method(k, :require, k.instance_method(:gem_original_require)) + end + end + end + + def replace_gem(specs, specs_by_name) + reverse_rubygems_kernel_mixin + + executables = nil + + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |kernel_class| + redefine_method(kernel_class, :gem) do |dep, *reqs| + if executables && executables.include?(File.basename(caller.first.split(":").first)) + break + end + + reqs.pop if reqs.last.is_a?(Hash) + + unless dep.respond_to?(:name) && dep.respond_to?(:requirement) + dep = Gem::Dependency.new(dep, reqs) + end + + if spec = specs_by_name[dep.name] + return true if dep.matches_spec?(spec) + end + + message = if spec.nil? + target_file = begin + Bundler.default_gemfile.basename + rescue GemfileNotFound + "inline Gemfile" + end + "#{dep.name} is not part of the bundle." \ + " Add it to your #{target_file}." + else + "can't activate #{dep}, already activated #{spec.full_name}. " \ + "Make sure all dependencies are added to Gemfile." + end + + e = Gem::LoadError.new(message) + e.name = dep.name + if e.respond_to?(:requirement=) + e.requirement = dep.requirement + elsif e.respond_to?(:version_requirement=) + e.version_requirement = dep.requirement + end + raise e + end + + # backwards compatibility shim, see https://github.com/rubygems/bundler/issues/5102 + kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public? + end + end + + # Used to make bin stubs that are not created by bundler work + # under bundler. The new Gem.bin_path only considers gems in + # +specs+ + def replace_bin_path(specs_by_name) + gem_class = (class << Gem; self; end) + + redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args| + exec_name = args.first + raise ArgumentError, "you must supply exec_name" unless exec_name + + spec_with_name = specs_by_name[gem_name] + matching_specs_by_exec_name = specs_by_name.values.select {|s| s.executables.include?(exec_name) } + spec = matching_specs_by_exec_name.delete(spec_with_name) + + unless spec || !matching_specs_by_exec_name.empty? + message = "can't find executable #{exec_name} for gem #{gem_name}" + if spec_with_name.nil? + message += ". #{gem_name} is not currently included in the bundle, " \ + "perhaps you meant to add it to your #{Bundler.default_gemfile.basename}?" + end + raise Gem::Exception, message + end + + unless spec + spec = matching_specs_by_exec_name.shift + warn \ + "Bundler is using a binstub that was created for a different gem (#{spec.name}).\n" \ + "You should run `bundle binstub #{gem_name}` " \ + "to work around a system/bundle conflict." + end + + unless matching_specs_by_exec_name.empty? + conflicting_names = matching_specs_by_exec_name.map(&:name).join(", ") + warn \ + "The `#{exec_name}` executable in the `#{spec.name}` gem is being loaded, but it's also present in other gems (#{conflicting_names}).\n" \ + "If you meant to run the executable for another gem, make sure you use a project specific binstub (`bundle binstub `).\n" \ + "If you plan to use multiple conflicting executables, generate binstubs for them and disambiguate their names." + end + + spec + end + + redefine_method(gem_class, :activate_bin_path) do |name, *args| + exec_name = args.first + return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle" + + # Copy of Rubygems activate_bin_path impl + requirement = args.last + spec = find_spec_for_exe name, exec_name, [requirement] + + gem_bin = File.join(spec.full_gem_path, spec.bindir, exec_name) + gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name) + File.exist?(gem_bin) ? gem_bin : gem_from_path_bin + end + + redefine_method(gem_class, :bin_path) do |name, *args| + exec_name = args.first + return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle" + + spec = find_spec_for_exe(name, *args) + exec_name ||= spec.default_executable + + gem_bin = File.join(spec.full_gem_path, spec.bindir, exec_name) + gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name) + File.exist?(gem_bin) ? gem_bin : gem_from_path_bin + end + end + + # Replace or hook into RubyGems to provide a bundlerized view + # of the world. + def replace_entrypoints(specs) + specs_by_name = add_default_gems_to(specs) + + replace_gem(specs, specs_by_name) + stub_rubygems(specs) + replace_bin_path(specs_by_name) + + Gem.clear_paths + end + + # Add default gems not already present in specs, and return them as a hash. + def add_default_gems_to(specs) + specs_by_name = specs.reduce({}) do |h, s| + h[s.name] = s + h + end + + Bundler.rubygems.default_stubs.each do |stub| + default_spec = stub.to_spec + default_spec_name = default_spec.name + next if specs_by_name.key?(default_spec_name) + + specs << default_spec + specs_by_name[default_spec_name] = default_spec + end + + specs_by_name + end + + def undo_replacements + @replaced_methods.each do |(sym, klass), method| + redefine_method(klass, sym, method) + end + if Binding.public_method_defined?(:source_location) + post_reset_hooks.reject! {|proc| proc.binding.source_location[0] == __FILE__ } + else + post_reset_hooks.reject! {|proc| proc.binding.eval("__FILE__") == __FILE__ } + end + @replaced_methods.clear + end + + def redefine_method(klass, method, unbound_method = nil, &block) + visibility = method_visibility(klass, method) + begin + if (instance_method = klass.instance_method(method)) && method != :initialize + # doing this to ensure we also get private methods + klass.send(:remove_method, method) + end + rescue NameError + # method isn't defined + nil + end + @replaced_methods[[method, klass]] = instance_method + if unbound_method + klass.send(:define_method, method, unbound_method) + klass.send(visibility, method) + elsif block + klass.send(:define_method, method, &block) + klass.send(visibility, method) + end + end + + def method_visibility(klass, method) + if klass.private_method_defined?(method) + :private + elsif klass.protected_method_defined?(method) + :protected + else + :public + end + end + + def stub_rubygems(specs) + Gem::Specification.all = specs + + Gem.post_reset do + Gem::Specification.all = specs + end + + redefine_method((class << Gem; self; end), :finish_resolve) do |*| + [] + end + end + + def plain_specs + Gem::Specification._all + end + + def plain_specs=(specs) + Gem::Specification.all = specs + end + + def fetch_specs(remote, name) + path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz" + fetcher = gem_remote_fetcher + fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri + string = fetcher.fetch_path(path) + Bundler.load_marshal(string) + rescue Gem::RemoteFetcher::FetchError => e + # it's okay for prerelease to fail + raise e unless name == "prerelease_specs" + end + + def fetch_all_remote_specs(remote) + specs = fetch_specs(remote, "specs") + pres = fetch_specs(remote, "prerelease_specs") || [] + + specs.concat(pres) + end + + def download_gem(spec, uri, path) + uri = Bundler.settings.mirror_for(uri) + fetcher = gem_remote_fetcher + fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri + Bundler::Retry.new("download gem from #{uri}").attempts do + fetcher.download(spec, uri, path) + end + rescue Gem::RemoteFetcher::FetchError => e + raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>" + end + + def gem_remote_fetcher + require "rubygems/remote_fetcher" + proxy = configuration[:http_proxy] + Gem::RemoteFetcher.new(proxy) + end + + def gem_from_path(path, policy = nil) + require "rubygems/package" + p = Gem::Package.new(path) + p.security_policy = policy if policy + p + end + + def build(spec, skip_validation = false) + require "rubygems/package" + Gem::Package.build(spec, skip_validation) + end + + def repository_subdirectories + Gem::REPOSITORY_SUBDIRECTORIES + end + + def install_with_build_args(args) + yield + end + + def path_separator + Gem.path_separator + end + + def all_specs + Gem::Specification.stubs.map do |stub| + StubSpecification.from_stub(stub) + end + end + + def backport_ext_builder_monitor + # So we can avoid requiring "rubygems/ext" in its entirety + Gem.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + module Ext + end + RUBY + + require "rubygems/ext/builder" + + Gem::Ext::Builder.class_eval do + unless const_defined?(:CHDIR_MONITOR) + const_set(:CHDIR_MONITOR, EXT_LOCK) + end + + remove_const(:CHDIR_MUTEX) if const_defined?(:CHDIR_MUTEX) + const_set(:CHDIR_MUTEX, const_get(:CHDIR_MONITOR)) + end + end + + def find_name(name) + Gem::Specification.stubs_for(name).map(&:to_spec) + end + + if Gem::Specification.respond_to?(:default_stubs) + def default_stubs + Gem::Specification.default_stubs("*.gemspec") + end + else + def default_stubs + Gem::Specification.send(:default_stubs, "*.gemspec") + end + end + + def use_gemdeps(gemfile) + ENV["BUNDLE_GEMFILE"] ||= File.expand_path(gemfile) + require_relative "gemdeps" + runtime = Bundler.setup + activated_spec_names = runtime.requested_specs.map(&:to_spec).sort_by(&:name) + [Gemdeps.new(runtime), activated_spec_names] + end + end + + def self.rubygems + @rubygems ||= RubygemsIntegration.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/runtime.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/runtime.rb new file mode 100644 index 0000000000..e259b590bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/runtime.rb @@ -0,0 +1,305 @@ +# frozen_string_literal: true + +module Bundler + class Runtime + include SharedHelpers + + def initialize(root, definition) + @root = root + @definition = definition + end + + def setup(*groups) + @definition.ensure_equivalent_gemfile_and_lockfile if Bundler.frozen_bundle? + + groups.map!(&:to_sym) + + # Has to happen first + clean_load_path + + specs = groups.any? ? @definition.specs_for(groups) : requested_specs + + SharedHelpers.set_bundle_environment + Bundler.rubygems.replace_entrypoints(specs) + + # Activate the specs + load_paths = specs.map do |spec| + unless spec.loaded_from + raise GemNotFound, "#{spec.full_name} is missing. Run `bundle install` to get it." + end + + check_for_activated_spec!(spec) + + Bundler.rubygems.mark_loaded(spec) + spec.load_paths.reject {|path| $LOAD_PATH.include?(path) } + end.reverse.flatten + + Bundler.rubygems.add_to_load_path(load_paths) + + setup_manpath + + lock(:preserve_unknown_sections => true) + + self + end + + def require(*groups) + groups.map!(&:to_sym) + groups = [:default] if groups.empty? + + @definition.dependencies.each do |dep| + # Skip the dependency if it is not in any of the requested groups, or + # not for the current platform, or doesn't match the gem constraints. + next unless (dep.groups & groups).any? && dep.should_include? + + required_file = nil + + begin + # Loop through all the specified autorequires for the + # dependency. If there are none, use the dependency's name + # as the autorequire. + Array(dep.autorequire || dep.name).each do |file| + # Allow `require: true` as an alias for `require: ` + file = dep.name if file == true + required_file = file + begin + Kernel.require file + rescue RuntimeError => e + raise e if e.is_a?(LoadError) # we handle this a little later + raise Bundler::GemRequireError.new e, + "There was an error while trying to load the gem '#{file}'." + end + end + rescue LoadError => e + raise if dep.autorequire || e.path != required_file + + if dep.autorequire.nil? && dep.name.include?("-") + begin + namespaced_file = dep.name.tr("-", "/") + Kernel.require namespaced_file + rescue LoadError => e + raise if e.path != namespaced_file + end + end + end + end + end + + def self.definition_method(meth) + define_method(meth) do + raise ArgumentError, "no definition when calling Runtime##{meth}" unless @definition + @definition.send(meth) + end + end + private_class_method :definition_method + + definition_method :requested_specs + definition_method :specs + definition_method :dependencies + definition_method :current_dependencies + definition_method :requires + + def lock(opts = {}) + return if @definition.nothing_changed? && !@definition.unlocking? + @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + end + + alias_method :gems, :specs + + def cache(custom_path = nil) + cache_path = Bundler.app_cache(custom_path) + SharedHelpers.filesystem_access(cache_path) do |p| + FileUtils.mkdir_p(p) + end unless File.exist?(cache_path) + + Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}" + + specs_to_cache = Bundler.settings[:cache_all_platforms] ? @definition.resolve.materialized_for_all_platforms : specs + specs_to_cache.each do |spec| + next if spec.name == "bundler" + next if spec.source.is_a?(Source::Gemspec) + spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true) + spec.source.cache(spec, custom_path) if spec.source.respond_to?(:cache) + end + + Dir[cache_path.join("*/.git")].each do |git_dir| + FileUtils.rm_rf(git_dir) + FileUtils.touch(File.expand_path("../.bundlecache", git_dir)) + end + + prune_cache(cache_path) unless Bundler.settings[:no_prune] + end + + def prune_cache(cache_path) + SharedHelpers.filesystem_access(cache_path) do |p| + FileUtils.mkdir_p(p) + end unless File.exist?(cache_path) + resolve = @definition.resolve + prune_gem_cache(resolve, cache_path) + prune_git_and_path_cache(resolve, cache_path) + end + + def clean(dry_run = false) + gem_bins = Dir["#{Gem.dir}/bin/*"] + git_dirs = Dir["#{Gem.dir}/bundler/gems/*"] + git_cache_dirs = Dir["#{Gem.dir}/cache/bundler/git/*"] + gem_dirs = Dir["#{Gem.dir}/gems/*"] + gem_files = Dir["#{Gem.dir}/cache/*.gem"] + gemspec_files = Dir["#{Gem.dir}/specifications/*.gemspec"] + extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"] + spec_gem_paths = [] + # need to keep git sources around + spec_git_paths = @definition.spec_git_paths + spec_git_cache_dirs = [] + spec_gem_executables = [] + spec_cache_paths = [] + spec_gemspec_paths = [] + spec_extension_paths = [] + Bundler.rubygems.add_default_gems_to(specs).values.each do |spec| + spec_gem_paths << spec.full_gem_path + # need to check here in case gems are nested like for the rails git repo + md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path) + spec_git_paths << md[1] if md + spec_gem_executables << spec.executables.collect do |executable| + e = "#{Bundler.rubygems.gem_bindir}/#{executable}" + [e, "#{e}.bat"] + end + spec_cache_paths << spec.cache_file + spec_gemspec_paths << spec.spec_file + spec_extension_paths << spec.extension_dir if spec.respond_to?(:extension_dir) + spec_git_cache_dirs << spec.source.cache_path.to_s if spec.source.is_a?(Bundler::Source::Git) + end + spec_gem_paths.uniq! + spec_gem_executables.flatten! + + stale_gem_bins = gem_bins - spec_gem_executables + stale_git_dirs = git_dirs - spec_git_paths - ["#{Gem.dir}/bundler/gems/extensions"] + stale_git_cache_dirs = git_cache_dirs - spec_git_cache_dirs + stale_gem_dirs = gem_dirs - spec_gem_paths + stale_gem_files = gem_files - spec_cache_paths + stale_gemspec_files = gemspec_files - spec_gemspec_paths + stale_extension_dirs = extension_dirs - spec_extension_paths + + removed_stale_gem_dirs = stale_gem_dirs.collect {|dir| remove_dir(dir, dry_run) } + removed_stale_git_dirs = stale_git_dirs.collect {|dir| remove_dir(dir, dry_run) } + output = removed_stale_gem_dirs + removed_stale_git_dirs + + unless dry_run + stale_files = stale_gem_bins + stale_gem_files + stale_gemspec_files + stale_files.each do |file| + SharedHelpers.filesystem_access(File.dirname(file)) do |_p| + FileUtils.rm(file) if File.exist?(file) + end + end + + stale_dirs = stale_git_cache_dirs + stale_extension_dirs + stale_dirs.each do |stale_dir| + SharedHelpers.filesystem_access(stale_dir) do |dir| + FileUtils.rm_rf(dir) if File.exist?(dir) + end + end + end + + output + end + + private + + def prune_gem_cache(resolve, cache_path) + cached = Dir["#{cache_path}/*.gem"] + + cached = cached.delete_if do |path| + spec = Bundler.rubygems.spec_from_gem path + + resolve.any? do |s| + s.name == spec.name && s.version == spec.version && !s.source.is_a?(Bundler::Source::Git) + end + end + + if cached.any? + Bundler.ui.info "Removing outdated .gem files from #{Bundler.settings.app_cache_path}" + + cached.each do |path| + Bundler.ui.info " * #{File.basename(path)}" + File.delete(path) + end + end + end + + def prune_git_and_path_cache(resolve, cache_path) + cached = Dir["#{cache_path}/*/.bundlecache"] + + cached = cached.delete_if do |path| + name = File.basename(File.dirname(path)) + + resolve.any? do |s| + source = s.source + source.respond_to?(:app_cache_dirname) && source.app_cache_dirname == name + end + end + + if cached.any? + Bundler.ui.info "Removing outdated git and path gems from #{Bundler.settings.app_cache_path}" + + cached.each do |path| + path = File.dirname(path) + Bundler.ui.info " * #{File.basename(path)}" + FileUtils.rm_rf(path) + end + end + end + + def setup_manpath + # Add man/ subdirectories from activated bundles to MANPATH for man(1) + manuals = $LOAD_PATH.map do |path| + man_subdir = path.sub(/lib$/, "man") + man_subdir unless Dir[man_subdir + "/man?/"].empty? + end.compact + + return if manuals.empty? + Bundler::SharedHelpers.set_env "MANPATH", manuals.concat( + ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR) + ).uniq.join(File::PATH_SEPARATOR) + end + + def remove_dir(dir, dry_run) + full_name = Pathname.new(dir).basename.to_s + + parts = full_name.split("-") + name = parts[0..-2].join("-") + version = parts.last + output = "#{name} (#{version})" + + if dry_run + Bundler.ui.info "Would have removed #{output}" + else + Bundler.ui.info "Removing #{output}" + FileUtils.rm_rf(dir) + end + + output + end + + def check_for_activated_spec!(spec) + return unless activated_spec = Bundler.rubygems.loaded_specs(spec.name) + return if activated_spec.version == spec.version + + suggestion = if Bundler.rubygems.spec_default_gem?(activated_spec) + "Since #{spec.name} is a default gem, you can either remove your dependency on it" \ + " or try updating to a newer version of bundler that supports #{spec.name} as a default gem." + else + "Prepending `bundle exec` to your command may solve this." + end + + e = Gem::LoadError.new "You have already activated #{activated_spec.name} #{activated_spec.version}, " \ + "but your Gemfile requires #{spec.name} #{spec.version}. #{suggestion}" + e.name = spec.name + if e.respond_to?(:requirement=) + e.requirement = Gem::Requirement.new(spec.version.to_s) + else + e.version_requirement = Gem::Requirement.new(spec.version.to_s) + end + raise e + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings.rb new file mode 100644 index 0000000000..de42cc16af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings.rb @@ -0,0 +1,500 @@ +# frozen_string_literal: true + +module Bundler + class Settings + autoload :Mirror, File.expand_path("mirror", __dir__) + autoload :Mirrors, File.expand_path("mirror", __dir__) + autoload :Validator, File.expand_path("settings/validator", __dir__) + + BOOL_KEYS = %w[ + allow_deployment_source_credential_changes + allow_offline_install + auto_clean_without_path + auto_install + cache_all + cache_all_platforms + clean + default_install_uses_path + deployment + disable_checksum_validation + disable_exec_load + disable_local_branch_check + disable_local_revision_check + disable_shared_gems + disable_version_check + force_ruby_platform + forget_cli_options + frozen + gem.changelog + gem.coc + gem.mit + git.allow_insecure + global_gem_cache + ignore_messages + init_gems_rb + inline + no_install + no_prune + path_relative_to_cwd + path.system + plugins + prefer_patch + print_only_version_number + setup_makes_kernel_gem_public + silence_deprecations + silence_root_warning + suppress_install_using_messages + update_requires_all_flag + use_gem_version_promoter_for_major_updates + ].freeze + + NUMBER_KEYS = %w[ + jobs + redirect + retry + ssl_verify_mode + timeout + ].freeze + + ARRAY_KEYS = %w[ + with + without + ].freeze + + STRING_KEYS = %w[ + bin + cache_path + console + gem.ci + gem.github_username + gem.linter + gem.rubocop + gem.test + gemfile + path + shebang + system_bindir + trust-policy + ].freeze + + DEFAULT_CONFIG = { + "BUNDLE_SILENCE_DEPRECATIONS" => false, + "BUNDLE_DISABLE_VERSION_CHECK" => true, + "BUNDLE_PREFER_PATCH" => false, + "BUNDLE_REDIRECT" => 5, + "BUNDLE_RETRY" => 3, + "BUNDLE_TIMEOUT" => 10, + }.freeze + + def initialize(root = nil) + @root = root + @local_config = load_config(local_config_file) + @env_config = ENV.to_h.select {|key, _value| key =~ /\ABUNDLE_.+/ } + @global_config = load_config(global_config_file) + @temporary = {} + end + + def [](name) + key = key_for(name) + value = configs.values.map {|config| config[key] }.compact.first + + converted_value(value, name) + end + + def set_command_option(key, value) + if Bundler.feature_flag.forget_cli_options? + temporary(key => value) + value + else + set_local(key, value) + end + end + + def set_command_option_if_given(key, value) + return if value.nil? + set_command_option(key, value) + end + + def set_local(key, value) + local_config_file || raise(GemfileNotFound, "Could not locate Gemfile") + + set_key(key, value, @local_config, local_config_file) + end + + def temporary(update) + existing = Hash[update.map {|k, _| [k, @temporary[key_for(k)]] }] + update.each do |k, v| + set_key(k, v, @temporary, nil) + end + return unless block_given? + begin + yield + ensure + existing.each {|k, v| set_key(k, v, @temporary, nil) } + end + end + + def set_global(key, value) + set_key(key, value, @global_config, global_config_file) + end + + def all + keys = @temporary.keys | @global_config.keys | @local_config.keys | @env_config.keys + + keys.map do |key| + key.sub(/^BUNDLE_/, "").gsub(/___/, "-").gsub(/__/, ".").downcase + end.sort + end + + def local_overrides + repos = {} + all.each do |k| + repos[$'] = self[k] if k =~ /^local\./ + end + repos + end + + def mirror_for(uri) + if uri.is_a?(String) + require_relative "vendored_uri" + uri = Bundler::URI(uri) + end + + gem_mirrors.for(uri.to_s).uri + end + + def credentials_for(uri) + self[uri.to_s] || self[uri.host] + end + + def gem_mirrors + all.inject(Mirrors.new) do |mirrors, k| + mirrors.parse(k, self[k]) if k.start_with?("mirror.") + mirrors + end + end + + def locations(key) + key = key_for(key) + configs.keys.inject({}) do |partial_locations, level| + value_on_level = configs[level][key] + partial_locations[level] = value_on_level unless value_on_level.nil? + partial_locations + end + end + + def pretty_values_for(exposed_key) + key = key_for(exposed_key) + + locations = [] + + if value = @temporary[key] + locations << "Set for the current command: #{printable_value(value, exposed_key).inspect}" + end + + if value = @local_config[key] + locations << "Set for your local app (#{local_config_file}): #{printable_value(value, exposed_key).inspect}" + end + + if value = @env_config[key] + locations << "Set via #{key}: #{printable_value(value, exposed_key).inspect}" + end + + if value = @global_config[key] + locations << "Set for the current user (#{global_config_file}): #{printable_value(value, exposed_key).inspect}" + end + + return ["You have not configured a value for `#{exposed_key}`"] if locations.empty? + locations + end + + def processor_count + require "etc" + Etc.nprocessors + rescue StandardError + 1 + end + + # for legacy reasons, in Bundler 2, we do not respect :disable_shared_gems + def path + configs.each do |_level, settings| + path = value_for("path", settings) + path_system = value_for("path.system", settings) + disabled_shared_gems = value_for("disable_shared_gems", settings) + next if path.nil? && path_system.nil? && disabled_shared_gems.nil? + system_path = path_system || (disabled_shared_gems == false) + return Path.new(path, system_path) + end + + Path.new(nil, false) + end + + Path = Struct.new(:explicit_path, :system_path) do + def path + path = base_path + path = File.join(path, Bundler.ruby_scope) unless use_system_gems? + path + end + + def use_system_gems? + return true if system_path + return false if explicit_path + !Bundler.feature_flag.default_install_uses_path? + end + + def base_path + path = explicit_path + path ||= ".bundle" unless use_system_gems? + path ||= Bundler.rubygems.gem_dir + path + end + + def base_path_relative_to_pwd + base_path = Pathname.new(self.base_path) + expanded_base_path = base_path.expand_path(Bundler.root) + relative_path = expanded_base_path.relative_path_from(Pathname.pwd) + if relative_path.to_s.start_with?("..") + relative_path = base_path if base_path.absolute? + else + relative_path = Pathname.new(File.join(".", relative_path)) + end + relative_path + rescue ArgumentError + expanded_base_path + end + + def validate! + return unless explicit_path && system_path + path = Bundler.settings.pretty_values_for(:path) + path.unshift(nil, "path:") unless path.empty? + system_path = Bundler.settings.pretty_values_for("path.system") + system_path.unshift(nil, "path.system:") unless system_path.empty? + disable_shared_gems = Bundler.settings.pretty_values_for(:disable_shared_gems) + disable_shared_gems.unshift(nil, "disable_shared_gems:") unless disable_shared_gems.empty? + raise InvalidOption, + "Using a custom path while using system gems is unsupported.\n#{path.join("\n")}\n#{system_path.join("\n")}\n#{disable_shared_gems.join("\n")}" + end + end + + def allow_sudo? + key = key_for(:path) + path_configured = @temporary.key?(key) || @local_config.key?(key) + !path_configured + end + + def ignore_config? + ENV["BUNDLE_IGNORE_CONFIG"] + end + + def app_cache_path + @app_cache_path ||= self[:cache_path] || "vendor/cache" + end + + def validate! + all.each do |raw_key| + [@local_config, @env_config, @global_config].each do |settings| + value = value_for(raw_key, settings) + Validator.validate!(raw_key, value, settings.dup) + end + end + end + + def key_for(key) + self.class.key_for(key) + end + + private + + def configs + { + :temporary => @temporary, + :local => @local_config, + :env => @env_config, + :global => @global_config, + :default => DEFAULT_CONFIG, + } + end + + def value_for(name, config) + converted_value(config[key_for(name)], name) + end + + def parent_setting_for(name) + split_specific_setting_for(name)[0] + end + + def specific_gem_for(name) + split_specific_setting_for(name)[1] + end + + def split_specific_setting_for(name) + name.split(".") + end + + def is_bool(name) + BOOL_KEYS.include?(name.to_s) || BOOL_KEYS.include?(parent_setting_for(name.to_s)) + end + + def is_string(name) + STRING_KEYS.include?(name.to_s) || name.to_s.start_with?("local.") || name.to_s.start_with?("mirror.") || name.to_s.start_with?("build.") + end + + def to_bool(value) + case value + when nil, /\A(false|f|no|n|0|)\z/i, false + false + else + true + end + end + + def is_num(key) + NUMBER_KEYS.include?(key.to_s) + end + + def is_array(key) + ARRAY_KEYS.include?(key.to_s) + end + + def is_credential(key) + key == "gem.push_key" + end + + def is_userinfo(value) + value.include?(":") + end + + def to_array(value) + return [] unless value + value.split(":").map(&:to_sym) + end + + def array_to_s(array) + array = Array(array) + return nil if array.empty? + array.join(":").tr(" ", ":") + end + + def set_key(raw_key, value, hash, file) + raw_key = raw_key.to_s + value = array_to_s(value) if is_array(raw_key) + + key = key_for(raw_key) + + return if hash[key] == value + + hash[key] = value + hash.delete(key) if value.nil? + + Validator.validate!(raw_key, converted_value(value, raw_key), hash) + + return unless file + SharedHelpers.filesystem_access(file) do |p| + FileUtils.mkdir_p(p.dirname) + require_relative "yaml_serializer" + p.open("w") {|f| f.write(YAMLSerializer.dump(hash)) } + end + end + + def converted_value(value, key) + if is_array(key) + to_array(value) + elsif value.nil? + nil + elsif is_bool(key) || value == "false" + to_bool(value) + elsif is_num(key) + value.to_i + else + value.to_s + end + end + + def printable_value(value, key) + converted = converted_value(value, key) + return converted unless converted.is_a?(String) + + if is_string(key) + converted + elsif is_credential(key) + "[REDACTED]" + elsif is_userinfo(converted) + converted.gsub(/:.*$/, ":[REDACTED]") + else + converted + end + end + + def global_config_file + if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? + Pathname.new(ENV["BUNDLE_CONFIG"]) + elsif Bundler.rubygems.user_home && !Bundler.rubygems.user_home.empty? + Pathname.new(Bundler.rubygems.user_home).join(".bundle/config") + end + end + + def local_config_file + Pathname.new(@root).join("config") if @root + end + + def load_config(config_file) + return {} if !config_file || ignore_config? + SharedHelpers.filesystem_access(config_file, :read) do |file| + valid_file = file.exist? && !file.size.zero? + return {} unless valid_file + require_relative "yaml_serializer" + YAMLSerializer.load(file.read).inject({}) do |config, (k, v)| + new_k = k + + if k.include?("-") + Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ + "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ + "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." + + new_k = k.gsub("-", "___") + end + + config[new_k] = v + config + end + end + end + + PER_URI_OPTIONS = %w[ + fallback_timeout + ].freeze + + NORMALIZE_URI_OPTIONS_PATTERN = + / + \A + (\w+\.)? # optional prefix key + (https?.*?) # URI + (\.#{Regexp.union(PER_URI_OPTIONS)})? # optional suffix key + \z + /ix.freeze + + def self.key_for(key) + key = normalize_uri(key).to_s if key.is_a?(String) && /https?:/ =~ key + key = key.to_s.gsub(".", "__").gsub("-", "___").upcase + "BUNDLE_#{key}" + end + + # TODO: duplicates Rubygems#normalize_uri + # TODO: is this the correct place to validate mirror URIs? + def self.normalize_uri(uri) + uri = uri.to_s + if uri =~ NORMALIZE_URI_OPTIONS_PATTERN + prefix = $1 + uri = $2 + suffix = $3 + end + uri = "#{uri}/" unless uri.end_with?("/") + require_relative "vendored_uri" + uri = Bundler::URI(uri) + unless uri.absolute? + raise ArgumentError, format("Gem sources must be absolute. You provided '%s'.", uri) + end + "#{prefix}#{uri}#{suffix}" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings/validator.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings/validator.rb new file mode 100644 index 0000000000..0a57ea7f03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/settings/validator.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module Bundler + class Settings + class Validator + class Rule + attr_reader :description + + def initialize(keys, description, &validate) + @keys = keys + @description = description + @validate = validate + end + + def validate!(key, value, settings) + instance_exec(key, value, settings, &@validate) + end + + def fail!(key, value, *reasons) + reasons.unshift @description + raise InvalidOption, "Setting `#{key}` to #{value.inspect} failed:\n#{reasons.map {|r| " - #{r}" }.join("\n")}" + end + + def set(settings, key, value, *reasons) + hash_key = k(key) + return if settings[hash_key] == value + reasons.unshift @description + Bundler.ui.info "Setting `#{key}` to #{value.inspect}, since #{reasons.join(", ")}" + if value.nil? + settings.delete(hash_key) + else + settings[hash_key] = value + end + end + + def k(key) + Bundler.settings.key_for(key) + end + end + + def self.rules + @rules ||= Hash.new {|h, k| h[k] = [] } + end + private_class_method :rules + + def self.rule(keys, description, &blk) + rule = Rule.new(keys, description, &blk) + keys.each {|k| rules[k] << rule } + end + private_class_method :rule + + def self.validate!(key, value, settings) + rules_to_validate = rules[key] + rules_to_validate.each {|rule| rule.validate!(key, value, settings) } + end + + rule %w[path path.system], "path and path.system are mutually exclusive" do |key, value, settings| + if key == "path" && value + set(settings, "path.system", nil) + elsif key == "path.system" && value + set(settings, :path, nil) + end + end + + rule %w[with without], "a group cannot be in both `with` & `without` simultaneously" do |key, value, settings| + with = settings.fetch(k(:with), "").split(":").map(&:to_sym) + without = settings.fetch(k(:without), "").split(":").map(&:to_sym) + + other_key = key == "with" ? :without : :with + other_setting = key == "with" ? without : with + + conflicting = with & without + if conflicting.any? + fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict") + end + end + + rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings| + next if value.nil? + + path = Pathname.new(value) + next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd? + + path = path.expand_path + + root = begin + Bundler.root + rescue GemfileNotFound + Pathname.pwd.expand_path + end + + path = begin + path.relative_path_from(root) + rescue ArgumentError + path + end + + set(settings, key, path.to_s) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/setup.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/setup.rb new file mode 100644 index 0000000000..27911dc1ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/setup.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "shared_helpers" + +if Bundler::SharedHelpers.in_bundle? + require_relative "../bundler" + + if STDOUT.tty? || ENV["BUNDLER_FORCE_TTY"] + begin + Bundler.ui.silence { Bundler.setup } + rescue Bundler::BundlerError => e + Bundler.ui.warn "\e[31m#{e.message}\e[0m" + Bundler.ui.warn e.backtrace.join("\n") if ENV["DEBUG"] + if e.is_a?(Bundler::GemNotFound) + Bundler.ui.warn "\e[33mRun `bundle install` to install missing gems.\e[0m" + end + exit e.status_code + end + else + Bundler.ui.silence { Bundler.setup } + end + + # We might be in the middle of shelling out to rubygems + # (RUBYOPT=-rbundler/setup), so we need to give rubygems the opportunity of + # not being silent. + Gem::DefaultUserInteraction.ui = nil +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/shared_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/shared_helpers.rb new file mode 100644 index 0000000000..09b79acbf9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/shared_helpers.rb @@ -0,0 +1,358 @@ +# frozen_string_literal: true + +require "pathname" +require "rbconfig" + +require_relative "version" +require_relative "constants" +require_relative "rubygems_integration" +require_relative "current_ruby" + +module Bundler + module SharedHelpers + def root + gemfile = find_gemfile + raise GemfileNotFound, "Could not locate Gemfile" unless gemfile + Pathname.new(gemfile).tap{|x| x.untaint if RUBY_VERSION < "2.7" }.expand_path.parent + end + + def default_gemfile + gemfile = find_gemfile + raise GemfileNotFound, "Could not locate Gemfile" unless gemfile + Pathname.new(gemfile).tap{|x| x.untaint if RUBY_VERSION < "2.7" }.expand_path + end + + def default_lockfile + gemfile = default_gemfile + + case gemfile.basename.to_s + when "gems.rb" then Pathname.new(gemfile.sub(/.rb$/, ".locked")) + else Pathname.new("#{gemfile}.lock") + end.tap{|x| x.untaint if RUBY_VERSION < "2.7" } + end + + def default_bundle_dir + bundle_dir = find_directory(".bundle") + return nil unless bundle_dir + + bundle_dir = Pathname.new(bundle_dir) + + global_bundle_dir = Bundler.user_home.join(".bundle") + return nil if bundle_dir == global_bundle_dir + + bundle_dir + end + + def in_bundle? + find_gemfile + end + + def chdir(dir, &blk) + Bundler.rubygems.ext_lock.synchronize do + Dir.chdir dir, &blk + end + end + + def pwd + Bundler.rubygems.ext_lock.synchronize do + Pathname.pwd + end + end + + def with_clean_git_env(&block) + keys = %w[GIT_DIR GIT_WORK_TREE] + old_env = keys.inject({}) do |h, k| + h.update(k => ENV[k]) + end + + keys.each {|key| ENV.delete(key) } + + block.call + ensure + keys.each {|key| ENV[key] = old_env[key] } + end + + def set_bundle_environment + set_bundle_variables + set_path + set_rubyopt + set_rubylib + end + + # Rescues permissions errors raised by file system operations + # (ie. Errno:EACCESS, Errno::EAGAIN) and raises more friendly errors instead. + # + # @param path [String] the path that the action will be attempted to + # @param action [Symbol, #to_s] the type of operation that will be + # performed. For example: :write, :read, :exec + # + # @yield path + # + # @raise [Bundler::PermissionError] if Errno:EACCES is raised in the + # given block + # @raise [Bundler::TemporaryResourceError] if Errno:EAGAIN is raised in the + # given block + # + # @example + # filesystem_access("vendor/cache", :write) do + # FileUtils.mkdir_p("vendor/cache") + # end + # + # @see {Bundler::PermissionError} + def filesystem_access(path, action = :write, &block) + yield(path.dup.tap{|x| x.untaint if RUBY_VERSION < "2.7" }) + rescue Errno::EACCES + raise PermissionError.new(path, action) + rescue Errno::EAGAIN + raise TemporaryResourceError.new(path, action) + rescue Errno::EPROTO + raise VirtualProtocolError.new + rescue Errno::ENOSPC + raise NoSpaceOnDeviceError.new(path, action) + rescue *[const_get_safely(:ENOTSUP, Errno)].compact + raise OperationNotSupportedError.new(path, action) + rescue Errno::EEXIST, Errno::ENOENT + raise + rescue SystemCallError => e + raise GenericSystemCallError.new(e, "There was an error accessing `#{path}`.") + end + + def const_get_safely(constant_name, namespace) + const_in_namespace = namespace.constants.include?(constant_name.to_s) || + namespace.constants.include?(constant_name.to_sym) + return nil unless const_in_namespace + namespace.const_get(constant_name) + end + + def major_deprecation(major_version, message, print_caller_location: false) + if print_caller_location + caller_location = caller_locations(2, 2).first + message = "#{message} (called at #{caller_location.path}:#{caller_location.lineno})" + end + + bundler_major_version = Bundler.bundler_major_version + if bundler_major_version > major_version + require_relative "errors" + raise DeprecatedError, "[REMOVED] #{message}" + end + + return unless bundler_major_version >= major_version && prints_major_deprecations? + Bundler.ui.warn("[DEPRECATED] #{message}") + end + + def print_major_deprecations! + multiple_gemfiles = search_up(".") do |dir| + gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) } + next if gemfiles.empty? + break gemfiles.size != 1 + end + return unless multiple_gemfiles + message = "Multiple gemfiles (gems.rb and Gemfile) detected. " \ + "Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.rb.locked." + Bundler.ui.warn message + end + + def trap(signal, override = false, &block) + prior = Signal.trap(signal) do + block.call + prior.call unless override + end + end + + def ensure_same_dependencies(spec, old_deps, new_deps) + new_deps = new_deps.reject {|d| d.type == :development } + old_deps = old_deps.reject {|d| d.type == :development } + + without_type = proc {|d| Gem::Dependency.new(d.name, d.requirements_list.sort) } + new_deps.map!(&without_type) + old_deps.map!(&without_type) + + extra_deps = new_deps - old_deps + return if extra_deps.empty? + + Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has either corrupted API or lockfile dependencies" \ + " (was expecting #{old_deps.map(&:to_s)}, but the real spec has #{new_deps.map(&:to_s)})" + raise APIResponseMismatchError, + "Downloading #{spec.full_name} revealed dependencies not in the API or the lockfile (#{extra_deps.join(", ")})." \ + "\nEither installing with `--full-index` or running `bundle update #{spec.name}` should fix the problem." + end + + def pretty_dependency(dep, print_source = false) + msg = String.new(dep.name) + msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default + + if dep.is_a?(Bundler::Dependency) + platform_string = dep.platforms.join(", ") + msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY + end + + msg << " from the `#{dep.source}` source" if print_source && dep.source + msg + end + + def md5_available? + return @md5_available if defined?(@md5_available) + @md5_available = begin + require "openssl" + ::OpenSSL::Digest.digest("MD5", "") + true + rescue LoadError + true + rescue ::OpenSSL::Digest::DigestError + false + end + end + + def digest(name) + require "digest" + Digest(name) + end + + def write_to_gemfile(gemfile_path, contents) + filesystem_access(gemfile_path) {|g| File.open(g, "w") {|file| file.puts contents } } + end + + private + + def validate_bundle_path + path_separator = Bundler.rubygems.path_separator + return unless Bundler.bundle_path.to_s.split(path_separator).size > 1 + message = "Your bundle path contains text matching #{path_separator.inspect}, " \ + "which is the path separator for your system. Bundler cannot " \ + "function correctly when the Bundle path contains the " \ + "system's PATH separator. Please change your " \ + "bundle path to not match #{path_separator.inspect}." \ + "\nYour current bundle path is '#{Bundler.bundle_path}'." + raise Bundler::PathError, message + end + + def find_gemfile + given = ENV["BUNDLE_GEMFILE"] + return given if given && !given.empty? + find_file(*gemfile_names) + end + + def gemfile_names + ["gems.rb", "Gemfile"] + end + + def find_file(*names) + search_up(*names) do |filename| + return filename if File.file?(filename) + end + end + + def find_directory(*names) + search_up(*names) do |dirname| + return dirname if File.directory?(dirname) + end + end + + def search_up(*names) + previous = nil + current = File.expand_path(SharedHelpers.pwd).tap{|x| x.untaint if RUBY_VERSION < "2.7" } + + until !File.directory?(current) || current == previous + if ENV["BUNDLE_SPEC_RUN"] + # avoid stepping above the tmp directory when testing + gemspec = if ENV["GEM_COMMAND"] + # for Ruby Core + "lib/bundler/bundler.gemspec" + else + "bundler.gemspec" + end + + # avoid stepping above the tmp directory when testing + return nil if File.file?(File.join(current, gemspec)) + end + + names.each do |name| + filename = File.join(current, name) + yield filename + end + previous = current + current = File.expand_path("..", current) + end + end + + def set_env(key, value) + raise ArgumentError, "new key #{key}" unless EnvironmentPreserver::BUNDLER_KEYS.include?(key) + orig_key = "#{EnvironmentPreserver::BUNDLER_PREFIX}#{key}" + orig = ENV[key] + orig ||= EnvironmentPreserver::INTENTIONALLY_NIL + ENV[orig_key] ||= orig + + ENV[key] = value + end + public :set_env + + def set_bundle_variables + # bundler exe & lib folders have same root folder, typical gem installation + exe_file = File.expand_path("../../../exe/bundle", __FILE__) + + # for Ruby core repository testing + exe_file = File.expand_path("../../../libexec/bundle", __FILE__) unless File.exist?(exe_file) + + # bundler is a default gem, exe path is separate + exe_file = Bundler.rubygems.bin_path("bundler", "bundle", VERSION) unless File.exist?(exe_file) + + Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", exe_file + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION + end + + def set_path + validate_bundle_path + paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR) + paths.unshift "#{Bundler.bundle_path}/bin" + Bundler::SharedHelpers.set_env "PATH", paths.uniq.join(File::PATH_SEPARATOR) + end + + def set_rubyopt + rubyopt = [ENV["RUBYOPT"]].compact + setup_require = "-r#{File.expand_path("setup", __dir__)}" + return if !rubyopt.empty? && rubyopt.first =~ /#{setup_require}/ + rubyopt.unshift setup_require + Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ") + end + + def set_rubylib + rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR) + rubylib.unshift bundler_ruby_lib unless RbConfig::CONFIG["rubylibdir"] == bundler_ruby_lib + Bundler::SharedHelpers.set_env "RUBYLIB", rubylib.uniq.join(File::PATH_SEPARATOR) + end + + def bundler_ruby_lib + resolve_path File.expand_path("../..", __FILE__) + end + + def clean_load_path + bundler_lib = bundler_ruby_lib + + loaded_gem_paths = Bundler.rubygems.loaded_gem_paths + + $LOAD_PATH.reject! do |p| + next if resolve_path(p).start_with?(bundler_lib) + loaded_gem_paths.delete(p) + end + $LOAD_PATH.uniq! + end + + def resolve_path(path) + expanded = File.expand_path(path) + return expanded unless File.respond_to?(:realpath) && File.exist?(expanded) + + File.realpath(expanded) + end + + def prints_major_deprecations? + require_relative "../bundler" + return false if Bundler.settings[:silence_deprecations] + require_relative "deprecate" + return false if Bundler::Deprecate.skip + true + end + + extend self + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/similarity_detector.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/similarity_detector.rb new file mode 100644 index 0000000000..50e66b9cab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/similarity_detector.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Bundler + class SimilarityDetector + SimilarityScore = Struct.new(:string, :distance) + + # initialize with an array of words to be matched against + def initialize(corpus) + @corpus = corpus + end + + # return an array of words similar to 'word' from the corpus + def similar_words(word, limit = 3) + words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) } + words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string) + end + + # return the result of 'similar_words', concatenated into a list + # (eg "a, b, or c") + def similar_word_list(word, limit = 3) + words = similar_words(word, limit) + if words.length == 1 + words[0] + elsif words.length > 1 + [words[0..-2].join(", "), words[-1]].join(" or ") + end + end + + protected + + # https://www.informit.com/articles/article.aspx?p=683059&seqNum=36 + def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1) + # ins, del, sub are weighted costs + return nil if this.nil? + return nil if that.nil? + dm = [] # distance matrix + + # Initialize first row values + dm[0] = (0..this.length).collect {|i| i * ins } + fill = [0] * (this.length - 1) + + # Initialize first column values + (1..that.length).each do |i| + dm[i] = [i * del, fill.flatten] + end + + # populate matrix + (1..that.length).each do |i| + (1..this.length).each do |j| + # critical comparison + dm[i][j] = [ + dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub), + dm[i][j - 1] + ins, + dm[i - 1][j] + del, + ].min + end + end + + # The last value in matrix is the Levenshtein distance between the strings + dm[that.length][this.length] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source.rb new file mode 100644 index 0000000000..5388a7681e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module Bundler + class Source + autoload :Gemspec, File.expand_path("source/gemspec", __dir__) + autoload :Git, File.expand_path("source/git", __dir__) + autoload :Metadata, File.expand_path("source/metadata", __dir__) + autoload :Path, File.expand_path("source/path", __dir__) + autoload :Rubygems, File.expand_path("source/rubygems", __dir__) + autoload :RubygemsAggregate, File.expand_path("source/rubygems_aggregate", __dir__) + + attr_accessor :dependency_names + + def unmet_deps + specs.unmet_dependency_names + end + + def version_message(spec) + message = "#{spec.name} #{spec.version}" + message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY && !spec.platform.nil? + + if Bundler.locked_gems + locked_spec = Bundler.locked_gems.specs.find {|s| s.name == spec.name } + locked_spec_version = locked_spec.version if locked_spec + if locked_spec_version && spec.version != locked_spec_version + message += Bundler.ui.add_color(" (was #{locked_spec_version})", version_color(spec.version, locked_spec_version)) + end + end + + message + end + + def can_lock?(spec) + spec.source == self + end + + def local!; end + + def local_only!; end + + def cached!; end + + def remote!; end + + def add_dependency_names(names) + @dependency_names = Array(dependency_names) | Array(names) + end + + # it's possible that gems from one source depend on gems from some + # other source, so now we download gemspecs and iterate over those + # dependencies, looking for gems we don't have info on yet. + def double_check_for(*); end + + def dependency_names_to_double_check + specs.dependency_names + end + + def spec_names + specs.spec_names + end + + def include?(other) + other == self + end + + def inspect + "#<#{self.class}:0x#{object_id} #{self}>" + end + + def path? + instance_of?(Bundler::Source::Path) + end + + def extension_cache_path(spec) + return unless Bundler.feature_flag.global_gem_cache? + return unless source_slug = extension_cache_slug(spec) + Bundler.user_cache.join( + "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope, + source_slug, spec.full_name + ) + end + + private + + def version_color(spec_version, locked_spec_version) + if Gem::Version.correct?(spec_version) && Gem::Version.correct?(locked_spec_version) + # display yellow if there appears to be a regression + earlier_version?(spec_version, locked_spec_version) ? :yellow : :green + else + # default to green if the versions cannot be directly compared + :green + end + end + + def earlier_version?(spec_version, locked_spec_version) + Gem::Version.new(spec_version) < Gem::Version.new(locked_spec_version) + end + + def print_using_message(message) + if !message.include?("(was ") && Bundler.feature_flag.suppress_install_using_messages? + Bundler.ui.debug message + else + Bundler.ui.info message + end + end + + def extension_cache_slug(_) + nil + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/gemspec.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/gemspec.rb new file mode 100644 index 0000000000..7e3447e776 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/gemspec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + class Source + class Gemspec < Path + attr_reader :gemspec + + def initialize(options) + super + @gemspec = options["gemspec"] + end + + def as_path_source + Path.new(options) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git.rb new file mode 100644 index 0000000000..fb13ca0578 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git.rb @@ -0,0 +1,338 @@ +# frozen_string_literal: true + +require_relative "../vendored_fileutils" + +module Bundler + class Source + class Git < Path + autoload :GitProxy, File.expand_path("git/git_proxy", __dir__) + + attr_reader :uri, :ref, :branch, :options, :glob, :submodules + + def initialize(options) + @options = options + @glob = options["glob"] || DEFAULT_GLOB + + @allow_cached = false + @allow_remote = false + + # Stringify options that could be set as symbols + %w[ref branch tag revision].each {|k| options[k] = options[k].to_s if options[k] } + + @uri = options["uri"] || "" + @safe_uri = URICredentialsFilter.credential_filtered_uri(@uri) + @branch = options["branch"] + @ref = options["ref"] || options["branch"] || options["tag"] + @submodules = options["submodules"] + @name = options["name"] + @version = options["version"].to_s.strip.gsub("-", ".pre.") + + @copied = false + @local = false + end + + def self.from_lock(options) + new(options.merge("uri" => options.delete("remote"))) + end + + def to_lock + out = String.new("GIT\n") + out << " remote: #{@uri}\n" + out << " revision: #{revision}\n" + %w[ref branch tag submodules].each do |opt| + out << " #{opt}: #{options[opt]}\n" if options[opt] + end + out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB + out << " specs:\n" + end + + def hash + [self.class, uri, ref, branch, name, version, glob, submodules].hash + end + + def eql?(other) + other.is_a?(Git) && uri == other.uri && ref == other.ref && + branch == other.branch && name == other.name && + version == other.version && glob == other.glob && + submodules == other.submodules + end + + alias_method :==, :eql? + + def to_s + begin + at = if local? + path + elsif user_ref = options["ref"] + if ref =~ /\A[a-z0-9]{4,}\z/i + shortref_for_display(user_ref) + else + user_ref + end + elsif ref + ref + else + git_proxy.branch + end + + rev = " (at #{at}@#{shortref_for_display(revision)})" + rescue GitError + "" + end + + "#{@safe_uri}#{rev}" + end + + def name + File.basename(@uri, ".git") + end + + # This is the path which is going to contain a specific + # checkout of the git repository. When using local git + # repos, this is set to the local repo. + def install_path + @install_path ||= begin + git_scope = "#{base_name}-#{shortref_for_path(revision)}" + + path = Bundler.install_path.join(git_scope) + + if !path.exist? && Bundler.requires_sudo? + Bundler.user_bundle_path.join(Bundler.ruby_scope).join(git_scope) + else + path + end + end + end + + alias_method :path, :install_path + + def extension_dir_name + "#{base_name}-#{shortref_for_path(revision)}" + end + + def unlock! + git_proxy.revision = nil + options["revision"] = nil + + @unlocked = true + end + + def local_override!(path) + return false if local? + + original_path = path + path = Pathname.new(path) + path = path.expand_path(Bundler.root) unless path.relative? + + unless options["branch"] || Bundler.settings[:disable_local_branch_check] + raise GitError, "Cannot use local override for #{name} at #{path} because " \ + ":branch is not specified in Gemfile. Specify a branch or run " \ + "`bundle config unset local.#{override_for(original_path)}` to remove the local override" + end + + unless path.exist? + raise GitError, "Cannot use local override for #{name} because #{path} " \ + "does not exist. Run `bundle config unset local.#{override_for(original_path)}` to remove the local override" + end + + set_local!(path) + + # Create a new git proxy without the cached revision + # so the Gemfile.lock always picks up the new revision. + @git_proxy = GitProxy.new(path, uri, ref) + + if git_proxy.branch != options["branch"] && !Bundler.settings[:disable_local_branch_check] + raise GitError, "Local override for #{name} at #{path} is using branch " \ + "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}" + end + + changed = cached_revision && cached_revision != git_proxy.revision + + if !Bundler.settings[:disable_local_revision_check] && changed && !@unlocked && !git_proxy.contains?(cached_revision) + raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \ + "but the current branch in your local override for #{name} does not contain such commit. " \ + "Please make sure your branch is up to date." + end + + changed + end + + def specs(*) + set_local!(app_cache_path) if has_app_cache? && !local? + + if requires_checkout? && !@copied + fetch + git_proxy.copy_to(install_path, submodules) + serialize_gemspecs_in(install_path) + @copied = true + end + + local_specs + end + + def install(spec, options = {}) + force = options[:force] + + print_using_message "Using #{version_message(spec)} from #{self}" + + if (requires_checkout? && !@copied) || force + Bundler.ui.debug " * Checking out revision: #{ref}" + git_proxy.copy_to(install_path, submodules) + serialize_gemspecs_in(install_path) + @copied = true + end + + generate_bin_options = { :disable_extensions => !Bundler.rubygems.spec_missing_extensions?(spec), :build_args => options[:build_args] } + generate_bin(spec, generate_bin_options) + + requires_checkout? ? spec.post_install_message : nil + end + + def cache(spec, custom_path = nil) + app_cache_path = app_cache_path(custom_path) + return unless Bundler.feature_flag.cache_all? + return if path == app_cache_path + cached! + FileUtils.rm_rf(app_cache_path) + git_proxy.checkout if requires_checkout? + git_proxy.copy_to(app_cache_path, @submodules) + serialize_gemspecs_in(app_cache_path) + end + + def load_spec_files + super + rescue PathError => e + Bundler.ui.trace e + raise GitError, "#{self} is not yet checked out. Run `bundle install` first." + end + + # This is the path which is going to contain a cache + # of the git repository. When using the same git repository + # across different projects, this cache will be shared. + # When using local git repos, this is set to the local repo. + def cache_path + @cache_path ||= begin + if Bundler.requires_sudo? || Bundler.feature_flag.global_gem_cache? + Bundler.user_cache + else + Bundler.bundle_path.join("cache", "bundler") + end.join("git", git_scope) + end + end + + def app_cache_dirname + "#{base_name}-#{shortref_for_path(cached_revision || revision)}" + end + + def revision + git_proxy.revision + end + + def allow_git_ops? + @allow_remote || @allow_cached + end + + def local? + @local + end + + private + + def serialize_gemspecs_in(destination) + destination = destination.expand_path(Bundler.root) if destination.relative? + Dir["#{destination}/#{@glob}"].each do |spec_path| + # Evaluate gemspecs and cache the result. Gemspecs + # in git might require git or other dependencies. + # The gemspecs we cache should already be evaluated. + spec = Bundler.load_gemspec(spec_path) + next unless spec + Bundler.rubygems.set_installed_by_version(spec) + Bundler.rubygems.validate(spec) + File.open(spec_path, "wb") {|file| file.write(spec.to_ruby) } + end + end + + def set_local!(path) + @local = true + @local_specs = @git_proxy = nil + @cache_path = @install_path = path + end + + def has_app_cache? + cached_revision && super + end + + def requires_checkout? + allow_git_ops? && !local? && !cached_revision_checked_out? + end + + def cached_revision_checked_out? + cached_revision && cached_revision == revision && install_path.exist? + end + + def base_name + File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git") + end + + def shortref_for_display(ref) + ref[0..6] + end + + def shortref_for_path(ref) + ref[0..11] + end + + def uri_hash + if uri =~ %r{^\w+://(\w+@)?} + # Downcase the domain component of the URI + # and strip off a trailing slash, if one is present + input = Bundler::URI.parse(uri).normalize.to_s.sub(%r{/$}, "") + else + # If there is no URI scheme, assume it is an ssh/git URI + input = uri + end + SharedHelpers.digest(:SHA1).hexdigest(input) + end + + def cached_revision + options["revision"] + end + + def cached? + cache_path.exist? + end + + def git_proxy + @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision, self) + end + + def fetch + git_proxy.checkout + rescue GitError => e + raise unless Bundler.feature_flag.allow_offline_install? + Bundler.ui.warn "Using cached git data because of network errors:\n#{e}" + end + + # no-op, since we validate when re-serializing the gemspec + def validate_spec(_spec); end + + def load_gemspec(file) + stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent) + stub.full_gem_path = Pathname.new(file).dirname.expand_path(root).to_s.tap{|x| x.untaint if RUBY_VERSION < "2.7" } + StubSpecification.from_stub(stub) + end + + def git_scope + "#{base_name}-#{uri_hash}" + end + + def extension_cache_slug(_) + extension_dir_name + end + + def override_for(path) + Bundler.settings.local_overrides.key(path) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git/git_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git/git_proxy.rb new file mode 100644 index 0000000000..ae21770306 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/git/git_proxy.rb @@ -0,0 +1,261 @@ +# frozen_string_literal: true + +require "shellwords" + +module Bundler + class Source + class Git + class GitNotInstalledError < GitError + def initialize + msg = String.new + msg << "You need to install git to be able to use gems from git repositories. " + msg << "For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git" + super msg + end + end + + class GitNotAllowedError < GitError + def initialize(command) + msg = String.new + msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, " + msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " + msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" + super msg + end + end + + class GitCommandError < GitError + attr_reader :command + + def initialize(command, path, extra_info = nil) + @command = command + + msg = String.new + msg << "Git error: command `#{command}` in directory #{path} has failed." + msg << "\n#{extra_info}" if extra_info + msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path.exist? + super msg + end + end + + class MissingGitRevisionError < GitCommandError + def initialize(command, destination_path, ref, repo) + msg = "Revision #{ref} does not exist in the repository #{repo}. Maybe you misspelled it?" + super command, destination_path, msg + end + end + + # The GitProxy is responsible to interact with git repositories. + # All actions required by the Git source is encapsulated in this + # object. + class GitProxy + attr_accessor :path, :uri, :ref + attr_writer :revision + + def initialize(path, uri, ref, revision = nil, git = nil) + @path = path + @uri = uri + @ref = ref + @revision = revision + @git = git + raise GitNotInstalledError.new if allow? && !Bundler.git_present? + end + + def revision + @revision ||= find_local_revision + end + + def branch + @branch ||= allowed_with_path do + git("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip + end + end + + def contains?(commit) + allowed_with_path do + result, status = git_null("branch", "--contains", commit, :dir => path) + status.success? && result =~ /^\* (.*)$/ + end + end + + def version + git("--version").match(/(git version\s*)?((\.?\d+)+).*/)[2] + end + + def full_version + git("--version").sub("git version", "").strip + end + + def checkout + return if path.exist? && has_revision_cached? + extra_ref = "#{ref}:#{ref}" if ref && ref.start_with?("refs/") + + Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" + + configured_uri = configured_uri_for(uri).to_s + + unless path.exist? + SharedHelpers.filesystem_access(path.dirname) do |p| + FileUtils.mkdir_p(p) + end + git_retry "clone", configured_uri, path.to_s, "--bare", "--no-hardlinks", "--quiet" + return unless extra_ref + end + + with_path do + git_retry(*["fetch", "--force", "--quiet", "--tags", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path) + end + end + + def copy_to(destination, submodules = false) + # method 1 + unless File.exist?(destination.join(".git")) + begin + SharedHelpers.filesystem_access(destination.dirname) do |p| + FileUtils.mkdir_p(p) + end + SharedHelpers.filesystem_access(destination) do |p| + FileUtils.rm_rf(p) + end + git_retry "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s + File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination) + rescue Errno::EEXIST => e + file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1] + raise GitError, "Bundler could not install a gem because it needs to " \ + "create a directory, but a file exists - #{file_path}. Please delete " \ + "this file and try again." + end + end + # method 2 + git_retry "fetch", "--force", "--quiet", "--tags", path.to_s, :dir => destination + + begin + git "reset", "--hard", @revision, :dir => destination + rescue GitCommandError => e + raise MissingGitRevisionError.new(e.command, destination, @revision, URICredentialsFilter.credential_filtered_uri(uri)) + end + + if submodules + git_retry "submodule", "update", "--init", "--recursive", :dir => destination + elsif Gem::Version.create(version) >= Gem::Version.create("2.9.0") + inner_command = "git -C $toplevel submodule deinit --force $sm_path" + git_retry "submodule", "foreach", "--quiet", inner_command, :dir => destination + end + end + + private + + def git_null(*command, dir: nil) + check_allowed(command) + + out, status = SharedHelpers.with_clean_git_env do + capture_and_ignore_stderr(*capture3_args_for(command, dir)) + end + + [URICredentialsFilter.credential_filtered_string(out, uri), status] + end + + def git_retry(*command, dir: nil) + command_with_no_credentials = check_allowed(command) + + Bundler::Retry.new("`#{command_with_no_credentials}` at #{dir || SharedHelpers.pwd}").attempts do + git(*command, :dir => dir) + end + end + + def git(*command, dir: nil) + command_with_no_credentials = check_allowed(command) + + out, status = SharedHelpers.with_clean_git_env do + capture_and_filter_stderr(*capture3_args_for(command, dir)) + end + + filtered_out = URICredentialsFilter.credential_filtered_string(out, uri) + + raise GitCommandError.new(command_with_no_credentials, dir || SharedHelpers.pwd, filtered_out) unless status.success? + + filtered_out + end + + def has_revision_cached? + return unless @revision + with_path { git("cat-file", "-e", @revision, :dir => path) } + true + rescue GitError + false + end + + def remove_cache + FileUtils.rm_rf(path) + end + + def find_local_revision + allowed_with_path do + git("rev-parse", "--verify", ref || "HEAD", :dir => path).strip + end + rescue GitCommandError => e + raise MissingGitRevisionError.new(e.command, path, ref, URICredentialsFilter.credential_filtered_uri(uri)) + end + + # Adds credentials to the URI as Fetcher#configured_uri_for does + def configured_uri_for(uri) + if /https?:/ =~ uri + remote = Bundler::URI(uri) + config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host] + remote.userinfo ||= config_auth + remote.to_s + else + uri + end + end + + def allow? + @git ? @git.allow_git_ops? : true + end + + def with_path(&blk) + checkout unless path.exist? + blk.call + end + + def allowed_with_path + return with_path { yield } if allow? + raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application" + end + + def check_allowed(command) + command_with_no_credentials = URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri) + raise GitNotAllowedError.new(command_with_no_credentials) unless allow? + command_with_no_credentials + end + + def capture_and_filter_stderr(*cmd) + require "open3" + return_value, captured_err, status = Open3.capture3(*cmd) + Bundler.ui.warn URICredentialsFilter.credential_filtered_string(captured_err, uri) unless captured_err.empty? + [return_value, status] + end + + def capture_and_ignore_stderr(*cmd) + require "open3" + return_value, _, status = Open3.capture3(*cmd) + [return_value, status] + end + + def capture3_args_for(cmd, dir) + return ["git", *cmd] unless dir + + if Bundler.feature_flag.bundler_3_mode? || supports_minus_c? + ["git", "-C", dir.to_s, *cmd] + else + ["git", *cmd, { :chdir => dir.to_s }] + end + end + + def supports_minus_c? + @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/metadata.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/metadata.rb new file mode 100644 index 0000000000..50b65ce0ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/metadata.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Bundler + class Source + class Metadata < Source + def specs + @specs ||= Index.build do |idx| + idx << Gem::Specification.new("Ruby\0", RubyVersion.system.to_gem_version_with_patchlevel) + idx << Gem::Specification.new("RubyGems\0", Gem::VERSION) do |s| + s.required_rubygems_version = Gem::Requirement.default + end + + idx << Gem::Specification.new do |s| + s.name = "bundler" + s.version = VERSION + s.license = "MIT" + s.platform = Gem::Platform::RUBY + s.source = self + s.authors = ["bundler team"] + s.bindir = "exe" + s.homepage = "https://bundler.io" + s.summary = "The best way to manage your application's dependencies" + s.executables = %w[bundle] + # can't point to the actual gemspec or else the require paths will be wrong + s.loaded_from = File.expand_path("..", __FILE__) + end + + if local_spec = Bundler.rubygems.find_name("bundler").find {|s| s.version.to_s == VERSION } + idx << local_spec + end + + idx.each {|s| s.source = self } + end + end + + def options + {} + end + + def install(spec, _opts = {}) + print_using_message "Using #{version_message(spec)}" + nil + end + + def to_s + "the local ruby installation" + end + + def ==(other) + self.class == other.class + end + alias_method :eql?, :== + + def hash + self.class.hash + end + + def version_message(spec) + "#{spec.name} #{spec.version}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path.rb new file mode 100644 index 0000000000..01f89b204d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +module Bundler + class Source + class Path < Source + autoload :Installer, File.expand_path("path/installer", __dir__) + + attr_reader :path, :options, :root_path, :original_path + attr_writer :name + attr_accessor :version + + protected :original_path + + DEFAULT_GLOB = "{,*,*/*}.gemspec".freeze + + def initialize(options) + @options = options.dup + @glob = options["glob"] || DEFAULT_GLOB + + @allow_cached = false + @allow_remote = false + + @root_path = options["root_path"] || root + + if options["path"] + @path = Pathname.new(options["path"]) + expanded_path = expand(@path) + @path = if @path.relative? + expanded_path.relative_path_from(root_path.expand_path) + else + expanded_path + end + end + + @name = options["name"] + @version = options["version"] + + # Stores the original path. If at any point we move to the + # cached directory, we still have the original path to copy from. + @original_path = @path + end + + def remote! + @local_specs = nil + @allow_remote = true + end + + def cached! + @local_specs = nil + @allow_cached = true + end + + def self.from_lock(options) + new(options.merge("path" => options.delete("remote"))) + end + + def to_lock + out = String.new("PATH\n") + out << " remote: #{lockfile_path}\n" + out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB + out << " specs:\n" + end + + def to_s + "source at `#{@path}`" + end + + def hash + [self.class, expanded_path, version].hash + end + + def eql?(other) + return unless other.class == self.class + expanded_original_path == other.expanded_original_path && + version == other.version + end + + alias_method :==, :eql? + + def name + File.basename(expanded_path.to_s) + end + + def install(spec, options = {}) + using_message = "Using #{version_message(spec)} from #{self}" + using_message += " and installing its executables" unless spec.executables.empty? + print_using_message using_message + generate_bin(spec, :disable_extensions => true) + nil # no post-install message + end + + def cache(spec, custom_path = nil) + app_cache_path = app_cache_path(custom_path) + return unless Bundler.feature_flag.cache_all? + return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0 + + unless @original_path.exist? + raise GemNotFound, "Can't cache gem #{version_message(spec)} because #{self} is missing!" + end + + FileUtils.rm_rf(app_cache_path) + FileUtils.cp_r("#{@original_path}/.", app_cache_path) + FileUtils.touch(app_cache_path.join(".bundlecache")) + end + + def local_specs(*) + @local_specs ||= load_spec_files + end + + def specs + if has_app_cache? + @path = app_cache_path + @expanded_path = nil # Invalidate + end + local_specs + end + + def app_cache_dirname + name + end + + def root + Bundler.root + end + + def expanded_original_path + @expanded_original_path ||= expand(original_path) + end + + private + + def expanded_path + @expanded_path ||= expand(path) + end + + def expand(somepath) + if Bundler.current_ruby.jruby? # TODO: Unify when https://github.com/rubygems/bundler/issues/7598 fixed upstream and all supported jrubies include the fix + somepath.expand_path(root_path).expand_path + else + somepath.expand_path(root_path) + end + rescue ArgumentError => e + Bundler.ui.debug(e) + raise PathError, "There was an error while trying to use the path " \ + "`#{somepath}`.\nThe error message was: #{e.message}." + end + + def lockfile_path + return relative_path(original_path) if original_path.absolute? + expand(original_path).relative_path_from(root) + end + + def app_cache_path(custom_path = nil) + @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname) + end + + def has_app_cache? + SharedHelpers.in_bundle? && app_cache_path.exist? + end + + def load_gemspec(file) + return unless spec = Bundler.load_gemspec(file) + Bundler.rubygems.set_installed_by_version(spec) + spec + end + + def validate_spec(spec) + Bundler.rubygems.validate(spec) + end + + def load_spec_files + index = Index.new + + if File.directory?(expanded_path) + # We sort depth-first since `<<` will override the earlier-found specs + Gem::Util.glob_files_in_dir(@glob, expanded_path).sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file| + next unless spec = load_gemspec(file) + spec.source = self + + # Validation causes extension_dir to be calculated, which depends + # on #source, so we validate here instead of load_gemspec + validate_spec(spec) + index << spec + end + + if index.empty? && @name && @version + index << Gem::Specification.new do |s| + s.name = @name + s.source = self + s.version = Gem::Version.new(@version) + s.platform = Gem::Platform::RUBY + s.summary = "Fake gemspec for #{@name}" + s.relative_loaded_from = "#{@name}.gemspec" + s.authors = ["no one"] + if expanded_path.join("bin").exist? + executables = expanded_path.join("bin").children + executables.reject! {|p| File.directory?(p) } + s.executables = executables.map {|c| c.basename.to_s } + end + end + end + else + message = String.new("The path `#{expanded_path}` ") + message << if File.exist?(expanded_path) + "is not a directory." + else + "does not exist." + end + raise PathError, message + end + + index + end + + def relative_path(path = self.path) + if path.to_s.start_with?(root_path.to_s) + return path.relative_path_from(root_path) + end + path + end + + def generate_bin(spec, options = {}) + gem_dir = Pathname.new(spec.full_gem_path) + + # Some gem authors put absolute paths in their gemspec + # and we have to save them from themselves + spec.files = spec.files.map do |p| + next p unless p =~ /\A#{Pathname::SEPARATOR_PAT}/ + next if File.directory?(p) + begin + Pathname.new(p).relative_path_from(gem_dir).to_s + rescue ArgumentError + p + end + end.compact + + installer = Path::Installer.new( + spec, + :env_shebang => false, + :disable_extensions => options[:disable_extensions], + :build_args => options[:build_args], + :bundler_extension_cache_path => extension_cache_path(spec) + ) + installer.post_install + rescue Gem::InvalidSpecificationException => e + Bundler.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \ + "This prevents bundler from installing bins or native extensions, but " \ + "that may not affect its functionality." + + if !spec.extensions.empty? && !spec.email.empty? + Bundler.ui.warn "If you need to use this package without installing it from a gem " \ + "repository, please contact #{spec.email} and ask them " \ + "to modify their .gemspec so it can work with `gem build`." + end + + Bundler.ui.warn "The validation message from RubyGems was:\n #{e.message}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path/installer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path/installer.rb new file mode 100644 index 0000000000..a70973bde7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/path/installer.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require_relative "../../rubygems_gem_installer" + +module Bundler + class Source + class Path + class Installer < Bundler::RubyGemsGemInstaller + attr_reader :spec + + def initialize(spec, options = {}) + @options = options + @spec = spec + @gem_dir = Bundler.rubygems.path(spec.full_gem_path) + @wrappers = true + @env_shebang = true + @format_executable = options[:format_executable] || false + @build_args = options[:build_args] || Bundler.rubygems.build_args + @gem_bin_dir = "#{Bundler.rubygems.gem_dir}/bin" + @disable_extensions = options[:disable_extensions] + + if Bundler.requires_sudo? + @tmp_dir = Bundler.tmp(spec.full_name).to_s + @bin_dir = "#{@tmp_dir}/bin" + else + @bin_dir = @gem_bin_dir + end + end + + def post_install + run_hooks(:pre_install) + + unless @disable_extensions + build_extensions + run_hooks(:post_build) + end + + generate_bin unless spec.executables.empty? + + run_hooks(:post_install) + ensure + Bundler.rm_rf(@tmp_dir) if Bundler.requires_sudo? + end + + private + + def generate_bin + super + + if Bundler.requires_sudo? + SharedHelpers.filesystem_access(@gem_bin_dir) do |p| + Bundler.mkdir_p(p) + end + spec.executables.each do |exe| + Bundler.sudo "cp -R #{@bin_dir}/#{exe} #{@gem_bin_dir}" + end + end + end + + def run_hooks(type) + hooks_meth = "#{type}_hooks" + return unless Gem.respond_to?(hooks_meth) + Gem.send(hooks_meth).each do |hook| + result = hook.call(self) + next unless result == false + location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/ + message = "#{type} hook#{location} failed for #{spec.full_name}" + raise InstallHookError, message + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems.rb new file mode 100644 index 0000000000..43b193cf1c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems.rb @@ -0,0 +1,566 @@ +# frozen_string_literal: true + +require "rubygems/user_interaction" + +module Bundler + class Source + class Rubygems < Source + autoload :Remote, File.expand_path("rubygems/remote", __dir__) + + # Use the API when installing less than X gems + API_REQUEST_LIMIT = 500 + # Ask for X gems per API request + API_REQUEST_SIZE = 50 + + attr_reader :remotes, :caches + + def initialize(options = {}) + @options = options + @remotes = [] + @dependency_names = [] + @allow_remote = false + @allow_cached = false + @allow_local = options["allow_local"] || false + @caches = [cache_path, *Bundler.rubygems.gem_cache] + + Array(options["remotes"]).reverse_each {|r| add_remote(r) } + end + + def local_only! + @specs = nil + @allow_local = true + @allow_remote = false + end + + def local! + return if @allow_local + + @specs = nil + @allow_local = true + end + + def remote! + return if @allow_remote + + @specs = nil + @allow_remote = true + end + + def cached! + return if @allow_cached + + @specs = nil + @allow_cached = true + end + + def hash + @remotes.hash + end + + def eql?(other) + other.is_a?(Rubygems) && other.credless_remotes == credless_remotes + end + + alias_method :==, :eql? + + def include?(o) + o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty? + end + + def multiple_remotes? + @remotes.size > 1 + end + + def can_lock?(spec) + return super unless multiple_remotes? + include?(spec.source) + end + + def options + { "remotes" => @remotes.map(&:to_s) } + end + + def self.from_lock(options) + new(options) + end + + def to_lock + out = String.new("GEM\n") + remotes.reverse_each do |remote| + out << " remote: #{suppress_configured_credentials remote}\n" + end + out << " specs:\n" + end + + def to_s + if remotes.empty? + "locally installed gems" + else + remote_names = remotes.map(&:to_s).join(", ") + "rubygems repository #{remote_names} or installed locally" + end + end + alias_method :name, :to_s + + def specs + @specs ||= begin + # remote_specs usually generates a way larger Index than the other + # sources, and large_idx.use small_idx is way faster than + # small_idx.use large_idx. + idx = @allow_remote ? remote_specs.dup : Index.new + idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote + idx.use(installed_specs, :override_dupes) if @allow_local + idx + end + end + + def install(spec, opts = {}) + force = opts[:force] + ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached] + + if ensure_builtin_gems_cached && builtin_gem?(spec) + if !cached_path(spec) + cached_built_in_gem(spec) unless spec.remote + force = true + else + spec.loaded_from = loaded_from(spec) + end + end + + if (installed?(spec) || Plugin.installed?(spec.name)) && !force + print_using_message "Using #{version_message(spec)}" + return nil # no post-install message + end + + # Download the gem to get the spec, because some specs that are returned + # by rubygems.org are broken and wrong. + if spec.remote + # Check for this spec from other sources + uris = [spec.remote.anonymized_uri] + uris += remotes_for_spec(spec).map(&:anonymized_uri) + uris.uniq! + Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1 + + path = fetch_gem(spec) + begin + s = Bundler.rubygems.spec_from_gem(path, Bundler.settings["trust-policy"]) + spec.__swap__(s) + rescue StandardError + Bundler.rm_rf(path) + raise + end + end + + unless Bundler.settings[:no_install] + message = "Installing #{version_message(spec)}" + message += " with native extensions" if spec.extensions.any? + Bundler.ui.confirm message + + path = cached_gem(spec) + if requires_sudo? + install_path = Bundler.tmp(spec.full_name) + bin_path = install_path.join("bin") + else + install_path = rubygems_dir + bin_path = Bundler.system_bindir + end + + Bundler.mkdir_p bin_path, :no_sudo => true unless spec.executables.empty? || Bundler.rubygems.provides?(">= 2.7.5") + + require_relative "../rubygems_gem_installer" + + installed_spec = Bundler::RubyGemsGemInstaller.at( + path, + :install_dir => install_path.to_s, + :bin_dir => bin_path.to_s, + :ignore_dependencies => true, + :wrappers => true, + :env_shebang => true, + :build_args => opts[:build_args], + :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum, + :bundler_extension_cache_path => extension_cache_path(spec) + ).install + spec.full_gem_path = installed_spec.full_gem_path + + # SUDO HAX + if requires_sudo? + Bundler.rubygems.repository_subdirectories.each do |name| + src = File.join(install_path, name, "*") + dst = File.join(rubygems_dir, name) + if name == "extensions" && Dir.glob(src).any? + src = File.join(src, "*/*") + ext_src = Dir.glob(src).first + ext_src.gsub!(src[0..-6], "") + dst = File.dirname(File.join(dst, ext_src)) + end + SharedHelpers.filesystem_access(dst) do |p| + Bundler.mkdir_p(p) + end + Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any? + end + + spec.executables.each do |exe| + SharedHelpers.filesystem_access(Bundler.system_bindir) do |p| + Bundler.mkdir_p(p) + end + Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/" + end + end + installed_spec.loaded_from = loaded_from(spec) + end + spec.loaded_from = loaded_from(spec) + + spec.post_install_message + ensure + Bundler.rm_rf(install_path) if requires_sudo? + end + + def cache(spec, custom_path = nil) + if builtin_gem?(spec) + cached_path = cached_built_in_gem(spec) + else + cached_path = cached_gem(spec) + end + raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path + return if File.dirname(cached_path) == Bundler.app_cache.to_s + Bundler.ui.info " * #{File.basename(cached_path)}" + FileUtils.cp(cached_path, Bundler.app_cache(custom_path)) + rescue Errno::EACCES => e + Bundler.ui.debug(e) + raise InstallError, e.message + end + + def cached_built_in_gem(spec) + cached_path = cached_path(spec) + if cached_path.nil? + remote_spec = remote_specs.search(spec).first + if remote_spec + cached_path = fetch_gem(remote_spec) + else + Bundler.ui.warn "#{spec.full_name} is built in to Ruby, and can't be cached because your Gemfile doesn't have any sources that contain it." + end + end + cached_path + end + + def add_remote(source) + uri = normalize_uri(source) + @remotes.unshift(uri) unless @remotes.include?(uri) + end + + def equivalent_remotes?(other_remotes) + other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth)) + end + + def spec_names + if @allow_remote && dependency_api_available? + remote_specs.spec_names + else + [] + end + end + + def unmet_deps + if @allow_remote && dependency_api_available? + remote_specs.unmet_dependency_names + else + [] + end + end + + def fetchers + @fetchers ||= remotes.map do |uri| + remote = Source::Rubygems::Remote.new(uri) + Bundler::Fetcher.new(remote) + end + end + + def double_check_for(unmet_dependency_names) + return unless @allow_remote + return unless dependency_api_available? + + unmet_dependency_names = unmet_dependency_names.call + unless unmet_dependency_names.nil? + if api_fetchers.size <= 1 + # can't do this when there are multiple fetchers because then we might not fetch from _all_ + # of them + unmet_dependency_names -= remote_specs.spec_names # avoid re-fetching things we've already gotten + end + return if unmet_dependency_names.empty? + end + + Bundler.ui.debug "Double checking for #{unmet_dependency_names || "all specs (due to the size of the request)"} in #{self}" + + fetch_names(api_fetchers, unmet_dependency_names, specs, false) + end + + def dependency_names_to_double_check + names = [] + remote_specs.each do |spec| + case spec + when EndpointSpecification, Gem::Specification, StubSpecification, LazySpecification + names.concat(spec.runtime_dependencies.map(&:name)) + when RemoteSpecification # from the full index + return nil + else + raise "unhandled spec type (#{spec.inspect})" + end + end + names + end + + def dependency_api_available? + api_fetchers.any? + end + + protected + + def credless_remotes + remotes.map(&method(:suppress_configured_credentials)) + end + + def remotes_for_spec(spec) + specs.search_all(spec.name).inject([]) do |uris, s| + uris << s.remote if s.remote + uris + end + end + + def loaded_from(spec) + "#{rubygems_dir}/specifications/#{spec.full_name}.gemspec" + end + + def cached_gem(spec) + cached_gem = cached_path(spec) + unless cached_gem + raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation" + end + cached_gem + end + + def cached_path(spec) + possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" } + possibilities.find {|p| File.exist?(p) } + end + + def normalize_uri(uri) + uri = uri.to_s + uri = "#{uri}/" unless uri =~ %r{/$} + require_relative "../vendored_uri" + uri = Bundler::URI(uri) + raise ArgumentError, "The source must be an absolute URI. For example:\n" \ + "source 'https://rubygems.org'" if !uri.absolute? || (uri.is_a?(Bundler::URI::HTTP) && uri.host.nil?) + uri + end + + def suppress_configured_credentials(remote) + remote_nouser = remove_auth(remote) + if remote.userinfo && remote.userinfo == Bundler.settings[remote_nouser] + remote_nouser + else + remote + end + end + + def remove_auth(remote) + if remote.user || remote.password + remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s + else + remote.to_s + end + end + + def installed_specs + @installed_specs ||= Index.build do |idx| + Bundler.rubygems.all_specs.reverse_each do |spec| + spec.source = self + if Bundler.rubygems.spec_missing_extensions?(spec, false) + Bundler.ui.debug "Source #{self} is ignoring #{spec} because it is missing extensions" + next + end + idx << spec + end + end + end + + def cached_specs + @cached_specs ||= begin + idx = @allow_local ? installed_specs.dup : Index.new + + Dir["#{cache_path}/*.gem"].each do |gemfile| + next if gemfile =~ /^bundler\-[\d\.]+?\.gem/ + s ||= Bundler.rubygems.spec_from_gem(gemfile) + s.source = self + idx << s + end + + idx + end + end + + def api_fetchers + fetchers.select {|f| f.use_api && f.fetchers.first.api_fetcher? } + end + + def remote_specs + @remote_specs ||= Index.build do |idx| + index_fetchers = fetchers - api_fetchers + + # gather lists from non-api sites + fetch_names(index_fetchers, nil, idx, false) + + # because ensuring we have all the gems we need involves downloading + # the gemspecs of those gems, if the non-api sites contain more than + # about 500 gems, we treat all sites as non-api for speed. + allow_api = idx.size < API_REQUEST_LIMIT && dependency_names.size < API_REQUEST_LIMIT + Bundler.ui.debug "Need to query more than #{API_REQUEST_LIMIT} gems." \ + " Downloading full index instead..." unless allow_api + + fetch_names(api_fetchers, allow_api && dependency_names, idx, false) + end + end + + def fetch_names(fetchers, dependency_names, index, override_dupes) + fetchers.each do |f| + if dependency_names + Bundler.ui.info "Fetching gem metadata from #{URICredentialsFilter.credential_filtered_uri(f.uri)}", Bundler.ui.debug? + index.use f.specs_with_retry(dependency_names, self), override_dupes + Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over + else + Bundler.ui.info "Fetching source index from #{URICredentialsFilter.credential_filtered_uri(f.uri)}" + index.use f.specs_with_retry(nil, self), override_dupes + end + end + end + + def fetch_gem(spec) + return false unless spec.remote + + spec.fetch_platform + + download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir + gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem" + + SharedHelpers.filesystem_access("#{download_path}/cache") do |p| + FileUtils.mkdir_p(p) + end + download_gem(spec, download_path) + + if requires_sudo? + SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p| + Bundler.mkdir_p(p) + end + Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}" + end + + gem_path + ensure + Bundler.rm_rf(download_path) if requires_sudo? + end + + def builtin_gem?(spec) + # Ruby 2.1, where all included gems have this summary + return true if spec.summary =~ /is bundled with Ruby/ + + # Ruby 2.0, where gemspecs are stored in specifications/default/ + spec.loaded_from && spec.loaded_from.include?("specifications/default/") + end + + def installed?(spec) + installed_specs[spec].any? + end + + def requires_sudo? + Bundler.requires_sudo? + end + + def rubygems_dir + Bundler.rubygems.gem_dir + end + + def cache_path + Bundler.app_cache + end + + private + + # Checks if the requested spec exists in the global cache. If it does, + # we copy it to the download path, and if it does not, we download it. + # + # @param [Specification] spec + # the spec we want to download or retrieve from the cache. + # + # @param [String] download_path + # the local directory the .gem will end up in. + # + def download_gem(spec, download_path) + local_path = File.join(download_path, "cache/#{spec.full_name}.gem") + + if (cache_path = download_cache_path(spec)) && cache_path.file? + SharedHelpers.filesystem_access(local_path) do + FileUtils.cp(cache_path, local_path) + end + else + uri = spec.remote.uri + Bundler.ui.confirm("Fetching #{version_message(spec)}") + rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path) + + # older rubygems return varying file:// variants depending on version + rubygems_local_path = rubygems_local_path.gsub(/\Afile:/, "") unless Bundler.rubygems.provides?(">= 3.2.0.rc.2") + rubygems_local_path = rubygems_local_path.gsub(%r{\A//}, "") if Bundler.rubygems.provides?("< 3.1.0") + + if rubygems_local_path != local_path + SharedHelpers.filesystem_access(local_path) do + FileUtils.mv(rubygems_local_path, local_path) + end + end + cache_globally(spec, local_path) + end + end + + # Checks if the requested spec exists in the global cache. If it does + # not, we create the relevant global cache subdirectory if it does not + # exist and copy the spec from the local cache to the global cache. + # + # @param [Specification] spec + # the spec we want to copy to the global cache. + # + # @param [String] local_cache_path + # the local directory from which we want to copy the .gem. + # + def cache_globally(spec, local_cache_path) + return unless cache_path = download_cache_path(spec) + return if cache_path.exist? + + SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath) + SharedHelpers.filesystem_access(cache_path) do + FileUtils.cp(local_cache_path, cache_path) + end + end + + # Returns the global cache path of the calling Rubygems::Source object. + # + # Note that the Source determines the path's subdirectory. We use this + # subdirectory in the global cache path so that gems with the same name + # -- and possibly different versions -- from different sources are saved + # to their respective subdirectories and do not override one another. + # + # @param [Gem::Specification] specification + # + # @return [Pathname] The global cache path. + # + def download_cache_path(spec) + return unless Bundler.feature_flag.global_gem_cache? + return unless remote = spec.remote + return unless cache_slug = remote.cache_slug + + Bundler.user_cache.join("gems", cache_slug, spec.file_name) + end + + def extension_cache_slug(spec) + return unless remote = spec.remote + remote.cache_slug + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems/remote.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems/remote.rb new file mode 100644 index 0000000000..82c850ffbb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems/remote.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Bundler + class Source + class Rubygems + class Remote + attr_reader :uri, :anonymized_uri, :original_uri + + def initialize(uri) + orig_uri = uri + uri = Bundler.settings.mirror_for(uri) + @original_uri = orig_uri if orig_uri != uri + fallback_auth = Bundler.settings.credentials_for(uri) + + @uri = apply_auth(uri, fallback_auth).freeze + @anonymized_uri = remove_auth(@uri).freeze + end + + # @return [String] A slug suitable for use as a cache key for this + # remote. + # + def cache_slug + @cache_slug ||= begin + return nil unless SharedHelpers.md5_available? + + cache_uri = original_uri || uri + + host = cache_uri.to_s.start_with?("file://") ? nil : cache_uri.host + + uri_parts = [host, cache_uri.user, cache_uri.port, cache_uri.path] + uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.compact.join(".")) + + uri_parts[-1] = uri_digest + uri_parts.compact.join(".") + end + end + + def to_s + "rubygems remote at #{anonymized_uri}" + end + + private + + def apply_auth(uri, auth) + if auth && uri.userinfo.nil? + uri = uri.dup + uri.userinfo = auth + end + + uri + rescue Bundler::URI::InvalidComponentError + error_message = "Please CGI escape your usernames and passwords before " \ + "setting them for authentication." + raise HTTPError.new(error_message) + end + + def remove_auth(uri) + if uri.userinfo + uri = uri.dup + uri.user = uri.password = nil + end + + uri + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems_aggregate.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems_aggregate.rb new file mode 100644 index 0000000000..685bf7e90a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source/rubygems_aggregate.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Bundler + class Source + class RubygemsAggregate + attr_reader :source_map, :sources + + def initialize(sources, source_map) + @sources = sources + @source_map = source_map + + @index = build_index + end + + def specs + @index + end + + def to_s + "any of the sources" + end + + private + + def build_index + Index.build do |idx| + dependency_names = source_map.pinned_spec_names + + sources.all_sources.each do |source| + source.dependency_names = dependency_names - source_map.pinned_spec_names(source) + idx.add_source source.specs + dependency_names.concat(source.unmet_deps).uniq! + end + + double_check_for_index(idx, dependency_names) + end + end + + # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both + # sources A and B. At this point, the API request will have found all the versions of Bar in source A, + # but will not have found any versions of Bar from source B, which is a problem if the requested version + # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for + # each spec we found, we add all possible versions from all sources to the index. + def double_check_for_index(idx, dependency_names) + pinned_names = source_map.pinned_spec_names + + names = :names # do this so we only have to traverse to get dependency_names from the index once + unmet_dependency_names = lambda do + return names unless names == :names + new_names = sources.all_sources.map(&:dependency_names_to_double_check) + return names = nil if new_names.compact! + names = new_names.flatten(1).concat(dependency_names) + names.uniq! + names -= pinned_names + names + end + + sources.all_sources.each do |source| + source.double_check_for(unmet_dependency_names) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_list.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_list.rb new file mode 100644 index 0000000000..113d49ba72 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_list.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +module Bundler + class SourceList + attr_reader :path_sources, + :git_sources, + :plugin_sources, + :global_path_source, + :metadata_source + + def global_rubygems_source + @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + end + + def initialize + @path_sources = [] + @git_sources = [] + @plugin_sources = [] + @global_rubygems_source = nil + @global_path_source = nil + @rubygems_sources = [] + @metadata_source = Source::Metadata.new + + @merged_gem_lockfile_sections = false + end + + def merged_gem_lockfile_sections? + @merged_gem_lockfile_sections + end + + def merged_gem_lockfile_sections!(replacement_source) + @merged_gem_lockfile_sections = true + @global_rubygems_source = replacement_source + end + + def aggregate_global_source? + global_rubygems_source.multiple_remotes? + end + + def add_path_source(options = {}) + if options["gemspec"] + add_source_to_list Source::Gemspec.new(options), path_sources + else + path_source = add_source_to_list Source::Path.new(options), path_sources + @global_path_source ||= path_source if options["global"] + path_source + end + end + + def add_git_source(options = {}) + add_source_to_list(Source::Git.new(options), git_sources).tap do |source| + warn_on_git_protocol(source) + end + end + + def add_rubygems_source(options = {}) + new_source = Source::Rubygems.new(options) + return @global_rubygems_source if @global_rubygems_source == new_source + + add_source_to_list new_source, @rubygems_sources + end + + def add_plugin_source(source, options = {}) + add_source_to_list Plugin.source(source).new(options), @plugin_sources + end + + def add_global_rubygems_remote(uri) + global_rubygems_source.add_remote(uri) + global_rubygems_source + end + + def default_source + global_path_source || global_rubygems_source + end + + def rubygems_sources + non_global_rubygems_sources + [global_rubygems_source] + end + + def non_global_rubygems_sources + @rubygems_sources + end + + def rubygems_remotes + rubygems_sources.map(&:remotes).flatten.uniq + end + + def all_sources + path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source] + end + + def non_default_explicit_sources + all_sources - [default_source, metadata_source] + end + + def get(source) + source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) } + end + + def lock_sources + lock_other_sources + lock_rubygems_sources + end + + def lock_other_sources + (path_sources + git_sources + plugin_sources).sort_by(&:to_s) + end + + def lock_rubygems_sources + if merged_gem_lockfile_sections? + [combine_rubygems_sources] + else + rubygems_sources.sort_by(&:to_s) + end + end + + # Returns true if there are changes + def replace_sources!(replacement_sources) + return false if replacement_sources.empty? + + @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources) + + different_sources?(lock_sources, replacement_sources) + end + + # Returns true if there are changes + def expired_sources?(replacement_sources) + return false if replacement_sources.empty? + + lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources + + different_sources?(lock_sources, replacement_sources) + end + + def local_only! + all_sources.each(&:local_only!) + end + + def cached! + all_sources.each(&:cached!) + end + + def remote! + all_sources.each(&:remote!) + end + + private + + def dup_with_replaced_sources(replacement_sources) + new_source_list = dup + new_source_list.replace_sources!(replacement_sources) + new_source_list + end + + def map_sources(replacement_sources) + [path_sources, git_sources, plugin_sources].map do |sources| + sources.map do |source| + replacement_sources.find {|s| s == source } || source + end + end + end + + def different_sources?(lock_sources, replacement_sources) + !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources) + end + + def rubygems_aggregate_class + Source::Rubygems + end + + def add_source_to_list(source, list) + list.unshift(source).uniq! + source + end + + def source_list_for(source) + case source + when Source::Git then git_sources + when Source::Path then path_sources + when Source::Rubygems then rubygems_sources + when Plugin::API::Source then plugin_sources + else raise ArgumentError, "Invalid source: #{source.inspect}" + end + end + + def combine_rubygems_sources + Source::Rubygems.new("remotes" => rubygems_remotes) + end + + def warn_on_git_protocol(source) + return if Bundler.settings["git.allow_insecure"] + + if source.uri =~ /^git\:/ + Bundler.ui.warn "The git source `#{source.uri}` uses the `git` protocol, " \ + "which transmits data without encryption. Disable this warning with " \ + "`bundle config set --local git.allow_insecure true`, or switch to the `https` " \ + "protocol to keep your data secure." + end + end + + def equal_sources?(lock_sources, replacement_sources) + lock_sources.sort_by(&:to_s) == replacement_sources.sort_by(&:to_s) + end + + def equal_source?(source, other_source) + return source.include?(other_source) if source.is_a?(Source::Rubygems) && other_source.is_a?(Source::Rubygems) && !merged_gem_lockfile_sections? + + source == other_source + end + + def equivalent_source?(source, other_source) + return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems) + + equivalent_rubygems_sources?([source], [other_source]) + end + + def equivalent_sources?(lock_sources, replacement_sources) + return false unless Bundler.settings[:allow_deployment_source_credential_changes] + + lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) } + replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) } + + equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources) + end + + def equivalent_rubygems_sources?(lock_sources, replacement_sources) + actual_remotes = replacement_sources.map(&:remotes).flatten.uniq + lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_map.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_map.rb new file mode 100644 index 0000000000..a554f26f76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/source_map.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Bundler + class SourceMap + attr_reader :sources, :dependencies + + def initialize(sources, dependencies) + @sources = sources + @dependencies = dependencies + end + + def pinned_spec_names(skip = nil) + direct_requirements.reject {|_, source| source == skip }.keys + end + + def all_requirements + requirements = direct_requirements.dup + + unmet_deps = sources.non_default_explicit_sources.map do |source| + (source.spec_names - pinned_spec_names).each do |indirect_dependency_name| + previous_source = requirements[indirect_dependency_name] + if previous_source.nil? + requirements[indirect_dependency_name] = source + else + no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode? + + msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] + msg.concat [previous_source, source].map {|s| " * #{s}" }.sort + msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg = msg.join("\n") + + raise SecurityError, msg if no_ambiguous_sources + Bundler.ui.warn "Warning: #{msg}" + end + end + + source.unmet_deps + end + + sources.default_source.add_dependency_names(unmet_deps.flatten - requirements.keys) + + requirements + end + + def direct_requirements + @direct_requirements ||= begin + requirements = {} + default = sources.default_source + dependencies.each do |dep| + dep_source = dep.source || default + dep_source.add_dependency_names(dep.name) + requirements[dep.name] = dep_source + end + requirements + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/spec_set.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/spec_set.rb new file mode 100644 index 0000000000..2ab0386955 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/spec_set.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "tsort" + +module Bundler + class SpecSet + include Enumerable + include TSort + + def initialize(specs) + @specs = specs + end + + def for(dependencies, skip = [], check = false, match_current_platform = false, raise_on_missing = true) + handled = [] + deps = dependencies.dup + specs = [] + skip += ["bundler"] + + loop do + break unless dep = deps.shift + next if handled.include?(dep) || skip.include?(dep.name) + + handled << dep + + specs_for_dep = spec_for_dependency(dep, match_current_platform) + if specs_for_dep.any? + specs += specs_for_dep + + specs_for_dep.first.dependencies.each do |d| + next if d.type == :development + d = DepProxy.get_proxy(d, dep.__platform) unless match_current_platform + deps << d + end + elsif check + return false + elsif raise_on_missing + others = lookup[dep.name] if match_current_platform + message = "Unable to find a spec satisfying #{dep} in the set. Perhaps the lockfile is corrupted?" + message += " Found #{others.join(", ")} that did not match the current platform." if others && !others.empty? + raise GemNotFound, message + end + end + + if spec = lookup["bundler"].first + specs << spec + end + + check ? true : specs + end + + def [](key) + key = key.name if key.respond_to?(:name) + lookup[key].reverse + end + + def []=(key, value) + @specs << value + @lookup = nil + @sorted = nil + end + + def sort! + self + end + + def to_a + sorted.dup + end + + def to_hash + lookup.dup + end + + def materialize(deps, missing_specs = nil) + materialized = self.for(deps, [], false, true, !missing_specs) + + materialized.group_by(&:source).each do |source, specs| + next unless specs.any?{|s| s.is_a?(LazySpecification) } + + source.local! + names = -> { specs.map(&:name).uniq } + source.double_check_for(names) + end + + materialized.map! do |s| + next s unless s.is_a?(LazySpecification) + spec = s.__materialize__ + unless spec + unless missing_specs + raise GemNotFound, "Could not find #{s.full_name} in any of the sources" + end + missing_specs << s + end + spec + end + SpecSet.new(missing_specs ? materialized.compact : materialized) + end + + # Materialize for all the specs in the spec set, regardless of what platform they're for + # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform) + # @return [Array] + def materialized_for_all_platforms + @specs.group_by(&:source).each do |source, specs| + next unless specs.any?{|s| s.is_a?(LazySpecification) } + + source.local! + source.remote! + names = -> { specs.map(&:name).uniq } + source.double_check_for(names) + end + + @specs.map do |s| + next s unless s.is_a?(LazySpecification) + spec = s.__materialize__ + raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec + spec + end + end + + def merge(set) + arr = sorted.dup + set.each do |set_spec| + full_name = set_spec.full_name + next if arr.any? {|spec| spec.full_name == full_name } + arr << set_spec + end + SpecSet.new(arr) + end + + def find_by_name_and_platform(name, platform) + @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } + end + + def what_required(spec) + unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } } + return [spec] + end + what_required(req) << spec + end + + def <<(spec) + @specs << spec + end + + def length + @specs.length + end + + def size + @specs.size + end + + def empty? + @specs.empty? + end + + def each(&b) + sorted.each(&b) + end + + private + + def sorted + rake = @specs.find {|s| s.name == "rake" } + begin + @sorted ||= ([rake] + tsort).compact.uniq + rescue TSort::Cyclic => error + cgems = extract_circular_gems(error) + raise CyclicDependencyError, "Your bundle requires gems that depend" \ + " on each other, creating an infinite loop. Please remove either" \ + " gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again." + end + end + + def extract_circular_gems(error) + error.message.scan(/@name="(.*?)"/).flatten + end + + def lookup + @lookup ||= begin + lookup = Hash.new {|h, k| h[k] = [] } + Index.sort_specs(@specs).reverse_each do |s| + lookup[s.name] << s + end + lookup + end + end + + def tsort_each_node + # MUST sort by name for backwards compatibility + @specs.sort_by(&:name).each {|s| yield s } + end + + def spec_for_dependency(dep, match_current_platform) + specs_for_platforms = lookup[dep.name] + if match_current_platform + GemHelpers.select_best_platform_match(specs_for_platforms.select{|s| Gem::Platform.match_spec?(s) }, Bundler.local_platform) + else + GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform) + end + end + + def tsort_each_child(s) + s.dependencies.sort_by(&:name).each do |d| + next if d.type == :development + lookup[d.name].each {|s2| yield s2 } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/stub_specification.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/stub_specification.rb new file mode 100644 index 0000000000..fa071901e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/stub_specification.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Bundler + class StubSpecification < RemoteSpecification + def self.from_stub(stub) + return stub if stub.is_a?(Bundler::StubSpecification) + spec = new(stub.name, stub.version, stub.platform, nil) + spec.stub = stub + spec + end + + attr_accessor :stub, :ignored + + def source=(source) + super + # Stub has no concept of source, which means that extension_dir may be wrong + # This is the case for git-based gems. So, instead manually assign the extension dir + return unless source.respond_to?(:extension_dir_name) + path = File.join(stub.extensions_dir, source.extension_dir_name) + stub.extension_dir = File.expand_path(path) + end + + def to_yaml + _remote_specification.to_yaml + end + + # @!group Stub Delegates + + def manually_installed? + # This is for manually installed gems which are gems that were fixed in place after a + # failed installation. Once the issue was resolved, the user then manually created + # the gem specification using the instructions provided by `gem help install` + installed_by_version == Gem::Version.new(0) + end + + # This is defined directly to avoid having to loading the full spec + def missing_extensions? + return false if default_gem? + return false if extensions.empty? + return false if File.exist? gem_build_complete_path + return false if manually_installed? + + true + end + + def activated + stub.activated + end + + def activated=(activated) + stub.instance_variable_set(:@activated, activated) + end + + def extensions + stub.extensions + end + + def gem_build_complete_path + File.join(extension_dir, "gem.build_complete") + end + + def default_gem? + stub.default_gem? + end + + def full_gem_path + # deleted gems can have their stubs return nil, so in that case grab the + # expired path from the full spec + stub.full_gem_path || method_missing(:full_gem_path) + end + + def full_require_paths + stub.full_require_paths + end + + def load_paths + full_require_paths + end + + def loaded_from + stub.loaded_from + end + + def matches_for_glob(glob) + stub.matches_for_glob(glob) + end + + def raw_require_paths + stub.raw_require_paths + end + + private + + def _remote_specification + @_remote_specification ||= begin + rs = stub.to_spec + if rs.equal?(self) # happens when to_spec gets the spec from Gem.loaded_specs + rs = Gem::Specification.load(loaded_from) + Bundler.rubygems.stub_set_spec(stub, rs) + end + + unless rs + raise GemspecError, "The gemspec for #{full_name} at #{loaded_from}" \ + " was missing or broken. Try running `gem pristine #{name} -v #{version}`" \ + " to fix the cached spec." + end + + rs.source = source + + rs + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/.document b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/.document new file mode 100644 index 0000000000..fb66f13c33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/.document @@ -0,0 +1 @@ +# Ignore all files in this directory diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable new file mode 100644 index 0000000000..3e8d5b317a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable @@ -0,0 +1,29 @@ +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application '<%= executable %>' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../<%= relative_gemfile_path %>", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("<%= spec.name %>", "<%= executable %>") diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.bundler b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.bundler new file mode 100644 index 0000000000..69f26bb9c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.bundler @@ -0,0 +1,114 @@ +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application '<%= executable %>' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../<%= relative_gemfile_path %>", __FILE__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= + env_var_version || cli_arg_version || + lockfile_version + end + + def bundler_requirement + return "#{Gem::Requirement.default}.a" unless bundler_version + + bundler_gem_version = Gem::Version.new(bundler_version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("<%= spec.name %>", "<%= executable %>") +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.standalone b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.standalone new file mode 100644 index 0000000000..4bf0753f44 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Executable.standalone @@ -0,0 +1,14 @@ +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> +# +# This file was generated by Bundler. +# +# The application '<%= executable %>' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +path = Pathname.new(__FILE__) +$:.unshift File.expand_path "../<%= standalone_path %>", path.realpath + +require "bundler/setup" +load File.expand_path "../<%= executable_path %>", path.realpath diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Gemfile b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Gemfile new file mode 100644 index 0000000000..d41f2719b4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +# gem "rails" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/gems.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/gems.rb new file mode 100644 index 0000000000..56a62a7a82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/gems.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# A sample gems.rb +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +# gem "rails" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CHANGELOG.md.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CHANGELOG.md.tt new file mode 100644 index 0000000000..c9ea96d453 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CHANGELOG.md.tt @@ -0,0 +1,5 @@ +## [Unreleased] + +## [0.1.0] - <%= Time.now.strftime('%F') %> + +- Initial release diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt new file mode 100644 index 0000000000..175b821a62 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -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 <%= config[:email] %>. 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. + +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. Translations are available at https://www.contributor-covenant.org/translations. diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Gemfile.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Gemfile.tt new file mode 100644 index 0000000000..b09ccfff15 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Gemfile.tt @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in <%= config[:name] %>.gemspec +gemspec + +gem "rake", "~> 13.0" +<%- if config[:ext] -%> + +gem "rake-compiler" +<%- end -%> +<%- if config[:test] -%> + +gem "<%= config[:test] %>", "~> <%= config[:test_framework_version] %>" +<%- end -%> +<%- if config[:rubocop] -%> + +gem "rubocop", "~> <%= config[:rubocop_version] %>" +<%- end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/LICENSE.txt.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/LICENSE.txt.tt new file mode 100644 index 0000000000..76ef4b0191 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/LICENSE.txt.tt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) <%= Time.now.year %> <%= config[:author] %> + +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.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/README.md.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/README.md.tt new file mode 100644 index 0000000000..8fd87abe9a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/README.md.tt @@ -0,0 +1,49 @@ +# <%= config[:constant_name] %> + +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/<%= config[:namespaced_path] %>`. To experiment with that code, run `bin/console` for an interactive prompt. + +TODO: Delete this and the text above, and describe your gem + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem '<%= config[:name] %>' +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install <%= config[:name] %> + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies.<% if config[:test] %> Then, run `rake <%= config[:test].sub('mini', '').sub('rspec', 'spec') %>` to run the tests.<% end %> You can also run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the gem in this directory, ignoring other installed copies of this gem.<% end %> + +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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). +<% if config[:git] -%> + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:github_username] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/<%= config[:git_default_branch] %>/CODE_OF_CONDUCT.md).<% end %> +<% end -%> +<% if config[:mit] -%> + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +<% end -%> +<% if config[:git] && config[:coc] -%> + +## Code of Conduct + +Everyone interacting in the <%= config[:constant_name] %> project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/<%= config[:git_default_branch] %>/CODE_OF_CONDUCT.md). +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Rakefile.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Rakefile.tt new file mode 100644 index 0000000000..5393eb4e22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/Rakefile.tt @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +<% default_task_names = [config[:test_task]].compact -%> +<% case config[:test] -%> +<% when "minitest", "test-unit" -%> +require "rake/testtask" + +Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] +end + +<% when "rspec" -%> +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +<% end -%> +<% if config[:rubocop] -%> +<% default_task_names << :rubocop -%> +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +<% end -%> +<% if config[:ext] -%> +<% default_task_names.unshift(:clobber, :compile) -%> +require "rake/extensiontask" + +task build: :compile + +Rake::ExtensionTask.new("<%= config[:underscored_name] %>") do |ext| + ext.lib_dir = "lib/<%= config[:namespaced_path] %>" +end + +<% end -%> +<% if default_task_names.size == 1 -%> +task default: <%= default_task_names.first.inspect %> +<% else -%> +task default: %i[<%= default_task_names.join(" ") %>] +<% end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/console.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/console.tt new file mode 100644 index 0000000000..08dfaaef69 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/console.tt @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "<%= config[:namespaced_path] %>" + +# 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.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/setup.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/setup.tt new file mode 100644 index 0000000000..dce67d860a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/bin/setup.tt @@ -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.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/circleci/config.yml.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/circleci/config.yml.tt new file mode 100644 index 0000000000..79fd0dcc0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/circleci/config.yml.tt @@ -0,0 +1,13 @@ +version: 2.1 +jobs: + build: + docker: + - image: ruby:<%= RUBY_VERSION %> + steps: + - checkout + - run: + name: Run the default task + command: | + gem install bundler -v <%= Bundler::VERSION %> + bundle install + bundle exec rake diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/exe/newgem.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/exe/newgem.tt new file mode 100644 index 0000000000..a8339bb79f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/exe/newgem.tt @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby + +require "<%= config[:namespaced_path] %>" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt new file mode 100644 index 0000000000..e918063ddf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "mkmf" + +create_makefile(<%= config[:makefile_path].inspect %>) diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt new file mode 100644 index 0000000000..8177c4d202 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt @@ -0,0 +1,9 @@ +#include "<%= config[:underscored_name] %>.h" + +VALUE rb_m<%= config[:constant_array].join %>; + +void +Init_<%= config[:underscored_name] %>(void) +{ + rb_m<%= config[:constant_array].join %> = rb_define_module(<%= config[:constant_name].inspect %>); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt new file mode 100644 index 0000000000..c6e420b66e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt @@ -0,0 +1,6 @@ +#ifndef <%= config[:underscored_name].upcase %>_H +#define <%= config[:underscored_name].upcase %>_H 1 + +#include "ruby.h" + +#endif /* <%= config[:underscored_name].upcase %>_H */ diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/github/workflows/main.yml.tt new file mode 100644 index 0000000000..654978033f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -0,0 +1,16 @@ +name: Ruby + +on: [push,pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: <%= RUBY_VERSION %> + bundler-cache: true + - name: Run the default task + run: bundle exec rake diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitignore.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitignore.tt new file mode 100644 index 0000000000..b1c9f9986c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitignore.tt @@ -0,0 +1,20 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +<%- if config[:ext] -%> +*.bundle +*.so +*.o +*.a +mkmf.log +<%- end -%> +<%- if config[:test] == "rspec" -%> + +# rspec failure tracking +.rspec_status +<%- end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitlab-ci.yml.tt new file mode 100644 index 0000000000..0e71ff26a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/gitlab-ci.yml.tt @@ -0,0 +1,9 @@ +image: ruby:<%= RUBY_VERSION %> + +before_script: + - gem install bundler -v <%= Bundler::VERSION %> + - bundle install + +example_job: + script: + - bundle exec rake diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem.rb.tt new file mode 100644 index 0000000000..caf6e32f4a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative "<%= File.basename(config[:namespaced_path]) %>/version" +<%- if config[:ext] -%> +require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" +<%- end -%> + +<%- config[:constant_array].each_with_index do |c, i| -%> +<%= " " * i %>module <%= c %> +<%- end -%> +<%= " " * config[:constant_array].size %>class Error < StandardError; end +<%= " " * config[:constant_array].size %># Your code goes here... +<%- (config[:constant_array].size-1).downto(0) do |i| -%> +<%= " " * i %>end +<%- end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem/version.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem/version.rb.tt new file mode 100644 index 0000000000..b5cd4cb232 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/lib/newgem/version.rb.tt @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +<%- config[:constant_array].each_with_index do |c, i| -%> +<%= " " * i %>module <%= c %> +<%- end -%> +<%= " " * config[:constant_array].size %>VERSION = "0.1.0" +<%- (config[:constant_array].size-1).downto(0) do |i| -%> +<%= " " * i %>end +<%- end -%> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/newgem.gemspec.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/newgem.gemspec.tt new file mode 100644 index 0000000000..91ce856bff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative "lib/<%=config[:namespaced_path]%>/version" + +Gem::Specification.new do |spec| + spec.name = <%= config[:name].inspect %> + spec.version = <%= config[:constant_name] %>::VERSION + spec.authors = [<%= config[:author].inspect %>] + spec.email = [<%= config[:email].inspect %>] + + spec.summary = "TODO: Write a short summary, because RubyGems requires one." + spec.description = "TODO: Write a longer description or delete this line." + spec.homepage = "TODO: Put your gem's website or public repo URL here." +<%- if config[:mit] -%> + spec.license = "MIT" +<%- end -%> + spec.required_ruby_version = ">= <%= config[:required_ruby_version] %>" + + spec.metadata["allowed_push_host"] = "TODO: Set to 'https://mygemserver.com'" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." + spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # 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(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] +<%- if config[:ext] -%> + spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] +<%- end -%> + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + + # For more information and examples about making a new gem, checkout our + # guide at: https://bundler.io/guides/creating_gem.html +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rspec.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rspec.tt new file mode 100644 index 0000000000..34c5164d9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rspec.tt @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rubocop.yml.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rubocop.yml.tt new file mode 100644 index 0000000000..9ecec78807 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/rubocop.yml.tt @@ -0,0 +1,13 @@ +AllCops: + TargetRubyVersion: <%= ::Gem::Version.new(config[:required_ruby_version]).segments[0..1].join(".") %> + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/LineLength: + Max: 120 diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt new file mode 100644 index 0000000000..82cada988c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.describe <%= config[:constant_name] %> do + it "has a version number" do + expect(<%= config[:constant_name] %>::VERSION).not_to be nil + end + + it "does something useful" do + expect(false).to eq(true) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/spec_helper.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/spec_helper.rb.tt new file mode 100644 index 0000000000..70c6d1fcde --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/spec/spec_helper.rb.tt @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "<%= config[:namespaced_path] %>" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/newgem_test.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/newgem_test.rb.tt new file mode 100644 index 0000000000..9e005d69a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/newgem_test.rb.tt @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "test_helper" + +class <%= config[:constant_name] %>Test < Minitest::Test + def test_that_it_has_a_version_number + refute_nil ::<%= config[:constant_name] %>::VERSION + end + + def test_it_does_something_useful + assert false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/test_helper.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/test_helper.rb.tt new file mode 100644 index 0000000000..e05c387bfa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/minitest/test_helper.rb.tt @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) +require "<%= config[:namespaced_path] %>" + +require "minitest/autorun" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/newgem_test.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/newgem_test.rb.tt new file mode 100644 index 0000000000..5c61094e62 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/newgem_test.rb.tt @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "test_helper" + +class <%= config[:constant_name] %>Test < Test::Unit::TestCase + test "VERSION" do + assert do + ::<%= config[:constant_name] %>.const_defined?(:VERSION) + end + end + + test "something useful" do + assert_equal("expected", "actual") + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/test_helper.rb.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/test_helper.rb.tt new file mode 100644 index 0000000000..6f633c6039 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/test/test-unit/test_helper.rb.tt @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) +require "<%= config[:namespaced_path] %>" + +require "test-unit" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/travis.yml.tt b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/travis.yml.tt new file mode 100644 index 0000000000..eab16addca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/templates/newgem/travis.yml.tt @@ -0,0 +1,6 @@ +--- +language: ruby +cache: bundler +rvm: + - <%= RUBY_VERSION %> +before_install: gem install bundler -v <%= Bundler::VERSION %> diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui.rb new file mode 100644 index 0000000000..7a4fa03669 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Bundler + module UI + autoload :RGProxy, File.expand_path("ui/rg_proxy", __dir__) + autoload :Shell, File.expand_path("ui/shell", __dir__) + autoload :Silent, File.expand_path("ui/silent", __dir__) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/rg_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/rg_proxy.rb new file mode 100644 index 0000000000..ef6def225b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/rg_proxy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require_relative "../ui" +require "rubygems/user_interaction" + +module Bundler + module UI + class RGProxy < ::Gem::SilentUI + def initialize(ui) + @ui = ui + super() + end + + def say(message) + @ui && @ui.debug(message) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/shell.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/shell.rb new file mode 100644 index 0000000000..17777af4ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/shell.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require_relative "../vendored_thor" + +module Bundler + module UI + class Shell + LEVELS = %w[silent error warn confirm info debug].freeze + + attr_writer :shell + + def initialize(options = {}) + Thor::Base.shell = options["no-color"] ? Thor::Shell::Basic : nil + @shell = Thor::Base.shell.new + @level = ENV["DEBUG"] ? "debug" : "info" + @warning_history = [] + end + + def add_color(string, *color) + @shell.set_color(string, *color) + end + + def info(msg, newline = nil) + tell_me(msg, nil, newline) if level("info") + end + + def confirm(msg, newline = nil) + tell_me(msg, :green, newline) if level("confirm") + end + + def warn(msg, newline = nil, color = :yellow) + return unless level("warn") + return if @warning_history.include? msg + @warning_history << msg + + tell_err(msg, color, newline) + end + + def error(msg, newline = nil, color = :red) + return unless level("error") + tell_err(msg, color, newline) + end + + def debug(msg, newline = nil) + tell_me(msg, nil, newline) if debug? + end + + def debug? + level("debug") + end + + def quiet? + level("quiet") + end + + def ask(msg) + @shell.ask(msg) + end + + def yes?(msg) + @shell.yes?(msg) + end + + def no? + @shell.no?(msg) + end + + def level=(level) + raise ArgumentError unless LEVELS.include?(level.to_s) + @level = level.to_s + end + + def level(name = nil) + return @level unless name + unless index = LEVELS.index(name) + raise "#{name.inspect} is not a valid level" + end + index <= LEVELS.index(@level) + end + + def trace(e, newline = nil, force = false) + return unless debug? || force + msg = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n ")}" + tell_me(msg, nil, newline) + end + + def silence(&blk) + with_level("silent", &blk) + end + + def unprinted_warnings + [] + end + + private + + # valimism + def tell_me(msg, color = nil, newline = nil) + msg = word_wrap(msg) if newline.is_a?(Hash) && newline[:wrap] + if newline.nil? + @shell.say(msg, color) + else + @shell.say(msg, color, newline) + end + end + + def tell_err(message, color = nil, newline = nil) + return if @shell.send(:stderr).closed? + + newline ||= message.to_s !~ /( |\t)\Z/ + message = word_wrap(message) if newline.is_a?(Hash) && newline[:wrap] + + color = nil if color && !$stderr.tty? + + buffer = @shell.send(:prepare_message, message, *color) + buffer << "\n" if newline && !message.to_s.end_with?("\n") + + @shell.send(:stderr).print(buffer) + @shell.send(:stderr).flush + end + + def strip_leading_spaces(text) + spaces = text[/\A\s+/, 0] + spaces ? text.gsub(/#{spaces}/, "") : text + end + + def word_wrap(text, line_width = @shell.terminal_width) + strip_leading_spaces(text).split("\n").collect do |line| + line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line + end * "\n" + end + + def with_level(level) + original = @level + @level = level + yield + ensure + @level = original + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/silent.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/silent.rb new file mode 100644 index 0000000000..dca1b2ac86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/ui/silent.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Bundler + module UI + class Silent + attr_writer :shell + + def initialize + @warnings = [] + end + + def add_color(string, color) + string + end + + def info(message, newline = nil) + end + + def confirm(message, newline = nil) + end + + def warn(message, newline = nil) + @warnings |= [message] + end + + def error(message, newline = nil) + end + + def debug(message, newline = nil) + end + + def debug? + false + end + + def quiet? + false + end + + def ask(message) + end + + def yes?(msg) + raise "Cannot ask yes? with a silent shell" + end + + def no? + raise "Cannot ask no? with a silent shell" + end + + def level=(name) + end + + def level(name = nil) + end + + def trace(message, newline = nil, force = false) + end + + def silence + yield + end + + def unprinted_warnings + @warnings + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/uri_credentials_filter.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/uri_credentials_filter.rb new file mode 100644 index 0000000000..ccfaf0bc5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/uri_credentials_filter.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Bundler + module URICredentialsFilter + module_function + + def credential_filtered_uri(uri_to_anonymize) + return uri_to_anonymize if uri_to_anonymize.nil? + uri = uri_to_anonymize.dup + if uri.is_a?(String) + return uri if File.exist?(uri) + + require_relative "vendored_uri" + uri = Bundler::URI(uri) + end + + if uri.userinfo + # oauth authentication + if uri.password == "x-oauth-basic" || uri.password == "x" + # URI as string does not display with password if no user is set + oauth_designation = uri.password + uri.user = oauth_designation + end + uri.password = nil + end + return uri.to_s if uri_to_anonymize.is_a?(String) + uri + rescue Bundler::URI::InvalidURIError # uri is not canonical uri scheme + uri + end + + def credential_filtered_string(str_to_filter, uri) + return str_to_filter if uri.nil? || str_to_filter.nil? + str_with_no_credentials = str_to_filter.dup + anonymous_uri_str = credential_filtered_uri(uri).to_s + uri_str = uri.to_s + if anonymous_uri_str != uri_str + str_with_no_credentials = str_with_no_credentials.gsub(uri_str, anonymous_uri_str) + end + str_with_no_credentials + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool.rb new file mode 100644 index 0000000000..fbcd26c765 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -0,0 +1,161 @@ +require_relative 'connection_pool/version' +require_relative 'connection_pool/timed_stack' + + +# Generic connection pool class for e.g. sharing a limited number of network connections +# among many threads. Note: Connections are lazily created. +# +# Example usage with block (faster): +# +# @pool = Bundler::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 = Bundler::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 +# +class Bundler::ConnectionPool + DEFAULTS = {size: 5, timeout: 5} + + class Error < RuntimeError + end + + def self.wrap(options, &block) + Wrapper.new(options, &block) + end + + def initialize(options = {}, &block) + raise ArgumentError, 'Connection pool requires a block' unless block + + options = DEFAULTS.merge(options) + + @size = options.fetch(:size) + @timeout = options.fetch(:timeout) + + @available = TimedStack.new(@size, &block) + @key = :"current-#{@available.object_id}" + @key_count = :"current-#{@available.object_id}-count" + end + +if Thread.respond_to?(:handle_interrupt) + + # MRI + 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 + +else + + # jruby 1.7.x + def with(options = {}) + conn = checkout(options) + begin + yield conn + ensure + checkin + end + end + +end + + 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) + end + end + + def checkin + if ::Thread.current[@key] + if ::Thread.current[@key_count] == 1 + @available.push(::Thread.current[@key]) + ::Thread.current[@key]= nil + else + ::Thread.current[@key_count]-= 1 + end + else + raise Bundler::ConnectionPool::Error, 'no connections are checked out' + end + + nil + end + + def shutdown(&block) + @available.shutdown(&block) + end + + # Size of this connection pool + def size + @size + end + + # Number of pool entries available for checkout at this instant. + def available + @available.length + end + + private + + class Wrapper < ::BasicObject + METHODS = [:with, :pool_shutdown] + + def initialize(options = {}, &block) + @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) } + 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 + + def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb new file mode 100644 index 0000000000..5a9c4a27bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb @@ -0,0 +1,66 @@ +# Global monotonic clock from Concurrent Ruby 1.0. +# Copyright (c) Jerry D'Antonio -- released under the MIT license. +# Slightly modified; used with permission. +# https://github.com/ruby-concurrency/concurrent-ruby + +require 'thread' + +class Bundler::ConnectionPool + + class_definition = Class.new do + + if defined?(Process::CLOCK_MONOTONIC) + + # @!visibility private + def get_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' + + # @!visibility private + def get_time + java.lang.System.nanoTime() / 1_000_000_000.0 + end + + else + + # @!visibility private + def initialize + @mutex = Mutex.new + @last_time = Time.now.to_f + end + + # @!visibility private + def get_time + @mutex.synchronize do + now = Time.now.to_f + if @last_time < now + @last_time = now + else # clock has moved back in time + @last_time += 0.000_001 + end + end + end + end + end + + ## + # Clock that cannot be set and represents monotonic time since + # some unspecified starting point. + # + # @!visibility private + GLOBAL_MONOTONIC_CLOCK = class_definition.new + private_constant :GLOBAL_MONOTONIC_CLOCK + + class << self + ## + # Returns the current time a tracked by the application monotonic clock. + # + # @return [Float] The current monotonic time when `since` not given else + # the elapsed monotonic time between `since` and the current time + def monotonic_time + GLOBAL_MONOTONIC_CLOCK.get_time + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb new file mode 100644 index 0000000000..f3fe1e04ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -0,0 +1,176 @@ +require 'thread' +require 'timeout' +require_relative 'monotonic_time' + +## +# Raised when you attempt to retrieve a connection from a pool that has been +# shut down. + +class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end + +## +# 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 Timeout::Error after 5 seconds + +class Bundler::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 = Mutex.new + @resource = 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 + @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 Timeout::Error 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 = Bundler::ConnectionPool.monotonic_time + timeout + @mutex.synchronize do + loop do + raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block + return fetch_connection(options) if connection_stored?(options) + + connection = try_create(options) + return connection if connection + + to_wait = deadline - Bundler::ConnectionPool.monotonic_time + raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0 + @resource.wait(@mutex, to_wait) + end + end + end + + ## + # Shuts down the TimedStack which prevents connections from being checked + # out. The +block+ is called once for each connection on the stack. + + def shutdown(&block) + raise ArgumentError, "shutdown must receive a block" unless block_given? + + @mutex.synchronize do + @shutdown_block = block + @resource.broadcast + + shutdown_connections + 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 + + private + + ## + # 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 + 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 connection_stored?(options) + conn = fetch_connection(options) + @shutdown_block.call(conn) + end + 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 + 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.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb new file mode 100644 index 0000000000..b149c0e242 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -0,0 +1,3 @@ +class Bundler::ConnectionPool + VERSION = "2.2.2" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/fileutils/lib/fileutils.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/fileutils/lib/fileutils.rb new file mode 100644 index 0000000000..8f8faf30c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -0,0 +1,1764 @@ +# frozen_string_literal: true + +begin + require 'rbconfig' +rescue LoadError + # for make mjit-headers +end + +# +# = fileutils.rb +# +# Copyright (c) 2000-2007 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the same terms of ruby. +# +# == module Bundler::FileUtils +# +# Namespace for several file utility methods for copying, moving, removing, etc. +# +# === Module Functions +# +# require 'bundler/vendor/fileutils/lib/fileutils' +# +# Bundler::FileUtils.cd(dir, **options) +# Bundler::FileUtils.cd(dir, **options) {|dir| block } +# Bundler::FileUtils.pwd() +# Bundler::FileUtils.mkdir(dir, **options) +# Bundler::FileUtils.mkdir(list, **options) +# Bundler::FileUtils.mkdir_p(dir, **options) +# Bundler::FileUtils.mkdir_p(list, **options) +# Bundler::FileUtils.rmdir(dir, **options) +# Bundler::FileUtils.rmdir(list, **options) +# Bundler::FileUtils.ln(target, link, **options) +# Bundler::FileUtils.ln(targets, dir, **options) +# Bundler::FileUtils.ln_s(target, link, **options) +# Bundler::FileUtils.ln_s(targets, dir, **options) +# Bundler::FileUtils.ln_sf(target, link, **options) +# Bundler::FileUtils.cp(src, dest, **options) +# Bundler::FileUtils.cp(list, dir, **options) +# Bundler::FileUtils.cp_r(src, dest, **options) +# Bundler::FileUtils.cp_r(list, dir, **options) +# Bundler::FileUtils.mv(src, dest, **options) +# Bundler::FileUtils.mv(list, dir, **options) +# Bundler::FileUtils.rm(list, **options) +# Bundler::FileUtils.rm_r(list, **options) +# Bundler::FileUtils.rm_rf(list, **options) +# Bundler::FileUtils.install(src, dest, **options) +# Bundler::FileUtils.chmod(mode, list, **options) +# Bundler::FileUtils.chmod_R(mode, list, **options) +# Bundler::FileUtils.chown(user, group, list, **options) +# Bundler::FileUtils.chown_R(user, group, list, **options) +# Bundler::FileUtils.touch(list, **options) +# +# Possible options are: +# +# :force :: forced operation (rewrite files if exist, remove +# directories if not empty, etc.); +# :verbose :: print command to be run, in bash syntax, before +# performing it; +# :preserve :: preserve object's group, user and modification +# time on copying; +# :noop :: no changes are made (usable in combination with +# :verbose which will print the command to run) +# +# Each method documents the options that it honours. See also ::commands, +# ::options and ::options_of methods to introspect which command have which +# options. +# +# All methods that have the concept of a "source" file or directory can take +# either one file or a list of files in that argument. See the method +# documentation for examples. +# +# There are some `low level' methods, which do not accept keyword arguments: +# +# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) +# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true) +# Bundler::FileUtils.copy_stream(srcstream, deststream) +# Bundler::FileUtils.remove_entry(path, force = false) +# Bundler::FileUtils.remove_entry_secure(path, force = false) +# Bundler::FileUtils.remove_file(path, force = false) +# Bundler::FileUtils.compare_file(path_a, path_b) +# Bundler::FileUtils.compare_stream(stream_a, stream_b) +# Bundler::FileUtils.uptodate?(file, cmp_list) +# +# == module Bundler::FileUtils::Verbose +# +# This module has all methods of Bundler::FileUtils module, but it outputs messages +# before acting. This equates to passing the :verbose flag to methods +# in Bundler::FileUtils. +# +# == module Bundler::FileUtils::NoWrite +# +# This module has all methods of Bundler::FileUtils module, but never changes +# files/directories. This equates to passing the :noop flag to methods +# in Bundler::FileUtils. +# +# == module Bundler::FileUtils::DryRun +# +# This module has all methods of Bundler::FileUtils module, but never changes +# files/directories. This equates to passing the :noop and +# :verbose flags to methods in Bundler::FileUtils. +# +module Bundler::FileUtils + VERSION = "1.4.1" + + def self.private_module_function(name) #:nodoc: + module_function name + private_class_method name + end + + # + # Returns the name of the current directory. + # + def pwd + Dir.pwd + end + module_function :pwd + + alias getwd pwd + module_function :getwd + + # + # Changes the current directory to the directory +dir+. + # + # If this method is called with block, resumes to the previous + # working directory after the block execution has finished. + # + # Bundler::FileUtils.cd('/') # change directory + # + # Bundler::FileUtils.cd('/', verbose: true) # change directory and report it + # + # Bundler::FileUtils.cd('/') do # change directory + # # ... # do something + # end # return to original directory + # + def cd(dir, verbose: nil, &block) # :yield: dir + fu_output_message "cd #{dir}" if verbose + result = Dir.chdir(dir, &block) + fu_output_message 'cd -' if verbose and block + result + end + module_function :cd + + alias chdir cd + module_function :chdir + + # + # Returns true if +new+ is newer than all +old_list+. + # Non-existent files are older than any file. + # + # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ + # system 'make hello.o' + # + def uptodate?(new, old_list) + return false unless File.exist?(new) + new_time = File.mtime(new) + old_list.each do |old| + if File.exist?(old) + return false unless new_time > File.mtime(old) + end + end + true + end + module_function :uptodate? + + def remove_trailing_slash(dir) #:nodoc: + dir == '/' ? dir : dir.chomp(?/) + end + private_module_function :remove_trailing_slash + + # + # Creates one or more directories. + # + # Bundler::FileUtils.mkdir 'test' + # Bundler::FileUtils.mkdir %w(tmp data) + # Bundler::FileUtils.mkdir 'notexist', noop: true # Does not really create. + # Bundler::FileUtils.mkdir 'tmp', mode: 0700 + # + def mkdir(list, mode: nil, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose + return if noop + + list.each do |dir| + fu_mkdir dir, mode + end + end + module_function :mkdir + + # + # Creates a directory and all its parent directories. + # For example, + # + # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby' + # + # causes to make following directories, if they do not exist. + # + # * /usr + # * /usr/local + # * /usr/local/lib + # * /usr/local/lib/ruby + # + # You can pass several directories at a time in a list. + # + def mkdir_p(list, mode: nil, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose + return *list if noop + + list.map {|path| remove_trailing_slash(path)}.each do |path| + # optimize for the most common case + begin + fu_mkdir path, mode + next + rescue SystemCallError + next if File.directory?(path) + end + + stack = [] + until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + stack.push path + path = File.dirname(path) + end + stack.pop # root directory should exist + stack.reverse_each do |dir| + begin + fu_mkdir dir, mode + rescue SystemCallError + raise unless File.directory?(dir) + end + end + end + + return *list + end + module_function :mkdir_p + + alias mkpath mkdir_p + alias makedirs mkdir_p + module_function :mkpath + module_function :makedirs + + def fu_mkdir(path, mode) #:nodoc: + path = remove_trailing_slash(path) + if mode + Dir.mkdir path, mode + File.chmod mode, path + else + Dir.mkdir path + end + end + private_module_function :fu_mkdir + + # + # Removes one or more directories. + # + # Bundler::FileUtils.rmdir 'somedir' + # Bundler::FileUtils.rmdir %w(somedir anydir otherdir) + # # Does not really remove directory; outputs message. + # Bundler::FileUtils.rmdir 'somedir', verbose: true, noop: true + # + def rmdir(list, parents: nil, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose + return if noop + list.each do |dir| + Dir.rmdir(dir = remove_trailing_slash(dir)) + if parents + begin + until (parent = File.dirname(dir)) == '.' or parent == dir + dir = parent + Dir.rmdir(dir) + end + rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT + end + end + end + end + module_function :rmdir + + # + # :call-seq: + # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil) + # + # In the first form, creates a hard link +link+ which points to +target+. + # If +link+ already exists, raises Errno::EEXIST. + # But if the +force+ option is set, overwrites +link+. + # + # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true + # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # + # In the second form, creates a link +dir/target+ pointing to +target+. + # In the third form, creates several hard links in the directory +dir+, + # pointing to each item in +targets+. + # If +dir+ is not a directory, raises Errno::ENOTDIR. + # + # Bundler::FileUtils.cd '/sbin' + # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # + def ln(src, dest, force: nil, noop: nil, verbose: nil) + fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if force + File.link s, d + end + end + module_function :ln + + alias link ln + module_function :link + + # + # Hard link +src+ to +dest+. If +src+ is a directory, this method links + # all its contents recursively. If +dest+ is a directory, links + # +src+ to +dest/src+. + # + # +src+ can be a list of files. + # + # If +dereference_root+ is true, this method dereference tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true + # Bundler::FileUtils.cp_lr 'lib/', site_ruby + '/mylib' + # + # # Examples of linking several files to target directory. + # Bundler::FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail' + # Bundler::FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true + # + # # If you want to link all contents of a directory instead of the + # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, + # # use the following code. + # Bundler::FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't. + # + def cp_lr(src, dest, noop: nil, verbose: nil, + dereference_root: true, remove_destination: false) + fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest(src, dest) do |s, d| + link_entry s, d, dereference_root, remove_destination + end + end + module_function :cp_lr + + # + # :call-seq: + # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil) + # + # In the first form, creates a symbolic link +link+ which points to +target+. + # If +link+ already exists, raises Errno::EEXIST. + # But if the force option is set, overwrites +link+. + # + # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' + # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true + # + # In the second form, creates a link +dir/target+ pointing to +target+. + # In the third form, creates several symbolic links in the directory +dir+, + # pointing to each item in +targets+. + # If +dir+ is not a directory, raises Errno::ENOTDIR. + # + # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin' + # + def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if force + File.symlink s, d + end + end + module_function :ln_s + + alias symlink ln_s + module_function :symlink + + # + # :call-seq: + # Bundler::FileUtils.ln_sf(*args) + # + # Same as + # + # Bundler::FileUtils.ln_s(*args, force: true) + # + def ln_sf(src, dest, noop: nil, verbose: nil) + ln_s src, dest, force: true, noop: noop, verbose: verbose + end + module_function :ln_sf + + # + # Hard links a file system entry +src+ to +dest+. + # If +src+ is a directory, this method links its contents recursively. + # + # Both of +src+ and +dest+ must be a path name. + # +src+ must exist, +dest+ must not exist. + # + # If +dereference_root+ is true, this method dereferences the tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + def link_entry(src, dest, dereference_root = false, remove_destination = false) + Entry_.new(src, nil, dereference_root).traverse do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) + ent.link destent.path + end + end + module_function :link_entry + + # + # Copies a file content +src+ to +dest+. If +dest+ is a directory, + # copies +src+ to +dest/src+. + # + # If +src+ is a list of files, then +dest+ must be a directory. + # + # Bundler::FileUtils.cp 'eval.c', 'eval.c.org' + # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' + # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true + # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # + def cp(src, dest, preserve: nil, noop: nil, verbose: nil) + fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest(src, dest) do |s, d| + copy_file s, d, preserve + end + end + module_function :cp + + alias copy cp + module_function :copy + + # + # Copies +src+ to +dest+. If +src+ is a directory, this method copies + # all its contents recursively. If +dest+ is a directory, copies + # +src+ to +dest/src+. + # + # +src+ can be a list of files. + # + # If +dereference_root+ is true, this method dereference tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + # # Installing Ruby library "mylib" under the site_ruby + # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true + # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib' + # + # # Examples of copying several files to target directory. + # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' + # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true + # + # # If you want to copy all contents of a directory instead of the + # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, + # # use following code. + # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, + # # but this doesn't. + # + def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil, + dereference_root: true, remove_destination: nil) + fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest(src, dest) do |s, d| + copy_entry s, d, preserve, dereference_root, remove_destination + end + end + module_function :cp_r + + # + # Copies a file system entry +src+ to +dest+. + # If +src+ is a directory, this method copies its contents recursively. + # This method preserves file types, c.f. symlink, directory... + # (FIFO, device files and etc. are not supported yet) + # + # Both of +src+ and +dest+ must be a path name. + # +src+ must exist, +dest+ must not exist. + # + # If +preserve+ is true, this method preserves owner, group, and + # modified time. Permissions are copied regardless +preserve+. + # + # If +dereference_root+ is true, this method dereference tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) + if dereference_root + src = File.realpath(src) + end + + Entry_.new(src, nil, false).wrap_traverse(proc do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path)) + ent.copy destent.path + end, proc do |ent| + destent = Entry_.new(dest, ent.rel, false) + ent.copy_metadata destent.path if preserve + end) + end + module_function :copy_entry + + # + # Copies file contents of +src+ to +dest+. + # Both of +src+ and +dest+ must be a path name. + # + def copy_file(src, dest, preserve = false, dereference = true) + ent = Entry_.new(src, nil, dereference) + ent.copy_file dest + ent.copy_metadata dest if preserve + end + module_function :copy_file + + # + # Copies stream +src+ to +dest+. + # +src+ must respond to #read(n) and + # +dest+ must respond to #write(str). + # + def copy_stream(src, dest) + IO.copy_stream(src, dest) + end + module_function :copy_stream + + # + # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different + # disk partition, the file is copied then the original file is removed. + # + # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb' + # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error + # + # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/' + # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true + # + def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil) + fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose + return if noop + fu_each_src_dest(src, dest) do |s, d| + destent = Entry_.new(d, nil, true) + begin + if destent.exist? + if destent.directory? + raise Errno::EEXIST, d + end + end + begin + File.rename s, d + rescue Errno::EXDEV, + Errno::EPERM # move from unencrypted to encrypted dir (ext4) + copy_entry s, d, true + if secure + remove_entry_secure s, force + else + remove_entry s, force + end + end + rescue SystemCallError + raise unless force + end + end + end + module_function :mv + + alias move mv + module_function :move + + # + # Remove file(s) specified in +list+. This method cannot remove directories. + # All StandardErrors are ignored when the :force option is set. + # + # Bundler::FileUtils.rm %w( junk.txt dust.txt ) + # Bundler::FileUtils.rm Dir.glob('*.so') + # Bundler::FileUtils.rm 'NotExistFile', force: true # never raises exception + # + def rm(list, force: nil, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose + return if noop + + list.each do |path| + remove_file path, force + end + end + module_function :rm + + alias remove rm + module_function :remove + + # + # Equivalent to + # + # Bundler::FileUtils.rm(list, force: true) + # + def rm_f(list, noop: nil, verbose: nil) + rm list, force: true, noop: noop, verbose: verbose + end + module_function :rm_f + + alias safe_unlink rm_f + module_function :safe_unlink + + # + # remove files +list+[0] +list+[1]... If +list+[n] is a directory, + # removes its all contents recursively. This method ignores + # StandardError when :force option is set. + # + # Bundler::FileUtils.rm_r Dir.glob('/tmp/*') + # Bundler::FileUtils.rm_r 'some_dir', force: true + # + # WARNING: This method causes local vulnerability + # if one of parent directories or removing directory tree are world + # writable (including /tmp, whose permission is 1777), and the current + # process has strong privilege such as Unix super user (root), and the + # system has symbolic link. For secure removing, read the documentation + # of remove_entry_secure carefully, and set :secure option to true. + # Default is secure: false. + # + # NOTE: This method calls remove_entry_secure if :secure option is set. + # See also remove_entry_secure. + # + def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil) + list = fu_list(list) + fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose + return if noop + list.each do |path| + if secure + remove_entry_secure path, force + else + remove_entry path, force + end + end + end + module_function :rm_r + + # + # Equivalent to + # + # Bundler::FileUtils.rm_r(list, force: true) + # + # WARNING: This method causes local vulnerability. + # Read the documentation of rm_r first. + # + def rm_rf(list, noop: nil, verbose: nil, secure: nil) + rm_r list, force: true, noop: noop, verbose: verbose, secure: secure + end + module_function :rm_rf + + alias rmtree rm_rf + module_function :rmtree + + # + # This method removes a file system entry +path+. +path+ shall be a + # regular file, a directory, or something. If +path+ is a directory, + # remove it recursively. This method is required to avoid TOCTTOU + # (time-of-check-to-time-of-use) local security vulnerability of rm_r. + # #rm_r causes security hole when: + # + # * Parent directory is world writable (including /tmp). + # * Removing directory tree includes world writable directory. + # * The system has symbolic link. + # + # To avoid this security hole, this method applies special preprocess. + # If +path+ is a directory, this method chown(2) and chmod(2) all + # removing directories. This requires the current process is the + # owner of the removing whole directory tree, or is the super user (root). + # + # WARNING: You must ensure that *ALL* parent directories cannot be + # moved by other untrusted users. For example, parent directories + # should not be owned by untrusted users, and should not be world + # writable except when the sticky bit set. + # + # WARNING: Only the owner of the removing directory tree, or Unix super + # user (root) should invoke this method. Otherwise this method does not + # work. + # + # For details of this security vulnerability, see Perl's case: + # + # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 + # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 + # + # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # + def remove_entry_secure(path, force = false) + unless fu_have_symlink? + remove_entry path, force + return + end + fullpath = File.expand_path(path) + st = File.lstat(fullpath) + unless st.directory? + File.unlink fullpath + return + end + # is a directory. + parent_st = File.stat(File.dirname(fullpath)) + unless parent_st.world_writable? + remove_entry path, force + return + end + unless parent_st.sticky? + raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" + end + + # freeze tree root + euid = Process.euid + dot_file = fullpath + "/." + begin + File.open(dot_file) {|f| + unless fu_stat_identical_entry?(st, f.stat) + # symlink (TOC-to-TOU attack?) + File.unlink fullpath + return + end + f.chown euid, -1 + f.chmod 0700 + } + rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs + File.lstat(dot_file).tap {|fstat| + unless fu_stat_identical_entry?(st, fstat) + # symlink (TOC-to-TOU attack?) + File.unlink fullpath + return + end + File.chown euid, -1, dot_file + File.chmod 0700, dot_file + } + end + + unless fu_stat_identical_entry?(st, File.lstat(fullpath)) + # TOC-to-TOU attack? + File.unlink fullpath + return + end + + # ---- tree root is frozen ---- + root = Entry_.new(path) + root.preorder_traverse do |ent| + if ent.directory? + ent.chown euid, -1 + ent.chmod 0700 + end + end + root.postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry_secure + + def fu_have_symlink? #:nodoc: + File.symlink nil, nil + rescue NotImplementedError + return false + rescue TypeError + return true + end + private_module_function :fu_have_symlink? + + def fu_stat_identical_entry?(a, b) #:nodoc: + a.dev == b.dev and a.ino == b.ino + end + private_module_function :fu_stat_identical_entry? + + # + # This method removes a file system entry +path+. + # +path+ might be a regular file, a directory, or something. + # If +path+ is a directory, remove it recursively. + # + # See also remove_entry_secure. + # + def remove_entry(path, force = false) + Entry_.new(path).postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry + + # + # Removes a file +path+. + # This method ignores StandardError if +force+ is true. + # + def remove_file(path, force = false) + Entry_.new(path).remove_file + rescue + raise unless force + end + module_function :remove_file + + # + # Removes a directory +dir+ and its contents recursively. + # This method ignores StandardError if +force+ is true. + # + def remove_dir(path, force = false) + remove_entry path, force # FIXME?? check if it is a directory + end + module_function :remove_dir + + # + # Returns true if the contents of a file +a+ and a file +b+ are identical. + # + # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true + # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false + # + def compare_file(a, b) + return false unless File.size(a) == File.size(b) + File.open(a, 'rb') {|fa| + File.open(b, 'rb') {|fb| + return compare_stream(fa, fb) + } + } + end + module_function :compare_file + + alias identical? compare_file + alias cmp compare_file + module_function :identical? + module_function :cmp + + # + # Returns true if the contents of a stream +a+ and +b+ are identical. + # + def compare_stream(a, b) + bsize = fu_stream_blksize(a, b) + + if RUBY_VERSION > "2.4" + sa = String.new(capacity: bsize) + sb = String.new(capacity: bsize) + else + sa = String.new + sb = String.new + end + + begin + a.read(bsize, sa) + b.read(bsize, sb) + return true if sa.empty? && sb.empty? + end while sa == sb + false + end + module_function :compare_stream + + # + # If +src+ is not same as +dest+, copies it and changes the permission + # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. + # This method removes destination before copy. + # + # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true + # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true + # + def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil, + noop: nil, verbose: nil) + if verbose + msg = +"install -c" + msg << ' -p' if preserve + msg << ' -m ' << mode_to_s(mode) if mode + msg << " -o #{owner}" if owner + msg << " -g #{group}" if group + msg << ' ' << [src,dest].flatten.join(' ') + fu_output_message msg + end + return if noop + uid = fu_get_uid(owner) + gid = fu_get_gid(group) + fu_each_src_dest(src, dest) do |s, d| + st = File.stat(s) + unless File.exist?(d) and compare_file(s, d) + remove_file d, true + copy_file s, d + File.utime st.atime, st.mtime, d if preserve + File.chmod fu_mode(mode, st), d if mode + File.chown uid, gid, d if uid or gid + end + end + end + module_function :install + + def user_mask(target) #:nodoc: + target.each_char.inject(0) do |mask, chr| + case chr + when "u" + mask | 04700 + when "g" + mask | 02070 + when "o" + mask | 01007 + when "a" + mask | 07777 + else + raise ArgumentError, "invalid `who' symbol in file mode: #{chr}" + end + end + end + private_module_function :user_mask + + def apply_mask(mode, user_mask, op, mode_mask) #:nodoc: + case op + when '=' + (mode & ~user_mask) | (user_mask & mode_mask) + when '+' + mode | (user_mask & mode_mask) + when '-' + mode & ~(user_mask & mode_mask) + end + end + private_module_function :apply_mask + + def symbolic_modes_to_i(mode_sym, path) #:nodoc: + mode = if File::Stat === path + path.mode + else + File.stat(path).mode + end + mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause| + target, *actions = clause.split(/([=+-])/) + raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty? + target = 'a' if target.empty? + user_mask = user_mask(target) + actions.each_slice(2) do |op, perm| + need_apply = op == '=' + mode_mask = (perm || '').each_char.inject(0) do |mask, chr| + case chr + when "r" + mask | 0444 + when "w" + mask | 0222 + when "x" + mask | 0111 + when "X" + if FileTest.directory? path + mask | 0111 + else + mask + end + when "s" + mask | 06000 + when "t" + mask | 01000 + when "u", "g", "o" + if mask.nonzero? + current_mode = apply_mask(current_mode, user_mask, op, mask) + end + need_apply = false + copy_mask = user_mask(chr) + (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111) + else + raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}" + end + end + + if mode_mask.nonzero? || need_apply + current_mode = apply_mask(current_mode, user_mask, op, mode_mask) + end + end + current_mode + end + end + private_module_function :symbolic_modes_to_i + + def fu_mode(mode, path) #:nodoc: + mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode + end + private_module_function :fu_mode + + def mode_to_s(mode) #:nodoc: + mode.is_a?(String) ? mode : "%o" % mode + end + private_module_function :mode_to_s + + # + # Changes permission bits on the named files (in +list+) to the bit pattern + # represented by +mode+. + # + # +mode+ is the symbolic and absolute mode can be used. + # + # Absolute mode is + # Bundler::FileUtils.chmod 0755, 'somecommand' + # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) + # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true + # + # Symbolic mode is + # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand' + # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) + # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true + # + # "a" :: is user, group, other mask. + # "u" :: is user's mask. + # "g" :: is group's mask. + # "o" :: is other's mask. + # "w" :: is write permission. + # "r" :: is read permission. + # "x" :: is execute permission. + # "X" :: + # is execute permission for directories only, must be used in conjunction with "+" + # "s" :: is uid, gid. + # "t" :: is sticky bit. + # "+" :: is added to a class given the specified mode. + # "-" :: Is removed from a given class given mode. + # "=" :: Is the exact nature of the class will be given a specified mode. + + def chmod(mode, list, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose + return if noop + list.each do |path| + Entry_.new(path).chmod(fu_mode(mode, path)) + end + end + module_function :chmod + + # + # Changes permission bits on the named files (in +list+) + # to the bit pattern represented by +mode+. + # + # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}" + # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" + # + def chmod_R(mode, list, noop: nil, verbose: nil, force: nil) + list = fu_list(list) + fu_output_message sprintf('chmod -R%s %s %s', + (force ? 'f' : ''), + mode_to_s(mode), list.join(' ')) if verbose + return if noop + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chmod(fu_mode(mode, ent.path)) + rescue + raise unless force + end + end + end + end + module_function :chmod_R + + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+. +user+ and +group+ + # may be an ID (Integer/String) or a name (String). + # If +user+ or +group+ is nil, this method does not change + # the attribute. + # + # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' + # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true + # + def chown(user, group, list, noop: nil, verbose: nil) + list = fu_list(list) + fu_output_message sprintf('chown %s %s', + (group ? "#{user}:#{group}" : user || ':'), + list.join(' ')) if verbose + return if noop + uid = fu_get_uid(user) + gid = fu_get_gid(group) + list.each do |path| + Entry_.new(path).chown uid, gid + end + end + module_function :chown + + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+ recursively. + # +user+ and +group+ may be an ID (Integer/String) or + # a name (String). If +user+ or +group+ is nil, this + # method does not change the attribute. + # + # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs' + # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true + # + def chown_R(user, group, list, noop: nil, verbose: nil, force: nil) + list = fu_list(list) + fu_output_message sprintf('chown -R%s %s %s', + (force ? 'f' : ''), + (group ? "#{user}:#{group}" : user || ':'), + list.join(' ')) if verbose + return if noop + uid = fu_get_uid(user) + gid = fu_get_gid(group) + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chown uid, gid + rescue + raise unless force + end + end + end + end + module_function :chown_R + + def fu_get_uid(user) #:nodoc: + return nil unless user + case user + when Integer + user + when /\A\d+\z/ + user.to_i + else + require 'etc' + Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil + end + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + return nil unless group + case group + when Integer + group + when /\A\d+\z/ + group.to_i + else + require 'etc' + Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil + end + end + private_module_function :fu_get_gid + + # + # Updates modification time (mtime) and access time (atime) of file(s) in + # +list+. Files are created if they don't exist. + # + # Bundler::FileUtils.touch 'timestamp' + # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make' + # + def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil) + list = fu_list(list) + t = mtime + if verbose + fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}" + end + return if noop + list.each do |path| + created = nocreate + begin + File.utime(t, t, path) + rescue Errno::ENOENT + raise if created + File.open(path, 'a') { + ; + } + created = true + retry if t + end + end + end + module_function :touch + + private + + module StreamUtils_ + private + + case (defined?(::RbConfig) ? ::RbConfig::CONFIG['host_os'] : ::RUBY_PLATFORM) + when /mswin|mingw/ + def fu_windows?; true end + else + def fu_windows?; false end + end + + def fu_copy_stream0(src, dest, blksize = nil) #:nodoc: + IO.copy_stream(src, dest) + end + + def fu_stream_blksize(*streams) + streams.each do |s| + next unless s.respond_to?(:stat) + size = fu_blksize(s.stat) + return size if size + end + fu_default_blksize() + end + + def fu_blksize(st) + s = st.blksize + return nil unless s + return nil if s == 0 + s + end + + def fu_default_blksize + 1024 + end + end + + include StreamUtils_ + extend StreamUtils_ + + class Entry_ #:nodoc: internal use only + include StreamUtils_ + + def initialize(a, b = nil, deref = false) + @prefix = @rel = @path = nil + if b + @prefix = a + @rel = b + else + @path = a + end + @deref = deref + @stat = nil + @lstat = nil + end + + def inspect + "\#<#{self.class} #{path()}>" + end + + def path + if @path + File.path(@path) + else + join(@prefix, @rel) + end + end + + def prefix + @prefix || @path + end + + def rel + @rel + end + + def dereference? + @deref + end + + def exist? + begin + lstat + true + rescue Errno::ENOENT + false + end + end + + def file? + s = lstat! + s and s.file? + end + + def directory? + s = lstat! + s and s.directory? + end + + def symlink? + s = lstat! + s and s.symlink? + end + + def chardev? + s = lstat! + s and s.chardev? + end + + def blockdev? + s = lstat! + s and s.blockdev? + end + + def socket? + s = lstat! + s and s.socket? + end + + def pipe? + s = lstat! + s and s.pipe? + end + + S_IF_DOOR = 0xD000 + + def door? + s = lstat! + s and (s.mode & 0xF000 == S_IF_DOOR) + end + + def entries + opts = {} + opts[:encoding] = ::Encoding::UTF_8 if fu_windows? + + files = if Dir.respond_to?(:children) + Dir.children(path, **opts) + else + Dir.entries(path(), **opts) + .reject {|n| n == '.' or n == '..' } + end + + untaint = RUBY_VERSION < '2.7' + files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) } + end + + def stat + return @stat if @stat + if lstat() and lstat().symlink? + @stat = File.stat(path()) + else + @stat = lstat() + end + @stat + end + + def stat! + return @stat if @stat + if lstat! and lstat!.symlink? + @stat = File.stat(path()) + else + @stat = lstat! + end + @stat + rescue SystemCallError + nil + end + + def lstat + if dereference? + @lstat ||= File.stat(path()) + else + @lstat ||= File.lstat(path()) + end + end + + def lstat! + lstat() + rescue SystemCallError + nil + end + + def chmod(mode) + if symlink? + File.lchmod mode, path() if have_lchmod? + else + File.chmod mode, path() + end + end + + def chown(uid, gid) + if symlink? + File.lchown uid, gid, path() if have_lchown? + else + File.chown uid, gid, path() + end + end + + def link(dest) + case + when directory? + if !File.exist?(dest) and descendant_directory?(dest, path) + raise ArgumentError, "cannot link directory %s to itself %s" % [path, dest] + end + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + else + File.link path(), dest + end + end + + def copy(dest) + lstat + case + when file? + copy_file dest + when directory? + if !File.exist?(dest) and descendant_directory?(dest, path) + raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest] + end + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + when symlink? + File.symlink File.readlink(path()), dest + when chardev?, blockdev? + raise "cannot handle device file" + when socket? + begin + require 'socket' + rescue LoadError + raise "cannot handle socket" + else + raise "cannot handle socket" unless defined?(UNIXServer) + end + UNIXServer.new(dest).close + File.chmod lstat().mode, dest + when pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + File.mkfifo dest, lstat().mode + when door? + raise "cannot handle door: #{path()}" + else + raise "unknown file type: #{path()}" + end + end + + def copy_file(dest) + File.open(path()) do |s| + File.open(dest, 'wb', s.stat.mode) do |f| + IO.copy_stream(s, f) + end + end + end + + def copy_metadata(path) + st = lstat() + if !st.symlink? + File.utime st.atime, st.mtime, path + end + mode = st.mode + begin + if st.symlink? + begin + File.lchown st.uid, st.gid, path + rescue NotImplementedError + end + else + File.chown st.uid, st.gid, path + end + rescue Errno::EPERM, Errno::EACCES + # clear setuid/setgid + mode &= 01777 + end + if st.symlink? + begin + File.lchmod mode, path + rescue NotImplementedError + end + else + File.chmod mode, path + end + end + + def remove + if directory? + remove_dir1 + else + remove_file + end + end + + def remove_dir1 + platform_support { + Dir.rmdir path().chomp(?/) + } + end + + def remove_file + platform_support { + File.unlink path + } + end + + def platform_support + return yield unless fu_windows? + first_time_p = true + begin + yield + rescue Errno::ENOENT + raise + rescue => err + if first_time_p + first_time_p = false + begin + File.chmod 0700, path() # Windows does not have symlink + retry + rescue SystemCallError + end + end + raise err + end + end + + def preorder_traverse + stack = [self] + while ent = stack.pop + yield ent + stack.concat ent.entries.reverse if ent.directory? + end + end + + alias traverse preorder_traverse + + def postorder_traverse + if directory? + entries().each do |ent| + ent.postorder_traverse do |e| + yield e + end + end + end + ensure + yield self + end + + def wrap_traverse(pre, post) + pre.call self + if directory? + entries.each do |ent| + ent.wrap_traverse pre, post + end + end + post.call self + end + + private + + @@fileutils_rb_have_lchmod = nil + + def have_lchmod? + # This is not MT-safe, but it does not matter. + if @@fileutils_rb_have_lchmod == nil + @@fileutils_rb_have_lchmod = check_have_lchmod? + end + @@fileutils_rb_have_lchmod + end + + def check_have_lchmod? + return false unless File.respond_to?(:lchmod) + File.lchmod 0 + return true + rescue NotImplementedError + return false + end + + @@fileutils_rb_have_lchown = nil + + def have_lchown? + # This is not MT-safe, but it does not matter. + if @@fileutils_rb_have_lchown == nil + @@fileutils_rb_have_lchown = check_have_lchown? + end + @@fileutils_rb_have_lchown + end + + def check_have_lchown? + return false unless File.respond_to?(:lchown) + File.lchown nil, nil + return true + rescue NotImplementedError + return false + end + + def join(dir, base) + return File.path(dir) if not base or base == '.' + return File.path(base) if not dir or dir == '.' + File.join(dir, base) + end + + if File::ALT_SEPARATOR + DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)" + else + DIRECTORY_TERM = "(?=/|\\z)" + end + + def descendant_directory?(descendant, ascendant) + if File::FNM_SYSCASE.nonzero? + File.expand_path(File.dirname(descendant)).casecmp(File.expand_path(ascendant)) == 0 + else + File.expand_path(File.dirname(descendant)) == File.expand_path(ascendant) + end + end + end # class Entry_ + + def fu_list(arg) #:nodoc: + [arg].flatten.map {|path| File.path(path) } + end + private_module_function :fu_list + + def fu_each_src_dest(src, dest) #:nodoc: + fu_each_src_dest0(src, dest) do |s, d| + raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) + yield s, d + end + end + private_module_function :fu_each_src_dest + + def fu_each_src_dest0(src, dest) #:nodoc: + if tmp = Array.try_convert(src) + tmp.each do |s| + s = File.path(s) + yield s, File.join(dest, File.basename(s)) + end + else + src = File.path(src) + if File.directory?(dest) + yield src, File.join(dest, File.basename(src)) + else + yield src, File.path(dest) + end + end + end + private_module_function :fu_each_src_dest0 + + def fu_same?(a, b) #:nodoc: + File.identical?(a, b) + end + private_module_function :fu_same? + + def fu_output_message(msg) #:nodoc: + output = @fileutils_output if defined?(@fileutils_output) + output ||= $stderr + if defined?(@fileutils_label) + msg = @fileutils_label + msg + end + output.puts msg + end + private_module_function :fu_output_message + + # This hash table holds command options. + OPT_TABLE = {} #:nodoc: internal use only + (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name| + (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact! + tbl + } + + public + + # + # Returns an Array of names of high-level methods that accept any keyword + # arguments. + # + # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # + def self.commands + OPT_TABLE.keys + end + + # + # Returns an Array of option names. + # + # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # + def self.options + OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } + end + + # + # Returns true if the method +mid+ have an option +opt+. + # + # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true + # p Bundler::FileUtils.have_option?(:rm, :force) #=> true + # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false + # + def self.have_option?(mid, opt) + li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" + li.include?(opt) + end + + # + # Returns an Array of option names of the method +mid+. + # + # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"] + # + def self.options_of(mid) + OPT_TABLE[mid.to_s].map {|sym| sym.to_s } + end + + # + # Returns an Array of methods names which have the option +opt+. + # + # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # + def self.collect_method(opt) + OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } + end + + private + + LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern) # :nodoc: + module LowMethods # :nodoc: internal use only + private + def _do_nothing(*)end + ::Bundler::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing} + end + + METHODS = singleton_methods() - [:private_module_function, # :nodoc: + :commands, :options, :have_option?, :options_of, :collect_method] + + # + # This module has all methods of Bundler::FileUtils module, but it outputs messages + # before acting. This equates to passing the :verbose flag to + # methods in Bundler::FileUtils. + # + module Verbose + include Bundler::FileUtils + names = ::Bundler::FileUtils.collect_method(:verbose) + names.each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args, **options) + super(*args, **options, verbose: true) + end + EOS + end + private(*names) + extend self + class << self + public(*::Bundler::FileUtils::METHODS) + end + end + + # + # This module has all methods of Bundler::FileUtils module, but never changes + # files/directories. This equates to passing the :noop flag + # to methods in Bundler::FileUtils. + # + module NoWrite + include Bundler::FileUtils + include LowMethods + names = ::Bundler::FileUtils.collect_method(:noop) + names.each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args, **options) + super(*args, **options, noop: true) + end + EOS + end + private(*names) + extend self + class << self + public(*::Bundler::FileUtils::METHODS) + end + end + + # + # This module has all methods of Bundler::FileUtils module, but never changes + # files/directories, with printing message before acting. + # This equates to passing the :noop and :verbose flag + # to methods in Bundler::FileUtils. + # + module DryRun + include Bundler::FileUtils + include LowMethods + names = ::Bundler::FileUtils.collect_method(:noop) + names.each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args, **options) + super(*args, **options, noop: true, verbose: true) + end + EOS + end + private(*names) + extend self + class << self + public(*::Bundler::FileUtils::METHODS) + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo.rb new file mode 100644 index 0000000000..a52b96deaf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative 'molinillo/gem_metadata' +require_relative 'molinillo/errors' +require_relative 'molinillo/resolver' +require_relative 'molinillo/modules/ui' +require_relative 'molinillo/modules/specification_provider' + +# Bundler::Molinillo is a generic dependency resolution algorithm. +module Bundler::Molinillo +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb new file mode 100644 index 0000000000..bcacf35243 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # @!visibility private + module Delegates + # Delegates all {Bundler::Molinillo::ResolutionState} methods to a `#state` property. + module ResolutionState + # (see Bundler::Molinillo::ResolutionState#name) + def name + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.name + end + + # (see Bundler::Molinillo::ResolutionState#requirements) + def requirements + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.requirements + end + + # (see Bundler::Molinillo::ResolutionState#activated) + def activated + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.activated + end + + # (see Bundler::Molinillo::ResolutionState#requirement) + def requirement + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.requirement + end + + # (see Bundler::Molinillo::ResolutionState#possibilities) + def possibilities + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.possibilities + end + + # (see Bundler::Molinillo::ResolutionState#depth) + def depth + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.depth + end + + # (see Bundler::Molinillo::ResolutionState#conflicts) + def conflicts + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.conflicts + end + + # (see Bundler::Molinillo::ResolutionState#unused_unwind_options) + def unused_unwind_options + current_state = state || Bundler::Molinillo::ResolutionState.empty + current_state.unused_unwind_options + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb new file mode 100644 index 0000000000..f8c695c1ed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + module Delegates + # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a + # `#specification_provider` property. + module SpecificationProvider + # (see Bundler::Molinillo::SpecificationProvider#search_for) + def search_for(dependency) + with_no_such_dependency_error_handling do + specification_provider.search_for(dependency) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#dependencies_for) + def dependencies_for(specification) + with_no_such_dependency_error_handling do + specification_provider.dependencies_for(specification) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#requirement_satisfied_by?) + def requirement_satisfied_by?(requirement, activated, spec) + with_no_such_dependency_error_handling do + specification_provider.requirement_satisfied_by?(requirement, activated, spec) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#dependencies_equal?) + def dependencies_equal?(dependencies, other_dependencies) + with_no_such_dependency_error_handling do + specification_provider.dependencies_equal?(dependencies, other_dependencies) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#name_for) + def name_for(dependency) + with_no_such_dependency_error_handling do + specification_provider.name_for(dependency) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#name_for_explicit_dependency_source) + def name_for_explicit_dependency_source + with_no_such_dependency_error_handling do + specification_provider.name_for_explicit_dependency_source + end + end + + # (see Bundler::Molinillo::SpecificationProvider#name_for_locking_dependency_source) + def name_for_locking_dependency_source + with_no_such_dependency_error_handling do + specification_provider.name_for_locking_dependency_source + end + end + + # (see Bundler::Molinillo::SpecificationProvider#sort_dependencies) + def sort_dependencies(dependencies, activated, conflicts) + with_no_such_dependency_error_handling do + specification_provider.sort_dependencies(dependencies, activated, conflicts) + end + end + + # (see Bundler::Molinillo::SpecificationProvider#allow_missing?) + def allow_missing?(dependency) + with_no_such_dependency_error_handling do + specification_provider.allow_missing?(dependency) + end + end + + private + + # Ensures any raised {NoSuchDependencyError} has its + # {NoSuchDependencyError#required_by} set. + # @yield + def with_no_such_dependency_error_handling + yield + rescue NoSuchDependencyError => error + if state + vertex = activated.vertex_named(name_for(error.dependency)) + error.required_by += vertex.incoming_edges.map { |e| e.origin.name } + error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? + end + raise + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb new file mode 100644 index 0000000000..936399ed2f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require 'tsort' + +require_relative 'dependency_graph/log' +require_relative 'dependency_graph/vertex' + +module Bundler::Molinillo + # A directed acyclic graph that is tuned to hold named dependencies + class DependencyGraph + include Enumerable + + # Enumerates through the vertices of the graph. + # @return [Array] The graph's vertices. + def each + return vertices.values.each unless block_given? + vertices.values.each { |v| yield v } + end + + include TSort + + # @!visibility private + alias tsort_each_node each + + # @!visibility private + def tsort_each_child(vertex, &block) + vertex.successors.each(&block) + end + + # Topologically sorts the given vertices. + # @param [Enumerable] vertices the vertices to be sorted, which must + # all belong to the same graph. + # @return [Array] The sorted vertices. + def self.tsort(vertices) + TSort.tsort( + lambda { |b| vertices.each(&b) }, + lambda { |v, &b| (v.successors & vertices).each(&b) } + ) + end + + # A directed edge of a {DependencyGraph} + # @attr [Vertex] origin The origin of the directed edge + # @attr [Vertex] destination The destination of the directed edge + # @attr [Object] requirement The requirement the directed edge represents + Edge = Struct.new(:origin, :destination, :requirement) + + # @return [{String => Vertex}] the vertices of the dependency graph, keyed + # by {Vertex#name} + attr_reader :vertices + + # @return [Log] the op log for this graph + attr_reader :log + + # Initializes an empty dependency graph + def initialize + @vertices = {} + @log = Log.new + end + + # Tags the current state of the dependency as the given tag + # @param [Object] tag an opaque tag for the current state of the graph + # @return [Void] + def tag(tag) + log.tag(self, tag) + end + + # Rewinds the graph to the state tagged as `tag` + # @param [Object] tag the tag to rewind to + # @return [Void] + def rewind_to(tag) + log.rewind_to(self, tag) + end + + # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} + # are properly copied. + # @param [DependencyGraph] other the graph to copy. + def initialize_copy(other) + super + @vertices = {} + @log = other.log.dup + traverse = lambda do |new_v, old_v| + return if new_v.outgoing_edges.size == old_v.outgoing_edges.size + old_v.outgoing_edges.each do |edge| + destination = add_vertex(edge.destination.name, edge.destination.payload) + add_edge_no_circular(new_v, destination, edge.requirement) + traverse.call(destination, edge.destination) + end + end + other.vertices.each do |name, vertex| + new_vertex = add_vertex(name, vertex.payload, vertex.root?) + new_vertex.explicit_requirements.replace(vertex.explicit_requirements) + traverse.call(new_vertex, vertex) + end + end + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{vertices.values.inspect}" + end + + # @param [Hash] options options for dot output. + # @return [String] Returns a dot format representation of the graph + def to_dot(options = {}) + edge_label = options.delete(:edge_label) + raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? + + dot_vertices = [] + dot_edges = [] + vertices.each do |n, v| + dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" + v.outgoing_edges.each do |e| + label = edge_label ? edge_label.call(e) : e.requirement + dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" + end + end + + dot_vertices.uniq! + dot_vertices.sort! + dot_edges.uniq! + dot_edges.sort! + + dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') + dot.join("\n") + end + + # @param [DependencyGraph] other + # @return [Boolean] whether the two dependency graphs are equal, determined + # by a recursive traversal of each {#root_vertices} and its + # {Vertex#successors} + def ==(other) + return false unless other + return true if equal?(other) + vertices.each do |name, vertex| + other_vertex = other.vertex_named(name) + return false unless other_vertex + return false unless vertex.payload == other_vertex.payload + return false unless other_vertex.successors.to_set == vertex.successors.to_set + end + end + + # @param [String] name + # @param [Object] payload + # @param [Array] parent_names + # @param [Object] requirement the requirement that is requiring the child + # @return [void] + def add_child_vertex(name, payload, parent_names, requirement) + root = !parent_names.delete(nil) { true } + vertex = add_vertex(name, payload, root) + vertex.explicit_requirements << requirement if root + parent_names.each do |parent_name| + parent_vertex = vertex_named(parent_name) + add_edge(parent_vertex, vertex, requirement) + end + vertex + end + + # Adds a vertex with the given name, or updates the existing one. + # @param [String] name + # @param [Object] payload + # @return [Vertex] the vertex that was added to `self` + def add_vertex(name, payload, root = false) + log.add_vertex(self, name, payload, root) + end + + # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively + # removing any non-root vertices that were orphaned in the process + # @param [String] name + # @return [Array] the vertices which have been detached + def detach_vertex_named(name) + log.detach_vertex_named(self, name) + end + + # @param [String] name + # @return [Vertex,nil] the vertex with the given name + def vertex_named(name) + vertices[name] + end + + # @param [String] name + # @return [Vertex,nil] the root vertex with the given name + def root_vertex_named(name) + vertex = vertex_named(name) + vertex if vertex && vertex.root? + end + + # Adds a new {Edge} to the dependency graph + # @param [Vertex] origin + # @param [Vertex] destination + # @param [Object] requirement the requirement that this edge represents + # @return [Edge] the added edge + def add_edge(origin, destination, requirement) + if destination.path_to?(origin) + raise CircularDependencyError.new(path(destination, origin)) + end + add_edge_no_circular(origin, destination, requirement) + end + + # Deletes an {Edge} from the dependency graph + # @param [Edge] edge + # @return [Void] + def delete_edge(edge) + log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) + end + + # Sets the payload of the vertex with the given name + # @param [String] name the name of the vertex + # @param [Object] payload the payload + # @return [Void] + def set_payload(name, payload) + log.set_payload(self, name, payload) + end + + private + + # Adds a new {Edge} to the dependency graph without checking for + # circularity. + # @param (see #add_edge) + # @return (see #add_edge) + def add_edge_no_circular(origin, destination, requirement) + log.add_edge_no_circular(self, origin.name, destination.name, requirement) + end + + # Returns the path between two vertices + # @raise [ArgumentError] if there is no path between the vertices + # @param [Vertex] from + # @param [Vertex] to + # @return [Array] the shortest path from `from` to `to` + def path(from, to) + distances = Hash.new(vertices.size + 1) + distances[from.name] = 0 + predecessors = {} + each do |vertex| + vertex.successors.each do |successor| + if distances[successor.name] > distances[vertex.name] + 1 + distances[successor.name] = distances[vertex.name] + 1 + predecessors[successor] = vertex + end + end + end + + path = [to] + while before = predecessors[to] + path << before + to = before + break if to == from + end + + unless path.last.equal?(from) + raise ArgumentError, "There is no path from #{from.name} to #{to.name}" + end + + path.reverse + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb new file mode 100644 index 0000000000..c04c7eec9c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + class DependencyGraph + # An action that modifies a {DependencyGraph} that is reversible. + # @abstract + class Action + # rubocop:disable Lint/UnusedMethodArgument + + # @return [Symbol] The name of the action. + def self.action_name + raise 'Abstract' + end + + # Performs the action on the given graph. + # @param [DependencyGraph] graph the graph to perform the action on. + # @return [Void] + def up(graph) + raise 'Abstract' + end + + # Reverses the action on the given graph. + # @param [DependencyGraph] graph the graph to reverse the action on. + # @return [Void] + def down(graph) + raise 'Abstract' + end + + # @return [Action,Nil] The previous action + attr_accessor :previous + + # @return [Action,Nil] The next action + attr_accessor :next + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb new file mode 100644 index 0000000000..946a08236e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#add_edge_no_circular) + class AddEdgeNoCircular < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges << edge + edge.destination.incoming_edges << edge + edge + end + + # (see Action#down) + def down(graph) + edge = make_edge(graph) + delete_first(edge.origin.outgoing_edges, edge) + delete_first(edge.destination.incoming_edges, edge) + end + + # @!group AddEdgeNoCircular + + # @return [String] the name of the origin of the edge + attr_reader :origin + + # @return [String] the name of the destination of the edge + attr_reader :destination + + # @return [Object] the requirement that the edge represents + attr_reader :requirement + + # @param [DependencyGraph] graph the graph to find vertices from + # @return [Edge] The edge this action adds + def make_edge(graph) + Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) + end + + # Initialize an action to add an edge to a dependency graph + # @param [String] origin the name of the origin of the edge + # @param [String] destination the name of the destination of the edge + # @param [Object] requirement the requirement that the edge represents + def initialize(origin, destination, requirement) + @origin = origin + @destination = destination + @requirement = requirement + end + + private + + def delete_first(array, item) + return unless index = array.index(item) + array.delete_at(index) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb new file mode 100644 index 0000000000..483527daf8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#add_vertex) + class AddVertex < Action # :nodoc: + # @!group Action + + # (see Action.action_name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + if existing = graph.vertices[name] + @existing_payload = existing.payload + @existing_root = existing.root + end + vertex = existing || Vertex.new(name, payload) + graph.vertices[vertex.name] = vertex + vertex.payload ||= payload + vertex.root ||= root + vertex + end + + # (see Action#down) + def down(graph) + if defined?(@existing_payload) + vertex = graph.vertices[name] + vertex.payload = @existing_payload + vertex.root = @existing_root + else + graph.vertices.delete(name) + end + end + + # @!group AddVertex + + # @return [String] the name of the vertex + attr_reader :name + + # @return [Object] the payload for the vertex + attr_reader :payload + + # @return [Boolean] whether the vertex is root or not + attr_reader :root + + # Initialize an action to add a vertex to a dependency graph + # @param [String] name the name of the vertex + # @param [Object] payload the payload for the vertex + # @param [Boolean] root whether the vertex is root or not + def initialize(name, payload, root) + @name = name + @payload = payload + @root = root + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb new file mode 100644 index 0000000000..d81940585a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#delete_edge) + class DeleteEdge < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :delete_edge + end + + # (see Action#up) + def up(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges.delete(edge) + edge.destination.incoming_edges.delete(edge) + end + + # (see Action#down) + def down(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges << edge + edge.destination.incoming_edges << edge + edge + end + + # @!group DeleteEdge + + # @return [String] the name of the origin of the edge + attr_reader :origin_name + + # @return [String] the name of the destination of the edge + attr_reader :destination_name + + # @return [Object] the requirement that the edge represents + attr_reader :requirement + + # @param [DependencyGraph] graph the graph to find vertices from + # @return [Edge] The edge this action adds + def make_edge(graph) + Edge.new( + graph.vertex_named(origin_name), + graph.vertex_named(destination_name), + requirement + ) + end + + # Initialize an action to add an edge to a dependency graph + # @param [String] origin_name the name of the origin of the edge + # @param [String] destination_name the name of the destination of the edge + # @param [Object] requirement the requirement that the edge represents + def initialize(origin_name, destination_name, requirement) + @origin_name = origin_name + @destination_name = destination_name + @requirement = requirement + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb new file mode 100644 index 0000000000..36fce7c526 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#detach_vertex_named + class DetachVertexNamed < Action + # @!group Action + + # (see Action#name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + return [] unless @vertex = graph.vertices.delete(name) + + removed_vertices = [@vertex] + @vertex.outgoing_edges.each do |e| + v = e.destination + v.incoming_edges.delete(e) + if !v.root? && v.incoming_edges.empty? + removed_vertices.concat graph.detach_vertex_named(v.name) + end + end + + @vertex.incoming_edges.each do |e| + v = e.origin + v.outgoing_edges.delete(e) + end + + removed_vertices + end + + # (see Action#down) + def down(graph) + return unless @vertex + graph.vertices[@vertex.name] = @vertex + @vertex.outgoing_edges.each do |e| + e.destination.incoming_edges << e + end + @vertex.incoming_edges.each do |e| + e.origin.outgoing_edges << e + end + end + + # @!group DetachVertexNamed + + # @return [String] the name of the vertex to detach + attr_reader :name + + # Initialize an action to detach a vertex from a dependency graph + # @param [String] name the name of the vertex to detach + def initialize(name) + @name = name + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb new file mode 100644 index 0000000000..6f0de19886 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require_relative 'add_edge_no_circular' +require_relative 'add_vertex' +require_relative 'delete_edge' +require_relative 'detach_vertex_named' +require_relative 'set_payload' +require_relative 'tag' + +module Bundler::Molinillo + class DependencyGraph + # A log for dependency graph actions + class Log + # Initializes an empty log + def initialize + @current_action = @first_action = nil + end + + # @!macro [new] action + # {include:DependencyGraph#$0} + # @param [Graph] graph the graph to perform the action on + # @param (see DependencyGraph#$0) + # @return (see DependencyGraph#$0) + + # @macro action + def tag(graph, tag) + push_action(graph, Tag.new(tag)) + end + + # @macro action + def add_vertex(graph, name, payload, root) + push_action(graph, AddVertex.new(name, payload, root)) + end + + # @macro action + def detach_vertex_named(graph, name) + push_action(graph, DetachVertexNamed.new(name)) + end + + # @macro action + def add_edge_no_circular(graph, origin, destination, requirement) + push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) + end + + # {include:DependencyGraph#delete_edge} + # @param [Graph] graph the graph to perform the action on + # @param [String] origin_name + # @param [String] destination_name + # @param [Object] requirement + # @return (see DependencyGraph#delete_edge) + def delete_edge(graph, origin_name, destination_name, requirement) + push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) + end + + # @macro action + def set_payload(graph, name, payload) + push_action(graph, SetPayload.new(name, payload)) + end + + # Pops the most recent action from the log and undoes the action + # @param [DependencyGraph] graph + # @return [Action] the action that was popped off the log + def pop!(graph) + return unless action = @current_action + unless @current_action = action.previous + @first_action = nil + end + action.down(graph) + action + end + + extend Enumerable + + # @!visibility private + # Enumerates each action in the log + # @yield [Action] + def each + return enum_for unless block_given? + action = @first_action + loop do + break unless action + yield action + action = action.next + end + self + end + + # @!visibility private + # Enumerates each action in the log in reverse order + # @yield [Action] + def reverse_each + return enum_for(:reverse_each) unless block_given? + action = @current_action + loop do + break unless action + yield action + action = action.previous + end + self + end + + # @macro action + def rewind_to(graph, tag) + loop do + action = pop!(graph) + raise "No tag #{tag.inspect} found" unless action + break if action.class.action_name == :tag && action.tag == tag + end + end + + private + + # Adds the given action to the log, running the action + # @param [DependencyGraph] graph + # @param [Action] action + # @return The value returned by `action.up` + def push_action(graph, action) + action.previous = @current_action + @current_action.next = action if @current_action + @current_action = action + @first_action ||= action + action.up(graph) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb new file mode 100644 index 0000000000..2e9b90e6cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#set_payload + class SetPayload < Action # :nodoc: + # @!group Action + + # (see Action.action_name) + def self.action_name + :set_payload + end + + # (see Action#up) + def up(graph) + vertex = graph.vertex_named(name) + @old_payload = vertex.payload + vertex.payload = payload + end + + # (see Action#down) + def down(graph) + graph.vertex_named(name).payload = @old_payload + end + + # @!group SetPayload + + # @return [String] the name of the vertex + attr_reader :name + + # @return [Object] the payload for the vertex + attr_reader :payload + + # Initialize an action to add set the payload for a vertex in a dependency + # graph + # @param [String] name the name of the vertex + # @param [Object] payload the payload for the vertex + def initialize(name, payload) + @name = name + @payload = payload + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb new file mode 100644 index 0000000000..5b5da3e4f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#tag + class Tag < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :tag + end + + # (see Action#up) + def up(graph) + end + + # (see Action#down) + def down(graph) + end + + # @!group Tag + + # @return [Object] An opaque tag + attr_reader :tag + + # Initialize an action to tag a state of a dependency graph + # @param [Object] tag an opaque tag + def initialize(tag) + @tag = tag + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb new file mode 100644 index 0000000000..1185a8ab05 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + class DependencyGraph + # A vertex in a {DependencyGraph} that encapsulates a {#name} and a + # {#payload} + class Vertex + # @return [String] the name of the vertex + attr_accessor :name + + # @return [Object] the payload the vertex holds + attr_accessor :payload + + # @return [Array] the explicit requirements that required + # this vertex + attr_reader :explicit_requirements + + # @return [Boolean] whether the vertex is considered a root vertex + attr_accessor :root + alias root? root + + # Initializes a vertex with the given name and payload. + # @param [String] name see {#name} + # @param [Object] payload see {#payload} + def initialize(name, payload) + @name = name.frozen? ? name : name.dup.freeze + @payload = payload + @explicit_requirements = [] + @outgoing_edges = [] + @incoming_edges = [] + end + + # @return [Array] all of the requirements that required + # this vertex + def requirements + (incoming_edges.map(&:requirement) + explicit_requirements).uniq + end + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#origin} + attr_accessor :outgoing_edges + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#destination} + attr_accessor :incoming_edges + + # @return [Array] the vertices of {#graph} that have an edge with + # `self` as their {Edge#destination} + def predecessors + incoming_edges.map(&:origin) + end + + # @return [Set] the vertices of {#graph} where `self` is a + # {#descendent?} + def recursive_predecessors + _recursive_predecessors + end + + # @param [Set] vertices the set to add the predecessors to + # @return [Set] the vertices of {#graph} where `self` is a + # {#descendent?} + def _recursive_predecessors(vertices = new_vertex_set) + incoming_edges.each do |edge| + vertex = edge.origin + next unless vertices.add?(vertex) + vertex._recursive_predecessors(vertices) + end + + vertices + end + protected :_recursive_predecessors + + # @return [Array] the vertices of {#graph} that have an edge with + # `self` as their {Edge#origin} + def successors + outgoing_edges.map(&:destination) + end + + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def recursive_successors + _recursive_successors + end + + # @param [Set] vertices the set to add the successors to + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def _recursive_successors(vertices = new_vertex_set) + outgoing_edges.each do |edge| + vertex = edge.destination + next unless vertices.add?(vertex) + vertex._recursive_successors(vertices) + end + + vertices + end + protected :_recursive_successors + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{name}(#{payload.inspect})" + end + + # @return [Boolean] whether the two vertices are equal, determined + # by a recursive traversal of each {Vertex#successors} + def ==(other) + return true if equal?(other) + shallow_eql?(other) && + successors.to_set == other.successors.to_set + end + + # @param [Vertex] other the other vertex to compare to + # @return [Boolean] whether the two vertices are equal, determined + # solely by {#name} and {#payload} equality + def shallow_eql?(other) + return true if equal?(other) + other && + name == other.name && + payload == other.payload + end + + alias eql? == + + # @return [Fixnum] a hash for the vertex based upon its {#name} + def hash + name.hash + end + + # Is there a path from `self` to `other` following edges in the + # dependency graph? + # @return whether there is a path following edges within this {#graph} + def path_to?(other) + _path_to?(other) + end + + alias descendent? path_to? + + # @param [Vertex] other the vertex to check if there's a path to + # @param [Set] visited the vertices of {#graph} that have been visited + # @return [Boolean] whether there is a path to `other` from `self` + def _path_to?(other, visited = new_vertex_set) + return false unless visited.add?(self) + return true if equal?(other) + successors.any? { |v| v._path_to?(other, visited) } + end + protected :_path_to? + + # Is there a path from `other` to `self` following edges in the + # dependency graph? + # @return whether there is a path following edges within this {#graph} + def ancestor?(other) + other.path_to?(self) + end + + alias is_reachable_from? ancestor? + + def new_vertex_set + require 'set' + Set.new + end + private :new_vertex_set + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb new file mode 100644 index 0000000000..e210202b69 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # An error that occurred during the resolution process + class ResolverError < StandardError; end + + # An error caused by searching for a dependency that is completely unknown, + # i.e. has no versions available whatsoever. + class NoSuchDependencyError < ResolverError + # @return [Object] the dependency that could not be found + attr_accessor :dependency + + # @return [Array] the specifications that depended upon {#dependency} + attr_accessor :required_by + + # Initializes a new error with the given missing dependency. + # @param [Object] dependency @see {#dependency} + # @param [Array] required_by @see {#required_by} + def initialize(dependency, required_by = []) + @dependency = dependency + @required_by = required_by.uniq + super() + end + + # The error message for the missing dependency, including the specifications + # that had this dependency. + def message + sources = required_by.map { |r| "`#{r}`" }.join(' and ') + message = "Unable to find a specification for `#{dependency}`" + message += " depended upon by #{sources}" unless sources.empty? + message + end + end + + # An error caused by attempting to fulfil a dependency that was circular + # + # @note This exception will be thrown if and only if a {Vertex} is added to a + # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an + # existing {DependencyGraph::Vertex} + class CircularDependencyError < ResolverError + # [Set] the dependencies responsible for causing the error + attr_reader :dependencies + + # Initializes a new error with the given circular vertices. + # @param [Array] vertices the vertices in the dependency + # that caused the error + def initialize(vertices) + super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" + @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set + end + end + + # An error caused by conflicts in version + class VersionConflict < ResolverError + # @return [{String => Resolution::Conflict}] the conflicts that caused + # resolution to fail + attr_reader :conflicts + + # @return [SpecificationProvider] the specification provider used during + # resolution + attr_reader :specification_provider + + # Initializes a new error with the given version conflicts. + # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} + # @param [SpecificationProvider] specification_provider see {#specification_provider} + def initialize(conflicts, specification_provider) + pairs = [] + conflicts.values.flat_map(&:requirements).each do |conflicting| + conflicting.each do |source, conflict_requirements| + conflict_requirements.each do |c| + pairs << [c, source] + end + end + end + + super "Unable to satisfy the following requirements:\n\n" \ + "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" + + @conflicts = conflicts + @specification_provider = specification_provider + end + + require_relative 'delegates/specification_provider' + include Delegates::SpecificationProvider + + # @return [String] An error message that includes requirement trees, + # which is much more detailed & customizable than the default message + # @param [Hash] opts the options to create a message with. + # @option opts [String] :solver_name The user-facing name of the solver + # @option opts [String] :possibility_type The generic name of a possibility + # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees + # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements + # @option opts [Proc] :additional_message_for_conflict A proc that appends additional + # messages for each conflict + # @option opts [Proc] :version_for_spec A proc that returns the version number for a + # possibility + def message_with_trees(opts = {}) + solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } + possibility_type = opts.delete(:possibility_type) { 'possibility named' } + reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } + printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } + additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } + version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } + incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do + proc do |name, _conflict| + %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) + end + end + + conflicts.sort.reduce(''.dup) do |o, (name, conflict)| + o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n" + if conflict.locked_requirement + o << %( In snapshot (#{name_for_locking_dependency_source}):\n) + o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) + o << %(\n) + end + o << %( In #{name_for_explicit_dependency_source}:\n) + trees = reduce_trees.call(conflict.requirement_trees) + + o << trees.map do |tree| + t = ''.dup + depth = 2 + tree.each do |req| + t << ' ' * depth << printable_requirement.call(req) + unless tree.last == req + if spec = conflict.activated_by_name[name_for(req)] + t << %( was resolved to #{version_for_spec.call(spec)}, which) + end + t << %( depends on) + end + t << %(\n) + depth += 1 + end + t + end.join("\n") + + additional_message_for_conflict.call(o, name, conflict) + + o + end.strip + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb new file mode 100644 index 0000000000..e13a781a50 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # The version of Bundler::Molinillo. + VERSION = '0.7.0'.freeze +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb new file mode 100644 index 0000000000..eeae79af3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # Provides information about specifications and dependencies to the resolver, + # allowing the {Resolver} class to remain generic while still providing power + # and flexibility. + # + # This module contains the methods that users of Bundler::Molinillo must to implement, + # using knowledge of their own model classes. + module SpecificationProvider + # Search for the specifications that match the given dependency. + # The specifications in the returned array will be considered in reverse + # order, so the latest version ought to be last. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [Array] the specifications that satisfy the given + # `dependency`. + def search_for(dependency) + [] + end + + # Returns the dependencies of `specification`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `specification` parameter. + # + # @param [Object] specification + # @return [Array] the dependencies that are required by the given + # `specification`. + def dependencies_for(specification) + [] + end + + # Determines whether the given `requirement` is satisfied by the given + # `spec`, in the context of the current `activated` dependency graph. + # + # @param [Object] requirement + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [Object] spec + # @return [Boolean] whether `requirement` is satisfied by `spec` in the + # context of the current `activated` dependency graph. + def requirement_satisfied_by?(requirement, activated, spec) + true + end + + # Determines whether two arrays of dependencies are equal, and thus can be + # grouped. + # + # @param [Array] dependencies + # @param [Array] other_dependencies + # @return [Boolean] whether `dependencies` and `other_dependencies` should + # be considered equal. + def dependencies_equal?(dependencies, other_dependencies) + dependencies == other_dependencies + end + + # Returns the name for the given `dependency`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [String] the name for the given `dependency`. + def name_for(dependency) + dependency.to_s + end + + # @return [String] the name of the source of explicit dependencies, i.e. + # those passed to {Resolver#resolve} directly. + def name_for_explicit_dependency_source + 'user-specified dependency' + end + + # @return [String] the name of the source of 'locked' dependencies, i.e. + # those passed to {Resolver#resolve} directly as the `base` + def name_for_locking_dependency_source + 'Lockfile' + end + + # Sort dependencies so that the ones that are easiest to resolve are first. + # Easiest to resolve is (usually) defined by: + # 1) Is this dependency already activated? + # 2) How relaxed are the requirements? + # 3) Are there any conflicts for this dependency? + # 4) How many possibilities are there to satisfy this dependency? + # + # @param [Array] dependencies + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [{String => Array}] conflicts + # @return [Array] a sorted copy of `dependencies`. + def sort_dependencies(dependencies, activated, conflicts) + dependencies.sort_by do |dependency| + name = name_for(dependency) + [ + activated.vertex_named(name).payload ? 0 : 1, + conflicts[name] ? 0 : 1, + ] + end + end + + # Returns whether this dependency, which has no possible matching + # specifications, can safely be ignored. + # + # @param [Object] dependency + # @return [Boolean] whether this dependency can safely be skipped. + def allow_missing?(dependency) + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb new file mode 100644 index 0000000000..a166bc6991 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # Conveys information about the resolution process to a user. + module UI + # The {IO} object that should be used to print output. `STDOUT`, by default. + # + # @return [IO] + def output + STDOUT + end + + # Called roughly every {#progress_rate}, this method should convey progress + # to the user. + # + # @return [void] + def indicate_progress + output.print '.' unless debug? + end + + # How often progress should be conveyed to the user via + # {#indicate_progress}, in seconds. A third of a second, by default. + # + # @return [Float] + def progress_rate + 0.33 + end + + # Called before resolution begins. + # + # @return [void] + def before_resolution + output.print 'Resolving dependencies...' + end + + # Called after resolution ends (either successfully or with an error). + # By default, prints a newline. + # + # @return [void] + def after_resolution + output.puts + end + + # Conveys debug information to the user. + # + # @param [Integer] depth the current depth of the resolution process. + # @return [void] + def debug(depth = 0) + if debug? + debug_info = yield + debug_info = debug_info.inspect unless debug_info.is_a?(String) + debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } + output.puts debug_info + end + end + + # Whether or not debug messages should be printed. + # By default, whether or not the `MOLINILLO_DEBUG` environment variable is + # set. + # + # @return [Boolean] + def debug? + return @debug_mode if defined?(@debug_mode) + @debug_mode = ENV['MOLINILLO_DEBUG'] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb new file mode 100644 index 0000000000..c689ca7635 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb @@ -0,0 +1,839 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + class Resolver + # A specific resolution from a given {Resolver} + class Resolution + # A conflict that the resolution process encountered + # @attr [Object] requirement the requirement that immediately led to the conflict + # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict + # @attr [Object, nil] existing the existing spec that was in conflict with + # the {#possibility} + # @attr [Object] possibility_set the set of specs that was unable to be + # activated due to a conflict. + # @attr [Object] locked_requirement the relevant locking requirement. + # @attr [Array>] requirement_trees the different requirement + # trees that led to every requirement for the conflicting name. + # @attr [{String=>Object}] activated_by_name the already-activated specs. + # @attr [Object] underlying_error an error that has occurred during resolution, and + # will be raised at the end of it if no resolution is found. + Conflict = Struct.new( + :requirement, + :requirements, + :existing, + :possibility_set, + :locked_requirement, + :requirement_trees, + :activated_by_name, + :underlying_error + ) + + class Conflict + # @return [Object] a spec that was unable to be activated due to a conflict + def possibility + possibility_set && possibility_set.latest_version + end + end + + # A collection of possibility states that share the same dependencies + # @attr [Array] dependencies the dependencies for this set of possibilities + # @attr [Array] possibilities the possibilities + PossibilitySet = Struct.new(:dependencies, :possibilities) + + class PossibilitySet + # String representation of the possibility set, for debugging + def to_s + "[#{possibilities.join(', ')}]" + end + + # @return [Object] most up-to-date dependency in the possibility set + def latest_version + possibilities.last + end + end + + # Details of the state to unwind to when a conflict occurs, and the cause of the unwind + # @attr [Integer] state_index the index of the state to unwind to + # @attr [Object] state_requirement the requirement of the state we're unwinding to + # @attr [Array] requirement_tree for the requirement we're relaxing + # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict + # @attr [Array] requirement_trees for the conflict + # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind + UnwindDetails = Struct.new( + :state_index, + :state_requirement, + :requirement_tree, + :conflicting_requirements, + :requirement_trees, + :requirements_unwound_to_instead + ) + + class UnwindDetails + include Comparable + + # We compare UnwindDetails when choosing which state to unwind to. If + # two options have the same state_index we prefer the one most + # removed from a requirement that caused the conflict. Both options + # would unwind to the same state, but a `grandparent` option will + # filter out fewer of its possibilities after doing so - where a state + # is both a `parent` and a `grandparent` to requirements that have + # caused a conflict this is the correct behaviour. + # @param [UnwindDetail] other UnwindDetail to be compared + # @return [Integer] integer specifying ordering + def <=>(other) + if state_index > other.state_index + 1 + elsif state_index == other.state_index + reversed_requirement_tree_index <=> other.reversed_requirement_tree_index + else + -1 + end + end + + # @return [Integer] index of state requirement in reversed requirement tree + # (the conflicting requirement itself will be at position 0) + def reversed_requirement_tree_index + @reversed_requirement_tree_index ||= + if state_requirement + requirement_tree.reverse.index(state_requirement) + else + 999_999 + end + end + + # @return [Boolean] where the requirement of the state we're unwinding + # to directly caused the conflict. Note: in this case, it is + # impossible for the state we're unwinding to to be a parent of + # any of the other conflicting requirements (or we would have + # circularity) + def unwinding_to_primary_requirement? + requirement_tree.last == state_requirement + end + + # @return [Array] array of sub-dependencies to avoid when choosing a + # new possibility for the state we've unwound to. Only relevant for + # non-primary unwinds + def sub_dependencies_to_avoid + @requirements_to_avoid ||= + requirement_trees.map do |tree| + index = tree.index(state_requirement) + tree[index + 1] if index + end.compact + end + + # @return [Array] array of all the requirements that led to the need for + # this unwind + def all_requirements + @all_requirements ||= requirement_trees.flatten(1) + end + end + + # @return [SpecificationProvider] the provider that knows about + # dependencies, requirements, specifications, versions, etc. + attr_reader :specification_provider + + # @return [UI] the UI that knows how to communicate feedback about the + # resolution process back to the user + attr_reader :resolver_ui + + # @return [DependencyGraph] the base dependency graph to which + # dependencies should be 'locked' + attr_reader :base + + # @return [Array] the dependencies that were explicitly required + attr_reader :original_requested + + # Initializes a new resolution. + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui see {#resolver_ui} + # @param [Array] requested see {#original_requested} + # @param [DependencyGraph] base see {#base} + def initialize(specification_provider, resolver_ui, requested, base) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + @original_requested = requested + @base = base + @states = [] + @iteration_counter = 0 + @parents_of = Hash.new { |h, k| h[k] = [] } + end + + # Resolves the {#original_requested} dependencies into a full dependency + # graph + # @raise [ResolverError] if successful resolution is impossible + # @return [DependencyGraph] the dependency graph of successfully resolved + # dependencies + def resolve + start_resolution + + while state + break if !state.requirement && state.requirements.empty? + indicate_progress + if state.respond_to?(:pop_possibility_state) # DependencyState + debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } + state.pop_possibility_state.tap do |s| + if s + states.push(s) + activated.tag(s) + end + end + end + process_topmost_state + end + + resolve_activated_specs + ensure + end_resolution + end + + # @return [Integer] the number of resolver iterations in between calls to + # {#resolver_ui}'s {UI#indicate_progress} method + attr_accessor :iteration_rate + private :iteration_rate + + # @return [Time] the time at which resolution began + attr_accessor :started_at + private :started_at + + # @return [Array] the stack of states for the resolution + attr_accessor :states + private :states + + private + + # Sets up the resolution process + # @return [void] + def start_resolution + @started_at = Time.now + + push_initial_state + + debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } + resolver_ui.before_resolution + end + + def resolve_activated_specs + activated.vertices.each do |_, vertex| + next unless vertex.payload + + latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| + vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } + end + + activated.set_payload(vertex.name, latest_version) + end + activated.freeze + end + + # Ends the resolution process + # @return [void] + def end_resolution + resolver_ui.after_resolution + debug do + "Finished resolution (#{@iteration_counter} steps) " \ + "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" + end + debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state + debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state + end + + require_relative 'state' + require_relative 'modules/specification_provider' + + require_relative 'delegates/resolution_state' + require_relative 'delegates/specification_provider' + + include Bundler::Molinillo::Delegates::ResolutionState + include Bundler::Molinillo::Delegates::SpecificationProvider + + # Processes the topmost available {RequirementState} on the stack + # @return [void] + def process_topmost_state + if possibility + attempt_to_activate + else + create_conflict + unwind_for_conflict + end + rescue CircularDependencyError => underlying_error + create_conflict(underlying_error) + unwind_for_conflict + end + + # @return [Object] the current possibility that the resolution is trying + # to activate + def possibility + possibilities.last + end + + # @return [RequirementState] the current state the resolution is + # operating upon + def state + states.last + end + + # Creates and pushes the initial state for the resolution, based upon the + # {#requested} dependencies + # @return [void] + def push_initial_state + graph = DependencyGraph.new.tap do |dg| + original_requested.each do |requested| + vertex = dg.add_vertex(name_for(requested), nil, true) + vertex.explicit_requirements << requested + end + dg.tag(:initial_state) + end + + push_state_for_requirements(original_requested, true, graph) + end + + # Unwinds the states stack because a conflict has been encountered + # @return [void] + def unwind_for_conflict + details_for_unwind = build_details_for_unwind + unwind_options = unused_unwind_options + debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } + conflicts.tap do |c| + sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) + raise_error_unless_state(c) + activated.rewind_to(sliced_states.first || :initial_state) if sliced_states + state.conflicts = c + state.unused_unwind_options = unwind_options + filter_possibilities_after_unwind(details_for_unwind) + index = states.size - 1 + @parents_of.each { |_, a| a.reject! { |i| i >= index } } + state.unused_unwind_options.reject! { |uw| uw.state_index >= index } + end + end + + # Raises a VersionConflict error, or any underlying error, if there is no + # current state + # @return [void] + def raise_error_unless_state(conflicts) + return if state + + error = conflicts.values.map(&:underlying_error).compact.first + raise error || VersionConflict.new(conflicts, specification_provider) + end + + # @return [UnwindDetails] Details of the nearest index to which we could unwind + def build_details_for_unwind + # Get the possible unwinds for the current conflict + current_conflict = conflicts[name] + binding_requirements = binding_requirements_for_conflict(current_conflict) + unwind_details = unwind_options_for_requirements(binding_requirements) + + last_detail_for_current_unwind = unwind_details.sort.last + current_detail = last_detail_for_current_unwind + + # Look for past conflicts that could be unwound to affect the + # requirement tree for the current conflict + all_reqs = last_detail_for_current_unwind.all_requirements + all_reqs_size = all_reqs.size + relevant_unused_unwinds = unused_unwind_options.select do |alternative| + diff_reqs = all_reqs - alternative.requirements_unwound_to_instead + next if diff_reqs.size == all_reqs_size + # Find the highest index unwind whilst looping through + current_detail = alternative if alternative > current_detail + alternative + end + + # Add the current unwind options to the `unused_unwind_options` array. + # The "used" option will be filtered out during `unwind_for_conflict`. + state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } + + # Update the requirements_unwound_to_instead on any relevant unused unwinds + relevant_unused_unwinds.each do |d| + (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! + end + unwind_details.each do |d| + (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! + end + + current_detail + end + + # @param [Array] binding_requirements array of requirements that combine to create a conflict + # @return [Array] array of UnwindDetails that have a chance + # of resolving the passed requirements + def unwind_options_for_requirements(binding_requirements) + unwind_details = [] + + trees = [] + binding_requirements.reverse_each do |r| + partial_tree = [r] + trees << partial_tree + unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) + + # If this requirement has alternative possibilities, check if any would + # satisfy the other requirements that created this conflict + requirement_state = find_state_for(r) + if conflict_fixing_possibilities?(requirement_state, binding_requirements) + unwind_details << UnwindDetails.new( + states.index(requirement_state), + r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Next, look at the parent of this requirement, and check if the requirement + # could have been avoided if an alternative PossibilitySet had been chosen + parent_r = parent_of(r) + next if parent_r.nil? + partial_tree.unshift(parent_r) + requirement_state = find_state_for(parent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + parent_r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Finally, look at the grandparent and up of this requirement, looking + # for any possibilities that wouldn't create their parent requirement + grandparent_r = parent_of(parent_r) + until grandparent_r.nil? + partial_tree.unshift(grandparent_r) + requirement_state = find_state_for(grandparent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + grandparent_r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + parent_r = grandparent_r + grandparent_r = parent_of(parent_r) + end + end + + unwind_details + end + + # @param [DependencyState] state + # @param [Array] binding_requirements array of requirements + # @return [Boolean] whether or not the given state has any possibilities + # that could satisfy the given requirements + def conflict_fixing_possibilities?(state, binding_requirements) + return false unless state + + state.possibilities.any? do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, binding_requirements) + end + end + end + + # Filter's a state's possibilities to remove any that would not fix the + # conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just + # unwound from + # @return [void] + def filter_possibilities_after_unwind(unwind_details) + return unless state && !state.possibilities.empty? + + if unwind_details.unwinding_to_primary_requirement? + filter_possibilities_for_primary_unwind(unwind_details) + else + filter_possibilities_for_parent_unwind(unwind_details) + end + end + + # Filter's a state's possibilities to remove any that would not satisfy + # the requirements in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_primary_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) + + state.possibilities.reject! do |possibility_set| + possibility_set.possibilities.none? do |poss| + unwind_requirement_sets.any? do |requirements| + possibility_satisfies_requirements?(poss, requirements) + end + end + end + end + + # @param [Object] possibility a single possibility + # @param [Array] requirements an array of requirements + # @return [Boolean] whether the possibility satisfies all of the + # given requirements + def possibility_satisfies_requirements?(possibility, requirements) + name = name_for(possibility) + + activated.tag(:swap) + activated.set_payload(name, possibility) if activated.vertex_named(name) + satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } + activated.rewind_to(:swap) + + satisfied + end + + # Filter's a state's possibilities to remove any that would (eventually) + # create a requirement in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_parent_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + + primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq + parent_unwinds = unwinds_to_state.uniq - primary_unwinds + + allowed_possibility_sets = primary_unwinds.flat_map do |unwind| + states[unwind.state_index].possibilities.select do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) + end + end + end + + requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) + + state.possibilities.reject! do |possibility_set| + !allowed_possibility_sets.include?(possibility_set) && + (requirements_to_avoid - possibility_set.dependencies).empty? + end + end + + # @param [Conflict] conflict + # @return [Array] minimal array of requirements that would cause the passed + # conflict to occur. + def binding_requirements_for_conflict(conflict) + return [conflict.requirement] if conflict.possibility.nil? + + possible_binding_requirements = conflict.requirements.values.flatten(1).uniq + + # When there's a `CircularDependency` error the conflicting requirement + # (the one causing the circular) won't be `conflict.requirement` + # (which won't be for the right state, because we won't have created it, + # because it's circular). + # We need to make sure we have that requirement in the conflict's list, + # otherwise we won't be able to unwind properly, so we just return all + # the requirements for the conflict. + return possible_binding_requirements if conflict.underlying_error + + possibilities = search_for(conflict.requirement) + + # If all the requirements together don't filter out all possibilities, + # then the only two requirements we need to consider are the initial one + # (where the dependency's version was first chosen) and the last + if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) + return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact + end + + # Loop through the possible binding requirements, removing each one + # that doesn't bind. Use a `reverse_each` as we want the earliest set of + # binding requirements, and don't use `reject!` as we wish to refine the + # array *on each iteration*. + binding_requirements = possible_binding_requirements.dup + possible_binding_requirements.reverse_each do |req| + next if req == conflict.requirement + unless binding_requirement_in_set?(req, binding_requirements, possibilities) + binding_requirements -= [req] + end + end + + binding_requirements + end + + # @param [Object] requirement we wish to check + # @param [Array] possible_binding_requirements array of requirements + # @param [Array] possibilities array of possibilities the requirements will be used to filter + # @return [Boolean] whether or not the given requirement is required to filter + # out all elements of the array of possibilities. + def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) + possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) + end + end + + # @param [Object] requirement + # @return [Object] the requirement that led to `requirement` being added + # to the list of requirements. + def parent_of(requirement) + return unless requirement + return unless index = @parents_of[requirement].last + return unless parent_state = @states[index] + parent_state.requirement + end + + # @param [String] name + # @return [Object] the requirement that led to a version of a possibility + # with the given name being activated. + def requirement_for_existing_name(name) + return nil unless vertex = activated.vertex_named(name) + return nil unless vertex.payload + states.find { |s| s.name == name }.requirement + end + + # @param [Object] requirement + # @return [ResolutionState] the state whose `requirement` is the given + # `requirement`. + def find_state_for(requirement) + return nil unless requirement + states.find { |i| requirement == i.requirement } + end + + # @param [Object] underlying_error + # @return [Conflict] a {Conflict} that reflects the failure to activate + # the {#possibility} in conjunction with the current {#state} + def create_conflict(underlying_error = nil) + vertex = activated.vertex_named(name) + locked_requirement = locked_requirement_named(name) + + requirements = {} + unless vertex.explicit_requirements.empty? + requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements + end + requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement + vertex.incoming_edges.each do |edge| + (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) + end + + activated_by_name = {} + activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } + conflicts[name] = Conflict.new( + requirement, + requirements, + vertex.payload && vertex.payload.latest_version, + possibility, + locked_requirement, + requirement_trees, + activated_by_name, + underlying_error + ) + end + + # @return [Array>] The different requirement + # trees that led to every requirement for the current spec. + def requirement_trees + vertex = activated.vertex_named(name) + vertex.requirements.map { |r| requirement_tree_for(r) } + end + + # @param [Object] requirement + # @return [Array] the list of requirements that led to + # `requirement` being required. + def requirement_tree_for(requirement) + tree = [] + while requirement + tree.unshift(requirement) + requirement = parent_of(requirement) + end + tree + end + + # Indicates progress roughly once every second + # @return [void] + def indicate_progress + @iteration_counter += 1 + @progress_rate ||= resolver_ui.progress_rate + if iteration_rate.nil? + if Time.now - started_at >= @progress_rate + self.iteration_rate = @iteration_counter + end + end + + if iteration_rate && (@iteration_counter % iteration_rate) == 0 + resolver_ui.indicate_progress + end + end + + # Calls the {#resolver_ui}'s {UI#debug} method + # @param [Integer] depth the depth of the {#states} stack + # @param [Proc] block a block that yields a {#to_s} + # @return [void] + def debug(depth = 0, &block) + resolver_ui.debug(depth, &block) + end + + # Attempts to activate the current {#possibility} + # @return [void] + def attempt_to_activate + debug(depth) { 'Attempting to activate ' + possibility.to_s } + existing_vertex = activated.vertex_named(name) + if existing_vertex.payload + debug(depth) { "Found existing spec (#{existing_vertex.payload})" } + attempt_to_filter_existing_spec(existing_vertex) + else + latest = possibility.latest_version + possibility.possibilities.select! do |possibility| + requirement_satisfied_by?(requirement, activated, possibility) + end + if possibility.latest_version.nil? + # ensure there's a possibility for better error messages + possibility.possibilities << latest if latest + create_conflict + unwind_for_conflict + else + activate_new_spec + end + end + end + + # Attempts to update the existing vertex's `PossibilitySet` with a filtered version + # @return [void] + def attempt_to_filter_existing_spec(vertex) + filtered_set = filtered_possibility_set(vertex) + if !filtered_set.possibilities.empty? + activated.set_payload(name, filtered_set) + new_requirements = requirements.dup + push_state_for_requirements(new_requirements, false) + else + create_conflict + debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } + unwind_for_conflict + end + end + + # Generates a filtered version of the existing vertex's `PossibilitySet` using the + # current state's `requirement` + # @param [Object] vertex existing vertex + # @return [PossibilitySet] filtered possibility set + def filtered_possibility_set(vertex) + PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) + end + + # @param [String] requirement_name the spec name to search for + # @return [Object] the locked spec named `requirement_name`, if one + # is found on {#base} + def locked_requirement_named(requirement_name) + vertex = base.vertex_named(requirement_name) + vertex && vertex.payload + end + + # Add the current {#possibility} to the dependency graph of the current + # {#state} + # @return [void] + def activate_new_spec + conflicts.delete(name) + debug(depth) { "Activated #{name} at #{possibility}" } + activated.set_payload(name, possibility) + require_nested_dependencies_for(possibility) + end + + # Requires the dependencies that the recently activated spec has + # @param [Object] possibility_set the PossibilitySet that has just been + # activated + # @return [void] + def require_nested_dependencies_for(possibility_set) + nested_dependencies = dependencies_for(possibility_set.latest_version) + debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } + nested_dependencies.each do |d| + activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) + parent_index = states.size - 1 + parents = @parents_of[d] + parents << parent_index if parents.empty? + end + + push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) + end + + # Pushes a new {DependencyState} that encapsulates both existing and new + # requirements + # @param [Array] new_requirements + # @param [Boolean] requires_sort + # @param [Object] new_activated + # @return [void] + def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) + new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort + new_requirement = nil + loop do + new_requirement = new_requirements.shift + break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } + end + new_name = new_requirement ? name_for(new_requirement) : ''.freeze + possibilities = possibilities_for_requirement(new_requirement) + handle_missing_or_push_dependency_state DependencyState.new( + new_name, new_requirements, new_activated, + new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup + ) + end + + # Checks a proposed requirement with any existing locked requirement + # before generating an array of possibilities for it. + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibilities + def possibilities_for_requirement(requirement, activated = self.activated) + return [] unless requirement + if locked_requirement_named(name_for(requirement)) + return locked_requirement_possibility_set(requirement, activated) + end + + group_possibilities(search_for(requirement)) + end + + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibility set containing only the locked requirement, if any + def locked_requirement_possibility_set(requirement, activated = self.activated) + all_possibilities = search_for(requirement) + locked_requirement = locked_requirement_named(name_for(requirement)) + + # Longwinded way to build a possibilities array with either the locked + # requirement or nothing in it. Required, since the API for + # locked_requirement isn't guaranteed. + locked_possibilities = all_possibilities.select do |possibility| + requirement_satisfied_by?(locked_requirement, activated, possibility) + end + + group_possibilities(locked_possibilities) + end + + # Build an array of PossibilitySets, with each element representing a group of + # dependency versions that all have the same sub-dependency version constraints + # and are contiguous. + # @param [Array] possibilities an array of possibilities + # @return [Array] an array of possibility sets + def group_possibilities(possibilities) + possibility_sets = [] + current_possibility_set = nil + + possibilities.reverse_each do |possibility| + dependencies = dependencies_for(possibility) + if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) + current_possibility_set.possibilities.unshift(possibility) + else + possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) + current_possibility_set = possibility_sets.first + end + end + + possibility_sets + end + + # Pushes a new {DependencyState}. + # If the {#specification_provider} says to + # {SpecificationProvider#allow_missing?} that particular requirement, and + # there are no possibilities for that requirement, then `state` is not + # pushed, and the vertex in {#activated} is removed, and we continue + # resolving the remaining requirements. + # @param [DependencyState] state + # @return [void] + def handle_missing_or_push_dependency_state(state) + if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) + state.activated.detach_vertex_named(state.name) + push_state_for_requirements(state.requirements.dup, false, state.activated) + else + states.push(state).tap { activated.tag(state) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb new file mode 100644 index 0000000000..95eaab5991 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'dependency_graph' + +module Bundler::Molinillo + # This class encapsulates a dependency resolver. + # The resolver is responsible for determining which set of dependencies to + # activate, with feedback from the {#specification_provider} + # + # + class Resolver + require_relative 'resolution' + + # @return [SpecificationProvider] the specification provider used + # in the resolution process + attr_reader :specification_provider + + # @return [UI] the UI module used to communicate back to the user + # during the resolution process + attr_reader :resolver_ui + + # Initializes a new resolver. + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui + # see {#resolver_ui} + def initialize(specification_provider, resolver_ui) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + end + + # Resolves the requested dependencies into a {DependencyGraph}, + # locking to the base dependency graph (if specified) + # @param [Array] requested an array of 'requested' dependencies that the + # {#specification_provider} can understand + # @param [DependencyGraph,nil] base the base dependency graph to which + # dependencies should be 'locked' + def resolve(requested, base = DependencyGraph.new) + Resolution.new(specification_provider, + resolver_ui, + requested, + base). + resolve + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/state.rb new file mode 100644 index 0000000000..68fa1f54e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/molinillo/lib/molinillo/state.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Bundler::Molinillo + # A state that a {Resolution} can be in + # @attr [String] name the name of the current requirement + # @attr [Array] requirements currently unsatisfied requirements + # @attr [DependencyGraph] activated the graph of activated dependencies + # @attr [Object] requirement the current requirement + # @attr [Object] possibilities the possibilities to satisfy the current requirement + # @attr [Integer] depth the depth of the resolution + # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name + # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored + ResolutionState = Struct.new( + :name, + :requirements, + :activated, + :requirement, + :possibilities, + :depth, + :conflicts, + :unused_unwind_options + ) + + class ResolutionState + # Returns an empty resolution state + # @return [ResolutionState] an empty state + def self.empty + new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) + end + end + + # A state that encapsulates a set of {#requirements} with an {Array} of + # possibilities + class DependencyState < ResolutionState + # Removes a possibility from `self` + # @return [PossibilityState] a state with a single possibility, + # the possibility that was removed from `self` + def pop_possibility_state + PossibilityState.new( + name, + requirements.dup, + activated, + requirement, + [possibilities.pop], + depth + 1, + conflicts.dup, + unused_unwind_options.dup + ).tap do |state| + state.activated.tag(state) + end + end + end + + # A state that encapsulates a single possibility to fulfill the given + # {#requirement} + class PossibilityState < ResolutionState + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb new file mode 100644 index 0000000000..beff6d658b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb @@ -0,0 +1,1073 @@ +require 'net/http' +require_relative '../../../../uri/lib/uri' +require 'cgi' # for escaping +require_relative '../../../../connection_pool/lib/connection_pool' + +autoload :OpenSSL, 'openssl' + +## +# Persistent connections for Net::HTTP +# +# Bundler::Persistent::Net::HTTP::Persistent maintains persistent connections across all the +# servers you wish to talk to. For each host:port you communicate with a +# single persistent connection is created. +# +# Connections will be shared across threads through a connection pool to +# increase reuse of connections. +# +# You can shut down any remaining HTTP connections when done by calling +# #shutdown. +# +# Example: +# +# require 'bundler/vendor/net-http-persistent/lib/net/http/persistent' +# +# uri = Bundler::URI 'http://example.com/awesome/web/service' +# +# http = Bundler::Persistent::Net::HTTP::Persistent.new +# +# # perform a GET +# response = http.request uri +# +# # or +# +# get = Net::HTTP::Get.new uri.request_uri +# response = http.request get +# +# # create a POST +# post_uri = uri + 'create' +# post = Net::HTTP::Post.new post_uri.path +# post.set_form_data 'some' => 'cool data' +# +# # perform the POST, the Bundler::URI is always required +# response http.request post_uri, post +# +# Note that for GET, HEAD and other requests that do not have a body you want +# to use Bundler::URI#request_uri not Bundler::URI#path. The request_uri contains the query +# params which are sent in the body for other requests. +# +# == TLS/SSL +# +# TLS connections are automatically created depending upon the scheme of the +# Bundler::URI. TLS connections are automatically verified against the default +# certificate store for your computer. You can override this by changing +# verify_mode or by specifying an alternate cert_store. +# +# Here are the TLS settings, see the individual methods for documentation: +# +# #certificate :: This client's certificate +# #ca_file :: The certificate-authorities +# #ca_path :: Directory with certificate-authorities +# #cert_store :: An SSL certificate store +# #ciphers :: List of SSl ciphers allowed +# #private_key :: The client's SSL private key +# #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new +# connection +# #ssl_timeout :: Session lifetime +# #ssl_version :: Which specific SSL version to use +# #verify_callback :: For server certificate verification +# #verify_depth :: Depth of certificate verification +# #verify_mode :: How connections should be verified +# +# == Proxies +# +# A proxy can be set through #proxy= or at initialization time by providing a +# second argument to ::new. The proxy may be the Bundler::URI of the proxy server or +# :ENV which will consult environment variables. +# +# See #proxy= and #proxy_from_env for details. +# +# == Headers +# +# Headers may be specified for use in every request. #headers are appended to +# any headers on the request. #override_headers replace existing headers on +# the request. +# +# The difference between the two can be seen in setting the User-Agent. Using +# http.headers['User-Agent'] = 'MyUserAgent' will send "Ruby, +# MyUserAgent" while http.override_headers['User-Agent'] = +# 'MyUserAgent' will send "MyUserAgent". +# +# == Tuning +# +# === Segregation +# +# Each Bundler::Persistent::Net::HTTP::Persistent instance has its own pool of connections. There +# is no sharing with other instances (as was true in earlier versions). +# +# === Idle Timeout +# +# If a connection hasn't been used for this number of seconds it will +# automatically be reset upon the next use to avoid attempting to send to a +# closed connection. The default value is 5 seconds. nil means no timeout. +# Set through #idle_timeout. +# +# Reducing this value may help avoid the "too many connection resets" error +# when sending non-idempotent requests while increasing this value will cause +# fewer round-trips. +# +# === Read Timeout +# +# The amount of time allowed between reading two chunks from the socket. Set +# through #read_timeout +# +# === Max Requests +# +# The number of requests that should be made before opening a new connection. +# Typically many keep-alive capable servers tune this to 100 or less, so the +# 101st request will fail with ECONNRESET. If unset (default), this value has +# no effect, if set, connections will be reset on the request after +# max_requests. +# +# === Open Timeout +# +# The amount of time to wait for a connection to be opened. Set through +# #open_timeout. +# +# === Socket Options +# +# Socket options may be set on newly-created connections. See #socket_options +# for details. +# +# === Connection Termination +# +# If you are done using the Bundler::Persistent::Net::HTTP::Persistent instance you may shut down +# all the connections in the current thread with #shutdown. This is not +# recommended for normal use, it should only be used when it will be several +# minutes before you make another HTTP request. +# +# If you are using multiple threads, call #shutdown in each thread when the +# thread is done making requests. If you don't call shutdown, that's OK. +# Ruby will automatically garbage collect and shutdown your HTTP connections +# when the thread terminates. + +class Bundler::Persistent::Net::HTTP::Persistent + + ## + # The beginning of Time + + EPOCH = Time.at 0 # :nodoc: + + ## + # Is OpenSSL available? This test works with autoload + + HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: + + ## + # The default connection pool size is 1/4 the allowed open files + # (ulimit -n) or 256 if your OS does not support file handle + # limits (typically windows). + + if Process.const_defined? :RLIMIT_NOFILE + open_file_limits = Process.getrlimit(Process::RLIMIT_NOFILE) + + # Under JRuby on Windows Process responds to `getrlimit` but returns something that does not match docs + if open_file_limits.respond_to?(:first) + DEFAULT_POOL_SIZE = open_file_limits.first / 4 + else + DEFAULT_POOL_SIZE = 256 + end + else + DEFAULT_POOL_SIZE = 256 + end + + ## + # The version of Bundler::Persistent::Net::HTTP::Persistent you are using + + VERSION = '4.0.0' + + ## + # Error class for errors raised by Bundler::Persistent::Net::HTTP::Persistent. Various + # SystemCallErrors are re-raised with a human-readable message under this + # class. + + class Error < StandardError; end + + ## + # Use this method to detect the idle timeout of the host at +uri+. The + # value returned can be used to configure #idle_timeout. +max+ controls the + # maximum idle timeout to detect. + # + # After + # + # Idle timeout detection is performed by creating a connection then + # performing a HEAD request in a loop until the connection terminates + # waiting one additional second per loop. + # + # NOTE: This may not work on ruby > 1.9. + + def self.detect_idle_timeout uri, max = 10 + uri = Bundler::URI uri unless Bundler::URI::Generic === uri + uri += '/' + + req = Net::HTTP::Head.new uri.request_uri + + http = new 'net-http-persistent detect_idle_timeout' + + http.connection_for uri do |connection| + sleep_time = 0 + + http = connection.http + + loop do + response = http.request req + + $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG + + unless Net::HTTPOK === response then + raise Error, "bad response code #{response.code} detecting idle timeout" + end + + break if sleep_time >= max + + sleep_time += 1 + + $stderr.puts "sleeping #{sleep_time}" if $DEBUG + sleep sleep_time + end + end + rescue + # ignore StandardErrors, we've probably found the idle timeout. + ensure + return sleep_time unless $! + end + + ## + # This client's OpenSSL::X509::Certificate + + attr_reader :certificate + + ## + # For Net::HTTP parity + + alias cert certificate + + ## + # An SSL certificate authority. Setting this will set verify_mode to + # VERIFY_PEER. + + attr_reader :ca_file + + ## + # A directory of SSL certificates to be used as certificate authorities. + # Setting this will set verify_mode to VERIFY_PEER. + + attr_reader :ca_path + + ## + # An SSL certificate store. Setting this will override the default + # certificate store. See verify_mode for more information. + + attr_reader :cert_store + + ## + # The ciphers allowed for SSL connections + + attr_reader :ciphers + + ## + # Sends debug_output to this IO via Net::HTTP#set_debug_output. + # + # Never use this method in production code, it causes a serious security + # hole. + + attr_accessor :debug_output + + ## + # Current connection generation + + attr_reader :generation # :nodoc: + + ## + # Headers that are added to every request using Net::HTTP#add_field + + attr_reader :headers + + ## + # Maps host:port to an HTTP version. This allows us to enable version + # specific features. + + attr_reader :http_versions + + ## + # Maximum time an unused connection can remain idle before being + # automatically closed. + + attr_accessor :idle_timeout + + ## + # Maximum number of requests on a connection before it is considered expired + # and automatically closed. + + attr_accessor :max_requests + + ## + # Number of retries to perform if a request fails. + # + # See also #max_retries=, Net::HTTP#max_retries=. + + attr_reader :max_retries + + ## + # The value sent in the Keep-Alive header. Defaults to 30. Not needed for + # HTTP/1.1 servers. + # + # This may not work correctly for HTTP/1.0 servers + # + # This method may be removed in a future version as RFC 2616 does not + # require this header. + + attr_accessor :keep_alive + + ## + # The name for this collection of persistent connections. + + attr_reader :name + + ## + # Seconds to wait until a connection is opened. See Net::HTTP#open_timeout + + attr_accessor :open_timeout + + ## + # Headers that are added to every request using Net::HTTP#[]= + + attr_reader :override_headers + + ## + # This client's SSL private key + + attr_reader :private_key + + ## + # For Net::HTTP parity + + alias key private_key + + ## + # The URL through which requests will be proxied + + attr_reader :proxy_uri + + ## + # List of host suffixes which will not be proxied + + attr_reader :no_proxy + + ## + # Test-only accessor for the connection pool + + attr_reader :pool # :nodoc: + + ## + # Seconds to wait until reading one block. See Net::HTTP#read_timeout + + attr_accessor :read_timeout + + ## + # Seconds to wait until writing one block. See Net::HTTP#write_timeout + + attr_accessor :write_timeout + + ## + # By default SSL sessions are reused to avoid extra SSL handshakes. Set + # this to false if you have problems communicating with an HTTPS server + # like: + # + # SSL_connect [...] read finished A: unexpected message (OpenSSL::SSL::SSLError) + + attr_accessor :reuse_ssl_sessions + + ## + # An array of options for Socket#setsockopt. + # + # By default the TCP_NODELAY option is set on sockets. + # + # To set additional options append them to this array: + # + # http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1] + + attr_reader :socket_options + + ## + # Current SSL connection generation + + attr_reader :ssl_generation # :nodoc: + + ## + # SSL session lifetime + + attr_reader :ssl_timeout + + ## + # SSL version to use. + # + # By default, the version will be negotiated automatically between client + # and server. Ruby 1.9 and newer only. Deprecated since Ruby 2.5. + + attr_reader :ssl_version + + ## + # Minimum SSL version to use, e.g. :TLS1_1 + # + # By default, the version will be negotiated automatically between client + # and server. Ruby 2.5 and newer only. + + attr_reader :min_version + + ## + # Maximum SSL version to use, e.g. :TLS1_2 + # + # By default, the version will be negotiated automatically between client + # and server. Ruby 2.5 and newer only. + + attr_reader :max_version + + ## + # Where this instance's last-use times live in the thread local variables + + attr_reader :timeout_key # :nodoc: + + ## + # SSL verification callback. Used when ca_file or ca_path is set. + + attr_reader :verify_callback + + ## + # Sets the depth of SSL certificate verification + + attr_reader :verify_depth + + ## + # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies + # the server certificate. + # + # If no ca_file, ca_path or cert_store is set the default system certificate + # store is used. + # + # You can use +verify_mode+ to override any default values. + + attr_reader :verify_mode + + ## + # Creates a new Bundler::Persistent::Net::HTTP::Persistent. + # + # Set a +name+ for fun. Your library name should be good enough, but this + # otherwise has no purpose. + # + # +proxy+ may be set to a Bundler::URI::HTTP or :ENV to pick up proxy options from + # the environment. See proxy_from_env for details. + # + # In order to use a Bundler::URI for the proxy you may need to do some extra work + # beyond Bundler::URI parsing if the proxy requires a password: + # + # proxy = Bundler::URI 'http://proxy.example' + # proxy.user = 'AzureDiamond' + # proxy.password = 'hunter2' + # + # Set +pool_size+ to limit the maximum number of connections allowed. + # Defaults to 1/4 the number of allowed file handles or 256 if your OS does + # not support a limit on allowed file handles. You can have no more than + # this many threads with active HTTP transactions. + + def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE + @name = name + + @debug_output = nil + @proxy_uri = nil + @no_proxy = [] + @headers = {} + @override_headers = {} + @http_versions = {} + @keep_alive = 30 + @open_timeout = nil + @read_timeout = nil + @write_timeout = nil + @idle_timeout = 5 + @max_requests = nil + @max_retries = 1 + @socket_options = [] + @ssl_generation = 0 # incremented when SSL session variables change + + @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if + Socket.const_defined? :TCP_NODELAY + + @pool = Bundler::Persistent::Net::HTTP::Persistent::Pool.new size: pool_size do |http_args| + Bundler::Persistent::Net::HTTP::Persistent::Connection.new Net::HTTP, http_args, @ssl_generation + end + + @certificate = nil + @ca_file = nil + @ca_path = nil + @ciphers = nil + @private_key = nil + @ssl_timeout = nil + @ssl_version = nil + @min_version = nil + @max_version = nil + @verify_callback = nil + @verify_depth = nil + @verify_mode = nil + @cert_store = nil + + @generation = 0 # incremented when proxy Bundler::URI changes + + if HAVE_OPENSSL then + @verify_mode = OpenSSL::SSL::VERIFY_PEER + @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session + end + + self.proxy = proxy if proxy + end + + ## + # Sets this client's OpenSSL::X509::Certificate + + def certificate= certificate + @certificate = certificate + + reconnect_ssl + end + + # For Net::HTTP parity + alias cert= certificate= + + ## + # Sets the SSL certificate authority file. + + def ca_file= file + @ca_file = file + + reconnect_ssl + end + + ## + # Sets the SSL certificate authority path. + + def ca_path= path + @ca_path = path + + reconnect_ssl + end + + ## + # Overrides the default SSL certificate store used for verifying + # connections. + + def cert_store= store + @cert_store = store + + reconnect_ssl + end + + ## + # The ciphers allowed for SSL connections + + def ciphers= ciphers + @ciphers = ciphers + + reconnect_ssl + end + + ## + # Creates a new connection for +uri+ + + def connection_for uri + use_ssl = uri.scheme.downcase == 'https' + + net_http_args = [uri.hostname, uri.port] + + # I'm unsure if uri.host or uri.hostname should be checked against + # the proxy bypass list. + if @proxy_uri and not proxy_bypass? uri.host, uri.port then + net_http_args.concat @proxy_args + else + net_http_args.concat [nil, nil, nil, nil] + end + + connection = @pool.checkout net_http_args + + http = connection.http + + connection.ressl @ssl_generation if + connection.ssl_generation != @ssl_generation + + if not http.started? then + ssl http if use_ssl + start http + elsif expired? connection then + reset connection + end + + http.keep_alive_timeout = @idle_timeout if @idle_timeout + http.max_retries = @max_retries if http.respond_to?(:max_retries=) + http.read_timeout = @read_timeout if @read_timeout + http.write_timeout = @write_timeout if + @write_timeout && http.respond_to?(:write_timeout=) + + return yield connection + rescue Errno::ECONNREFUSED + address = http.proxy_address || http.address + port = http.proxy_port || http.port + + raise Error, "connection refused: #{address}:#{port}" + rescue Errno::EHOSTDOWN + address = http.proxy_address || http.address + port = http.proxy_port || http.port + + raise Error, "host down: #{address}:#{port}" + ensure + @pool.checkin net_http_args + end + + ## + # CGI::escape wrapper + + def escape str + CGI.escape str if str + end + + ## + # CGI::unescape wrapper + + def unescape str + CGI.unescape str if str + end + + + ## + # Returns true if the connection should be reset due to an idle timeout, or + # maximum request count, false otherwise. + + def expired? connection + return true if @max_requests && connection.requests >= @max_requests + return false unless @idle_timeout + return true if @idle_timeout.zero? + + Time.now - connection.last_use > @idle_timeout + end + + ## + # Starts the Net::HTTP +connection+ + + def start http + http.set_debug_output @debug_output if @debug_output + http.open_timeout = @open_timeout if @open_timeout + + http.start + + socket = http.instance_variable_get :@socket + + if socket then # for fakeweb + @socket_options.each do |option| + socket.io.setsockopt(*option) + end + end + end + + ## + # Finishes the Net::HTTP +connection+ + + def finish connection + connection.finish + + connection.http.instance_variable_set :@last_communicated, nil + connection.http.instance_variable_set :@ssl_session, nil unless + @reuse_ssl_sessions + end + + ## + # Returns the HTTP protocol version for +uri+ + + def http_version uri + @http_versions["#{uri.hostname}:#{uri.port}"] + end + + ## + # Adds "http://" to the String +uri+ if it is missing. + + def normalize_uri uri + (uri =~ /^https?:/) ? uri : "http://#{uri}" + end + + ## + # Set the maximum number of retries for a request. + # + # Defaults to one retry. + # + # Set this to 0 to disable retries. + + def max_retries= retries + retries = retries.to_int + + raise ArgumentError, "max_retries must be positive" if retries < 0 + + @max_retries = retries + + reconnect + end + + ## + # Sets this client's SSL private key + + def private_key= key + @private_key = key + + reconnect_ssl + end + + # For Net::HTTP parity + alias key= private_key= + + ## + # Sets the proxy server. The +proxy+ may be the Bundler::URI of the proxy server, + # the symbol +:ENV+ which will read the proxy from the environment or nil to + # disable use of a proxy. See #proxy_from_env for details on setting the + # proxy from the environment. + # + # If the proxy Bundler::URI is set after requests have been made, the next request + # will shut-down and re-open all connections. + # + # The +no_proxy+ query parameter can be used to specify hosts which shouldn't + # be reached via proxy; if set it should be a comma separated list of + # hostname suffixes, optionally with +:port+ appended, for example + # example.com,some.host:8080. + + def proxy= proxy + @proxy_uri = case proxy + when :ENV then proxy_from_env + when Bundler::URI::HTTP then proxy + when nil then # ignore + else raise ArgumentError, 'proxy must be :ENV or a Bundler::URI::HTTP' + end + + @no_proxy.clear + + if @proxy_uri then + @proxy_args = [ + @proxy_uri.hostname, + @proxy_uri.port, + unescape(@proxy_uri.user), + unescape(@proxy_uri.password), + ] + + @proxy_connection_id = [nil, *@proxy_args].join ':' + + if @proxy_uri.query then + @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? } + end + end + + reconnect + reconnect_ssl + end + + ## + # Creates a Bundler::URI for an HTTP proxy server from ENV variables. + # + # If +HTTP_PROXY+ is set a proxy will be returned. + # + # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the Bundler::URI is given the + # indicated user and password unless HTTP_PROXY contains either of these in + # the Bundler::URI. + # + # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't + # be reached via proxy; if set it should be a comma separated list of + # hostname suffixes, optionally with +:port+ appended, for example + # example.com,some.host:8080. When set to * no proxy will + # be returned. + # + # For Windows users, lowercase ENV variables are preferred over uppercase ENV + # variables. + + def proxy_from_env + env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] + + return nil if env_proxy.nil? or env_proxy.empty? + + uri = Bundler::URI normalize_uri env_proxy + + env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY'] + + # '*' is special case for always bypass + return nil if env_no_proxy == '*' + + if env_no_proxy then + uri.query = "no_proxy=#{escape(env_no_proxy)}" + end + + unless uri.user or uri.password then + uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'] + uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'] + end + + uri + end + + ## + # Returns true when proxy should by bypassed for host. + + def proxy_bypass? host, port + host = host.downcase + host_port = [host, port].join ':' + + @no_proxy.each do |name| + return true if host[-name.length, name.length] == name or + host_port[-name.length, name.length] == name + end + + false + end + + ## + # Forces reconnection of all HTTP connections, including TLS/SSL + # connections. + + def reconnect + @generation += 1 + end + + ## + # Forces reconnection of only TLS/SSL connections. + + def reconnect_ssl + @ssl_generation += 1 + end + + ## + # Finishes then restarts the Net::HTTP +connection+ + + def reset connection + http = connection.http + + finish connection + + start http + rescue Errno::ECONNREFUSED + e = Error.new "connection refused: #{http.address}:#{http.port}" + e.set_backtrace $@ + raise e + rescue Errno::EHOSTDOWN + e = Error.new "host down: #{http.address}:#{http.port}" + e.set_backtrace $@ + raise e + end + + ## + # Makes a request on +uri+. If +req+ is nil a Net::HTTP::Get is performed + # against +uri+. + # + # If a block is passed #request behaves like Net::HTTP#request (the body of + # the response will not have been read). + # + # +req+ must be a Net::HTTPGenericRequest subclass (see Net::HTTP for a list). + + def request uri, req = nil, &block + uri = Bundler::URI uri + req = request_setup req || uri + response = nil + + connection_for uri do |connection| + http = connection.http + + begin + connection.requests += 1 + + response = http.request req, &block + + if req.connection_close? or + (response.http_version <= '1.0' and + not response.connection_keep_alive?) or + response.connection_close? then + finish connection + end + rescue Exception # make sure to close the connection when it was interrupted + finish connection + + raise + ensure + connection.last_use = Time.now + end + end + + @http_versions["#{uri.hostname}:#{uri.port}"] ||= response.http_version + + response + end + + ## + # Creates a GET request if +req_or_uri+ is a Bundler::URI and adds headers to the + # request. + # + # Returns the request. + + def request_setup req_or_uri # :nodoc: + req = if req_or_uri.respond_to? 'request_uri' then + Net::HTTP::Get.new req_or_uri.request_uri + else + req_or_uri + end + + @headers.each do |pair| + req.add_field(*pair) + end + + @override_headers.each do |name, value| + req[name] = value + end + + unless req['Connection'] then + req.add_field 'Connection', 'keep-alive' + req.add_field 'Keep-Alive', @keep_alive + end + + req + end + + ## + # Shuts down all connections + # + # *NOTE*: Calling shutdown for can be dangerous! + # + # If any thread is still using a connection it may cause an error! Call + # #shutdown when you are completely done making requests! + + def shutdown + @pool.shutdown { |http| http.finish } + end + + ## + # Enables SSL on +connection+ + + def ssl connection + connection.use_ssl = true + + connection.ciphers = @ciphers if @ciphers + connection.ssl_timeout = @ssl_timeout if @ssl_timeout + connection.ssl_version = @ssl_version if @ssl_version + connection.min_version = @min_version if @min_version + connection.max_version = @max_version if @max_version + + connection.verify_depth = @verify_depth + connection.verify_mode = @verify_mode + + if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and + not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then + warn <<-WARNING + !!!SECURITY WARNING!!! + +The SSL HTTP connection to: + + #{connection.address}:#{connection.port} + + !!!MAY NOT BE VERIFIED!!! + +On your platform your OpenSSL implementation is broken. + +There is no difference between the values of VERIFY_NONE and VERIFY_PEER. + +This means that attempting to verify the security of SSL connections may not +work. This exposes you to man-in-the-middle exploits, snooping on the +contents of your connection and other dangers to the security of your data. + +To disable this warning define the following constant at top-level in your +application: + + I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil + + WARNING + end + + connection.ca_file = @ca_file if @ca_file + connection.ca_path = @ca_path if @ca_path + + if @ca_file or @ca_path then + connection.verify_mode = OpenSSL::SSL::VERIFY_PEER + connection.verify_callback = @verify_callback if @verify_callback + end + + if @certificate and @private_key then + connection.cert = @certificate + connection.key = @private_key + end + + connection.cert_store = if @cert_store then + @cert_store + else + store = OpenSSL::X509::Store.new + store.set_default_paths + store + end + end + + ## + # SSL session lifetime + + def ssl_timeout= ssl_timeout + @ssl_timeout = ssl_timeout + + reconnect_ssl + end + + ## + # SSL version to use + + def ssl_version= ssl_version + @ssl_version = ssl_version + + reconnect_ssl + end + + ## + # Minimum SSL version to use + + def min_version= min_version + @min_version = min_version + + reconnect_ssl + end + + ## + # maximum SSL version to use + + def max_version= max_version + @max_version = max_version + + reconnect_ssl + end + + ## + # Sets the depth of SSL certificate verification + + def verify_depth= verify_depth + @verify_depth = verify_depth + + reconnect_ssl + end + + ## + # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER. + # + # Setting this to VERIFY_NONE is a VERY BAD IDEA and should NEVER be used. + # Securely transfer the correct certificate and update the default + # certificate store or set the ca file instead. + + def verify_mode= verify_mode + @verify_mode = verify_mode + + reconnect_ssl + end + + ## + # SSL verification callback. + + def verify_callback= callback + @verify_callback = callback + + reconnect_ssl + end +end + +require_relative 'persistent/connection' +require_relative 'persistent/pool' + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb new file mode 100644 index 0000000000..a57a5d1352 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb @@ -0,0 +1,40 @@ +## +# A Net::HTTP connection wrapper that holds extra information for managing the +# connection's lifetime. + +class Bundler::Persistent::Net::HTTP::Persistent::Connection # :nodoc: + + attr_accessor :http + + attr_accessor :last_use + + attr_accessor :requests + + attr_accessor :ssl_generation + + def initialize http_class, http_args, ssl_generation + @http = http_class.new(*http_args) + @ssl_generation = ssl_generation + + reset + end + + def finish + @http.finish + rescue IOError + ensure + reset + end + + def reset + @last_use = Bundler::Persistent::Net::HTTP::Persistent::EPOCH + @requests = 0 + end + + def ressl ssl_generation + @ssl_generation = ssl_generation + + finish + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb new file mode 100644 index 0000000000..9dfa6ffdb1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb @@ -0,0 +1,53 @@ +class Bundler::Persistent::Net::HTTP::Persistent::Pool < Bundler::ConnectionPool # :nodoc: + + attr_reader :available # :nodoc: + attr_reader :key # :nodoc: + + def initialize(options = {}, &block) + super + + @available = Bundler::Persistent::Net::HTTP::Persistent::TimedStackMulti.new(@size, &block) + @key = "current-#{@available.object_id}" + end + + def checkin net_http_args + stack = Thread.current[@key][net_http_args] ||= [] + + raise Bundler::ConnectionPool::Error, 'no connections are checked out' if + stack.empty? + + conn = stack.pop + + if stack.empty? + @available.push conn, connection_args: net_http_args + + Thread.current[@key].delete(net_http_args) + Thread.current[@key] = nil if Thread.current[@key].empty? + end + + nil + end + + def checkout net_http_args + stacks = Thread.current[@key] ||= {} + stack = stacks[net_http_args] ||= [] + + if stack.empty? then + conn = @available.pop connection_args: net_http_args + else + conn = stack.last + end + + stack.push conn + + conn + end + + def shutdown + Thread.current[@key] = nil + super + end +end + +require_relative 'timed_stack_multi' + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb new file mode 100644 index 0000000000..2da881c554 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb @@ -0,0 +1,79 @@ +class Bundler::Persistent::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::TimedStack # :nodoc: + + ## + # Returns a new hash that has arrays for keys + # + # Using a class method to limit the bindings referenced by the hash's + # default_proc + + def self.hash_of_arrays # :nodoc: + Hash.new { |h,k| h[k] = [] } + end + + def initialize(size = 0, &block) + super + + @enqueued = 0 + @ques = self.class.hash_of_arrays + @lru = {} + @key = :"connection_args-#{object_id}" + end + + def empty? + (@created - @enqueued) >= @max + end + + def length + @max - @created + @enqueued + end + + private + + def connection_stored? options = {} # :nodoc: + !@ques[options[:connection_args]].empty? + end + + def fetch_connection options = {} # :nodoc: + connection_args = options[:connection_args] + + @enqueued -= 1 + lru_update connection_args + @ques[connection_args].pop + end + + def lru_update connection_args # :nodoc: + @lru.delete connection_args + @lru[connection_args] = true + end + + def shutdown_connections # :nodoc: + @ques.each_key do |key| + super connection_args: key + end + end + + def store_connection obj, options = {} # :nodoc: + @ques[options[:connection_args]].push obj + @enqueued += 1 + end + + def try_create options = {} # :nodoc: + connection_args = options[:connection_args] + + if @created >= @max && @enqueued >= 1 + oldest, = @lru.first + @lru.delete oldest + @ques[oldest].pop + + @created -= 1 + end + + if @created < @max + @created += 1 + lru_update connection_args + return @create_block.call(connection_args) + end + end + +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor.rb new file mode 100644 index 0000000000..0794dbb522 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor.rb @@ -0,0 +1,516 @@ +require_relative "thor/base" + +class Bundler::Thor + $thor_runner ||= false + class << self + # Allows for custom "Command" package naming. + # + # === Parameters + # name + # options + # + def package_name(name, _ = {}) + @package_name = name.nil? || name == "" ? nil : name + end + + # Sets the default command when thor is executed without an explicit command to be called. + # + # ==== Parameters + # meth:: name of the default command + # + def default_command(meth = nil) + if meth + @default_command = meth == :none ? "help" : meth.to_s + else + @default_command ||= from_superclass(:default_command, "help") + end + end + alias_method :default_task, :default_command + + # Registers another Bundler::Thor subclass as a command. + # + # ==== Parameters + # klass:: Bundler::Thor subclass to register + # command:: Subcommand name to use + # usage:: Short usage for the subcommand + # description:: Description for the subcommand + def register(klass, subcommand_name, usage, description, options = {}) + if klass <= Bundler::Thor::Group + desc usage, description, options + define_method(subcommand_name) { |*args| invoke(klass, args) } + else + desc usage, description, options + subcommand subcommand_name, klass + end + end + + # Defines the usage and the description of the next command. + # + # ==== Parameters + # usage + # description + # options + # + def desc(usage, description, options = {}) + if options[:for] + command = find_and_refresh_command(options[:for]) + command.usage = usage if usage + command.description = description if description + else + @usage = usage + @desc = description + @hide = options[:hide] || false + end + end + + # Defines the long description of the next command. + # + # ==== Parameters + # long description + # + def long_desc(long_description, options = {}) + if options[:for] + command = find_and_refresh_command(options[:for]) + command.long_description = long_description if long_description + else + @long_desc = long_description + end + end + + # Maps an input to a command. If you define: + # + # map "-T" => "list" + # + # Running: + # + # thor -T + # + # Will invoke the list command. + # + # ==== Parameters + # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command. + # + def map(mappings = nil, **kw) + @map ||= from_superclass(:map, {}) + + if mappings && !kw.empty? + mappings = kw.merge!(mappings) + else + mappings ||= kw + end + if mappings + mappings.each do |key, value| + if key.respond_to?(:each) + key.each { |subkey| @map[subkey] = value } + else + @map[key] = value + end + end + end + + @map + end + + # Declares the options for the next command to be declared. + # + # ==== Parameters + # Hash[Symbol => Object]:: The hash key is the name of the option and the value + # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric + # or :required (string). If you give a value, the type of the value is used. + # + def method_options(options = nil) + @method_options ||= {} + build_options(options, @method_options) if options + @method_options + end + + alias_method :options, :method_options + + # Adds an option to the set of method options. If :for is given as option, + # it allows you to change the options from a previous defined command. + # + # def previous_command + # # magic + # end + # + # method_option :foo => :bar, :for => :previous_command + # + # def next_command + # # magic + # end + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc - Description for the argument. + # :required - If the argument is required or not. + # :default - Default value for this argument. It cannot be required and have default values. + # :aliases - Aliases for this option. + # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. + # :banner - String to show on usage notes. + # :hide - If you want to hide this option from the help. + # + def method_option(name, options = {}) + scope = if options[:for] + find_and_refresh_command(options[:for]).options + else + method_options + end + + build_option(name, options, scope) + end + alias_method :option, :method_option + + # Prints help information for the given command. + # + # ==== Parameters + # shell + # command_name + # + def command_help(shell, command_name) + meth = normalize_command_name(command_name) + command = all_commands[meth] + handle_no_command_error(meth) unless command + + shell.say "Usage:" + shell.say " #{banner(command).split("\n").join("\n ")}" + shell.say + class_options_help(shell, nil => command.options.values) + if command.long_description + shell.say "Description:" + shell.print_wrapped(command.long_description, :indent => 2) + else + shell.say command.description + end + end + alias_method :task_help, :command_help + + # Prints help information for this class. + # + # ==== Parameters + # shell + # + def help(shell, subcommand = false) + list = printable_commands(true, subcommand) + Bundler::Thor::Util.thor_classes_in(self).each do |klass| + list += klass.printable_commands(false) + end + list.sort! { |a, b| a[0] <=> b[0] } + + if defined?(@package_name) && @package_name + shell.say "#{@package_name} commands:" + else + shell.say "Commands:" + end + + shell.print_table(list, :indent => 2, :truncate => true) + shell.say + class_options_help(shell) + end + + # Returns commands ready to be printed. + def printable_commands(all = true, subcommand = false) + (all ? all_commands : commands).map do |_, command| + next if command.hidden? + item = [] + item << banner(command, false, subcommand) + item << (command.description ? "# #{command.description.gsub(/\s+/m, ' ')}" : "") + item + end.compact + end + alias_method :printable_tasks, :printable_commands + + def subcommands + @subcommands ||= from_superclass(:subcommands, []) + end + alias_method :subtasks, :subcommands + + def subcommand_classes + @subcommand_classes ||= {} + end + + def subcommand(subcommand, subcommand_class) + subcommands << subcommand.to_s + subcommand_class.subcommand_help subcommand + subcommand_classes[subcommand.to_s] = subcommand_class + + define_method(subcommand) do |*args| + args, opts = Bundler::Thor::Arguments.split(args) + invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}] + invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h") + invoke subcommand_class, *invoke_args + end + subcommand_class.commands.each do |_meth, command| + command.ancestor_name = subcommand + end + end + alias_method :subtask, :subcommand + + # Extend check unknown options to accept a hash of conditions. + # + # === Parameters + # options: A hash containing :only and/or :except keys + def check_unknown_options!(options = {}) + @check_unknown_options ||= {} + options.each do |key, value| + if value + @check_unknown_options[key] = Array(value) + else + @check_unknown_options.delete(key) + end + end + @check_unknown_options + end + + # Overwrite check_unknown_options? to take subcommands and options into account. + def check_unknown_options?(config) #:nodoc: + options = check_unknown_options + return false unless options + + command = config[:current_command] + return true unless command + + name = command.name + + if subcommands.include?(name) + false + elsif options[:except] + !options[:except].include?(name.to_sym) + elsif options[:only] + options[:only].include?(name.to_sym) + else + true + end + end + + # Stop parsing of options as soon as an unknown option or a regular + # argument is encountered. All remaining arguments are passed to the command. + # This is useful if you have a command that can receive arbitrary additional + # options, and where those additional options should not be handled by + # Bundler::Thor. + # + # ==== Example + # + # To better understand how this is useful, let's consider a command that calls + # an external command. A user may want to pass arbitrary options and + # arguments to that command. The command itself also accepts some options, + # which should be handled by Bundler::Thor. + # + # class_option "verbose", :type => :boolean + # stop_on_unknown_option! :exec + # check_unknown_options! :except => :exec + # + # desc "exec", "Run a shell command" + # def exec(*args) + # puts "diagnostic output" if options[:verbose] + # Kernel.exec(*args) + # end + # + # Here +exec+ can be called with +--verbose+ to get diagnostic output, + # e.g.: + # + # $ thor exec --verbose echo foo + # diagnostic output + # foo + # + # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead: + # + # $ thor exec echo --verbose foo + # --verbose foo + # + # ==== Parameters + # Symbol ...:: A list of commands that should be affected. + def stop_on_unknown_option!(*command_names) + @stop_on_unknown_option = stop_on_unknown_option | command_names + end + + def stop_on_unknown_option?(command) #:nodoc: + command && stop_on_unknown_option.include?(command.name.to_sym) + end + + # Disable the check for required options for the given commands. + # This is useful if you have a command that does not need the required options + # to work, like help. + # + # ==== Parameters + # Symbol ...:: A list of commands that should be affected. + def disable_required_check!(*command_names) + @disable_required_check = disable_required_check | command_names + end + + def disable_required_check?(command) #:nodoc: + command && disable_required_check.include?(command.name.to_sym) + end + + protected + + def stop_on_unknown_option #:nodoc: + @stop_on_unknown_option ||= [] + end + + # help command has the required check disabled by default. + def disable_required_check #:nodoc: + @disable_required_check ||= [:help] + end + + # The method responsible for dispatching given the args. + def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength + meth ||= retrieve_command_name(given_args) + command = all_commands[normalize_command_name(meth)] + + if !command && config[:invoked_via_subcommand] + # We're a subcommand and our first argument didn't match any of our + # commands. So we put it back and call our default command. + given_args.unshift(meth) + command = all_commands[normalize_command_name(default_command)] + end + + if command + args, opts = Bundler::Thor::Options.split(given_args) + if stop_on_unknown_option?(command) && !args.empty? + # given_args starts with a non-option, so we treat everything as + # ordinary arguments + args.concat opts + opts.clear + end + else + args = given_args + opts = nil + command = dynamic_command_class.new(meth) + end + + opts = given_opts || opts || [] + config[:current_command] = command + config[:command_options] = command.options + + instance = new(args, opts, config) + yield instance if block_given? + args = instance.args + trailing = args[Range.new(arguments.size, -1)] + instance.invoke_command(command, trailing || []) + end + + # The banner for this class. You can customize it if you are invoking the + # thor class by another ways which is not the Bundler::Thor::Runner. It receives + # the command that is going to be invoked and a boolean which indicates if + # the namespace should be displayed as arguments. + # + def banner(command, namespace = nil, subcommand = false) + command.formatted_usage(self, $thor_runner, subcommand).split("\n").map do |formatted_usage| + "#{basename} #{formatted_usage}" + end.join("\n") + end + + def baseclass #:nodoc: + Bundler::Thor + end + + def dynamic_command_class #:nodoc: + Bundler::Thor::DynamicCommand + end + + def create_command(meth) #:nodoc: + @usage ||= nil + @desc ||= nil + @long_desc ||= nil + @hide ||= nil + + if @usage && @desc + base_class = @hide ? Bundler::Thor::HiddenCommand : Bundler::Thor::Command + commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options) + @usage, @desc, @long_desc, @method_options, @hide = nil + true + elsif all_commands[meth] || meth == "method_missing" + true + else + puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \ + "Call desc if you want this method to be available as command or declare it inside a " \ + "no_commands{} block. Invoked from #{caller[1].inspect}." + false + end + end + alias_method :create_task, :create_command + + def initialize_added #:nodoc: + class_options.merge!(method_options) + @method_options = nil + end + + # Retrieve the command name from given args. + def retrieve_command_name(args) #:nodoc: + meth = args.first.to_s unless args.empty? + args.shift if meth && (map[meth] || meth !~ /^\-/) + end + alias_method :retrieve_task_name, :retrieve_command_name + + # receives a (possibly nil) command name and returns a name that is in + # the commands hash. In addition to normalizing aliases, this logic + # will determine if a shortened command is an unambiguous substring of + # a command or alias. + # + # +normalize_command_name+ also converts names like +animal-prison+ + # into +animal_prison+. + def normalize_command_name(meth) #:nodoc: + return default_command.to_s.tr("-", "_") unless meth + + possibilities = find_command_possibilities(meth) + raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1 + + if possibilities.empty? + meth ||= default_command + elsif map[meth] + meth = map[meth] + else + meth = possibilities.first + end + + meth.to_s.tr("-", "_") # treat foo-bar as foo_bar + end + alias_method :normalize_task_name, :normalize_command_name + + # this is the logic that takes the command name passed in by the user + # and determines whether it is an unambiguous substrings of a command or + # alias name. + def find_command_possibilities(meth) + len = meth.to_s.length + possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort + unique_possibilities = possibilities.map { |k| map[k] || k }.uniq + + if possibilities.include?(meth) + [meth] + elsif unique_possibilities.size == 1 + unique_possibilities + else + possibilities + end + end + alias_method :find_task_possibilities, :find_command_possibilities + + def subcommand_help(cmd) + desc "help [COMMAND]", "Describe subcommands or one specific subcommand" + class_eval " + def help(command = nil, subcommand = true); super; end +" + end + alias_method :subtask_help, :subcommand_help + end + + include Bundler::Thor::Base + + map HELP_MAPPINGS => :help + + desc "help [COMMAND]", "Describe available commands or one specific command" + def help(command = nil, subcommand = false) + if command + if self.class.subcommands.include? command + self.class.subcommand_classes[command].help(shell, true) + else + self.class.command_help(shell, command) + end + else + self.class.help(shell, subcommand) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions.rb new file mode 100644 index 0000000000..de9323b2db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions.rb @@ -0,0 +1,336 @@ +require_relative "actions/create_file" +require_relative "actions/create_link" +require_relative "actions/directory" +require_relative "actions/empty_directory" +require_relative "actions/file_manipulation" +require_relative "actions/inject_into_file" + +class Bundler::Thor + module Actions + attr_accessor :behavior + + def self.included(base) #:nodoc: + super(base) + base.extend ClassMethods + end + + module ClassMethods + # Hold source paths for one Bundler::Thor instance. source_paths_for_search is the + # method responsible to gather source_paths from this current class, + # inherited paths and the source root. + # + def source_paths + @_source_paths ||= [] + end + + # Stores and return the source root for this class + def source_root(path = nil) + @_source_root = path if path + @_source_root ||= nil + end + + # Returns the source paths in the following order: + # + # 1) This class source paths + # 2) Source root + # 3) Parents source paths + # + def source_paths_for_search + paths = [] + paths += source_paths + paths << source_root if source_root + paths += from_superclass(:source_paths, []) + paths + end + + # Add runtime options that help actions execution. + # + def add_runtime_options! + class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, + :desc => "Overwrite files that already exist" + + class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, + :desc => "Run but do not make any changes" + + class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, + :desc => "Suppress status output" + + class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, + :desc => "Skip files that already exist" + end + end + + # Extends initializer to add more configuration options. + # + # ==== Configuration + # behavior:: The actions default behavior. Can be :invoke or :revoke. + # It also accepts :force, :skip and :pretend to set the behavior + # and the respective option. + # + # destination_root:: The root directory needed for some actions. + # + def initialize(args = [], options = {}, config = {}) + self.behavior = case config[:behavior].to_s + when "force", "skip" + _cleanup_options_and_set(options, config[:behavior]) + :invoke + when "revoke" + :revoke + else + :invoke + end + + super + self.destination_root = config[:destination_root] + end + + # Wraps an action object and call it accordingly to the thor class behavior. + # + def action(instance) #:nodoc: + if behavior == :revoke + instance.revoke! + else + instance.invoke! + end + end + + # Returns the root for this thor class (also aliased as destination root). + # + def destination_root + @destination_stack.last + end + + # Sets the root for this thor class. Relatives path are added to the + # directory where the script was invoked and expanded. + # + def destination_root=(root) + @destination_stack ||= [] + @destination_stack[0] = File.expand_path(root || "") + end + + # Returns the given path relative to the absolute root (ie, root where + # the script started). + # + def relative_to_original_destination_root(path, remove_dot = true) + root = @destination_stack[0] + if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size]) + path = path.dup + path[0...root.size] = '.' + remove_dot ? (path[2..-1] || "") : path + else + path + end + end + + # Holds source paths in instance so they can be manipulated. + # + def source_paths + @source_paths ||= self.class.source_paths_for_search + end + + # Receives a file or directory and search for it in the source paths. + # + def find_in_source_paths(file) + possible_files = [file, file + TEMPLATE_EXTNAME] + relative_root = relative_to_original_destination_root(destination_root, false) + + source_paths.each do |source| + possible_files.each do |f| + source_file = File.expand_path(f, File.join(source, relative_root)) + return source_file if File.exist?(source_file) + end + end + + message = "Could not find #{file.inspect} in any of your source paths. ".dup + + unless self.class.source_root + message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. " + end + + message << if source_paths.empty? + "Currently you have no source paths." + else + "Your current source paths are: \n#{source_paths.join("\n")}" + end + + raise Error, message + end + + # Do something in the root or on a provided subfolder. If a relative path + # is given it's referenced from the current root. The full path is yielded + # to the block you provide. The path is set back to the previous path when + # the method exits. + # + # ==== Parameters + # dir:: the directory to move to. + # config:: give :verbose => true to log and use padding. + # + def inside(dir = "", config = {}, &block) + verbose = config.fetch(:verbose, false) + pretend = options[:pretend] + + say_status :inside, dir, verbose + shell.padding += 1 if verbose + @destination_stack.push File.expand_path(dir, destination_root) + + # If the directory doesnt exist and we're not pretending + if !File.exist?(destination_root) && !pretend + require "fileutils" + FileUtils.mkdir_p(destination_root) + end + + if pretend + # In pretend mode, just yield down to the block + block.arity == 1 ? yield(destination_root) : yield + else + require "fileutils" + FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } + end + + @destination_stack.pop + shell.padding -= 1 if verbose + end + + # Goes to the root and execute the given block. + # + def in_root + inside(@destination_stack.first) { yield } + end + + # Loads an external file and execute it in the instance binding. + # + # ==== Parameters + # path:: The path to the file to execute. Can be a web address or + # a relative path from the source root. + # + # ==== Examples + # + # apply "http://gist.github.com/103208" + # + # apply "recipes/jquery.rb" + # + def apply(path, config = {}) + verbose = config.fetch(:verbose, true) + is_uri = path =~ %r{^https?\://} + path = find_in_source_paths(path) unless is_uri + + say_status :apply, path, verbose + shell.padding += 1 if verbose + + contents = if is_uri + require "open-uri" + URI.open(path, "Accept" => "application/x-thor-template", &:read) + else + open(path, &:read) + end + + instance_eval(contents, path) + shell.padding -= 1 if verbose + end + + # Executes a command returning the contents of the command. + # + # ==== Parameters + # command:: the command to be executed. + # config:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with + # to append an executable to command execution. + # + # ==== Example + # + # inside('vendor') do + # run('ln -s ~/edge rails') + # end + # + def run(command, config = {}) + return unless behavior == :invoke + + destination = relative_to_original_destination_root(destination_root, false) + desc = "#{command} from #{destination.inspect}" + + if config[:with] + desc = "#{File.basename(config[:with].to_s)} #{desc}" + command = "#{config[:with]} #{command}" + end + + say_status :run, desc, config.fetch(:verbose, true) + + return if options[:pretend] + + env_splat = [config[:env]] if config[:env] + + if config[:capture] + require "open3" + result, status = Open3.capture2e(*env_splat, command.to_s) + success = status.success? + else + result = system(*env_splat, command.to_s) + success = result + end + + abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?) + + result + end + + # Executes a ruby script (taking into account WIN32 platform quirks). + # + # ==== Parameters + # command:: the command to be executed. + # config:: give :verbose => false to not log the status. + # + def run_ruby_script(command, config = {}) + return unless behavior == :invoke + run command, config.merge(:with => Bundler::Thor::Util.ruby_command) + end + + # Run a thor command. A hash of options can be given and it's converted to + # switches. + # + # ==== Parameters + # command:: the command to be invoked + # args:: arguments to the command + # config:: give :verbose => false to not log the status, :capture => true to hide to output. + # Other options are given as parameter to Bundler::Thor. + # + # + # ==== Examples + # + # thor :install, "http://gist.github.com/103208" + # #=> thor install http://gist.github.com/103208 + # + # thor :list, :all => true, :substring => 'rails' + # #=> thor list --all --substring=rails + # + def thor(command, *args) + config = args.last.is_a?(Hash) ? args.pop : {} + verbose = config.key?(:verbose) ? config.delete(:verbose) : true + pretend = config.key?(:pretend) ? config.delete(:pretend) : false + capture = config.key?(:capture) ? config.delete(:capture) : false + + args.unshift(command) + args.push Bundler::Thor::Options.to_switches(config) + command = args.join(" ").strip + + run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture + end + + protected + + # Allow current root to be shared between invocations. + # + def _shared_configuration #:nodoc: + super.merge!(:destination_root => destination_root) + end + + def _cleanup_options_and_set(options, key) #:nodoc: + case options + when Array + %w(--force -f --skip -s).each { |i| options.delete(i) } + options << "--#{key}" + when Hash + [:force, :skip, "force", "skip"].each { |i| options.delete(i) } + options.merge!(key => true) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb new file mode 100644 index 0000000000..330fc08cae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb @@ -0,0 +1,104 @@ +require_relative "empty_directory" + +class Bundler::Thor + module Actions + # Create a new file relative to the destination root with the given data, + # which is the return value of a block or a data string. + # + # ==== Parameters + # destination:: the relative path to the destination root. + # data:: the data to append to the file. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # create_file "lib/fun_party.rb" do + # hostname = ask("What is the virtual hostname I should use?") + # "vhost.name = #{hostname}" + # end + # + # create_file "config/apache.conf", "your apache config" + # + def create_file(destination, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + data = args.first + action CreateFile.new(self, destination, block || data.to_s, config) + end + alias_method :add_file, :create_file + + # CreateFile is a subset of Template, which instead of rendering a file with + # ERB, it gets the content from the user. + # + class CreateFile < EmptyDirectory #:nodoc: + attr_reader :data + + def initialize(base, destination, data, config = {}) + @data = data + super(base, destination, config) + end + + # Checks if the content of the file at the destination is identical to the rendered result. + # + # ==== Returns + # Boolean:: true if it is identical, false otherwise. + # + def identical? + exists? && File.binread(destination) == render + end + + # Holds the content to be added to the file. + # + def render + @render ||= if data.is_a?(Proc) + data.call + else + data + end + end + + def invoke! + invoke_with_conflict_check do + require "fileutils" + FileUtils.mkdir_p(File.dirname(destination)) + File.open(destination, "wb") { |f| f.write render } + end + given_destination + end + + protected + + # Now on conflict we check if the file is identical or not. + # + def on_conflict_behavior(&block) + if identical? + say_status :identical, :blue + else + options = base.options.merge(config) + force_or_skip_or_conflict(options[:force], options[:skip], &block) + end + end + + # If force is true, run the action, otherwise check if it's not being + # skipped. If both are false, show the file_collision menu, if the menu + # returns true, force it, otherwise skip. + # + def force_or_skip_or_conflict(force, skip, &block) + if force + say_status :force, :yellow + yield unless pretend? + elsif skip + say_status :skip, :yellow + else + say_status :conflict, :red + force_or_skip_or_conflict(force_on_collision?, true, &block) + end + end + + # Shows the file collision menu to the user and gets the result. + # + def force_on_collision? + base.shell.file_collision(destination) { render } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb new file mode 100644 index 0000000000..fb76fcdbe9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb @@ -0,0 +1,61 @@ +require_relative "create_file" + +class Bundler::Thor + module Actions + # Create a new file relative to the destination root from the given source. + # + # ==== Parameters + # destination:: the relative path to the destination root. + # source:: the relative path to the source root. + # config:: give :verbose => false to not log the status. + # :: give :symbolic => false for hard link. + # + # ==== Examples + # + # create_link "config/apache.conf", "/etc/apache.conf" + # + def create_link(destination, *args) + config = args.last.is_a?(Hash) ? args.pop : {} + source = args.first + action CreateLink.new(self, destination, source, config) + end + alias_method :add_link, :create_link + + # CreateLink is a subset of CreateFile, which instead of taking a block of + # data, just takes a source string from the user. + # + class CreateLink < CreateFile #:nodoc: + attr_reader :data + + # Checks if the content of the file at the destination is identical to the rendered result. + # + # ==== Returns + # Boolean:: true if it is identical, false otherwise. + # + def identical? + source = File.expand_path(render, File.dirname(destination)) + exists? && File.identical?(source, destination) + end + + def invoke! + invoke_with_conflict_check do + require "fileutils" + FileUtils.mkdir_p(File.dirname(destination)) + # Create a symlink by default + config[:symbolic] = true if config[:symbolic].nil? + File.unlink(destination) if exists? + if config[:symbolic] + File.symlink(render, destination) + else + File.link(render, destination) + end + end + given_destination + end + + def exists? + super || File.symlink?(destination) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/directory.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/directory.rb new file mode 100644 index 0000000000..d37327a139 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/directory.rb @@ -0,0 +1,108 @@ +require_relative "empty_directory" + +class Bundler::Thor + module Actions + # Copies recursively the files from source directory to root directory. + # If any of the files finishes with .tt, it's considered to be a template + # and is placed in the destination without the extension .tt. If any + # empty directory is found, it's copied and all .empty_directory files are + # ignored. If any file name is wrapped within % signs, the text within + # the % signs will be executed as a method and replaced with the returned + # value. Let's suppose a doc directory with the following files: + # + # doc/ + # components/.empty_directory + # README + # rdoc.rb.tt + # %app_name%.rb + # + # When invoked as: + # + # directory "doc" + # + # It will create a doc directory in the destination with the following + # files (assuming that the `app_name` method returns the value "blog"): + # + # doc/ + # components/ + # README + # rdoc.rb + # blog.rb + # + # Encoded path note: Since Bundler::Thor internals use Object#respond_to? to check if it can + # expand %something%, this `something` should be a public method in the class calling + # #directory. If a method is private, Bundler::Thor stack raises PrivateMethodEncodedError. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # If :recursive => false, does not look for paths recursively. + # If :mode => :preserve, preserve the file mode from the source. + # If :exclude_pattern => /regexp/, prevents copying files that match that regexp. + # + # ==== Examples + # + # directory "doc" + # directory "doc", "docs", :recursive => false + # + def directory(source, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + destination = args.first || source + action Directory.new(self, source, destination || source, config, &block) + end + + class Directory < EmptyDirectory #:nodoc: + attr_reader :source + + def initialize(base, source, destination = nil, config = {}, &block) + @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first) + @block = block + super(base, destination, {:recursive => true}.merge(config)) + end + + def invoke! + base.empty_directory given_destination, config + execute! + end + + def revoke! + execute! + end + + protected + + def execute! + lookup = Util.escape_globs(source) + lookup = config[:recursive] ? File.join(lookup, "**") : lookup + lookup = file_level_lookup(lookup) + + files(lookup).sort.each do |file_source| + next if File.directory?(file_source) + next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern]) + file_destination = File.join(given_destination, file_source.gsub(source, ".")) + file_destination.gsub!("/./", "/") + + case file_source + when /\.empty_directory$/ + dirname = File.dirname(file_destination).gsub(%r{/\.$}, "") + next if dirname == given_destination + base.empty_directory(dirname, config) + when /#{TEMPLATE_EXTNAME}$/ + base.template(file_source, file_destination[0..-4], config, &@block) + else + base.copy_file(file_source, file_destination, config, &@block) + end + end + end + + def file_level_lookup(previous_lookup) + File.join(previous_lookup, "*") + end + + def files(lookup) + Dir.glob(lookup, File::FNM_DOTMATCH) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb new file mode 100644 index 0000000000..284d92c19a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb @@ -0,0 +1,143 @@ +class Bundler::Thor + module Actions + # Creates an empty directory. + # + # ==== Parameters + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # empty_directory "doc" + # + def empty_directory(destination, config = {}) + action EmptyDirectory.new(self, destination, config) + end + + # Class which holds create directory logic. This is the base class for + # other actions like create_file and directory. + # + # This implementation is based in Templater actions, created by Jonas Nicklas + # and Michael S. Klishin under MIT LICENSE. + # + class EmptyDirectory #:nodoc: + attr_reader :base, :destination, :given_destination, :relative_destination, :config + + # Initializes given the source and destination. + # + # ==== Parameters + # base:: A Bundler::Thor::Base instance + # source:: Relative path to the source of this file + # destination:: Relative path to the destination of this file + # config:: give :verbose => false to not log the status. + # + def initialize(base, destination, config = {}) + @base = base + @config = {:verbose => true}.merge(config) + self.destination = destination + end + + # Checks if the destination file already exists. + # + # ==== Returns + # Boolean:: true if the file exists, false otherwise. + # + def exists? + ::File.exist?(destination) + end + + def invoke! + invoke_with_conflict_check do + require "fileutils" + ::FileUtils.mkdir_p(destination) + end + end + + def revoke! + say_status :remove, :red + require "fileutils" + ::FileUtils.rm_rf(destination) if !pretend? && exists? + given_destination + end + + protected + + # Shortcut for pretend. + # + def pretend? + base.options[:pretend] + end + + # Sets the absolute destination value from a relative destination value. + # It also stores the given and relative destination. Let's suppose our + # script is being executed on "dest", it sets the destination root to + # "dest". The destination, given_destination and relative_destination + # are related in the following way: + # + # inside "bar" do + # empty_directory "baz" + # end + # + # destination #=> dest/bar/baz + # relative_destination #=> bar/baz + # given_destination #=> baz + # + def destination=(destination) + return unless destination + @given_destination = convert_encoded_instructions(destination.to_s) + @destination = ::File.expand_path(@given_destination, base.destination_root) + @relative_destination = base.relative_to_original_destination_root(@destination) + end + + # Filenames in the encoded form are converted. If you have a file: + # + # %file_name%.rb + # + # It calls #file_name from the base and replaces %-string with the + # return value (should be String) of #file_name: + # + # user.rb + # + # The method referenced can be either public or private. + # + def convert_encoded_instructions(filename) + filename.gsub(/%(.*?)%/) do |initial_string| + method = $1.strip + base.respond_to?(method, true) ? base.send(method) : initial_string + end + end + + # Receives a hash of options and just execute the block if some + # conditions are met. + # + def invoke_with_conflict_check(&block) + if exists? + on_conflict_behavior(&block) + else + yield unless pretend? + say_status :create, :green + end + + destination + rescue Errno::EISDIR, Errno::EEXIST + on_file_clash_behavior + end + + def on_file_clash_behavior + say_status :file_clash, :red + end + + # What to do when the destination file already exists. + # + def on_conflict_behavior + say_status :exist, :blue + end + + # Shortcut to say_status shell method. + # + def say_status(status, color) + base.shell.say_status status, relative_destination, color if config[:verbose] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb new file mode 100644 index 0000000000..90a8d2e847 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -0,0 +1,375 @@ +require "erb" + +class Bundler::Thor + module Actions + # Copies the file from the relative source to the relative destination. If + # the destination is not given it's assumed to be equal to the source. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status, and + # :mode => :preserve, to preserve the file mode from the source. + + # + # ==== Examples + # + # copy_file "README", "doc/README" + # + # copy_file "doc/README" + # + def copy_file(source, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + destination = args.first || source + source = File.expand_path(find_in_source_paths(source.to_s)) + + resulting_destination = create_file destination, nil, config do + content = File.binread(source) + content = yield(content) if block + content + end + if config[:mode] == :preserve + mode = File.stat(source).mode + chmod(resulting_destination, mode, config) + end + end + + # Links the file from the relative source to the relative destination. If + # the destination is not given it's assumed to be equal to the source. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # link_file "README", "doc/README" + # + # link_file "doc/README" + # + def link_file(source, *args) + config = args.last.is_a?(Hash) ? args.pop : {} + destination = args.first || source + source = File.expand_path(find_in_source_paths(source.to_s)) + + create_link destination, source, config + end + + # Gets the content at the given address and places it at the given relative + # destination. If a block is given instead of destination, the content of + # the url is yielded and used as location. + # + # +get+ relies on open-uri, so passing application user input would provide + # a command injection attack vector. + # + # ==== Parameters + # source:: the address of the given content. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # get "http://gist.github.com/103208", "doc/README" + # + # get "http://gist.github.com/103208" do |content| + # content.split("\n").first + # end + # + def get(source, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + destination = args.first + + render = if source =~ %r{^https?\://} + require "open-uri" + URI.send(:open, source) { |input| input.binmode.read } + else + source = File.expand_path(find_in_source_paths(source.to_s)) + open(source) { |input| input.binmode.read } + end + + destination ||= if block_given? + block.arity == 1 ? yield(render) : yield + else + File.basename(source) + end + + create_file destination, render, config + end + + # Gets an ERB template at the relative source, executes it and makes a copy + # at the relative destination. If the destination is not given it's assumed + # to be equal to the source removing .tt from the filename. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # template "README", "doc/README" + # + # template "doc/README" + # + def template(source, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "") + + source = File.expand_path(find_in_source_paths(source.to_s)) + context = config.delete(:context) || instance_eval("binding") + + create_file destination, nil, config do + match = ERB.version.match(/(\d+\.\d+\.\d+)/) + capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+ + CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer") + else + CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer") + end + content = capturable_erb.tap do |erb| + erb.filename = source + end.result(context) + content = yield(content) if block + content + end + end + + # Changes the mode of the given file or directory. + # + # ==== Parameters + # mode:: the file mode + # path:: the name of the file to change mode + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # chmod "script/server", 0755 + # + def chmod(path, mode, config = {}) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true) + unless options[:pretend] + require "fileutils" + FileUtils.chmod_R(mode, path) + end + end + + # Prepend text to a file. Since it depends on insert_into_file, it's reversible. + # + # ==== Parameters + # path:: path of the file to be changed + # data:: the data to prepend to the file, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"' + # + # prepend_to_file 'config/environments/test.rb' do + # 'config.gem "rspec"' + # end + # + def prepend_to_file(path, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + config[:after] = /\A/ + insert_into_file(path, *(args << config), &block) + end + alias_method :prepend_file, :prepend_to_file + + # Append text to a file. Since it depends on insert_into_file, it's reversible. + # + # ==== Parameters + # path:: path of the file to be changed + # data:: the data to append to the file, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # append_to_file 'config/environments/test.rb', 'config.gem "rspec"' + # + # append_to_file 'config/environments/test.rb' do + # 'config.gem "rspec"' + # end + # + def append_to_file(path, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + config[:before] = /\z/ + insert_into_file(path, *(args << config), &block) + end + alias_method :append_file, :append_to_file + + # Injects text right after the class definition. Since it depends on + # insert_into_file, it's reversible. + # + # ==== Parameters + # path:: path of the file to be changed + # klass:: the class to be manipulated + # data:: the data to append to the class, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n" + # + # inject_into_class "app/controllers/application_controller.rb", ApplicationController do + # " filter_parameter :password\n" + # end + # + def inject_into_class(path, klass, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + config[:after] = /class #{klass}\n|class #{klass} .*\n/ + insert_into_file(path, *(args << config), &block) + end + + # Injects text right after the module definition. Since it depends on + # insert_into_file, it's reversible. + # + # ==== Parameters + # path:: path of the file to be changed + # module_name:: the module to be manipulated + # data:: the data to append to the class, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n" + # + # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do + # " def help; 'help'; end\n" + # end + # + def inject_into_module(path, module_name, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + config[:after] = /module #{module_name}\n|module #{module_name} .*\n/ + insert_into_file(path, *(args << config), &block) + end + + # Run a regular expression replacement on a file. + # + # ==== Parameters + # path:: path of the file to be changed + # flag:: the regexp or string to be replaced + # replacement:: the replacement, can be also given as a block + # config:: give :verbose => false to not log the status, and + # :force => true, to force the replacement regardless of runner behavior. + # + # ==== Example + # + # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' + # + # gsub_file 'README', /rake/, :green do |match| + # match << " no more. Use thor!" + # end + # + def gsub_file(path, flag, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + + return unless behavior == :invoke || config.fetch(:force, false) + + path = File.expand_path(path, destination_root) + say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) + + unless options[:pretend] + content = File.binread(path) + content.gsub!(flag, *args, &block) + File.open(path, "wb") { |file| file.write(content) } + end + end + + # Uncomment all lines matching a given regex. It will leave the space + # which existed before the comment hash in tact but will remove any spacing + # between the comment hash and the beginning of the line. + # + # ==== Parameters + # path:: path of the file to be changed + # flag:: the regexp or string used to decide which lines to uncomment + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # uncomment_lines 'config/initializers/session_store.rb', /active_record/ + # + def uncomment_lines(path, flag, *args) + flag = flag.respond_to?(:source) ? flag.source : flag + + gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args) + end + + # Comment all lines matching a given regex. It will leave the space + # which existed before the beginning of the line in tact and will insert + # a single space after the comment hash. + # + # ==== Parameters + # path:: path of the file to be changed + # flag:: the regexp or string used to decide which lines to comment + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # comment_lines 'config/initializers/session_store.rb', /cookie_store/ + # + def comment_lines(path, flag, *args) + flag = flag.respond_to?(:source) ? flag.source : flag + + gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args) + end + + # Removes a file at the given location. + # + # ==== Parameters + # path:: path of the file to be changed + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # remove_file 'README' + # remove_file 'app/controllers/application_controller.rb' + # + def remove_file(path, config = {}) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + + say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) + if !options[:pretend] && File.exist?(path) + require "fileutils" + ::FileUtils.rm_rf(path) + end + end + alias_method :remove_dir, :remove_file + + attr_accessor :output_buffer + private :output_buffer, :output_buffer= + + private + + def concat(string) + @output_buffer.concat(string) + end + + def capture(*args) + with_output_buffer { yield(*args) } + end + + def with_output_buffer(buf = "".dup) #:nodoc: + raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen? + old_buffer = output_buffer + self.output_buffer = buf + yield + output_buffer + ensure + self.output_buffer = old_buffer + end + + # Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB. + # Thus CapturableERB fixes ERB to use String buffer. + class CapturableERB < ERB + def set_eoutvar(compiler, eoutvar = "_erbout") + compiler.put_cmd = "#{eoutvar}.concat" + compiler.insert_cmd = "#{eoutvar}.concat" + compiler.pre_cmd = ["#{eoutvar} = ''.dup"] + compiler.post_cmd = [eoutvar] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb new file mode 100644 index 0000000000..09ce0864f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb @@ -0,0 +1,120 @@ +require_relative "empty_directory" + +class Bundler::Thor + module Actions + # Injects the given content into a file. Different from gsub_file, this + # method is reversible. + # + # ==== Parameters + # destination:: Relative path to the destination root + # data:: Data to add to the file. Can be given as a block. + # config:: give :verbose => false to not log the status and the flag + # for injection (:after or :before) or :force => true for + # insert two or more times the same content. + # + # ==== Examples + # + # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n" + # + # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do + # gems = ask "Which gems would you like to add?" + # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") + # end + # + WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' } + + def insert_into_file(destination, *args, &block) + data = block_given? ? block : args.shift + + config = args.shift || {} + config[:after] = /\z/ unless config.key?(:before) || config.key?(:after) + + action InjectIntoFile.new(self, destination, data, config) + end + alias_method :inject_into_file, :insert_into_file + + class InjectIntoFile < EmptyDirectory #:nodoc: + attr_reader :replacement, :flag, :behavior + + def initialize(base, destination, data, config) + super(base, destination, {:verbose => true}.merge(config)) + + @behavior, @flag = if @config.key?(:after) + [:after, @config.delete(:after)] + else + [:before, @config.delete(:before)] + end + + @replacement = data.is_a?(Proc) ? data.call : data + @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp) + end + + def invoke! + content = if @behavior == :after + '\0' + replacement + else + replacement + '\0' + end + + if exists? + if replace!(/#{flag}/, content, config[:force]) + say_status(:invoke) + else + say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red) + end + else + unless pretend? + raise Bundler::Thor::Error, "The file #{ destination } does not appear to exist" + end + end + end + + def revoke! + say_status :revoke + + regexp = if @behavior == :after + content = '\1\2' + /(#{flag})(.*)(#{Regexp.escape(replacement)})/m + else + content = '\2\3' + /(#{Regexp.escape(replacement)})(.*)(#{flag})/m + end + + replace!(regexp, content, true) + end + + protected + + def say_status(behavior, warning: nil, color: nil) + status = if behavior == :invoke + if flag == /\A/ + :prepend + elsif flag == /\z/ + :append + else + :insert + end + elsif warning + warning + else + :subtract + end + + super(status, (color || config[:verbose])) + end + + # Adds the content to the file. + # + def replace!(regexp, string, force) + return if pretend? + content = File.read(destination) + if force || !content.include?(replacement) + success = content.gsub!(regexp, string) + + File.open(destination, "wb") { |file| file.write(content) } + success + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/base.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/base.rb new file mode 100644 index 0000000000..8487f9590a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/base.rb @@ -0,0 +1,699 @@ +require_relative "command" +require_relative "core_ext/hash_with_indifferent_access" +require_relative "error" +require_relative "invocation" +require_relative "nested_context" +require_relative "parser" +require_relative "shell" +require_relative "line_editor" +require_relative "util" + +class Bundler::Thor + autoload :Actions, File.expand_path("actions", __dir__) + autoload :RakeCompat, File.expand_path("rake_compat", __dir__) + autoload :Group, File.expand_path("group", __dir__) + + # Shortcuts for help. + HELP_MAPPINGS = %w(-h -? --help -D) + + # Bundler::Thor methods that should not be overwritten by the user. + THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root + action add_file create_file in_root inside run run_ruby_script) + + TEMPLATE_EXTNAME = ".tt" + + class << self + def deprecation_warning(message) #:nodoc: + unless ENV['THOR_SILENCE_DEPRECATION'] + warn "Deprecation warning: #{message}\n" + + 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.' + end + end + end + + module Base + attr_accessor :options, :parent_options, :args + + # It receives arguments in an Array and two hashes, one for options and + # other for configuration. + # + # Notice that it does not check if all required arguments were supplied. + # It should be done by the parser. + # + # ==== Parameters + # args:: An array of objects. The objects are applied to their + # respective accessors declared with argument. + # + # options:: An options hash that will be available as self.options. + # The hash given is converted to a hash with indifferent + # access, magic predicates (options.skip?) and then frozen. + # + # config:: Configuration for this Bundler::Thor class. + # + def initialize(args = [], local_options = {}, config = {}) + parse_options = self.class.class_options + + # The start method splits inbound arguments at the first argument + # that looks like an option (starts with - or --). It then calls + # new, passing in the two halves of the arguments Array as the + # first two parameters. + + command_options = config.delete(:command_options) # hook for start + parse_options = parse_options.merge(command_options) if command_options + if local_options.is_a?(Array) + array_options = local_options + hash_options = {} + else + # Handle the case where the class was explicitly instantiated + # with pre-parsed options. + array_options = [] + hash_options = local_options + end + + # Let Bundler::Thor::Options parse the options first, so it can remove + # declared options from the array. This will leave us with + # a list of arguments that weren't declared. + stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command] + disable_required_check = self.class.disable_required_check? config[:current_command] + opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check) + self.options = opts.parse(array_options) + self.options = config[:class_options].merge(options) if config[:class_options] + + # If unknown options are disallowed, make sure that none of the + # remaining arguments looks like an option. + opts.check_unknown! if self.class.check_unknown_options?(config) + + # Add the remaining arguments from the options parser to the + # arguments passed in to initialize. Then remove any positional + # arguments declared using #argument (this is primarily used + # by Bundler::Thor::Group). Tis will leave us with the remaining + # positional arguments. + to_parse = args + to_parse += opts.remaining unless self.class.strict_args_position?(config) + + thor_args = Bundler::Thor::Arguments.new(self.class.arguments) + thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) } + @args = thor_args.remaining + end + + class << self + def included(base) #:nodoc: + super(base) + base.extend ClassMethods + base.send :include, Invocation + base.send :include, Shell + end + + # Returns the classes that inherits from Bundler::Thor or Bundler::Thor::Group. + # + # ==== Returns + # Array[Class] + # + def subclasses + @subclasses ||= [] + end + + # Returns the files where the subclasses are kept. + # + # ==== Returns + # Hash[path => Class] + # + def subclass_files + @subclass_files ||= Hash.new { |h, k| h[k] = [] } + end + + # Whenever a class inherits from Bundler::Thor or Bundler::Thor::Group, we should track the + # class and the file on Bundler::Thor::Base. This is the method responsible for it. + # + def register_klass_file(klass) #:nodoc: + file = caller[1].match(/(.*):\d+/)[1] + Bundler::Thor::Base.subclasses << klass unless Bundler::Thor::Base.subclasses.include?(klass) + + file_subclasses = Bundler::Thor::Base.subclass_files[File.expand_path(file)] + file_subclasses << klass unless file_subclasses.include?(klass) + end + end + + module ClassMethods + def attr_reader(*) #:nodoc: + no_commands { super } + end + + def attr_writer(*) #:nodoc: + no_commands { super } + end + + def attr_accessor(*) #:nodoc: + no_commands { super } + end + + # If you want to raise an error for unknown options, call check_unknown_options! + # This is disabled by default to allow dynamic invocations. + def check_unknown_options! + @check_unknown_options = true + end + + def check_unknown_options #:nodoc: + @check_unknown_options ||= from_superclass(:check_unknown_options, false) + end + + def check_unknown_options?(config) #:nodoc: + !!check_unknown_options + end + + # If you want to raise an error when the default value of an option does not match + # the type call check_default_type! + # This will be the default; for compatibility a deprecation warning is issued if necessary. + def check_default_type! + @check_default_type = true + end + + # If you want to use defaults that don't match the type of an option, + # either specify `check_default_type: false` or call `allow_incompatible_default_type!` + def allow_incompatible_default_type! + @check_default_type = false + end + + def check_default_type #:nodoc: + @check_default_type = from_superclass(:check_default_type, nil) unless defined?(@check_default_type) + @check_default_type + end + + # If true, option parsing is suspended as soon as an unknown option or a + # regular argument is encountered. All remaining arguments are passed to + # the command as regular arguments. + def stop_on_unknown_option?(command_name) #:nodoc: + false + end + + # If true, option set will not suspend the execution of the command when + # a required option is not provided. + def disable_required_check?(command_name) #:nodoc: + false + end + + # If you want only strict string args (useful when cascading thor classes), + # call strict_args_position! This is disabled by default to allow dynamic + # invocations. + def strict_args_position! + @strict_args_position = true + end + + def strict_args_position #:nodoc: + @strict_args_position ||= from_superclass(:strict_args_position, false) + end + + def strict_args_position?(config) #:nodoc: + !!strict_args_position + end + + # Adds an argument to the class and creates an attr_accessor for it. + # + # Arguments are different from options in several aspects. The first one + # is how they are parsed from the command line, arguments are retrieved + # from position: + # + # thor command NAME + # + # Instead of: + # + # thor command --name=NAME + # + # Besides, arguments are used inside your code as an accessor (self.argument), + # while options are all kept in a hash (self.options). + # + # Finally, arguments cannot have type :default or :boolean but can be + # optional (supplying :optional => :true or :required => false), although + # you cannot have a required argument after a non-required argument. If you + # try it, an error is raised. + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc - Description for the argument. + # :required - If the argument is required or not. + # :optional - If the argument is optional or not. + # :type - The type of the argument, can be :string, :hash, :array, :numeric. + # :default - Default value for this argument. It cannot be required and have default values. + # :banner - String to show on usage notes. + # + # ==== Errors + # ArgumentError:: Raised if you supply a required argument after a non required one. + # + def argument(name, options = {}) + is_thor_reserved_word?(name, :argument) + no_commands { attr_accessor name } + + required = if options.key?(:optional) + !options[:optional] + elsif options.key?(:required) + options[:required] + else + options[:default].nil? + end + + remove_argument name + + if required + arguments.each do |argument| + next if argument.required? + raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \ + "the non-required argument #{argument.human_name.inspect}." + end + end + + options[:required] = required + + arguments << Bundler::Thor::Argument.new(name, options) + end + + # Returns this class arguments, looking up in the ancestors chain. + # + # ==== Returns + # Array[Bundler::Thor::Argument] + # + def arguments + @arguments ||= from_superclass(:arguments, []) + end + + # Adds a bunch of options to the set of class options. + # + # class_options :foo => false, :bar => :required, :baz => :string + # + # If you prefer more detailed declaration, check class_option. + # + # ==== Parameters + # Hash[Symbol => Object] + # + def class_options(options = nil) + @class_options ||= from_superclass(:class_options, {}) + build_options(options, @class_options) if options + @class_options + end + + # Adds an option to the set of class options + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc:: -- Description for the argument. + # :required:: -- If the argument is required or not. + # :default:: -- Default value for this argument. + # :group:: -- The group for this options. Use by class options to output options in different levels. + # :aliases:: -- Aliases for this option. Note: Bundler::Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead. + # :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean. + # :banner:: -- String to show on usage notes. + # :hide:: -- If you want to hide this option from the help. + # + def class_option(name, options = {}) + build_option(name, options, class_options) + end + + # Removes a previous defined argument. If :undefine is given, undefine + # accessors as well. + # + # ==== Parameters + # names:: Arguments to be removed + # + # ==== Examples + # + # remove_argument :foo + # remove_argument :foo, :bar, :baz, :undefine => true + # + def remove_argument(*names) + options = names.last.is_a?(Hash) ? names.pop : {} + + names.each do |name| + arguments.delete_if { |a| a.name == name.to_s } + undef_method name, "#{name}=" if options[:undefine] + end + end + + # Removes a previous defined class option. + # + # ==== Parameters + # names:: Class options to be removed + # + # ==== Examples + # + # remove_class_option :foo + # remove_class_option :foo, :bar, :baz + # + def remove_class_option(*names) + names.each do |name| + class_options.delete(name) + end + end + + # Defines the group. This is used when thor list is invoked so you can specify + # that only commands from a pre-defined group will be shown. Defaults to standard. + # + # ==== Parameters + # name + # + def group(name = nil) + if name + @group = name.to_s + else + @group ||= from_superclass(:group, "standard") + end + end + + # Returns the commands for this Bundler::Thor class. + # + # ==== Returns + # Hash:: An ordered hash with commands names as keys and Bundler::Thor::Command + # objects as values. + # + def commands + @commands ||= Hash.new + end + alias_method :tasks, :commands + + # Returns the commands for this Bundler::Thor class and all subclasses. + # + # ==== Returns + # Hash:: An ordered hash with commands names as keys and Bundler::Thor::Command + # objects as values. + # + def all_commands + @all_commands ||= from_superclass(:all_commands, Hash.new) + @all_commands.merge!(commands) + end + alias_method :all_tasks, :all_commands + + # Removes a given command from this Bundler::Thor class. This is usually done if you + # are inheriting from another class and don't want it to be available + # anymore. + # + # By default it only remove the mapping to the command. But you can supply + # :undefine => true to undefine the method from the class as well. + # + # ==== Parameters + # name:: The name of the command to be removed + # options:: You can give :undefine => true if you want commands the method + # to be undefined from the class as well. + # + def remove_command(*names) + options = names.last.is_a?(Hash) ? names.pop : {} + + names.each do |name| + commands.delete(name.to_s) + all_commands.delete(name.to_s) + undef_method name if options[:undefine] + end + end + alias_method :remove_task, :remove_command + + # All methods defined inside the given block are not added as commands. + # + # So you can do: + # + # class MyScript < Bundler::Thor + # no_commands do + # def this_is_not_a_command + # end + # end + # end + # + # You can also add the method and remove it from the command list: + # + # class MyScript < Bundler::Thor + # def this_is_not_a_command + # end + # remove_command :this_is_not_a_command + # end + # + def no_commands(&block) + no_commands_context.enter(&block) + end + + alias_method :no_tasks, :no_commands + + def no_commands_context + @no_commands_context ||= NestedContext.new + end + + def no_commands? + no_commands_context.entered? + end + + # Sets the namespace for the Bundler::Thor or Bundler::Thor::Group class. By default the + # namespace is retrieved from the class name. If your Bundler::Thor class is named + # Scripts::MyScript, the help method, for example, will be called as: + # + # thor scripts:my_script -h + # + # If you change the namespace: + # + # namespace :my_scripts + # + # You change how your commands are invoked: + # + # thor my_scripts -h + # + # Finally, if you change your namespace to default: + # + # namespace :default + # + # Your commands can be invoked with a shortcut. Instead of: + # + # thor :my_command + # + def namespace(name = nil) + if name + @namespace = name.to_s + else + @namespace ||= Bundler::Thor::Util.namespace_from_thor_class(self) + end + end + + # Parses the command and options from the given args, instantiate the class + # and invoke the command. This method is used when the arguments must be parsed + # from an array. If you are inside Ruby and want to use a Bundler::Thor class, you + # can simply initialize it: + # + # script = MyScript.new(args, options, config) + # script.invoke(:command, first_arg, second_arg, third_arg) + # + def start(given_args = ARGV, config = {}) + config[:shell] ||= Bundler::Thor::Base.shell.new + dispatch(nil, given_args.dup, nil, config) + rescue Bundler::Thor::Error => e + config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message) + exit(false) if exit_on_failure? + rescue Errno::EPIPE + # This happens if a thor command is piped to something like `head`, + # which closes the pipe when it's done reading. This will also + # mean that if the pipe is closed, further unnecessary + # computation will not occur. + exit(true) + end + + # Allows to use private methods from parent in child classes as commands. + # + # ==== Parameters + # names:: Method names to be used as commands + # + # ==== Examples + # + # public_command :foo + # public_command :foo, :bar, :baz + # + def public_command(*names) + names.each do |name| + class_eval "def #{name}(*); super end" + end + end + alias_method :public_task, :public_command + + def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc: + raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace)) + end + alias_method :handle_no_task_error, :handle_no_command_error + + def handle_argument_error(command, error, args, arity) #:nodoc: + name = [command.ancestor_name, command.name].compact.join(" ") + msg = "ERROR: \"#{basename} #{name}\" was called with ".dup + msg << "no arguments" if args.empty? + msg << "arguments " << args.inspect unless args.empty? + msg << "\nUsage: \"#{banner(command).split("\n").join("\"\n \"")}\"" + raise InvocationError, msg + end + + # A flag that makes the process exit with status 1 if any error happens. + def exit_on_failure? + Bundler::Thor.deprecation_warning "Bundler::Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `#{self.name}`" + false + end + + protected + + # Prints the class options per group. If an option does not belong to + # any group, it's printed as Class option. + # + def class_options_help(shell, groups = {}) #:nodoc: + # Group options by group + class_options.each do |_, value| + groups[value.group] ||= [] + groups[value.group] << value + end + + # Deal with default group + global_options = groups.delete(nil) || [] + print_options(shell, global_options) + + # Print all others + groups.each do |group_name, options| + print_options(shell, options, group_name) + end + end + + # Receives a set of options and print them. + def print_options(shell, options, group_name = nil) + return if options.empty? + + list = [] + padding = options.map { |o| o.aliases.size }.max.to_i * 4 + + options.each do |option| + next if option.hide + item = [option.usage(padding)] + item.push(option.description ? "# #{option.description}" : "") + + list << item + list << ["", "# Default: #{option.default}"] if option.show_default? + list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum + end + + shell.say(group_name ? "#{group_name} options:" : "Options:") + shell.print_table(list, :indent => 2) + shell.say "" + end + + # Raises an error if the word given is a Bundler::Thor reserved word. + def is_thor_reserved_word?(word, type) #:nodoc: + return false unless THOR_RESERVED_WORDS.include?(word.to_s) + raise "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}" + end + + # Build an option and adds it to the given scope. + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described in both class_option and method_option. + # scope:: Options hash that is being built up + def build_option(name, options, scope) #:nodoc: + scope[name] = Bundler::Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options)) + end + + # Receives a hash of options, parse them and add to the scope. This is a + # fast way to set a bunch of options: + # + # build_options :foo => true, :bar => :required, :baz => :string + # + # ==== Parameters + # Hash[Symbol => Object] + def build_options(options, scope) #:nodoc: + options.each do |key, value| + scope[key] = Bundler::Thor::Option.parse(key, value) + end + end + + # Finds a command with the given name. If the command belongs to the current + # class, just return it, otherwise dup it and add the fresh copy to the + # current command hash. + def find_and_refresh_command(name) #:nodoc: + if commands[name.to_s] + commands[name.to_s] + elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition + commands[name.to_s] = command.clone + else + raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." + end + end + alias_method :find_and_refresh_task, :find_and_refresh_command + + # Everytime someone inherits from a Bundler::Thor class, register the klass + # and file into baseclass. + def inherited(klass) + super(klass) + Bundler::Thor::Base.register_klass_file(klass) + klass.instance_variable_set(:@no_commands, 0) + end + + # Fire this callback whenever a method is added. Added methods are + # tracked as commands by invoking the create_command method. + def method_added(meth) + super(meth) + meth = meth.to_s + + if meth == "initialize" + initialize_added + return + end + + # Return if it's not a public instance method + return unless public_method_defined?(meth.to_sym) + + return if no_commands? || !create_command(meth) + + is_thor_reserved_word?(meth, :command) + Bundler::Thor::Base.register_klass_file(self) + end + + # Retrieves a value from superclass. If it reaches the baseclass, + # returns default. + def from_superclass(method, default = nil) + if self == baseclass || !superclass.respond_to?(method, true) + default + else + value = superclass.send(method) + + # Ruby implements `dup` on Object, but raises a `TypeError` + # if the method is called on immediates. As a result, we + # don't have a good way to check whether dup will succeed + # without calling it and rescuing the TypeError. + begin + value.dup + rescue TypeError + value + end + + end + end + + # + # The basename of the program invoking the thor class. + # + def basename + File.basename($PROGRAM_NAME).split(" ").first + end + + # SIGNATURE: Sets the baseclass. This is where the superclass lookup + # finishes. + def baseclass #:nodoc: + end + + # SIGNATURE: Creates a new command if valid_command? is true. This method is + # called when a new method is added to the class. + def create_command(meth) #:nodoc: + end + alias_method :create_task, :create_command + + # SIGNATURE: Defines behavior when the initialize method is added to the + # class. + def initialize_added #:nodoc: + end + + # SIGNATURE: The hook invoked by start. + def dispatch(command, given_args, given_opts, config) #:nodoc: + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/command.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/command.rb new file mode 100644 index 0000000000..040c971c0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/command.rb @@ -0,0 +1,142 @@ +class Bundler::Thor + class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name) + FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ + + def initialize(name, description, long_description, usage, options = nil) + super(name.to_s, description, long_description, usage, options || {}) + end + + def initialize_copy(other) #:nodoc: + super(other) + self.options = other.options.dup if other.options + end + + def hidden? + false + end + + # By default, a command invokes a method in the thor class. You can change this + # implementation to create custom commands. + def run(instance, args = []) + arity = nil + + if private_method?(instance) + instance.class.handle_no_command_error(name) + elsif public_method?(instance) + arity = instance.method(name).arity + instance.__send__(name, *args) + elsif local_method?(instance, :method_missing) + instance.__send__(:method_missing, name.to_sym, *args) + else + instance.class.handle_no_command_error(name) + end + rescue ArgumentError => e + handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e) + rescue NoMethodError => e + handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e) + end + + # Returns the formatted usage by injecting given required arguments + # and required options into the given usage. + def formatted_usage(klass, namespace = true, subcommand = false) + if ancestor_name + formatted = "#{ancestor_name} ".dup # add space + elsif namespace + namespace = klass.namespace + formatted = "#{namespace.gsub(/^(default)/, '')}:".dup + end + formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand + + formatted ||= "".dup + + Array(usage).map do |specific_usage| + formatted_specific_usage = formatted + + formatted_specific_usage += required_arguments_for(klass, specific_usage) + + # Add required options + formatted_specific_usage += " #{required_options}" + + # Strip and go! + formatted_specific_usage.strip + end.join("\n") + end + + protected + + # Add usage with required arguments + def required_arguments_for(klass, usage) + if klass && !klass.arguments.empty? + usage.to_s.gsub(/^#{name}/) do |match| + match << " " << klass.arguments.map(&:usage).compact.join(" ") + end + else + usage.to_s + end + end + + def not_debugging?(instance) + !(instance.class.respond_to?(:debugging) && instance.class.debugging) + end + + def required_options + @required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ") + end + + # Given a target, checks if this class name is a public method. + def public_method?(instance) #:nodoc: + !(instance.public_methods & [name.to_s, name.to_sym]).empty? + end + + def private_method?(instance) + !(instance.private_methods & [name.to_s, name.to_sym]).empty? + end + + def local_method?(instance, name) + methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false) + !(methods & [name.to_s, name.to_sym]).empty? + end + + def sans_backtrace(backtrace, caller) #:nodoc: + saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) } + saned - caller + end + + def handle_argument_error?(instance, error, caller) + not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin + saned = sans_backtrace(error.backtrace, caller) + saned.empty? || saned.size == 1 + end + end + + def handle_no_method_error?(instance, error, caller) + not_debugging?(instance) && + error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ + end + end + Task = Command + + # A command that is hidden in help messages but still invocable. + class HiddenCommand < Command + def hidden? + true + end + end + HiddenTask = HiddenCommand + + # A dynamic command that handles method missing scenarios. + class DynamicCommand < Command + def initialize(name, options = nil) + super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options) + end + + def run(instance, args = []) + if (instance.methods & [name.to_s, name.to_sym]).empty? + super + else + instance.class.handle_no_command_error(name) + end + end + end + DynamicTask = DynamicCommand +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb new file mode 100644 index 0000000000..c167aa33b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -0,0 +1,97 @@ +class Bundler::Thor + module CoreExt #:nodoc: + # A hash with indifferent access and magic predicates. + # + # hash = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true + # + # hash[:foo] #=> 'bar' + # hash['foo'] #=> 'bar' + # hash.foo? #=> true + # + class HashWithIndifferentAccess < ::Hash #:nodoc: + def initialize(hash = {}) + super() + hash.each do |key, value| + self[convert_key(key)] = value + end + end + + def [](key) + super(convert_key(key)) + end + + def []=(key, value) + super(convert_key(key), value) + end + + def delete(key) + super(convert_key(key)) + end + + def fetch(key, *args) + super(convert_key(key), *args) + end + + def key?(key) + super(convert_key(key)) + end + + def values_at(*indices) + indices.map { |key| self[convert_key(key)] } + end + + def merge(other) + dup.merge!(other) + end + + def merge!(other) + other.each do |key, value| + self[convert_key(key)] = value + end + self + end + + def reverse_merge(other) + self.class.new(other).merge(self) + end + + def reverse_merge!(other_hash) + replace(reverse_merge(other_hash)) + end + + def replace(other_hash) + super(other_hash) + end + + # Convert to a Hash with String keys. + def to_hash + Hash.new(default).merge!(self) + end + + protected + + def convert_key(key) + key.is_a?(Symbol) ? key.to_s : key + end + + # Magic predicates. For instance: + # + # options.force? # => !!options['force'] + # options.shebang # => "/usr/lib/local/ruby" + # options.test_framework?(:rspec) # => options[:test_framework] == :rspec + # + def method_missing(method, *args) + method = method.to_s + if method =~ /^(\w+)\?$/ + if args.empty? + !!self[$1] + else + self[$1] == args.first + end + else + self[method] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/error.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/error.rb new file mode 100644 index 0000000000..7d57129b83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/error.rb @@ -0,0 +1,110 @@ +class Bundler::Thor + Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName + # In order to support versions of Ruby that don't have keyword + # arguments, we need our own spell checker class that doesn't take key + # words. Even though this code wouldn't be hit because of the check + # above, it's still necessary because the interpreter would otherwise be + # unable to parse the file. + class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc: + def initialize(dictionary) + @dictionary = dictionary + end + end + + DidYouMean::Correctable + end + + # Bundler::Thor::Error is raised when it's caused by wrong usage of thor classes. Those + # errors have their backtrace suppressed and are nicely shown to the user. + # + # Errors that are caused by the developer, like declaring a method which + # overwrites a thor keyword, SHOULD NOT raise a Bundler::Thor::Error. This way, we + # ensure that developer errors are shown with full backtrace. + class Error < StandardError + end + + # Raised when a command was not found. + class UndefinedCommandError < Error + class SpellChecker + attr_reader :error + + def initialize(error) + @error = error + end + + def corrections + @corrections ||= spell_checker.correct(error.command).map(&:inspect) + end + + def spell_checker + NoKwargSpellChecker.new(error.all_commands) + end + end + + attr_reader :command, :all_commands + + def initialize(command, all_commands, namespace) + @command = command + @all_commands = all_commands + + message = "Could not find command #{command.inspect}" + message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}." + + super(message) + end + + prepend Correctable if Correctable + end + UndefinedTaskError = UndefinedCommandError + + class AmbiguousCommandError < Error + end + AmbiguousTaskError = AmbiguousCommandError + + # Raised when a command was found, but not invoked properly. + class InvocationError < Error + end + + class UnknownArgumentError < Error + class SpellChecker + attr_reader :error + + def initialize(error) + @error = error + end + + def corrections + @corrections ||= + error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect) + end + + def spell_checker + @spell_checker ||= NoKwargSpellChecker.new(error.switches) + end + end + + attr_reader :switches, :unknown + + def initialize(switches, unknown) + @switches = switches + @unknown = unknown + + super("Unknown switches #{unknown.map(&:inspect).join(', ')}") + end + + prepend Correctable if Correctable + end + + class RequiredArgumentMissingError < InvocationError + end + + class MalformattedArgumentError < InvocationError + end + + if Correctable + DidYouMean::SPELL_CHECKERS.merge!( + 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, + 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker + ) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/group.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/group.rb new file mode 100644 index 0000000000..7861d05345 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/group.rb @@ -0,0 +1,281 @@ +require_relative "base" + +# Bundler::Thor has a special class called Bundler::Thor::Group. The main difference to Bundler::Thor class +# is that it invokes all commands at once. It also include some methods that allows +# invocations to be done at the class method, which are not available to Bundler::Thor +# commands. +class Bundler::Thor::Group + class << self + # The description for this Bundler::Thor::Group. If none is provided, but a source root + # exists, tries to find the USAGE one folder above it, otherwise searches + # in the superclass. + # + # ==== Parameters + # description:: The description for this Bundler::Thor::Group. + # + def desc(description = nil) + if description + @desc = description + else + @desc ||= from_superclass(:desc, nil) + end + end + + # Prints help information. + # + # ==== Options + # short:: When true, shows only usage. + # + def help(shell) + shell.say "Usage:" + shell.say " #{banner}\n" + shell.say + class_options_help(shell) + shell.say desc if desc + end + + # Stores invocations for this class merging with superclass values. + # + def invocations #:nodoc: + @invocations ||= from_superclass(:invocations, {}) + end + + # Stores invocation blocks used on invoke_from_option. + # + def invocation_blocks #:nodoc: + @invocation_blocks ||= from_superclass(:invocation_blocks, {}) + end + + # Invoke the given namespace or class given. It adds an instance + # method that will invoke the klass and command. You can give a block to + # configure how it will be invoked. + # + # The namespace/class given will have its options showed on the help + # usage. Check invoke_from_option for more information. + # + def invoke(*names, &block) + options = names.last.is_a?(Hash) ? names.pop : {} + verbose = options.fetch(:verbose, true) + + names.each do |name| + invocations[name] = false + invocation_blocks[name] = block if block_given? + + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def _invoke_#{name.to_s.gsub(/\W/, '_')} + klass, command = self.class.prepare_for_invocation(nil, #{name.inspect}) + + if klass + say_status :invoke, #{name.inspect}, #{verbose.inspect} + block = self.class.invocation_blocks[#{name.inspect}] + _invoke_for_class_method klass, command, &block + else + say_status :error, %(#{name.inspect} [not found]), :red + end + end + METHOD + end + end + + # Invoke a thor class based on the value supplied by the user to the + # given option named "name". A class option must be created before this + # method is invoked for each name given. + # + # ==== Examples + # + # class GemGenerator < Bundler::Thor::Group + # class_option :test_framework, :type => :string + # invoke_from_option :test_framework + # end + # + # ==== Boolean options + # + # In some cases, you want to invoke a thor class if some option is true or + # false. This is automatically handled by invoke_from_option. Then the + # option name is used to invoke the generator. + # + # ==== Preparing for invocation + # + # In some cases you want to customize how a specified hook is going to be + # invoked. You can do that by overwriting the class method + # prepare_for_invocation. The class method must necessarily return a klass + # and an optional command. + # + # ==== Custom invocations + # + # You can also supply a block to customize how the option is going to be + # invoked. The block receives two parameters, an instance of the current + # class and the klass to be invoked. + # + def invoke_from_option(*names, &block) + options = names.last.is_a?(Hash) ? names.pop : {} + verbose = options.fetch(:verbose, :white) + + names.each do |name| + unless class_options.key?(name) + raise ArgumentError, "You have to define the option #{name.inspect} " \ + "before setting invoke_from_option." + end + + invocations[name] = true + invocation_blocks[name] = block if block_given? + + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} + return unless options[#{name.inspect}] + + value = options[#{name.inspect}] + value = #{name.inspect} if TrueClass === value + klass, command = self.class.prepare_for_invocation(#{name.inspect}, value) + + if klass + say_status :invoke, value, #{verbose.inspect} + block = self.class.invocation_blocks[#{name.inspect}] + _invoke_for_class_method klass, command, &block + else + say_status :error, %(\#{value} [not found]), :red + end + end + METHOD + end + end + + # Remove a previously added invocation. + # + # ==== Examples + # + # remove_invocation :test_framework + # + def remove_invocation(*names) + names.each do |name| + remove_command(name) + remove_class_option(name) + invocations.delete(name) + invocation_blocks.delete(name) + end + end + + # Overwrite class options help to allow invoked generators options to be + # shown recursively when invoking a generator. + # + def class_options_help(shell, groups = {}) #:nodoc: + get_options_from_invocations(groups, class_options) do |klass| + klass.send(:get_options_from_invocations, groups, class_options) + end + super(shell, groups) + end + + # Get invocations array and merge options from invocations. Those + # options are added to group_options hash. Options that already exists + # in base_options are not added twice. + # + def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength + invocations.each do |name, from_option| + value = if from_option + option = class_options[name] + option.type == :boolean ? name : option.default + else + name + end + next unless value + + klass, _ = prepare_for_invocation(name, value) + next unless klass && klass.respond_to?(:class_options) + + value = value.to_s + human_name = value.respond_to?(:classify) ? value.classify : value + + group_options[human_name] ||= [] + group_options[human_name] += klass.class_options.values.select do |class_option| + base_options[class_option.name.to_sym].nil? && class_option.group.nil? && + !group_options.values.flatten.any? { |i| i.name == class_option.name } + end + + yield klass if block_given? + end + end + + # Returns commands ready to be printed. + def printable_commands(*) + item = [] + item << banner + item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "") + [item] + end + alias_method :printable_tasks, :printable_commands + + def handle_argument_error(command, error, _args, arity) #:nodoc: + msg = "#{basename} #{command.name} takes #{arity} argument".dup + msg << "s" if arity > 1 + msg << ", but it should not." + raise error, msg + end + + protected + + # The method responsible for dispatching given the args. + def dispatch(command, given_args, given_opts, config) #:nodoc: + if Bundler::Thor::HELP_MAPPINGS.include?(given_args.first) + help(config[:shell]) + return + end + + args, opts = Bundler::Thor::Options.split(given_args) + opts = given_opts || opts + + instance = new(args, opts, config) + yield instance if block_given? + + if command + instance.invoke_command(all_commands[command]) + else + instance.invoke_all + end + end + + # The banner for this class. You can customize it if you are invoking the + # thor class by another ways which is not the Bundler::Thor::Runner. + def banner + "#{basename} #{self_command.formatted_usage(self, false)}" + end + + # Represents the whole class as a command. + def self_command #:nodoc: + Bundler::Thor::DynamicCommand.new(namespace, class_options) + end + alias_method :self_task, :self_command + + def baseclass #:nodoc: + Bundler::Thor::Group + end + + def create_command(meth) #:nodoc: + commands[meth.to_s] = Bundler::Thor::Command.new(meth, nil, nil, nil, nil) + true + end + alias_method :create_task, :create_command + end + + include Bundler::Thor::Base + +protected + + # Shortcut to invoke with padding and block handling. Use internally by + # invoke and invoke_from_option class methods. + def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc: + with_padding do + if block + case block.arity + when 3 + yield(self, klass, command) + when 2 + yield(self, klass) + when 1 + instance_exec(klass, &block) + end + else + invoke klass, command, *args + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/invocation.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/invocation.rb new file mode 100644 index 0000000000..248a466f8e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/invocation.rb @@ -0,0 +1,178 @@ +class Bundler::Thor + module Invocation + def self.included(base) #:nodoc: + super(base) + base.extend ClassMethods + end + + module ClassMethods + # This method is responsible for receiving a name and find the proper + # class and command for it. The key is an optional parameter which is + # available only in class methods invocations (i.e. in Bundler::Thor::Group). + def prepare_for_invocation(key, name) #:nodoc: + case name + when Symbol, String + Bundler::Thor::Util.find_class_and_command_by_namespace(name.to_s, !key) + else + name + end + end + end + + # Make initializer aware of invocations and the initialization args. + def initialize(args = [], options = {}, config = {}, &block) #:nodoc: + @_invocations = config[:invocations] || Hash.new { |h, k| h[k] = [] } + @_initializer = [args, options, config] + super + end + + # Make the current command chain accessible with in a Bundler::Thor-(sub)command + def current_command_chain + @_invocations.values.flatten.map(&:to_sym) + end + + # Receives a name and invokes it. The name can be a string (either "command" or + # "namespace:command"), a Bundler::Thor::Command, a Class or a Bundler::Thor instance. If the + # command cannot be guessed by name, it can also be supplied as second argument. + # + # You can also supply the arguments, options and configuration values for + # the command to be invoked, if none is given, the same values used to + # initialize the invoker are used to initialize the invoked. + # + # When no name is given, it will invoke the default command of the current class. + # + # ==== Examples + # + # class A < Bundler::Thor + # def foo + # invoke :bar + # invoke "b:hello", ["Erik"] + # end + # + # def bar + # invoke "b:hello", ["Erik"] + # end + # end + # + # class B < Bundler::Thor + # def hello(name) + # puts "hello #{name}" + # end + # end + # + # You can notice that the method "foo" above invokes two commands: "bar", + # which belongs to the same class and "hello" which belongs to the class B. + # + # By using an invocation system you ensure that a command is invoked only once. + # In the example above, invoking "foo" will invoke "b:hello" just once, even + # if it's invoked later by "bar" method. + # + # When class A invokes class B, all arguments used on A initialization are + # supplied to B. This allows lazy parse of options. Let's suppose you have + # some rspec commands: + # + # class Rspec < Bundler::Thor::Group + # class_option :mock_framework, :type => :string, :default => :rr + # + # def invoke_mock_framework + # invoke "rspec:#{options[:mock_framework]}" + # end + # end + # + # As you noticed, it invokes the given mock framework, which might have its + # own options: + # + # class Rspec::RR < Bundler::Thor::Group + # class_option :style, :type => :string, :default => :mock + # end + # + # Since it's not rspec concern to parse mock framework options, when RR + # is invoked all options are parsed again, so RR can extract only the options + # that it's going to use. + # + # If you want Rspec::RR to be initialized with its own set of options, you + # have to do that explicitly: + # + # invoke "rspec:rr", [], :style => :foo + # + # Besides giving an instance, you can also give a class to invoke: + # + # invoke Rspec::RR, [], :style => :foo + # + def invoke(name = nil, *args) + if name.nil? + warn "[Bundler::Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}" + return invoke_all + end + + args.unshift(nil) if args.first.is_a?(Array) || args.first.nil? + command, args, opts, config = args + + klass, command = _retrieve_class_and_command(name, command) + raise "Missing Bundler::Thor class for invoke #{name}" unless klass + raise "Expected Bundler::Thor class, got #{klass}" unless klass <= Bundler::Thor::Base + + args, opts, config = _parse_initialization_options(args, opts, config) + klass.send(:dispatch, command, args, opts, config) do |instance| + instance.parent_options = options + end + end + + # Invoke the given command if the given args. + def invoke_command(command, *args) #:nodoc: + current = @_invocations[self.class] + + unless current.include?(command.name) + current << command.name + command.run(self, *args) + end + end + alias_method :invoke_task, :invoke_command + + # Invoke all commands for the current instance. + def invoke_all #:nodoc: + self.class.all_commands.map { |_, command| invoke_command(command) } + end + + # Invokes using shell padding. + def invoke_with_padding(*args) + with_padding { invoke(*args) } + end + + protected + + # Configuration values that are shared between invocations. + def _shared_configuration #:nodoc: + {:invocations => @_invocations} + end + + # This method simply retrieves the class and command to be invoked. + # If the name is nil or the given name is a command in the current class, + # use the given name and return self as class. Otherwise, call + # prepare_for_invocation in the current class. + def _retrieve_class_and_command(name, sent_command = nil) #:nodoc: + if name.nil? + [self.class, nil] + elsif self.class.all_commands[name.to_s] + [self.class, name.to_s] + else + klass, command = self.class.prepare_for_invocation(nil, name) + [klass, command || sent_command] + end + end + alias_method :_retrieve_class_and_task, :_retrieve_class_and_command + + # Initialize klass using values stored in the @_initializer. + def _parse_initialization_options(args, opts, config) #:nodoc: + stored_args, stored_opts, stored_config = @_initializer + + args ||= stored_args.dup + opts ||= stored_opts.dup + + config ||= {} + config = stored_config.merge(_shared_configuration).merge!(config) + + [args, opts, config] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor.rb new file mode 100644 index 0000000000..5c0c336e7a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor.rb @@ -0,0 +1,17 @@ +require_relative "line_editor/basic" +require_relative "line_editor/readline" + +class Bundler::Thor + module LineEditor + def self.readline(prompt, options = {}) + best_available.new(prompt, options).readline + end + + def self.best_available + [ + Bundler::Thor::LineEditor::Readline, + Bundler::Thor::LineEditor::Basic + ].detect(&:available?) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb new file mode 100644 index 0000000000..fe3d7c998f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb @@ -0,0 +1,37 @@ +class Bundler::Thor + module LineEditor + class Basic + attr_reader :prompt, :options + + def self.available? + true + end + + def initialize(prompt, options) + @prompt = prompt + @options = options + end + + def readline + $stdout.print(prompt) + get_input + end + + private + + def get_input + if echo? + $stdin.gets + else + # Lazy-load io/console since it is gem-ified as of 2.3 + require "io/console" + $stdin.noecho(&:gets) + end + end + + def echo? + options.fetch(:echo, true) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb new file mode 100644 index 0000000000..120eadd06a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb @@ -0,0 +1,88 @@ +class Bundler::Thor + module LineEditor + class Readline < Basic + def self.available? + begin + require "readline" + rescue LoadError + end + + Object.const_defined?(:Readline) + end + + def readline + if echo? + ::Readline.completion_append_character = nil + # rb-readline does not allow Readline.completion_proc= to receive nil. + if complete = completion_proc + ::Readline.completion_proc = complete + end + ::Readline.readline(prompt, add_to_history?) + else + super + end + end + + private + + def add_to_history? + options.fetch(:add_to_history, true) + end + + def completion_proc + if use_path_completion? + proc { |text| PathCompletion.new(text).matches } + elsif completion_options.any? + proc do |text| + completion_options.select { |option| option.start_with?(text) } + end + end + end + + def completion_options + options.fetch(:limited_to, []) + end + + def use_path_completion? + options.fetch(:path, false) + end + + class PathCompletion + attr_reader :text + private :text + + def initialize(text) + @text = text + end + + def matches + relative_matches + end + + private + + def relative_matches + absolute_matches.map { |path| path.sub(base_path, "") } + end + + def absolute_matches + Dir[glob_pattern].map do |path| + if File.directory?(path) + "#{path}/" + else + path + end + end + end + + def glob_pattern + "#{base_path}#{text}*" + end + + def base_path + "#{Dir.pwd}/" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/nested_context.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/nested_context.rb new file mode 100644 index 0000000000..fd36b9d43f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/nested_context.rb @@ -0,0 +1,29 @@ +class Bundler::Thor + class NestedContext + def initialize + @depth = 0 + end + + def enter + push + + yield + ensure + pop + end + + def entered? + @depth > 0 + end + + private + + def push + @depth += 1 + end + + def pop + @depth -= 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser.rb new file mode 100644 index 0000000000..45394732ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser.rb @@ -0,0 +1,4 @@ +require_relative "parser/argument" +require_relative "parser/arguments" +require_relative "parser/option" +require_relative "parser/options" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/argument.rb new file mode 100644 index 0000000000..dfe7398583 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/argument.rb @@ -0,0 +1,70 @@ +class Bundler::Thor + class Argument #:nodoc: + VALID_TYPES = [:numeric, :hash, :array, :string] + + attr_reader :name, :description, :enum, :required, :type, :default, :banner + alias_method :human_name, :name + + def initialize(name, options = {}) + class_name = self.class.name.split("::").last + + type = options[:type] + + raise ArgumentError, "#{class_name} name can't be nil." if name.nil? + raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) + + @name = name.to_s + @description = options[:desc] + @required = options.key?(:required) ? options[:required] : true + @type = (type || :string).to_sym + @default = options[:default] + @banner = options[:banner] || default_banner + @enum = options[:enum] + + validate! # Trigger specific validations + end + + def usage + required? ? banner : "[#{banner}]" + end + + def required? + required + end + + def show_default? + case default + when Array, String, Hash + !default.empty? + else + default + end + end + + protected + + def validate! + raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? + raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array) + end + + def valid_type?(type) + self.class::VALID_TYPES.include?(type.to_sym) + end + + def default_banner + case type + when :boolean + nil + when :string, :default + human_name.upcase + when :numeric + "N" + when :hash + "key:value" + when :array + "one two three" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb new file mode 100644 index 0000000000..3a5d82cf29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb @@ -0,0 +1,179 @@ +class Bundler::Thor + class Arguments #:nodoc: # rubocop:disable ClassLength + NUMERIC = /[-+]?(\d*\.\d+|\d+)/ + + # Receives an array of args and returns two arrays, one with arguments + # and one with switches. + # + def self.split(args) + arguments = [] + + args.each do |item| + break if item.is_a?(String) && item =~ /^-/ + arguments << item + end + + [arguments, args[Range.new(arguments.size, -1)]] + end + + def self.parse(*args) + to_parse = args.pop + new(*args).parse(to_parse) + end + + # Takes an array of Bundler::Thor::Argument objects. + # + def initialize(arguments = []) + @assigns = {} + @non_assigned_required = [] + @switches = arguments + + arguments.each do |argument| + if !argument.default.nil? + begin + @assigns[argument.human_name] = argument.default.dup + rescue TypeError # Compatibility shim for un-dup-able Fixnum in Ruby < 2.4 + @assigns[argument.human_name] = argument.default + end + elsif argument.required? + @non_assigned_required << argument + end + end + end + + def parse(args) + @pile = args.dup + + @switches.each do |argument| + break unless peek + @non_assigned_required.delete(argument) + @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) + end + + check_requirement! + @assigns + end + + def remaining + @pile + end + + private + + def no_or_skip?(arg) + arg =~ /^--(no|skip)-([-\w]+)$/ + $2 + end + + def last? + @pile.empty? + end + + def peek + @pile.first + end + + def shift + @pile.shift + end + + def unshift(arg) + if arg.is_a?(Array) + @pile = arg + @pile + else + @pile.unshift(arg) + end + end + + def current_is_value? + peek && peek.to_s !~ /^-{1,2}\S+/ + end + + # Runs through the argument array getting strings that contains ":" and + # mark it as a hash: + # + # [ "name:string", "age:integer" ] + # + # Becomes: + # + # { "name" => "string", "age" => "integer" } + # + def parse_hash(name) + return shift if peek.is_a?(Hash) + hash = {} + + while current_is_value? && peek.include?(":") + key, value = shift.split(":", 2) + raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key + hash[key] = value + end + hash + end + + # Runs through the argument array getting all strings until no string is + # found or a switch is found. + # + # ["a", "b", "c"] + # + # And returns it as an array: + # + # ["a", "b", "c"] + # + def parse_array(name) + return shift if peek.is_a?(Array) + array = [] + array << shift while current_is_value? + array + end + + # Check if the peek is numeric format and return a Float or Integer. + # Check if the peek is included in enum if enum is provided. + # Otherwise raises an error. + # + def parse_numeric(name) + return shift if peek.is_a?(Numeric) + + unless peek =~ NUMERIC && $& == peek + raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" + end + + value = $&.index(".") ? shift.to_f : shift.to_i + if @switches.is_a?(Hash) && switch = @switches[name] + if switch.enum && !switch.enum.include?(value) + raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" + end + end + value + end + + # Parse string: + # for --string-arg, just return the current value in the pile + # for --no-string-arg, nil + # Check if the peek is included in enum if enum is provided. Otherwise raises an error. + # + def parse_string(name) + if no_or_skip?(name) + nil + else + value = shift + if @switches.is_a?(Hash) && switch = @switches[name] + if switch.enum && !switch.enum.include?(value) + raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" + end + end + value + end + end + + # Raises an error if @non_assigned_required array is not empty. + # + def check_requirement! + return if @non_assigned_required.empty? + names = @non_assigned_required.map do |o| + o.respond_to?(:switch_name) ? o.switch_name : o.human_name + end.join("', '") + class_name = self.class.name.split("::").last.downcase + raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/option.rb new file mode 100644 index 0000000000..5a5af6f888 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/option.rb @@ -0,0 +1,159 @@ +class Bundler::Thor + class Option < Argument #:nodoc: + attr_reader :aliases, :group, :lazy_default, :hide, :repeatable + + VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] + + def initialize(name, options = {}) + @check_default_type = options[:check_default_type] + options[:required] = false unless options.key?(:required) + @repeatable = options.fetch(:repeatable, false) + super + @lazy_default = options[:lazy_default] + @group = options[:group].to_s.capitalize if options[:group] + @aliases = Array(options[:aliases]) + @hide = options[:hide] + end + + # This parse quick options given as method_options. It makes several + # assumptions, but you can be more specific using the option method. + # + # parse :foo => "bar" + # #=> Option foo with default value bar + # + # parse [:foo, :baz] => "bar" + # #=> Option foo with default value bar and alias :baz + # + # parse :foo => :required + # #=> Required option foo without default value + # + # parse :foo => 2 + # #=> Option foo with default value 2 and type numeric + # + # parse :foo => :numeric + # #=> Option foo without default value and type numeric + # + # parse :foo => true + # #=> Option foo with default value true and type boolean + # + # The valid types are :boolean, :numeric, :hash, :array and :string. If none + # is given a default type is assumed. This default type accepts arguments as + # string (--foo=value) or booleans (just --foo). + # + # By default all options are optional, unless :required is given. + # + def self.parse(key, value) + if key.is_a?(Array) + name, *aliases = key + else + name = key + aliases = [] + end + + name = name.to_s + default = value + + type = case value + when Symbol + default = nil + if VALID_TYPES.include?(value) + value + elsif required = (value == :required) # rubocop:disable AssignmentInCondition + :string + end + when TrueClass, FalseClass + :boolean + when Numeric + :numeric + when Hash, Array, String + value.class.name.downcase.to_sym + end + + new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) + end + + def switch_name + @switch_name ||= dasherized? ? name : dasherize(name) + end + + def human_name + @human_name ||= dasherized? ? undasherize(name) : name + end + + def usage(padding = 0) + sample = if banner && !banner.to_s.empty? + "#{switch_name}=#{banner}".dup + else + switch_name + end + + sample = "[#{sample}]".dup unless required? + + if boolean? + sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") + end + + if aliases.empty? + (" " * padding) << sample + else + "#{aliases.join(', ')}, #{sample}" + end + end + + VALID_TYPES.each do |type| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{type}? + self.type == #{type.inspect} + end + RUBY + end + + protected + + def validate! + raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? + validate_default_type! + end + + def validate_default_type! + default_type = case @default + when nil + return + when TrueClass, FalseClass + required? ? :string : :boolean + when Numeric + :numeric + when Symbol + :string + when Hash, Array, String + @default.class.name.downcase.to_sym + end + + expected_type = (@repeatable && @type != :hash) ? :array : @type + + if default_type != expected_type + err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" + + if @check_default_type + raise ArgumentError, err + elsif @check_default_type == nil + Bundler::Thor.deprecation_warning "#{err}.\n" + + 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' + + ' or call `allow_incompatible_default_type!` in your code' + end + end + end + + def dasherized? + name.index("-") == 0 + end + + def undasherize(str) + str.sub(/^-{1,2}/, "") + end + + def dasherize(str) + (str.length > 1 ? "--" : "-") + str.tr("_", "-") + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/options.rb new file mode 100644 index 0000000000..3a8927d09c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -0,0 +1,237 @@ +class Bundler::Thor + class Options < Arguments #:nodoc: # rubocop:disable ClassLength + LONG_RE = /^(--\w+(?:-\w+)*)$/ + SHORT_RE = /^(-[a-z])$/i + EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i + SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args + SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i + OPTS_END = "--".freeze + + # Receives a hash and makes it switches. + def self.to_switches(options) + options.map do |key, value| + case value + when true + "--#{key}" + when Array + "--#{key} #{value.map(&:inspect).join(' ')}" + when Hash + "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}" + when nil, false + nil + else + "--#{key} #{value.inspect}" + end + end.compact.join(" ") + end + + # Takes a hash of Bundler::Thor::Option and a hash with defaults. + # + # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters + # an unknown option or a regular argument. + def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false) + @stop_on_unknown = stop_on_unknown + @disable_required_check = disable_required_check + options = hash_options.values + super(options) + + # Add defaults + defaults.each do |key, value| + @assigns[key.to_s] = value + @non_assigned_required.delete(hash_options[key]) + end + + @shorts = {} + @switches = {} + @extra = [] + @stopped_parsing_after_extra_index = nil + + options.each do |option| + @switches[option.switch_name] = option + + option.aliases.each do |short| + name = short.to_s.sub(/^(?!\-)/, "-") + @shorts[name] ||= option.switch_name + end + end + end + + def remaining + @extra + end + + def peek + return super unless @parsing_options + + result = super + if result == OPTS_END + shift + @parsing_options = false + @stopped_parsing_after_extra_index ||= @extra.size + super + else + result + end + end + + def parse(args) # rubocop:disable MethodLength + @pile = args.dup + @parsing_options = true + + while peek + if parsing_options? + match, is_switch = current_is_switch? + shifted = shift + + if is_switch + case shifted + when SHORT_SQ_RE + unshift($1.split("").map { |f| "-#{f}" }) + next + when EQ_RE, SHORT_NUM + unshift($2) + switch = $1 + when LONG_RE, SHORT_RE + switch = $1 + end + + switch = normalize_switch(switch) + option = switch_option(switch) + result = parse_peek(switch, option) + assign_result!(option, result) + elsif @stop_on_unknown + @parsing_options = false + @extra << shifted + @stopped_parsing_after_extra_index ||= @extra.size + @extra << shift while peek + break + elsif match + @extra << shifted + @extra << shift while peek && peek !~ /^-/ + else + @extra << shifted + end + else + @extra << shift + end + end + + check_requirement! unless @disable_required_check + + assigns = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) + assigns.freeze + assigns + end + + def check_unknown! + to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra + + # an unknown option starts with - or -- and has no more --'s afterward. + unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ } + raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty? + end + + protected + + def assign_result!(option, result) + if option.repeatable && option.type == :hash + (@assigns[option.human_name] ||= {}).merge!(result) + elsif option.repeatable + (@assigns[option.human_name] ||= []) << result + else + @assigns[option.human_name] = result + end + end + + # Check if the current value in peek is a registered switch. + # + # Two booleans are returned. The first is true if the current value + # starts with a hyphen; the second is true if it is a registered switch. + def current_is_switch? + case peek + when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM + [true, switch?($1)] + when SHORT_SQ_RE + [true, $1.split("").any? { |f| switch?("-#{f}") }] + else + [false, false] + end + end + + def current_is_switch_formatted? + case peek + when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE + true + else + false + end + end + + def current_is_value? + peek && (!parsing_options? || super) + end + + def switch?(arg) + !switch_option(normalize_switch(arg)).nil? + end + + def switch_option(arg) + if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition + @switches[arg] || @switches["--#{match}"] + else + @switches[arg] + end + end + + # Check if the given argument is actually a shortcut. + # + def normalize_switch(arg) + (@shorts[arg] || arg).tr("_", "-") + end + + def parsing_options? + peek + @parsing_options + end + + # Parse boolean values which can be given as --foo=true, --foo or --no-foo. + # + def parse_boolean(switch) + if current_is_value? + if ["true", "TRUE", "t", "T", true].include?(peek) + shift + true + elsif ["false", "FALSE", "f", "F", false].include?(peek) + shift + false + else + @switches.key?(switch) || !no_or_skip?(switch) + end + else + @switches.key?(switch) || !no_or_skip?(switch) + end + end + + # Parse the value at the peek analyzing if it requires an input or not. + # + def parse_peek(switch, option) + if parsing_options? && (current_is_switch_formatted? || last?) + if option.boolean? + # No problem for boolean types + elsif no_or_skip?(switch) + return nil # User set value to nil + elsif option.string? && !option.required? + # Return the default if there is one, else the human name + return option.lazy_default || option.default || option.human_name + elsif option.lazy_default + return option.lazy_default + else + raise MalformattedArgumentError, "No value provided for option '#{switch}'" + end + end + + @non_assigned_required.delete(option) + send(:"parse_#{option.type}", switch) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/rake_compat.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/rake_compat.rb new file mode 100644 index 0000000000..f8f86372cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/rake_compat.rb @@ -0,0 +1,72 @@ +require "rake" +require "rake/dsl_definition" + +class Bundler::Thor + # Adds a compatibility layer to your Bundler::Thor classes which allows you to use + # rake package tasks. For example, to use rspec rake tasks, one can do: + # + # require 'bundler/vendor/thor/lib/thor/rake_compat' + # require 'rspec/core/rake_task' + # + # class Default < Bundler::Thor + # include Bundler::Thor::RakeCompat + # + # RSpec::Core::RakeTask.new(:spec) do |t| + # t.spec_opts = ['--options', './.rspec'] + # t.spec_files = FileList['spec/**/*_spec.rb'] + # end + # end + # + module RakeCompat + include Rake::DSL if defined?(Rake::DSL) + + def self.rake_classes + @rake_classes ||= [] + end + + def self.included(base) + super(base) + # Hack. Make rakefile point to invoker, so rdoc task is generated properly. + rakefile = File.basename(caller[0].match(/(.*):\d+/)[1]) + Rake.application.instance_variable_set(:@rakefile, rakefile) + rake_classes << base + end + end +end + +# override task on (main), for compatibility with Rake 0.9 +instance_eval do + alias rake_namespace namespace + + def task(*) + task = super + + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + non_namespaced_name = task.name.split(":").last + + description = non_namespaced_name + description << task.arg_names.map { |n| n.to_s.upcase }.join(" ") + description.strip! + + klass.desc description, Rake.application.last_description || non_namespaced_name + Rake.application.last_description = nil + klass.send :define_method, non_namespaced_name do |*args| + Rake::Task[task.name.to_sym].invoke(*args) + end + end + + task + end + + def namespace(name) + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + const_name = Bundler::Thor::Util.camel_case(name.to_s).to_sym + klass.const_set(const_name, Class.new(Bundler::Thor)) + new_klass = klass.const_get(const_name) + Bundler::Thor::RakeCompat.rake_classes << new_klass + end + + super + Bundler::Thor::RakeCompat.rake_classes.pop + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/runner.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/runner.rb new file mode 100644 index 0000000000..54c5525093 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -0,0 +1,325 @@ +require_relative "../thor" +require_relative "group" + +require "yaml" +require "digest/md5" +require "pathname" + +class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength + autoload :OpenURI, "open-uri" + + map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version + + def self.banner(command, all = false, subcommand = false) + "thor " + command.formatted_usage(self, all, subcommand) + end + + def self.exit_on_failure? + true + end + + # Override Bundler::Thor#help so it can give information about any class and any method. + # + def help(meth = nil) + if meth && !respond_to?(meth) + initialize_thorfiles(meth) + klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth) + self.class.handle_no_command_error(command, false) if klass.nil? + klass.start(["-h", command].compact, :shell => shell) + else + super + end + end + + # If a command is not found on Bundler::Thor::Runner, method missing is invoked and + # Bundler::Thor::Runner is then responsible for finding the command in all classes. + # + def method_missing(meth, *args) + meth = meth.to_s + initialize_thorfiles(meth) + klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth) + self.class.handle_no_command_error(command, false) if klass.nil? + args.unshift(command) if command + klass.start(args, :shell => shell) + end + + desc "install NAME", "Install an optionally named Bundler::Thor file into your system commands" + method_options :as => :string, :relative => :boolean, :force => :boolean + def install(name) # rubocop:disable MethodLength + initialize_thorfiles + + # If a directory name is provided as the argument, look for a 'main.thor' + # command in said directory. + begin + if File.directory?(File.expand_path(name)) + base = File.join(name, "main.thor") + package = :directory + contents = open(base, &:read) + else + base = name + package = :file + contents = open(name, &:read) + end + rescue OpenURI::HTTPError + raise Error, "Error opening URI '#{name}'" + rescue Errno::ENOENT + raise Error, "Error opening file '#{name}'" + end + + say "Your Thorfile contains:" + say contents + + unless options["force"] + return false if no?("Do you wish to continue [y/N]?") + end + + as = options["as"] || begin + first_line = contents.split("\n")[0] + (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil + end + + unless as + basename = File.basename(name) + as = ask("Please specify a name for #{name} in the system repository [#{basename}]:") + as = basename if as.empty? + end + + location = if options[:relative] || name =~ %r{^https?://} + name + else + File.expand_path(name) + end + + thor_yaml[as] = { + :filename => Digest::MD5.hexdigest(name + as), + :location => location, + :namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base) + } + + save_yaml(thor_yaml) + say "Storing thor file in your system repository" + destination = File.join(thor_root, thor_yaml[as][:filename]) + + if package == :file + File.open(destination, "w") { |f| f.puts contents } + else + require "fileutils" + FileUtils.cp_r(name, destination) + end + + thor_yaml[as][:filename] # Indicate success + end + + desc "version", "Show Bundler::Thor version" + def version + require_relative "version" + say "Bundler::Thor #{Bundler::Thor::VERSION}" + end + + desc "uninstall NAME", "Uninstall a named Bundler::Thor module" + def uninstall(name) + raise Error, "Can't find module '#{name}'" unless thor_yaml[name] + say "Uninstalling #{name}." + require "fileutils" + FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s)) + + thor_yaml.delete(name) + save_yaml(thor_yaml) + + puts "Done." + end + + desc "update NAME", "Update a Bundler::Thor file from its original location" + def update(name) + raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] + + say "Updating '#{name}' from #{thor_yaml[name][:location]}" + + old_filename = thor_yaml[name][:filename] + self.options = options.merge("as" => name) + + if File.directory? File.expand_path(name) + require "fileutils" + FileUtils.rm_rf(File.join(thor_root, old_filename)) + + thor_yaml.delete(old_filename) + save_yaml(thor_yaml) + + filename = install(name) + else + filename = install(thor_yaml[name][:location]) + end + + File.delete(File.join(thor_root, old_filename)) unless filename == old_filename + end + + desc "installed", "List the installed Bundler::Thor modules and commands" + method_options :internal => :boolean + def installed + initialize_thorfiles(nil, true) + display_klasses(true, options["internal"]) + end + + desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)" + method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean + def list(search = "") + initialize_thorfiles + + search = ".*#{search}" if options["substring"] + search = /^#{search}.*/i + group = options[:group] || "standard" + + klasses = Bundler::Thor::Base.subclasses.select do |k| + (options[:all] || k.group == group) && k.namespace =~ search + end + + display_klasses(false, false, klasses) + end + +private + + def thor_root + Bundler::Thor::Util.thor_root + end + + def thor_yaml + @thor_yaml ||= begin + yaml_file = File.join(thor_root, "thor.yml") + yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file) + yaml || {} + end + end + + # Save the yaml file. If none exists in thor root, creates one. + # + def save_yaml(yaml) + yaml_file = File.join(thor_root, "thor.yml") + + unless File.exist?(yaml_file) + require "fileutils" + FileUtils.mkdir_p(thor_root) + yaml_file = File.join(thor_root, "thor.yml") + FileUtils.touch(yaml_file) + end + + File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } + end + + # Load the Thorfiles. If relevant_to is supplied, looks for specific files + # in the thor_root instead of loading them all. + # + # By default, it also traverses the current path until find Bundler::Thor files, as + # described in thorfiles. This look up can be skipped by supplying + # skip_lookup true. + # + def initialize_thorfiles(relevant_to = nil, skip_lookup = false) + thorfiles(relevant_to, skip_lookup).each do |f| + Bundler::Thor::Util.load_thorfile(f, nil, options[:debug]) unless Bundler::Thor::Base.subclass_files.keys.include?(File.expand_path(f)) + end + end + + # Finds Thorfiles by traversing from your current directory down to the root + # directory of your system. If at any time we find a Bundler::Thor file, we stop. + # + # We also ensure that system-wide Thorfiles are loaded first, so local + # Thorfiles can override them. + # + # ==== Example + # + # If we start at /Users/wycats/dev/thor ... + # + # 1. /Users/wycats/dev/thor + # 2. /Users/wycats/dev + # 3. /Users/wycats <-- we find a Thorfile here, so we stop + # + # Suppose we start at c:\Documents and Settings\james\dev\thor ... + # + # 1. c:\Documents and Settings\james\dev\thor + # 2. c:\Documents and Settings\james\dev + # 3. c:\Documents and Settings\james + # 4. c:\Documents and Settings + # 5. c:\ <-- no Thorfiles found! + # + def thorfiles(relevant_to = nil, skip_lookup = false) + thorfiles = [] + + unless skip_lookup + Pathname.pwd.ascend do |path| + thorfiles = Bundler::Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten + break unless thorfiles.empty? + end + end + + files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Bundler::Thor::Util.thor_root_glob) + files += thorfiles + files -= ["#{thor_root}/thor.yml"] + + files.map! do |file| + File.directory?(file) ? File.join(file, "main.thor") : file + end + end + + # Load Thorfiles relevant to the given method. If you provide "foo:bar" it + # will load all thor files in the thor.yaml that has "foo" e "foo:bar" + # namespaces registered. + # + def thorfiles_relevant_to(meth) + lookup = [meth, meth.split(":")[0...-1].join(":")] + + files = thor_yaml.select do |_, v| + v[:namespaces] && !(v[:namespaces] & lookup).empty? + end + + files.map { |_, v| File.join(thor_root, (v[:filename]).to_s) } + end + + # Display information about the given klasses. If with_module is given, + # it shows a table with information extracted from the yaml file. + # + def display_klasses(with_modules = false, show_internal = false, klasses = Bundler::Thor::Base.subclasses) + klasses -= [Bundler::Thor, Bundler::Thor::Runner, Bundler::Thor::Group] unless show_internal + + raise Error, "No Bundler::Thor commands available" if klasses.empty? + show_modules if with_modules && !thor_yaml.empty? + + list = Hash.new { |h, k| h[k] = [] } + groups = klasses.select { |k| k.ancestors.include?(Bundler::Thor::Group) } + + # Get classes which inherit from Bundler::Thor + (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) } + + # Get classes which inherit from Bundler::Thor::Base + groups.map! { |k| k.printable_commands(false).first } + list["root"] = groups + + # Order namespaces with default coming first + list = list.sort { |a, b| a[0].sub(/^default/, "") <=> b[0].sub(/^default/, "") } + list.each { |n, commands| display_commands(n, commands) unless commands.empty? } + end + + def display_commands(namespace, list) #:nodoc: + list.sort! { |a, b| a[0] <=> b[0] } + + say shell.set_color(namespace, :blue, true) + say "-" * namespace.size + + print_table(list, :truncate => true) + say + end + alias_method :display_tasks, :display_commands + + def show_modules #:nodoc: + info = [] + labels = %w(Modules Namespaces) + + info << labels + info << ["-" * labels[0].size, "-" * labels[1].size] + + thor_yaml.each do |name, hash| + info << [name, hash[:namespaces].join(", ")] + end + + print_table info + say "" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell.rb new file mode 100644 index 0000000000..e36fa472d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell.rb @@ -0,0 +1,81 @@ +require "rbconfig" + +class Bundler::Thor + module Base + class << self + attr_writer :shell + + # Returns the shell used in all Bundler::Thor classes. If you are in a Unix platform + # it will use a colored log, otherwise it will use a basic one without color. + # + def shell + @shell ||= if ENV["THOR_SHELL"] && !ENV["THOR_SHELL"].empty? + Bundler::Thor::Shell.const_get(ENV["THOR_SHELL"]) + elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"] + Bundler::Thor::Shell::Basic + else + Bundler::Thor::Shell::Color + end + end + end + end + + module Shell + SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] + attr_writer :shell + + autoload :Basic, File.expand_path("shell/basic", __dir__) + autoload :Color, File.expand_path("shell/color", __dir__) + autoload :HTML, File.expand_path("shell/html", __dir__) + + # Add shell to initialize config values. + # + # ==== Configuration + # shell:: An instance of the shell to be used. + # + # ==== Examples + # + # class MyScript < Bundler::Thor + # argument :first, :type => :numeric + # end + # + # MyScript.new [1.0], { :foo => :bar }, :shell => Bundler::Thor::Shell::Basic.new + # + def initialize(args = [], options = {}, config = {}) + super + self.shell = config[:shell] + shell.base ||= self if shell.respond_to?(:base) + end + + # Holds the shell for the given Bundler::Thor instance. If no shell is given, + # it gets a default shell from Bundler::Thor::Base.shell. + def shell + @shell ||= Bundler::Thor::Base.shell.new + end + + # Common methods that are delegated to the shell. + SHELL_DELEGATED_METHODS.each do |method| + module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(*args,&block) + shell.#{method}(*args,&block) + end + METHOD + end + + # Yields the given block with padding. + def with_padding + shell.padding += 1 + yield + ensure + shell.padding -= 1 + end + + protected + + # Allow shell to be shared between invocations. + # + def _shared_configuration #:nodoc: + super.merge!(:shell => shell) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/basic.rb new file mode 100644 index 0000000000..2dddd4a53a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -0,0 +1,494 @@ +class Bundler::Thor + module Shell + class Basic + DEFAULT_TERMINAL_WIDTH = 80 + + attr_accessor :base + attr_reader :padding + + # Initialize base, mute and padding to nil. + # + def initialize #:nodoc: + @base = nil + @mute = false + @padding = 0 + @always_force = false + end + + # Mute everything that's inside given block + # + def mute + @mute = true + yield + ensure + @mute = false + end + + # Check if base is muted + # + def mute? + @mute + end + + # Sets the output padding, not allowing less than zero values. + # + def padding=(value) + @padding = [0, value].max + end + + # Sets the output padding while executing a block and resets it. + # + def indent(count = 1) + orig_padding = padding + self.padding = padding + count + yield + self.padding = orig_padding + end + + # Asks something to the user and receives a response. + # + # If a default value is specified it will be presented to the user + # and allows them to select that value with an empty response. This + # option is ignored when limited answers are supplied. + # + # If asked to limit the correct responses, you can pass in an + # array of acceptable answers. If one of those is not supplied, + # they will be shown a message stating that one of those answers + # must be given and re-asked the question. + # + # If asking for sensitive information, the :echo option can be set + # to false to mask user input from $stdin. + # + # If the required input is a path, then set the path option to + # true. This will enable tab completion for file paths relative + # to the current working directory on systems that support + # Readline. + # + # ==== Example + # ask("What is your name?") + # + # ask("What is the planet furthest from the sun?", :default => "Pluto") + # + # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) + # + # ask("What is your password?", :echo => false) + # + # ask("Where should the file be saved?", :path => true) + # + def ask(statement, *args) + options = args.last.is_a?(Hash) ? args.pop : {} + color = args.first + + if options[:limited_to] + ask_filtered(statement, color, options) + else + ask_simply(statement, color, options) + end + end + + # Say (print) something to the user. If the sentence ends with a whitespace + # or tab character, a new line is not appended (print + flush). Otherwise + # are passed straight to puts (behavior got from Highline). + # + # ==== Example + # say("I know you knew that.") + # + def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) + return if quiet? + + buffer = prepare_message(message, *color) + buffer << "\n" if force_new_line && !message.to_s.end_with?("\n") + + stdout.print(buffer) + stdout.flush + end + + # Say a status with the given color and appends the message. Since this + # method is used frequently by actions, it allows nil or false to be given + # in log_status, avoiding the message from being shown. If a Symbol is + # given in log_status, it's used as the color. + # + def say_status(status, message, log_status = true) + return if quiet? || log_status == false + spaces = " " * (padding + 1) + color = log_status.is_a?(Symbol) ? log_status : :green + + status = status.to_s.rjust(12) + status = set_color status, color, true if color + + buffer = "#{status}#{spaces}#{message}" + buffer = "#{buffer}\n" unless buffer.end_with?("\n") + + stdout.print(buffer) + stdout.flush + end + + # Make a question the to user and returns true if the user replies "y" or + # "yes". + # + def yes?(statement, color = nil) + !!(ask(statement, color, :add_to_history => false) =~ is?(:yes)) + end + + # Make a question the to user and returns true if the user replies "n" or + # "no". + # + def no?(statement, color = nil) + !!(ask(statement, color, :add_to_history => false) =~ is?(:no)) + end + + # Prints values in columns + # + # ==== Parameters + # Array[String, String, ...] + # + def print_in_columns(array) + return if array.empty? + colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 + array.each_with_index do |value, index| + # Don't output trailing spaces when printing the last column + if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length + stdout.puts value + else + stdout.printf("%-#{colwidth}s", value) + end + end + end + + # Prints a table. + # + # ==== Parameters + # Array[Array[String, String, ...]] + # + # ==== Options + # indent:: Indent the first column by indent value. + # colwidth:: Force the first column to colwidth spaces wide. + # + def print_table(array, options = {}) # rubocop:disable MethodLength + return if array.empty? + + formats = [] + indent = options[:indent].to_i + colwidth = options[:colwidth] + options[:truncate] = terminal_width if options[:truncate] == true + + formats << "%-#{colwidth + 2}s".dup if colwidth + start = colwidth ? 1 : 0 + + colcount = array.max { |a, b| a.size <=> b.size }.size + + maximas = [] + + start.upto(colcount - 1) do |index| + maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max + maximas << maxima + formats << if index == colcount - 1 + # Don't output 2 trailing spaces when printing the last column + "%-s".dup + else + "%-#{maxima + 2}s".dup + end + end + + formats[0] = formats[0].insert(0, " " * indent) + formats << "%s" + + array.each do |row| + sentence = "".dup + + row.each_with_index do |column, index| + maxima = maximas[index] + + f = if column.is_a?(Numeric) + if index == row.size - 1 + # Don't output 2 trailing spaces when printing the last column + "%#{maxima}s" + else + "%#{maxima}s " + end + else + formats[index] + end + sentence << f % column.to_s + end + + sentence = truncate(sentence, options[:truncate]) if options[:truncate] + stdout.puts sentence + end + end + + # Prints a long string, word-wrapping the text to the current width of the + # terminal display. Ideal for printing heredocs. + # + # ==== Parameters + # String + # + # ==== Options + # indent:: Indent each line of the printed paragraph by indent value. + # + def print_wrapped(message, options = {}) + indent = options[:indent] || 0 + width = terminal_width - indent + paras = message.split("\n\n") + + paras.map! do |unwrapped| + words = unwrapped.split(" ") + counter = words.first.length + words.inject do |memo, word| + word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n") + counter = 0 if word.include? "\n" + if (counter + word.length + 1) < width + memo = "#{memo} #{word}" + counter += (word.length + 1) + else + memo = "#{memo}\n#{word}" + counter = word.length + end + memo + end + end.compact! + + paras.each do |para| + para.split("\n").each do |line| + stdout.puts line.insert(0, " " * indent) + end + stdout.puts unless para == paras.last + end + end + + # Deals with file collision and returns true if the file should be + # overwritten and false otherwise. If a block is given, it uses the block + # response as the content for the diff. + # + # ==== Parameters + # destination:: the destination file to solve conflicts + # block:: an optional block that returns the value to be used in diff and merge + # + def file_collision(destination) + return true if @always_force + options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]" + + loop do + answer = ask( + %[Overwrite #{destination}? (enter "h" for help) #{options}], + :add_to_history => false + ) + + case answer + when nil + say "" + return true + when is?(:yes), is?(:force), "" + return true + when is?(:no), is?(:skip) + return false + when is?(:always) + return @always_force = true + when is?(:quit) + say "Aborting..." + raise SystemExit + when is?(:diff) + show_diff(destination, yield) if block_given? + say "Retrying..." + when is?(:merge) + if block_given? && !merge_tool.empty? + merge(destination, yield) + return nil + end + + say "Please specify merge tool to `THOR_MERGE` env." + else + say file_collision_help + end + end + end + + # This code was copied from Rake, available under MIT-LICENSE + # Copyright (c) 2003, 2004 Jim Weirich + def terminal_width + result = if ENV["THOR_COLUMNS"] + ENV["THOR_COLUMNS"].to_i + else + unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH + end + result < 10 ? DEFAULT_TERMINAL_WIDTH : result + rescue + DEFAULT_TERMINAL_WIDTH + end + + # Called if something goes wrong during the execution. This is used by Bundler::Thor + # internally and should not be used inside your scripts. If something went + # wrong, you can always raise an exception. If you raise a Bundler::Thor::Error, it + # will be rescued and wrapped in the method below. + # + def error(statement) + stderr.puts statement + end + + # Apply color to the given string with optional bold. Disabled in the + # Bundler::Thor::Shell::Basic class. + # + def set_color(string, *) #:nodoc: + string + end + + protected + + def prepare_message(message, *color) + spaces = " " * padding + spaces + set_color(message.to_s, *color) + end + + def can_display_colors? + false + end + + def lookup_color(color) + return color unless color.is_a?(Symbol) + self.class.const_get(color.to_s.upcase) + end + + def stdout + $stdout + end + + def stderr + $stderr + end + + def is?(value) #:nodoc: + value = value.to_s + + if value.size == 1 + /\A#{value}\z/i + else + /\A(#{value}|#{value[0, 1]})\z/i + end + end + + def file_collision_help #:nodoc: + <<-HELP + Y - yes, overwrite + n - no, do not overwrite + a - all, overwrite this and all others + q - quit, abort + d - diff, show the differences between the old and the new + h - help, show this help + m - merge, run merge tool + HELP + end + + def show_diff(destination, content) #:nodoc: + diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u" + + require "tempfile" + Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp| + temp.write content + temp.rewind + system %(#{diff_cmd} "#{destination}" "#{temp.path}") + end + end + + def quiet? #:nodoc: + mute? || (base && base.options[:quiet]) + end + + # Calculate the dynamic width of the terminal + def dynamic_width + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty + `stty size 2>/dev/null`.split[1].to_i + end + + def dynamic_width_tput + `tput cols 2>/dev/null`.to_i + end + + def unix? + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def truncate(string, width) + as_unicode do + chars = string.chars.to_a + if chars.length <= width + chars.join + else + chars[0, width - 3].join + "..." + end + end + end + + if "".respond_to?(:encode) + def as_unicode + yield + end + else + def as_unicode + old = $KCODE + $KCODE = "U" + yield + ensure + $KCODE = old + end + end + + def ask_simply(statement, color, options) + default = options[:default] + message = [statement, ("(#{default})" if default), nil].uniq.join(" ") + message = prepare_message(message, *color) + result = Bundler::Thor::LineEditor.readline(message, options) + + return unless result + + result = result.strip + + if default && result == "" + default + else + result + end + end + + def ask_filtered(statement, color, options) + answer_set = options[:limited_to] + case_insensitive = options.fetch(:case_insensitive, false) + correct_answer = nil + until correct_answer + answers = answer_set.join(", ") + answer = ask_simply("#{statement} [#{answers}]", color, options) + correct_answer = answer_match(answer_set, answer, case_insensitive) + say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer + end + correct_answer + end + + def answer_match(possibilities, answer, case_insensitive) + if case_insensitive + possibilities.detect{ |possibility| possibility.downcase == answer.downcase } + else + possibilities.detect{ |possibility| possibility == answer } + end + end + + def merge(destination, content) #:nodoc: + require "tempfile" + Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp| + temp.write content + temp.rewind + system %(#{merge_tool} "#{temp.path}" "#{destination}") + end + end + + def merge_tool #:nodoc: + @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool + end + + def git_merge_tool #:nodoc: + `git config merge.tool`.rstrip rescue "" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/color.rb new file mode 100644 index 0000000000..dc167ed3cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/color.rb @@ -0,0 +1,157 @@ +require_relative "basic" + +class Bundler::Thor + module Shell + # Inherit from Bundler::Thor::Shell::Basic and add set_color behavior. Check + # Bundler::Thor::Shell::Basic to see all available methods. + # + class Color < Basic + # Embed in a String to clear all previous ANSI sequences. + CLEAR = "\e[0m" + # The start of an ANSI bold sequence. + BOLD = "\e[1m" + + # Set the terminal's foreground ANSI color to black. + BLACK = "\e[30m" + # Set the terminal's foreground ANSI color to red. + RED = "\e[31m" + # Set the terminal's foreground ANSI color to green. + GREEN = "\e[32m" + # Set the terminal's foreground ANSI color to yellow. + YELLOW = "\e[33m" + # Set the terminal's foreground ANSI color to blue. + BLUE = "\e[34m" + # Set the terminal's foreground ANSI color to magenta. + MAGENTA = "\e[35m" + # Set the terminal's foreground ANSI color to cyan. + CYAN = "\e[36m" + # Set the terminal's foreground ANSI color to white. + WHITE = "\e[37m" + + # Set the terminal's background ANSI color to black. + ON_BLACK = "\e[40m" + # Set the terminal's background ANSI color to red. + ON_RED = "\e[41m" + # Set the terminal's background ANSI color to green. + ON_GREEN = "\e[42m" + # Set the terminal's background ANSI color to yellow. + ON_YELLOW = "\e[43m" + # Set the terminal's background ANSI color to blue. + ON_BLUE = "\e[44m" + # Set the terminal's background ANSI color to magenta. + ON_MAGENTA = "\e[45m" + # Set the terminal's background ANSI color to cyan. + ON_CYAN = "\e[46m" + # Set the terminal's background ANSI color to white. + ON_WHITE = "\e[47m" + + # Set color by using a string or one of the defined constants. If a third + # option is set to true, it also adds bold to the string. This is based + # on Highline implementation and it automatically appends CLEAR to the end + # of the returned String. + # + # Pass foreground, background and bold options to this method as + # symbols. + # + # Example: + # + # set_color "Hi!", :red, :on_white, :bold + # + # The available colors are: + # + # :bold + # :black + # :red + # :green + # :yellow + # :blue + # :magenta + # :cyan + # :white + # :on_black + # :on_red + # :on_green + # :on_yellow + # :on_blue + # :on_magenta + # :on_cyan + # :on_white + def set_color(string, *colors) + if colors.compact.empty? || !can_display_colors? + string + elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } + ansi_colors = colors.map { |color| lookup_color(color) } + "#{ansi_colors.join}#{string}#{CLEAR}" + else + # The old API was `set_color(color, bold=boolean)`. We + # continue to support the old API because you should never + # break old APIs unnecessarily :P + foreground, bold = colors + foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol) + + bold = bold ? BOLD : "" + "#{bold}#{foreground}#{string}#{CLEAR}" + end + end + + protected + + def can_display_colors? + are_colors_supported? && !are_colors_disabled? + end + + def are_colors_supported? + stdout.tty? && ENV["TERM"] != "dumb" + end + + def are_colors_disabled? + !ENV['NO_COLOR'].nil? + end + + # Overwrite show_diff to show diff with colors if Diff::LCS is + # available. + # + def show_diff(destination, content) #:nodoc: + if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? + actual = File.binread(destination).to_s.split("\n") + content = content.to_s.split("\n") + + Diff::LCS.sdiff(actual, content).each do |diff| + output_diff_line(diff) + end + else + super + end + end + + def output_diff_line(diff) #:nodoc: + case diff.action + when "-" + say "- #{diff.old_element.chomp}", :red, true + when "+" + say "+ #{diff.new_element.chomp}", :green, true + when "!" + say "- #{diff.old_element.chomp}", :red, true + say "+ #{diff.new_element.chomp}", :green, true + else + say " #{diff.old_element.chomp}", nil, true + end + end + + # Check if Diff::LCS is loaded. If it is, use it to create pretty output + # for diff. + # + def diff_lcs_loaded? #:nodoc: + return true if defined?(Diff::LCS) + return @diff_lcs_loaded unless @diff_lcs_loaded.nil? + + @diff_lcs_loaded = begin + require "diff/lcs" + true + rescue LoadError + false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/html.rb new file mode 100644 index 0000000000..77a6d13a23 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -0,0 +1,126 @@ +require_relative "basic" + +class Bundler::Thor + module Shell + # Inherit from Bundler::Thor::Shell::Basic and add set_color behavior. Check + # Bundler::Thor::Shell::Basic to see all available methods. + # + class HTML < Basic + # The start of an HTML bold sequence. + BOLD = "font-weight: bold" + + # Set the terminal's foreground HTML color to black. + BLACK = "color: black" + # Set the terminal's foreground HTML color to red. + RED = "color: red" + # Set the terminal's foreground HTML color to green. + GREEN = "color: green" + # Set the terminal's foreground HTML color to yellow. + YELLOW = "color: yellow" + # Set the terminal's foreground HTML color to blue. + BLUE = "color: blue" + # Set the terminal's foreground HTML color to magenta. + MAGENTA = "color: magenta" + # Set the terminal's foreground HTML color to cyan. + CYAN = "color: cyan" + # Set the terminal's foreground HTML color to white. + WHITE = "color: white" + + # Set the terminal's background HTML color to black. + ON_BLACK = "background-color: black" + # Set the terminal's background HTML color to red. + ON_RED = "background-color: red" + # Set the terminal's background HTML color to green. + ON_GREEN = "background-color: green" + # Set the terminal's background HTML color to yellow. + ON_YELLOW = "background-color: yellow" + # Set the terminal's background HTML color to blue. + ON_BLUE = "background-color: blue" + # Set the terminal's background HTML color to magenta. + ON_MAGENTA = "background-color: magenta" + # Set the terminal's background HTML color to cyan. + ON_CYAN = "background-color: cyan" + # Set the terminal's background HTML color to white. + ON_WHITE = "background-color: white" + + # Set color by using a string or one of the defined constants. If a third + # option is set to true, it also adds bold to the string. This is based + # on Highline implementation and it automatically appends CLEAR to the end + # of the returned String. + # + def set_color(string, *colors) + if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } + html_colors = colors.map { |color| lookup_color(color) } + "#{Bundler::Thor::Util.escape_html(string)}" + else + color, bold = colors + html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) + styles = [html_color] + styles << BOLD if bold + "#{Bundler::Thor::Util.escape_html(string)}" + end + end + + # Ask something to the user and receives a response. + # + # ==== Example + # ask("What is your name?") + # + # TODO: Implement #ask for Bundler::Thor::Shell::HTML + def ask(statement, color = nil) + raise NotImplementedError, "Implement #ask for Bundler::Thor::Shell::HTML" + end + + protected + + def can_display_colors? + true + end + + # Overwrite show_diff to show diff with colors if Diff::LCS is + # available. + # + def show_diff(destination, content) #:nodoc: + if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? + actual = File.binread(destination).to_s.split("\n") + content = content.to_s.split("\n") + + Diff::LCS.sdiff(actual, content).each do |diff| + output_diff_line(diff) + end + else + super + end + end + + def output_diff_line(diff) #:nodoc: + case diff.action + when "-" + say "- #{diff.old_element.chomp}", :red, true + when "+" + say "+ #{diff.new_element.chomp}", :green, true + when "!" + say "- #{diff.old_element.chomp}", :red, true + say "+ #{diff.new_element.chomp}", :green, true + else + say " #{diff.old_element.chomp}", nil, true + end + end + + # Check if Diff::LCS is loaded. If it is, use it to create pretty output + # for diff. + # + def diff_lcs_loaded? #:nodoc: + return true if defined?(Diff::LCS) + return @diff_lcs_loaded unless @diff_lcs_loaded.nil? + + @diff_lcs_loaded = begin + require "diff/lcs" + true + rescue LoadError + false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/util.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/util.rb new file mode 100644 index 0000000000..ddf4d21b90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/util.rb @@ -0,0 +1,284 @@ +require "rbconfig" + +class Bundler::Thor + module Sandbox #:nodoc: + end + + # This module holds several utilities: + # + # 1) Methods to convert thor namespaces to constants and vice-versa. + # + # Bundler::Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz" + # + # 2) Loading thor files and sandboxing: + # + # Bundler::Thor::Util.load_thorfile("~/.thor/foo") + # + module Util + class << self + # Receives a namespace and search for it in the Bundler::Thor::Base subclasses. + # + # ==== Parameters + # namespace:: The namespace to search for. + # + def find_by_namespace(namespace) + namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/ + Bundler::Thor::Base.subclasses.detect { |klass| klass.namespace == namespace } + end + + # Receives a constant and converts it to a Bundler::Thor namespace. Since Bundler::Thor + # commands can be added to a sandbox, this method is also responsible for + # removing the sandbox namespace. + # + # This method should not be used in general because it's used to deal with + # older versions of Bundler::Thor. On current versions, if you need to get the + # namespace from a class, just call namespace on it. + # + # ==== Parameters + # constant:: The constant to be converted to the thor path. + # + # ==== Returns + # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" + # + def namespace_from_thor_class(constant) + constant = constant.to_s.gsub(/^Bundler::Thor::Sandbox::/, "") + constant = snake_case(constant).squeeze(":") + constant + end + + # Given the contents, evaluate it inside the sandbox and returns the + # namespaces defined in the sandbox. + # + # ==== Parameters + # contents + # + # ==== Returns + # Array[Object] + # + def namespaces_in_content(contents, file = __FILE__) + old_constants = Bundler::Thor::Base.subclasses.dup + Bundler::Thor::Base.subclasses.clear + + load_thorfile(file, contents) + + new_constants = Bundler::Thor::Base.subclasses.dup + Bundler::Thor::Base.subclasses.replace(old_constants) + + new_constants.map!(&:namespace) + new_constants.compact! + new_constants + end + + # Returns the thor classes declared inside the given class. + # + def thor_classes_in(klass) + stringfied_constants = klass.constants.map(&:to_s) + Bundler::Thor::Base.subclasses.select do |subclass| + next unless subclass.name + stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", "")) + end + end + + # Receives a string and convert it to snake case. SnakeCase returns snake_case. + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def snake_case(str) + return str.downcase if str =~ /^[A-Z_]+$/ + str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/ + $+.downcase + end + + # Receives a string and convert it to camel case. camel_case returns CamelCase. + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def camel_case(str) + return str if str !~ /_/ && str =~ /[A-Z]+.*/ + str.split("_").map(&:capitalize).join + end + + # Receives a namespace and tries to retrieve a Bundler::Thor or Bundler::Thor::Group class + # from it. It first searches for a class using the all the given namespace, + # if it's not found, removes the highest entry and searches for the class + # again. If found, returns the highest entry as the class name. + # + # ==== Examples + # + # class Foo::Bar < Bundler::Thor + # def baz + # end + # end + # + # class Baz::Foo < Bundler::Thor::Group + # end + # + # Bundler::Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command + # Bundler::Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil + # Bundler::Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz" + # + # ==== Parameters + # namespace + # + def find_class_and_command_by_namespace(namespace, fallback = true) + if namespace.include?(":") # look for a namespaced command + pieces = namespace.split(":") + command = pieces.pop + klass = Bundler::Thor::Util.find_by_namespace(pieces.join(":")) + end + unless klass # look for a Bundler::Thor::Group with the right name + klass = Bundler::Thor::Util.find_by_namespace(namespace) + command = nil + end + if !klass && fallback # try a command in the default namespace + command = namespace + klass = Bundler::Thor::Util.find_by_namespace("") + end + [klass, command] + end + alias_method :find_class_and_task_by_namespace, :find_class_and_command_by_namespace + + # Receives a path and load the thor file in the path. The file is evaluated + # inside the sandbox to avoid namespacing conflicts. + # + def load_thorfile(path, content = nil, debug = false) + content ||= File.binread(path) + + begin + Bundler::Thor::Sandbox.class_eval(content, path) + rescue StandardError => e + $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}") + if debug + $stderr.puts(*e.backtrace) + else + $stderr.puts(e.backtrace.first) + end + end + end + + def user_home + @@user_home ||= if ENV["HOME"] + ENV["HOME"] + elsif ENV["USERPROFILE"] + ENV["USERPROFILE"] + elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"] + File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"]) + elsif ENV["APPDATA"] + ENV["APPDATA"] + else + begin + File.expand_path("~") + rescue + if File::ALT_SEPARATOR + "C:/" + else + "/" + end + end + end + end + + # Returns the root where thor files are located, depending on the OS. + # + def thor_root + File.join(user_home, ".thor").tr('\\', "/") + end + + # Returns the files in the thor root. On Windows thor_root will be something + # like this: + # + # C:\Documents and Settings\james\.thor + # + # If we don't #gsub the \ character, Dir.glob will fail. + # + def thor_root_glob + files = Dir["#{escape_globs(thor_root)}/*"] + + files.map! do |file| + File.directory?(file) ? File.join(file, "main.thor") : file + end + end + + # Where to look for Bundler::Thor files. + # + def globs_for(path) + path = escape_globs(path) + ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] + end + + # Return the path to the ruby interpreter taking into account multiple + # installations and windows extensions. + # + def ruby_command + @ruby_command ||= begin + ruby_name = RbConfig::CONFIG["ruby_install_name"] + ruby = File.join(RbConfig::CONFIG["bindir"], ruby_name) + ruby << RbConfig::CONFIG["EXEEXT"] + + # avoid using different name than ruby (on platforms supporting links) + if ruby_name != "ruby" && File.respond_to?(:readlink) + begin + alternate_ruby = File.join(RbConfig::CONFIG["bindir"], "ruby") + alternate_ruby << RbConfig::CONFIG["EXEEXT"] + + # ruby is a symlink + if File.symlink? alternate_ruby + linked_ruby = File.readlink alternate_ruby + + # symlink points to 'ruby_install_name' + ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby + end + rescue NotImplementedError # rubocop:disable HandleExceptions + # just ignore on windows + end + end + + # escape string in case path to ruby executable contain spaces. + ruby.sub!(/.*\s.*/m, '"\&"') + ruby + end + end + + # Returns a string that has had any glob characters escaped. + # The glob characters are `* ? { } [ ]`. + # + # ==== Examples + # + # Bundler::Thor::Util.escape_globs('[apps]') # => '\[apps\]' + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def escape_globs(path) + path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&') + end + + # Returns a string that has had any HTML characters escaped. + # + # ==== Examples + # + # Bundler::Thor::Util.escape_html('
') # => "<div>" + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def escape_html(string) + CGI.escapeHTML(string) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/version.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/version.rb new file mode 100644 index 0000000000..a3efa9f762 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/thor/lib/thor/version.rb @@ -0,0 +1,3 @@ +class Bundler::Thor + VERSION = "1.1.0" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/tmpdir/lib/tmpdir.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/tmpdir/lib/tmpdir.rb new file mode 100644 index 0000000000..70d43e0c6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/tmpdir/lib/tmpdir.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true +# +# tmpdir - retrieve temporary directory path +# +# $Id$ +# + +require_relative '../../fileutils/lib/fileutils' +begin + require 'etc.so' +rescue LoadError # rescue LoadError for miniruby +end + +class Bundler::Dir < Dir + + @systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' + + ## + # Returns the operating system's temporary file path. + + def self.tmpdir + tmp = nil + ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @systmpdir], ['/tmp']*2, ['.']*2].each do |name, dir = ENV[name]| + next if !dir + dir = File.expand_path(dir) + stat = File.stat(dir) rescue next + case + when !stat.directory? + warn "#{name} is not a directory: #{dir}" + when !stat.writable? + warn "#{name} is not writable: #{dir}" + when stat.world_writable? && !stat.sticky? + warn "#{name} is world-writable: #{dir}" + else + tmp = dir + break + end + end + raise ArgumentError, "could not find a temporary directory" unless tmp + tmp + end + + # Bundler::Dir.mktmpdir creates a temporary directory. + # + # The directory is created with 0700 permission. + # Application should not change the permission to make the temporary directory accessible from other users. + # + # The prefix and suffix of the name of the directory is specified by + # the optional first argument, prefix_suffix. + # - If it is not specified or nil, "d" is used as the prefix and no suffix is used. + # - If it is a string, it is used as the prefix and no suffix is used. + # - If it is an array, first element is used as the prefix and second element is used as a suffix. + # + # Bundler::Dir.mktmpdir {|dir| dir is ".../d..." } + # Bundler::Dir.mktmpdir("foo") {|dir| dir is ".../foo..." } + # Bundler::Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" } + # + # The directory is created under Bundler::Dir.tmpdir or + # the optional second argument tmpdir if non-nil value is given. + # + # Bundler::Dir.mktmpdir {|dir| dir is "#{Bundler::Dir.tmpdir}/d..." } + # Bundler::Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." } + # + # If a block is given, + # it is yielded with the path of the directory. + # The directory and its contents are removed + # using Bundler::FileUtils.remove_entry before Bundler::Dir.mktmpdir returns. + # The value of the block is returned. + # + # Bundler::Dir.mktmpdir {|dir| + # # use the directory... + # open("#{dir}/foo", "w") { ... } + # } + # + # If a block is not given, + # The path of the directory is returned. + # In this case, Bundler::Dir.mktmpdir doesn't remove the directory. + # + # dir = Bundler::Dir.mktmpdir + # begin + # # use the directory... + # open("#{dir}/foo", "w") { ... } + # ensure + # # remove the directory. + # Bundler::FileUtils.remove_entry dir + # end + # + def self.mktmpdir(prefix_suffix=nil, *rest, **options) + base = nil + path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|p, _, _, d| + base = d + mkdir(p, 0700) + } + if block_given? + begin + yield path.dup + ensure + unless base + stat = File.stat(File.dirname(path)) + if stat.world_writable? and !stat.sticky? + raise ArgumentError, "parent directory is world writable but not sticky" + end + end + Bundler::FileUtils.remove_entry path + end + else + path + end + end + + module Tmpname # :nodoc: + module_function + + def tmpdir + Bundler::Dir.tmpdir + end + + UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" + + class << (RANDOM = Random.new) + MAX = 36**6 # < 0x100000000 + def next + rand(MAX).to_s(36) + end + end + private_constant :RANDOM + + def create(basename, tmpdir=nil, max_try: nil, **opts) + origdir = tmpdir + tmpdir ||= tmpdir() + n = nil + prefix, suffix = basename + prefix = (String.try_convert(prefix) or + raise ArgumentError, "unexpected prefix: #{prefix.inspect}") + prefix = prefix.delete(UNUSABLE_CHARS) + suffix &&= (String.try_convert(suffix) or + raise ArgumentError, "unexpected suffix: #{suffix.inspect}") + suffix &&= suffix.delete(UNUSABLE_CHARS) + begin + t = Time.now.strftime("%Y%m%d") + path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\ + "#{n ? %[-#{n}] : ''}#{suffix||''}" + path = File.join(tmpdir, path) + yield(path, n, opts, origdir) + rescue Errno::EEXIST + n ||= 0 + n += 1 + retry if !max_try or n < max_try + raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'" + end + path + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri.rb new file mode 100644 index 0000000000..00c01bd07b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: false +# Bundler::URI is a module providing classes to handle Uniform Resource Identifiers +# (RFC2396[http://tools.ietf.org/html/rfc2396]). +# +# == Features +# +# * Uniform way of handling URIs. +# * Flexibility to introduce custom Bundler::URI schemes. +# * Flexibility to have an alternate Bundler::URI::Parser (or just different patterns +# and regexp's). +# +# == Basic example +# +# require 'bundler/vendor/uri/lib/uri' +# +# uri = Bundler::URI("http://foo.com/posts?id=30&limit=5#time=1305298413") +# #=> # +# +# uri.scheme #=> "http" +# uri.host #=> "foo.com" +# uri.path #=> "/posts" +# uri.query #=> "id=30&limit=5" +# uri.fragment #=> "time=1305298413" +# +# uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" +# +# == Adding custom URIs +# +# module Bundler::URI +# class RSYNC < Generic +# DEFAULT_PORT = 873 +# end +# @@schemes['RSYNC'] = RSYNC +# end +# #=> Bundler::URI::RSYNC +# +# Bundler::URI.scheme_list +# #=> {"FILE"=>Bundler::URI::File, "FTP"=>Bundler::URI::FTP, "HTTP"=>Bundler::URI::HTTP, +# # "HTTPS"=>Bundler::URI::HTTPS, "LDAP"=>Bundler::URI::LDAP, "LDAPS"=>Bundler::URI::LDAPS, +# # "MAILTO"=>Bundler::URI::MailTo, "RSYNC"=>Bundler::URI::RSYNC} +# +# uri = Bundler::URI("rsync://rsync.foo.com") +# #=> # +# +# == RFC References +# +# A good place to view an RFC spec is http://www.ietf.org/rfc.html. +# +# Here is a list of all related RFC's: +# - RFC822[http://tools.ietf.org/html/rfc822] +# - RFC1738[http://tools.ietf.org/html/rfc1738] +# - RFC2255[http://tools.ietf.org/html/rfc2255] +# - RFC2368[http://tools.ietf.org/html/rfc2368] +# - RFC2373[http://tools.ietf.org/html/rfc2373] +# - RFC2396[http://tools.ietf.org/html/rfc2396] +# - RFC2732[http://tools.ietf.org/html/rfc2732] +# - RFC3986[http://tools.ietf.org/html/rfc3986] +# +# == Class tree +# +# - Bundler::URI::Generic (in uri/generic.rb) +# - Bundler::URI::File - (in uri/file.rb) +# - Bundler::URI::FTP - (in uri/ftp.rb) +# - Bundler::URI::HTTP - (in uri/http.rb) +# - Bundler::URI::HTTPS - (in uri/https.rb) +# - Bundler::URI::LDAP - (in uri/ldap.rb) +# - Bundler::URI::LDAPS - (in uri/ldaps.rb) +# - Bundler::URI::MailTo - (in uri/mailto.rb) +# - Bundler::URI::Parser - (in uri/common.rb) +# - Bundler::URI::REGEXP - (in uri/common.rb) +# - Bundler::URI::REGEXP::PATTERN - (in uri/common.rb) +# - Bundler::URI::Util - (in uri/common.rb) +# - Bundler::URI::Escape - (in uri/common.rb) +# - Bundler::URI::Error - (in uri/common.rb) +# - Bundler::URI::InvalidURIError - (in uri/common.rb) +# - Bundler::URI::InvalidComponentError - (in uri/common.rb) +# - Bundler::URI::BadURIError - (in uri/common.rb) +# +# == Copyright Info +# +# Author:: Akira Yamada +# Documentation:: +# Akira Yamada +# Dmitry V. Sabanin +# Vincent Batts +# License:: +# Copyright (c) 2001 akira yamada +# You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# + +module Bundler::URI +end + +require_relative 'uri/version' +require_relative 'uri/common' +require_relative 'uri/generic' +require_relative 'uri/file' +require_relative 'uri/ftp' +require_relative 'uri/http' +require_relative 'uri/https' +require_relative 'uri/ldap' +require_relative 'uri/ldaps' +require_relative 'uri/mailto' diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/common.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/common.rb new file mode 100644 index 0000000000..cc1ab86c2f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/common.rb @@ -0,0 +1,744 @@ +# frozen_string_literal: true +#-- +# = uri/common.rb +# +# Author:: Akira Yamada +# Revision:: $Id$ +# License:: +# You can redistribute it and/or modify it under the same term as Ruby. +# +# See Bundler::URI for general documentation +# + +require_relative "rfc2396_parser" +require_relative "rfc3986_parser" + +module Bundler::URI + REGEXP = RFC2396_REGEXP + Parser = RFC2396_Parser + RFC3986_PARSER = RFC3986_Parser.new + + # Bundler::URI::Parser.new + DEFAULT_PARSER = Parser.new + DEFAULT_PARSER.pattern.each_pair do |sym, str| + unless REGEXP::PATTERN.const_defined?(sym) + REGEXP::PATTERN.const_set(sym, str) + end + end + DEFAULT_PARSER.regexp.each_pair do |sym, str| + const_set(sym, str) + end + + module Util # :nodoc: + def make_components_hash(klass, array_hash) + tmp = {} + if array_hash.kind_of?(Array) && + array_hash.size == klass.component.size - 1 + klass.component[1..-1].each_index do |i| + begin + tmp[klass.component[i + 1]] = array_hash[i].clone + rescue TypeError + tmp[klass.component[i + 1]] = array_hash[i] + end + end + + elsif array_hash.kind_of?(Hash) + array_hash.each do |key, value| + begin + tmp[key] = value.clone + rescue TypeError + tmp[key] = value + end + end + else + raise ArgumentError, + "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})" + end + tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase + + return tmp + end + module_function :make_components_hash + end + + # Module for escaping unsafe characters with codes. + module Escape + # + # == Synopsis + # + # Bundler::URI.escape(str [, unsafe]) + # + # == Args + # + # +str+:: + # String to replaces in. + # +unsafe+:: + # Regexp that matches all symbols that must be replaced with codes. + # By default uses UNSAFE. + # When this argument is a String, it represents a character set. + # + # == Description + # + # Escapes the string, replacing all unsafe characters with codes. + # + # This method is obsolete and should not be used. Instead, use + # CGI.escape, Bundler::URI.encode_www_form or Bundler::URI.encode_www_form_component + # depending on your specific use case. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # enc_uri = Bundler::URI.escape("http://example.com/?a=\11\15") + # # => "http://example.com/?a=%09%0D" + # + # Bundler::URI.unescape(enc_uri) + # # => "http://example.com/?a=\t\r" + # + # Bundler::URI.escape("@?@!", "!?") + # # => "@%3F@%21" + # + def escape(*arg) + warn "Bundler::URI.escape is obsolete", uplevel: 1 + DEFAULT_PARSER.escape(*arg) + end + alias encode escape + # + # == Synopsis + # + # Bundler::URI.unescape(str) + # + # == Args + # + # +str+:: + # String to unescape. + # + # == Description + # + # This method is obsolete and should not be used. Instead, use + # CGI.unescape, Bundler::URI.decode_www_form or Bundler::URI.decode_www_form_component + # depending on your specific use case. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # enc_uri = Bundler::URI.escape("http://example.com/?a=\11\15") + # # => "http://example.com/?a=%09%0D" + # + # Bundler::URI.unescape(enc_uri) + # # => "http://example.com/?a=\t\r" + # + def unescape(*arg) + warn "Bundler::URI.unescape is obsolete", uplevel: 1 + DEFAULT_PARSER.unescape(*arg) + end + alias decode unescape + end # module Escape + + extend Escape + include REGEXP + + @@schemes = {} + # Returns a Hash of the defined schemes. + def self.scheme_list + @@schemes + end + + # + # Base class for all Bundler::URI exceptions. + # + class Error < StandardError; end + # + # Not a Bundler::URI. + # + class InvalidURIError < Error; end + # + # Not a Bundler::URI component. + # + class InvalidComponentError < Error; end + # + # Bundler::URI is valid, bad usage is not. + # + class BadURIError < Error; end + + # + # == Synopsis + # + # Bundler::URI::split(uri) + # + # == Args + # + # +uri+:: + # String with Bundler::URI. + # + # == Description + # + # Splits the string on following parts and returns array with result: + # + # * Scheme + # * Userinfo + # * Host + # * Port + # * Registry + # * Path + # * Opaque + # * Query + # * Fragment + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # Bundler::URI.split("http://www.ruby-lang.org/") + # # => ["http", nil, "www.ruby-lang.org", nil, nil, "/", nil, nil, nil] + # + def self.split(uri) + RFC3986_PARSER.split(uri) + end + + # + # == Synopsis + # + # Bundler::URI::parse(uri_str) + # + # == Args + # + # +uri_str+:: + # String with Bundler::URI. + # + # == Description + # + # Creates one of the Bundler::URI's subclasses instance from the string. + # + # == Raises + # + # Bundler::URI::InvalidURIError:: + # Raised if Bundler::URI given is not a correct one. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://www.ruby-lang.org/") + # # => # + # uri.scheme + # # => "http" + # uri.host + # # => "www.ruby-lang.org" + # + # It's recommended to first ::escape the provided +uri_str+ if there are any + # invalid Bundler::URI characters. + # + def self.parse(uri) + RFC3986_PARSER.parse(uri) + end + + # + # == Synopsis + # + # Bundler::URI::join(str[, str, ...]) + # + # == Args + # + # +str+:: + # String(s) to work with, will be converted to RFC3986 URIs before merging. + # + # == Description + # + # Joins URIs. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # Bundler::URI.join("http://example.com/","main.rbx") + # # => # + # + # Bundler::URI.join('http://example.com', 'foo') + # # => # + # + # Bundler::URI.join('http://example.com', '/foo', '/bar') + # # => # + # + # Bundler::URI.join('http://example.com', '/foo', 'bar') + # # => # + # + # Bundler::URI.join('http://example.com', '/foo/', 'bar') + # # => # + # + def self.join(*str) + RFC3986_PARSER.join(*str) + end + + # + # == Synopsis + # + # Bundler::URI::extract(str[, schemes][,&blk]) + # + # == Args + # + # +str+:: + # String to extract URIs from. + # +schemes+:: + # Limit Bundler::URI matching to specific schemes. + # + # == Description + # + # Extracts URIs from a string. If block given, iterates through all matched URIs. + # Returns nil if block given or array with matches. + # + # == Usage + # + # require "bundler/vendor/uri/lib/uri" + # + # Bundler::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") + # # => ["http://foo.example.com/bla", "mailto:test@example.com"] + # + def self.extract(str, schemes = nil, &block) + warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE + DEFAULT_PARSER.extract(str, schemes, &block) + end + + # + # == Synopsis + # + # Bundler::URI::regexp([match_schemes]) + # + # == Args + # + # +match_schemes+:: + # Array of schemes. If given, resulting regexp matches to URIs + # whose scheme is one of the match_schemes. + # + # == Description + # + # Returns a Regexp object which matches to Bundler::URI-like strings. + # The Regexp object returned by this method includes arbitrary + # number of capture group (parentheses). Never rely on it's number. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # # extract first Bundler::URI from html_string + # html_string.slice(Bundler::URI.regexp) + # + # # remove ftp URIs + # html_string.sub(Bundler::URI.regexp(['ftp']), '') + # + # # You should not rely on the number of parentheses + # html_string.scan(Bundler::URI.regexp) do |*matches| + # p $& + # end + # + def self.regexp(schemes = nil) + warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE + DEFAULT_PARSER.make_regexp(schemes) + end + + TBLENCWWWCOMP_ = {} # :nodoc: + 256.times do |i| + TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) + end + TBLENCWWWCOMP_[' '] = '+' + TBLENCWWWCOMP_.freeze + TBLDECWWWCOMP_ = {} # :nodoc: + 256.times do |i| + h, l = i>>4, i&15 + TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr + TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr + TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr + TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr + end + TBLDECWWWCOMP_['+'] = ' ' + TBLDECWWWCOMP_.freeze + + # Encodes given +str+ to URL-encoded form data. + # + # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP + # (ASCII space) to + and converts others to %XX. + # + # If +enc+ is given, convert +str+ to the encoding before percent encoding. + # + # This is an implementation of + # http://www.w3.org/TR/2013/CR-html5-20130806/forms.html#url-encoded-form-data. + # + # See Bundler::URI.decode_www_form_component, Bundler::URI.encode_www_form. + def self.encode_www_form_component(str, enc=nil) + str = str.to_s.dup + if str.encoding != Encoding::ASCII_8BIT + if enc && enc != Encoding::ASCII_8BIT + str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) + str.encode!(enc, fallback: ->(x){"&##{x.ord};"}) + end + str.force_encoding(Encoding::ASCII_8BIT) + end + str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_) + str.force_encoding(Encoding::US_ASCII) + end + + # Decodes given +str+ of URL-encoded form data. + # + # This decodes + to SP. + # + # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. + def self.decode_www_form_component(str, enc=Encoding::UTF_8) + raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str + str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) + end + + # Generates URL-encoded form data from given +enum+. + # + # This generates application/x-www-form-urlencoded data defined in HTML5 + # from given an Enumerable object. + # + # This internally uses Bundler::URI.encode_www_form_component(str). + # + # This method doesn't convert the encoding of given items, so convert them + # before calling this method if you want to send data as other than original + # encoding or mixed encoding data. (Strings which are encoded in an HTML5 + # ASCII incompatible encoding are converted to UTF-8.) + # + # This method doesn't handle files. When you send a file, use + # multipart/form-data. + # + # This refers http://url.spec.whatwg.org/#concept-urlencoded-serializer + # + # Bundler::URI.encode_www_form([["q", "ruby"], ["lang", "en"]]) + # #=> "q=ruby&lang=en" + # Bundler::URI.encode_www_form("q" => "ruby", "lang" => "en") + # #=> "q=ruby&lang=en" + # Bundler::URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en") + # #=> "q=ruby&q=perl&lang=en" + # Bundler::URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]]) + # #=> "q=ruby&q=perl&lang=en" + # + # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. + def self.encode_www_form(enum, enc=nil) + enum.map do |k,v| + if v.nil? + encode_www_form_component(k, enc) + elsif v.respond_to?(:to_ary) + v.to_ary.map do |w| + str = encode_www_form_component(k, enc) + unless w.nil? + str << '=' + str << encode_www_form_component(w, enc) + end + end.join('&') + else + str = encode_www_form_component(k, enc) + str << '=' + str << encode_www_form_component(v, enc) + end + end.join('&') + end + + # Decodes URL-encoded form data from given +str+. + # + # This decodes application/x-www-form-urlencoded data + # and returns an array of key-value arrays. + # + # This refers http://url.spec.whatwg.org/#concept-urlencoded-parser, + # so this supports only &-separator, and doesn't support ;-separator. + # + # ary = Bundler::URI.decode_www_form("a=1&a=2&b=3") + # ary #=> [['a', '1'], ['a', '2'], ['b', '3']] + # ary.assoc('a').last #=> '1' + # ary.assoc('b').last #=> '3' + # ary.rassoc('a').last #=> '2' + # Hash[ary] #=> {"a"=>"2", "b"=>"3"} + # + # See Bundler::URI.decode_www_form_component, Bundler::URI.encode_www_form. + def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) + raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? + ary = [] + return ary if str.empty? + enc = Encoding.find(enc) + str.b.each_line(separator) do |string| + string.chomp!(separator) + key, sep, val = string.partition('=') + if isindex + if sep.empty? + val = key + key = +'' + end + isindex = false + end + + if use__charset_ and key == '_charset_' and e = get_encoding(val) + enc = e + use__charset_ = false + end + + key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) + if val + val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) + else + val = +'' + end + + ary << [key, val] + end + ary.each do |k, v| + k.force_encoding(enc) + k.scrub! + v.force_encoding(enc) + v.scrub! + end + ary + end + + private +=begin command for WEB_ENCODINGS_ + curl https://encoding.spec.whatwg.org/encodings.json| + ruby -rjson -e 'H={} + h={ + "shift_jis"=>"Windows-31J", + "euc-jp"=>"cp51932", + "iso-2022-jp"=>"cp50221", + "x-mac-cyrillic"=>"macCyrillic", + } + JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x| + Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next + x["labels"].each{|y|H[y]=n} + } + puts "{" + H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]} + puts "}" +' +=end + WEB_ENCODINGS_ = { + "unicode-1-1-utf-8"=>"utf-8", + "utf-8"=>"utf-8", + "utf8"=>"utf-8", + "866"=>"ibm866", + "cp866"=>"ibm866", + "csibm866"=>"ibm866", + "ibm866"=>"ibm866", + "csisolatin2"=>"iso-8859-2", + "iso-8859-2"=>"iso-8859-2", + "iso-ir-101"=>"iso-8859-2", + "iso8859-2"=>"iso-8859-2", + "iso88592"=>"iso-8859-2", + "iso_8859-2"=>"iso-8859-2", + "iso_8859-2:1987"=>"iso-8859-2", + "l2"=>"iso-8859-2", + "latin2"=>"iso-8859-2", + "csisolatin3"=>"iso-8859-3", + "iso-8859-3"=>"iso-8859-3", + "iso-ir-109"=>"iso-8859-3", + "iso8859-3"=>"iso-8859-3", + "iso88593"=>"iso-8859-3", + "iso_8859-3"=>"iso-8859-3", + "iso_8859-3:1988"=>"iso-8859-3", + "l3"=>"iso-8859-3", + "latin3"=>"iso-8859-3", + "csisolatin4"=>"iso-8859-4", + "iso-8859-4"=>"iso-8859-4", + "iso-ir-110"=>"iso-8859-4", + "iso8859-4"=>"iso-8859-4", + "iso88594"=>"iso-8859-4", + "iso_8859-4"=>"iso-8859-4", + "iso_8859-4:1988"=>"iso-8859-4", + "l4"=>"iso-8859-4", + "latin4"=>"iso-8859-4", + "csisolatincyrillic"=>"iso-8859-5", + "cyrillic"=>"iso-8859-5", + "iso-8859-5"=>"iso-8859-5", + "iso-ir-144"=>"iso-8859-5", + "iso8859-5"=>"iso-8859-5", + "iso88595"=>"iso-8859-5", + "iso_8859-5"=>"iso-8859-5", + "iso_8859-5:1988"=>"iso-8859-5", + "arabic"=>"iso-8859-6", + "asmo-708"=>"iso-8859-6", + "csiso88596e"=>"iso-8859-6", + "csiso88596i"=>"iso-8859-6", + "csisolatinarabic"=>"iso-8859-6", + "ecma-114"=>"iso-8859-6", + "iso-8859-6"=>"iso-8859-6", + "iso-8859-6-e"=>"iso-8859-6", + "iso-8859-6-i"=>"iso-8859-6", + "iso-ir-127"=>"iso-8859-6", + "iso8859-6"=>"iso-8859-6", + "iso88596"=>"iso-8859-6", + "iso_8859-6"=>"iso-8859-6", + "iso_8859-6:1987"=>"iso-8859-6", + "csisolatingreek"=>"iso-8859-7", + "ecma-118"=>"iso-8859-7", + "elot_928"=>"iso-8859-7", + "greek"=>"iso-8859-7", + "greek8"=>"iso-8859-7", + "iso-8859-7"=>"iso-8859-7", + "iso-ir-126"=>"iso-8859-7", + "iso8859-7"=>"iso-8859-7", + "iso88597"=>"iso-8859-7", + "iso_8859-7"=>"iso-8859-7", + "iso_8859-7:1987"=>"iso-8859-7", + "sun_eu_greek"=>"iso-8859-7", + "csiso88598e"=>"iso-8859-8", + "csisolatinhebrew"=>"iso-8859-8", + "hebrew"=>"iso-8859-8", + "iso-8859-8"=>"iso-8859-8", + "iso-8859-8-e"=>"iso-8859-8", + "iso-ir-138"=>"iso-8859-8", + "iso8859-8"=>"iso-8859-8", + "iso88598"=>"iso-8859-8", + "iso_8859-8"=>"iso-8859-8", + "iso_8859-8:1988"=>"iso-8859-8", + "visual"=>"iso-8859-8", + "csisolatin6"=>"iso-8859-10", + "iso-8859-10"=>"iso-8859-10", + "iso-ir-157"=>"iso-8859-10", + "iso8859-10"=>"iso-8859-10", + "iso885910"=>"iso-8859-10", + "l6"=>"iso-8859-10", + "latin6"=>"iso-8859-10", + "iso-8859-13"=>"iso-8859-13", + "iso8859-13"=>"iso-8859-13", + "iso885913"=>"iso-8859-13", + "iso-8859-14"=>"iso-8859-14", + "iso8859-14"=>"iso-8859-14", + "iso885914"=>"iso-8859-14", + "csisolatin9"=>"iso-8859-15", + "iso-8859-15"=>"iso-8859-15", + "iso8859-15"=>"iso-8859-15", + "iso885915"=>"iso-8859-15", + "iso_8859-15"=>"iso-8859-15", + "l9"=>"iso-8859-15", + "iso-8859-16"=>"iso-8859-16", + "cskoi8r"=>"koi8-r", + "koi"=>"koi8-r", + "koi8"=>"koi8-r", + "koi8-r"=>"koi8-r", + "koi8_r"=>"koi8-r", + "koi8-ru"=>"koi8-u", + "koi8-u"=>"koi8-u", + "dos-874"=>"windows-874", + "iso-8859-11"=>"windows-874", + "iso8859-11"=>"windows-874", + "iso885911"=>"windows-874", + "tis-620"=>"windows-874", + "windows-874"=>"windows-874", + "cp1250"=>"windows-1250", + "windows-1250"=>"windows-1250", + "x-cp1250"=>"windows-1250", + "cp1251"=>"windows-1251", + "windows-1251"=>"windows-1251", + "x-cp1251"=>"windows-1251", + "ansi_x3.4-1968"=>"windows-1252", + "ascii"=>"windows-1252", + "cp1252"=>"windows-1252", + "cp819"=>"windows-1252", + "csisolatin1"=>"windows-1252", + "ibm819"=>"windows-1252", + "iso-8859-1"=>"windows-1252", + "iso-ir-100"=>"windows-1252", + "iso8859-1"=>"windows-1252", + "iso88591"=>"windows-1252", + "iso_8859-1"=>"windows-1252", + "iso_8859-1:1987"=>"windows-1252", + "l1"=>"windows-1252", + "latin1"=>"windows-1252", + "us-ascii"=>"windows-1252", + "windows-1252"=>"windows-1252", + "x-cp1252"=>"windows-1252", + "cp1253"=>"windows-1253", + "windows-1253"=>"windows-1253", + "x-cp1253"=>"windows-1253", + "cp1254"=>"windows-1254", + "csisolatin5"=>"windows-1254", + "iso-8859-9"=>"windows-1254", + "iso-ir-148"=>"windows-1254", + "iso8859-9"=>"windows-1254", + "iso88599"=>"windows-1254", + "iso_8859-9"=>"windows-1254", + "iso_8859-9:1989"=>"windows-1254", + "l5"=>"windows-1254", + "latin5"=>"windows-1254", + "windows-1254"=>"windows-1254", + "x-cp1254"=>"windows-1254", + "cp1255"=>"windows-1255", + "windows-1255"=>"windows-1255", + "x-cp1255"=>"windows-1255", + "cp1256"=>"windows-1256", + "windows-1256"=>"windows-1256", + "x-cp1256"=>"windows-1256", + "cp1257"=>"windows-1257", + "windows-1257"=>"windows-1257", + "x-cp1257"=>"windows-1257", + "cp1258"=>"windows-1258", + "windows-1258"=>"windows-1258", + "x-cp1258"=>"windows-1258", + "x-mac-cyrillic"=>"macCyrillic", + "x-mac-ukrainian"=>"macCyrillic", + "chinese"=>"gbk", + "csgb2312"=>"gbk", + "csiso58gb231280"=>"gbk", + "gb2312"=>"gbk", + "gb_2312"=>"gbk", + "gb_2312-80"=>"gbk", + "gbk"=>"gbk", + "iso-ir-58"=>"gbk", + "x-gbk"=>"gbk", + "gb18030"=>"gb18030", + "big5"=>"big5", + "big5-hkscs"=>"big5", + "cn-big5"=>"big5", + "csbig5"=>"big5", + "x-x-big5"=>"big5", + "cseucpkdfmtjapanese"=>"cp51932", + "euc-jp"=>"cp51932", + "x-euc-jp"=>"cp51932", + "csiso2022jp"=>"cp50221", + "iso-2022-jp"=>"cp50221", + "csshiftjis"=>"Windows-31J", + "ms932"=>"Windows-31J", + "ms_kanji"=>"Windows-31J", + "shift-jis"=>"Windows-31J", + "shift_jis"=>"Windows-31J", + "sjis"=>"Windows-31J", + "windows-31j"=>"Windows-31J", + "x-sjis"=>"Windows-31J", + "cseuckr"=>"euc-kr", + "csksc56011987"=>"euc-kr", + "euc-kr"=>"euc-kr", + "iso-ir-149"=>"euc-kr", + "korean"=>"euc-kr", + "ks_c_5601-1987"=>"euc-kr", + "ks_c_5601-1989"=>"euc-kr", + "ksc5601"=>"euc-kr", + "ksc_5601"=>"euc-kr", + "windows-949"=>"euc-kr", + "utf-16be"=>"utf-16be", + "utf-16"=>"utf-16le", + "utf-16le"=>"utf-16le", + } # :nodoc: + + # :nodoc: + # return encoding or nil + # http://encoding.spec.whatwg.org/#concept-encoding-get + def self.get_encoding(label) + Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil + end +end # module Bundler::URI + +module Bundler + + # + # Returns +uri+ converted to an Bundler::URI object. + # + def URI(uri) + if uri.is_a?(Bundler::URI::Generic) + uri + elsif uri = String.try_convert(uri) + Bundler::URI.parse(uri) + else + raise ArgumentError, + "bad argument (expected Bundler::URI object or Bundler::URI string)" + end + end + module_function :URI +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/file.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/file.rb new file mode 100644 index 0000000000..df42f8bcdd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/file.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative 'generic' + +module Bundler::URI + + # + # The "file" Bundler::URI is defined by RFC8089. + # + class File < Generic + # A Default port of nil for Bundler::URI::File. + DEFAULT_PORT = nil + + # + # An Array of the available components for Bundler::URI::File. + # + COMPONENT = [ + :scheme, + :host, + :path + ].freeze + + # + # == Description + # + # Creates a new Bundler::URI::File object from components, with syntax checking. + # + # The components accepted are +host+ and +path+. + # + # The components should be provided either as an Array, or as a Hash + # with keys formed by preceding the component names with a colon. + # + # If an Array is used, the components must be passed in the + # order [host, path]. + # + # Examples: + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri1 = Bundler::URI::File.build(['host.example.com', '/path/file.zip']) + # uri1.to_s # => "file://host.example.com/path/file.zip" + # + # uri2 = Bundler::URI::File.build({:host => 'host.example.com', + # :path => '/ruby/src'}) + # uri2.to_s # => "file://host.example.com/ruby/src" + # + def self.build(args) + tmp = Util::make_components_hash(self, args) + super(tmp) + end + + # Protected setter for the host component +v+. + # + # See also Bundler::URI::Generic.host=. + # + def set_host(v) + v = "" if v.nil? || v == "localhost" + @host = v + end + + # do nothing + def set_port(v) + end + + # raise InvalidURIError + def check_userinfo(user) + raise Bundler::URI::InvalidURIError, "can not set userinfo for file Bundler::URI" + end + + # raise InvalidURIError + def check_user(user) + raise Bundler::URI::InvalidURIError, "can not set user for file Bundler::URI" + end + + # raise InvalidURIError + def check_password(user) + raise Bundler::URI::InvalidURIError, "can not set password for file Bundler::URI" + end + + # do nothing + def set_userinfo(v) + end + + # do nothing + def set_user(v) + end + + # do nothing + def set_password(v) + end + end + + @@schemes['FILE'] = File +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ftp.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ftp.rb new file mode 100644 index 0000000000..ad39f57d7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ftp.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: false +# = uri/ftp.rb +# +# Author:: Akira Yamada +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'generic' + +module Bundler::URI + + # + # FTP Bundler::URI syntax is defined by RFC1738 section 3.2. + # + # This class will be redesigned because of difference of implementations; + # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it + # is a good summary about the de facto spec. + # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04 + # + class FTP < Generic + # A Default port of 21 for Bundler::URI::FTP. + DEFAULT_PORT = 21 + + # + # An Array of the available components for Bundler::URI::FTP. + # + COMPONENT = [ + :scheme, + :userinfo, :host, :port, + :path, :typecode + ].freeze + + # + # Typecode is "a", "i", or "d". + # + # * "a" indicates a text file (the FTP command was ASCII) + # * "i" indicates a binary file (FTP command IMAGE) + # * "d" indicates the contents of a directory should be displayed + # + TYPECODE = ['a', 'i', 'd'].freeze + + # Typecode prefix ";type=". + TYPECODE_PREFIX = ';type='.freeze + + def self.new2(user, password, host, port, path, + typecode = nil, arg_check = true) # :nodoc: + # Do not use this method! Not tested. [Bug #7301] + # This methods remains just for compatibility, + # Keep it undocumented until the active maintainer is assigned. + typecode = nil if typecode.size == 0 + if typecode && !TYPECODE.include?(typecode) + raise ArgumentError, + "bad typecode is specified: #{typecode}" + end + + # do escape + + self.new('ftp', + [user, password], + host, port, nil, + typecode ? path + TYPECODE_PREFIX + typecode : path, + nil, nil, nil, arg_check) + end + + # + # == Description + # + # Creates a new Bundler::URI::FTP object from components, with syntax checking. + # + # The components accepted are +userinfo+, +host+, +port+, +path+, and + # +typecode+. + # + # The components should be provided either as an Array, or as a Hash + # with keys formed by preceding the component names with a colon. + # + # If an Array is used, the components must be passed in the + # order [userinfo, host, port, path, typecode]. + # + # If the path supplied is absolute, it will be escaped in order to + # make it absolute in the Bundler::URI. + # + # Examples: + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri1 = Bundler::URI::FTP.build(['user:password', 'ftp.example.com', nil, + # '/path/file.zip', 'i']) + # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i" + # + # uri2 = Bundler::URI::FTP.build({:host => 'ftp.example.com', + # :path => 'ruby/src'}) + # uri2.to_s # => "ftp://ftp.example.com/ruby/src" + # + def self.build(args) + + # Fix the incoming path to be generic URL syntax + # FTP path -> URL path + # foo/bar /foo/bar + # /foo/bar /%2Ffoo/bar + # + if args.kind_of?(Array) + args[3] = '/' + args[3].sub(/^\//, '%2F') + else + args[:path] = '/' + args[:path].sub(/^\//, '%2F') + end + + tmp = Util::make_components_hash(self, args) + + if tmp[:typecode] + if tmp[:typecode].size == 1 + tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode] + end + tmp[:path] << tmp[:typecode] + end + + return super(tmp) + end + + # + # == Description + # + # Creates a new Bundler::URI::FTP object from generic URL components with no + # syntax checking. + # + # Unlike build(), this method does not escape the path component as + # required by RFC1738; instead it is treated as per RFC2396. + # + # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, + # +opaque+, +query+, and +fragment+, in that order. + # + def initialize(scheme, + userinfo, host, port, registry, + path, opaque, + query, + fragment, + parser = nil, + arg_check = false) + raise InvalidURIError unless path + path = path.sub(/^\//,'') + path.sub!(/^%2F/,'/') + super(scheme, userinfo, host, port, registry, path, opaque, + query, fragment, parser, arg_check) + @typecode = nil + if tmp = @path.index(TYPECODE_PREFIX) + typecode = @path[tmp + TYPECODE_PREFIX.size..-1] + @path = @path[0..tmp - 1] + + if arg_check + self.typecode = typecode + else + self.set_typecode(typecode) + end + end + end + + # typecode accessor. + # + # See Bundler::URI::FTP::COMPONENT. + attr_reader :typecode + + # Validates typecode +v+, + # returns +true+ or +false+. + # + def check_typecode(v) + if TYPECODE.include?(v) + return true + else + raise InvalidComponentError, + "bad typecode(expected #{TYPECODE.join(', ')}): #{v}" + end + end + private :check_typecode + + # Private setter for the typecode +v+. + # + # See also Bundler::URI::FTP.typecode=. + # + def set_typecode(v) + @typecode = v + end + protected :set_typecode + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the typecode +v+ + # (with validation). + # + # See also Bundler::URI::FTP.check_typecode. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("ftp://john@ftp.example.com/my_file.img") + # #=> # + # uri.typecode = "i" + # uri + # #=> # + # + def typecode=(typecode) + check_typecode(typecode) + set_typecode(typecode) + typecode + end + + def merge(oth) # :nodoc: + tmp = super(oth) + if self != tmp + tmp.set_typecode(oth.typecode) + end + + return tmp + end + + # Returns the path from an FTP Bundler::URI. + # + # RFC 1738 specifically states that the path for an FTP Bundler::URI does not + # include the / which separates the Bundler::URI path from the Bundler::URI host. Example: + # + # ftp://ftp.example.com/pub/ruby + # + # The above Bundler::URI indicates that the client should connect to + # ftp.example.com then cd to pub/ruby from the initial login directory. + # + # If you want to cd to an absolute directory, you must include an + # escaped / (%2F) in the path. Example: + # + # ftp://ftp.example.com/%2Fpub/ruby + # + # This method will then return "/pub/ruby". + # + def path + return @path.sub(/^\//,'').sub(/^%2F/,'/') + end + + # Private setter for the path of the Bundler::URI::FTP. + def set_path(v) + super("/" + v.sub(/^\//, "%2F")) + end + protected :set_path + + # Returns a String representation of the Bundler::URI::FTP. + def to_s + save_path = nil + if @typecode + save_path = @path + @path = @path + TYPECODE_PREFIX + @typecode + end + str = super + if @typecode + @path = save_path + end + + return str + end + end + @@schemes['FTP'] = FTP +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/generic.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/generic.rb new file mode 100644 index 0000000000..56b09e1d7f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -0,0 +1,1568 @@ +# frozen_string_literal: true + +# = uri/generic.rb +# +# Author:: Akira Yamada +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'common' +autoload :IPSocket, 'socket' +autoload :IPAddr, 'ipaddr' + +module Bundler::URI + + # + # Base class for all Bundler::URI classes. + # Implements generic Bundler::URI syntax as per RFC 2396. + # + class Generic + include Bundler::URI + + # + # A Default port of nil for Bundler::URI::Generic. + # + DEFAULT_PORT = nil + + # + # Returns default port. + # + def self.default_port + self::DEFAULT_PORT + end + + # + # Returns default port. + # + def default_port + self.class.default_port + end + + # + # An Array of the available components for Bundler::URI::Generic. + # + COMPONENT = [ + :scheme, + :userinfo, :host, :port, :registry, + :path, :opaque, + :query, + :fragment + ].freeze + + # + # Components of the Bundler::URI in the order. + # + def self.component + self::COMPONENT + end + + USE_REGISTRY = false # :nodoc: + + def self.use_registry # :nodoc: + self::USE_REGISTRY + end + + # + # == Synopsis + # + # See ::new. + # + # == Description + # + # At first, tries to create a new Bundler::URI::Generic instance using + # Bundler::URI::Generic::build. But, if exception Bundler::URI::InvalidComponentError is raised, + # then it does Bundler::URI::Escape.escape all Bundler::URI components and tries again. + # + def self.build2(args) + begin + return self.build(args) + rescue InvalidComponentError + if args.kind_of?(Array) + return self.build(args.collect{|x| + if x.is_a?(String) + DEFAULT_PARSER.escape(x) + else + x + end + }) + elsif args.kind_of?(Hash) + tmp = {} + args.each do |key, value| + tmp[key] = if value + DEFAULT_PARSER.escape(value) + else + value + end + end + return self.build(tmp) + end + end + end + + # + # == Synopsis + # + # See ::new. + # + # == Description + # + # Creates a new Bundler::URI::Generic instance from components of Bundler::URI::Generic + # with check. Components are: scheme, userinfo, host, port, registry, path, + # opaque, query, and fragment. You can provide arguments either by an Array or a Hash. + # See ::new for hash keys to use or for order of array items. + # + def self.build(args) + if args.kind_of?(Array) && + args.size == ::Bundler::URI::Generic::COMPONENT.size + tmp = args.dup + elsif args.kind_of?(Hash) + tmp = ::Bundler::URI::Generic::COMPONENT.collect do |c| + if args.include?(c) + args[c] + else + nil + end + end + else + component = self.class.component rescue ::Bundler::URI::Generic::COMPONENT + raise ArgumentError, + "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + end + + tmp << nil + tmp << true + return self.new(*tmp) + end + + # + # == Args + # + # +scheme+:: + # Protocol scheme, i.e. 'http','ftp','mailto' and so on. + # +userinfo+:: + # User name and password, i.e. 'sdmitry:bla'. + # +host+:: + # Server host name. + # +port+:: + # Server port. + # +registry+:: + # Registry of naming authorities. + # +path+:: + # Path on server. + # +opaque+:: + # Opaque part. + # +query+:: + # Query data. + # +fragment+:: + # Part of the Bundler::URI after '#' character. + # +parser+:: + # Parser for internal use [Bundler::URI::DEFAULT_PARSER by default]. + # +arg_check+:: + # Check arguments [false by default]. + # + # == Description + # + # Creates a new Bundler::URI::Generic instance from ``generic'' components without check. + # + def initialize(scheme, + userinfo, host, port, registry, + path, opaque, + query, + fragment, + parser = DEFAULT_PARSER, + arg_check = false) + @scheme = nil + @user = nil + @password = nil + @host = nil + @port = nil + @path = nil + @query = nil + @opaque = nil + @fragment = nil + @parser = parser == DEFAULT_PARSER ? nil : parser + + if arg_check + self.scheme = scheme + self.userinfo = userinfo + self.hostname = host + self.port = port + self.path = path + self.query = query + self.opaque = opaque + self.fragment = fragment + else + self.set_scheme(scheme) + self.set_userinfo(userinfo) + self.set_host(host) + self.set_port(port) + self.set_path(path) + self.query = query + self.set_opaque(opaque) + self.fragment=(fragment) + end + if registry + raise InvalidURIError, + "the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)" + end + + @scheme&.freeze + self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2) + self.set_port(self.default_port) if self.default_port && !@port + end + + # + # Returns the scheme component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz").scheme #=> "http" + # + attr_reader :scheme + + # Returns the host component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz").host #=> "foo" + # + # It returns nil if no host component exists. + # + # Bundler::URI("mailto:foo@example.org").host #=> nil + # + # The component does not contain the port number. + # + # Bundler::URI("http://foo:8080/bar/baz").host #=> "foo" + # + # Since IPv6 addresses are wrapped with brackets in URIs, + # this method returns IPv6 addresses wrapped with brackets. + # This form is not appropriate to pass to socket methods such as TCPSocket.open. + # If unwrapped host names are required, use the #hostname method. + # + # Bundler::URI("http://[::1]/bar/baz").host #=> "[::1]" + # Bundler::URI("http://[::1]/bar/baz").hostname #=> "::1" + # + attr_reader :host + + # Returns the port component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz").port #=> 80 + # Bundler::URI("http://foo:8080/bar/baz").port #=> 8080 + # + attr_reader :port + + def registry # :nodoc: + nil + end + + # Returns the path component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz").path #=> "/bar/baz" + # + attr_reader :path + + # Returns the query component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar" + # + attr_reader :query + + # Returns the opaque part of the Bundler::URI. + # + # Bundler::URI("mailto:foo@example.org").opaque #=> "foo@example.org" + # Bundler::URI("http://foo/bar/baz").opaque #=> nil + # + # The portion of the path that does not make use of the slash '/'. + # The path typically refers to an absolute path or an opaque part. + # (See RFC2396 Section 3 and 5.2.) + # + attr_reader :opaque + + # Returns the fragment component of the Bundler::URI. + # + # Bundler::URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies" + # + attr_reader :fragment + + # Returns the parser to be used. + # + # Unless a Bundler::URI::Parser is defined, DEFAULT_PARSER is used. + # + def parser + if !defined?(@parser) || !@parser + DEFAULT_PARSER + else + @parser || DEFAULT_PARSER + end + end + + # Replaces self by other Bundler::URI object. + # + def replace!(oth) + if self.class != oth.class + raise ArgumentError, "expected #{self.class} object" + end + + component.each do |c| + self.__send__("#{c}=", oth.__send__(c)) + end + end + private :replace! + + # + # Components of the Bundler::URI in the order. + # + def component + self.class.component + end + + # + # Checks the scheme +v+ component against the Bundler::URI::Parser Regexp for :SCHEME. + # + def check_scheme(v) + if v && parser.regexp[:SCHEME] !~ v + raise InvalidComponentError, + "bad component(expected scheme component): #{v}" + end + + return true + end + private :check_scheme + + # Protected setter for the scheme component +v+. + # + # See also Bundler::URI::Generic.scheme=. + # + def set_scheme(v) + @scheme = v&.downcase + end + protected :set_scheme + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the scheme component +v+ + # (with validation). + # + # See also Bundler::URI::Generic.check_scheme. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.scheme = "https" + # uri.to_s #=> "https://my.example.com" + # + def scheme=(v) + check_scheme(v) + set_scheme(v) + v + end + + # + # Checks the +user+ and +password+. + # + # If +password+ is not provided, then +user+ is + # split, using Bundler::URI::Generic.split_userinfo, to + # pull +user+ and +password. + # + # See also Bundler::URI::Generic.check_user, Bundler::URI::Generic.check_password. + # + def check_userinfo(user, password = nil) + if !password + user, password = split_userinfo(user) + end + check_user(user) + check_password(password, user) + + return true + end + private :check_userinfo + + # + # Checks the user +v+ component for RFC2396 compliance + # and against the Bundler::URI::Parser Regexp for :USERINFO. + # + # Can not have a registry or opaque component defined, + # with a user component defined. + # + def check_user(v) + if @opaque + raise InvalidURIError, + "can not set user with opaque" + end + + return v unless v + + if parser.regexp[:USERINFO] !~ v + raise InvalidComponentError, + "bad component(expected userinfo component or user component): #{v}" + end + + return true + end + private :check_user + + # + # Checks the password +v+ component for RFC2396 compliance + # and against the Bundler::URI::Parser Regexp for :USERINFO. + # + # Can not have a registry or opaque component defined, + # with a user component defined. + # + def check_password(v, user = @user) + if @opaque + raise InvalidURIError, + "can not set password with opaque" + end + return v unless v + + if !user + raise InvalidURIError, + "password component depends user component" + end + + if parser.regexp[:USERINFO] !~ v + raise InvalidComponentError, + "bad password component" + end + + return true + end + private :check_password + + # + # Sets userinfo, argument is string like 'name:pass'. + # + def userinfo=(userinfo) + if userinfo.nil? + return nil + end + check_userinfo(*userinfo) + set_userinfo(*userinfo) + # returns userinfo + end + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the +user+ component + # (with validation). + # + # See also Bundler::URI::Generic.check_user. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://john:S3nsit1ve@my.example.com") + # uri.user = "sam" + # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" + # + def user=(user) + check_user(user) + set_user(user) + # returns user + end + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the +password+ component + # (with validation). + # + # See also Bundler::URI::Generic.check_password. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://john:S3nsit1ve@my.example.com") + # uri.password = "V3ry_S3nsit1ve" + # uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com" + # + def password=(password) + check_password(password) + set_password(password) + # returns password + end + + # Protected setter for the +user+ component, and +password+ if available + # (with validation). + # + # See also Bundler::URI::Generic.userinfo=. + # + def set_userinfo(user, password = nil) + unless password + user, password = split_userinfo(user) + end + @user = user + @password = password if password + + [@user, @password] + end + protected :set_userinfo + + # Protected setter for the user component +v+. + # + # See also Bundler::URI::Generic.user=. + # + def set_user(v) + set_userinfo(v, @password) + v + end + protected :set_user + + # Protected setter for the password component +v+. + # + # See also Bundler::URI::Generic.password=. + # + def set_password(v) + @password = v + # returns v + end + protected :set_password + + # Returns the userinfo +ui+ as [user, password] + # if properly formatted as 'user:password'. + def split_userinfo(ui) + return nil, nil unless ui + user, password = ui.split(':', 2) + + return user, password + end + private :split_userinfo + + # Escapes 'user:password' +v+ based on RFC 1738 section 3.1. + def escape_userpass(v) + parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/ + end + private :escape_userpass + + # Returns the userinfo, either as 'user' or 'user:password'. + def userinfo + if @user.nil? + nil + elsif @password.nil? + @user + else + @user + ':' + @password + end + end + + # Returns the user component. + def user + @user + end + + # Returns the password component. + def password + @password + end + + # + # Checks the host +v+ component for RFC2396 compliance + # and against the Bundler::URI::Parser Regexp for :HOST. + # + # Can not have a registry or opaque component defined, + # with a host component defined. + # + def check_host(v) + return v unless v + + if @opaque + raise InvalidURIError, + "can not set host with registry or opaque" + elsif parser.regexp[:HOST] !~ v + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + return true + end + private :check_host + + # Protected setter for the host component +v+. + # + # See also Bundler::URI::Generic.host=. + # + def set_host(v) + @host = v + end + protected :set_host + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the host component +v+ + # (with validation). + # + # See also Bundler::URI::Generic.check_host. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.host = "foo.com" + # uri.to_s #=> "http://foo.com" + # + def host=(v) + check_host(v) + set_host(v) + v + end + + # Extract the host part of the Bundler::URI and unwrap brackets for IPv6 addresses. + # + # This method is the same as Bundler::URI::Generic#host except + # brackets for IPv6 (and future IP) addresses are removed. + # + # uri = Bundler::URI("http://[::1]/bar") + # uri.hostname #=> "::1" + # uri.host #=> "[::1]" + # + def hostname + v = self.host + /\A\[(.*)\]\z/ =~ v ? $1 : v + end + + # Sets the host part of the Bundler::URI as the argument with brackets for IPv6 addresses. + # + # This method is the same as Bundler::URI::Generic#host= except + # the argument can be a bare IPv6 address. + # + # uri = Bundler::URI("http://foo/bar") + # uri.hostname = "::1" + # uri.to_s #=> "http://[::1]/bar" + # + # If the argument seems to be an IPv6 address, + # it is wrapped with brackets. + # + def hostname=(v) + v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + self.host = v + end + + # + # Checks the port +v+ component for RFC2396 compliance + # and against the Bundler::URI::Parser Regexp for :PORT. + # + # Can not have a registry or opaque component defined, + # with a port component defined. + # + def check_port(v) + return v unless v + + if @opaque + raise InvalidURIError, + "can not set port with registry or opaque" + elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v + raise InvalidComponentError, + "bad component(expected port component): #{v.inspect}" + end + + return true + end + private :check_port + + # Protected setter for the port component +v+. + # + # See also Bundler::URI::Generic.port=. + # + def set_port(v) + v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer) + @port = v + end + protected :set_port + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the port component +v+ + # (with validation). + # + # See also Bundler::URI::Generic.check_port. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.port = 8080 + # uri.to_s #=> "http://my.example.com:8080" + # + def port=(v) + check_port(v) + set_port(v) + port + end + + def check_registry(v) # :nodoc: + raise InvalidURIError, "can not set registry" + end + private :check_registry + + def set_registry(v) #:nodoc: + raise InvalidURIError, "can not set registry" + end + protected :set_registry + + def registry=(v) + raise InvalidURIError, "can not set registry" + end + + # + # Checks the path +v+ component for RFC2396 compliance + # and against the Bundler::URI::Parser Regexp + # for :ABS_PATH and :REL_PATH. + # + # Can not have a opaque component defined, + # with a path component defined. + # + def check_path(v) + # raise if both hier and opaque are not nil, because: + # absoluteURI = scheme ":" ( hier_part | opaque_part ) + # hier_part = ( net_path | abs_path ) [ "?" query ] + if v && @opaque + raise InvalidURIError, + "path conflicts with opaque" + end + + # If scheme is ftp, path may be relative. + # See RFC 1738 section 3.2.2, and RFC 2396. + if @scheme && @scheme != "ftp" + if v && v != '' && parser.regexp[:ABS_PATH] !~ v + raise InvalidComponentError, + "bad component(expected absolute path component): #{v}" + end + else + if v && v != '' && parser.regexp[:ABS_PATH] !~ v && + parser.regexp[:REL_PATH] !~ v + raise InvalidComponentError, + "bad component(expected relative path component): #{v}" + end + end + + return true + end + private :check_path + + # Protected setter for the path component +v+. + # + # See also Bundler::URI::Generic.path=. + # + def set_path(v) + @path = v + end + protected :set_path + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the path component +v+ + # (with validation). + # + # See also Bundler::URI::Generic.check_path. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com/pub/files") + # uri.path = "/faq/" + # uri.to_s #=> "http://my.example.com/faq/" + # + def path=(v) + check_path(v) + set_path(v) + v + end + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the query component +v+. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com/?id=25") + # uri.query = "id=1" + # uri.to_s #=> "http://my.example.com/?id=1" + # + def query=(v) + return @query = nil unless v + raise InvalidURIError, "query conflicts with opaque" if @opaque + + x = v.to_str + v = x.dup if x.equal? v + v.encode!(Encoding::UTF_8) rescue nil + v.delete!("\t\r\n") + v.force_encoding(Encoding::ASCII_8BIT) + raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v) + v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord} + v.force_encoding(Encoding::US_ASCII) + @query = v + end + + # + # Checks the opaque +v+ component for RFC2396 compliance and + # against the Bundler::URI::Parser Regexp for :OPAQUE. + # + # Can not have a host, port, user, or path component defined, + # with an opaque component defined. + # + def check_opaque(v) + return v unless v + + # raise if both hier and opaque are not nil, because: + # absoluteURI = scheme ":" ( hier_part | opaque_part ) + # hier_part = ( net_path | abs_path ) [ "?" query ] + if @host || @port || @user || @path # userinfo = @user + ':' + @password + raise InvalidURIError, + "can not set opaque with host, port, userinfo or path" + elsif v && parser.regexp[:OPAQUE] !~ v + raise InvalidComponentError, + "bad component(expected opaque component): #{v}" + end + + return true + end + private :check_opaque + + # Protected setter for the opaque component +v+. + # + # See also Bundler::URI::Generic.opaque=. + # + def set_opaque(v) + @opaque = v + end + protected :set_opaque + + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the opaque component +v+ + # (with validation). + # + # See also Bundler::URI::Generic.check_opaque. + # + def opaque=(v) + check_opaque(v) + set_opaque(v) + v + end + + # + # Checks the fragment +v+ component against the Bundler::URI::Parser Regexp for :FRAGMENT. + # + # + # == Args + # + # +v+:: + # String + # + # == Description + # + # Public setter for the fragment component +v+ + # (with validation). + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com/?id=25#time=1305212049") + # uri.fragment = "time=1305212086" + # uri.to_s #=> "http://my.example.com/?id=25#time=1305212086" + # + def fragment=(v) + return @fragment = nil unless v + + x = v.to_str + v = x.dup if x.equal? v + v.encode!(Encoding::UTF_8) rescue nil + v.delete!("\t\r\n") + v.force_encoding(Encoding::ASCII_8BIT) + v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord} + v.force_encoding(Encoding::US_ASCII) + @fragment = v + end + + # + # Returns true if Bundler::URI is hierarchical. + # + # == Description + # + # Bundler::URI has components listed in order of decreasing significance from left to right, + # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com/") + # uri.hierarchical? + # #=> true + # uri = Bundler::URI.parse("mailto:joe@example.com") + # uri.hierarchical? + # #=> false + # + def hierarchical? + if @path + true + else + false + end + end + + # + # Returns true if Bundler::URI has a scheme (e.g. http:// or https://) specified. + # + def absolute? + if @scheme + true + else + false + end + end + alias absolute absolute? + + # + # Returns true if Bundler::URI does not have a scheme (e.g. http:// or https://) specified. + # + def relative? + !absolute? + end + + # + # Returns an Array of the path split on '/'. + # + def split_path(path) + path.split("/", -1) + end + private :split_path + + # + # Merges a base path +base+, with relative path +rel+, + # returns a modified base path. + # + def merge_path(base, rel) + + # RFC2396, Section 5.2, 5) + # RFC2396, Section 5.2, 6) + base_path = split_path(base) + rel_path = split_path(rel) + + # RFC2396, Section 5.2, 6), a) + base_path << '' if base_path.last == '..' + while i = base_path.index('..') + base_path.slice!(i - 1, 2) + end + + if (first = rel_path.first) and first.empty? + base_path.clear + rel_path.shift + end + + # RFC2396, Section 5.2, 6), c) + # RFC2396, Section 5.2, 6), d) + rel_path.push('') if rel_path.last == '.' || rel_path.last == '..' + rel_path.delete('.') + + # RFC2396, Section 5.2, 6), e) + tmp = [] + rel_path.each do |x| + if x == '..' && + !(tmp.empty? || tmp.last == '..') + tmp.pop + else + tmp << x + end + end + + add_trailer_slash = !tmp.empty? + if base_path.empty? + base_path = [''] # keep '/' for root directory + elsif add_trailer_slash + base_path.pop + end + while x = tmp.shift + if x == '..' + # RFC2396, Section 4 + # a .. or . in an absolute path has no special meaning + base_path.pop if base_path.size > 1 + else + # if x == '..' + # valid absolute (but abnormal) path "/../..." + # else + # valid absolute path + # end + base_path << x + tmp.each {|t| base_path << t} + add_trailer_slash = false + break + end + end + base_path.push('') if add_trailer_slash + + return base_path.join('/') + end + private :merge_path + + # + # == Args + # + # +oth+:: + # Bundler::URI or String + # + # == Description + # + # Destructive form of #merge. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.merge!("/main.rbx?page=1") + # uri.to_s # => "http://my.example.com/main.rbx?page=1" + # + def merge!(oth) + t = merge(oth) + if self == t + nil + else + replace!(t) + self + end + end + + # + # == Args + # + # +oth+:: + # Bundler::URI or String + # + # == Description + # + # Merges two URIs. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.merge("/main.rbx?page=1") + # # => "http://my.example.com/main.rbx?page=1" + # + def merge(oth) + rel = parser.send(:convert_to_uri, oth) + + if rel.absolute? + #raise BadURIError, "both Bundler::URI are absolute" if absolute? + # hmm... should return oth for usability? + return rel + end + + unless self.absolute? + raise BadURIError, "both Bundler::URI are relative" + end + + base = self.dup + + authority = rel.userinfo || rel.host || rel.port + + # RFC2396, Section 5.2, 2) + if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query + base.fragment=(rel.fragment) if rel.fragment + return base + end + + base.query = nil + base.fragment=(nil) + + # RFC2396, Section 5.2, 4) + if !authority + base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path + else + # RFC2396, Section 5.2, 4) + base.set_path(rel.path) if rel.path + end + + # RFC2396, Section 5.2, 7) + base.set_userinfo(rel.userinfo) if rel.userinfo + base.set_host(rel.host) if rel.host + base.set_port(rel.port) if rel.port + base.query = rel.query if rel.query + base.fragment=(rel.fragment) if rel.fragment + + return base + end # merge + alias + merge + + # :stopdoc: + def route_from_path(src, dst) + case dst + when src + # RFC2396, Section 4.2 + return '' + when %r{(?:\A|/)\.\.?(?:/|\z)} + # dst has abnormal absolute path, + # like "/./", "/../", "/x/../", ... + return dst.dup + end + + src_path = src.scan(%r{[^/]*/}) + dst_path = dst.scan(%r{[^/]*/?}) + + # discard same parts + while !dst_path.empty? && dst_path.first == src_path.first + src_path.shift + dst_path.shift + end + + tmp = dst_path.join + + # calculate + if src_path.empty? + if tmp.empty? + return './' + elsif dst_path.first.include?(':') # (see RFC2396 Section 5) + return './' + tmp + else + return tmp + end + end + + return '../' * src_path.size + tmp + end + private :route_from_path + # :startdoc: + + # :stopdoc: + def route_from0(oth) + oth = parser.send(:convert_to_uri, oth) + if self.relative? + raise BadURIError, + "relative Bundler::URI: #{self}" + end + if oth.relative? + raise BadURIError, + "relative Bundler::URI: #{oth}" + end + + if self.scheme != oth.scheme + return self, self.dup + end + rel = Bundler::URI::Generic.new(nil, # it is relative Bundler::URI + self.userinfo, self.host, self.port, + nil, self.path, self.opaque, + self.query, self.fragment, parser) + + if rel.userinfo != oth.userinfo || + rel.host.to_s.downcase != oth.host.to_s.downcase || + rel.port != oth.port + + if self.userinfo.nil? && self.host.nil? + return self, self.dup + end + + rel.set_port(nil) if rel.port == oth.default_port + return rel, rel + end + rel.set_userinfo(nil) + rel.set_host(nil) + rel.set_port(nil) + + if rel.path && rel.path == oth.path + rel.set_path('') + rel.query = nil if rel.query == oth.query + return rel, rel + elsif rel.opaque && rel.opaque == oth.opaque + rel.set_opaque('') + rel.query = nil if rel.query == oth.query + return rel, rel + end + + # you can modify `rel', but can not `oth'. + return oth, rel + end + private :route_from0 + # :startdoc: + + # + # == Args + # + # +oth+:: + # Bundler::URI or String + # + # == Description + # + # Calculates relative path from oth to self. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse('http://my.example.com/main.rbx?page=1') + # uri.route_from('http://my.example.com') + # #=> # + # + def route_from(oth) + # you can modify `rel', but can not `oth'. + begin + oth, rel = route_from0(oth) + rescue + raise $!.class, $!.message + end + if oth == rel + return rel + end + + rel.set_path(route_from_path(oth.path, self.path)) + if rel.path == './' && self.query + # "./?foo" -> "?foo" + rel.set_path('') + end + + return rel + end + + alias - route_from + + # + # == Args + # + # +oth+:: + # Bundler::URI or String + # + # == Description + # + # Calculates relative path to oth from self. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse('http://my.example.com') + # uri.route_to('http://my.example.com/main.rbx?page=1') + # #=> # + # + def route_to(oth) + parser.send(:convert_to_uri, oth).route_from(self) + end + + # + # Returns normalized Bundler::URI. + # + # require 'bundler/vendor/uri/lib/uri' + # + # Bundler::URI("HTTP://my.EXAMPLE.com").normalize + # #=> # + # + # Normalization here means: + # + # * scheme and host are converted to lowercase, + # * an empty path component is set to "/". + # + def normalize + uri = dup + uri.normalize! + uri + end + + # + # Destructive version of #normalize. + # + def normalize! + if path&.empty? + set_path('/') + end + if scheme && scheme != scheme.downcase + set_scheme(self.scheme.downcase) + end + if host && host != host.downcase + set_host(self.host.downcase) + end + end + + # + # Constructs String from Bundler::URI. + # + def to_s + str = ''.dup + if @scheme + str << @scheme + str << ':' + end + + if @opaque + str << @opaque + else + if @host || %w[file postgres].include?(@scheme) + str << '//' + end + if self.userinfo + str << self.userinfo + str << '@' + end + if @host + str << @host + end + if @port && @port != self.default_port + str << ':' + str << @port.to_s + end + str << @path + if @query + str << '?' + str << @query + end + end + if @fragment + str << '#' + str << @fragment + end + str + end + + # + # Compares two URIs. + # + def ==(oth) + if self.class == oth.class + self.normalize.component_ary == oth.normalize.component_ary + else + false + end + end + + def hash + self.component_ary.hash + end + + def eql?(oth) + self.class == oth.class && + parser == oth.parser && + self.component_ary.eql?(oth.component_ary) + end + +=begin + +--- Bundler::URI::Generic#===(oth) + +=end +# def ===(oth) +# raise NotImplementedError +# end + +=begin +=end + + + # Returns an Array of the components defined from the COMPONENT Array. + def component_ary + component.collect do |x| + self.send(x) + end + end + protected :component_ary + + # == Args + # + # +components+:: + # Multiple Symbol arguments defined in Bundler::URI::HTTP. + # + # == Description + # + # Selects specified components from Bundler::URI. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse('http://myuser:mypass@my.example.com/test.rbx') + # uri.select(:userinfo, :host, :path) + # # => ["myuser:mypass", "my.example.com", "/test.rbx"] + # + def select(*components) + components.collect do |c| + if component.include?(c) + self.send(c) + else + raise ArgumentError, + "expected of components of #{self.class} (#{self.class.component.join(', ')})" + end + end + end + + def inspect + "#<#{self.class} #{self}>" + end + + # + # == Args + # + # +v+:: + # Bundler::URI or String + # + # == Description + # + # Attempts to parse other Bundler::URI +oth+, + # returns [parsed_oth, self]. + # + # == Usage + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("http://my.example.com") + # uri.coerce("http://foo.com") + # #=> [#, #] + # + def coerce(oth) + case oth + when String + oth = parser.parse(oth) + else + super + end + + return oth, self + end + + # Returns a proxy Bundler::URI. + # The proxy Bundler::URI is obtained from environment variables such as http_proxy, + # ftp_proxy, no_proxy, etc. + # If there is no proper proxy, nil is returned. + # + # If the optional parameter +env+ is specified, it is used instead of ENV. + # + # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) + # are examined, too. + # + # But http_proxy and HTTP_PROXY is treated specially under CGI environment. + # It's because HTTP_PROXY may be set by Proxy: header. + # So HTTP_PROXY is not used. + # http_proxy is not used too if the variable is case insensitive. + # CGI_HTTP_PROXY can be used instead. + def find_proxy(env=ENV) + raise BadURIError, "relative Bundler::URI: #{self}" if self.relative? + name = self.scheme.downcase + '_proxy' + proxy_uri = nil + if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI? + # HTTP_PROXY conflicts with *_proxy for proxy settings and + # HTTP_* for header information in CGI. + # So it should be careful to use it. + pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k } + case pairs.length + when 0 # no proxy setting anyway. + proxy_uri = nil + when 1 + k, _ = pairs.shift + if k == 'http_proxy' && env[k.upcase] == nil + # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = env[name] + else + proxy_uri = nil + end + else # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = env.to_hash[name] + end + if !proxy_uri + # Use CGI_HTTP_PROXY. cf. libwww-perl. + proxy_uri = env["CGI_#{name.upcase}"] + end + elsif name == 'http_proxy' + unless proxy_uri = env[name] + if proxy_uri = env[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + end + end + else + proxy_uri = env[name] || env[name.upcase] + end + + if proxy_uri.nil? || proxy_uri.empty? + return nil + end + + if self.hostname + begin + addr = IPSocket.getaddress(self.hostname) + return nil if /\A127\.|\A::1\z/ =~ addr + rescue SocketError + end + end + + name = 'no_proxy' + if no_proxy = env[name] || env[name.upcase] + return nil unless Bundler::URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy) + end + Bundler::URI.parse(proxy_uri) + end + + def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: + hostname = hostname.downcase + dothostname = ".#{hostname}" + no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port| + if !p_port || port == p_port.to_i + if p_host.start_with?('.') + return false if hostname.end_with?(p_host.downcase) + else + return false if dothostname.end_with?(".#{p_host.downcase}") + end + if addr + begin + return false if IPAddr.new(p_host).include?(addr) + rescue IPAddr::InvalidAddressError + next + end + end + end + } + true + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/http.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/http.rb new file mode 100644 index 0000000000..b6ca1c51de --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/http.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: false +# = uri/http.rb +# +# Author:: Akira Yamada +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'generic' + +module Bundler::URI + + # + # The syntax of HTTP URIs is defined in RFC1738 section 3.3. + # + # Note that the Ruby Bundler::URI library allows HTTP URLs containing usernames and + # passwords. This is not legal as per the RFC, but used to be + # supported in Internet Explorer 5 and 6, before the MS04-004 security + # update. See . + # + class HTTP < Generic + # A Default port of 80 for Bundler::URI::HTTP. + DEFAULT_PORT = 80 + + # An Array of the available components for Bundler::URI::HTTP. + COMPONENT = %i[ + scheme + userinfo host port + path + query + fragment + ].freeze + + # + # == Description + # + # Creates a new Bundler::URI::HTTP object from components, with syntax checking. + # + # The components accepted are userinfo, host, port, path, query, and + # fragment. + # + # The components should be provided either as an Array, or as a Hash + # with keys formed by preceding the component names with a colon. + # + # If an Array is used, the components must be passed in the + # order [userinfo, host, port, path, query, fragment]. + # + # Example: + # + # uri = Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') + # + # uri = Bundler::URI::HTTP.build([nil, "www.example.com", nil, "/path", + # "query", 'fragment']) + # + # Currently, if passed userinfo components this method generates + # invalid HTTP URIs as per RFC 1738. + # + def self.build(args) + tmp = Util.make_components_hash(self, args) + super(tmp) + end + + # + # == Description + # + # Returns the full path for an HTTP request, as required by Net::HTTP::Get. + # + # If the Bundler::URI contains a query, the full path is Bundler::URI#path + '?' + Bundler::URI#query. + # Otherwise, the path is simply Bundler::URI#path. + # + # Example: + # + # uri = Bundler::URI::HTTP.build(path: '/foo/bar', query: 'test=true') + # uri.request_uri # => "/foo/bar?test=true" + # + def request_uri + return unless @path + + url = @query ? "#@path?#@query" : @path.dup + url.start_with?(?/.freeze) ? url : ?/ + url + end + end + + @@schemes['HTTP'] = HTTP + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/https.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/https.rb new file mode 100644 index 0000000000..78dc6bf532 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/https.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: false +# = uri/https.rb +# +# Author:: Akira Yamada +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'http' + +module Bundler::URI + + # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather + # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs; + # see Bundler::URI::HTTP. + class HTTPS < HTTP + # A Default port of 443 for Bundler::URI::HTTPS + DEFAULT_PORT = 443 + end + @@schemes['HTTPS'] = HTTPS +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldap.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldap.rb new file mode 100644 index 0000000000..b707bedb97 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldap.rb @@ -0,0 +1,261 @@ +# frozen_string_literal: false +# = uri/ldap.rb +# +# Author:: +# Takaaki Tateishi +# Akira Yamada +# License:: +# Bundler::URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada. +# You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'generic' + +module Bundler::URI + + # + # LDAP Bundler::URI SCHEMA (described in RFC2255). + #-- + # ldap:///[?[?[?[?]]]] + #++ + class LDAP < Generic + + # A Default port of 389 for Bundler::URI::LDAP. + DEFAULT_PORT = 389 + + # An Array of the available components for Bundler::URI::LDAP. + COMPONENT = [ + :scheme, + :host, :port, + :dn, + :attributes, + :scope, + :filter, + :extensions, + ].freeze + + # Scopes available for the starting point. + # + # * SCOPE_BASE - the Base DN + # * SCOPE_ONE - one level under the Base DN, not including the base DN and + # not including any entries under this + # * SCOPE_SUB - subtrees, all entries at all levels + # + SCOPE = [ + SCOPE_ONE = 'one', + SCOPE_SUB = 'sub', + SCOPE_BASE = 'base', + ].freeze + + # + # == Description + # + # Creates a new Bundler::URI::LDAP object from components, with syntax checking. + # + # The components accepted are host, port, dn, attributes, + # scope, filter, and extensions. + # + # The components should be provided either as an Array, or as a Hash + # with keys formed by preceding the component names with a colon. + # + # If an Array is used, the components must be passed in the + # order [host, port, dn, attributes, scope, filter, extensions]. + # + # Example: + # + # uri = Bundler::URI::LDAP.build({:host => 'ldap.example.com', + # :dn => '/dc=example'}) + # + # uri = Bundler::URI::LDAP.build(["ldap.example.com", nil, + # "/dc=example;dc=com", "query", nil, nil, nil]) + # + def self.build(args) + tmp = Util::make_components_hash(self, args) + + if tmp[:dn] + tmp[:path] = tmp[:dn] + end + + query = [] + [:extensions, :filter, :scope, :attributes].collect do |x| + next if !tmp[x] && query.size == 0 + query.unshift(tmp[x]) + end + + tmp[:query] = query.join('?') + + return super(tmp) + end + + # + # == Description + # + # Creates a new Bundler::URI::LDAP object from generic Bundler::URI components as per + # RFC 2396. No LDAP-specific syntax checking is performed. + # + # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, + # +opaque+, +query+, and +fragment+, in that order. + # + # Example: + # + # uri = Bundler::URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil, + # "/dc=example;dc=com", nil, "query", nil) + # + # See also Bundler::URI::Generic.new. + # + def initialize(*arg) + super(*arg) + + if @fragment + raise InvalidURIError, 'bad LDAP URL' + end + + parse_dn + parse_query + end + + # Private method to cleanup +dn+ from using the +path+ component attribute. + def parse_dn + @dn = @path[1..-1] + end + private :parse_dn + + # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+ + # from using the +query+ component attribute. + def parse_query + @attributes = nil + @scope = nil + @filter = nil + @extensions = nil + + if @query + attrs, scope, filter, extensions = @query.split('?') + + @attributes = attrs if attrs && attrs.size > 0 + @scope = scope if scope && scope.size > 0 + @filter = filter if filter && filter.size > 0 + @extensions = extensions if extensions && extensions.size > 0 + end + end + private :parse_query + + # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+. + def build_path_query + @path = '/' + @dn + + query = [] + [@extensions, @filter, @scope, @attributes].each do |x| + next if !x && query.size == 0 + query.unshift(x) + end + @query = query.join('?') + end + private :build_path_query + + # Returns dn. + def dn + @dn + end + + # Private setter for dn +val+. + def set_dn(val) + @dn = val + build_path_query + @dn + end + protected :set_dn + + # Setter for dn +val+. + def dn=(val) + set_dn(val) + val + end + + # Returns attributes. + def attributes + @attributes + end + + # Private setter for attributes +val+. + def set_attributes(val) + @attributes = val + build_path_query + @attributes + end + protected :set_attributes + + # Setter for attributes +val+. + def attributes=(val) + set_attributes(val) + val + end + + # Returns scope. + def scope + @scope + end + + # Private setter for scope +val+. + def set_scope(val) + @scope = val + build_path_query + @scope + end + protected :set_scope + + # Setter for scope +val+. + def scope=(val) + set_scope(val) + val + end + + # Returns filter. + def filter + @filter + end + + # Private setter for filter +val+. + def set_filter(val) + @filter = val + build_path_query + @filter + end + protected :set_filter + + # Setter for filter +val+. + def filter=(val) + set_filter(val) + val + end + + # Returns extensions. + def extensions + @extensions + end + + # Private setter for extensions +val+. + def set_extensions(val) + @extensions = val + build_path_query + @extensions + end + protected :set_extensions + + # Setter for extensions +val+. + def extensions=(val) + set_extensions(val) + val + end + + # Checks if Bundler::URI has a path. + # For Bundler::URI::LDAP this will return +false+. + def hierarchical? + false + end + end + + @@schemes['LDAP'] = LDAP +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldaps.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldaps.rb new file mode 100644 index 0000000000..0af35bb16b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/ldaps.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: false +# = uri/ldap.rb +# +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# +# See Bundler::URI for general documentation +# + +require_relative 'ldap' + +module Bundler::URI + + # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather + # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs; + # see Bundler::URI::LDAP. + class LDAPS < LDAP + # A Default port of 636 for Bundler::URI::LDAPS + DEFAULT_PORT = 636 + end + @@schemes['LDAPS'] = LDAPS +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/mailto.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/mailto.rb new file mode 100644 index 0000000000..5b2a4765c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/mailto.rb @@ -0,0 +1,294 @@ +# frozen_string_literal: false +# = uri/mailto.rb +# +# Author:: Akira Yamada +# License:: You can redistribute it and/or modify it under the same term as Ruby. +# Revision:: $Id$ +# +# See Bundler::URI for general documentation +# + +require_relative 'generic' + +module Bundler::URI + + # + # RFC6068, the mailto URL scheme. + # + class MailTo < Generic + include REGEXP + + # A Default port of nil for Bundler::URI::MailTo. + DEFAULT_PORT = nil + + # An Array of the available components for Bundler::URI::MailTo. + COMPONENT = [ :scheme, :to, :headers ].freeze + + # :stopdoc: + # "hname" and "hvalue" are encodings of an RFC 822 header name and + # value, respectively. As with "to", all URL reserved characters must + # be encoded. + # + # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it + # consists of zero or more comma-separated mail addresses, possibly + # including "phrase" and "comment" components. Note that all URL + # reserved characters in "to" must be encoded: in particular, + # parentheses, commas, and the percent sign ("%"), which commonly occur + # in the "mailbox" syntax. + # + # Within mailto URLs, the characters "?", "=", "&" are reserved. + + # ; RFC 6068 + # hfields = "?" hfield *( "&" hfield ) + # hfield = hfname "=" hfvalue + # hfname = *qchar + # hfvalue = *qchar + # qchar = unreserved / pct-encoded / some-delims + # some-delims = "!" / "$" / "'" / "(" / ")" / "*" + # / "+" / "," / ";" / ":" / "@" + # + # ; RFC3986 + # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + # pct-encoded = "%" HEXDIG HEXDIG + HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ + # practical regexp for email address + # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address + EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ + # :startdoc: + + # + # == Description + # + # Creates a new Bundler::URI::MailTo object from components, with syntax checking. + # + # Components can be provided as an Array or Hash. If an Array is used, + # the components must be supplied as [to, headers]. + # + # If a Hash is used, the keys are the component names preceded by colons. + # + # The headers can be supplied as a pre-encoded string, such as + # "subject=subscribe&cc=address", or as an Array of Arrays + # like [['subject', 'subscribe'], ['cc', 'address']]. + # + # Examples: + # + # require 'bundler/vendor/uri/lib/uri' + # + # m1 = Bundler::URI::MailTo.build(['joe@example.com', 'subject=Ruby']) + # m1.to_s # => "mailto:joe@example.com?subject=Ruby" + # + # m2 = Bundler::URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) + # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com" + # + # m3 = Bundler::URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) + # m3.to_s # => "mailto:listman@example.com?subject=subscribe" + # + def self.build(args) + tmp = Util.make_components_hash(self, args) + + case tmp[:to] + when Array + tmp[:opaque] = tmp[:to].join(',') + when String + tmp[:opaque] = tmp[:to].dup + else + tmp[:opaque] = '' + end + + if tmp[:headers] + query = + case tmp[:headers] + when Array + tmp[:headers].collect { |x| + if x.kind_of?(Array) + x[0] + '=' + x[1..-1].join + else + x.to_s + end + }.join('&') + when Hash + tmp[:headers].collect { |h,v| + h + '=' + v + }.join('&') + else + tmp[:headers].to_s + end + unless query.empty? + tmp[:opaque] << '?' << query + end + end + + super(tmp) + end + + # + # == Description + # + # Creates a new Bundler::URI::MailTo object from generic URL components with + # no syntax checking. + # + # This method is usually called from Bundler::URI::parse, which checks + # the validity of each component. + # + def initialize(*arg) + super(*arg) + + @to = nil + @headers = [] + + # The RFC3986 parser does not normally populate opaque + @opaque = "?#{@query}" if @query && !@opaque + + unless @opaque + raise InvalidComponentError, + "missing opaque part for mailto URL" + end + to, header = @opaque.split('?', 2) + # allow semicolon as a addr-spec separator + # http://support.microsoft.com/kb/820868 + unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to + raise InvalidComponentError, + "unrecognised opaque part for mailtoURL: #{@opaque}" + end + + if arg[10] # arg_check + self.to = to + self.headers = header + else + set_to(to) + set_headers(header) + end + end + + # The primary e-mail address of the URL, as a String. + attr_reader :to + + # E-mail headers set by the URL, as an Array of Arrays. + attr_reader :headers + + # Checks the to +v+ component. + def check_to(v) + return true unless v + return true if v.size == 0 + + v.split(/[,;]/).each do |addr| + # check url safety as path-rootless + if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr + raise InvalidComponentError, + "an address in 'to' is invalid as Bundler::URI #{addr.dump}" + end + + # check addr-spec + # don't s/\+/ /g + addr.gsub!(/%\h\h/, Bundler::URI::TBLDECWWWCOMP_) + if EMAIL_REGEXP !~ addr + raise InvalidComponentError, + "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}" + end + end + + true + end + private :check_to + + # Private setter for to +v+. + def set_to(v) + @to = v + end + protected :set_to + + # Setter for to +v+. + def to=(v) + check_to(v) + set_to(v) + v + end + + # Checks the headers +v+ component against either + # * HEADER_REGEXP + def check_headers(v) + return true unless v + return true if v.size == 0 + if HEADER_REGEXP !~ v + raise InvalidComponentError, + "bad component(expected opaque component): #{v}" + end + + true + end + private :check_headers + + # Private setter for headers +v+. + def set_headers(v) + @headers = [] + if v + v.split('&').each do |x| + @headers << x.split(/=/, 2) + end + end + end + protected :set_headers + + # Setter for headers +v+. + def headers=(v) + check_headers(v) + set_headers(v) + v + end + + # Constructs String from Bundler::URI. + def to_s + @scheme + ':' + + if @to + @to + else + '' + end + + if @headers.size > 0 + '?' + @headers.collect{|x| x.join('=')}.join('&') + else + '' + end + + if @fragment + '#' + @fragment + else + '' + end + end + + # Returns the RFC822 e-mail text equivalent of the URL, as a String. + # + # Example: + # + # require 'bundler/vendor/uri/lib/uri' + # + # uri = Bundler::URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") + # uri.to_mailtext + # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" + # + def to_mailtext + to = Bundler::URI.decode_www_form_component(@to) + head = '' + body = '' + @headers.each do |x| + case x[0] + when 'body' + body = Bundler::URI.decode_www_form_component(x[1]) + when 'to' + to << ', ' + Bundler::URI.decode_www_form_component(x[1]) + else + head << Bundler::URI.decode_www_form_component(x[0]).capitalize + ': ' + + Bundler::URI.decode_www_form_component(x[1]) + "\n" + end + end + + "To: #{to} +#{head} +#{body} +" + end + alias to_rfc822text to_mailtext + end + + @@schemes['MAILTO'] = MailTo +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb new file mode 100644 index 0000000000..a0d62ede64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -0,0 +1,546 @@ +# frozen_string_literal: false +#-- +# = uri/common.rb +# +# Author:: Akira Yamada +# Revision:: $Id$ +# License:: +# You can redistribute it and/or modify it under the same term as Ruby. +# +# See Bundler::URI for general documentation +# + +module Bundler::URI + # + # Includes Bundler::URI::REGEXP::PATTERN + # + module RFC2396_REGEXP + # + # Patterns used to parse Bundler::URI's + # + module PATTERN + # :stopdoc: + + # RFC 2396 (Bundler::URI Generic Syntax) + # RFC 2732 (IPv6 Literal Addresses in URL's) + # RFC 2373 (IPv6 Addressing Architecture) + + # alpha = lowalpha | upalpha + ALPHA = "a-zA-Z" + # alphanum = alpha | digit + ALNUM = "#{ALPHA}\\d" + + # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | + # "a" | "b" | "c" | "d" | "e" | "f" + HEX = "a-fA-F\\d" + # escaped = "%" hex hex + ESCAPED = "%[#{HEX}]{2}" + # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | + # "(" | ")" + # unreserved = alphanum | mark + UNRESERVED = "\\-_.!~*'()#{ALNUM}" + # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + # "$" | "," + # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + # "$" | "," | "[" | "]" (RFC 2732) + RESERVED = ";/?:@&=+$,\\[\\]" + + # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)" + # toplabel = alpha | alpha *( alphanum | "-" ) alphanum + TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)" + # hostname = *( domainlabel "." ) toplabel [ "." ] + HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?" + + # :startdoc: + end # PATTERN + + # :startdoc: + end # REGEXP + + # Class that parses String's into Bundler::URI's. + # + # It contains a Hash set of patterns and Regexp's that match and validate. + # + class RFC2396_Parser + include RFC2396_REGEXP + + # + # == Synopsis + # + # Bundler::URI::Parser.new([opts]) + # + # == Args + # + # The constructor accepts a hash as options for parser. + # Keys of options are pattern names of Bundler::URI components + # and values of options are pattern strings. + # The constructor generates set of regexps for parsing URIs. + # + # You can use the following keys: + # + # * :ESCAPED (Bundler::URI::PATTERN::ESCAPED in default) + # * :UNRESERVED (Bundler::URI::PATTERN::UNRESERVED in default) + # * :DOMLABEL (Bundler::URI::PATTERN::DOMLABEL in default) + # * :TOPLABEL (Bundler::URI::PATTERN::TOPLABEL in default) + # * :HOSTNAME (Bundler::URI::PATTERN::HOSTNAME in default) + # + # == Examples + # + # p = Bundler::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # u = p.parse("http://example.jp/%uABCD") #=> # + # Bundler::URI.parse(u.to_s) #=> raises Bundler::URI::InvalidURIError + # + # s = "http://example.com/ABCD" + # u1 = p.parse(s) #=> # + # u2 = Bundler::URI.parse(s) #=> # + # u1 == u2 #=> true + # u1.eql?(u2) #=> false + # + def initialize(opts = {}) + @pattern = initialize_pattern(opts) + @pattern.each_value(&:freeze) + @pattern.freeze + + @regexp = initialize_regexp(@pattern) + @regexp.each_value(&:freeze) + @regexp.freeze + end + + # The Hash of patterns. + # + # See also Bundler::URI::Parser.initialize_pattern. + attr_reader :pattern + + # The Hash of Regexp. + # + # See also Bundler::URI::Parser.initialize_regexp. + attr_reader :regexp + + # Returns a split Bundler::URI against regexp[:ABS_URI]. + def split(uri) + case uri + when '' + # null uri + + when @regexp[:ABS_URI] + scheme, opaque, userinfo, host, port, + registry, path, query, fragment = $~[1..-1] + + # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + + # absoluteURI = scheme ":" ( hier_part | opaque_part ) + # hier_part = ( net_path | abs_path ) [ "?" query ] + # opaque_part = uric_no_slash *uric + + # abs_path = "/" path_segments + # net_path = "//" authority [ abs_path ] + + # authority = server | reg_name + # server = [ [ userinfo "@" ] hostport ] + + if !scheme + raise InvalidURIError, + "bad Bundler::URI(absolute but no scheme): #{uri}" + end + if !opaque && (!path && (!host && !registry)) + raise InvalidURIError, + "bad Bundler::URI(absolute but no path): #{uri}" + end + + when @regexp[:REL_URI] + scheme = nil + opaque = nil + + userinfo, host, port, registry, + rel_segment, abs_path, query, fragment = $~[1..-1] + if rel_segment && abs_path + path = rel_segment + abs_path + elsif rel_segment + path = rel_segment + elsif abs_path + path = abs_path + end + + # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + + # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] + + # net_path = "//" authority [ abs_path ] + # abs_path = "/" path_segments + # rel_path = rel_segment [ abs_path ] + + # authority = server | reg_name + # server = [ [ userinfo "@" ] hostport ] + + else + raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri}" + end + + path = '' if !path && !opaque # (see RFC2396 Section 5.2) + ret = [ + scheme, + userinfo, host, port, # X + registry, # X + path, # Y + opaque, # Y + query, + fragment + ] + return ret + end + + # + # == Args + # + # +uri+:: + # String + # + # == Description + # + # Parses +uri+ and constructs either matching Bundler::URI scheme object + # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or Bundler::URI::Generic. + # + # == Usage + # + # p = Bundler::URI::Parser.new + # p.parse("ldap://ldap.example.com/dc=example?user=john") + # #=> # + # + def parse(uri) + scheme, userinfo, host, port, + registry, path, opaque, query, fragment = self.split(uri) + + if scheme && Bundler::URI.scheme_list.include?(scheme.upcase) + Bundler::URI.scheme_list[scheme.upcase].new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment, self) + else + Generic.new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment, self) + end + end + + + # + # == Args + # + # +uris+:: + # an Array of Strings + # + # == Description + # + # Attempts to parse and merge a set of URIs. + # + def join(*uris) + uris[0] = convert_to_uri(uris[0]) + uris.inject :merge + end + + # + # :call-seq: + # extract( str ) + # extract( str, schemes ) + # extract( str, schemes ) {|item| block } + # + # == Args + # + # +str+:: + # String to search + # +schemes+:: + # Patterns to apply to +str+ + # + # == Description + # + # Attempts to parse and merge a set of URIs. + # If no +block+ given, then returns the result, + # else it calls +block+ for each element in result. + # + # See also Bundler::URI::Parser.make_regexp. + # + def extract(str, schemes = nil) + if block_given? + str.scan(make_regexp(schemes)) { yield $& } + nil + else + result = [] + str.scan(make_regexp(schemes)) { result.push $& } + result + end + end + + # Returns Regexp that is default self.regexp[:ABS_URI_REF], + # unless +schemes+ is provided. Then it is a Regexp.union with self.pattern[:X_ABS_URI]. + def make_regexp(schemes = nil) + unless schemes + @regexp[:ABS_URI_REF] + else + /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + end + end + + # + # :call-seq: + # escape( str ) + # escape( str, unsafe ) + # + # == Args + # + # +str+:: + # String to make safe + # +unsafe+:: + # Regexp to apply. Defaults to self.regexp[:UNSAFE] + # + # == Description + # + # Constructs a safe String from +str+, removing unsafe characters, + # replacing them with codes. + # + def escape(str, unsafe = @regexp[:UNSAFE]) + unless unsafe.kind_of?(Regexp) + # perhaps unsafe is String object + unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false) + end + str.gsub(unsafe) do + us = $& + tmp = '' + us.each_byte do |uc| + tmp << sprintf('%%%02X', uc) + end + tmp + end.force_encoding(Encoding::US_ASCII) + end + + # + # :call-seq: + # unescape( str ) + # unescape( str, escaped ) + # + # == Args + # + # +str+:: + # String to remove escapes from + # +escaped+:: + # Regexp to apply. Defaults to self.regexp[:ESCAPED] + # + # == Description + # + # Removes escapes from +str+. + # + def unescape(str, escaped = @regexp[:ESCAPED]) + enc = str.encoding + enc = Encoding::UTF_8 if enc == Encoding::US_ASCII + str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } + end + + @@to_s = Kernel.instance_method(:to_s) + def inspect + @@to_s.bind_call(self) + end + + private + + # Constructs the default Hash of patterns. + def initialize_pattern(opts = {}) + ret = {} + ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED) + ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED + ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED + ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL + ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL + ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME) + + # RFC 2396 (Bundler::URI Generic Syntax) + # RFC 2732 (IPv6 Literal Addresses in URL's) + # RFC 2373 (IPv6 Addressing Architecture) + + # uric = reserved | unreserved | escaped + ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})" + # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | + # "&" | "=" | "+" | "$" | "," + ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})" + # query = *uric + ret[:QUERY] = query = "#{uric}*" + # fragment = *uric + ret[:FRAGMENT] = fragment = "#{uric}*" + + # hostname = *( domainlabel "." ) toplabel [ "." ] + # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986 + unless hostname + ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+" + end + + # RFC 2373, APPENDIX B: + # IPv6address = hexpart [ ":" IPv4address ] + # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT + # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] + # hexseq = hex4 *( ":" hex4) + # hex4 = 1*4HEXDIG + # + # XXX: This definition has a flaw. "::" + IPv4address must be + # allowed too. Here is a replacement. + # + # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT + ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" + # hex4 = 1*4HEXDIG + hex4 = "[#{PATTERN::HEX}]{1,4}" + # lastpart = hex4 | IPv4address + lastpart = "(?:#{hex4}|#{ipv4addr})" + # hexseq1 = *( hex4 ":" ) hex4 + hexseq1 = "(?:#{hex4}:)*#{hex4}" + # hexseq2 = *( hex4 ":" ) lastpart + hexseq2 = "(?:#{hex4}:)*#{lastpart}" + # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ] + ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)" + + # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT + # unused + + # ipv6reference = "[" IPv6address "]" (RFC 2732) + ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]" + + # host = hostname | IPv4address + # host = hostname | IPv4address | IPv6reference (RFC 2732) + ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})" + # port = *digit + ret[:PORT] = port = '\d*' + # hostport = host [ ":" port ] + ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?" + + # userinfo = *( unreserved | escaped | + # ";" | ":" | "&" | "=" | "+" | "$" | "," ) + ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*" + + # pchar = unreserved | escaped | + # ":" | "@" | "&" | "=" | "+" | "$" | "," + pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})" + # param = *pchar + param = "#{pchar}*" + # segment = *pchar *( ";" param ) + segment = "#{pchar}*(?:;#{param})*" + # path_segments = segment *( "/" segment ) + ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*" + + # server = [ [ userinfo "@" ] hostport ] + server = "(?:#{userinfo}@)?#{hostport}" + # reg_name = 1*( unreserved | escaped | "$" | "," | + # ";" | ":" | "@" | "&" | "=" | "+" ) + ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+" + # authority = server | reg_name + authority = "(?:#{server}|#{reg_name})" + + # rel_segment = 1*( unreserved | escaped | + # ";" | "@" | "&" | "=" | "+" | "$" | "," ) + ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+" + + # scheme = alpha *( alpha | digit | "+" | "-" | "." ) + ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*" + + # abs_path = "/" path_segments + ret[:ABS_PATH] = abs_path = "/#{path_segments}" + # rel_path = rel_segment [ abs_path ] + ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?" + # net_path = "//" authority [ abs_path ] + ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?" + + # hier_part = ( net_path | abs_path ) [ "?" query ] + ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?" + # opaque_part = uric_no_slash *uric + ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*" + + # absoluteURI = scheme ":" ( hier_part | opaque_part ) + ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})" + # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] + ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?" + + # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?" + + ret[:X_ABS_URI] = " + (#{scheme}): (?# 1: scheme) + (?: + (#{opaque_part}) (?# 2: opaque) + | + (?:(?: + //(?: + (?:(?:(#{userinfo})@)? (?# 3: userinfo) + (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port) + | + (#{reg_name}) (?# 6: registry) + ) + | + (?!//)) (?# XXX: '//' is the mark for hostport) + (#{abs_path})? (?# 7: path) + )(?:\\?(#{query}))? (?# 8: query) + ) + (?:\\#(#{fragment}))? (?# 9: fragment) + " + + ret[:X_REL_URI] = " + (?: + (?: + // + (?: + (?:(#{userinfo})@)? (?# 1: userinfo) + (#{host})?(?::(\\d*))? (?# 2: host, 3: port) + | + (#{reg_name}) (?# 4: registry) + ) + ) + | + (#{rel_segment}) (?# 5: rel_segment) + )? + (#{abs_path})? (?# 6: abs_path) + (?:\\?(#{query}))? (?# 7: query) + (?:\\#(#{fragment}))? (?# 8: fragment) + " + + ret + end + + # Constructs the default Hash of Regexp's. + def initialize_regexp(pattern) + ret = {} + + # for Bundler::URI::split + ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) + ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) + + # for Bundler::URI::extract + ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) + ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED) + ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED) + + # for Bundler::URI::escape/unescape + ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED]) + ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]") + + # for Generic#initialize + ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z") + ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z") + ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z") + ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z") + ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z") + ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z") + ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z") + ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z") + ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z") + ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z") + + ret + end + + def convert_to_uri(uri) + if uri.is_a?(Bundler::URI::Generic) + uri + elsif uri = String.try_convert(uri) + parse(uri) + else + raise ArgumentError, + "bad argument (expected Bundler::URI object or Bundler::URI string)" + end + end + + end # class Parser +end # module Bundler::URI diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb new file mode 100644 index 0000000000..07ef4391c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: false +module Bundler::URI + class RFC3986_Parser # :nodoc: + # Bundler::URI defined in RFC3986 + # this regexp is modified not to host is not empty string + RFC3986_URI = /\A(?(?[A-Za-z][+\-.0-9A-Za-z]*):(?\/\/(?(?:(?(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?(?\[(?:(?(?:\h{1,4}:){6}(?\h{1,4}:\h{1,4}|(?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g\.\g\.\g))|::(?:\h{1,4}:){5}\g|\h{1,4}?::(?:\h{1,4}:){4}\g|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g|(?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?\d*))?)(?(?:\/(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?\/(?:(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g)*)?)|(?\g(?:\/\g)*)|(?))(?:\?(?[^#]*))?(?:\#(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + RFC3986_relative_ref = /\A(?(?\/\/(?(?:(?(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?(?\[(?(?:\h{1,4}:){6}(?\h{1,4}:\h{1,4}|(?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g\.\g\.\g))|::(?:\h{1,4}:){5}\g|\h{1,4}?::(?:\h{1,4}:){4}\g|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g|(?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?\d*))?)(?(?:\/(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?\/(?:(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g)*)?)|(?(?(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g)*)|(?))(?:\?(?[^#]*))?(?:\#(?(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + attr_reader :regexp + + def initialize + @regexp = default_regexp.each_value(&:freeze).freeze + end + + def split(uri) #:nodoc: + begin + uri = uri.to_str + rescue NoMethodError + raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri.inspect}" + end + uri.ascii_only? or + raise InvalidURIError, "Bundler::URI must be ascii only #{uri.dump}" + if m = RFC3986_URI.match(uri) + query = m["query".freeze] + scheme = m["scheme".freeze] + opaque = m["path-rootless".freeze] + if opaque + opaque << "?#{query}" if query + [ scheme, + nil, # userinfo + nil, # host + nil, # port + nil, # registry + nil, # path + opaque, + nil, # query + m["fragment".freeze] + ] + else # normal + [ scheme, + m["userinfo".freeze], + m["host".freeze], + m["port".freeze], + nil, # registry + (m["path-abempty".freeze] || + m["path-absolute".freeze] || + m["path-empty".freeze]), + nil, # opaque + query, + m["fragment".freeze] + ] + end + elsif m = RFC3986_relative_ref.match(uri) + [ nil, # scheme + m["userinfo".freeze], + m["host".freeze], + m["port".freeze], + nil, # registry, + (m["path-abempty".freeze] || + m["path-absolute".freeze] || + m["path-noscheme".freeze] || + m["path-empty".freeze]), + nil, # opaque + m["query".freeze], + m["fragment".freeze] + ] + else + raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri.inspect}" + end + end + + def parse(uri) # :nodoc: + scheme, userinfo, host, port, + registry, path, opaque, query, fragment = self.split(uri) + scheme_list = Bundler::URI.scheme_list + if scheme && scheme_list.include?(uc = scheme.upcase) + scheme_list[uc].new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment, self) + else + Generic.new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment, self) + end + end + + + def join(*uris) # :nodoc: + uris[0] = convert_to_uri(uris[0]) + uris.inject :merge + end + + @@to_s = Kernel.instance_method(:to_s) + def inspect + @@to_s.bind_call(self) + end + + private + + def default_regexp # :nodoc: + { + SCHEME: /\A[A-Za-z][A-Za-z0-9+\-.]*\z/, + USERINFO: /\A(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*\z/, + HOST: /\A(?:(?\[(?:(?(?:\h{1,4}:){6}(?\h{1,4}:\h{1,4}|(?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g\.\g\.\g))|::(?:\h{1,4}:){5}\g|\h{,4}::(?:\h{1,4}:){4}\g|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g|(?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*))\z/, + ABS_PATH: /\A\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/, + REL_PATH: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/, + QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, + FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, + OPAQUE: /\A(?:[^\/].*)?\z/, + PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/, + } + end + + def convert_to_uri(uri) + if uri.is_a?(Bundler::URI::Generic) + uri + elsif uri = String.try_convert(uri) + parse(uri) + else + raise ArgumentError, + "bad argument (expected Bundler::URI object or Bundler::URI string)" + end + end + + end # class Parser +end # module Bundler::URI diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/version.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/version.rb new file mode 100644 index 0000000000..56177ef194 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendor/uri/lib/uri/version.rb @@ -0,0 +1,6 @@ +module Bundler::URI + # :stopdoc: + VERSION_CODE = '001000'.freeze + VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_fileutils.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_fileutils.rb new file mode 100644 index 0000000000..1be1138ce2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_fileutils.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/fileutils/lib/fileutils" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_molinillo.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_molinillo.rb new file mode 100644 index 0000000000..d1976f5cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_molinillo.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/molinillo/lib/molinillo" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_persistent.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_persistent.rb new file mode 100644 index 0000000000..dc9573e025 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_persistent.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Bundler + module Persistent + module Net + module HTTP + end + end + end +end +require_relative "vendor/net-http-persistent/lib/net/http/persistent" + +module Bundler + class PersistentHTTP < Persistent::Net::HTTP::Persistent + def connection_for(uri) + super(uri) do |connection| + result = yield connection + warn_old_tls_version_rubygems_connection(uri, connection) + result + end + end + + def warn_old_tls_version_rubygems_connection(uri, connection) + return unless connection.http.use_ssl? + return unless (uri.host || "").end_with?("rubygems.org") + + socket = connection.instance_variable_get(:@socket) + return unless socket + socket_io = socket.io + return unless socket_io.respond_to?(:ssl_version) + ssl_version = socket_io.ssl_version + + case ssl_version + when /TLSv([\d\.]+)/ + version = Gem::Version.new($1) + if version < Gem::Version.new("1.2") + Bundler.ui.warn \ + "Warning: Your Ruby version is compiled against a copy of OpenSSL that is very old. " \ + "Starting in January 2018, RubyGems.org will refuse connection requests from these " \ + "very old versions of OpenSSL. If you will need to continue installing gems after " \ + "January 2018, please follow this guide to upgrade: http://ruby.to/tls-outdated.", + :wrap => true + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_thor.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_thor.rb new file mode 100644 index 0000000000..0666cfc9b9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_thor.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Bundler + def self.require_thor_actions + require_relative "vendor/thor/lib/thor/actions" + end +end +require_relative "vendor/thor/lib/thor" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_tmpdir.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_tmpdir.rb new file mode 100644 index 0000000000..43b4fa75fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_tmpdir.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/tmpdir/lib/tmpdir" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_uri.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_uri.rb new file mode 100644 index 0000000000..905e8158e8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vendored_uri.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/uri/lib/uri" diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version.rb new file mode 100644 index 0000000000..f6851e35d3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: false + +module Bundler + VERSION = "2.2.23".freeze + + def self.bundler_major_version + @bundler_major_version ||= VERSION.split(".").first.to_i + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version_ranges.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version_ranges.rb new file mode 100644 index 0000000000..12a956d6a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/version_ranges.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module Bundler + module VersionRanges + NEq = Struct.new(:version) + ReqR = Struct.new(:left, :right) + class ReqR + Endpoint = Struct.new(:version, :inclusive) do + def <=>(other) + if version.equal?(INFINITY) + return 0 if other.version.equal?(INFINITY) + return 1 + elsif other.version.equal?(INFINITY) + return -1 + end + + comp = version <=> other.version + return comp unless comp.zero? + + if inclusive && !other.inclusive + 1 + elsif !inclusive && other.inclusive + -1 + else + 0 + end + end + end + + def to_s + "#{left.inclusive ? "[" : "("}#{left.version}, #{right.version}#{right.inclusive ? "]" : ")"}" + end + INFINITY = begin + inf = Object.new + def inf.to_s + "∞" + end + def inf.<=>(other) + return 0 if other.equal?(self) + 1 + end + inf.freeze + end + ZERO = Gem::Version.new("0.a") + + def cover?(v) + return false if left.inclusive && left.version > v + return false if !left.inclusive && left.version >= v + + if right.version != INFINITY + return false if right.inclusive && right.version < v + return false if !right.inclusive && right.version <= v + end + + true + end + + def empty? + left.version == right.version && !(left.inclusive && right.inclusive) + end + + def single? + left.version == right.version + end + + def <=>(other) + return -1 if other.equal?(INFINITY) + + comp = left <=> other.left + return comp unless comp.zero? + + right <=> other.right + end + + UNIVERSAL = ReqR.new(ReqR::Endpoint.new(Gem::Version.new("0.a"), true), ReqR::Endpoint.new(ReqR::INFINITY, false)).freeze + end + + def self.for_many(requirements) + requirements = requirements.map(&:requirements).flatten(1).map {|r| r.join(" ") } + requirements << ">= 0.a" if requirements.empty? + requirement = Gem::Requirement.new(requirements) + self.for(requirement) + end + + def self.for(requirement) + ranges = requirement.requirements.map do |op, v| + case op + when "=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v, true)) + when "!=" then NEq.new(v) + when ">=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(ReqR::INFINITY, false)) + when ">" then ReqR.new(ReqR::Endpoint.new(v, false), ReqR::Endpoint.new(ReqR::INFINITY, false)) + when "<" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, false)) + when "<=" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, true)) + when "~>" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v.bump, false)) + else raise "unknown version op #{op} in requirement #{requirement}" + end + end.uniq + ranges, neqs = ranges.partition {|r| !r.is_a?(NEq) } + + [ranges.sort, neqs.map(&:version)] + end + + def self.empty?(ranges, neqs) + !ranges.reduce(ReqR::UNIVERSAL) do |last_range, curr_range| + next false unless last_range + next false if curr_range.single? && neqs.include?(curr_range.left.version) + next curr_range if last_range.right.version == ReqR::INFINITY + case last_range.right.version <=> curr_range.left.version + # higher + when 1 then next ReqR.new(curr_range.left, last_range.right) + # equal + when 0 + if last_range.right.inclusive && curr_range.left.inclusive && !neqs.include?(curr_range.left.version) + ReqR.new(curr_range.left, [curr_range.right, last_range.right].max) + end + # lower + when -1 then next false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vlad.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vlad.rb new file mode 100644 index 0000000000..538e8c3e74 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/vlad.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "shared_helpers" +Bundler::SharedHelpers.major_deprecation 2, + "The Bundler task for Vlad" + +# Vlad task for Bundler. +# +# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and +# include the vlad:bundle:install task in your vlad:deploy task. +require_relative "deployment" + +include Rake::DSL if defined? Rake::DSL + +namespace :vlad do + Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, :roles => :app) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/worker.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/worker.rb new file mode 100644 index 0000000000..10139ed25b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/worker.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module Bundler + class Worker + POISON = Object.new + + class WrappedException < StandardError + attr_reader :exception + def initialize(exn) + @exception = exn + end + end + + # @return [String] the name of the worker + attr_reader :name + + # Creates a worker pool of specified size + # + # @param size [Integer] Size of pool + # @param name [String] name the name of the worker + # @param func [Proc] job to run in inside the worker pool + def initialize(size, name, func) + @name = name + @request_queue = Queue.new + @response_queue = Queue.new + @func = func + @size = size + @threads = nil + SharedHelpers.trap("INT") { abort_threads } + end + + # Enqueue a request to be executed in the worker pool + # + # @param obj [String] mostly it is name of spec that should be downloaded + def enq(obj) + create_threads unless @threads + @request_queue.enq obj + end + + # Retrieves results of job function being executed in worker pool + def deq + result = @response_queue.deq + raise result.exception if result.is_a?(WrappedException) + result + end + + def stop + stop_threads + end + + private + + def process_queue(i) + loop do + obj = @request_queue.deq + break if obj.equal? POISON + @response_queue.enq apply_func(obj, i) + end + end + + def apply_func(obj, i) + @func.call(obj, i) + rescue Exception => e # rubocop:disable Lint/RescueException + WrappedException.new(e) + end + + # Stop the worker threads by sending a poison object down the request queue + # so as worker threads after retrieving it, shut themselves down + def stop_threads + return unless @threads + @threads.each { @request_queue.enq POISON } + @threads.each(&:join) + @threads = nil + end + + def abort_threads + return unless @threads + Bundler.ui.debug("\n#{caller.join("\n")}") + @threads.each(&:exit) + exit 1 + end + + def create_threads + creation_errors = [] + + @threads = Array.new(@size) do |i| + begin + Thread.start { process_queue(i) }.tap do |thread| + thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) + end + rescue ThreadError => e + creation_errors << e + nil + end + end.compact + + return if creation_errors.empty? + + message = "Failed to create threads for the #{name} worker: #{creation_errors.map(&:to_s).uniq.join(", ")}" + raise ThreadCreationError, message if @threads.empty? + Bundler.ui.info message + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/yaml_serializer.rb b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/yaml_serializer.rb new file mode 100644 index 0000000000..d5ecbd4aef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/bundler-2.2.23/lib/bundler/yaml_serializer.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module Bundler + # A stub yaml serializer that can handle only hashes and strings (as of now). + module YAMLSerializer + module_function + + def dump(hash) + yaml = String.new("---") + yaml << dump_hash(hash) + end + + def dump_hash(hash) + yaml = String.new("\n") + hash.each do |k, v| + yaml << k << ":" + if v.is_a?(Hash) + yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines + elsif v.is_a?(Array) # Expected to be array of strings + yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n" + else + yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n" + end + end + yaml + end + + ARRAY_REGEX = / + ^ + (?:[ ]*-[ ]) # '- ' before array items + (['"]?) # optional opening quote + (.*) # value + \1 # matching closing quote + $ + /xo.freeze + + HASH_REGEX = / + ^ + ([ ]*) # indentations + (.+) # key + (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value) + [ ]? + (['"]?) # optional opening quote + (.*) # value + \3 # matching closing quote + $ + /xo.freeze + + def load(str) + res = {} + stack = [res] + last_hash = nil + last_empty_key = nil + str.split(/\r?\n/).each do |line| + if match = HASH_REGEX.match(line) + indent, key, quote, val = match.captures + key = convert_to_backward_compatible_key(key) + depth = indent.scan(/ /).length + if quote.empty? && val.empty? + new_hash = {} + stack[depth][key] = new_hash + stack[depth + 1] = new_hash + last_empty_key = key + last_hash = stack[depth] + else + stack[depth][key] = val + end + elsif match = ARRAY_REGEX.match(line) + _, val = match.captures + last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array) + + last_hash[last_empty_key].push(val) + end + end + res + end + + # for settings' keys + def convert_to_backward_compatible_key(key) + key = "#{key}/" if key =~ /https?:/i && key !~ %r{/\Z} + key = key.gsub(".", "__") if key.include?(".") + key + end + + class << self + private :dump_hash, :convert_to_backward_compatible_key + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/MIT-LICENSE new file mode 100644 index 0000000000..d8d009d1c6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/MIT-LICENSE @@ -0,0 +1,22 @@ +Copyright (C) 2005-2012 Kornelius Kalnbach (@murphy_karasu) + +http://coderay.rubychan.de/ + +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.0.0/gems/coderay-1.1.3/README_INDEX.rdoc b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/README_INDEX.rdoc new file mode 100644 index 0000000000..7332653c6a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/README_INDEX.rdoc @@ -0,0 +1,123 @@ += CodeRay + +Tired of blue'n'gray? Try the original version of this documentation on +coderay.rubychan.de[http://coderay.rubychan.de/doc/] :-) + +== About + +CodeRay is a Ruby library for syntax highlighting. + +You put your code in, and you get it back colored; Keywords, strings, +floats, comments - all in different colors. And with line numbers. + +*Syntax* *Highlighting*... +* makes code easier to read and maintain +* lets you detect syntax errors faster +* helps you to understand the syntax of a language +* looks nice +* is what everybody wants to have on their website +* solves all your problems and makes the girls run after you + + +== Installation + + % gem install coderay + + +=== Dependencies + +CodeRay needs Ruby 1.8.7+ or 1.9.2+. It also runs on Rubinius and JRuby. + + +== Example Usage + + require 'coderay' + + html = CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table) + + +== Documentation + +See CodeRay. + + +== Credits + +=== Special Thanks to + +* licenser (Heinz N. Gies) for ending my QBasic career, inventing the Coder + project and the input/output plugin system. + CodeRay would not exist without him. +* bovi (Daniel Bovensiepen) for helping me out on various occasions. + +=== Thanks to + +* Caleb Clausen for writing RubyLexer (see + http://rubyforge.org/projects/rubylexer) and lots of very interesting mail + traffic +* birkenfeld (Georg Brandl) and mitsuhiku (Arnim Ronacher) for PyKleur, now pygments. + You guys rock! +* Jamis Buck for writing Syntax (see http://rubyforge.org/projects/syntax) + I got some useful ideas from it. +* Doug Kearns and everyone else who worked on ruby.vim - it not only helped me + coding CodeRay, but also gave me a wonderful target to reach for the Ruby + scanner. +* everyone who uses CodeBB on http://www.rubyforen.de and http://www.python-forum.de +* iGEL, magichisoka, manveru, WoNáDo and everyone I forgot from rubyforen.de +* Dethix from ruby-mine.de +* zickzackw +* Dookie (who is no longer with us...) and Leonidas from http://www.python-forum.de +* Andreas Schwarz for finding out that CaseIgnoringWordList was not case + ignoring! Such things really make you write tests. +* closure for the first version of the Scheme scanner. +* Stefan Walk for the first version of the JavaScript and PHP scanners. +* Josh Goebel for another version of the JavaScript scanner, a SQL and a Diff scanner. +* Jonathan Younger for pointing out the licence confusion caused by wrong LICENSE file. +* Jeremy Hinegardner for finding the shebang-on-empty-file bug in FileType. +* Charles Oliver Nutter and Yehuda Katz for helping me benchmark CodeRay on JRuby. +* Andreas Neuhaus for pointing out a markup bug in coderay/for_redcloth. +* 0xf30fc7 for the FileType patch concerning Delphi file extensions. +* The folks at redmine.org - thank you for using and fixing CodeRay! +* Keith Pitt for his SQL scanners +* Rob Aldred for the terminal encoder +* Trans for pointing out $DEBUG dependencies +* Flameeyes for finding that Term::ANSIColor was obsolete +* matz and all Ruby gods and gurus +* The inventors of: the computer, the internet, the true color display, HTML & + CSS, VIM, Ruby, pizza, microwaves, guitars, scouting, programming, anime, + manga, coke and green ice tea. + +Where would we be without all those people? + +=== Created using + +* Ruby[http://ruby-lang.org/] +* Chihiro (my Sony VAIO laptop); Henrietta (my old MacBook); + Triella, born Rico (my new MacBook); as well as + Seras and Hikari (my PCs) +* RDE[http://homepage2.nifty.com/sakazuki/rde_e.html], + VIM[http://vim.org] and TextMate[http://macromates.com] +* Subversion[http://subversion.tigris.org/] +* Redmine[http://redmine.org/] +* Firefox[http://www.mozilla.org/products/firefox/], + Firebug[http://getfirebug.com/], Safari[http://www.apple.com/safari/], and + Thunderbird[http://www.mozilla.org/products/thunderbird/] +* RubyGems[http://docs.rubygems.org/] and Rake[http://rake.rubyforge.org/] +* TortoiseSVN[http://tortoisesvn.tigris.org/] using Apache via + XAMPP[http://www.apachefriends.org/en/xampp.html] +* RDoc (though I'm quite unsatisfied with it) +* Microsoft Windows (yes, I confess!) and MacOS X +* GNUWin32, MinGW and some other tools to make the shell under windows a bit + less useless +* Term::ANSIColor[http://term-ansicolor.rubyforge.org/] +* PLEAC[http://pleac.sourceforge.net/] code examples +* Github +* Travis CI (http://travis-ci.org/rubychan/github) + +=== Free + +* As you can see, CodeRay was created under heavy use of *free* software. +* So CodeRay is also *free*. +* If you use CodeRay to create software, think about making this software + *free*, too. +* Thanks :) diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/bin/coderay b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/bin/coderay new file mode 100755 index 0000000000..130a50ba91 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/bin/coderay @@ -0,0 +1,215 @@ +#!/usr/bin/env ruby +require 'coderay' + +$options, args = ARGV.partition { |arg| arg[/^-[hv]$|--\w+/] } +subcommand = args.first if /^\w/ === args.first +subcommand = nil if subcommand && File.exist?(subcommand) +args.delete subcommand + +def option? *options + !($options & options).empty? +end + +def tty? + $stdout.tty? || option?('--tty') +end + +def version + puts <<-USAGE +CodeRay #{CodeRay::VERSION} + USAGE +end + +def help + puts <<-HELP +This is CodeRay #{CodeRay::VERSION}, a syntax highlighting tool for selected languages. + +usage: + coderay [-language] [input] [-format] [output] + +defaults: + language detect from input file name or shebang; fall back to plain text + input STDIN + format detect from output file name or use terminal; fall back to HTML + output STDOUT + +common: + coderay file.rb # highlight file to terminal + coderay file.rb -page > file.html # highlight file to HTML page + coderay file.rb -div > file.html # highlight file to HTML snippet + +configure output: + coderay file.py output.json # output tokens as JSON + coderay file.py -loc # count lines of code in Python file + +configure input: + coderay -python file # specify the input language + coderay -ruby # take input from STDIN + +more: + coderay stylesheet [style] # print CSS stylesheet + HELP +end + +def commands + puts <<-COMMANDS + general: + highlight code highlighting (default command, optional) + stylesheet print the CSS stylesheet with the given name (aliases: style, css) + + about: + list [of] list all available plugins (or just the scanners|encoders|styles|filetypes) + commands print this list + help show some help + version print CodeRay version + COMMANDS +end + +def print_list_of plugin_host + plugins = plugin_host.all_plugins.map do |plugin| + info = " #{plugin.plugin_id}: #{plugin.title}" + + aliases = (plugin.aliases - [:default]).map { |key| "-#{key}" }.sort_by { |key| key.size } + if plugin.respond_to?(:file_extension) || !aliases.empty? + additional_info = [] + additional_info << aliases.join(', ') unless aliases.empty? + info << " (#{additional_info.join('; ')})" + end + + info << ' <-- default' if plugin.aliases.include? :default + + info + end + puts plugins.sort +end + +if option? '-v', '--version' + version +end + +if option? '-h', '--help' + help +end + +case subcommand +when 'highlight', nil + if ARGV.empty? + version + help + else + signature = args.map { |arg| arg[/^-/] ? '-' : 'f' }.join + names = args.map { |arg| arg.sub(/^-/, '') } + case signature + when /^$/ + exit + when /^ff?$/ + input_file, output_file, = *names + when /^f-f?$/ + input_file, output_format, output_file, = *names + when /^-ff?$/ + input_lang, input_file, output_file, = *names + when /^-f-f?$/ + input_lang, input_file, output_format, output_file, = *names + when /^--?f?$/ + input_lang, output_format, output_file, = *names + else + $stdout = $stderr + help + puts + puts "Unknown parameter order: #{args.join ' '}, expected: [-language] [input] [-format] [output]" + exit 1 + end + + if input_file + input_lang ||= CodeRay::FileType.fetch input_file, :text, true + end + + if output_file + output_format ||= CodeRay::FileType[output_file] || :plain + else + output_format ||= :terminal + end + + output_format = :page if output_format.to_s == 'html' + + if input_file + input = File.read input_file + else + input = $stdin.read + end + + begin + file = + if output_file + File.open output_file, 'w' + else + $stdout + end + CodeRay.encode(input, input_lang, output_format, :out => file) + file.puts + rescue CodeRay::PluginHost::PluginNotFound => boom + $stdout = $stderr + if boom.message[/CodeRay::(\w+)s could not load plugin :?(.*?): /] + puts "I don't know the #$1 \"#$2\"." + else + puts boom.message + end + # puts "I don't know this plugin: #{boom.message[/Could not load plugin (.*?): /, 1]}." + rescue CodeRay::Scanners::Scanner::ScanError + # this is sometimes raised by pagers; ignore + # FIXME: rescue Errno::EPIPE + ensure + file.close if output_file + end + end +when 'li', 'list' + arg = args.first && args.first.downcase + if [nil, 's', 'sc', 'scanner', 'scanners'].include? arg + puts 'input languages (Scanners):' + print_list_of CodeRay::Scanners + end + + if [nil, 'e', 'en', 'enc', 'encoder', 'encoders'].include? arg + puts 'output formats (Encoders):' + print_list_of CodeRay::Encoders + end + + if [nil, 'st', 'style', 'styles'].include? arg + puts 'CSS themes for HTML output (Styles):' + print_list_of CodeRay::Styles + end + + if [nil, 'f', 'ft', 'file', 'filetype', 'filetypes'].include? arg + puts 'recognized file types:' + + filetypes = Hash.new { |h, k| h[k] = [] } + CodeRay::FileType::TypeFromExt.inject filetypes do |types, (ext, type)| + types[type.to_s] << ".#{ext}" + types + end + CodeRay::FileType::TypeFromName.inject filetypes do |types, (name, type)| + types[type.to_s] << name + types + end + + filetypes.sort.each do |type, exts| + puts " #{type}: #{exts.sort_by { |ext| ext.size }.join(', ')}" + end + end +when 'stylesheet', 'style', 'css' + puts CodeRay::Encoders[:html]::CSS.new(args.first || :default).stylesheet +when 'commands' + commands +when 'help' + help +else + $stdout = $stderr + help + puts + if subcommand[/\A\w+\z/] + puts "Unknown command: #{subcommand}" + else + puts "File not found: #{subcommand}" + end + exit 1 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay.rb new file mode 100644 index 0000000000..c3de20b533 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay.rb @@ -0,0 +1,284 @@ +# encoding: utf-8 +# Encoding.default_internal = 'UTF-8' + +# = CodeRay Library +# +# CodeRay is a Ruby library for syntax highlighting. +# +# I try to make CodeRay easy to use and intuitive, but at the same time fully +# featured, complete, fast and efficient. +# +# See README. +# +# It consists mainly of +# * the main engine: CodeRay (Scanners::Scanner, Tokens, Encoders::Encoder) +# * the plugin system: PluginHost, Plugin +# * the scanners in CodeRay::Scanners +# * the encoders in CodeRay::Encoders +# * the styles in CodeRay::Styles +# +# Here's a fancy graphic to light up this gray docu: +# +# http://cycnus.de/raindark/coderay/scheme.png +# +# == Documentation +# +# See CodeRay, Encoders, Scanners, Tokens. +# +# == Usage +# +# Remember you need RubyGems to use CodeRay, unless you have it in your load +# path. Run Ruby with -rubygems option if required. +# +# === Highlight Ruby code in a string as html +# +# require 'coderay' +# print CodeRay.scan('puts "Hello, world!"', :ruby).html +# +# # prints something like this: +# puts "Hello, world!" +# +# +# === Highlight C code from a file in a html div +# +# require 'coderay' +# print CodeRay.scan(File.read('ruby.h'), :c).div +# print CodeRay.scan_file('ruby.h').html.div +# +# You can include this div in your page. The used CSS styles can be printed with +# +# % coderay_stylesheet +# +# === Highlight without typing too much +# +# If you are one of the hasty (or lazy, or extremely curious) people, just run this file: +# +# % ruby -rubygems /path/to/coderay/coderay.rb > example.html +# +# and look at the file it created in your browser. +# +# = CodeRay Module +# +# The CodeRay module provides convenience methods for the engine. +# +# * The +lang+ and +format+ arguments select Scanner and Encoder to use. These are +# simply lower-case symbols, like :python or :html. +# * All methods take an optional hash as last parameter, +options+, that is send to +# the Encoder / Scanner. +# * Input and language are always sorted in this order: +code+, +lang+. +# (This is in alphabetical order, if you need a mnemonic ;) +# +# You should be able to highlight everything you want just using these methods; +# so there is no need to dive into CodeRay's deep class hierarchy. +# +# The examples in the demo directory demonstrate common cases using this interface. +# +# = Basic Access Ways +# +# Read this to get a general view what CodeRay provides. +# +# == Scanning +# +# Scanning means analysing an input string, splitting it up into Tokens. +# Each Token knows about what type it is: string, comment, class name, etc. +# +# Each +lang+ (language) has its own Scanner; for example, :ruby code is +# handled by CodeRay::Scanners::Ruby. +# +# CodeRay.scan:: Scan a string in a given language into Tokens. +# This is the most common method to use. +# CodeRay.scan_file:: Scan a file and guess the language using FileType. +# +# The Tokens object you get from these methods can encode itself; see Tokens. +# +# == Encoding +# +# Encoding means compiling Tokens into an output. This can be colored HTML or +# LaTeX, a textual statistic or just the number of non-whitespace tokens. +# +# Each Encoder provides output in a specific +format+, so you select Encoders via +# formats like :html or :statistic. +# +# CodeRay.encode:: Scan and encode a string in a given language. +# CodeRay.encode_tokens:: Encode the given tokens. +# CodeRay.encode_file:: Scan a file, guess the language using FileType and encode it. +# +# == All-in-One Encoding +# +# CodeRay.encode:: Highlight a string with a given input and output format. +# +# == Instanciating +# +# You can use an Encoder instance to highlight multiple inputs. This way, the setup +# for this Encoder must only be done once. +# +# CodeRay.encoder:: Create an Encoder instance with format and options. +# CodeRay.scanner:: Create an Scanner instance for lang, with '' as default code. +# +# To make use of CodeRay.scanner, use CodeRay::Scanner::code=. +# +# The scanning methods provide more flexibility; we recommend to use these. +# +# == Reusing Scanners and Encoders +# +# If you want to re-use scanners and encoders (because that is faster), see +# CodeRay::Duo for the most convenient (and recommended) interface. +module CodeRay + + $CODERAY_DEBUG ||= false + + CODERAY_PATH = File.expand_path('../coderay', __FILE__) + + # Assuming the path is a subpath of lib/coderay/ + def self.coderay_path *path + File.join CODERAY_PATH, *path + end + + autoload :VERSION, 'coderay/version' + + # helpers + autoload :FileType, coderay_path('helpers', 'file_type') + + # Tokens + autoload :Tokens, coderay_path('tokens') + autoload :TokensProxy, coderay_path('tokens_proxy') + autoload :TokenKinds, coderay_path('token_kinds') + + # Plugin system + autoload :PluginHost, coderay_path('helpers', 'plugin_host') + autoload :Plugin, coderay_path('helpers', 'plugin') + + # Plugins + autoload :Scanners, coderay_path('scanners') + autoload :Encoders, coderay_path('encoders') + autoload :Styles, coderay_path('styles') + + # convenience access and reusable Encoder/Scanner pair + autoload :Duo, coderay_path('duo') + + class << self + + # Scans the given +code+ (a String) with the Scanner for +lang+. + # + # This is a simple way to use CodeRay. Example: + # require 'coderay' + # page = CodeRay.scan("puts 'Hello, world!'", :ruby).html + # + # See also demo/demo_simple. + def scan code, lang, options = {}, &block + CodeRay::TokensProxy.new code, lang, options, block + end + + # Scans +filename+ (a path to a code file) with the Scanner for +lang+. + # + # If +lang+ is :auto or omitted, the CodeRay::FileType module is used to + # determine it. If it cannot find out what type it is, it uses + # CodeRay::Scanners::Text. + # + # Calls CodeRay.scan. + # + # Example: + # require 'coderay' + # page = CodeRay.scan_file('some_c_code.c').html + def scan_file filename, lang = :auto, options = {}, &block + lang = CodeRay::FileType.fetch filename, :text, true if lang == :auto + code = File.read filename + scan code, lang, options, &block + end + + # Encode a string. + # + # This scans +code+ with the the Scanner for +lang+ and then + # encodes it with the Encoder for +format+. + # +options+ will be passed to the Encoder. + # + # See CodeRay::Encoder.encode. + def encode code, lang, format, options = {} + encoder(format, options).encode code, lang, options + end + + # Encode pre-scanned Tokens. + # Use this together with CodeRay.scan: + # + # require 'coderay' + # + # # Highlight a short Ruby code example in a HTML span + # tokens = CodeRay.scan '1 + 2', :ruby + # puts CodeRay.encode_tokens(tokens, :span) + # + def encode_tokens tokens, format, options = {} + encoder(format, options).encode_tokens tokens, options + end + + # Encodes +filename+ (a path to a code file) with the Scanner for +lang+. + # + # See CodeRay.scan_file. + # Notice that the second argument is the output +format+, not the input language. + # + # Example: + # require 'coderay' + # page = CodeRay.encode_file 'some_c_code.c', :html + def encode_file filename, format, options = {} + tokens = scan_file filename, :auto, get_scanner_options(options) + encode_tokens tokens, format, options + end + + # Highlight a string into a HTML
. + # + # CSS styles use classes, so you have to include a stylesheet + # in your output. + # + # See encode. + def highlight code, lang, options = { :css => :class }, format = :div + encode code, lang, format, options + end + + # Highlight a file into a HTML
. + # + # CSS styles use classes, so you have to include a stylesheet + # in your output. + # + # See encode. + def highlight_file filename, options = { :css => :class }, format = :div + encode_file filename, format, options + end + + # Finds the Encoder class for +format+ and creates an instance, passing + # +options+ to it. + # + # Example: + # require 'coderay' + # + # stats = CodeRay.encoder(:statistic) + # stats.encode("puts 17 + 4\n", :ruby) + # + # puts '%d out of %d tokens have the kind :integer.' % [ + # stats.type_stats[:integer].count, + # stats.real_token_count + # ] + # #-> 2 out of 4 tokens have the kind :integer. + def encoder format, options = {} + CodeRay::Encoders[format].new options + end + + # Finds the Scanner class for +lang+ and creates an instance, passing + # +options+ to it. + # + # See Scanner.new. + def scanner lang, options = {}, &block + CodeRay::Scanners[lang].new '', options, &block + end + + # Extract the options for the scanner from the +options+ hash. + # + # Returns an empty Hash if :scanner_options is not set. + # + # This is used if a method like CodeRay.encode has to provide options + # for Encoder _and_ scanner. + def get_scanner_options options + options.fetch :scanner_options, {} + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/duo.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/duo.rb new file mode 100644 index 0000000000..cb3f8ee825 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/duo.rb @@ -0,0 +1,81 @@ +module CodeRay + + # = Duo + # + # A Duo is a convenient way to use CodeRay. You just create a Duo, + # giving it a lang (language of the input code) and a format (desired + # output format), and call Duo#highlight with the code. + # + # Duo makes it easy to re-use both scanner and encoder for a repetitive + # task. It also provides a very easy interface syntax: + # + # require 'coderay' + # CodeRay::Duo[:python, :div].highlight 'import this' + # + # Until you want to do uncommon things with CodeRay, I recommend to use + # this method, since it takes care of everything. + class Duo + + attr_accessor :lang, :format, :options + + # Create a new Duo, holding a lang and a format to highlight code. + # + # simple: + # CodeRay::Duo[:ruby, :html].highlight 'bla 42' + # + # with options: + # CodeRay::Duo[:ruby, :html, :hint => :debug].highlight '????::??' + # + # alternative syntax without options: + # CodeRay::Duo[:ruby => :statistic].encode 'class << self; end' + # + # alternative syntax with options: + # CodeRay::Duo[{ :ruby => :statistic }, :do => :something].encode 'abc' + # + # The options are forwarded to scanner and encoder + # (see CodeRay.get_scanner_options). + def initialize lang = nil, format = nil, options = {} + if format.nil? && lang.is_a?(Hash) && lang.size == 1 + @lang = lang.keys.first + @format = lang[@lang] + else + @lang = lang + @format = format + end + @options = options + end + + class << self + # To allow calls like Duo[:ruby, :html].highlight. + alias [] new + end + + # The scanner of the duo. Only created once. + def scanner + @scanner ||= CodeRay.scanner @lang, CodeRay.get_scanner_options(@options) + end + + # The encoder of the duo. Only created once. + def encoder + @encoder ||= CodeRay.encoder @format, @options + end + + # Tokenize and highlight the code using +scanner+ and +encoder+. + def encode code, options = {} + options = @options.merge options + encoder.encode(code, @lang, options) + end + alias highlight encode + + # Allows to use Duo like a proc object: + # + # CodeRay::Duo[:python => :yaml].call(code) + # + # or, in Ruby 1.9 and later: + # + # CodeRay::Duo[:python => :yaml].(code) + alias call encode + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders.rb new file mode 100644 index 0000000000..6599186e5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders.rb @@ -0,0 +1,18 @@ +module CodeRay + + # This module holds the Encoder class and its subclasses. + # For example, the HTML encoder is named CodeRay::Encoders::HTML + # can be found in coderay/encoders/html. + # + # Encoders also provides methods and constants for the register + # mechanism and the [] method that returns the Encoder class + # belonging to the given format. + module Encoders + + extend PluginHost + plugin_path File.dirname(__FILE__), 'encoders' + + autoload :Encoder, CodeRay.coderay_path('encoders', 'encoder') + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/_map.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/_map.rb new file mode 100644 index 0000000000..4cca1963f7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/_map.rb @@ -0,0 +1,17 @@ +module CodeRay +module Encoders + + map \ + :loc => :lines_of_code, + :plain => :text, + :plaintext => :text, + :remove_comments => :comment_filter, + :stats => :statistic, + :term => :terminal, + :tty => :terminal, + :yml => :yaml + + # No default because Tokens#nonsense should raise NoMethodError. + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/comment_filter.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/comment_filter.rb new file mode 100644 index 0000000000..28336b3d46 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/comment_filter.rb @@ -0,0 +1,25 @@ +module CodeRay +module Encoders + + load :token_kind_filter + + # A simple Filter that removes all tokens of the :comment kind. + # + # Alias: +remove_comments+ + # + # Usage: + # CodeRay.scan('print # foo', :ruby).comment_filter.text + # #-> "print " + # + # See also: TokenKindFilter, LinesOfCode + class CommentFilter < TokenKindFilter + + register_for :comment_filter + + DEFAULT_OPTIONS = superclass::DEFAULT_OPTIONS.merge \ + :exclude => [:comment, :docstring] + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/count.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/count.rb new file mode 100644 index 0000000000..98a427e182 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/count.rb @@ -0,0 +1,39 @@ +module CodeRay +module Encoders + + # Returns the number of tokens. + # + # Text and block tokens are counted. + class Count < Encoder + + register_for :count + + protected + + def setup options + super + + @count = 0 + end + + def finish options + output @count + end + + public + + def text_token text, kind + @count += 1 + end + + def begin_group kind + @count += 1 + end + alias end_group begin_group + alias begin_line begin_group + alias end_line begin_group + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug.rb new file mode 100644 index 0000000000..f4db330197 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug.rb @@ -0,0 +1,49 @@ +module CodeRay +module Encoders + + # = Debug Encoder + # + # Fast encoder producing simple debug output. + # + # It is readable and diff-able and is used for testing. + # + # You cannot fully restore the tokens information from the + # output, because consecutive :space tokens are merged. + # + # See also: Scanners::Debug + class Debug < Encoder + + register_for :debug + + FILE_EXTENSION = 'raydebug' + + def text_token text, kind + if kind == :space + @out << text + else + text = text.gsub('\\', '\\\\\\\\') if text.index('\\') + text = text.gsub(')', '\\\\)') if text.index(')') + @out << "#{kind}(#{text})" + end + end + + def begin_group kind + @out << "#{kind}<" + end + + def end_group kind + @out << '>' + end + + def begin_line kind + @out << "#{kind}[" + end + + def end_line kind + @out << ']' + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug_lint.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug_lint.rb new file mode 100644 index 0000000000..a4eba2c748 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/debug_lint.rb @@ -0,0 +1,63 @@ +module CodeRay +module Encoders + + load :lint + + # = Debug Lint Encoder + # + # Debug encoder with additional checks for: + # + # - empty tokens + # - incorrect nesting + # + # It will raise an InvalidTokenStream exception when any of the above occurs. + # + # See also: Encoders::Debug + class DebugLint < Debug + + register_for :debug_lint + + def text_token text, kind + raise Lint::EmptyToken, 'empty token for %p' % [kind] if text.empty? + raise Lint::UnknownTokenKind, 'unknown token kind %p (text was %p)' % [kind, text] unless TokenKinds.has_key? kind + super + end + + def begin_group kind + @opened << kind + super + end + + def end_group kind + raise Lint::IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_group)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind + @opened.pop + super + end + + def begin_line kind + @opened << kind + super + end + + def end_line kind + raise Lint::IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_line)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind + @opened.pop + super + end + + protected + + def setup options + super + @opened = [] + end + + def finish options + raise 'Some tokens still open at end of token stream: %p' % [@opened] unless @opened.empty? + super + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/div.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/div.rb new file mode 100644 index 0000000000..efd9435c24 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/div.rb @@ -0,0 +1,23 @@ +module CodeRay +module Encoders + + load :html + + # Wraps HTML output into a DIV element, using inline styles by default. + # + # See Encoders::HTML for available options. + class Div < HTML + + FILE_EXTENSION = 'div.html' + + register_for :div + + DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ + :css => :style, + :wrap => :div, + :line_numbers => false + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/encoder.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/encoder.rb new file mode 100644 index 0000000000..2baeedb619 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/encoder.rb @@ -0,0 +1,190 @@ +module CodeRay + module Encoders + + # = Encoder + # + # The Encoder base class. Together with Scanner and + # Tokens, it forms the highlighting triad. + # + # Encoder instances take a Tokens object and do something with it. + # + # The most common Encoder is surely the HTML encoder + # (CodeRay::Encoders::HTML). It highlights the code in a colorful + # html page. + # If you want the highlighted code in a div or a span instead, + # use its subclasses Div and Span. + class Encoder + extend Plugin + plugin_host Encoders + + class << self + + # If FILE_EXTENSION isn't defined, this method returns the + # downcase class name instead. + def const_missing sym + if sym == :FILE_EXTENSION + (defined?(@plugin_id) && @plugin_id || name[/\w+$/].downcase).to_s + else + super + end + end + + # The default file extension for output file of this encoder class. + def file_extension + self::FILE_EXTENSION + end + + end + + # Subclasses are to store their default options in this constant. + DEFAULT_OPTIONS = { } + + # The options you gave the Encoder at creating. + attr_accessor :options, :scanner + + # Creates a new Encoder. + # +options+ is saved and used for all encode operations, as long + # as you don't overwrite it there by passing additional options. + # + # Encoder objects provide three encode methods: + # - encode simply takes a +code+ string and a +lang+ + # - encode_tokens expects a +tokens+ object instead + # + # Each method has an optional +options+ parameter. These are + # added to the options you passed at creation. + def initialize options = {} + @options = self.class::DEFAULT_OPTIONS.merge options + @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = false + end + + # Encode a Tokens object. + def encode_tokens tokens, options = {} + options = @options.merge options + @scanner = tokens.scanner if tokens.respond_to? :scanner + setup options + compile tokens, options + finish options + end + + # Encode the given +code+ using the Scanner for +lang+. + def encode code, lang, options = {} + options = @options.merge options + @scanner = Scanners[lang].new code, CodeRay.get_scanner_options(options).update(:tokens => self) + setup options + @scanner.tokenize + finish options + end + + # You can use highlight instead of encode, if that seems + # more clear to you. + alias highlight encode + + # The default file extension for this encoder. + def file_extension + self.class.file_extension + end + + def << token + unless @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN + warn 'Using old Tokens#<< interface.' + @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = true + end + self.token(*token) + end + + # Called with +content+ and +kind+ of the currently scanned token. + # For simple scanners, it's enougth to implement this method. + # + # By default, it calls text_token, begin_group, end_group, begin_line, + # or end_line, depending on the +content+. + def token content, kind + case content + when String + text_token content, kind + when :begin_group + begin_group kind + when :end_group + end_group kind + when :begin_line + begin_line kind + when :end_line + end_line kind + else + raise ArgumentError, 'Unknown token content type: %p, kind = %p' % [content, kind] + end + end + + # Called for each text token ([text, kind]), where text is a String. + def text_token text, kind + @out << text + end + + # Starts a token group with the given +kind+. + def begin_group kind + end + + # Ends a token group with the given +kind+. + def end_group kind + end + + # Starts a new line token group with the given +kind+. + def begin_line kind + end + + # Ends a new line token group with the given +kind+. + def end_line kind + end + + protected + + # Called with merged options before encoding starts. + # Sets @out to an empty string. + # + # See the HTML Encoder for an example of option caching. + def setup options + @out = get_output(options) + end + + def get_output options + options[:out] || ''.dup + end + + # Append data.to_s to the output. Returns the argument. + def output data + @out << data.to_s + data + end + + # Called with merged options after encoding starts. + # The return value is the result of encoding, typically @out. + def finish options + @out + end + + # Do the encoding. + # + # The already created +tokens+ object must be used; it must be a + # Tokens object. + def compile tokens, options = {} + content = nil + for item in tokens + if item.is_a? Array + raise ArgumentError, 'Two-element array tokens are no longer supported.' + end + if content + token content, item + content = nil + else + content = item + end + end + raise 'odd number list for Tokens' if content + end + + alias tokens compile + public :tokens + + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/filter.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/filter.rb new file mode 100644 index 0000000000..e7f34d6adf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/filter.rb @@ -0,0 +1,58 @@ +module CodeRay +module Encoders + + # A Filter encoder has another Tokens instance as output. + # It can be subclass to select, remove, or modify tokens in the stream. + # + # Subclasses of Filter are called "Filters" and can be chained. + # + # == Options + # + # === :tokens + # + # The Tokens object which will receive the output. + # + # Default: Tokens.new + # + # See also: TokenKindFilter + class Filter < Encoder + + register_for :filter + + protected + def setup options + super + + @tokens = options[:tokens] || Tokens.new + end + + def finish options + output @tokens + end + + public + + def text_token text, kind # :nodoc: + @tokens.text_token text, kind + end + + def begin_group kind # :nodoc: + @tokens.begin_group kind + end + + def begin_line kind # :nodoc: + @tokens.begin_line kind + end + + def end_group kind # :nodoc: + @tokens.end_group kind + end + + def end_line kind # :nodoc: + @tokens.end_line kind + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html.rb new file mode 100644 index 0000000000..1b33e921cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html.rb @@ -0,0 +1,333 @@ +require 'set' + +module CodeRay +module Encoders + + # = HTML Encoder + # + # This is CodeRay's most important highlighter: + # It provides save, fast XHTML generation and CSS support. + # + # == Usage + # + # require 'coderay' + # puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page + # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span) + # #-> Some /code/ + # puts CodeRay.scan('Some /code/', :ruby).span #-> the same + # + # puts CodeRay.scan('Some code', :ruby).html( + # :wrap => nil, + # :line_numbers => :inline, + # :css => :style + # ) + # + # == Options + # + # === :tab_width + # Convert \t characters to +n+ spaces (a number or false.) + # false will keep tab characters untouched. + # + # Default: 8 + # + # === :css + # How to include the styles; can be :class or :style. + # + # Default: :class + # + # === :wrap + # Wrap in :page, :div, :span or nil. + # + # You can also use Encoders::Div and Encoders::Span. + # + # Default: nil + # + # === :title + # + # The title of the HTML page (works only when :wrap is set to :page.) + # + # Default: 'CodeRay output' + # + # === :break_lines + # + # Split multiline blocks at line breaks. + # Forced to true if :line_numbers option is set to :inline. + # + # Default: false + # + # === :line_numbers + # Include line numbers in :table, :inline, or nil (no line numbers) + # + # Default: nil + # + # === :line_number_anchors + # Adds anchors and links to the line numbers. Can be false (off), true (on), + # or a prefix string that will be prepended to the anchor name. + # + # The prefix must consist only of letters, digits, and underscores. + # + # Default: true, default prefix name: "line" + # + # === :line_number_start + # Where to start with line number counting. + # + # Default: 1 + # + # === :bold_every + # Make every +n+-th number appear bold. + # + # Default: 10 + # + # === :highlight_lines + # + # Highlights certain line numbers. + # Can be any Enumerable, typically just an Array or Range, of numbers. + # + # Bolding is deactivated when :highlight_lines is set. It only makes sense + # in combination with :line_numbers. + # + # Default: nil + # + # === :hint + # Include some information into the output using the title attribute. + # Can be :info (show token kind on mouse-over), :info_long (with full path) + # or :debug (via inspect). + # + # Default: false + class HTML < Encoder + + register_for :html + + FILE_EXTENSION = 'snippet.html' + + DEFAULT_OPTIONS = { + :tab_width => 8, + + :css => :class, + :style => :alpha, + :wrap => nil, + :title => 'CodeRay output', + + :break_lines => false, + + :line_numbers => nil, + :line_number_anchors => 'n', + :line_number_start => 1, + :bold_every => 10, + :highlight_lines => nil, + + :hint => false, + } + + autoload :Output, CodeRay.coderay_path('encoders', 'html', 'output') + autoload :CSS, CodeRay.coderay_path('encoders', 'html', 'css') + autoload :Numbering, CodeRay.coderay_path('encoders', 'html', 'numbering') + + attr_reader :css + + protected + + def self.make_html_escape_hash + { + '&' => '&', + '"' => '"', + '>' => '>', + '<' => '<', + # "\t" => will be set to ' ' * options[:tab_width] during setup + }.tap do |hash| + # Escape ASCII control codes except \x9 == \t and \xA == \n. + (Array(0x00..0x8) + Array(0xB..0x1F)).each { |invalid| hash[invalid.chr] = ' ' } + end + end + + HTML_ESCAPE = make_html_escape_hash + HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1F]/ + + TOKEN_KIND_TO_INFO = Hash.new do |h, kind| + h[kind] = kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize } + end + + TRANSPARENT_TOKEN_KINDS = Set[ + :delimiter, :modifier, :content, :escape, :inline_delimiter, + ] + + # Generate a hint about the given +kinds+ in a +hint+ style. + # + # +hint+ may be :info, :info_long or :debug. + def self.token_path_to_hint hint, kinds + kinds = Array kinds + title = + case hint + when :info + kinds = kinds[1..-1] if TRANSPARENT_TOKEN_KINDS.include? kinds.first + TOKEN_KIND_TO_INFO[kinds.first] + when :info_long + kinds.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/') + when :debug + kinds.inspect + end + title ? " title=\"#{title}\"" : '' + end + + def setup options + super + + check_options! options + + if options[:wrap] || options[:line_numbers] + @real_out = @out + @out = ''.dup + end + + @break_lines = (options[:break_lines] == true) + + @HTML_ESCAPE = HTML_ESCAPE.merge("\t" => options[:tab_width] ? ' ' * options[:tab_width] : "\t") + + @opened = [] + @last_opened = nil + @css = CSS.new options[:style] + + @span_for_kinds = make_span_for_kinds(options[:css], options[:hint]) + + @set_last_opened = options[:hint] || options[:css] == :style + end + + def finish options + unless @opened.empty? + @out << '' while @opened.pop + @last_opened = nil + end + + if @out.respond_to? :to_str + @out.extend Output + @out.css = @css + if options[:line_numbers] + Numbering.number! @out, options[:line_numbers], options + end + @out.wrap! options[:wrap] + @out.apply_title! options[:title] + end + + if defined?(@real_out) && @real_out + @real_out << @out + @out = @real_out + end + + super + end + + public + + def text_token text, kind + style = @span_for_kinds[@last_opened ? [kind, *@opened] : kind] + + text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] } if text =~ /#{HTML_ESCAPE_PATTERN}/o + text = break_lines(text, style) if @break_lines && (style || @opened.size > 0) && text.index("\n") + + if style + @out << style << text << '' + else + @out << text + end + end + + # token groups, eg. strings + def begin_group kind + @out << (@span_for_kinds[@last_opened ? [kind, *@opened] : kind] || '') + @opened << kind + @last_opened = kind if @set_last_opened + end + + def end_group kind + check_group_nesting 'token group', kind if $CODERAY_DEBUG + close_span + end + + # whole lines to be highlighted, eg. a deleted line in a diff + def begin_line kind + if style = @span_for_kinds[@last_opened ? [kind, *@opened] : kind] + if style['class="'] + @out << style.sub('class="', 'class="line ') + else + @out << style.sub('>', ' class="line">') + end + else + @out << '' + end + @opened << kind + @last_opened = kind if @options[:css] == :style + end + + def end_line kind + check_group_nesting 'line', kind if $CODERAY_DEBUG + close_span + end + + protected + + def check_options! options + unless [false, nil, :debug, :info, :info_long].include? options[:hint] + raise ArgumentError, "Unknown value %p for :hint; expected :info, :info_long, :debug, false, or nil." % [options[:hint]] + end + + unless [:class, :style].include? options[:css] + raise ArgumentError, 'Unknown value %p for :css.' % [options[:css]] + end + + options[:break_lines] = true if options[:line_numbers] == :inline + end + + def css_class_for_kinds kinds + TokenKinds[kinds.is_a?(Symbol) ? kinds : kinds.first] + end + + def style_for_kinds kinds + css_classes = kinds.is_a?(Array) ? kinds.map { |c| TokenKinds[c] } : [TokenKinds[kinds]] + @css.get_style_for_css_classes css_classes + end + + def make_span_for_kinds method, hint + Hash.new do |h, kinds| + begin + css_class = css_class_for_kinds(kinds) + title = HTML.token_path_to_hint hint, kinds if hint + + if css_class || title + if method == :style + style = style_for_kinds(kinds) + "" + else + "" + end + end + end.tap do |span| + h.clear if h.size >= 100 + h[kinds] = span + end + end + end + + def check_group_nesting name, kind + if @opened.empty? || @opened.last != kind + warn "Malformed token stream: Trying to close a #{name} (%p) that is not open. Open are: %p." % [kind, @opened[1..-1]] + end + end + + def break_lines text, style + reopen = ''.dup + @opened.each_with_index do |kind, index| + reopen << (@span_for_kinds[index > 0 ? [kind, *@opened[0...index]] : kind] || '') + end + text.gsub("\n", "#{'' * @opened.size}#{'' if style}\n#{reopen}#{style}") + end + + def close_span + if @opened.pop + @out << '' + @last_opened = @opened.last if @last_opened + end + end + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/css.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/css.rb new file mode 100644 index 0000000000..164d7f8526 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/css.rb @@ -0,0 +1,65 @@ +module CodeRay +module Encoders + + class HTML + class CSS # :nodoc: + + attr :stylesheet + + def CSS.load_stylesheet style = nil + CodeRay::Styles[style] + end + + def initialize style = :default + @styles = Hash.new + style = CSS.load_stylesheet style + @stylesheet = [ + style::CSS_MAIN_STYLES, + style::TOKEN_COLORS.gsub(/^(?!$)/, '.CodeRay ') + ].join("\n") + parse style::TOKEN_COLORS + end + + def get_style_for_css_classes css_classes + cl = @styles[css_classes.first] + return '' unless cl + style = '' + 1.upto css_classes.size do |offset| + break if style = cl[css_classes[offset .. -1]] + end + # warn 'Style not found: %p' % [styles] if style.empty? + return style + end + + private + + CSS_CLASS_PATTERN = / + ( # $1 = selectors + (?: + (?: \s* \. [-\w]+ )+ + \s* ,? + )+ + ) + \s* \{ \s* + ( [^\}]+ )? # $2 = style + \s* \} \s* + | + ( [^\n]+ ) # $3 = error + /mx + def parse stylesheet + stylesheet.scan CSS_CLASS_PATTERN do |selectors, style, error| + raise "CSS parse error: '#{error.inspect}' not recognized" if error + for selector in selectors.split(',') + classes = selector.scan(/[-\w]+/) + cl = classes.pop + @styles[cl] ||= Hash.new + @styles[cl][classes] = style.to_s.strip.delete(' ').chomp(';') + end + end + end + + end + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/numbering.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/numbering.rb new file mode 100644 index 0000000000..a1b9c04a04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/numbering.rb @@ -0,0 +1,108 @@ +module CodeRay +module Encoders + + class HTML + + module Numbering # :nodoc: + + def self.number! output, mode = :table, options = {} + return self unless mode + + options = DEFAULT_OPTIONS.merge options + + start = options[:line_number_start] + unless start.is_a? Integer + raise ArgumentError, "Invalid value %p for :line_number_start; Integer expected." % start + end + + anchor_prefix = options[:line_number_anchors] + anchor_prefix = 'line' if anchor_prefix == true + anchor_prefix = anchor_prefix.to_s[/[\w-]+/] if anchor_prefix + anchoring = + if anchor_prefix + proc do |line| + line = line.to_s + anchor = anchor_prefix + line + "#{line}" + end + else + :to_s.to_proc + end + + bold_every = options[:bold_every] + highlight_lines = options[:highlight_lines] + bolding = + if bold_every == false && highlight_lines == nil + anchoring + elsif highlight_lines.is_a? Enumerable + highlight_lines = highlight_lines.to_set + proc do |line| + if highlight_lines.include? line + "#{anchoring[line]}" # highlighted line numbers in bold + else + anchoring[line] + end + end + elsif bold_every.is_a? Integer + raise ArgumentError, ":bolding can't be 0." if bold_every == 0 + proc do |line| + if line % bold_every == 0 + "#{anchoring[line]}" # every bold_every-th number in bold + else + anchoring[line] + end + end + else + raise ArgumentError, 'Invalid value %p for :bolding; false or Integer expected.' % bold_every + end + + if position_of_last_newline = output.rindex(RUBY_VERSION >= '1.9' ? /\n/ : ?\n) + after_last_newline = output[position_of_last_newline + 1 .. -1] + ends_with_newline = after_last_newline[/\A(?:<\/span>)*\z/] + + if ends_with_newline + line_count = output.count("\n") + else + line_count = output.count("\n") + 1 + end + else + line_count = 1 + end + + case mode + when :inline + max_width = (start + line_count).to_s.size + line_number = start + output.gsub!(/^.*$\n?/) do |line| + line_number_text = bolding.call line_number + indent = ' ' * (max_width - line_number.to_s.size) + line_number += 1 + "#{indent}#{line_number_text}#{line}" + end + + when :table + line_numbers = (start ... start + line_count).map(&bolding).join("\n") + line_numbers << "\n" + line_numbers_table_template = Output::TABLE.apply('LINE_NUMBERS', line_numbers) + + output.gsub!(/<\/div>\n/, '
') + output.wrap_in! line_numbers_table_template + output.wrapped_in = :div + + when :list + raise NotImplementedError, 'The :list option is no longer available. Use :table.' + + else + raise ArgumentError, 'Unknown value %p for mode: expected one of %p' % + [mode, [:table, :inline]] + end + + output + end + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/output.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/output.rb new file mode 100644 index 0000000000..ee87fea56c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/html/output.rb @@ -0,0 +1,164 @@ +module CodeRay +module Encoders + + class HTML + + # This module is included in the output String of the HTML Encoder. + # + # It provides methods like wrap, div, page etc. + # + # Remember to use #clone instead of #dup to keep the modules the object was + # extended with. + # + # TODO: Rewrite this without monkey patching. + module Output + + attr_accessor :css + + class << self + + # Raises an exception if an object that doesn't respond to to_str is extended by Output, + # to prevent users from misuse. Use Module#remove_method to disable. + def extended o # :nodoc: + warn "The Output module is intended to extend instances of String, not #{o.class}." unless o.respond_to? :to_str + end + + def make_stylesheet css, in_tag = false # :nodoc: + sheet = css.stylesheet + sheet = <<-'CSS' if in_tag + + CSS + sheet + end + + def page_template_for_css css # :nodoc: + sheet = make_stylesheet css + PAGE.apply 'CSS', sheet + end + + end + + def wrapped_in? element + wrapped_in == element + end + + def wrapped_in + @wrapped_in ||= nil + end + attr_writer :wrapped_in + + def wrap_in! template + Template.wrap! self, template, 'CONTENT' + self + end + + def apply_title! title + self.sub!(/()(<\/title>)/) { $1 + title + $2 } + self + end + + def wrap! element, *args + return self if not element or element == wrapped_in + case element + when :div + raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil + wrap_in! DIV + when :span + raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil + wrap_in! SPAN + when :page + wrap! :div if wrapped_in? nil + raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? :div + wrap_in! Output.page_template_for_css(@css) + if args.first.is_a?(Hash) && title = args.first[:title] + apply_title! title + end + self + else + raise "Unknown value %p for :wrap" % element + end + @wrapped_in = element + self + end + + def stylesheet in_tag = false + Output.make_stylesheet @css, in_tag + end + +#-- don't include the templates in docu + + class Template < String # :nodoc: + + def self.wrap! str, template, target + target = Regexp.new(Regexp.escape("<%#{target}%>")) + if template =~ target + str[0,0] = $` + str << $' + else + raise "Template target <%%%p%%> not found" % target + end + end + + def apply target, replacement + target = Regexp.new(Regexp.escape("<%#{target}%>")) + if self =~ target + Template.new($` + replacement + $') + else + raise "Template target <%%%p%%> not found" % target + end + end + + end + + SPAN = Template.new '<span class="CodeRay"><%CONTENT%></span>' + + DIV = Template.new <<-DIV +<div class="CodeRay"> + <div class="code"><pre><%CONTENT%></pre></div> +</div> + DIV + + TABLE = Template.new <<-TABLE +<table class="CodeRay"><tr> + <td class="line-numbers"><pre><%LINE_NUMBERS%></pre></td> + <td class="code"><pre><%CONTENT%></pre></td> +</tr></table> + TABLE + + PAGE = Template.new <<-PAGE +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title> + + + + +<%CONTENT%> + + + PAGE + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/json.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/json.rb new file mode 100644 index 0000000000..a9e40dc60a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/json.rb @@ -0,0 +1,83 @@ +module CodeRay +module Encoders + + # A simple JSON Encoder. + # + # Example: + # CodeRay.scan('puts "Hello world!"', :ruby).json + # yields + # [ + # {"type"=>"text", "text"=>"puts", "kind"=>"ident"}, + # {"type"=>"text", "text"=>" ", "kind"=>"space"}, + # {"type"=>"block", "action"=>"open", "kind"=>"string"}, + # {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, + # {"type"=>"text", "text"=>"Hello world!", "kind"=>"content"}, + # {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, + # {"type"=>"block", "action"=>"close", "kind"=>"string"}, + # ] + class JSON < Encoder + + begin + require 'json' + rescue LoadError + begin + require 'rubygems' unless defined? Gem + gem 'json' + require 'json' + rescue LoadError + $stderr.puts "The JSON encoder needs the JSON library.\n" \ + "Please gem install json." + raise + end + end + + register_for :json + FILE_EXTENSION = 'json' + + protected + def setup options + super + + @first = true + @out << '[' + end + + def finish options + @out << ']' + end + + def append data + if @first + @first = false + else + @out << ',' + end + + @out << data.to_json + end + + public + def text_token text, kind + append :type => 'text', :text => text, :kind => kind + end + + def begin_group kind + append :type => 'block', :action => 'open', :kind => kind + end + + def end_group kind + append :type => 'block', :action => 'close', :kind => kind + end + + def begin_line kind + append :type => 'block', :action => 'begin_line', :kind => kind + end + + def end_line kind + append :type => 'block', :action => 'end_line', :kind => kind + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lines_of_code.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lines_of_code.rb new file mode 100644 index 0000000000..5f8422f3dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lines_of_code.rb @@ -0,0 +1,45 @@ +module CodeRay +module Encoders + + # Counts the LoC (Lines of Code). Returns an Integer >= 0. + # + # Alias: +loc+ + # + # Everything that is not comment, markup, doctype/shebang, or an empty line, + # is considered to be code. + # + # For example, + # * HTML files not containing JavaScript have 0 LoC + # * in a Java class without comments, LoC is the number of non-empty lines + # + # A Scanner class should define the token kinds that are not code in the + # KINDS_NOT_LOC constant, which defaults to [:comment, :doctype]. + class LinesOfCode < TokenKindFilter + + register_for :lines_of_code + + NON_EMPTY_LINE = /^\s*\S.*$/ + + protected + + def setup options + if scanner + kinds_not_loc = scanner.class::KINDS_NOT_LOC + else + warn "Tokens have no associated scanner, counting all nonempty lines." if $VERBOSE + kinds_not_loc = CodeRay::Scanners::Scanner::KINDS_NOT_LOC + end + + options[:exclude] = kinds_not_loc + + super options + end + + def finish options + output @tokens.text.scan(NON_EMPTY_LINE).size + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lint.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lint.rb new file mode 100644 index 0000000000..88c8bd1d59 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/lint.rb @@ -0,0 +1,59 @@ +module CodeRay +module Encoders + + # = Lint Encoder + # + # Checks for: + # + # - empty tokens + # - incorrect nesting + # + # It will raise an InvalidTokenStream exception when any of the above occurs. + # + # See also: Encoders::DebugLint + class Lint < Debug + + register_for :lint + + InvalidTokenStream = Class.new StandardError + EmptyToken = Class.new InvalidTokenStream + UnknownTokenKind = Class.new InvalidTokenStream + IncorrectTokenGroupNesting = Class.new InvalidTokenStream + + def text_token text, kind + raise EmptyToken, 'empty token for %p' % [kind] if text.empty? + raise UnknownTokenKind, 'unknown token kind %p (text was %p)' % [kind, text] unless TokenKinds.has_key? kind + end + + def begin_group kind + @opened << kind + end + + def end_group kind + raise IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_group)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind + @opened.pop + end + + def begin_line kind + @opened << kind + end + + def end_line kind + raise IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_line)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind + @opened.pop + end + + protected + + def setup options + @opened = [] + end + + def finish options + raise 'Some tokens still open at end of token stream: %p' % [@opened] unless @opened.empty? + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/null.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/null.rb new file mode 100644 index 0000000000..73ba47d359 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/null.rb @@ -0,0 +1,18 @@ +module CodeRay +module Encoders + + # = Null Encoder + # + # Does nothing and returns an empty string. + class Null < Encoder + + register_for :null + + def text_token text, kind + # do nothing + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/page.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/page.rb new file mode 100644 index 0000000000..800e73f325 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/page.rb @@ -0,0 +1,24 @@ +module CodeRay +module Encoders + + load :html + + # Wraps the output into a HTML page, using CSS classes and + # line numbers in the table format by default. + # + # See Encoders::HTML for available options. + class Page < HTML + + FILE_EXTENSION = 'html' + + register_for :page + + DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ + :css => :class, + :wrap => :page, + :line_numbers => :table + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/span.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/span.rb new file mode 100644 index 0000000000..da705bdc07 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/span.rb @@ -0,0 +1,23 @@ +module CodeRay +module Encoders + + load :html + + # Wraps HTML output into a SPAN element, using inline styles by default. + # + # See Encoders::HTML for available options. + class Span < HTML + + FILE_EXTENSION = 'span.html' + + register_for :span + + DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ + :css => :style, + :wrap => :span, + :line_numbers => false + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/statistic.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/statistic.rb new file mode 100644 index 0000000000..b2f8b8306e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/statistic.rb @@ -0,0 +1,95 @@ +module CodeRay +module Encoders + + # Makes a statistic for the given tokens. + # + # Alias: +stats+ + class Statistic < Encoder + + register_for :statistic + + attr_reader :type_stats, :real_token_count # :nodoc: + + TypeStats = Struct.new :count, :size # :nodoc: + + protected + + def setup options + super + + @type_stats = Hash.new { |h, k| h[k] = TypeStats.new 0, 0 } + @real_token_count = 0 + end + + STATS = <<-STATS # :nodoc: + +Code Statistics + +Tokens %8d + Non-Whitespace %8d +Bytes Total %8d + +Token Types (%d): + type count ratio size (average) +------------------------------------------------------------- +%s + STATS + + TOKEN_TYPES_ROW = <<-TKR # :nodoc: + %-20s %8d %6.2f %% %5.1f + TKR + + def finish options + all = @type_stats['TOTAL'] + all_count, all_size = all.count, all.size + @type_stats.each do |type, stat| + stat.size /= stat.count.to_f + end + types_stats = @type_stats.sort_by { |k, v| [-v.count, k.to_s] }.map do |k, v| + TOKEN_TYPES_ROW % [k, v.count, 100.0 * v.count / all_count, v.size] + end.join + @out << STATS % [ + all_count, @real_token_count, all_size, + @type_stats.delete_if { |k, v| k.is_a? String }.size, + types_stats + ] + + super + end + + public + + def text_token text, kind + @real_token_count += 1 unless kind == :space + @type_stats[kind].count += 1 + @type_stats[kind].size += text.size + @type_stats['TOTAL'].size += text.size + @type_stats['TOTAL'].count += 1 + end + + def begin_group kind + block_token ':begin_group', kind + end + + def end_group kind + block_token ':end_group', kind + end + + def begin_line kind + block_token ':begin_line', kind + end + + def end_line kind + block_token ':end_line', kind + end + + def block_token action, kind + @type_stats['TOTAL'].count += 1 + @type_stats[action].count += 1 + @type_stats[kind].count += 1 + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/terminal.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/terminal.rb new file mode 100644 index 0000000000..c7ae014645 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/terminal.rb @@ -0,0 +1,195 @@ +module CodeRay + module Encoders + + # Outputs code highlighted for a color terminal. + # + # Note: This encoder is in beta. It currently doesn't use the Styles. + # + # Alias: +term+ + # + # == Authors & License + # + # By Rob Aldred (http://robaldred.co.uk) + # + # Based on idea by Nathan Weizenbaum (http://nex-3.com) + # + # MIT License (http://www.opensource.org/licenses/mit-license.php) + class Terminal < Encoder + + register_for :terminal + + TOKEN_COLORS = { + :debug => "\e[1;37;44m", + + :annotation => "\e[34m", + :attribute_name => "\e[35m", + :attribute_value => "\e[31m", + :binary => { + :self => "\e[31m", + :char => "\e[1;31m", + :delimiter => "\e[1;31m", + }, + :char => { + :self => "\e[35m", + :delimiter => "\e[1;35m" + }, + :class => "\e[1;35;4m", + :class_variable => "\e[36m", + :color => "\e[32m", + :comment => { + :self => "\e[1;30m", + :char => "\e[37m", + :delimiter => "\e[37m", + }, + :constant => "\e[1;34;4m", + :decorator => "\e[35m", + :definition => "\e[1;33m", + :directive => "\e[33m", + :docstring => "\e[31m", + :doctype => "\e[1;34m", + :done => "\e[1;30;2m", + :entity => "\e[31m", + :error => "\e[1;37;41m", + :exception => "\e[1;31m", + :float => "\e[1;35m", + :function => "\e[1;34m", + :global_variable => "\e[1;32m", + :hex => "\e[1;36m", + :id => "\e[1;34m", + :include => "\e[31m", + :integer => "\e[1;34m", + :imaginary => "\e[1;34m", + :important => "\e[1;31m", + :key => { + :self => "\e[35m", + :char => "\e[1;35m", + :delimiter => "\e[1;35m", + }, + :keyword => "\e[32m", + :label => "\e[1;33m", + :local_variable => "\e[33m", + :namespace => "\e[1;35m", + :octal => "\e[1;34m", + :predefined => "\e[36m", + :predefined_constant => "\e[1;36m", + :predefined_type => "\e[1;32m", + :preprocessor => "\e[1;36m", + :pseudo_class => "\e[1;34m", + :regexp => { + :self => "\e[35m", + :delimiter => "\e[1;35m", + :modifier => "\e[35m", + :char => "\e[1;35m", + }, + :reserved => "\e[32m", + :shell => { + :self => "\e[33m", + :char => "\e[1;33m", + :delimiter => "\e[1;33m", + :escape => "\e[1;33m", + }, + :string => { + :self => "\e[31m", + :modifier => "\e[1;31m", + :char => "\e[1;35m", + :delimiter => "\e[1;31m", + :escape => "\e[1;31m", + }, + :symbol => { + :self => "\e[33m", + :delimiter => "\e[1;33m", + }, + :tag => "\e[32m", + :type => "\e[1;34m", + :value => "\e[36m", + :variable => "\e[34m", + + :insert => { + :self => "\e[42m", + :insert => "\e[1;32;42m", + :eyecatcher => "\e[102m", + }, + :delete => { + :self => "\e[41m", + :delete => "\e[1;31;41m", + :eyecatcher => "\e[101m", + }, + :change => { + :self => "\e[44m", + :change => "\e[37;44m", + }, + :head => { + :self => "\e[45m", + :filename => "\e[37;45m" + }, + } + + TOKEN_COLORS[:keyword] = TOKEN_COLORS[:reserved] + TOKEN_COLORS[:method] = TOKEN_COLORS[:function] + TOKEN_COLORS[:escape] = TOKEN_COLORS[:delimiter] + + protected + + def setup(options) + super + @opened = [] + @color_scopes = [TOKEN_COLORS] + end + + public + + def text_token text, kind + if color = @color_scopes.last[kind] + color = color[:self] if color.is_a? Hash + + @out << color + @out << (text.index("\n") ? text.gsub("\n", "\e[0m\n" + color) : text) + @out << "\e[0m" + if outer_color = @color_scopes.last[:self] + @out << outer_color + end + else + @out << text + end + end + + def begin_group kind + @opened << kind + @out << open_token(kind) + end + alias begin_line begin_group + + def end_group kind + if @opened.pop + @color_scopes.pop + @out << "\e[0m" + if outer_color = @color_scopes.last[:self] + @out << outer_color + end + end + end + + def end_line kind + @out << (@line_filler ||= "\t" * 100) + end_group kind + end + + private + + def open_token kind + if color = @color_scopes.last[kind] + if color.is_a? Hash + @color_scopes << color + color[:self] + else + @color_scopes << @color_scopes.last + color + end + else + @color_scopes << @color_scopes.last + '' + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/text.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/text.rb new file mode 100644 index 0000000000..15c66f9c72 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/text.rb @@ -0,0 +1,46 @@ +module CodeRay +module Encoders + + # Concats the tokens into a single string, resulting in the original + # code string if no tokens were removed. + # + # Alias: +plain+, +plaintext+ + # + # == Options + # + # === :separator + # A separator string to join the tokens. + # + # Default: empty String + class Text < Encoder + + register_for :text + + FILE_EXTENSION = 'txt' + + DEFAULT_OPTIONS = { + :separator => nil + } + + def text_token text, kind + super + + if @first + @first = false + else + @out << @sep + end if @sep + end + + protected + def setup options + super + + @first = true + @sep = options[:separator] + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/token_kind_filter.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/token_kind_filter.rb new file mode 100644 index 0000000000..4773ea34e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/token_kind_filter.rb @@ -0,0 +1,111 @@ +module CodeRay +module Encoders + + load :filter + + # A Filter that selects tokens based on their token kind. + # + # == Options + # + # === :exclude + # + # One or many symbols (in an Array) which shall be excluded. + # + # Default: [] + # + # === :include + # + # One or many symbols (in an array) which shall be included. + # + # Default: :all, which means all tokens are included. + # + # Exclusion wins over inclusion. + # + # See also: CommentFilter + class TokenKindFilter < Filter + + register_for :token_kind_filter + + DEFAULT_OPTIONS = { + :exclude => [], + :include => :all + } + + protected + def setup options + super + + @group_excluded = false + @exclude = options[:exclude] + @exclude = Array(@exclude) unless @exclude == :all + @include = options[:include] + @include = Array(@include) unless @include == :all + end + + def include_text_token? text, kind + include_group? kind + end + + def include_group? kind + (@include == :all || @include.include?(kind)) && + !(@exclude == :all || @exclude.include?(kind)) + end + + public + + # Add the token to the output stream if +kind+ matches the conditions. + def text_token text, kind + super if !@group_excluded && include_text_token?(text, kind) + end + + # Add the token group to the output stream if +kind+ matches the + # conditions. + # + # If it does not, all tokens inside the group are excluded from the + # stream, even if their kinds match. + def begin_group kind + if @group_excluded + @group_excluded += 1 + elsif include_group? kind + super + else + @group_excluded = 1 + end + end + + # See +begin_group+. + def begin_line kind + if @group_excluded + @group_excluded += 1 + elsif include_group? kind + super + else + @group_excluded = 1 + end + end + + # Take care of re-enabling the delegation of tokens to the output stream + # if an exluded group has ended. + def end_group kind + if @group_excluded + @group_excluded -= 1 + @group_excluded = false if @group_excluded.zero? + else + super + end + end + + # See +end_group+. + def end_line kind + if @group_excluded + @group_excluded -= 1 + @group_excluded = false if @group_excluded.zero? + else + super + end + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/xml.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/xml.rb new file mode 100644 index 0000000000..3d306a6082 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/xml.rb @@ -0,0 +1,72 @@ +module CodeRay +module Encoders + + # = XML Encoder + # + # Uses REXML. Very slow. + class XML < Encoder + + register_for :xml + + FILE_EXTENSION = 'xml' + + autoload :REXML, 'rexml/document' + + DEFAULT_OPTIONS = { + :tab_width => 8, + :pretty => -1, + :transitive => false, + } + + protected + def setup options + super + + @doc = REXML::Document.new + @doc << REXML::XMLDecl.new + @tab_width = options[:tab_width] + @root = @node = @doc.add_element('coderay-tokens') + end + + def finish options + @doc.write @out, options[:pretty], options[:transitive], true + + super + end + + public + def text_token text, kind + if kind == :space + token = @node + else + token = @node.add_element kind.to_s + end + text.scan(/(\x20+)|(\t+)|(\n)|[^\x20\t\n]+/) do |space, tab, nl| + case + when space + token << REXML::Text.new(space, true) + when tab + token << REXML::Text.new(tab, true) + when nl + token << REXML::Text.new(nl, true) + else + token << REXML::Text.new($&) + end + end + end + + def begin_group kind + @node = @node.add_element kind.to_s + end + + def end_group kind + if @node == @root + raise 'no token to close!' + end + @node = @node.parent + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/yaml.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/yaml.rb new file mode 100644 index 0000000000..ba6e715558 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/encoders/yaml.rb @@ -0,0 +1,50 @@ +autoload :YAML, 'yaml' + +module CodeRay +module Encoders + + # = YAML Encoder + # + # Slow. + class YAML < Encoder + + register_for :yaml + + FILE_EXTENSION = 'yaml' + + protected + def setup options + super + + @data = [] + end + + def finish options + output ::YAML.dump(@data) + end + + public + def text_token text, kind + @data << [text, kind] + end + + def begin_group kind + @data << [:begin_group, kind] + end + + def end_group kind + @data << [:end_group, kind] + end + + def begin_line kind + @data << [:begin_line, kind] + end + + def end_line kind + @data << [:end_line, kind] + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/for_redcloth.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/for_redcloth.rb new file mode 100644 index 0000000000..f9df32be6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/for_redcloth.rb @@ -0,0 +1,95 @@ +module CodeRay + + # A little hack to enable CodeRay highlighting in RedCloth. + # + # Usage: + # require 'coderay' + # require 'coderay/for_redcloth' + # RedCloth.new('@[ruby]puts "Hello, World!"@').to_html + # + # Make sure you have RedCloth 4.0.3 activated, for example by calling + # require 'rubygems' + # before RedCloth is loaded and before calling CodeRay.for_redcloth. + module ForRedCloth + + def self.install + gem 'RedCloth', '>= 4.0.3' if defined? gem + require 'redcloth' + unless RedCloth::VERSION.to_s >= '4.0.3' + if defined? gem + raise 'CodeRay.for_redcloth needs RedCloth version 4.0.3 or later. ' + + "You have #{RedCloth::VERSION}. Please gem install RedCloth." + else + $".delete 'redcloth.rb' # sorry, but it works + require 'rubygems' + return install # retry + end + end + unless RedCloth::VERSION.to_s >= '4.2.2' + warn 'CodeRay.for_redcloth works best with RedCloth version 4.2.2 or later.' + end + RedCloth::TextileDoc.send :include, ForRedCloth::TextileDoc + RedCloth::Formatters::HTML.module_eval do + def unescape(html) # :nodoc: + replacements = { + '&' => '&', + '"' => '"', + '>' => '>', + '<' => '<', + } + html.gsub(/&(?:amp|quot|[gl]t);/) { |entity| replacements[entity] } + end + undef code, bc_open, bc_close, escape_pre + def code(opts) # :nodoc: + opts[:block] = true + if !opts[:lang] && RedCloth::VERSION.to_s >= '4.2.0' + # simulating pre-4.2 behavior + if opts[:text].sub!(/\A\[(\w+)\]/, '') + if CodeRay::Scanners[$1].lang == :text + opts[:text] = $& + opts[:text] + else + opts[:lang] = $1 + end + end + end + if opts[:lang] && !filter_coderay + require 'coderay' + @in_bc ||= nil + format = @in_bc ? :div : :span + opts[:text] = unescape(opts[:text]) unless @in_bc + highlighted_code = CodeRay.encode opts[:text], opts[:lang], format + highlighted_code.sub!(/\A<(span|div)/) { |m| m + pba(@in_bc || opts) } + highlighted_code + else + "#{opts[:text]}" + end + end + def bc_open(opts) # :nodoc: + opts[:block] = true + @in_bc = opts + opts[:lang] ? '' : "" + end + def bc_close(opts) # :nodoc: + opts = @in_bc + @in_bc = nil + opts[:lang] ? '' : "\n" + end + def escape_pre(text) # :nodoc: + if @in_bc ||= nil + text + else + html_esc(text, :html_escape_preformatted) + end + end + end + end + + module TextileDoc # :nodoc: + attr_accessor :filter_coderay + end + + end + +end + +CodeRay::ForRedCloth.install \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/file_type.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/file_type.rb new file mode 100644 index 0000000000..7de34d58ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/file_type.rb @@ -0,0 +1,151 @@ +module CodeRay + + # = FileType + # + # A simple filetype recognizer. + # + # == Usage + # + # # determine the type of the given + # lang = FileType[file_name] + # + # # return :text if the file type is unknown + # lang = FileType.fetch file_name, :text + # + # # try the shebang line, too + # lang = FileType.fetch file_name, :text, true + module FileType + + UnknownFileType = Class.new Exception + + class << self + + # Try to determine the file type of the file. + # + # +filename+ is a relative or absolute path to a file. + # + # The file itself is only accessed when +read_shebang+ is set to true. + # That means you can get filetypes from files that don't exist. + def [] filename, read_shebang = false + name = File.basename filename + ext = File.extname(name).sub(/^\./, '') # from last dot, delete the leading dot + ext2 = filename.to_s[/\.(.*)/, 1] # from first dot + + type = + TypeFromExt[ext] || + TypeFromExt[ext.downcase] || + (TypeFromExt[ext2] if ext2) || + (TypeFromExt[ext2.downcase] if ext2) || + TypeFromName[name] || + TypeFromName[name.downcase] + type ||= type_from_shebang(filename) if read_shebang + + type + end + + # This works like Hash#fetch. + # + # If the filetype cannot be found, the +default+ value + # is returned. + def fetch filename, default = nil, read_shebang = false + if default && block_given? + warn 'Block supersedes default value argument; use either.' + end + + if type = self[filename, read_shebang] + type + else + return yield if block_given? + return default if default + raise UnknownFileType, 'Could not determine type of %p.' % filename + end + end + + protected + + def type_from_shebang filename + return unless File.exist? filename + File.open filename, 'r' do |f| + if first_line = f.gets + if type = first_line[TypeFromShebang] + type.to_sym + end + end + end + end + + end + + TypeFromExt = { + 'c' => :c, + 'cfc' => :xml, + 'cfm' => :xml, + 'clj' => :clojure, + 'css' => :css, + 'diff' => :diff, + 'dpr' => :delphi, + 'erb' => :erb, + 'gemspec' => :ruby, + 'go' => :go, + 'groovy' => :groovy, + 'gvy' => :groovy, + 'h' => :c, + 'haml' => :haml, + 'htm' => :html, + 'html' => :html, + 'html.erb' => :erb, + 'java' => :java, + 'js' => :java_script, + 'json' => :json, + 'lua' => :lua, + 'mab' => :ruby, + 'pas' => :delphi, + 'patch' => :diff, + 'phtml' => :php, + 'php' => :php, + 'php3' => :php, + 'php4' => :php, + 'php5' => :php, + 'prawn' => :ruby, + 'py' => :python, + 'py3' => :python, + 'pyw' => :python, + 'rake' => :ruby, + 'raydebug' => :raydebug, + 'rb' => :ruby, + 'rbw' => :ruby, + 'rhtml' => :erb, + 'rjs' => :ruby, + 'rpdf' => :ruby, + 'ru' => :ruby, # config.ru + 'rxml' => :ruby, + 'sass' => :sass, + 'sql' => :sql, + 'taskpaper' => :taskpaper, + 'template' => :json, # AWS CloudFormation template + 'tmproj' => :xml, + 'xaml' => :xml, + 'xhtml' => :html, + 'xml' => :xml, + 'yaml' => :yaml, + 'yml' => :yaml, + } + for cpp_alias in %w[cc cpp cp cxx c++ C hh hpp h++ cu] + TypeFromExt[cpp_alias] = :cpp + end + + TypeFromShebang = /\b(?:ruby|perl|python|sh)\b/ + + TypeFromName = { + 'Capfile' => :ruby, + 'Rakefile' => :ruby, + 'Rantfile' => :ruby, + 'Gemfile' => :ruby, + 'Guardfile' => :ruby, + 'Vagrantfile' => :ruby, + 'Appraisals' => :ruby + } + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin.rb new file mode 100644 index 0000000000..45679436b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin.rb @@ -0,0 +1,55 @@ +module CodeRay + + # = Plugin + # + # Plugins have to include this module. + # + # IMPORTANT: Use extend for this module. + # + # See CodeRay::PluginHost for examples. + module Plugin + + attr_reader :plugin_id + + # Register this class for the given +id+. + # + # Example: + # class MyPlugin < PluginHost::BaseClass + # register_for :my_id + # ... + # end + # + # See PluginHost.register. + def register_for id + @plugin_id = id + plugin_host.register self, id + end + + # Returns the title of the plugin, or sets it to the + # optional argument +title+. + def title title = nil + if title + @title = title.to_s + else + @title ||= name[/([^:]+)$/, 1] + end + end + + # The PluginHost for this Plugin class. + def plugin_host host = nil + if host.is_a? PluginHost + const_set :PLUGIN_HOST, host + end + self::PLUGIN_HOST + end + + def aliases + plugin_host.plugin_hash.inject [] do |aliases, (key, _)| + aliases << key if plugin_host[key] == self + aliases + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin_host.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin_host.rb new file mode 100644 index 0000000000..e9bc17c130 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/plugin_host.rb @@ -0,0 +1,221 @@ +module CodeRay + + # = PluginHost + # + # A simple subclass/subfolder plugin system. + # + # Example: + # class Generators + # extend PluginHost + # plugin_path 'app/generators' + # end + # + # class Generator + # extend Plugin + # PLUGIN_HOST = Generators + # end + # + # class FancyGenerator < Generator + # register_for :fancy + # end + # + # Generators[:fancy] #-> FancyGenerator + # # or + # CodeRay.require_plugin 'Generators/fancy' + # # or + # Generators::Fancy + module PluginHost + + # Raised if Encoders::[] fails because: + # * a file could not be found + # * the requested Plugin is not registered + PluginNotFound = Class.new LoadError + HostNotFound = Class.new LoadError + + PLUGIN_HOSTS = [] + PLUGIN_HOSTS_BY_ID = {} # dummy hash + + # Loads all plugins using list and load. + def load_all + for plugin in list + load plugin + end + end + + # Returns the Plugin for +id+. + # + # Example: + # yaml_plugin = MyPluginHost[:yaml] + def [] id, *args, &blk + plugin = validate_id(id) + begin + plugin = plugin_hash.[](plugin, *args, &blk) + end while plugin.is_a? String + plugin + end + + alias load [] + + # Tries to +load+ the missing plugin by translating +const+ to the + # underscore form (eg. LinesOfCode becomes lines_of_code). + def const_missing const + id = const.to_s. + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + downcase + load id + end + + class << self + + # Adds the module/class to the PLUGIN_HOSTS list. + def extended mod + PLUGIN_HOSTS << mod + end + + end + + # The path where the plugins can be found. + def plugin_path *args + unless args.empty? + @plugin_path = File.expand_path File.join(*args) + end + @plugin_path ||= '' + end + + # Map a plugin_id to another. + # + # Usage: Put this in a file plugin_path/_map.rb. + # + # class MyColorHost < PluginHost + # map :navy => :dark_blue, + # :maroon => :brown, + # :luna => :moon + # end + def map hash + for from, to in hash + from = validate_id from + to = validate_id to + plugin_hash[from] = to unless plugin_hash.has_key? from + end + end + + # Define the default plugin to use when no plugin is found + # for a given id, or return the default plugin. + # + # See also map. + # + # class MyColorHost < PluginHost + # map :navy => :dark_blue + # default :gray + # end + # + # MyColorHost.default # loads and returns the Gray plugin + def default id = nil + if id + id = validate_id id + raise "The default plugin can't be named \"default\"." if id == :default + plugin_hash[:default] = id + else + load :default + end + end + + # Every plugin must register itself for +id+ by calling register_for, + # which calls this method. + # + # See Plugin#register_for. + def register plugin, id + plugin_hash[validate_id(id)] = plugin + end + + # A Hash of plugion_id => Plugin pairs. + def plugin_hash + @plugin_hash ||= (@plugin_hash = make_plugin_hash).tap { load_plugin_map } + end + + # Returns an array of all .rb files in the plugin path. + # + # The extension .rb is not included. + def list + Dir[path_to('*')].select do |file| + File.basename(file)[/^(?!_)\w+\.rb$/] + end.map do |file| + File.basename(file, '.rb').to_sym + end + end + + # Returns an array of all Plugins. + # + # Note: This loads all plugins using load_all. + def all_plugins + load_all + plugin_hash.values.grep(Class) + end + + # Loads the map file (see map). + # + # This is done automatically when plugin_path is called. + def load_plugin_map + mapfile = path_to '_map' + if File.exist? mapfile + require mapfile + true + else + false + end + end + + protected + + # Return a plugin hash that automatically loads plugins. + def make_plugin_hash + Hash.new do |h, plugin_id| + id = validate_id(plugin_id) + path = path_to id + begin + require path + rescue LoadError => boom + if h.has_key?(:default) + h[:default] + else + raise PluginNotFound, '%p could not load plugin %p: %s' % [self, id, boom] + end + else + # Plugin should have registered by now + if h.has_key? id + h[id] + else + raise PluginNotFound, "No #{self.name} plugin for #{id.inspect} found in #{path}." + end + end + end + end + + # Returns the expected path to the plugin file for the given id. + def path_to plugin_id + File.join plugin_path, "#{plugin_id}.rb" + end + + # Converts +id+ to a valid plugin ID String, or returns +nil+. + # + # Raises +ArgumentError+ for all other objects, or if the + # given String includes non-alphanumeric characters (\W). + def validate_id id + case id + when Symbol + id.to_s + when String + if id[/\w+/] == id + id.downcase + else + raise ArgumentError, "Invalid id given: #{id}" + end + else + raise ArgumentError, "Symbol or String expected, but #{id.class} given." + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/word_list.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/word_list.rb new file mode 100644 index 0000000000..4a42c4a73a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/helpers/word_list.rb @@ -0,0 +1,72 @@ +module CodeRay + + # = WordList + # + # A Hash subclass designed for mapping word lists to token types. + # + # A WordList is a Hash with some additional features. + # It is intended to be used for keyword recognition. + # + # WordList is optimized to be used in Scanners, + # typically to decide whether a given ident is a special token. + # + # For case insensitive words use WordList::CaseIgnoring. + # + # Example: + # + # # define word arrays + # RESERVED_WORDS = %w[ + # asm break case continue default do else + # ] + # + # PREDEFINED_TYPES = %w[ + # int long short char void + # ] + # + # # make a WordList + # IDENT_KIND = WordList.new(:ident). + # add(RESERVED_WORDS, :reserved). + # add(PREDEFINED_TYPES, :predefined_type) + # + # ... + # + # def scan_tokens tokens, options + # ... + # + # elsif scan(/[A-Za-z_][A-Za-z_0-9]*/) + # # use it + # kind = IDENT_KIND[match] + # ... + class WordList < Hash + + # Create a new WordList with +default+ as default value. + def initialize default = false + super default + end + + # Add words to the list and associate them with +value+. + # + # Returns +self+, so you can concat add calls. + def add words, value = true + words.each { |word| self[word] = value } + self + end + + end + + + # A CaseIgnoring WordList is like a WordList, only that + # keys are compared case-insensitively (normalizing keys using +downcase+). + class WordList::CaseIgnoring < WordList + + def [] key + super key.downcase + end + + def []= key, value + super key.downcase, value + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners.rb new file mode 100644 index 0000000000..3c7e594d71 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners.rb @@ -0,0 +1,27 @@ +require 'strscan' + +module CodeRay + + autoload :WordList, coderay_path('helpers', 'word_list') + + # = Scanners + # + # This module holds the Scanner class and its subclasses. + # For example, the Ruby scanner is named CodeRay::Scanners::Ruby + # can be found in coderay/scanners/ruby. + # + # Scanner also provides methods and constants for the register + # mechanism and the [] method that returns the Scanner class + # belonging to the given lang. + # + # See PluginHost. + module Scanners + + extend PluginHost + plugin_path File.dirname(__FILE__), 'scanners' + + autoload :Scanner, CodeRay.coderay_path('scanners', 'scanner') + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/_map.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/_map.rb new file mode 100644 index 0000000000..a240298d19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/_map.rb @@ -0,0 +1,24 @@ +module CodeRay +module Scanners + + map \ + :'c++' => :cpp, + :cplusplus => :cpp, + :ecmascript => :java_script, + :ecma_script => :java_script, + :rhtml => :erb, + :eruby => :erb, + :irb => :ruby, + :javascript => :java_script, + :js => :java_script, + :pascal => :delphi, + :patch => :diff, + :plain => :text, + :plaintext => :text, + :xhtml => :html, + :yml => :yaml + + default :text + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/c.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/c.rb new file mode 100644 index 0000000000..fb2f30dbe2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/c.rb @@ -0,0 +1,189 @@ +module CodeRay +module Scanners + + # Scanner for C. + class C < Scanner + + register_for :c + file_extension 'c' + + KEYWORDS = [ + 'asm', 'break', 'case', 'continue', 'default', 'do', + 'else', 'enum', 'for', 'goto', 'if', 'return', + 'sizeof', 'struct', 'switch', 'typedef', 'union', 'while', + 'restrict', # added in C99 + ] # :nodoc: + + PREDEFINED_TYPES = [ + 'int', 'long', 'short', 'char', + 'signed', 'unsigned', 'float', 'double', + 'bool', 'complex', # added in C99 + ] # :nodoc: + + PREDEFINED_CONSTANTS = [ + 'EOF', 'NULL', + 'true', 'false', # added in C99 + ] # :nodoc: + DIRECTIVES = [ + 'auto', 'extern', 'register', 'static', 'void', + 'const', 'volatile', # added in C89 + 'inline', # added in C99 + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(PREDEFINED_TYPES, :predefined_type). + add(DIRECTIVES, :directive). + add(PREDEFINED_CONSTANTS, :predefined_constant) # :nodoc: + + ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + label_expected = true + case_expected = false + label_expected_before_preproc_line = nil + in_preproc_line = false + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + if in_preproc_line && match != "\\\n" && match.index(?\n) + in_preproc_line = false + label_expected = label_expected_before_preproc_line + end + encoder.text_token match, :space + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) + encoder.text_token match, :comment + + elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x) + label_expected = match =~ /[;\{\}]/ + if case_expected + label_expected = true if match == ':' + case_expected = false + end + encoder.text_token match, :operator + + elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) + kind = IDENT_KIND[match] + if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/) + kind = :label + match << matched + else + label_expected = false + if kind == :keyword + case match + when 'case', 'default' + case_expected = true + end + end + end + encoder.text_token match, kind + + elsif match = scan(/L?"/) + encoder.begin_group :string + if match[0] == ?L + encoder.text_token 'L', :modifier + match = '"' + end + encoder.text_token match, :delimiter + state = :string + + elsif match = scan(/ \# \s* if \s* 0 /x) + match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos? + encoder.text_token match, :comment + + elsif match = scan(/#[ \t]*(\w*)/) + encoder.text_token match, :preprocessor + in_preproc_line = true + label_expected_before_preproc_line = label_expected + state = :include_expected if self[1] == 'include' + + elsif match = scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox) + label_expected = false + encoder.text_token match, :char + + elsif match = scan(/\$/) + encoder.text_token match, :ident + + elsif match = scan(/0[xX][0-9A-Fa-f]+/) + label_expected = false + encoder.text_token match, :hex + + elsif match = scan(/(?:0[0-7]+)(?![89.eEfF])/) + label_expected = false + encoder.text_token match, :octal + + elsif match = scan(/(?:\d+)(?![.eEfF])L?L?/) + label_expected = false + encoder.text_token match, :integer + + elsif match = scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) + label_expected = false + encoder.text_token match, :float + + else + encoder.text_token getch, :error + + end + + when :string + if match = scan(/[^\\\n"]+/) + encoder.text_token match, :content + elsif match = scan(/"/) + encoder.text_token match, :delimiter + encoder.end_group :string + state = :initial + label_expected = false + elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/ \\ | $ /x) + encoder.end_group :string + encoder.text_token match, :error unless match.empty? + state = :initial + label_expected = false + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + end + + when :include_expected + if match = scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/) + encoder.text_token match, :include + state = :initial + + elsif match = scan(/\s+/) + encoder.text_token match, :space + state = :initial if match.index ?\n + + else + state = :initial + + end + + else + raise_inspect 'Unknown state', encoder + + end + + end + + if state == :string + encoder.end_group :string + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/clojure.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/clojure.rb new file mode 100644 index 0000000000..f8fbf65060 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/clojure.rb @@ -0,0 +1,217 @@ +# encoding: utf-8 +module CodeRay + module Scanners + + # Clojure scanner by Licenser. + class Clojure < Scanner + + register_for :clojure + file_extension 'clj' + + SPECIAL_FORMS = %w[ + def if do let quote var fn loop recur throw try catch monitor-enter monitor-exit . + new + ] # :nodoc: + + CORE_FORMS = %w[ + + - -> ->> .. / * <= < = == >= > accessor aclone add-classpath add-watch + agent agent-error agent-errors aget alength alias all-ns alter alter-meta! + alter-var-root amap ancestors and apply areduce array-map aset aset-boolean + aset-byte aset-char aset-double aset-float aset-int aset-long aset-short + assert assoc assoc! assoc-in associative? atom await await-for bases bean + bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or + bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array + booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char + char-array char-escape-string char-name-string char? chars class class? + clear-agent-errors clojure-version coll? comment commute comp comparator + compare compare-and-set! compile complement concat cond condp conj conj! + cons constantly construct-proxy contains? count counted? create-ns + create-struct cycle dec decimal? declare definline defmacro defmethod defmulti + defn defn- defonce defprotocol defrecord defstruct deftype delay delay? + deliver denominator deref derive descendants disj disj! dissoc dissoc! + distinct distinct? doall doc dorun doseq dosync dotimes doto double + double-array doubles drop drop-last drop-while empty empty? ensure + enumeration-seq error-handler error-mode eval even? every? extend + extend-protocol extend-type extenders extends? false? ffirst file-seq + filter find find-doc find-ns find-var first float float-array float? + floats flush fn fn? fnext for force format future future-call future-cancel + future-cancelled? future-done? future? gen-class gen-interface gensym get + get-in get-method get-proxy-class get-thread-bindings get-validator hash + hash-map hash-set identical? identity if-let if-not ifn? import in-ns + inc init-proxy instance? int int-array integer? interleave intern + interpose into into-array ints io! isa? iterate iterator-seq juxt key + keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* + list? load load-file load-reader load-string loaded-libs locking long + long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy + map map? mapcat max max-key memfn memoize merge merge-with meta methods + min min-key mod name namespace neg? newline next nfirst nil? nnext not + not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns + ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth + nthnext num number? numerator object-array odd? or parents partial + partition pcalls peek persistent! pmap pop pop! pop-thread-bindings + pos? pr pr-str prefer-method prefers print print-namespace-doc + print-str printf println println-str prn prn-str promise proxy + proxy-mappings proxy-super push-thread-bindings pvalues quot rand + rand-int range ratio? rationalize re-find re-groups re-matcher + re-matches re-pattern re-seq read read-line read-string reduce ref + ref-history-count ref-max-history ref-min-history ref-set refer + refer-clojure reify release-pending-sends rem remove remove-all-methods + remove-method remove-ns remove-watch repeat repeatedly replace replicate + require reset! reset-meta! resolve rest restart-agent resultset-seq + reverse reversible? rseq rsubseq satisfies? second select-keys send + send-off seq seq? seque sequence sequential? set set-error-handler! + set-error-mode! set-validator! set? short short-array shorts + shutdown-agents slurp some sort sort-by sorted-map sorted-map-by + sorted-set sorted-set-by sorted? special-form-anchor special-symbol? + split-at split-with str string? struct struct-map subs subseq subvec + supers swap! symbol symbol? sync syntax-symbol-anchor take take-last + take-nth take-while test the-ns thread-bound? time to-array to-array-2d + trampoline transient tree-seq true? type unchecked-add unchecked-dec + unchecked-divide unchecked-inc unchecked-multiply unchecked-negate + unchecked-remainder unchecked-subtract underive update-in update-proxy + use val vals var-get var-set var? vary-meta vec vector vector-of vector? + when when-first when-let when-not while with-bindings with-bindings* + with-in-str with-local-vars with-meta with-open with-out-str + with-precision xml-seq zero? zipmap + ] # :nodoc: + + PREDEFINED_CONSTANTS = %w[ + true false nil *1 *2 *3 *agent* *clojure-version* *command-line-args* + *compile-files* *compile-path* *e *err* *file* *flush-on-newline* + *in* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* + *print-readably* *read-eval* *warn-on-reflection* + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(SPECIAL_FORMS, :keyword). + add(CORE_FORMS, :keyword). + add(PREDEFINED_CONSTANTS, :predefined_constant) + + KEYWORD_NEXT_TOKEN_KIND = WordList.new(nil). + add(%w[ def defn defn- definline defmacro defmulti defmethod defstruct defonce declare ], :function). + add(%w[ ns ], :namespace). + add(%w[ defprotocol defrecord ], :class) + + BASIC_IDENTIFIER = /[a-zA-Z$%*\/_+!?&<>\-=]=?[a-zA-Z0-9$&*+!\/_?<>\-\#]*/ + IDENTIFIER = /(?!-\d)(?:(?:#{BASIC_IDENTIFIER}\.)*#{BASIC_IDENTIFIER}(?:\/#{BASIC_IDENTIFIER})?\.?)|\.\.?/ + SYMBOL = /::?#{IDENTIFIER}/o + DIGIT = /\d/ + DIGIT10 = DIGIT + DIGIT16 = /[0-9a-f]/i + DIGIT8 = /[0-7]/ + DIGIT2 = /[01]/ + RADIX16 = /\#x/i + RADIX8 = /\#o/i + RADIX2 = /\#b/i + RADIX10 = /\#d/i + EXACTNESS = /#i|#e/i + SIGN = /[\+-]?/ + EXP_MARK = /[esfdl]/i + EXP = /#{EXP_MARK}#{SIGN}#{DIGIT}+/ + SUFFIX = /#{EXP}?/ + PREFIX10 = /#{RADIX10}?#{EXACTNESS}?|#{EXACTNESS}?#{RADIX10}?/ + PREFIX16 = /#{RADIX16}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX16}/ + PREFIX8 = /#{RADIX8}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX8}/ + PREFIX2 = /#{RADIX2}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX2}/ + UINT10 = /#{DIGIT10}+#*/ + UINT16 = /#{DIGIT16}+#*/ + UINT8 = /#{DIGIT8}+#*/ + UINT2 = /#{DIGIT2}+#*/ + DECIMAL = /#{DIGIT10}+#+\.#*#{SUFFIX}|#{DIGIT10}+\.#{DIGIT10}*#*#{SUFFIX}|\.#{DIGIT10}+#*#{SUFFIX}|#{UINT10}#{EXP}/ + UREAL10 = /#{UINT10}\/#{UINT10}|#{DECIMAL}|#{UINT10}/ + UREAL16 = /#{UINT16}\/#{UINT16}|#{UINT16}/ + UREAL8 = /#{UINT8}\/#{UINT8}|#{UINT8}/ + UREAL2 = /#{UINT2}\/#{UINT2}|#{UINT2}/ + REAL10 = /#{SIGN}#{UREAL10}/ + REAL16 = /#{SIGN}#{UREAL16}/ + REAL8 = /#{SIGN}#{UREAL8}/ + REAL2 = /#{SIGN}#{UREAL2}/ + IMAG10 = /i|#{UREAL10}i/ + IMAG16 = /i|#{UREAL16}i/ + IMAG8 = /i|#{UREAL8}i/ + IMAG2 = /i|#{UREAL2}i/ + COMPLEX10 = /#{REAL10}@#{REAL10}|#{REAL10}\+#{IMAG10}|#{REAL10}-#{IMAG10}|\+#{IMAG10}|-#{IMAG10}|#{REAL10}/ + COMPLEX16 = /#{REAL16}@#{REAL16}|#{REAL16}\+#{IMAG16}|#{REAL16}-#{IMAG16}|\+#{IMAG16}|-#{IMAG16}|#{REAL16}/ + COMPLEX8 = /#{REAL8}@#{REAL8}|#{REAL8}\+#{IMAG8}|#{REAL8}-#{IMAG8}|\+#{IMAG8}|-#{IMAG8}|#{REAL8}/ + COMPLEX2 = /#{REAL2}@#{REAL2}|#{REAL2}\+#{IMAG2}|#{REAL2}-#{IMAG2}|\+#{IMAG2}|-#{IMAG2}|#{REAL2}/ + NUM10 = /#{PREFIX10}?#{COMPLEX10}/ + NUM16 = /#{PREFIX16}#{COMPLEX16}/ + NUM8 = /#{PREFIX8}#{COMPLEX8}/ + NUM2 = /#{PREFIX2}#{COMPLEX2}/ + NUM = /#{NUM10}|#{NUM16}|#{NUM8}|#{NUM2}/ + + protected + + def scan_tokens encoder, options + + state = :initial + kind = nil + + until eos? + + case state + when :initial + if match = scan(/ \s+ | \\\n | , /x) + encoder.text_token match, :space + elsif match = scan(/['`\(\[\)\]\{\}]|\#[({]|~@?|[@\^]/) + encoder.text_token match, :operator + elsif match = scan(/;.*/) + encoder.text_token match, :comment # TODO: recognize (comment ...) too + elsif match = scan(/\#?\\(?:newline|space|.?)/) + encoder.text_token match, :char + elsif match = scan(/\#[ft]/) + encoder.text_token match, :predefined_constant + elsif match = scan(/#{IDENTIFIER}/o) + kind = IDENT_KIND[match] + encoder.text_token match, kind + if rest? && kind == :keyword + if kind = KEYWORD_NEXT_TOKEN_KIND[match] + encoder.text_token match, :space if match = scan(/\s+/o) + encoder.text_token match, kind if match = scan(/#{IDENTIFIER}/o) + end + end + elsif match = scan(/#{SYMBOL}/o) + encoder.text_token match, :symbol + elsif match = scan(/\./) + encoder.text_token match, :operator + elsif match = scan(/ \# \^ #{IDENTIFIER} /ox) + encoder.text_token match, :type + elsif match = scan(/ (\#)? " /x) + state = self[1] ? :regexp : :string + encoder.begin_group state + encoder.text_token match, :delimiter + elsif match = scan(/#{NUM}/o) and not matched.empty? + encoder.text_token match, match[/[.e\/]/i] ? :float : :integer + else + encoder.text_token getch, :error + end + + when :string, :regexp + if match = scan(/[^"\\]+|\\.?/) + encoder.text_token match, :content + elsif match = scan(/"/) + encoder.text_token match, :delimiter + encoder.end_group state + state = :initial + else + raise_inspect "else case \" reached; %p not handled." % peek(1), + encoder, state + end + + else + raise 'else case reached' + + end + + end + + if [:string, :regexp].include? state + encoder.end_group state + end + + encoder + + end + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/cpp.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/cpp.rb new file mode 100644 index 0000000000..cd4d09414f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/cpp.rb @@ -0,0 +1,217 @@ +module CodeRay +module Scanners + + # Scanner for C++. + # + # Aliases: +cplusplus+, c++ + class CPlusPlus < Scanner + + register_for :cpp + file_extension 'cpp' + title 'C++' + + #-- http://www.cppreference.com/wiki/keywords/start + KEYWORDS = [ + 'and', 'and_eq', 'asm', 'bitand', 'bitor', 'break', + 'case', 'catch', 'class', 'compl', 'const_cast', + 'continue', 'default', 'delete', 'do', 'dynamic_cast', 'else', + 'enum', 'export', 'for', 'goto', 'if', 'namespace', 'new', + 'not', 'not_eq', 'or', 'or_eq', 'reinterpret_cast', 'return', + 'sizeof', 'static_assert', 'static_cast', 'struct', 'switch', + 'template', 'throw', 'try', 'typedef', 'typeid', 'typename', 'union', + 'while', 'xor', 'xor_eq', + ] # :nodoc: + + PREDEFINED_TYPES = [ + 'bool', 'char', 'char16_t', 'char32_t', 'double', 'float', + 'int', 'long', 'short', 'signed', 'unsigned', + 'wchar_t', 'string', + ] # :nodoc: + PREDEFINED_CONSTANTS = [ + 'false', 'true', + 'EOF', 'NULL', 'nullptr' + ] # :nodoc: + PREDEFINED_VARIABLES = [ + 'this', + ] # :nodoc: + DIRECTIVES = [ + 'alignas', 'alignof', 'auto', 'const', 'constexpr', 'decltype', 'explicit', + 'extern', 'final', 'friend', 'inline', 'mutable', 'noexcept', 'operator', + 'override', 'private', 'protected', 'public', 'register', 'static', + 'thread_local', 'using', 'virtual', 'void', 'volatile', + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(PREDEFINED_TYPES, :predefined_type). + add(PREDEFINED_VARIABLES, :local_variable). + add(DIRECTIVES, :directive). + add(PREDEFINED_CONSTANTS, :predefined_constant) # :nodoc: + + ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + label_expected = true + case_expected = false + label_expected_before_preproc_line = nil + in_preproc_line = false + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + if in_preproc_line && match != "\\\n" && match.index(?\n) + in_preproc_line = false + label_expected = label_expected_before_preproc_line + end + encoder.text_token match, :space + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) + encoder.text_token match, :comment + + elsif match = scan(/ \# \s* if \s* 0 /x) + match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos? + encoder.text_token match, :comment + + elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x) + label_expected = match =~ /[;\{\}]/ + if case_expected + label_expected = true if match == ':' + case_expected = false + end + encoder.text_token match, :operator + + elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) + kind = IDENT_KIND[match] + if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/) + kind = :label + match << matched + else + label_expected = false + if kind == :keyword + case match + when 'class' + state = :class_name_expected + when 'case', 'default' + case_expected = true + end + end + end + encoder.text_token match, kind + + elsif match = scan(/\$/) + encoder.text_token match, :ident + + elsif match = scan(/L?"/) + encoder.begin_group :string + if match[0] == ?L + encoder.text_token match, 'L', :modifier + match = '"' + end + state = :string + encoder.text_token match, :delimiter + + elsif match = scan(/#[ \t]*(\w*)/) + encoder.text_token match, :preprocessor + in_preproc_line = true + label_expected_before_preproc_line = label_expected + state = :include_expected if self[1] == 'include' + + elsif match = scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox) + label_expected = false + encoder.text_token match, :char + + elsif match = scan(/0[xX][0-9A-Fa-f]+/) + label_expected = false + encoder.text_token match, :hex + + elsif match = scan(/(?:0[0-7]+)(?![89.eEfF])/) + label_expected = false + encoder.text_token match, :octal + + elsif match = scan(/(?:\d+)(?![.eEfF])L?L?/) + label_expected = false + encoder.text_token match, :integer + + elsif match = scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) + label_expected = false + encoder.text_token match, :float + + else + encoder.text_token getch, :error + + end + + when :string + if match = scan(/[^\\"]+/) + encoder.text_token match, :content + elsif match = scan(/"/) + encoder.text_token match, :delimiter + encoder.end_group :string + state = :initial + label_expected = false + elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/ \\ | $ /x) + encoder.end_group :string + encoder.text_token match, :error unless match.empty? + state = :initial + label_expected = false + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + end + + when :include_expected + if match = scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/) + encoder.text_token match, :include + state = :initial + + elsif match = scan(/\s+/) + encoder.text_token match, :space + state = :initial if match.index ?\n + + else + state = :initial + + end + + when :class_name_expected + if match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) + encoder.text_token match, :class + state = :initial + + elsif match = scan(/\s+/) + encoder.text_token match, :space + + else + encoder.text_token getch, :error + state = :initial + + end + + else + raise_inspect 'Unknown state', encoder + + end + + end + + if state == :string + encoder.end_group :string + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/css.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/css.rb new file mode 100644 index 0000000000..55d5239783 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/css.rb @@ -0,0 +1,196 @@ +module CodeRay +module Scanners + + class CSS < Scanner + + register_for :css + + KINDS_NOT_LOC = [ + :comment, + :class, :pseudo_class, :tag, + :id, :directive, + :key, :value, :operator, :color, :float, :string, + :error, :important, :type, + ] # :nodoc: + + module RE # :nodoc: + Hex = /[0-9a-fA-F]/ + Unicode = /\\#{Hex}{1,6}\b/ # differs from standard because it allows uppercase hex too + Escape = /#{Unicode}|\\[^\n0-9a-fA-F]/ + NMChar = /[-_a-zA-Z0-9]/ + NMStart = /[_a-zA-Z]/ + String1 = /"(?:[^\n\\"]+|\\\n|#{Escape})*"?/ # TODO: buggy regexp + String2 = /'(?:[^\n\\']+|\\\n|#{Escape})*'?/ # TODO: buggy regexp + String = /#{String1}|#{String2}/ + + HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/ + + Num = /-?(?:[0-9]*\.[0-9]+|[0-9]+)n?/ + Name = /#{NMChar}+/ + Ident = /-?#{NMStart}#{NMChar}*/ + AtKeyword = /@#{Ident}/ + Percentage = /#{Num}%/ + + reldimensions = %w[em ex px] + absdimensions = %w[in cm mm pt pc] + Unit = Regexp.union(*(reldimensions + absdimensions + %w[s dpi dppx deg])) + + Dimension = /#{Num}#{Unit}/ + + Function = /(?:url|alpha|attr|counters?)\((?:[^)\n]|\\\))*\)?/ + + Id = /(?!#{HexColor}\b(?!-))##{Name}/ + Class = /\.#{Name}/ + PseudoClass = /::?#{Ident}/ + AttributeSelector = /\[[^\]]*\]?/ + end + + protected + + def setup + @state = :initial + @value_expected = false + end + + def scan_tokens encoder, options + states = Array(options[:state] || @state).dup + value_expected = @value_expected + + until eos? + + if match = scan(/\s+/) + encoder.text_token match, :space + + elsif case states.last + when :initial, :media + if match = scan(/(?>#{RE::Ident})(?!\()|\*/ox) + encoder.text_token match, :tag + next + elsif match = scan(RE::Class) + encoder.text_token match, :class + next + elsif match = scan(RE::Id) + encoder.text_token match, :id + next + elsif match = scan(RE::PseudoClass) + encoder.text_token match, :pseudo_class + next + elsif match = scan(RE::AttributeSelector) + # TODO: Improve highlighting inside of attribute selectors. + encoder.text_token match[0,1], :operator + encoder.text_token match[1..-2], :attribute_name if match.size > 2 + encoder.text_token match[-1,1], :operator if match[-1] == ?] + next + elsif match = scan(/@media/) + encoder.text_token match, :directive + states.push :media_before_name + next + end + + when :block + if match = scan(/(?>#{RE::Ident})(?!\()/ox) + if value_expected + encoder.text_token match, :value + else + encoder.text_token match, :key + end + next + end + + when :media_before_name + if match = scan(RE::Ident) + encoder.text_token match, :type + states[-1] = :media_after_name + next + end + + when :media_after_name + if match = scan(/\{/) + encoder.text_token match, :operator + states[-1] = :media + next + end + + else + #:nocov: + raise_inspect 'Unknown state', encoder + #:nocov: + + end + + elsif match = scan(/\/\*(?:.*?\*\/|\z)/m) + encoder.text_token match, :comment + + elsif match = scan(/\{/) + value_expected = false + encoder.text_token match, :operator + states.push :block + + elsif match = scan(/\}/) + value_expected = false + encoder.text_token match, :operator + if states.last == :block || states.last == :media + states.pop + end + + elsif match = scan(/#{RE::String}/o) + encoder.begin_group :string + encoder.text_token match[0, 1], :delimiter + encoder.text_token match[1..-2], :content if match.size > 2 + encoder.text_token match[-1, 1], :delimiter if match.size >= 2 + encoder.end_group :string + + elsif match = scan(/#{RE::Function}/o) + encoder.begin_group :function + start = match[/^\w+\(/] + encoder.text_token start, :delimiter + if match[-1] == ?) + encoder.text_token match[start.size..-2], :content if match.size > start.size + 1 + encoder.text_token ')', :delimiter + else + encoder.text_token match[start.size..-1], :content if match.size > start.size + end + encoder.end_group :function + + elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) + encoder.text_token match, :float + + elsif match = scan(/#{RE::HexColor}/o) + encoder.text_token match, :color + + elsif match = scan(/! *important/) + encoder.text_token match, :important + + elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/) + encoder.text_token match, :color + + elsif match = scan(RE::AtKeyword) + encoder.text_token match, :directive + + elsif match = scan(/ [+>~:;,.=()\/] /x) + if match == ':' + value_expected = true + elsif match == ';' + value_expected = false + end + encoder.text_token match, :operator + + else + encoder.text_token getch, :error + + end + + end + + if options[:keep_state] + @state = states + @value_expected = value_expected + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/debug.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/debug.rb new file mode 100644 index 0000000000..83ede9a585 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/debug.rb @@ -0,0 +1,75 @@ +require 'set' + +module CodeRay +module Scanners + + # = Debug Scanner + # + # Interprets the output of the Encoders::Debug encoder (basically the inverse function). + class Debug < Scanner + + register_for :debug + title 'CodeRay Token Dump Import' + + protected + + def setup + super + @known_token_kinds = TokenKinds.keys.map(&:to_s).to_set + end + + def scan_tokens encoder, options + + opened_tokens = [] + + until eos? + + if match = scan(/\s+/) + encoder.text_token match, :space + + elsif match = scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) \)? /x) + if @known_token_kinds.include? self[1] + encoder.text_token self[2].gsub(/\\(.)/m, '\1'), self[1].to_sym + else + encoder.text_token matched, :unknown + end + + elsif match = scan(/ (\w+) ([<\[]) /x) + if @known_token_kinds.include? self[1] + kind = self[1].to_sym + else + kind = :unknown + end + + opened_tokens << kind + case self[2] + when '<' + encoder.begin_group kind + when '[' + encoder.begin_line kind + else + raise 'CodeRay bug: This case should not be reached.' + end + + elsif !opened_tokens.empty? && match = scan(/ > /x) + encoder.end_group opened_tokens.pop + + elsif !opened_tokens.empty? && match = scan(/ \] /x) + encoder.end_line opened_tokens.pop + + else + encoder.text_token getch, :space + + end + + end + + encoder.end_group opened_tokens.pop until opened_tokens.empty? + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/delphi.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/delphi.rb new file mode 100644 index 0000000000..b328155abf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/delphi.rb @@ -0,0 +1,144 @@ +module CodeRay +module Scanners + + # Scanner for the Delphi language (Object Pascal). + # + # Alias: +pascal+ + class Delphi < Scanner + + register_for :delphi + file_extension 'pas' + + KEYWORDS = [ + 'and', 'array', 'as', 'at', 'asm', 'at', 'begin', 'case', 'class', + 'const', 'constructor', 'destructor', 'dispinterface', 'div', 'do', + 'downto', 'else', 'end', 'except', 'exports', 'file', 'finalization', + 'finally', 'for', 'function', 'goto', 'if', 'implementation', 'in', + 'inherited', 'initialization', 'inline', 'interface', 'is', 'label', + 'library', 'mod', 'nil', 'not', 'object', 'of', 'or', 'out', 'packed', + 'procedure', 'program', 'property', 'raise', 'record', 'repeat', + 'resourcestring', 'set', 'shl', 'shr', 'string', 'then', 'threadvar', + 'to', 'try', 'type', 'unit', 'until', 'uses', 'var', 'while', 'with', + 'xor', 'on', + ] # :nodoc: + + DIRECTIVES = [ + 'absolute', 'abstract', 'assembler', 'at', 'automated', 'cdecl', + 'contains', 'deprecated', 'dispid', 'dynamic', 'export', + 'external', 'far', 'forward', 'implements', 'local', + 'near', 'nodefault', 'on', 'overload', 'override', + 'package', 'pascal', 'platform', 'private', 'protected', 'public', + 'published', 'read', 'readonly', 'register', 'reintroduce', + 'requires', 'resident', 'safecall', 'stdcall', 'stored', 'varargs', + 'virtual', 'write', 'writeonly', + ] # :nodoc: + + IDENT_KIND = WordList::CaseIgnoring.new(:ident). + add(KEYWORDS, :keyword). + add(DIRECTIVES, :directive) # :nodoc: + + NAME_FOLLOWS = WordList::CaseIgnoring.new(false). + add(%w(procedure function .)) # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + last_token = '' + + until eos? + + if state == :initial + + if match = scan(/ \s+ /x) + encoder.text_token match, :space + next + + elsif match = scan(%r! \{ \$ [^}]* \}? | \(\* \$ (?: .*? \*\) | .* ) !mx) + encoder.text_token match, :preprocessor + next + + elsif match = scan(%r! // [^\n]* | \{ [^}]* \}? | \(\* (?: .*? \*\) | .* ) !mx) + encoder.text_token match, :comment + next + + elsif match = scan(/ <[>=]? | >=? | :=? | [-+=*\/;,@\^|\(\)\[\]] | \.\. /x) + encoder.text_token match, :operator + + elsif match = scan(/\./) + encoder.text_token match, :operator + next if last_token == 'end' + + elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) + encoder.text_token match, NAME_FOLLOWS[last_token] ? :ident : IDENT_KIND[match] + + elsif match = skip(/ ' ( [^\n']|'' ) (?:'|$) /x) + encoder.begin_group :char + encoder.text_token "'", :delimiter + encoder.text_token self[1], :content + encoder.text_token "'", :delimiter + encoder.end_group :char + next + + elsif match = scan(/ ' /x) + encoder.begin_group :string + encoder.text_token match, :delimiter + state = :string + + elsif match = scan(/ \# (?: \d+ | \$[0-9A-Fa-f]+ ) /x) + encoder.text_token match, :char + + elsif match = scan(/ \$ [0-9A-Fa-f]+ /x) + encoder.text_token match, :hex + + elsif match = scan(/ (?: \d+ ) (?![eE]|\.[^.]) /x) + encoder.text_token match, :integer + + elsif match = scan(/ \d+ (?: \.\d+ (?: [eE][+-]? \d+ )? | [eE][+-]? \d+ ) /x) + encoder.text_token match, :float + + else + encoder.text_token getch, :error + next + + end + + elsif state == :string + if match = scan(/[^\n']+/) + encoder.text_token match, :content + elsif match = scan(/''/) + encoder.text_token match, :char + elsif match = scan(/'/) + encoder.text_token match, :delimiter + encoder.end_group :string + state = :initial + next + elsif match = scan(/\n/) + encoder.end_group :string + encoder.text_token match, :space + state = :initial + else + raise "else case \' reached; %p not handled." % peek(1), encoder + end + + else + raise 'else-case reached', encoder + + end + + last_token = match + + end + + if state == :string + encoder.end_group state + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/diff.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/diff.rb new file mode 100644 index 0000000000..a2a6fccf27 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/diff.rb @@ -0,0 +1,221 @@ +module CodeRay +module Scanners + + # Scanner for output of the diff command. + # + # Alias: +patch+ + class Diff < Scanner + + register_for :diff + title 'diff output' + + DEFAULT_OPTIONS = { + :highlight_code => true, + :inline_diff => true, + } + + protected + + def scan_tokens encoder, options + + line_kind = nil + state = :initial + deleted_lines_count = 0 + scanners = Hash.new do |h, lang| + h[lang] = Scanners[lang].new '', :keep_tokens => true, :keep_state => true + end + content_scanner = scanners[:plain] + content_scanner_entry_state = nil + + until eos? + + if match = scan(/\n/) + deleted_lines_count = 0 unless line_kind == :delete + if line_kind + encoder.end_line line_kind + line_kind = nil + end + encoder.text_token match, :space + next + end + + case state + + when :initial + if match = scan(/--- |\+\+\+ |=+|_+/) + encoder.begin_line line_kind = :head + encoder.text_token match, :head + if match = scan(/[^\x00\n]+?(?=$|[\t\n]| \(revision)/) + encoder.text_token match, :filename + if options[:highlight_code] && match != '/dev/null' + file_type = CodeRay::FileType.fetch(match, :text) + file_type = :text if file_type == :diff + content_scanner = scanners[file_type] + content_scanner_entry_state = nil + end + end + next unless match = scan(/.+/) + encoder.text_token match, :plain + elsif match = scan(/Index: |Property changes on: /) + encoder.begin_line line_kind = :head + encoder.text_token match, :head + next unless match = scan(/.+/) + encoder.text_token match, :plain + elsif match = scan(/Added: /) + encoder.begin_line line_kind = :head + encoder.text_token match, :head + next unless match = scan(/.+/) + encoder.text_token match, :plain + state = :added + elsif match = scan(/\\ .*/) + encoder.text_token match, :comment + elsif match = scan(/@@(?>[^@\n]+)@@/) + content_scanner.state = :initial unless match?(/\n\+/) + content_scanner_entry_state = nil + if check(/\n|$/) + encoder.begin_line line_kind = :change + else + encoder.begin_group :change + end + encoder.text_token match[0,2], :change + encoder.text_token match[2...-2], :plain + encoder.text_token match[-2,2], :change + encoder.end_group :change unless line_kind + next unless match = scan(/.+/) + if options[:highlight_code] + content_scanner.tokenize match, :tokens => encoder + else + encoder.text_token match, :plain + end + next + elsif match = scan(/\+/) + encoder.begin_line line_kind = :insert + encoder.text_token match, :insert + next unless match = scan(/.+/) + if options[:highlight_code] + content_scanner.tokenize match, :tokens => encoder + else + encoder.text_token match, :plain + end + next + elsif match = scan(/-/) + deleted_lines_count += 1 + if options[:inline_diff] && deleted_lines_count == 1 && (changed_lines_count = 1 + check(/.*(?:\n\-.*)*/).count("\n")) && changed_lines_count <= 100_000 && match?(/(?>.*(?:\n\-.*){#{changed_lines_count - 1}}(?:\n\+.*){#{changed_lines_count}})$(?!\n\+)/) + deleted_lines = Array.new(changed_lines_count) { |i| skip(/\n\-/) if i > 0; scan(/.*/) } + inserted_lines = Array.new(changed_lines_count) { |i| skip(/\n\+/) ; scan(/.*/) } + + deleted_lines_tokenized = [] + inserted_lines_tokenized = [] + for deleted_line, inserted_line in deleted_lines.zip(inserted_lines) + pre, deleted_part, inserted_part, post = diff deleted_line, inserted_line + content_scanner_entry_state = content_scanner.state + deleted_lines_tokenized << content_scanner.tokenize([pre, deleted_part, post], :tokens => Tokens.new) + content_scanner.state = content_scanner_entry_state || :initial + inserted_lines_tokenized << content_scanner.tokenize([pre, inserted_part, post], :tokens => Tokens.new) + end + + for pre, deleted_part, post in deleted_lines_tokenized + encoder.begin_line :delete + encoder.text_token '-', :delete + encoder.tokens pre + unless deleted_part.empty? + encoder.begin_group :eyecatcher + encoder.tokens deleted_part + encoder.end_group :eyecatcher + end + encoder.tokens post + encoder.end_line :delete + encoder.text_token "\n", :space + end + + for pre, inserted_part, post in inserted_lines_tokenized + encoder.begin_line :insert + encoder.text_token '+', :insert + encoder.tokens pre + unless inserted_part.empty? + encoder.begin_group :eyecatcher + encoder.tokens inserted_part + encoder.end_group :eyecatcher + end + encoder.tokens post + changed_lines_count -= 1 + if changed_lines_count > 0 + encoder.end_line :insert + encoder.text_token "\n", :space + end + end + + line_kind = :insert + + elsif match = scan(/.*/) + encoder.begin_line line_kind = :delete + encoder.text_token '-', :delete + if options[:highlight_code] + if deleted_lines_count == 1 + content_scanner_entry_state = content_scanner.state + end + content_scanner.tokenize match, :tokens => encoder unless match.empty? + if !match?(/\n-/) + if match?(/\n\+/) + content_scanner.state = content_scanner_entry_state || :initial + end + content_scanner_entry_state = nil + end + else + encoder.text_token match, :plain + end + end + next + elsif match = scan(/ .*/) + if options[:highlight_code] + content_scanner.tokenize match, :tokens => encoder + else + encoder.text_token match, :plain + end + next + elsif match = scan(/.+/) + encoder.begin_line line_kind = :comment + encoder.text_token match, :plain + else + raise_inspect 'else case rached' + end + + when :added + if match = scan(/ \+/) + encoder.begin_line line_kind = :insert + encoder.text_token match, :insert + next unless match = scan(/.+/) + encoder.text_token match, :plain + else + state = :initial + next + end + end + + end + + encoder.end_line line_kind if line_kind + + encoder + end + + private + + def diff a, b + # i will be the index of the leftmost difference from the left. + i_max = [a.size, b.size].min + i = 0 + i += 1 while i < i_max && a[i] == b[i] + # j_min will be the index of the leftmost difference from the right. + j_min = i - i_max + # j will be the index of the rightmost difference from the right which + # does not precede the leftmost one from the left. + j = -1 + j -= 1 while j >= j_min && a[j] == b[j] + return a[0...i], a[i..j], b[i..j], (j < -1) ? a[j + 1..-1] : '' + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/erb.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/erb.rb new file mode 100644 index 0000000000..727a993bf9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/erb.rb @@ -0,0 +1,81 @@ +module CodeRay +module Scanners + + load :html + load :ruby + + # Scanner for HTML ERB templates. + class ERB < Scanner + + register_for :erb + title 'HTML ERB Template' + + KINDS_NOT_LOC = HTML::KINDS_NOT_LOC + + ERB_RUBY_BLOCK = / + (<%(?!%)[-=\#]?) + ((?> + [^\-%]* # normal* + (?> # special + (?: %(?!>) | -(?!%>) ) + [^\-%]* # normal* + )* + )) + ((?: -?%> )?) + /x # :nodoc: + + START_OF_ERB = / + <%(?!%) + /x # :nodoc: + + protected + + def setup + @ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true + @html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true + end + + def reset_instance + super + @html_scanner.reset + end + + def scan_tokens encoder, options + + until eos? + + if (match = scan_until(/(?=#{START_OF_ERB})/o) || scan_rest) and not match.empty? + @html_scanner.tokenize match, :tokens => encoder + + elsif match = scan(/#{ERB_RUBY_BLOCK}/o) + start_tag = self[1] + code = self[2] + end_tag = self[3] + + encoder.begin_group :inline + encoder.text_token start_tag, :inline_delimiter + + if start_tag == '<%#' + encoder.text_token code, :comment + else + @ruby_scanner.tokenize code, :tokens => encoder + end unless code.empty? + + encoder.text_token end_tag, :inline_delimiter unless end_tag.empty? + encoder.end_group :inline + + else + raise_inspect 'else-case reached!', encoder + + end + + end + + encoder + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/go.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/go.rb new file mode 100644 index 0000000000..99fdd638e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/go.rb @@ -0,0 +1,208 @@ +module CodeRay +module Scanners + + class Go < Scanner + + register_for :go + file_extension 'go' + + # http://golang.org/ref/spec#Keywords + KEYWORDS = [ + 'break', 'default', 'func', 'interface', 'select', + 'case', 'defer', 'go', 'map', 'struct', + 'chan', 'else', 'goto', 'package', 'switch', + 'const', 'fallthrough', 'if', 'range', 'type', + 'continue', 'for', 'import', 'return', 'var', + ] # :nodoc: + + # http://golang.org/ref/spec#Types + PREDEFINED_TYPES = [ + 'bool', + 'uint8', 'uint16', 'uint32', 'uint64', + 'int8', 'int16', 'int32', 'int64', + 'float32', 'float64', + 'complex64', 'complex128', + 'byte', 'rune', 'string', 'error', + 'uint', 'int', 'uintptr', + ] # :nodoc: + + PREDEFINED_CONSTANTS = [ + 'nil', 'iota', + 'true', 'false', + ] # :nodoc: + + PREDEFINED_FUNCTIONS = %w[ + append cap close complex copy delete imag len + make new panic print println real recover + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(PREDEFINED_TYPES, :predefined_type). + add(PREDEFINED_CONSTANTS, :predefined_constant). + add(PREDEFINED_FUNCTIONS, :predefined) # :nodoc: + + ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + label_expected = true + case_expected = false + label_expected_before_preproc_line = nil + in_preproc_line = false + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + if in_preproc_line && match != "\\\n" && match.index(?\n) + in_preproc_line = false + case_expected = false + label_expected = label_expected_before_preproc_line + end + encoder.text_token match, :space + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) + encoder.text_token match, :comment + + elsif match = scan(/ ?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x) + if case_expected + label_expected = true if match == ':' + case_expected = false + end + encoder.text_token match, :operator + + elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) + kind = IDENT_KIND[match] + if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/) + kind = :label + label_expected = false + match << matched + else + label_expected = false + if kind == :keyword + case match + when 'case', 'default' + case_expected = true + end + end + end + encoder.text_token match, kind + + elsif match = scan(/L?"/) + encoder.begin_group :string + if match[0] == ?L + encoder.text_token 'L', :modifier + match = '"' + end + encoder.text_token match, :delimiter + state = :string + + elsif match = scan(/ ` ([^`]+)? (`)? /x) + encoder.begin_group :shell + encoder.text_token '`', :delimiter + encoder.text_token self[1], :content if self[1] + encoder.text_token self[2], :delimiter if self[2] + encoder.end_group :shell + + elsif match = scan(/ \# \s* if \s* 0 /x) + match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos? + encoder.text_token match, :comment + + elsif match = scan(/#[ \t]*(\w*)/) + encoder.text_token match, :preprocessor + in_preproc_line = true + label_expected_before_preproc_line = label_expected + state = :include_expected if self[1] == 'include' + + elsif match = scan(/ L?' (?: [^\'\n\\] | \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) )? '? /ox) + label_expected = false + encoder.text_token match, :char + + elsif match = scan(/\$/) + encoder.text_token match, :ident + + elsif match = scan(/-?\d*(\.\d*)?([eE][+-]?\d+)?i/) + label_expected = false + encoder.text_token match, :imaginary + + elsif match = scan(/-?0[xX][0-9A-Fa-f]+/) + label_expected = false + encoder.text_token match, :hex + + elsif match = scan(/-?(?:0[0-7]+)(?![89.eEfF])/) + label_expected = false + encoder.text_token match, :octal + + elsif match = scan(/-?(?:\d*\.\d+|\d+\.)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+/) + label_expected = false + encoder.text_token match, :float + + elsif match = scan(/-?(?:\d+)(?![.eEfF])L?L?/) + label_expected = false + encoder.text_token match, :integer + + else + encoder.text_token getch, :error + + end + + when :string + if match = scan(/[^\\\n"]+/) + encoder.text_token match, :content + elsif match = scan(/"/) + encoder.text_token match, :delimiter + encoder.end_group :string + state = :initial + label_expected = false + elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/ \\ /x) + encoder.text_token match, :error + elsif match = scan(/$/) + encoder.end_group :string + state = :initial + label_expected = false + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + end + + when :include_expected + if match = scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/) + encoder.text_token match, :include + state = :initial + + elsif match = scan(/\s+/) + encoder.text_token match, :space + state = :initial if match.index ?\n + + else + state = :initial + + end + + else + raise_inspect 'Unknown state', encoder + + end + + end + + if state == :string + encoder.end_group :string + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/groovy.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/groovy.rb new file mode 100644 index 0000000000..c52ce8d316 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/groovy.rb @@ -0,0 +1,268 @@ +module CodeRay +module Scanners + + load :java + + # Scanner for Groovy. + class Groovy < Java + + register_for :groovy + + # TODO: check list of keywords + GROOVY_KEYWORDS = %w[ + as assert def in + ] # :nodoc: + KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ + case instanceof new return throw typeof while as assert in + ] # :nodoc: + GROOVY_MAGIC_VARIABLES = %w[ it ] # :nodoc: + + IDENT_KIND = Java::IDENT_KIND.dup. + add(GROOVY_KEYWORDS, :keyword). + add(GROOVY_MAGIC_VARIABLES, :local_variable) # :nodoc: + + ESCAPE = / [bfnrtv$\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x # :nodoc: no 4-byte unicode chars? U[a-fA-F0-9]{8} + REGEXP_ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | \d | [bBdDsSwW\/] /x # :nodoc: + + # TODO: interpretation inside ', ", / + STRING_CONTENT_PATTERN = { + "'" => /(?>\\[^\\'\n]+|[^\\'\n]+)+/, + '"' => /[^\\$"\n]+/, + "'''" => /(?>[^\\']+|'(?!''))+/, + '"""' => /(?>[^\\$"]+|"(?!""))+/, + '/' => /[^\\$\/\n]+/, + } # :nodoc: + + protected + + def setup + @state = :initial + end + + def scan_tokens encoder, options + state = options[:state] || @state + inline_block_stack = [] + inline_block_paren_depth = nil + string_delimiter = nil + import_clause = class_name_follows = last_token = after_def = false + value_expected = true + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + encoder.text_token match, :space + if match.index ?\n + import_clause = after_def = false + value_expected = true unless value_expected + end + next + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) + value_expected = true + after_def = false + encoder.text_token match, :comment + + elsif bol? && match = scan(/ \#!.* /x) + encoder.text_token match, :doctype + + elsif import_clause && match = scan(/ (?!as) #{IDENT} (?: \. #{IDENT} )* (?: \.\* )? /ox) + after_def = value_expected = false + encoder.text_token match, :include + + elsif match = scan(/ #{IDENT} | \[\] /ox) + kind = IDENT_KIND[match] + value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] + if last_token == '.' + kind = :ident + elsif class_name_follows + kind = :class + class_name_follows = false + elsif after_def && check(/\s*[({]/) + kind = :method + after_def = false + elsif kind == :ident && last_token != '?' && check(/:/) + kind = :key + else + class_name_follows = true if match == 'class' || (import_clause && match == 'as') + import_clause = match == 'import' + after_def = true if match == 'def' + end + encoder.text_token match, kind + + elsif match = scan(/;/) + import_clause = after_def = false + value_expected = true + encoder.text_token match, :operator + + elsif match = scan(/\{/) + class_name_follows = after_def = false + value_expected = true + encoder.text_token match, :operator + if !inline_block_stack.empty? + inline_block_paren_depth += 1 + end + + # TODO: ~'...', ~"..." and ~/.../ style regexps + elsif match = scan(/ \.\.] | \+\+ | + && | \|\| | \*\*=? | ==?~ | <=?>? | [-+*%^~&|>=!]=? | <<>>?=? /x) + value_expected = true + value_expected = :regexp if match == '~' + after_def = false + encoder.text_token match, :operator + + elsif match = scan(/ [)\]}] /x) + value_expected = after_def = false + if !inline_block_stack.empty? && match == '}' + inline_block_paren_depth -= 1 + if inline_block_paren_depth == 0 # closing brace of inline block reached + encoder.text_token match, :inline_delimiter + encoder.end_group :inline + state, string_delimiter, inline_block_paren_depth = inline_block_stack.pop + next + end + end + encoder.text_token match, :operator + + elsif check(/[\d.]/) + after_def = value_expected = false + if match = scan(/0[xX][0-9A-Fa-f]+/) + encoder.text_token match, :hex + elsif match = scan(/(?>0[0-7]+)(?![89.eEfF])/) + encoder.text_token match, :octal + elsif match = scan(/\d+[fFdD]|\d*\.\d+(?:[eE][+-]?\d+)?[fFdD]?|\d+[eE][+-]?\d+[fFdD]?/) + encoder.text_token match, :float + elsif match = scan(/\d+[lLgG]?/) + encoder.text_token match, :integer + end + + elsif match = scan(/'''|"""/) + after_def = value_expected = false + state = :multiline_string + encoder.begin_group :string + string_delimiter = match + encoder.text_token match, :delimiter + + # TODO: record.'name' syntax + elsif match = scan(/["']/) + after_def = value_expected = false + state = match == '/' ? :regexp : :string + encoder.begin_group state + string_delimiter = match + encoder.text_token match, :delimiter + + elsif value_expected && match = scan(/\//) + after_def = value_expected = false + encoder.begin_group :regexp + state = :regexp + string_delimiter = '/' + encoder.text_token match, :delimiter + + elsif match = scan(/ @ #{IDENT} /ox) + after_def = value_expected = false + encoder.text_token match, :annotation + + elsif match = scan(/\//) + after_def = false + value_expected = true + encoder.text_token match, :operator + + else + encoder.text_token getch, :error + + end + + when :string, :regexp, :multiline_string + if match = scan(STRING_CONTENT_PATTERN[string_delimiter]) + encoder.text_token match, :content + + elsif match = scan(state == :multiline_string ? /'''|"""/ : /["'\/]/) + encoder.text_token match, :delimiter + if state == :regexp + # TODO: regexp modifiers? s, m, x, i? + modifiers = scan(/[ix]+/) + encoder.text_token modifiers, :modifier if modifiers && !modifiers.empty? + end + state = :string if state == :multiline_string + encoder.end_group state + string_delimiter = nil + after_def = value_expected = false + state = :initial + next + + elsif (state == :string || state == :multiline_string) && + (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) + if string_delimiter[0] == ?' && !(match == "\\\\" || match == "\\'") + encoder.text_token match, :content + else + encoder.text_token match, :char + end + elsif state == :regexp && match = scan(/ \\ (?: #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + + elsif match = scan(/ \$ #{IDENT} /mox) + encoder.begin_group :inline + encoder.text_token '$', :inline_delimiter + match = match[1..-1] + encoder.text_token match, IDENT_KIND[match] + encoder.end_group :inline + next + elsif match = scan(/ \$ \{ /x) + encoder.begin_group :inline + encoder.text_token match, :inline_delimiter + inline_block_stack << [state, string_delimiter, inline_block_paren_depth] + inline_block_paren_depth = 1 + state = :initial + next + + elsif match = scan(/ \$ /mx) + encoder.text_token match, :content + + elsif match = scan(/ \\. /mx) + encoder.text_token match, :content # TODO: Shouldn't this be :error? + + elsif match = scan(/ \\ | \n /x) + encoder.end_group state == :regexp ? :regexp : :string + encoder.text_token match, :error + after_def = value_expected = false + state = :initial + + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + + end + + else + raise_inspect 'Unknown state', encoder + + end + + last_token = match unless [:space, :comment, :doctype].include? kind + + end + + if [:multiline_string, :string, :regexp].include? state + encoder.end_group state == :regexp ? :regexp : :string + end + + if options[:keep_state] + @state = state + end + + until inline_block_stack.empty? + state, = *inline_block_stack.pop + encoder.end_group :inline + encoder.end_group state == :regexp ? :regexp : :string + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/haml.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/haml.rb new file mode 100644 index 0000000000..d516ba9e08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/haml.rb @@ -0,0 +1,168 @@ +module CodeRay +module Scanners + + load :ruby + load :html + load :java_script + + class HAML < Scanner + + register_for :haml + title 'HAML Template' + + KINDS_NOT_LOC = HTML::KINDS_NOT_LOC + + protected + + def setup + super + @ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true + @embedded_ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true, :state => @ruby_scanner.interpreted_string_state + @html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true + end + + def scan_tokens encoder, options + + match = nil + code = '' + + until eos? + + if bol? + if match = scan(/!!!.*/) + encoder.text_token match, :doctype + next + end + + if match = scan(/(?>( *)(\/(?!\[if)|-\#|:javascript|:ruby|:\w+) *)(?=\n)/) + encoder.text_token match, :comment + + code = self[2] + if match = scan(/(?:\n+#{self[1]} .*)+/) + case code + when '/', '-#' + encoder.text_token match, :comment + when ':javascript' + # TODO: recognize #{...} snippets inside JavaScript + @java_script_scanner ||= CodeRay.scanner :java_script, :tokens => @tokens, :keep_tokens => true + @java_script_scanner.tokenize match, :tokens => encoder + when ':ruby' + @ruby_scanner.tokenize match, :tokens => encoder + when /:\w+/ + encoder.text_token match, :comment + else + raise 'else-case reached: %p' % [code] + end + end + end + + if match = scan(/ +/) + encoder.text_token match, :space + end + + if match = scan(/\/.*/) + encoder.text_token match, :comment + next + end + + if match = scan(/\\/) + encoder.text_token match, :plain + if match = scan(/.+/) + @html_scanner.tokenize match, :tokens => encoder + end + next + end + + tag = false + + if match = scan(/%[-\w:]+\/?/) + encoder.text_token match, :tag + # if match = scan(/( +)(.+)/) + # encoder.text_token self[1], :space + # @embedded_ruby_scanner.tokenize self[2], :tokens => encoder + # end + tag = true + end + + while match = scan(/([.#])[-\w]*\w/) + encoder.text_token match, self[1] == '#' ? :constant : :class + tag = true + end + + if tag && match = scan(/(\()([^)]+)?(\))?/) + # TODO: recognize title=@title, class="widget_#{@widget.number}" + encoder.text_token self[1], :plain + @html_scanner.tokenize self[2], :tokens => encoder, :state => :attribute if self[2] + encoder.text_token self[3], :plain if self[3] + end + + if tag && match = scan(/\{/) + encoder.text_token match, :plain + + code = '' + level = 1 + while true + code << scan(/([^\{\},\n]|, *\n?)*/) + case match = getch + when '{' + level += 1 + code << match + when '}' + level -= 1 + if level > 0 + code << match + else + break + end + when "\n", ",", nil + break + end + end + @ruby_scanner.tokenize code, :tokens => encoder unless code.empty? + + encoder.text_token match, :plain if match + end + + if tag && match = scan(/(\[)([^\]\n]+)?(\])?/) + encoder.text_token self[1], :plain + @ruby_scanner.tokenize self[2], :tokens => encoder if self[2] + encoder.text_token self[3], :plain if self[3] + end + + if tag && match = scan(/\//) + encoder.text_token match, :tag + end + + if scan(/(>? encoder + else + @ruby_scanner.tokenize self[4], :tokens => encoder + end + end + elsif match = scan(/((?:<|> encoder if self[2] + end + + elsif match = scan(/.+/) + @html_scanner.tokenize match, :tokens => encoder + + end + + if match = scan(/\n/) + encoder.text_token match, :space + end + end + + encoder + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/html.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/html.rb new file mode 100644 index 0000000000..ebe7b01d18 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/html.rb @@ -0,0 +1,275 @@ +module CodeRay +module Scanners + + # HTML Scanner + # + # Alias: +xhtml+ + # + # See also: Scanners::XML + class HTML < Scanner + + register_for :html + + KINDS_NOT_LOC = [ + :comment, :doctype, :preprocessor, + :tag, :attribute_name, :operator, + :attribute_value, :string, + :plain, :entity, :error, + ] # :nodoc: + + EVENT_ATTRIBUTES = %w( + onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay + oncanplaythrough onchange onclick oncontextmenu oncuechange ondblclick + ondrag ondragdrop ondragend ondragenter ondragleave ondragover + ondragstart ondrop ondurationchange onemptied onended onerror onfocus + onformchange onforminput onhashchange oninput oninvalid onkeydown + onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart + onmessage onmousedown onmousemove onmouseout onmouseover onmouseup + onmousewheel onmove onoffline ononline onpagehide onpageshow onpause + onplay onplaying onpopstate onprogress onratechange onreadystatechange + onredo onreset onresize onscroll onseeked onseeking onselect onshow + onstalled onstorage onsubmit onsuspend ontimeupdate onundo onunload + onvolumechange onwaiting + ) + + IN_ATTRIBUTE = WordList::CaseIgnoring.new(nil). + add(EVENT_ATTRIBUTES, :script). + add(['style'], :style) + + ATTR_NAME = /[\w.:-]+/ # :nodoc: + TAG_END = /\/?>/ # :nodoc: + HEX = /[0-9a-fA-F]/ # :nodoc: + ENTITY = / + & + (?: + \w+ + | + \# + (?: + \d+ + | + x#{HEX}+ + ) + ) + ; + /ox # :nodoc: + + PLAIN_STRING_CONTENT = { + "'" => /[^&'>\n]+/, + '"' => /[^&">\n]+/, + } # :nodoc: + + def reset + super + @state = :initial + @plain_string_content = nil + end + + protected + + def setup + @state = :initial + @plain_string_content = nil + @in_tag = nil + end + + def scan_java_script encoder, code + if code && !code.empty? + @java_script_scanner ||= Scanners::JavaScript.new '', :keep_tokens => true + @java_script_scanner.tokenize code, :tokens => encoder + end + end + + def scan_css encoder, code, state = [:initial] + if code && !code.empty? + @css_scanner ||= Scanners::CSS.new '', :keep_tokens => true + @css_scanner.tokenize code, :tokens => encoder, :state => state + end + end + + def scan_tokens encoder, options + state = options[:state] || @state + plain_string_content = @plain_string_content + in_tag = @in_tag + in_attribute = nil + + encoder.begin_group :string if state == :attribute_value_string + + until eos? + + if state != :in_special_tag && match = scan(/\s+/m) + encoder.text_token match, :space + + else + + case state + + when :initial + if match = scan(//m) + encoder.text_token match[0..-4], :plain + encoder.text_token ']]>', :inline_delimiter + elsif match = scan(/.+/) + encoder.text_token match, :error + end + elsif match = scan(/|.*)/m) + encoder.text_token match, :comment + elsif match = scan(/|.*)|\]>/m) + encoder.text_token match, :doctype + elsif match = scan(/<\?xml(?:.*?\?>|.*)/m) + encoder.text_token match, :preprocessor + elsif match = scan(/<\?(?:.*?\?>|.*)/m) + encoder.text_token match, :comment + elsif match = scan(/<\/[-\w.:]*>?/m) + in_tag = nil + encoder.text_token match, :tag + elsif match = scan(/<(?:(script|style)|[-\w.:]+)(>)?/m) + encoder.text_token match, :tag + in_tag = self[1] + if self[2] + state = :in_special_tag if in_tag + else + state = :attribute + end + elsif match = scan(/[^<>&]+/) + encoder.text_token match, :plain + elsif match = scan(/#{ENTITY}/ox) + encoder.text_token match, :entity + elsif match = scan(/[<>&]/) + in_tag = nil + encoder.text_token match, :error + else + raise_inspect '[BUG] else-case reached with state %p' % [state], encoder + end + + when :attribute + if match = scan(/#{TAG_END}/o) + encoder.text_token match, :tag + in_attribute = nil + if in_tag + state = :in_special_tag + else + state = :initial + end + elsif match = scan(/#{ATTR_NAME}/o) + in_attribute = IN_ATTRIBUTE[match] + encoder.text_token match, :attribute_name + state = :attribute_equal + else + in_tag = nil + encoder.text_token getch, :error + end + + when :attribute_equal + if match = scan(/=/) #/ + encoder.text_token match, :operator + state = :attribute_value + else + state = :attribute + next + end + + when :attribute_value + if match = scan(/#{ATTR_NAME}/o) + encoder.text_token match, :attribute_value + state = :attribute + elsif match = scan(/["']/) + if in_attribute == :script || in_attribute == :style + encoder.begin_group :string + encoder.text_token match, :delimiter + if scan(/javascript:[ \t]*/) + encoder.text_token matched, :comment + end + code = scan_until(match == '"' ? /(?="|\z)/ : /(?='|\z)/) + if in_attribute == :script + scan_java_script encoder, code + else + scan_css encoder, code, [:block] + end + match = scan(/["']/) + encoder.text_token match, :delimiter if match + encoder.end_group :string + state = :attribute + in_attribute = nil + else + encoder.begin_group :string + state = :attribute_value_string + plain_string_content = PLAIN_STRING_CONTENT[match] + encoder.text_token match, :delimiter + end + elsif match = scan(/#{TAG_END}/o) + encoder.text_token match, :tag + state = :initial + else + encoder.text_token getch, :error + end + + when :attribute_value_string + if match = scan(plain_string_content) + encoder.text_token match, :content + elsif match = scan(/['"]/) + encoder.text_token match, :delimiter + encoder.end_group :string + state = :attribute + elsif match = scan(/#{ENTITY}/ox) + encoder.text_token match, :entity + elsif match = scan(/&/) + encoder.text_token match, :content + elsif match = scan(/[\n>]/) + encoder.end_group :string + state = :initial + encoder.text_token match, :error + end + + when :in_special_tag + case in_tag + when 'script', 'style' + encoder.text_token match, :space if match = scan(/[ \t]*\n/) + if scan(/(\s*)|(.*))/m) + code = self[2] || self[4] + closing = self[3] + encoder.text_token self[1], :comment + else + code = scan_until(/(?=(?:\n\s*)?<\/#{in_tag}>)|\z/) + closing = false + end + unless code.empty? + encoder.begin_group :inline + if in_tag == 'script' + scan_java_script encoder, code + else + scan_css encoder, code + end + encoder.end_group :inline + end + encoder.text_token closing, :comment if closing + state = :initial + else + raise 'unknown special tag: %p' % [in_tag] + end + + else + raise_inspect 'Unknown state: %p' % [state], encoder + + end + + end + + end + + if options[:keep_state] + @state = state + @plain_string_content = plain_string_content + @in_tag = in_tag + end + + encoder.end_group :string if state == :attribute_value_string + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java.rb new file mode 100644 index 0000000000..7dd1919ec2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java.rb @@ -0,0 +1,174 @@ +module CodeRay +module Scanners + + # Scanner for Java. + class Java < Scanner + + register_for :java + + autoload :BuiltinTypes, CodeRay.coderay_path('scanners', 'java', 'builtin_types') + + # http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html + KEYWORDS = %w[ + assert break case catch continue default do else + finally for if instanceof import new package + return switch throw try typeof while + debugger export + ] # :nodoc: + RESERVED = %w[ const goto ] # :nodoc: + CONSTANTS = %w[ false null true ] # :nodoc: + MAGIC_VARIABLES = %w[ this super ] # :nodoc: + TYPES = %w[ + boolean byte char class double enum float int interface long + short void var + ] << '[]' # :nodoc: because int[] should be highlighted as a type + DIRECTIVES = %w[ + abstract extends final implements native private protected public + static strictfp synchronized throws transient volatile + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(RESERVED, :reserved). + add(CONSTANTS, :predefined_constant). + add(MAGIC_VARIABLES, :local_variable). + add(TYPES, :type). + add(BuiltinTypes::List, :predefined_type). + add(BuiltinTypes::List.select { |builtin| builtin[/(Error|Exception)$/] }, :exception). + add(DIRECTIVES, :directive) # :nodoc: + + ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x # :nodoc: + STRING_CONTENT_PATTERN = { + "'" => /[^\\']+/, + '"' => /[^\\"]+/, + '/' => /[^\\\/]+/, + } # :nodoc: + IDENT = RUBY_VERSION < '1.9' ? /[a-zA-Z_][A-Za-z_0-9]*/ : Regexp.new('[[[:alpha:]]_][[[:alnum:]]_]*') # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + string_delimiter = nil + package_name_expected = false + class_name_follows = false + last_token_dot = false + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + encoder.text_token match, :space + next + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) + encoder.text_token match, :comment + next + + elsif package_name_expected && match = scan(/ #{IDENT} (?: \. #{IDENT} )* /ox) + encoder.text_token match, package_name_expected + + elsif match = scan(/ #{IDENT} | \[\] /ox) + kind = IDENT_KIND[match] + if last_token_dot + kind = :ident + elsif class_name_follows + kind = :class + class_name_follows = false + else + case match + when 'import' + package_name_expected = :include + when 'package' + package_name_expected = :namespace + when 'class', 'interface' + class_name_follows = true + end + end + encoder.text_token match, kind + + elsif match = scan(/ \.(?!\d) | [,?:()\[\]}] | -- | \+\+ | && | \|\| | \*\*=? | [-+*\/%^~&|<>=!]=? | <<>>?=? /x) + encoder.text_token match, :operator + + elsif match = scan(/;/) + package_name_expected = false + encoder.text_token match, :operator + + elsif match = scan(/\{/) + class_name_follows = false + encoder.text_token match, :operator + + elsif check(/[\d.]/) + if match = scan(/0[xX][0-9A-Fa-f]+/) + encoder.text_token match, :hex + elsif match = scan(/(?>0[0-7]+)(?![89.eEfF])/) + encoder.text_token match, :octal + elsif match = scan(/\d+[fFdD]|\d*\.\d+(?:[eE][+-]?\d+)?[fFdD]?|\d+[eE][+-]?\d+[fFdD]?/) + encoder.text_token match, :float + elsif match = scan(/\d+[lL]?/) + encoder.text_token match, :integer + end + + elsif match = scan(/["']/) + state = :string + encoder.begin_group state + string_delimiter = match + encoder.text_token match, :delimiter + + elsif match = scan(/ @ #{IDENT} /ox) + encoder.text_token match, :annotation + + else + encoder.text_token getch, :error + + end + + when :string + if match = scan(STRING_CONTENT_PATTERN[string_delimiter]) + encoder.text_token match, :content + elsif match = scan(/["'\/]/) + encoder.text_token match, :delimiter + encoder.end_group state + state = :initial + string_delimiter = nil + elsif state == :string && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) + if string_delimiter == "'" && !(match == "\\\\" || match == "\\'") + encoder.text_token match, :content + else + encoder.text_token match, :char + end + elsif match = scan(/\\./m) + encoder.text_token match, :content + elsif match = scan(/ \\ | $ /x) + encoder.end_group state + state = :initial + encoder.text_token match, :error unless match.empty? + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + end + + else + raise_inspect 'Unknown state', encoder + + end + + last_token_dot = match == '.' + + end + + if state == :string + encoder.end_group state + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java/builtin_types.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java/builtin_types.rb new file mode 100644 index 0000000000..d1b8b73be6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java/builtin_types.rb @@ -0,0 +1,421 @@ +module CodeRay +module Scanners + + module Java::BuiltinTypes # :nodoc: + + #:nocov: + List = %w[ + AbstractAction AbstractBorder AbstractButton AbstractCellEditor AbstractCollection + AbstractColorChooserPanel AbstractDocument AbstractExecutorService AbstractInterruptibleChannel + AbstractLayoutCache AbstractList AbstractListModel AbstractMap AbstractMethodError AbstractPreferences + AbstractQueue AbstractQueuedSynchronizer AbstractSelectableChannel AbstractSelectionKey AbstractSelector + AbstractSequentialList AbstractSet AbstractSpinnerModel AbstractTableModel AbstractUndoableEdit + AbstractWriter AccessControlContext AccessControlException AccessController AccessException Accessible + AccessibleAction AccessibleAttributeSequence AccessibleBundle AccessibleComponent AccessibleContext + AccessibleEditableText AccessibleExtendedComponent AccessibleExtendedTable AccessibleExtendedText + AccessibleHyperlink AccessibleHypertext AccessibleIcon AccessibleKeyBinding AccessibleObject + AccessibleRelation AccessibleRelationSet AccessibleResourceBundle AccessibleRole AccessibleSelection + AccessibleState AccessibleStateSet AccessibleStreamable AccessibleTable AccessibleTableModelChange + AccessibleText AccessibleTextSequence AccessibleValue AccountException AccountExpiredException + AccountLockedException AccountNotFoundException Acl AclEntry AclNotFoundException Action ActionEvent + ActionListener ActionMap ActionMapUIResource Activatable ActivateFailedException ActivationDesc + ActivationException ActivationGroup ActivationGroupDesc ActivationGroupID ActivationGroup_Stub + ActivationID ActivationInstantiator ActivationMonitor ActivationSystem Activator ActiveEvent + ActivityCompletedException ActivityRequiredException Adjustable AdjustmentEvent AdjustmentListener + Adler32 AffineTransform AffineTransformOp AlgorithmParameterGenerator AlgorithmParameterGeneratorSpi + AlgorithmParameters AlgorithmParameterSpec AlgorithmParametersSpi AllPermission AlphaComposite + AlreadyBoundException AlreadyConnectedException AncestorEvent AncestorListener AnnotatedElement + Annotation AnnotationFormatError AnnotationTypeMismatchException AppConfigurationEntry Appendable Applet + AppletContext AppletInitializer AppletStub Arc2D Area AreaAveragingScaleFilter ArithmeticException Array + ArrayBlockingQueue ArrayIndexOutOfBoundsException ArrayList Arrays ArrayStoreException ArrayType + AssertionError AsyncBoxView AsynchronousCloseException AtomicBoolean AtomicInteger AtomicIntegerArray + AtomicIntegerFieldUpdater AtomicLong AtomicLongArray AtomicLongFieldUpdater AtomicMarkableReference + AtomicReference AtomicReferenceArray AtomicReferenceFieldUpdater AtomicStampedReference Attribute + AttributeChangeNotification AttributeChangeNotificationFilter AttributedCharacterIterator + AttributedString AttributeException AttributeInUseException AttributeList AttributeModificationException + AttributeNotFoundException Attributes AttributeSet AttributeSetUtilities AttributeValueExp AudioClip + AudioFileFormat AudioFileReader AudioFileWriter AudioFormat AudioInputStream AudioPermission AudioSystem + AuthenticationException AuthenticationNotSupportedException Authenticator AuthorizeCallback + AuthPermission AuthProvider Autoscroll AWTError AWTEvent AWTEventListener AWTEventListenerProxy + AWTEventMulticaster AWTException AWTKeyStroke AWTPermission BackingStoreException + BadAttributeValueExpException BadBinaryOpValueExpException BadLocationException BadPaddingException + BadStringOperationException BandCombineOp BandedSampleModel BaseRowSet BasicArrowButton BasicAttribute + BasicAttributes BasicBorders BasicButtonListener BasicButtonUI BasicCheckBoxMenuItemUI BasicCheckBoxUI + BasicColorChooserUI BasicComboBoxEditor BasicComboBoxRenderer BasicComboBoxUI BasicComboPopup + BasicControl BasicDesktopIconUI BasicDesktopPaneUI BasicDirectoryModel BasicEditorPaneUI + BasicFileChooserUI BasicFormattedTextFieldUI BasicGraphicsUtils BasicHTML BasicIconFactory + BasicInternalFrameTitlePane BasicInternalFrameUI BasicLabelUI BasicListUI BasicLookAndFeel + BasicMenuBarUI BasicMenuItemUI BasicMenuUI BasicOptionPaneUI BasicPanelUI BasicPasswordFieldUI + BasicPermission BasicPopupMenuSeparatorUI BasicPopupMenuUI BasicProgressBarUI BasicRadioButtonMenuItemUI + BasicRadioButtonUI BasicRootPaneUI BasicScrollBarUI BasicScrollPaneUI BasicSeparatorUI BasicSliderUI + BasicSpinnerUI BasicSplitPaneDivider BasicSplitPaneUI BasicStroke BasicTabbedPaneUI BasicTableHeaderUI + BasicTableUI BasicTextAreaUI BasicTextFieldUI BasicTextPaneUI BasicTextUI BasicToggleButtonUI + BasicToolBarSeparatorUI BasicToolBarUI BasicToolTipUI BasicTreeUI BasicViewportUI BatchUpdateException + BeanContext BeanContextChild BeanContextChildComponentProxy BeanContextChildSupport + BeanContextContainerProxy BeanContextEvent BeanContextMembershipEvent BeanContextMembershipListener + BeanContextProxy BeanContextServiceAvailableEvent BeanContextServiceProvider + BeanContextServiceProviderBeanInfo BeanContextServiceRevokedEvent BeanContextServiceRevokedListener + BeanContextServices BeanContextServicesListener BeanContextServicesSupport BeanContextSupport + BeanDescriptor BeanInfo Beans BevelBorder Bidi BigDecimal BigInteger BinaryRefAddr BindException Binding + BitSet Blob BlockingQueue BlockView BMPImageWriteParam Book Boolean BooleanControl Border BorderFactory + BorderLayout BorderUIResource BoundedRangeModel Box BoxLayout BoxView BreakIterator + BrokenBarrierException Buffer BufferCapabilities BufferedImage BufferedImageFilter BufferedImageOp + BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter BufferOverflowException + BufferStrategy BufferUnderflowException Button ButtonGroup ButtonModel ButtonUI Byte + ByteArrayInputStream ByteArrayOutputStream ByteBuffer ByteChannel ByteLookupTable ByteOrder CachedRowSet + CacheRequest CacheResponse Calendar Callable CallableStatement Callback CallbackHandler + CancelablePrintJob CancellationException CancelledKeyException CannotProceedException + CannotRedoException CannotUndoException Canvas CardLayout Caret CaretEvent CaretListener CellEditor + CellEditorListener CellRendererPane Certificate CertificateEncodingException CertificateException + CertificateExpiredException CertificateFactory CertificateFactorySpi CertificateNotYetValidException + CertificateParsingException CertPath CertPathBuilder CertPathBuilderException CertPathBuilderResult + CertPathBuilderSpi CertPathParameters CertPathTrustManagerParameters CertPathValidator + CertPathValidatorException CertPathValidatorResult CertPathValidatorSpi CertSelector CertStore + CertStoreException CertStoreParameters CertStoreSpi ChangedCharSetException ChangeEvent ChangeListener + Channel Channels Character CharacterCodingException CharacterIterator CharArrayReader CharArrayWriter + CharBuffer CharConversionException CharSequence Charset CharsetDecoder CharsetEncoder CharsetProvider + Checkbox CheckboxGroup CheckboxMenuItem CheckedInputStream CheckedOutputStream Checksum Choice + ChoiceCallback ChoiceFormat Chromaticity Cipher CipherInputStream CipherOutputStream CipherSpi Class + ClassCastException ClassCircularityError ClassDefinition ClassDesc ClassFileTransformer ClassFormatError + ClassLoader ClassLoaderRepository ClassLoadingMXBean ClassNotFoundException Clip Clipboard + ClipboardOwner Clob Cloneable CloneNotSupportedException Closeable ClosedByInterruptException + ClosedChannelException ClosedSelectorException CMMException CoderMalfunctionError CoderResult CodeSigner + CodeSource CodingErrorAction CollationElementIterator CollationKey Collator Collection + CollectionCertStoreParameters Collections Color ColorChooserComponentFactory ColorChooserUI + ColorConvertOp ColorModel ColorSelectionModel ColorSpace ColorSupported ColorType ColorUIResource + ComboBoxEditor ComboBoxModel ComboBoxUI ComboPopup CommunicationException Comparable Comparator + CompilationMXBean Compiler CompletionService Component ComponentAdapter ComponentColorModel + ComponentEvent ComponentInputMap ComponentInputMapUIResource ComponentListener ComponentOrientation + ComponentSampleModel ComponentUI ComponentView Composite CompositeContext CompositeData + CompositeDataSupport CompositeName CompositeType CompositeView CompoundBorder CompoundControl + CompoundEdit CompoundName Compression ConcurrentHashMap ConcurrentLinkedQueue ConcurrentMap + ConcurrentModificationException Condition Configuration ConfigurationException ConfirmationCallback + ConnectException ConnectIOException Connection ConnectionEvent ConnectionEventListener + ConnectionPendingException ConnectionPoolDataSource ConsoleHandler Constructor Container + ContainerAdapter ContainerEvent ContainerListener ContainerOrderFocusTraversalPolicy ContentHandler + ContentHandlerFactory ContentModel Context ContextNotEmptyException ContextualRenderedImageFactory + Control ControlFactory ControllerEventListener ConvolveOp CookieHandler Copies CopiesSupported + CopyOnWriteArrayList CopyOnWriteArraySet CountDownLatch CounterMonitor CounterMonitorMBean CRC32 + CredentialException CredentialExpiredException CredentialNotFoundException CRL CRLException CRLSelector + CropImageFilter CSS CubicCurve2D Currency Cursor Customizer CyclicBarrier DatabaseMetaData DataBuffer + DataBufferByte DataBufferDouble DataBufferFloat DataBufferInt DataBufferShort DataBufferUShort + DataFlavor DataFormatException DatagramChannel DatagramPacket DatagramSocket DatagramSocketImpl + DatagramSocketImplFactory DataInput DataInputStream DataLine DataOutput DataOutputStream DataSource + DataTruncation DatatypeConfigurationException DatatypeConstants DatatypeFactory Date DateFormat + DateFormatSymbols DateFormatter DateTimeAtCompleted DateTimeAtCreation DateTimeAtProcessing + DateTimeSyntax DebugGraphics DecimalFormat DecimalFormatSymbols DefaultBoundedRangeModel + DefaultButtonModel DefaultCaret DefaultCellEditor DefaultColorSelectionModel DefaultComboBoxModel + DefaultDesktopManager DefaultEditorKit DefaultFocusManager DefaultFocusTraversalPolicy DefaultFormatter + DefaultFormatterFactory DefaultHighlighter DefaultKeyboardFocusManager DefaultListCellRenderer + DefaultListModel DefaultListSelectionModel DefaultLoaderRepository DefaultMenuLayout DefaultMetalTheme + DefaultMutableTreeNode DefaultPersistenceDelegate DefaultSingleSelectionModel DefaultStyledDocument + DefaultTableCellRenderer DefaultTableColumnModel DefaultTableModel DefaultTextUI DefaultTreeCellEditor + DefaultTreeCellRenderer DefaultTreeModel DefaultTreeSelectionModel Deflater DeflaterOutputStream Delayed + DelayQueue DelegationPermission Deprecated Descriptor DescriptorAccess DescriptorSupport DESedeKeySpec + DesignMode DESKeySpec DesktopIconUI DesktopManager DesktopPaneUI Destination Destroyable + DestroyFailedException DGC DHGenParameterSpec DHKey DHParameterSpec DHPrivateKey DHPrivateKeySpec + DHPublicKey DHPublicKeySpec Dialog Dictionary DigestException DigestInputStream DigestOutputStream + Dimension Dimension2D DimensionUIResource DirContext DirectColorModel DirectoryManager DirObjectFactory + DirStateFactory DisplayMode DnDConstants Doc DocAttribute DocAttributeSet DocFlavor DocPrintJob Document + DocumentBuilder DocumentBuilderFactory Documented DocumentEvent DocumentFilter DocumentListener + DocumentName DocumentParser DomainCombiner DOMLocator DOMResult DOMSource Double DoubleBuffer + DragGestureEvent DragGestureListener DragGestureRecognizer DragSource DragSourceAdapter + DragSourceContext DragSourceDragEvent DragSourceDropEvent DragSourceEvent DragSourceListener + DragSourceMotionListener Driver DriverManager DriverPropertyInfo DropTarget DropTargetAdapter + DropTargetContext DropTargetDragEvent DropTargetDropEvent DropTargetEvent DropTargetListener DSAKey + DSAKeyPairGenerator DSAParameterSpec DSAParams DSAPrivateKey DSAPrivateKeySpec DSAPublicKey + DSAPublicKeySpec DTD DTDConstants DuplicateFormatFlagsException Duration DynamicMBean ECField ECFieldF2m + ECFieldFp ECGenParameterSpec ECKey ECParameterSpec ECPoint ECPrivateKey ECPrivateKeySpec ECPublicKey + ECPublicKeySpec EditorKit Element ElementIterator ElementType Ellipse2D EllipticCurve EmptyBorder + EmptyStackException EncodedKeySpec Encoder EncryptedPrivateKeyInfo Entity Enum + EnumConstantNotPresentException EnumControl Enumeration EnumMap EnumSet EnumSyntax EOFException Error + ErrorListener ErrorManager EtchedBorder Event EventContext EventDirContext EventHandler EventListener + EventListenerList EventListenerProxy EventObject EventQueue EventSetDescriptor Exception + ExceptionInInitializerError ExceptionListener Exchanger ExecutionException Executor + ExecutorCompletionService Executors ExecutorService ExemptionMechanism ExemptionMechanismException + ExemptionMechanismSpi ExpandVetoException ExportException Expression ExtendedRequest ExtendedResponse + Externalizable FactoryConfigurationError FailedLoginException FeatureDescriptor Fidelity Field + FieldPosition FieldView File FileCacheImageInputStream FileCacheImageOutputStream FileChannel + FileChooserUI FileDescriptor FileDialog FileFilter FileHandler FileImageInputStream + FileImageOutputStream FileInputStream FileLock FileLockInterruptionException FilenameFilter FileNameMap + FileNotFoundException FileOutputStream FilePermission FileReader FileSystemView FileView FileWriter + Filter FilteredImageSource FilteredRowSet FilterInputStream FilterOutputStream FilterReader FilterWriter + Finishings FixedHeightLayoutCache FlatteningPathIterator FlavorEvent FlavorException FlavorListener + FlavorMap FlavorTable Float FloatBuffer FloatControl FlowLayout FlowView Flushable FocusAdapter + FocusEvent FocusListener FocusManager FocusTraversalPolicy Font FontFormatException FontMetrics + FontRenderContext FontUIResource Format FormatConversionProvider FormatFlagsConversionMismatchException + Formattable FormattableFlags Formatter FormatterClosedException FormSubmitEvent FormView Frame Future + FutureTask GapContent GarbageCollectorMXBean GatheringByteChannel GaugeMonitor GaugeMonitorMBean + GeneralPath GeneralSecurityException GenericArrayType GenericDeclaration GenericSignatureFormatError + GlyphJustificationInfo GlyphMetrics GlyphVector GlyphView GradientPaint GraphicAttribute Graphics + Graphics2D GraphicsConfigTemplate GraphicsConfiguration GraphicsDevice GraphicsEnvironment GrayFilter + GregorianCalendar GridBagConstraints GridBagLayout GridLayout Group Guard GuardedObject GZIPInputStream + GZIPOutputStream Handler HandshakeCompletedEvent HandshakeCompletedListener HasControls HashAttributeSet + HashDocAttributeSet HashMap HashPrintJobAttributeSet HashPrintRequestAttributeSet + HashPrintServiceAttributeSet HashSet Hashtable HeadlessException HierarchyBoundsAdapter + HierarchyBoundsListener HierarchyEvent HierarchyListener Highlighter HostnameVerifier HTML HTMLDocument + HTMLEditorKit HTMLFrameHyperlinkEvent HTMLWriter HttpRetryException HttpsURLConnection HttpURLConnection + HyperlinkEvent HyperlinkListener ICC_ColorSpace ICC_Profile ICC_ProfileGray ICC_ProfileRGB Icon + IconUIResource IconView Identity IdentityHashMap IdentityScope IIOByteBuffer IIOException IIOImage + IIOInvalidTreeException IIOMetadata IIOMetadataController IIOMetadataFormat IIOMetadataFormatImpl + IIOMetadataNode IIOParam IIOParamController IIOReadProgressListener IIOReadUpdateListener + IIOReadWarningListener IIORegistry IIOServiceProvider IIOWriteProgressListener IIOWriteWarningListener + IllegalAccessError IllegalAccessException IllegalArgumentException IllegalBlockingModeException + IllegalBlockSizeException IllegalCharsetNameException IllegalClassFormatException + IllegalComponentStateException IllegalFormatCodePointException IllegalFormatConversionException + IllegalFormatException IllegalFormatFlagsException IllegalFormatPrecisionException + IllegalFormatWidthException IllegalMonitorStateException IllegalPathStateException + IllegalSelectorException IllegalStateException IllegalThreadStateException Image ImageCapabilities + ImageConsumer ImageFilter ImageGraphicAttribute ImageIcon ImageInputStream ImageInputStreamImpl + ImageInputStreamSpi ImageIO ImageObserver ImageOutputStream ImageOutputStreamImpl ImageOutputStreamSpi + ImageProducer ImageReader ImageReaderSpi ImageReaderWriterSpi ImageReadParam ImageTranscoder + ImageTranscoderSpi ImageTypeSpecifier ImageView ImageWriteParam ImageWriter ImageWriterSpi + ImagingOpException IncompatibleClassChangeError IncompleteAnnotationException IndexColorModel + IndexedPropertyChangeEvent IndexedPropertyDescriptor IndexOutOfBoundsException Inet4Address Inet6Address + InetAddress InetSocketAddress Inflater InflaterInputStream InheritableThreadLocal Inherited + InitialContext InitialContextFactory InitialContextFactoryBuilder InitialDirContext InitialLdapContext + InlineView InputContext InputEvent InputMap InputMapUIResource InputMethod InputMethodContext + InputMethodDescriptor InputMethodEvent InputMethodHighlight InputMethodListener InputMethodRequests + InputMismatchException InputStream InputStreamReader InputSubset InputVerifier Insets InsetsUIResource + InstanceAlreadyExistsException InstanceNotFoundException InstantiationError InstantiationException + Instrument Instrumentation InsufficientResourcesException IntBuffer Integer IntegerSyntax InternalError + InternalFrameAdapter InternalFrameEvent InternalFrameFocusTraversalPolicy InternalFrameListener + InternalFrameUI InternationalFormatter InterruptedException InterruptedIOException + InterruptedNamingException InterruptibleChannel IntrospectionException Introspector + InvalidActivityException InvalidAlgorithmParameterException InvalidApplicationException + InvalidAttributeIdentifierException InvalidAttributesException InvalidAttributeValueException + InvalidClassException InvalidDnDOperationException InvalidKeyException InvalidKeySpecException + InvalidMarkException InvalidMidiDataException InvalidNameException InvalidObjectException + InvalidOpenTypeException InvalidParameterException InvalidParameterSpecException + InvalidPreferencesFormatException InvalidPropertiesFormatException InvalidRelationIdException + InvalidRelationServiceException InvalidRelationTypeException InvalidRoleInfoException + InvalidRoleValueException InvalidSearchControlsException InvalidSearchFilterException + InvalidTargetObjectTypeException InvalidTransactionException InvocationEvent InvocationHandler + InvocationTargetException IOException ItemEvent ItemListener ItemSelectable Iterable Iterator + IvParameterSpec JApplet JarEntry JarException JarFile JarInputStream JarOutputStream JarURLConnection + JButton JCheckBox JCheckBoxMenuItem JColorChooser JComboBox JComponent JdbcRowSet JDesktopPane JDialog + JEditorPane JFileChooser JFormattedTextField JFrame JInternalFrame JLabel JLayeredPane JList JMenu + JMenuBar JMenuItem JMException JMRuntimeException JMXAuthenticator JMXConnectionNotification + JMXConnector JMXConnectorFactory JMXConnectorProvider JMXConnectorServer JMXConnectorServerFactory + JMXConnectorServerMBean JMXConnectorServerProvider JMXPrincipal JMXProviderException + JMXServerErrorException JMXServiceURL JobAttributes JobHoldUntil JobImpressions JobImpressionsCompleted + JobImpressionsSupported JobKOctets JobKOctetsProcessed JobKOctetsSupported JobMediaSheets + JobMediaSheetsCompleted JobMediaSheetsSupported JobMessageFromOperator JobName JobOriginatingUserName + JobPriority JobPrioritySupported JobSheets JobState JobStateReason JobStateReasons Joinable JoinRowSet + JOptionPane JPanel JPasswordField JPEGHuffmanTable JPEGImageReadParam JPEGImageWriteParam JPEGQTable + JPopupMenu JProgressBar JRadioButton JRadioButtonMenuItem JRootPane JScrollBar JScrollPane JSeparator + JSlider JSpinner JSplitPane JTabbedPane JTable JTableHeader JTextArea JTextComponent JTextField + JTextPane JToggleButton JToolBar JToolTip JTree JViewport JWindow KerberosKey KerberosPrincipal + KerberosTicket Kernel Key KeyAdapter KeyAgreement KeyAgreementSpi KeyAlreadyExistsException + KeyboardFocusManager KeyEvent KeyEventDispatcher KeyEventPostProcessor KeyException KeyFactory + KeyFactorySpi KeyGenerator KeyGeneratorSpi KeyListener KeyManagementException KeyManager + KeyManagerFactory KeyManagerFactorySpi Keymap KeyPair KeyPairGenerator KeyPairGeneratorSpi KeyRep + KeySpec KeyStore KeyStoreBuilderParameters KeyStoreException KeyStoreSpi KeyStroke Label LabelUI + LabelView LanguageCallback LastOwnerException LayeredHighlighter LayoutFocusTraversalPolicy + LayoutManager LayoutManager2 LayoutQueue LDAPCertStoreParameters LdapContext LdapName + LdapReferralException Lease Level LimitExceededException Line Line2D LineBorder LineBreakMeasurer + LineEvent LineListener LineMetrics LineNumberInputStream LineNumberReader LineUnavailableException + LinkageError LinkedBlockingQueue LinkedHashMap LinkedHashSet LinkedList LinkException LinkLoopException + LinkRef List ListCellRenderer ListDataEvent ListDataListener ListenerNotFoundException ListIterator + ListModel ListResourceBundle ListSelectionEvent ListSelectionListener ListSelectionModel ListUI ListView + LoaderHandler Locale LocateRegistry Lock LockSupport Logger LoggingMXBean LoggingPermission LoginContext + LoginException LoginModule LogManager LogRecord LogStream Long LongBuffer LookAndFeel LookupOp + LookupTable Mac MacSpi MalformedInputException MalformedLinkException MalformedObjectNameException + MalformedParameterizedTypeException MalformedURLException ManagementFactory ManagementPermission + ManageReferralControl ManagerFactoryParameters Manifest Map MappedByteBuffer MarshalException + MarshalledObject MaskFormatter Matcher MatchResult Math MathContext MatteBorder MBeanAttributeInfo + MBeanConstructorInfo MBeanException MBeanFeatureInfo MBeanInfo MBeanNotificationInfo MBeanOperationInfo + MBeanParameterInfo MBeanPermission MBeanRegistration MBeanRegistrationException MBeanServer + MBeanServerBuilder MBeanServerConnection MBeanServerDelegate MBeanServerDelegateMBean MBeanServerFactory + MBeanServerForwarder MBeanServerInvocationHandler MBeanServerNotification MBeanServerNotificationFilter + MBeanServerPermission MBeanTrustPermission Media MediaName MediaPrintableArea MediaSize MediaSizeName + MediaTracker MediaTray Member MemoryCacheImageInputStream MemoryCacheImageOutputStream MemoryHandler + MemoryImageSource MemoryManagerMXBean MemoryMXBean MemoryNotificationInfo MemoryPoolMXBean MemoryType + MemoryUsage Menu MenuBar MenuBarUI MenuComponent MenuContainer MenuDragMouseEvent MenuDragMouseListener + MenuElement MenuEvent MenuItem MenuItemUI MenuKeyEvent MenuKeyListener MenuListener MenuSelectionManager + MenuShortcut MessageDigest MessageDigestSpi MessageFormat MetaEventListener MetalBorders MetalButtonUI + MetalCheckBoxIcon MetalCheckBoxUI MetalComboBoxButton MetalComboBoxEditor MetalComboBoxIcon + MetalComboBoxUI MetalDesktopIconUI MetalFileChooserUI MetalIconFactory MetalInternalFrameTitlePane + MetalInternalFrameUI MetalLabelUI MetalLookAndFeel MetalMenuBarUI MetalPopupMenuSeparatorUI + MetalProgressBarUI MetalRadioButtonUI MetalRootPaneUI MetalScrollBarUI MetalScrollButton + MetalScrollPaneUI MetalSeparatorUI MetalSliderUI MetalSplitPaneUI MetalTabbedPaneUI MetalTextFieldUI + MetalTheme MetalToggleButtonUI MetalToolBarUI MetalToolTipUI MetalTreeUI MetaMessage Method + MethodDescriptor MGF1ParameterSpec MidiChannel MidiDevice MidiDeviceProvider MidiEvent MidiFileFormat + MidiFileReader MidiFileWriter MidiMessage MidiSystem MidiUnavailableException MimeTypeParseException + MinimalHTMLWriter MissingFormatArgumentException MissingFormatWidthException MissingResourceException + Mixer MixerProvider MLet MLetMBean ModelMBean ModelMBeanAttributeInfo ModelMBeanConstructorInfo + ModelMBeanInfo ModelMBeanInfoSupport ModelMBeanNotificationBroadcaster ModelMBeanNotificationInfo + ModelMBeanOperationInfo ModificationItem Modifier Monitor MonitorMBean MonitorNotification + MonitorSettingException MouseAdapter MouseDragGestureRecognizer MouseEvent MouseInfo MouseInputAdapter + MouseInputListener MouseListener MouseMotionAdapter MouseMotionListener MouseWheelEvent + MouseWheelListener MultiButtonUI MulticastSocket MultiColorChooserUI MultiComboBoxUI MultiDesktopIconUI + MultiDesktopPaneUI MultiDoc MultiDocPrintJob MultiDocPrintService MultiFileChooserUI + MultiInternalFrameUI MultiLabelUI MultiListUI MultiLookAndFeel MultiMenuBarUI MultiMenuItemUI + MultiOptionPaneUI MultiPanelUI MultiPixelPackedSampleModel MultipleDocumentHandling MultipleMaster + MultiPopupMenuUI MultiProgressBarUI MultiRootPaneUI MultiScrollBarUI MultiScrollPaneUI MultiSeparatorUI + MultiSliderUI MultiSpinnerUI MultiSplitPaneUI MultiTabbedPaneUI MultiTableHeaderUI MultiTableUI + MultiTextUI MultiToolBarUI MultiToolTipUI MultiTreeUI MultiViewportUI MutableAttributeSet + MutableComboBoxModel MutableTreeNode Name NameAlreadyBoundException NameCallback NameClassPair + NameNotFoundException NameParser NamespaceChangeListener NamespaceContext Naming NamingEnumeration + NamingEvent NamingException NamingExceptionEvent NamingListener NamingManager NamingSecurityException + NavigationFilter NegativeArraySizeException NetPermission NetworkInterface NoClassDefFoundError + NoConnectionPendingException NodeChangeEvent NodeChangeListener NoInitialContextException + NoninvertibleTransformException NonReadableChannelException NonWritableChannelException + NoPermissionException NoRouteToHostException NoSuchAlgorithmException NoSuchAttributeException + NoSuchElementException NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException + NoSuchObjectException NoSuchPaddingException NoSuchProviderException NotActiveException + NotBoundException NotCompliantMBeanException NotContextException Notification NotificationBroadcaster + NotificationBroadcasterSupport NotificationEmitter NotificationFilter NotificationFilterSupport + NotificationListener NotificationResult NotOwnerException NotSerializableException NotYetBoundException + NotYetConnectedException NullCipher NullPointerException Number NumberFormat NumberFormatException + NumberFormatter NumberOfDocuments NumberOfInterveningJobs NumberUp NumberUpSupported NumericShaper + OAEPParameterSpec Object ObjectChangeListener ObjectFactory ObjectFactoryBuilder ObjectInput + ObjectInputStream ObjectInputValidation ObjectInstance ObjectName ObjectOutput ObjectOutputStream + ObjectStreamClass ObjectStreamConstants ObjectStreamException ObjectStreamField ObjectView ObjID + Observable Observer OceanTheme OpenDataException OpenMBeanAttributeInfo OpenMBeanAttributeInfoSupport + OpenMBeanConstructorInfo OpenMBeanConstructorInfoSupport OpenMBeanInfo OpenMBeanInfoSupport + OpenMBeanOperationInfo OpenMBeanOperationInfoSupport OpenMBeanParameterInfo + OpenMBeanParameterInfoSupport OpenType OperatingSystemMXBean Operation OperationNotSupportedException + OperationsException Option OptionalDataException OptionPaneUI OrientationRequested OutOfMemoryError + OutputDeviceAssigned OutputKeys OutputStream OutputStreamWriter OverlappingFileLockException + OverlayLayout Override Owner Pack200 Package PackedColorModel Pageable PageAttributes + PagedResultsControl PagedResultsResponseControl PageFormat PageRanges PagesPerMinute PagesPerMinuteColor + Paint PaintContext PaintEvent Panel PanelUI Paper ParagraphView ParameterBlock ParameterDescriptor + ParameterizedType ParameterMetaData ParseException ParsePosition Parser ParserConfigurationException + ParserDelegator PartialResultException PasswordAuthentication PasswordCallback PasswordView Patch + PathIterator Pattern PatternSyntaxException PBEKey PBEKeySpec PBEParameterSpec PDLOverrideSupported + Permission PermissionCollection Permissions PersistenceDelegate PersistentMBean PhantomReference Pipe + PipedInputStream PipedOutputStream PipedReader PipedWriter PixelGrabber PixelInterleavedSampleModel + PKCS8EncodedKeySpec PKIXBuilderParameters PKIXCertPathBuilderResult PKIXCertPathChecker + PKIXCertPathValidatorResult PKIXParameters PlainDocument PlainView Point Point2D PointerInfo Policy + PolicyNode PolicyQualifierInfo Polygon PooledConnection Popup PopupFactory PopupMenu PopupMenuEvent + PopupMenuListener PopupMenuUI Port PortableRemoteObject PortableRemoteObjectDelegate + PortUnreachableException Position Predicate PreferenceChangeEvent PreferenceChangeListener Preferences + PreferencesFactory PreparedStatement PresentationDirection Principal Printable PrinterAbortException + PrinterException PrinterGraphics PrinterInfo PrinterIOException PrinterIsAcceptingJobs PrinterJob + PrinterLocation PrinterMakeAndModel PrinterMessageFromOperator PrinterMoreInfo + PrinterMoreInfoManufacturer PrinterName PrinterResolution PrinterState PrinterStateReason + PrinterStateReasons PrinterURI PrintEvent PrintException PrintGraphics PrintJob PrintJobAdapter + PrintJobAttribute PrintJobAttributeEvent PrintJobAttributeListener PrintJobAttributeSet PrintJobEvent + PrintJobListener PrintQuality PrintRequestAttribute PrintRequestAttributeSet PrintService + PrintServiceAttribute PrintServiceAttributeEvent PrintServiceAttributeListener PrintServiceAttributeSet + PrintServiceLookup PrintStream PrintWriter PriorityBlockingQueue PriorityQueue PrivateClassLoader + PrivateCredentialPermission PrivateKey PrivateMLet PrivilegedAction PrivilegedActionException + PrivilegedExceptionAction Process ProcessBuilder ProfileDataException ProgressBarUI ProgressMonitor + ProgressMonitorInputStream Properties PropertyChangeEvent PropertyChangeListener + PropertyChangeListenerProxy PropertyChangeSupport PropertyDescriptor PropertyEditor + PropertyEditorManager PropertyEditorSupport PropertyPermission PropertyResourceBundle + PropertyVetoException ProtectionDomain ProtocolException Provider ProviderException Proxy ProxySelector + PSource PSSParameterSpec PublicKey PushbackInputStream PushbackReader QName QuadCurve2D Query QueryEval + QueryExp Queue QueuedJobCount Random RandomAccess RandomAccessFile Raster RasterFormatException RasterOp + RC2ParameterSpec RC5ParameterSpec Rdn Readable ReadableByteChannel Reader ReadOnlyBufferException + ReadWriteLock RealmCallback RealmChoiceCallback Receiver Rectangle Rectangle2D RectangularShape + ReentrantLock ReentrantReadWriteLock Ref RefAddr Reference Referenceable ReferenceQueue + ReferenceUriSchemesSupported ReferralException ReflectionException ReflectPermission Refreshable + RefreshFailedException Region RegisterableService Registry RegistryHandler RejectedExecutionException + RejectedExecutionHandler Relation RelationException RelationNotFoundException RelationNotification + RelationService RelationServiceMBean RelationServiceNotRegisteredException RelationSupport + RelationSupportMBean RelationType RelationTypeNotFoundException RelationTypeSupport Remote RemoteCall + RemoteException RemoteObject RemoteObjectInvocationHandler RemoteRef RemoteServer RemoteStub + RenderableImage RenderableImageOp RenderableImageProducer RenderContext RenderedImage + RenderedImageFactory Renderer RenderingHints RepaintManager ReplicateScaleFilter RequestingUserName + RequiredModelMBean RescaleOp ResolutionSyntax Resolver ResolveResult ResourceBundle ResponseCache Result + ResultSet ResultSetMetaData Retention RetentionPolicy ReverbType RGBImageFilter RMIClassLoader + RMIClassLoaderSpi RMIClientSocketFactory RMIConnection RMIConnectionImpl RMIConnectionImpl_Stub + RMIConnector RMIConnectorServer RMIFailureHandler RMIIIOPServerImpl RMIJRMPServerImpl + RMISecurityException RMISecurityManager RMIServer RMIServerImpl RMIServerImpl_Stub + RMIServerSocketFactory RMISocketFactory Robot Role RoleInfo RoleInfoNotFoundException RoleList + RoleNotFoundException RoleResult RoleStatus RoleUnresolved RoleUnresolvedList RootPaneContainer + RootPaneUI RoundingMode RoundRectangle2D RowMapper RowSet RowSetEvent RowSetInternal RowSetListener + RowSetMetaData RowSetMetaDataImpl RowSetReader RowSetWarning RowSetWriter RSAKey RSAKeyGenParameterSpec + RSAMultiPrimePrivateCrtKey RSAMultiPrimePrivateCrtKeySpec RSAOtherPrimeInfo RSAPrivateCrtKey + RSAPrivateCrtKeySpec RSAPrivateKey RSAPrivateKeySpec RSAPublicKey RSAPublicKeySpec RTFEditorKit + RuleBasedCollator Runnable Runtime RuntimeErrorException RuntimeException RuntimeMBeanException + RuntimeMXBean RuntimeOperationsException RuntimePermission SampleModel Sasl SaslClient SaslClientFactory + SaslException SaslServer SaslServerFactory Savepoint SAXParser SAXParserFactory SAXResult SAXSource + SAXTransformerFactory Scanner ScatteringByteChannel ScheduledExecutorService ScheduledFuture + ScheduledThreadPoolExecutor Schema SchemaFactory SchemaFactoryLoader SchemaViolationException Scrollable + Scrollbar ScrollBarUI ScrollPane ScrollPaneAdjustable ScrollPaneConstants ScrollPaneLayout ScrollPaneUI + SealedObject SearchControls SearchResult SecretKey SecretKeyFactory SecretKeyFactorySpi SecretKeySpec + SecureCacheResponse SecureClassLoader SecureRandom SecureRandomSpi Security SecurityException + SecurityManager SecurityPermission Segment SelectableChannel SelectionKey Selector SelectorProvider + Semaphore SeparatorUI Sequence SequenceInputStream Sequencer SerialArray SerialBlob SerialClob + SerialDatalink SerialException Serializable SerializablePermission SerialJavaObject SerialRef + SerialStruct ServerCloneException ServerError ServerException ServerNotActiveException ServerRef + ServerRuntimeException ServerSocket ServerSocketChannel ServerSocketFactory ServiceNotFoundException + ServicePermission ServiceRegistry ServiceUI ServiceUIFactory ServiceUnavailableException Set + SetOfIntegerSyntax Severity Shape ShapeGraphicAttribute SheetCollate Short ShortBuffer + ShortBufferException ShortLookupTable ShortMessage Sides Signature SignatureException SignatureSpi + SignedObject Signer SimpleAttributeSet SimpleBeanInfo SimpleDateFormat SimpleDoc SimpleFormatter + SimpleTimeZone SimpleType SinglePixelPackedSampleModel SingleSelectionModel Size2DSyntax + SizeLimitExceededException SizeRequirements SizeSequence Skeleton SkeletonMismatchException + SkeletonNotFoundException SliderUI Socket SocketAddress SocketChannel SocketException SocketFactory + SocketHandler SocketImpl SocketImplFactory SocketOptions SocketPermission SocketSecurityException + SocketTimeoutException SoftBevelBorder SoftReference SortControl SortedMap SortedSet + SortingFocusTraversalPolicy SortKey SortResponseControl Soundbank SoundbankReader SoundbankResource + Source SourceDataLine SourceLocator SpinnerDateModel SpinnerListModel SpinnerModel SpinnerNumberModel + SpinnerUI SplitPaneUI Spring SpringLayout SQLData SQLException SQLInput SQLInputImpl SQLOutput + SQLOutputImpl SQLPermission SQLWarning SSLContext SSLContextSpi SSLEngine SSLEngineResult SSLException + SSLHandshakeException SSLKeyException SSLPeerUnverifiedException SSLPermission SSLProtocolException + SslRMIClientSocketFactory SslRMIServerSocketFactory SSLServerSocket SSLServerSocketFactory SSLSession + SSLSessionBindingEvent SSLSessionBindingListener SSLSessionContext SSLSocket SSLSocketFactory Stack + StackOverflowError StackTraceElement StandardMBean StartTlsRequest StartTlsResponse StateEdit + StateEditable StateFactory Statement StreamCorruptedException StreamHandler StreamPrintService + StreamPrintServiceFactory StreamResult StreamSource StreamTokenizer StrictMath String StringBuffer + StringBufferInputStream StringBuilder StringCharacterIterator StringContent + StringIndexOutOfBoundsException StringMonitor StringMonitorMBean StringReader StringRefAddr + StringSelection StringTokenizer StringValueExp StringWriter Stroke Struct Stub StubDelegate + StubNotFoundException Style StyleConstants StyleContext StyledDocument StyledEditorKit StyleSheet + Subject SubjectDelegationPermission SubjectDomainCombiner SupportedValuesAttribute SuppressWarnings + SwingConstants SwingPropertyChangeSupport SwingUtilities SyncFactory SyncFactoryException + SyncFailedException SynchronousQueue SyncProvider SyncProviderException SyncResolver SynthConstants + SynthContext Synthesizer SynthGraphicsUtils SynthLookAndFeel SynthPainter SynthStyle SynthStyleFactory + SysexMessage System SystemColor SystemFlavorMap TabableView TabbedPaneUI TabExpander TableCellEditor + TableCellRenderer TableColumn TableColumnModel TableColumnModelEvent TableColumnModelListener + TableHeaderUI TableModel TableModelEvent TableModelListener TableUI TableView TabSet TabStop TabularData + TabularDataSupport TabularType TagElement Target TargetDataLine TargetedNotification Templates + TemplatesHandler TextAction TextArea TextAttribute TextComponent TextEvent TextField TextHitInfo + TextInputCallback TextLayout TextListener TextMeasurer TextOutputCallback TextSyntax TextUI TexturePaint + Thread ThreadDeath ThreadFactory ThreadGroup ThreadInfo ThreadLocal ThreadMXBean ThreadPoolExecutor + Throwable Tie TileObserver Time TimeLimitExceededException TimeoutException Timer + TimerAlarmClockNotification TimerMBean TimerNotification TimerTask Timestamp TimeUnit TimeZone + TitledBorder ToolBarUI Toolkit ToolTipManager ToolTipUI TooManyListenersException Track + TransactionalWriter TransactionRequiredException TransactionRolledbackException Transferable + TransferHandler TransformAttribute Transformer TransformerConfigurationException TransformerException + TransformerFactory TransformerFactoryConfigurationError TransformerHandler Transmitter Transparency + TreeCellEditor TreeCellRenderer TreeExpansionEvent TreeExpansionListener TreeMap TreeModel + TreeModelEvent TreeModelListener TreeNode TreePath TreeSelectionEvent TreeSelectionListener + TreeSelectionModel TreeSet TreeUI TreeWillExpandListener TrustAnchor TrustManager TrustManagerFactory + TrustManagerFactorySpi Type TypeInfoProvider TypeNotPresentException Types TypeVariable UID UIDefaults + UIManager UIResource UndeclaredThrowableException UndoableEdit UndoableEditEvent UndoableEditListener + UndoableEditSupport UndoManager UnexpectedException UnicastRemoteObject UnknownError + UnknownFormatConversionException UnknownFormatFlagsException UnknownGroupException UnknownHostException + UnknownObjectException UnknownServiceException UnmappableCharacterException UnmarshalException + UnmodifiableClassException UnmodifiableSetException UnrecoverableEntryException + UnrecoverableKeyException Unreferenced UnresolvedAddressException UnresolvedPermission + UnsatisfiedLinkError UnsolicitedNotification UnsolicitedNotificationEvent + UnsolicitedNotificationListener UnsupportedAddressTypeException UnsupportedAudioFileException + UnsupportedCallbackException UnsupportedCharsetException UnsupportedClassVersionError + UnsupportedEncodingException UnsupportedFlavorException UnsupportedLookAndFeelException + UnsupportedOperationException URI URIException URIResolver URISyntax URISyntaxException URL + URLClassLoader URLConnection URLDecoder URLEncoder URLStreamHandler URLStreamHandlerFactory + UTFDataFormatException Util UtilDelegate Utilities UUID Validator ValidatorHandler ValueExp ValueHandler + ValueHandlerMultiFormat VariableHeightLayoutCache Vector VerifyError VetoableChangeListener + VetoableChangeListenerProxy VetoableChangeSupport View ViewFactory ViewportLayout ViewportUI + VirtualMachineError Visibility VMID VoiceStatus Void VolatileImage WeakHashMap WeakReference WebRowSet + WildcardType Window WindowAdapter WindowConstants WindowEvent WindowFocusListener WindowListener + WindowStateListener WrappedPlainView WritableByteChannel WritableRaster WritableRenderedImage + WriteAbortedException Writer X500Principal X500PrivateCredential X509Certificate X509CertSelector + X509CRL X509CRLEntry X509CRLSelector X509EncodedKeySpec X509ExtendedKeyManager X509Extension + X509KeyManager X509TrustManager XAConnection XADataSource XAException XAResource Xid XMLConstants + XMLDecoder XMLEncoder XMLFormatter XMLGregorianCalendar XMLParseException XmlReader XmlWriter XPath + XPathConstants XPathException XPathExpression XPathExpressionException XPathFactory + XPathFactoryConfigurationException XPathFunction XPathFunctionException XPathFunctionResolver + XPathVariableResolver ZipEntry ZipException ZipFile ZipInputStream ZipOutputStream ZoneView + ] + #:nocov: + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java_script.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java_script.rb new file mode 100644 index 0000000000..8c13d4ffb5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/java_script.rb @@ -0,0 +1,236 @@ +module CodeRay +module Scanners + + # Scanner for JavaScript. + # + # Aliases: +ecmascript+, +ecma_script+, +javascript+ + class JavaScript < Scanner + + register_for :java_script + file_extension 'js' + + # The actual JavaScript keywords. + KEYWORDS = %w[ + break case catch continue default delete do else + finally for function if in instanceof new + return switch throw try typeof var void while with + ] # :nodoc: + PREDEFINED_CONSTANTS = %w[ + false null true undefined NaN Infinity + ] # :nodoc: + + MAGIC_VARIABLES = %w[ this arguments ] # :nodoc: arguments was introduced in JavaScript 1.4 + + KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ + case delete in instanceof new return throw typeof with + ] # :nodoc: + + # Reserved for future use. + RESERVED_WORDS = %w[ + abstract boolean byte char class debugger double enum export extends + final float goto implements import int interface long native package + private protected public short static super synchronized throws transient + volatile + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(RESERVED_WORDS, :reserved). + add(PREDEFINED_CONSTANTS, :predefined_constant). + add(MAGIC_VARIABLES, :local_variable). + add(KEYWORDS, :keyword) # :nodoc: + + ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x # :nodoc: + REGEXP_ESCAPE = / [bBdDsSwW] /x # :nodoc: + STRING_CONTENT_PATTERN = { + "'" => /[^\\']+/, + '"' => /[^\\"]+/, + '/' => /[^\\\/]+/, + } # :nodoc: + KEY_CHECK_PATTERN = { + "'" => / (?> [^\\']* (?: \\. [^\\']* )* ) ' \s* : /mx, + '"' => / (?> [^\\"]* (?: \\. [^\\"]* )* ) " \s* : /mx, + } # :nodoc: + + protected + + def setup + @state = :initial + end + + def scan_tokens encoder, options + + state, string_delimiter = options[:state] || @state + if string_delimiter + encoder.begin_group state + end + + value_expected = true + key_expected = false + function_expected = false + + until eos? + + case state + + when :initial + + if match = scan(/ \s+ | \\\n /x) + value_expected = true if !value_expected && match.index(?\n) + encoder.text_token match, :space + + elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .*() ) !mx) + value_expected = true + encoder.text_token match, :comment + state = :open_multi_line_comment if self[1] + + elsif check(/\.?\d/) + key_expected = value_expected = false + if match = scan(/0[xX][0-9A-Fa-f]+/) + encoder.text_token match, :hex + elsif match = scan(/(?>0[0-7]+)(?![89.eEfF])/) + encoder.text_token match, :octal + elsif match = scan(/\d+[fF]|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) + encoder.text_token match, :float + elsif match = scan(/\d+/) + encoder.text_token match, :integer + end + + elsif value_expected && match = scan(/<([[:alpha:]]\w*) (?: [^\/>]*\/> | .*?<\/\1>)/xim) + # TODO: scan over nested tags + xml_scanner.tokenize match, :tokens => encoder + value_expected = false + + elsif match = scan(/ [-+*=<>?:;,!&^|(\[{~%]+ | \.(?!\d) /x) + value_expected = true + last_operator = match[-1] + key_expected = (last_operator == ?{) || (last_operator == ?,) + function_expected = false + encoder.text_token match, :operator + + elsif match = scan(/ [)\]}]+ /x) + function_expected = key_expected = value_expected = false + encoder.text_token match, :operator + + elsif match = scan(/ [$a-zA-Z_][A-Za-z_0-9$]* /x) + kind = IDENT_KIND[match] + value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] + # TODO: labels + if kind == :ident + if match.index(?$) # $ allowed inside an identifier + kind = :predefined + elsif function_expected + kind = :function + elsif check(/\s*[=:]\s*function\b/) + kind = :function + elsif key_expected && check(/\s*:/) + kind = :key + end + end + function_expected = (kind == :keyword) && (match == 'function') + key_expected = false + encoder.text_token match, kind + + elsif match = scan(/["']/) + if key_expected && check(KEY_CHECK_PATTERN[match]) + state = :key + else + state = :string + end + encoder.begin_group state + string_delimiter = match + encoder.text_token match, :delimiter + + elsif value_expected && (match = scan(/\//)) + encoder.begin_group :regexp + state = :regexp + string_delimiter = '/' + encoder.text_token match, :delimiter + + elsif match = scan(/ \/ /x) + value_expected = true + key_expected = false + encoder.text_token match, :operator + + else + encoder.text_token getch, :error + + end + + when :string, :regexp, :key + if match = scan(STRING_CONTENT_PATTERN[string_delimiter]) + encoder.text_token match, :content + elsif match = scan(/["'\/]/) + encoder.text_token match, :delimiter + if state == :regexp + modifiers = scan(/[gim]+/) + encoder.text_token modifiers, :modifier if modifiers && !modifiers.empty? + end + encoder.end_group state + string_delimiter = nil + key_expected = value_expected = false + state = :initial + elsif state != :regexp && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) + if string_delimiter == "'" && !(match == "\\\\" || match == "\\'") + encoder.text_token match, :content + else + encoder.text_token match, :char + end + elsif state == :regexp && match = scan(/ \\ (?: #{ESCAPE} | #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/\\./m) + encoder.text_token match, :content + elsif match = scan(/ \\ | $ /x) + encoder.end_group state + encoder.text_token match, :error unless match.empty? + string_delimiter = nil + key_expected = value_expected = false + state = :initial + else + raise_inspect "else case #{string_delimiter} reached; %p not handled." % peek(1), encoder + end + + when :open_multi_line_comment + if match = scan(%r! .*? \*/ !mx) + state = :initial + else + match = scan(%r! .+ !mx) + end + value_expected = true + encoder.text_token match, :comment if match + + else + #:nocov: + raise_inspect 'Unknown state: %p' % [state], encoder + #:nocov: + + end + + end + + if options[:keep_state] + @state = state, string_delimiter + end + + if [:string, :regexp].include? state + encoder.end_group state + end + + encoder + end + + protected + + def reset_instance + super + @xml_scanner.reset if defined? @xml_scanner + end + + def xml_scanner + @xml_scanner ||= CodeRay.scanner :xml, :tokens => @tokens, :keep_tokens => true, :keep_state => false + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/json.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/json.rb new file mode 100644 index 0000000000..b09970c23f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/json.rb @@ -0,0 +1,98 @@ +module CodeRay +module Scanners + + # Scanner for JSON (JavaScript Object Notation). + class JSON < Scanner + + register_for :json + file_extension 'json' + + KINDS_NOT_LOC = [ + :float, :char, :content, :delimiter, + :error, :integer, :operator, :value, + ] # :nodoc: + + ESCAPE = / [bfnrt\\"\/] /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x # :nodoc: + KEY = / (?> (?: [^\\"]+ | \\. )* ) " \s* : /x + + protected + + def setup + @state = :initial + end + + # See http://json.org/ for a definition of the JSON lexic/grammar. + def scan_tokens encoder, options + state = options[:state] || @state + + if [:string, :key].include? state + encoder.begin_group state + end + + until eos? + + case state + + when :initial + if match = scan(/ \s+ /x) + encoder.text_token match, :space + elsif match = scan(/"/) + state = check(/#{KEY}/o) ? :key : :string + encoder.begin_group state + encoder.text_token match, :delimiter + elsif match = scan(/ [:,\[{\]}] /x) + encoder.text_token match, :operator + elsif match = scan(/ true | false | null /x) + encoder.text_token match, :value + elsif match = scan(/ -? (?: 0 | [1-9]\d* ) /x) + if scan(/ \.\d+ (?:[eE][-+]?\d+)? | [eE][-+]? \d+ /x) + match << matched + encoder.text_token match, :float + else + encoder.text_token match, :integer + end + else + encoder.text_token getch, :error + end + + when :string, :key + if match = scan(/[^\\"]+/) + encoder.text_token match, :content + elsif match = scan(/"/) + encoder.text_token match, :delimiter + encoder.end_group state + state = :initial + elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/\\./m) + encoder.text_token match, :content + elsif match = scan(/ \\ | $ /x) + encoder.end_group state + encoder.text_token match, :error unless match.empty? + state = :initial + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder + end + + else + raise_inspect 'Unknown state: %p' % [state], encoder + + end + end + + if options[:keep_state] + @state = state + end + + if [:string, :key].include? state + encoder.end_group state + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/lua.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/lua.rb new file mode 100644 index 0000000000..81d7dae481 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/lua.rb @@ -0,0 +1,280 @@ +# encoding: utf-8 + +module CodeRay +module Scanners + + # Scanner for the Lua[http://lua.org] programming lanuage. + # + # The language’s complete syntax is defined in + # {the Lua manual}[http://www.lua.org/manual/5.2/manual.html], + # which is what this scanner tries to conform to. + class Lua < Scanner + + register_for :lua + file_extension 'lua' + title 'Lua' + + # Keywords used in Lua. + KEYWORDS = %w[and break do else elseif end + for function goto if in + local not or repeat return + then until while + ] + + # Constants set by the Lua core. + PREDEFINED_CONSTANTS = %w[false true nil] + + # The expressions contained in this array are parts of Lua’s `basic' + # library. Although it’s not entirely necessary to load that library, + # it is highly recommended and one would have to provide own implementations + # of some of these expressions if one does not do so. They however aren’t + # keywords, neither are they constants, but nearly predefined, so they + # get tagged as `predefined' rather than anything else. + # + # This list excludes values of form `_UPPERCASE' because the Lua manual + # requires such identifiers to be reserved by Lua anyway and they are + # highlighted directly accordingly, without the need for specific + # identifiers to be listed here. + PREDEFINED_EXPRESSIONS = %w[ + assert collectgarbage dofile error getmetatable + ipairs load loadfile next pairs pcall print + rawequal rawget rawlen rawset select setmetatable + tonumber tostring type xpcall + ] + + # Automatic token kind selection for normal words. + IDENT_KIND = CodeRay::WordList.new(:ident). + add(KEYWORDS, :keyword). + add(PREDEFINED_CONSTANTS, :predefined_constant). + add(PREDEFINED_EXPRESSIONS, :predefined) + + protected + + # Scanner initialization. + def setup + @state = :initial + @brace_depth = 0 + end + + # CodeRay entry hook. Starts parsing. + def scan_tokens(encoder, options) + state = options[:state] || @state + brace_depth = @brace_depth + num_equals = nil + + until eos? + case state + + when :initial + if match = scan(/\-\-\[\=*\[/) #--[[ long (possibly multiline) comment ]] + num_equals = match.count("=") # Number must match for comment end + encoder.begin_group(:comment) + encoder.text_token(match, :delimiter) + state = :long_comment + + elsif match = scan(/--.*$/) # --Lua comment + encoder.text_token(match, :comment) + + elsif match = scan(/\[=*\[/) # [[ long (possibly multiline) string ]] + num_equals = match.count("=") # Number must match for string end + encoder.begin_group(:string) + encoder.text_token(match, :delimiter) + state = :long_string + + elsif match = scan(/::\s*[a-zA-Z_][a-zA-Z0-9_]+\s*::/) # ::goto_label:: + encoder.text_token(match, :label) + + elsif match = scan(/_[A-Z]+/) # _UPPERCASE are names reserved for Lua + encoder.text_token(match, :predefined) + + elsif match = scan(/[a-zA-Z_][a-zA-Z0-9_]*/) # Normal letters (or letters followed by digits) + kind = IDENT_KIND[match] + + # Extra highlighting for entities following certain keywords + if kind == :keyword and match == "function" + state = :function_expected + elsif kind == :keyword and match == "goto" + state = :goto_label_expected + elsif kind == :keyword and match == "local" + state = :local_var_expected + end + + encoder.text_token(match, kind) + + elsif match = scan(/\{/) # Opening table brace { + encoder.begin_group(:map) + encoder.text_token(match, brace_depth >= 1 ? :inline_delimiter : :delimiter) + brace_depth += 1 + state = :map + + elsif match = scan(/\}/) # Closing table brace } + if brace_depth == 1 + brace_depth = 0 + encoder.text_token(match, :delimiter) + encoder.end_group(:map) + elsif brace_depth == 0 # Mismatched brace + encoder.text_token(match, :error) + else + brace_depth -= 1 + encoder.text_token(match, :inline_delimiter) + encoder.end_group(:map) + state = :map + end + + elsif match = scan(/["']/) # String delimiters " and ' + encoder.begin_group(:string) + encoder.text_token(match, :delimiter) + start_delim = match + state = :string + + # ↓Prefix hex number ←|→ decimal number + elsif match = scan(/-? (?:0x\h* \. \h+ (?:p[+\-]?\d+)? | \d*\.\d+ (?:e[+\-]?\d+)?)/ix) # hexadecimal constants have no E power, decimal ones no P power + encoder.text_token(match, :float) + + # ↓Prefix hex number ←|→ decimal number + elsif match = scan(/-? (?:0x\h+ (?:p[+\-]?\d+)? | \d+ (?:e[+\-]?\d+)?)/ix) # hexadecimal constants have no E power, decimal ones no P power + encoder.text_token(match, :integer) + + elsif match = scan(/[\+\-\*\/%^\#=~<>\(\)\[\]:;,] | \.(?!\d)/x) # Operators + encoder.text_token(match, :operator) + + elsif match = scan(/\s+/) # Space + encoder.text_token(match, :space) + + else # Invalid stuff. Note that Lua doesn’t accept multibyte chars outside of strings, hence these are also errors. + encoder.text_token(getch, :error) + end + + # It may be that we’re scanning a full-blown subexpression of a table + # (tables can contain full expressions in parts). + # If this is the case, return to :map scanning state. + state = :map if state == :initial && brace_depth >= 1 + + when :function_expected + if match = scan(/\(.*?\)/m) # x = function() # "Anonymous" function without explicit name + encoder.text_token(match, :operator) + state = :initial + elsif match = scan(/[a-zA-Z_] (?:[a-zA-Z0-9_\.] (?!\.\d))* [\.\:]/x) # function tbl.subtbl.foo() | function tbl:foo() # Colon only allowed as last separator + encoder.text_token(match, :ident) + elsif match = scan(/[a-zA-Z_][a-zA-Z0-9_]*/) # function foo() + encoder.text_token(match, :function) + state = :initial + elsif match = scan(/\s+/) # Between the `function' keyword and the ident may be any amount of whitespace + encoder.text_token(match, :space) + else + encoder.text_token(getch, :error) + state = :initial + end + + when :goto_label_expected + if match = scan(/[a-zA-Z_][a-zA-Z0-9_]*/) + encoder.text_token(match, :label) + state = :initial + elsif match = scan(/\s+/) # Between the `goto' keyword and the label may be any amount of whitespace + encoder.text_token(match, :space) + else + encoder.text_token(getch, :error) + end + + when :local_var_expected + if match = scan(/function/) # local function ... + encoder.text_token(match, :keyword) + state = :function_expected + elsif match = scan(/[a-zA-Z_][a-zA-Z0-9_]*/) + encoder.text_token(match, :local_variable) + elsif match = scan(/,/) + encoder.text_token(match, :operator) + elsif match = scan(/\=/) + encoder.text_token(match, :operator) + # After encountering the equal sign, arbitrary expressions are + # allowed again, so just return to the main state for further + # parsing. + state = :initial + elsif match = scan(/\n/) + encoder.text_token(match, :space) + state = :initial + elsif match = scan(/\s+/) + encoder.text_token(match, :space) + else + encoder.text_token(getch, :error) + end + + when :long_comment + if match = scan(/.*?(?=\]={#{num_equals}}\])/m) + encoder.text_token(match, :content) + + delim = scan(/\]={#{num_equals}}\]/) + encoder.text_token(delim, :delimiter) + else # No terminator found till EOF + encoder.text_token(rest, :error) + terminate + end + encoder.end_group(:comment) + state = :initial + + when :long_string + if match = scan(/.*?(?=\]={#{num_equals}}\])/m) # Long strings do not interpret any escape sequences + encoder.text_token(match, :content) + + delim = scan(/\]={#{num_equals}}\]/) + encoder.text_token(delim, :delimiter) + else # No terminator found till EOF + encoder.text_token(rest, :error) + terminate + end + encoder.end_group(:string) + state = :initial + + when :string + if match = scan(/[^\\#{start_delim}\n]+/) # Everything except \ and the start delimiter character is string content (newlines are only allowed if preceeded by \ or \z) + encoder.text_token(match, :content) + elsif match = scan(/\\(?:['"abfnrtv\\]|z\s*|x\h\h|\d{1,3}|\n)/m) + encoder.text_token(match, :char) + elsif match = scan(Regexp.compile(start_delim)) + encoder.text_token(match, :delimiter) + encoder.end_group(:string) + state = :initial + elsif match = scan(/\n/) # Lua forbids unescaped newlines in normal non-long strings + encoder.text_token("\\n\n", :error) # Visually appealing error indicator--otherwise users may wonder whether the highlighter cannot highlight multine strings + encoder.end_group(:string) + state = :initial + else + encoder.text_token(getch, :error) + end + + when :map + if match = scan(/[,;]/) + encoder.text_token(match, :operator) + elsif match = scan(/[a-zA-Z_][a-zA-Z0-9_]* (?=\s*=)/x) + encoder.text_token(match, :key) + encoder.text_token(scan(/\s+/), :space) if check(/\s+/) + encoder.text_token(scan(/\=/), :operator) + state = :initial + elsif match = scan(/\s+/m) + encoder.text_token(match, :space) + else + # Note this clause doesn’t advance the scan pointer, it’s a kind of + # "retry with other options" (the :initial state then of course + # advances the pointer). + state = :initial + end + else + raise + end + + end + + if options[:keep_state] + @state = state + end + + encoder.end_group :string if [:string].include? state + brace_depth.times { encoder.end_group :map } + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/php.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/php.rb new file mode 100644 index 0000000000..7a8d75d9c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/php.rb @@ -0,0 +1,527 @@ +# encoding: utf-8 +module CodeRay +module Scanners + + load :html + + # Scanner for PHP. + # + # Original by Stefan Walk. + class PHP < Scanner + + register_for :php + file_extension 'php' + + KINDS_NOT_LOC = HTML::KINDS_NOT_LOC + + protected + + def setup + @html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true + end + + def reset_instance + super + @html_scanner.reset + end + + module Words # :nodoc: + + # according to http://www.php.net/manual/en/reserved.keywords.php + KEYWORDS = %w[ + abstract and array as break case catch class clone const continue declare default do else elseif + enddeclare endfor endforeach endif endswitch endwhile extends final for foreach function global + goto if implements interface instanceof namespace new or private protected public static switch + throw try use var while xor + cfunction old_function + ] + + TYPES = %w[ int integer float double bool boolean string array object resource ] + + LANGUAGE_CONSTRUCTS = %w[ + die echo empty exit eval include include_once isset list + require require_once return print unset + ] + + CLASSES = %w[ Directory stdClass __PHP_Incomplete_Class exception php_user_filter Closure ] + + # according to http://php.net/quickref.php on 2009-04-21; + # all functions with _ excluded (module functions) and selected additional functions + BUILTIN_FUNCTIONS = %w[ + abs acos acosh addcslashes addslashes aggregate array arsort ascii2ebcdic asin asinh asort assert atan atan2 + atanh basename bcadd bccomp bcdiv bcmod bcmul bcpow bcpowmod bcscale bcsqrt bcsub bin2hex bindec + bindtextdomain bzclose bzcompress bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite + calculhmac ceil chdir checkdate checkdnsrr chgrp chmod chop chown chr chroot clearstatcache closedir closelog + compact constant copy cos cosh count crc32 crypt current date dcgettext dcngettext deaggregate decbin dechex + decoct define defined deg2rad delete dgettext die dirname diskfreespace dl dngettext doubleval each + ebcdic2ascii echo empty end ereg eregi escapeshellarg escapeshellcmd eval exec exit exp explode expm1 extract + fclose feof fflush fgetc fgetcsv fgets fgetss file fileatime filectime filegroup fileinode filemtime fileowner + fileperms filepro filesize filetype floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv + fputs fread frenchtojd fscanf fseek fsockopen fstat ftell ftok ftruncate fwrite getallheaders getcwd getdate + getenv gethostbyaddr gethostbyname gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid + getmyuid getopt getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext + gettimeofday gettype glob gmdate gmmktime gmstrftime gregoriantojd gzclose gzcompress gzdecode gzdeflate + gzencode gzeof gzfile gzgetc gzgets gzgetss gzinflate gzopen gzpassthru gzputs gzread gzrewind gzseek gztell + gzuncompress gzwrite hash header hebrev hebrevc hexdec htmlentities htmlspecialchars hypot iconv idate + implode include intval ip2long iptcembed iptcparse isset + jddayofweek jdmonthname jdtofrench jdtogregorian jdtojewish jdtojulian jdtounix jewishtojd join jpeg2wbmp + juliantojd key krsort ksort lcfirst lchgrp lchown levenshtein link linkinfo list localeconv localtime log + log10 log1p long2ip lstat ltrim mail main max md5 metaphone mhash microtime min mkdir mktime msql natcasesort + natsort next ngettext nl2br nthmac octdec opendir openlog + ord overload pack passthru pathinfo pclose pfsockopen phpcredits phpinfo phpversion pi png2wbmp popen pos pow + prev print printf putenv quotemeta rad2deg rand range rawurldecode rawurlencode readdir readfile readgzfile + readline readlink realpath recode rename require reset rewind rewinddir rmdir round rsort rtrim scandir + serialize setcookie setlocale setrawcookie settype sha1 shuffle signeurlpaiement sin sinh sizeof sleep snmpget + snmpgetnext snmprealwalk snmpset snmpwalk snmpwalkoid sort soundex split spliti sprintf sqrt srand sscanf stat + strcasecmp strchr strcmp strcoll strcspn strftime stripcslashes stripos stripslashes stristr strlen + strnatcasecmp strnatcmp strncasecmp strncmp strpbrk strpos strptime strrchr strrev strripos strrpos strspn + strstr strtok strtolower strtotime strtoupper strtr strval substr symlink syslog system tan tanh tempnam + textdomain time tmpfile touch trim uasort ucfirst ucwords uksort umask uniqid unixtojd unlink unpack + unserialize unset urldecode urlencode usleep usort vfprintf virtual vprintf vsprintf wordwrap + array_change_key_case array_chunk array_combine array_count_values array_diff array_diff_assoc + array_diff_key array_diff_uassoc array_diff_ukey array_fill array_fill_keys array_filter array_flip + array_intersect array_intersect_assoc array_intersect_key array_intersect_uassoc array_intersect_ukey + array_key_exists array_keys array_map array_merge array_merge_recursive array_multisort array_pad + array_pop array_product array_push array_rand array_reduce array_reverse array_search array_shift + array_slice array_splice array_sum array_udiff array_udiff_assoc array_udiff_uassoc array_uintersect + array_uintersect_assoc array_uintersect_uassoc array_unique array_unshift array_values array_walk + array_walk_recursive + assert_options base_convert base64_decode base64_encode + chunk_split class_exists class_implements class_parents + count_chars debug_backtrace debug_print_backtrace debug_zval_dump + error_get_last error_log error_reporting extension_loaded + file_exists file_get_contents file_put_contents load_file + func_get_arg func_get_args func_num_args function_exists + get_browser get_called_class get_cfg_var get_class get_class_methods get_class_vars + get_current_user get_declared_classes get_declared_interfaces get_defined_constants + get_defined_functions get_defined_vars get_extension_funcs get_headers get_html_translation_table + get_include_path get_included_files get_loaded_extensions get_magic_quotes_gpc get_magic_quotes_runtime + get_meta_tags get_object_vars get_parent_class get_required_filesget_resource_type + gc_collect_cycles gc_disable gc_enable gc_enabled + halt_compiler headers_list headers_sent highlight_file highlight_string + html_entity_decode htmlspecialchars_decode + in_array include_once inclued_get_data + is_a is_array is_binary is_bool is_buffer is_callable is_dir is_double is_executable is_file is_finite + is_float is_infinite is_int is_integer is_link is_long is_nan is_null is_numeric is_object is_readable + is_real is_resource is_scalar is_soap_fault is_string is_subclass_of is_unicode is_uploaded_file + is_writable is_writeable + locale_get_default locale_set_default + number_format override_function parse_str parse_url + php_check_syntax php_ini_loaded_file php_ini_scanned_files php_logo_guid php_sapi_name + php_strip_whitespace php_uname + preg_filter preg_grep preg_last_error preg_match preg_match_all preg_quote preg_replace + preg_replace_callback preg_split print_r + require_once register_shutdown_function register_tick_function + set_error_handler set_exception_handler set_file_buffer set_include_path + set_magic_quotes_runtime set_time_limit shell_exec + str_getcsv str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split str_word_count + strip_tags substr_compare substr_count substr_replace + time_nanosleep time_sleep_until + token_get_all token_name trigger_error + unregister_tick_function use_soap_error_handler user_error + utf8_decode utf8_encode var_dump var_export + version_compare + zend_logo_guid zend_thread_id zend_version + create_function call_user_func_array + posix_access posix_ctermid posix_get_last_error posix_getcwd posix_getegid + posix_geteuid posix_getgid posix_getgrgid posix_getgrnam posix_getgroups + posix_getlogin posix_getpgid posix_getpgrp posix_getpid posix_getppid + posix_getpwnam posix_getpwuid posix_getrlimit posix_getsid posix_getuid + posix_initgroups posix_isatty posix_kill posix_mkfifo posix_mknod + posix_setegid posix_seteuid posix_setgid posix_setpgid posix_setsid + posix_setuid posix_strerror posix_times posix_ttyname posix_uname + pcntl_alarm pcntl_exec pcntl_fork pcntl_getpriority pcntl_setpriority + pcntl_signal pcntl_signal_dispatch pcntl_sigprocmask pcntl_sigtimedwait + pcntl_sigwaitinfo pcntl_wait pcntl_waitpid pcntl_wexitstatus pcntl_wifexited + pcntl_wifsignaled pcntl_wifstopped pcntl_wstopsig pcntl_wtermsig + ] + # TODO: more built-in PHP functions? + + EXCEPTIONS = %w[ + E_ERROR E_WARNING E_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING + E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_DEPRECATED E_USER_DEPRECATED E_ALL E_STRICT + ] + + CONSTANTS = %w[ + null true false self parent + __LINE__ __DIR__ __FILE__ __LINE__ + __CLASS__ __NAMESPACE__ __METHOD__ __FUNCTION__ + PHP_VERSION PHP_MAJOR_VERSION PHP_MINOR_VERSION PHP_RELEASE_VERSION PHP_VERSION_ID PHP_EXTRA_VERSION PHP_ZTS + PHP_DEBUG PHP_MAXPATHLEN PHP_OS PHP_SAPI PHP_EOL PHP_INT_MAX PHP_INT_SIZE DEFAULT_INCLUDE_PATH + PEAR_INSTALL_DIR PEAR_EXTENSION_DIR PHP_EXTENSION_DIR PHP_PREFIX PHP_BINDIR PHP_LIBDIR PHP_DATADIR + PHP_SYSCONFDIR PHP_LOCALSTATEDIR PHP_CONFIG_FILE_PATH PHP_CONFIG_FILE_SCAN_DIR PHP_SHLIB_SUFFIX + PHP_OUTPUT_HANDLER_START PHP_OUTPUT_HANDLER_CONT PHP_OUTPUT_HANDLER_END + __COMPILER_HALT_OFFSET__ + EXTR_OVERWRITE EXTR_SKIP EXTR_PREFIX_SAME EXTR_PREFIX_ALL EXTR_PREFIX_INVALID EXTR_PREFIX_IF_EXISTS + EXTR_IF_EXISTS SORT_ASC SORT_DESC SORT_REGULAR SORT_NUMERIC SORT_STRING CASE_LOWER CASE_UPPER COUNT_NORMAL + COUNT_RECURSIVE ASSERT_ACTIVE ASSERT_CALLBACK ASSERT_BAIL ASSERT_WARNING ASSERT_QUIET_EVAL CONNECTION_ABORTED + CONNECTION_NORMAL CONNECTION_TIMEOUT INI_USER INI_PERDIR INI_SYSTEM INI_ALL M_E M_LOG2E M_LOG10E M_LN2 M_LN10 + M_PI M_PI_2 M_PI_4 M_1_PI M_2_PI M_2_SQRTPI M_SQRT2 M_SQRT1_2 CRYPT_SALT_LENGTH CRYPT_STD_DES CRYPT_EXT_DES + CRYPT_MD5 CRYPT_BLOWFISH DIRECTORY_SEPARATOR SEEK_SET SEEK_CUR SEEK_END LOCK_SH LOCK_EX LOCK_UN LOCK_NB + HTML_SPECIALCHARS HTML_ENTITIES ENT_COMPAT ENT_QUOTES ENT_NOQUOTES INFO_GENERAL INFO_CREDITS + INFO_CONFIGURATION INFO_MODULES INFO_ENVIRONMENT INFO_VARIABLES INFO_LICENSE INFO_ALL CREDITS_GROUP + CREDITS_GENERAL CREDITS_SAPI CREDITS_MODULES CREDITS_DOCS CREDITS_FULLPAGE CREDITS_QA CREDITS_ALL STR_PAD_LEFT + STR_PAD_RIGHT STR_PAD_BOTH PATHINFO_DIRNAME PATHINFO_BASENAME PATHINFO_EXTENSION PATH_SEPARATOR CHAR_MAX + LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_ALL LC_MESSAGES ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 + ABDAY_6 ABDAY_7 DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7 ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6 + ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12 MON_1 MON_2 MON_3 MON_4 MON_5 MON_6 MON_7 MON_8 MON_9 + MON_10 MON_11 MON_12 AM_STR PM_STR D_T_FMT D_FMT T_FMT T_FMT_AMPM ERA ERA_YEAR ERA_D_T_FMT ERA_D_FMT ERA_T_FMT + ALT_DIGITS INT_CURR_SYMBOL CURRENCY_SYMBOL CRNCYSTR MON_DECIMAL_POINT MON_THOUSANDS_SEP MON_GROUPING + POSITIVE_SIGN NEGATIVE_SIGN INT_FRAC_DIGITS FRAC_DIGITS P_CS_PRECEDES P_SEP_BY_SPACE N_CS_PRECEDES + N_SEP_BY_SPACE P_SIGN_POSN N_SIGN_POSN DECIMAL_POINT RADIXCHAR THOUSANDS_SEP THOUSEP GROUPING YESEXPR NOEXPR + YESSTR NOSTR CODESET LOG_EMERG LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG LOG_KERN + LOG_USER LOG_MAIL LOG_DAEMON LOG_AUTH LOG_SYSLOG LOG_LPR LOG_NEWS LOG_UUCP LOG_CRON LOG_AUTHPRIV LOG_LOCAL0 + LOG_LOCAL1 LOG_LOCAL2 LOG_LOCAL3 LOG_LOCAL4 LOG_LOCAL5 LOG_LOCAL6 LOG_LOCAL7 LOG_PID LOG_CONS LOG_ODELAY + LOG_NDELAY LOG_NOWAIT LOG_PERROR + ] + + PREDEFINED = %w[ + $GLOBALS $_SERVER $_GET $_POST $_FILES $_REQUEST $_SESSION $_ENV + $_COOKIE $php_errormsg $HTTP_RAW_POST_DATA $http_response_header + $argc $argv + ] + + IDENT_KIND = WordList::CaseIgnoring.new(:ident). + add(KEYWORDS, :keyword). + add(TYPES, :predefined_type). + add(LANGUAGE_CONSTRUCTS, :keyword). + add(BUILTIN_FUNCTIONS, :predefined). + add(CLASSES, :predefined_constant). + add(EXCEPTIONS, :exception). + add(CONSTANTS, :predefined_constant) + + VARIABLE_KIND = WordList.new(:local_variable). + add(PREDEFINED, :predefined) + end + + module RE # :nodoc: + + PHP_START = / + ]*?language\s*=\s*"php"[^>]*?> | + ]*?language\s*=\s*'php'[^>]*?> | + <\?php\d? | + <\?(?!xml) + /xi + + PHP_END = %r! + | + \?> + !xi + + HTML_INDICATOR = / ]/i + + IDENTIFIER = 'ä'[/[[:alpha:]]/] == 'ä' ? Regexp.new('[[:alpha:]_[^\0-\177]][[:alnum:]_[^\0-\177]]*') : Regexp.new('[a-z_\x7f-\xFF][a-z0-9_\x7f-\xFF]*', true) + VARIABLE = /\$#{IDENTIFIER}/ + + OPERATOR = / + \.(?!\d)=? | # dot that is not decimal point, string concatenation + && | \|\| | # logic + :: | -> | => | # scope, member, dictionary + \\(?!\n) | # namespace + \+\+ | -- | # increment, decrement + [,;?:()\[\]{}] | # simple delimiters + [-+*\/%&|^]=? | # ordinary math, binary logic, assignment shortcuts + [~$] | # whatever + =& | # reference assignment + [=!]=?=? | <> | # comparison and assignment + <<=? | >>=? | [<>]=? # comparison and shift + /x + + end + + protected + + def scan_tokens encoder, options + + if check(RE::PHP_START) || # starts with #{RE::IDENTIFIER}/o) + encoder.begin_group :inline + encoder.text_token match, :local_variable + encoder.text_token scan(/->/), :operator + encoder.text_token scan(/#{RE::IDENTIFIER}/o), :ident + encoder.end_group :inline + elsif check(/->/) + match << scan(/->/) + encoder.text_token match, :error + else + encoder.text_token match, :local_variable + end + elsif match = scan(/\{/) + if check(/\$/) + encoder.begin_group :inline + states[-1] = [states.last, delimiter] + delimiter = nil + states.push :php_inline + encoder.text_token match, :delimiter + else + encoder.text_token match, :content + end + elsif match = scan(/\$\{#{RE::IDENTIFIER}\}/o) + encoder.text_token match, :local_variable + elsif match = scan(/\$/) + encoder.text_token match, :content + else + encoder.end_group :string + states.pop + end + + when :class_expected + if match = scan(/\s+/) + encoder.text_token match, :space + elsif match = scan(/#{RE::IDENTIFIER}/o) + encoder.text_token match, :class + states.pop + else + states.pop + end + + when :function_expected + if match = scan(/\s+/) + encoder.text_token match, :space + elsif match = scan(/&/) + encoder.text_token match, :operator + elsif match = scan(/#{RE::IDENTIFIER}/o) + encoder.text_token match, :function + states.pop + else + states.pop + end + + else + raise_inspect 'Unknown state!', encoder, states + end + + end + + while state = states.pop + encoder.end_group :string if [:sqstring, :dqstring].include? state + if state.is_a? Array + encoder.end_group :inline + encoder.end_group :string if [:sqstring, :dqstring].include? state.first + end + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/python.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/python.rb new file mode 100644 index 0000000000..5da553a6db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/python.rb @@ -0,0 +1,287 @@ +module CodeRay +module Scanners + + # Scanner for Python. Supports Python 3. + # + # Based on pygments' PythonLexer, see + # http://dev.pocoo.org/projects/pygments/browser/pygments/lexers/agile.py. + class Python < Scanner + + register_for :python + file_extension 'py' + + KEYWORDS = [ + 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', + 'del', 'elif', 'else', 'except', 'finally', 'for', + 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', + 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield', + 'nonlocal', # new in Python 3 + ] # :nodoc: + + OLD_KEYWORDS = [ + 'exec', 'print', # gone in Python 3 + ] # :nodoc: + + PREDEFINED_METHODS_AND_TYPES = %w[ + __import__ abs all any apply basestring bin bool buffer + bytearray bytes callable chr classmethod cmp coerce compile + complex delattr dict dir divmod enumerate eval execfile exit + file filter float frozenset getattr globals hasattr hash hex id + input int intern isinstance issubclass iter len list locals + long map max min next object oct open ord pow property range + raw_input reduce reload repr reversed round set setattr slice + sorted staticmethod str sum super tuple type unichr unicode + vars xrange zip + ] # :nodoc: + + PREDEFINED_EXCEPTIONS = %w[ + ArithmeticError AssertionError AttributeError + BaseException DeprecationWarning EOFError EnvironmentError + Exception FloatingPointError FutureWarning GeneratorExit IOError + ImportError ImportWarning IndentationError IndexError KeyError + KeyboardInterrupt LookupError MemoryError NameError + NotImplemented NotImplementedError OSError OverflowError + OverflowWarning PendingDeprecationWarning ReferenceError + RuntimeError RuntimeWarning StandardError StopIteration + SyntaxError SyntaxWarning SystemError SystemExit TabError + TypeError UnboundLocalError UnicodeDecodeError + UnicodeEncodeError UnicodeError UnicodeTranslateError + UnicodeWarning UserWarning ValueError Warning ZeroDivisionError + ] # :nodoc: + + PREDEFINED_VARIABLES_AND_CONSTANTS = [ + 'False', 'True', 'None', # "keywords" since Python 3 + 'self', 'Ellipsis', 'NotImplemented', + ] # :nodoc: + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(OLD_KEYWORDS, :old_keyword). + add(PREDEFINED_METHODS_AND_TYPES, :predefined). + add(PREDEFINED_VARIABLES_AND_CONSTANTS, :predefined_constant). + add(PREDEFINED_EXCEPTIONS, :exception) # :nodoc: + + NAME = / [[:alpha:]_] \w* /x # :nodoc: + ESCAPE = / [abfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} | N\{[-\w ]+\} /x # :nodoc: + + OPERATOR = / + \.\.\. | # ellipsis + \.(?!\d) | # dot but not decimal point + [,;:()\[\]{}] | # simple delimiters + \/\/=? | \*\*=? | # special math + [-+*\/%&|^]=? | # ordinary math and binary logic + [~`] | # binary complement and inspection + <<=? | >>=? | [<>=]=? | != # comparison and assignment + /x # :nodoc: + + STRING_DELIMITER_REGEXP = Hash.new { |h, delimiter| + h[delimiter] = Regexp.union delimiter # :nodoc: + } + + STRING_CONTENT_REGEXP = Hash.new { |h, delimiter| + h[delimiter] = / [^\\\n]+? (?= \\ | $ | #{Regexp.escape(delimiter)} ) /x # :nodoc: + } + + DEF_NEW_STATE = WordList.new(:initial). + add(%w(def), :def_expected). + add(%w(import from), :include_expected). + add(%w(class), :class_expected) # :nodoc: + + DESCRIPTOR = / + #{NAME} + (?: \. #{NAME} )* + | \* + /x # :nodoc: + + DOCSTRING_COMING = / + [ \t]* u?r? ("""|''') + /x # :nodoc: + + protected + + def scan_tokens encoder, options + + state = :initial + string_delimiter = nil + string_raw = false + string_type = nil + docstring_coming = match?(/#{DOCSTRING_COMING}/o) + last_token_dot = false + unicode = string.respond_to?(:encoding) && string.encoding.name == 'UTF-8' + from_import_state = [] + + until eos? + + if state == :string + if match = scan(STRING_DELIMITER_REGEXP[string_delimiter]) + encoder.text_token match, :delimiter + encoder.end_group string_type + string_type = nil + state = :initial + next + elsif string_delimiter.size == 3 && match = scan(/\n/) + encoder.text_token match, :content + elsif match = scan(STRING_CONTENT_REGEXP[string_delimiter]) + encoder.text_token match, :content + elsif !string_raw && match = scan(/ \\ #{ESCAPE} /ox) + encoder.text_token match, :char + elsif match = scan(/ \\ #{UNICODE_ESCAPE} /ox) + encoder.text_token match, :char + elsif match = scan(/ \\ . /x) + encoder.text_token match, :content + elsif match = scan(/ \\ | $ /x) + encoder.end_group string_type + string_type = nil + encoder.text_token match, :error unless match.empty? + state = :initial + else + raise_inspect "else case \" reached; %p not handled." % peek(1), encoder, state + end + + elsif match = scan(/ [ \t]+ | \\?\n /x) + encoder.text_token match, :space + if match == "\n" + state = :initial if state == :include_expected + docstring_coming = true if match?(/#{DOCSTRING_COMING}/o) + end + next + + elsif match = scan(/ \# [^\n]* /mx) + encoder.text_token match, :comment + next + + elsif state == :initial + + if match = scan(/#{OPERATOR}/o) + encoder.text_token match, :operator + + elsif match = scan(/(u?r?|b)?("""|"|'''|')/i) + modifiers = self[1] + string_delimiter = self[2] + string_type = docstring_coming ? :docstring : (modifiers == 'b' ? :binary : :string) + docstring_coming = false if docstring_coming + encoder.begin_group string_type + string_raw = false + unless modifiers.empty? + string_raw = !!modifiers.index(?r) + encoder.text_token modifiers, :modifier + match = string_delimiter + end + state = :string + encoder.text_token match, :delimiter + + # TODO: backticks + + elsif match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) + kind = IDENT_KIND[match] + # TODO: keyword arguments + kind = :ident if last_token_dot + if kind == :old_keyword + kind = check(/\(/) ? :ident : :keyword + elsif kind == :predefined && check(/ *=/) + kind = :ident + elsif kind == :keyword + state = DEF_NEW_STATE[match] + from_import_state << match.to_sym if state == :include_expected + end + encoder.text_token match, kind + + elsif match = scan(/@[a-zA-Z0-9_.]+[lL]?/) + encoder.text_token match, :decorator + + elsif match = scan(/0[xX][0-9A-Fa-f]+[lL]?/) + encoder.text_token match, :hex + + elsif match = scan(/0[bB][01]+[lL]?/) + encoder.text_token match, :binary + + elsif match = scan(/(?:\d*\.\d+|\d+\.\d*)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+/) + if scan(/[jJ]/) + match << matched + encoder.text_token match, :imaginary + else + encoder.text_token match, :float + end + + elsif match = scan(/0[oO][0-7]+|0[0-7]+(?![89.eE])[lL]?/) + encoder.text_token match, :octal + + elsif match = scan(/\d+([lL])?/) + if self[1] == nil && scan(/[jJ]/) + match << matched + encoder.text_token match, :imaginary + else + encoder.text_token match, :integer + end + + else + encoder.text_token getch, :error + + end + + elsif state == :def_expected + state = :initial + if match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) + encoder.text_token match, :method + else + next + end + + elsif state == :class_expected + state = :initial + if match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) + encoder.text_token match, :class + else + next + end + + elsif state == :include_expected + if match = scan(unicode ? /#{DESCRIPTOR}/uo : /#{DESCRIPTOR}/o) + if match == 'as' + encoder.text_token match, :keyword + from_import_state << :as + elsif from_import_state.first == :from && match == 'import' + encoder.text_token match, :keyword + from_import_state << :import + elsif from_import_state.last == :as + # encoder.text_token match, match[0,1][unicode ? /[[:upper:]]/u : /[[:upper:]]/] ? :class : :method + encoder.text_token match, :ident + from_import_state.pop + elsif IDENT_KIND[match] == :keyword + unscan + match = nil + state = :initial + next + else + encoder.text_token match, :include + end + elsif match = scan(/,/) + from_import_state.pop if from_import_state.last == :as + encoder.text_token match, :operator + else + from_import_state = [] + state = :initial + next + end + + else + raise_inspect 'Unknown state', encoder, state + + end + + last_token_dot = match == '.' + + end + + if state == :string + encoder.end_group string_type + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/raydebug.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/raydebug.rb new file mode 100644 index 0000000000..1effdc851e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/raydebug.rb @@ -0,0 +1,75 @@ +require 'set' + +module CodeRay +module Scanners + + # = Raydebug Scanner + # + # Highlights the output of the Encoders::Debug encoder. + class Raydebug < Scanner + + register_for :raydebug + file_extension 'raydebug' + title 'CodeRay Token Dump' + + protected + + def setup + super + @known_token_kinds = TokenKinds.keys.map(&:to_s).to_set + end + + def scan_tokens encoder, options + + opened_tokens = [] + + until eos? + + if match = scan(/\s+/) + encoder.text_token match, :space + + elsif match = scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) /x) + kind = self[1] + encoder.text_token kind, :class + encoder.text_token '(', :operator + match = self[2] + unless match.empty? + if @known_token_kinds.include? kind + encoder.text_token match, kind.to_sym + else + encoder.text_token match, :plain + end + end + encoder.text_token match, :operator if match = scan(/\)/) + + elsif match = scan(/ (\w+) ([<\[]) /x) + encoder.text_token self[1], :class + if @known_token_kinds.include? self[1] + kind = self[1].to_sym + else + kind = :unknown + end + opened_tokens << kind + encoder.begin_group kind + encoder.text_token self[2], :operator + + elsif !opened_tokens.empty? && match = scan(/ [>\]] /x) + encoder.text_token match, :operator + encoder.end_group opened_tokens.pop + + else + encoder.text_token getch, :space + + end + + end + + encoder.end_group opened_tokens.pop until opened_tokens.empty? + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby.rb new file mode 100644 index 0000000000..5b8de42fab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby.rb @@ -0,0 +1,477 @@ +module CodeRay +module Scanners + + # This scanner is really complex, since Ruby _is_ a complex language! + # + # It tries to highlight 100% of all common code, + # and 90% of strange codes. + # + # It is optimized for HTML highlighting, and is not very useful for + # parsing or pretty printing. + class Ruby < Scanner + + register_for :ruby + file_extension 'rb' + + autoload :Patterns, CodeRay.coderay_path('scanners', 'ruby', 'patterns') + autoload :StringState, CodeRay.coderay_path('scanners', 'ruby', 'string_state') + + def interpreted_string_state + StringState.new :string, true, '"' + end + + protected + + def setup + @state = :initial + end + + def scan_tokens encoder, options + state, heredocs = options[:state] || @state + heredocs = heredocs.dup if heredocs.is_a?(Array) + + if state && state.instance_of?(StringState) + encoder.begin_group state.type + end + + last_state = nil + + method_call_expected = false + value_expected = true + + inline_block_stack = nil + inline_block_curly_depth = 0 + + if heredocs + state = heredocs.shift + encoder.begin_group state.type + heredocs = nil if heredocs.empty? + end + + # def_object_stack = nil + # def_object_paren_depth = 0 + + patterns = Patterns # avoid constant lookup + + unicode = string.respond_to?(:encoding) && string.encoding.name == 'UTF-8' + + until eos? + + if state.instance_of? ::Symbol + + if match = scan(/[ \t\f\v]+/) + encoder.text_token match, :space + + elsif match = scan(/\n/) + if heredocs + unscan # heredoc scanning needs \n at start + state = heredocs.shift + encoder.begin_group state.type + heredocs = nil if heredocs.empty? + else + state = :initial if state == :undef_comma_expected + encoder.text_token match, :space + value_expected = true + end + + elsif match = scan(bol? ? / \#(!)?.* | #{patterns::RUBYDOC_OR_DATA} /ox : /\#.*/) + encoder.text_token match, self[1] ? :doctype : :comment + + elsif match = scan(/\\\n/) + if heredocs + unscan # heredoc scanning needs \n at start + encoder.text_token scan(/\\/), :space + state = heredocs.shift + encoder.begin_group state.type + heredocs = nil if heredocs.empty? + else + encoder.text_token match, :space + end + + elsif state == :initial + + # IDENTS # + if !method_call_expected && + match = scan(unicode ? /#{patterns::METHOD_NAME}/uo : + /#{patterns::METHOD_NAME}/o) + + kind = patterns::IDENT_KIND[match] + if value_expected != :colon_expected && scan(/:(?!:)/) + value_expected = true + encoder.text_token match, :key + encoder.text_token ':', :operator + else + value_expected = false + if kind == :ident + if match[/\A[A-Z]/] && !(match[/[!?]$/] || match?(/\(/)) + kind = :constant + end + elsif kind == :keyword + state = patterns::KEYWORD_NEW_STATE[match] + if patterns::KEYWORDS_EXPECTING_VALUE[match] + value_expected = match == 'when' ? :colon_expected : true + end + end + value_expected = true if !value_expected && check(/#{patterns::VALUE_FOLLOWS}/o) + encoder.text_token match, kind + end + + elsif method_call_expected && + match = scan(unicode ? /#{patterns::METHOD_AFTER_DOT}/uo : + /#{patterns::METHOD_AFTER_DOT}/o) + if method_call_expected == '::' && match[/\A[A-Z]/] && !match?(/\(/) + encoder.text_token match, :constant + else + encoder.text_token match, :ident + end + method_call_expected = false + value_expected = check(/#{patterns::VALUE_FOLLOWS}/o) + + # OPERATORS # + elsif !method_call_expected && match = scan(/ (\.(?!\.)|::) | ( \.\.\.? | ==?=? | [,\(\[\{] ) | [\)\]\}] /x) + method_call_expected = self[1] + value_expected = !method_call_expected && !!self[2] + if inline_block_stack + case match + when '{' + inline_block_curly_depth += 1 + when '}' + inline_block_curly_depth -= 1 + if inline_block_curly_depth == 0 # closing brace of inline block reached + state, inline_block_curly_depth, heredocs = inline_block_stack.pop + inline_block_stack = nil if inline_block_stack.empty? + heredocs = nil if heredocs && heredocs.empty? + encoder.text_token match, :inline_delimiter + encoder.end_group :inline + next + end + end + end + encoder.text_token match, :operator + + elsif match = scan(unicode ? /#{patterns::SYMBOL}/uo : + /#{patterns::SYMBOL}/o) + case delim = match[1] + when ?', ?" + encoder.begin_group :symbol + encoder.text_token ':', :symbol + match = delim.chr + encoder.text_token match, :delimiter + state = self.class::StringState.new :symbol, delim == ?", match + else + encoder.text_token match, :symbol + value_expected = false + end + + elsif match = scan(/ ' (?:(?>[^'\\]*) ')? | " (?:(?>[^"\\\#]*) ")? /mx) + if match.size == 1 + kind = check(self.class::StringState.simple_key_pattern(match)) ? :key : :string + encoder.begin_group kind + encoder.text_token match, :delimiter + state = self.class::StringState.new kind, match == '"', match # important for streaming + else + kind = value_expected == true && scan(/:/) ? :key : :string + encoder.begin_group kind + encoder.text_token match[0,1], :delimiter + encoder.text_token match[1..-2], :content if match.size > 2 + encoder.text_token match[-1,1], :delimiter + encoder.end_group kind + encoder.text_token ':', :operator if kind == :key + value_expected = false + end + + elsif match = scan(unicode ? /#{patterns::INSTANCE_VARIABLE}/uo : + /#{patterns::INSTANCE_VARIABLE}/o) + value_expected = false + encoder.text_token match, :instance_variable + + elsif value_expected && match = scan(/\//) + encoder.begin_group :regexp + encoder.text_token match, :delimiter + state = self.class::StringState.new :regexp, true, '/' + + elsif match = scan(value_expected ? /[-+]?#{patterns::NUMERIC}/o : /#{patterns::NUMERIC}/o) + if method_call_expected + encoder.text_token match, :error + method_call_expected = false + else + kind = self[1] ? :float : :integer # TODO: send :hex/:octal/:binary + match << 'r' if match !~ /e/i && scan(/r/) + match << 'i' if scan(/i/) + encoder.text_token match, kind + end + value_expected = false + + elsif match = scan(/ [-+!~^\/]=? | [:;] | &\. | [*|&]{1,2}=? | >>? /x) + value_expected = true + encoder.text_token match, :operator + + elsif value_expected && match = scan(/#{patterns::HEREDOC_OPEN}/o) + quote = self[3] + delim = self[quote ? 4 : 2] + kind = patterns::QUOTE_TO_TYPE[quote] + encoder.begin_group kind + encoder.text_token match, :delimiter + encoder.end_group kind + heredocs ||= [] # create heredocs if empty + heredocs << self.class::StringState.new(kind, quote != "'", delim, + self[1] ? :indented : :linestart) + value_expected = false + + elsif value_expected && match = scan(/#{patterns::FANCY_STRING_START}/o) + kind = patterns::FANCY_STRING_KIND[self[1]] + encoder.begin_group kind + state = self.class::StringState.new kind, patterns::FANCY_STRING_INTERPRETED[self[1]], self[2] + encoder.text_token match, :delimiter + + elsif value_expected && match = scan(/#{patterns::CHARACTER}/o) + value_expected = false + encoder.text_token match, :integer + + elsif match = scan(/ %=? | <(?:<|=>?)? | \? /x) + value_expected = match == '?' ? :colon_expected : true + encoder.text_token match, :operator + + elsif match = scan(/`/) + encoder.begin_group :shell + encoder.text_token match, :delimiter + state = self.class::StringState.new :shell, true, match + + elsif match = scan(unicode ? /#{patterns::GLOBAL_VARIABLE}/uo : + /#{patterns::GLOBAL_VARIABLE}/o) + encoder.text_token match, :global_variable + value_expected = false + + elsif match = scan(unicode ? /#{patterns::CLASS_VARIABLE}/uo : + /#{patterns::CLASS_VARIABLE}/o) + encoder.text_token match, :class_variable + value_expected = false + + elsif match = scan(/\\\z/) + encoder.text_token match, :space + + else + if method_call_expected + method_call_expected = false + next + end + unless unicode + # check for unicode + $DEBUG_BEFORE, $DEBUG = $DEBUG, false + begin + if check(/./mu).size > 1 + # seems like we should try again with unicode + unicode = true + end + rescue + # bad unicode char; use getch + ensure + $DEBUG = $DEBUG_BEFORE + end + next if unicode + end + + encoder.text_token getch, :error + + end + + if last_state + state = last_state unless state.is_a?(StringState) # otherwise, a simple 'def"' results in unclosed tokens + last_state = nil + end + + elsif state == :def_expected + if match = scan(unicode ? /(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/uo : + /(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/o) + encoder.text_token match, :method + state = :initial + else + last_state = :dot_expected + state = :initial + end + + elsif state == :dot_expected + if match = scan(/\.|::/) + # invalid definition + state = :def_expected + encoder.text_token match, :operator + else + state = :initial + end + + elsif state == :module_expected + if match = scan(/<#{patterns::METHOD_NAME_EX})(?!\.|::)/uo : + /(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/o) + encoder.text_token match, :method + elsif match = scan(/#{patterns::SYMBOL}/o) + case delim = match[1] + when ?', ?" + encoder.begin_group :symbol + encoder.text_token ':', :symbol + match = delim.chr + encoder.text_token match, :delimiter + state = self.class::StringState.new :symbol, delim == ?", match + state.next_state = :undef_comma_expected + else + encoder.text_token match, :symbol + end + else + state = :initial + end + + elsif state == :undef_comma_expected + if match = scan(/,/) + encoder.text_token match, :operator + state = :undef_expected + else + state = :initial + end + + elsif state == :alias_expected + match = scan(unicode ? /(#{patterns::METHOD_NAME_OR_SYMBOL})([ \t]+)(#{patterns::METHOD_NAME_OR_SYMBOL})/uo : + /(#{patterns::METHOD_NAME_OR_SYMBOL})([ \t]+)(#{patterns::METHOD_NAME_OR_SYMBOL})/o) + + if match + encoder.text_token self[1], (self[1][0] == ?: ? :symbol : :method) + encoder.text_token self[2], :space + encoder.text_token self[3], (self[3][0] == ?: ? :symbol : :method) + end + state = :initial + + else + #:nocov: + raise_inspect 'Unknown state: %p' % [state], encoder + #:nocov: + end + + else # StringState + + match = scan_until(state.pattern) || scan_rest + unless match.empty? + encoder.text_token match, :content + break if eos? + end + + if state.heredoc && self[1] # end of heredoc + match = getch + match << scan_until(/$/) unless eos? + encoder.text_token match, :delimiter unless match.empty? + encoder.end_group state.type + state = state.next_state + next + end + + case match = getch + + when state.delim + if state.paren_depth + state.paren_depth -= 1 + if state.paren_depth > 0 + encoder.text_token match, :content + next + end + end + encoder.text_token match, :delimiter + if state.type == :regexp && !eos? + match = scan(/#{patterns::REGEXP_MODIFIERS}/o) + encoder.text_token match, :modifier unless match.empty? + end + encoder.end_group state.type + value_expected = false + state = state.next_state + + when '\\' + if state.interpreted + if esc = scan(/#{patterns::ESCAPE}/o) + encoder.text_token match + esc, :char + else + encoder.text_token match, :error + end + else + case esc = getch + when nil + encoder.text_token match, :content + when state.delim, '\\' + encoder.text_token match + esc, :char + else + encoder.text_token match + esc, :content + end + end + + when '#' + case peek(1) + when '{' + inline_block_stack ||= [] + inline_block_stack << [state, inline_block_curly_depth, heredocs] + value_expected = true + state = :initial + inline_block_curly_depth = 1 + encoder.begin_group :inline + encoder.text_token match + getch, :inline_delimiter + when '$', '@' + encoder.text_token match, :escape + last_state = state + state = :initial + else + #:nocov: + raise_inspect 'else-case # reached; #%p not handled' % [peek(1)], encoder + #:nocov: + end + + when state.opening_paren + state.paren_depth += 1 + encoder.text_token match, :content + + else + #:nocov + raise_inspect 'else-case " reached; %p not handled, state = %p' % [match, state], encoder + #:nocov: + + end + + end + + end + + # cleaning up + if state.is_a? StringState + encoder.end_group state.type + end + + if options[:keep_state] + if state.is_a?(StringState) && state.heredoc + (heredocs ||= []).unshift state + state = :initial + elsif heredocs && heredocs.empty? + heredocs = nil + end + @state = state, heredocs + end + + if inline_block_stack + until inline_block_stack.empty? + state, = *inline_block_stack.pop + encoder.end_group :inline + encoder.end_group state.type + end + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/patterns.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/patterns.rb new file mode 100644 index 0000000000..cd942d0dae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/patterns.rb @@ -0,0 +1,178 @@ +# encoding: utf-8 +module CodeRay +module Scanners + + module Ruby::Patterns # :nodoc: all + + KEYWORDS = %w[ + and def end in or unless begin + defined? ensure module redo super until + BEGIN break do next rescue then + when END case else for retry + while alias class elsif if not return + undef yield + ] + + # See http://murfy.de/ruby-constants. + PREDEFINED_CONSTANTS = %w[ + nil true false self + DATA ARGV ARGF ENV + FALSE TRUE NIL + STDERR STDIN STDOUT + TOPLEVEL_BINDING + RUBY_COPYRIGHT RUBY_DESCRIPTION RUBY_ENGINE RUBY_PATCHLEVEL + RUBY_PLATFORM RUBY_RELEASE_DATE RUBY_REVISION RUBY_VERSION + __FILE__ __LINE__ __ENCODING__ + ] + + IDENT_KIND = WordList.new(:ident). + add(KEYWORDS, :keyword). + add(PREDEFINED_CONSTANTS, :predefined_constant) + + KEYWORD_NEW_STATE = WordList.new(:initial). + add(%w[ def ], :def_expected). + add(%w[ undef ], :undef_expected). + add(%w[ alias ], :alias_expected). + add(%w[ class module ], :module_expected) + + IDENT = 'ä'[/[[:alpha:]]/] == 'ä' ? Regexp.new('[[:alpha:]_[^\0-\177]][[:alnum:]_[^\0-\177]]*') : /[^\W\d]\w*/ + + METHOD_NAME = / #{IDENT} [?!]? /ox + METHOD_NAME_OPERATOR = / + \*\*? # multiplication and power + | [-+~]@? # plus, minus, tilde with and without at sign + | [\/%&|^`] # division, modulo or format strings, and, or, xor, system + | \[\]=? # array getter and setter + | << | >> # append or shift left, shift right + | <=?>? | >=? # comparison, rocket operator + | ===? | =~ # simple equality, case equality, match + | ![~=@]? # negation with and without at sign, not-equal and not-match + /ox + METHOD_SUFFIX = / (?: [?!] | = (?![~>]|=(?!>)) ) /x + METHOD_NAME_EX = / #{IDENT} #{METHOD_SUFFIX}? | #{METHOD_NAME_OPERATOR} /ox + METHOD_AFTER_DOT = / #{IDENT} [?!]? | #{METHOD_NAME_OPERATOR} /ox + INSTANCE_VARIABLE = / @ #{IDENT} /ox + CLASS_VARIABLE = / @@ #{IDENT} /ox + OBJECT_VARIABLE = / @@? #{IDENT} /ox + GLOBAL_VARIABLE = / \$ (?: #{IDENT} | [1-9]\d* | 0\w* | [~&+`'=\/,;_.<>!@$?*":\\] | -[a-zA-Z_0-9] ) /ox + PREFIX_VARIABLE = / #{GLOBAL_VARIABLE} | #{OBJECT_VARIABLE} /ox + VARIABLE = / @?@? #{IDENT} | #{GLOBAL_VARIABLE} /ox + + QUOTE_TO_TYPE = { + '`' => :shell, + '/' => :regexp, + } + QUOTE_TO_TYPE.default = :string + + REGEXP_MODIFIERS = /[mousenix]*/ + + DECIMAL = /\d+(?:_\d+)*/ + OCTAL = /0_?[0-7]+(?:_[0-7]+)*/ + HEXADECIMAL = /0x[0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*/ + BINARY = /0b[01]+(?:_[01]+)*/ + + EXPONENT = / [eE] [+-]? #{DECIMAL} /ox + FLOAT_SUFFIX = / #{EXPONENT} | \. #{DECIMAL} #{EXPONENT}? /ox + FLOAT_OR_INT = / #{DECIMAL} (?: #{FLOAT_SUFFIX} () )? /ox + NUMERIC = / (?: (?=0) (?: #{OCTAL} | #{HEXADECIMAL} | #{BINARY} ) | #{FLOAT_OR_INT} ) /ox + + SYMBOL = / + : + (?: + #{METHOD_NAME_EX} + | #{PREFIX_VARIABLE} + | ['"] + ) + /ox + METHOD_NAME_OR_SYMBOL = / #{METHOD_NAME_EX} | #{SYMBOL} /ox + + SIMPLE_ESCAPE = / + [abefnrstv] + | [0-7]{1,3} + | x[0-9A-Fa-f]{1,2} + | . + /mx + + CONTROL_META_ESCAPE = / + (?: M-|C-|c ) + (?: \\ (?: M-|C-|c ) )* + (?: [^\\] | \\ #{SIMPLE_ESCAPE} )? + /mox + + ESCAPE = / + #{CONTROL_META_ESCAPE} | #{SIMPLE_ESCAPE} + /mox + + CHARACTER = / + \? + (?: + [^\s\\] + | \\ #{ESCAPE} + ) + /mox + + # NOTE: This is not completely correct, but + # nobody needs heredoc delimiters ending with \n. + HEREDOC_OPEN = / + << ([-~])? # $1 = float + (?: + ( [A-Za-z_0-9]+ ) # $2 = delim + | + ( ["'`\/] ) # $3 = quote, type + ( [^\n]*? ) \3 # $4 = delim + ) + /mx + + RUBYDOC = / + =begin (?!\S) + .*? + (?: \Z | ^=end (?!\S) [^\n]* ) + /mx + + DATA = / + __END__$ + .*? + (?: \Z | (?=^\#CODE) ) + /mx + + RUBYDOC_OR_DATA = / #{RUBYDOC} | #{DATA} /xo + + # Checks for a valid value to follow. This enables + # value_expected in method calls without parentheses. + VALUE_FOLLOWS = / + (?>[ \t\f\v]+) + (?: + [%\/][^\s=] + | <<-?\S + | [-+] \d + | #{CHARACTER} + ) + /ox + KEYWORDS_EXPECTING_VALUE = WordList.new.add(%w[ + and end in or unless begin + defined? ensure redo super until + break do next rescue then + when case else for retry + while elsif if not return + yield + ]) + + FANCY_STRING_START = / % ( [iIqQrswWx] | (?![a-zA-Z0-9]) ) ([^a-zA-Z0-9]) /x + FANCY_STRING_KIND = Hash.new(:string).merge({ + 'i' => :symbol, + 'I' => :symbol, + 'r' => :regexp, + 's' => :symbol, + 'x' => :shell, + }) + FANCY_STRING_INTERPRETED = Hash.new(true).merge({ + 'i' => false, + 'q' => false, + 's' => false, + 'w' => false, + }) + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/string_state.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/string_state.rb new file mode 100644 index 0000000000..95f1e832ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/ruby/string_state.rb @@ -0,0 +1,79 @@ +# encoding: utf-8 +module CodeRay +module Scanners + + class Ruby + + class StringState < Struct.new :type, :interpreted, :delim, :heredoc, + :opening_paren, :paren_depth, :pattern, :next_state # :nodoc: all + + CLOSING_PAREN = Hash[ *%w[ + ( ) + [ ] + < > + { } + ] ].each { |k,v| k.freeze; v.freeze } # debug, if I try to change it with << + + STRING_PATTERN = Hash.new do |h, k| + delim, interpreted = *k + delim_pattern = Regexp.escape(delim) + if closing_paren = CLOSING_PAREN[delim] + delim_pattern << Regexp.escape(closing_paren) + end + delim_pattern << '\\\\' unless delim == '\\' + + # special_escapes = + # case interpreted + # when :regexp_symbols + # '| [|?*+(){}\[\].^$]' + # end + + if interpreted && delim != '#' + / (?= [#{delim_pattern}] | \# [{$@] ) /mx + else + / (?= [#{delim_pattern}] ) /mx + end.tap do |pattern| + h[k] = pattern if (delim.respond_to?(:ord) ? delim.ord : delim[0]) < 256 + end + end + + def self.simple_key_pattern delim + if delim == "'" + / (?> (?: [^\\']+ | \\. )* ) ' : /mx + else + / (?> (?: [^\\"\#]+ | \\. | \#\$[\\"] | \#\{[^\{\}]+\} | \#(?!\{) )* ) " : /mx + end + end + + def initialize kind, interpreted, delim, heredoc = false + if heredoc + pattern = heredoc_pattern delim, interpreted, heredoc == :indented + delim = nil + else + pattern = STRING_PATTERN[ [delim, interpreted] ] + if closing_paren = CLOSING_PAREN[delim] + opening_paren = delim + delim = closing_paren + paren_depth = 1 + end + end + super kind, interpreted, delim, heredoc, opening_paren, paren_depth, pattern, :initial + end + + def heredoc_pattern delim, interpreted, indented + # delim = delim.dup # workaround for old Ruby + delim_pattern = Regexp.escape(delim) + delim_pattern = / (?:\A|\n) #{ '(?>[ \t]*)' if indented } #{ Regexp.new delim_pattern } $ /x + if interpreted + / (?= #{delim_pattern}() | \\ | \# [{$@] ) /mx # $1 set == end of heredoc + else + / (?= #{delim_pattern}() | \\ ) /mx + end + end + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sass.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sass.rb new file mode 100644 index 0000000000..e3296b90ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sass.rb @@ -0,0 +1,232 @@ +module CodeRay +module Scanners + + # A scanner for Sass. + class Sass < CSS + + register_for :sass + file_extension 'sass' + + protected + + def setup + @state = :initial + end + + def scan_tokens encoder, options + states = Array(options[:state] || @state).dup + + encoder.begin_group :string if states.last == :sqstring || states.last == :dqstring + + until eos? + + if bol? && (match = scan(/(?>( +)?(\/[\*\/])(.+)?)(?=\n)/)) + encoder.text_token self[1], :space if self[1] + encoder.begin_group :comment + encoder.text_token self[2], :delimiter + encoder.text_token self[3], :content if self[3] + if match = scan(/(?:\n+#{self[1]} .*)+/) + encoder.text_token match, :content + end + encoder.end_group :comment + elsif match = scan(/\n|[^\n\S]+\n?/) + encoder.text_token match, :space + if match.index(/\n/) + value_expected = false + states.pop if states.last == :include + end + + elsif states.last == :sass_inline && (match = scan(/\}/)) + encoder.text_token match, :inline_delimiter + encoder.end_group :inline + states.pop + + elsif case states.last + when :initial, :media, :sass_inline + if match = scan(/(?>#{RE::Ident})(?!\()/ox) + encoder.text_token match, value_expected ? :value : (check(/.*:(?![a-z])/) ? :key : :tag) + next + elsif !value_expected && (match = scan(/\*/)) + encoder.text_token match, :tag + next + elsif match = scan(RE::Class) + encoder.text_token match, :class + next + elsif match = scan(RE::Id) + encoder.text_token match, :id + next + elsif match = scan(RE::PseudoClass) + encoder.text_token match, :pseudo_class + next + elsif match = scan(RE::AttributeSelector) + # TODO: Improve highlighting inside of attribute selectors. + encoder.text_token match[0,1], :operator + encoder.text_token match[1..-2], :attribute_name if match.size > 2 + encoder.text_token match[-1,1], :operator if match[-1] == ?] + next + elsif match = scan(/(\=|@mixin +)#{RE::Ident}/o) + encoder.text_token match, :function + next + elsif match = scan(/@import\b/) + encoder.text_token match, :directive + states << :include + next + elsif match = scan(/@media\b/) + encoder.text_token match, :directive + # states.push :media_before_name + next + end + + when :block + if match = scan(/(?>#{RE::Ident})(?!\()/ox) + if value_expected + encoder.text_token match, :value + else + encoder.text_token match, :key + end + next + end + + when :sqstring, :dqstring + if match = scan(states.last == :sqstring ? /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/o : /(?:[^\n\"\#]+|\\\n|#{RE::Escape}|#(?!\{))+/o) + encoder.text_token match, :content + elsif match = scan(/['"]/) + encoder.text_token match, :delimiter + encoder.end_group :string + states.pop + elsif match = scan(/#\{/) + encoder.begin_group :inline + encoder.text_token match, :inline_delimiter + states.push :sass_inline + elsif match = scan(/ \\ | $ /x) + encoder.end_group states.last + encoder.text_token match, :error unless match.empty? + states.pop + else + raise_inspect "else case #{states.last} reached; %p not handled." % peek(1), encoder + end + + when :include + if match = scan(/[^\s'",]+/) + encoder.text_token match, :include + next + end + + else + #:nocov: + raise_inspect 'Unknown state: %p' % [states.last], encoder + #:nocov: + + end + + elsif match = scan(/\$#{RE::Ident}/o) + encoder.text_token match, :variable + next + + elsif match = scan(/&/) + encoder.text_token match, :local_variable + + elsif match = scan(/\+#{RE::Ident}/o) + encoder.text_token match, :include + value_expected = true + + elsif match = scan(/\/\*(?:.*?\*\/|.*)|\/\/.*/) + encoder.text_token match, :comment + + elsif match = scan(/#\{/) + encoder.begin_group :inline + encoder.text_token match, :inline_delimiter + states.push :sass_inline + + elsif match = scan(/\{/) + value_expected = false + encoder.text_token match, :operator + states.push :block + + elsif match = scan(/\}/) + value_expected = false + encoder.text_token match, :operator + if states.last == :block || states.last == :media + states.pop + end + + elsif match = scan(/['"]/) + encoder.begin_group :string + encoder.text_token match, :delimiter + if states.include? :sass_inline + # no nesting, just scan the string until delimiter + content = scan_until(/(?=#{match}|\}|\z)/) + encoder.text_token content, :content unless content.empty? + encoder.text_token match, :delimiter if scan(/#{match}/) + encoder.end_group :string + else + states.push match == "'" ? :sqstring : :dqstring + end + + elsif match = scan(/#{RE::Function}/o) + encoder.begin_group :function + start = match[/^[-\w]+\(/] + encoder.text_token start, :delimiter + if match[-1] == ?) + encoder.text_token match[start.size..-2], :content + encoder.text_token ')', :delimiter + else + encoder.text_token match[start.size..-1], :content if start.size < match.size + end + encoder.end_group :function + + elsif match = scan(/[a-z][-a-z_]*(?=\()/o) + encoder.text_token match, :predefined + + elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) + encoder.text_token match, :float + + elsif match = scan(/#{RE::HexColor}/o) + encoder.text_token match, :color + + elsif match = scan(/! *(?:important|optional)/) + encoder.text_token match, :important + + elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/) + encoder.text_token match, :color + + elsif match = scan(/@else if\b|#{RE::AtKeyword}/o) + encoder.text_token match, :directive + value_expected = true + + elsif match = scan(/ == | != | [-+*\/>~:;,.=()] /x) + if match == ':' + value_expected = true + elsif match == ';' + value_expected = false + end + encoder.text_token match, :operator + + else + encoder.text_token getch, :error + + end + + end + + states.pop if states.last == :include + + if options[:keep_state] + @state = states.dup + end + + while state = states.pop + if state == :sass_inline + encoder.end_group :inline + elsif state == :sqstring || state == :dqstring + encoder.end_group :string + end + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/scanner.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/scanner.rb new file mode 100644 index 0000000000..efa710d971 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/scanner.rb @@ -0,0 +1,337 @@ +# encoding: utf-8 + +module CodeRay + module Scanners + + # = Scanner + # + # The base class for all Scanners. + # + # It is a subclass of Ruby's great +StringScanner+, which + # makes it easy to access the scanning methods inside. + # + # It is also +Enumerable+, so you can use it like an Array of + # Tokens: + # + # require 'coderay' + # + # c_scanner = CodeRay::Scanners[:c].new "if (*p == '{') nest++;" + # + # for text, kind in c_scanner + # puts text if kind == :operator + # end + # + # # prints: (*==)++; + # + # OK, this is a very simple example :) + # You can also use +map+, +any?+, +find+ and even +sort_by+, + # if you want. + class Scanner < StringScanner + + extend Plugin + plugin_host Scanners + + # Raised if a Scanner fails while scanning + ScanError = Class.new StandardError + + # The default options for all scanner classes. + # + # Define @default_options for subclasses. + DEFAULT_OPTIONS = { } + + KINDS_NOT_LOC = [:comment, :doctype, :docstring] + + attr_accessor :state + + class << self + + # Normalizes the given code into a string with UNIX newlines, in the + # scanner's internal encoding, with invalid and undefined charachters + # replaced by placeholders. Always returns a new object. + def normalize code + # original = code + code = code.to_s unless code.is_a? ::String + return code if code.empty? + + if code.respond_to? :encoding + code = encode_with_encoding code, self.encoding + else + code = to_unix code + end + # code = code.dup if code.eql? original + code + end + + # The typical filename suffix for this scanner's language. + def file_extension extension = lang + @file_extension ||= extension.to_s + end + + # The encoding used internally by this scanner. + def encoding name = 'UTF-8' + @encoding ||= defined?(Encoding.find) && Encoding.find(name) + end + + # The lang of this Scanner class, which is equal to its Plugin ID. + def lang + @plugin_id + end + + protected + + def encode_with_encoding code, target_encoding + if code.encoding == target_encoding + if code.valid_encoding? + return to_unix(code) + else + source_encoding = guess_encoding code + end + else + source_encoding = code.encoding + end + # print "encode_with_encoding from #{source_encoding} to #{target_encoding}" + code.encode target_encoding, source_encoding, :universal_newline => true, :undef => :replace, :invalid => :replace + end + + def to_unix code + code.index(?\r) ? code.gsub(/\r\n?/, "\n") : code + end + + def guess_encoding s + #:nocov: + IO.popen("file -b --mime -", "w+") do |file| + file.write s[0, 1024] + file.close_write + begin + Encoding.find file.gets[/charset=([-\w]+)/, 1] + rescue ArgumentError + Encoding::BINARY + end + end + #:nocov: + end + + end + + # Create a new Scanner. + # + # * +code+ is the input String and is handled by the superclass + # StringScanner. + # * +options+ is a Hash with Symbols as keys. + # It is merged with the default options of the class (you can + # overwrite default options here.) + # + # Else, a Tokens object is used. + def initialize code = '', options = {} + if self.class == Scanner + raise NotImplementedError, "I am only the basic Scanner class. I can't scan anything. :( Use my subclasses." + end + + @options = self.class::DEFAULT_OPTIONS.merge options + + super self.class.normalize(code) + + @tokens = options[:tokens] || Tokens.new + @tokens.scanner = self if @tokens.respond_to? :scanner= + + setup + end + + # Sets back the scanner. Subclasses should redefine the reset_instance + # method instead of this one. + def reset + super + reset_instance + end + + # Set a new string to be scanned. + def string= code + code = self.class.normalize(code) + super code + reset_instance + end + + # the Plugin ID for this scanner + def lang + self.class.lang + end + + # the default file extension for this scanner + def file_extension + self.class.file_extension + end + + # Scan the code and returns all tokens in a Tokens object. + def tokenize source = nil, options = {} + options = @options.merge(options) + + set_tokens_from_options options + set_string_from_source source + + begin + scan_tokens @tokens, options + rescue => e + message = "Error in %s#scan_tokens, initial state was: %p" % [self.class, defined?(state) && state] + raise_inspect e.message, @tokens, message, 30, e.backtrace + end + + @cached_tokens = @tokens + if source.is_a? Array + @tokens.split_into_parts(*source.map { |part| part.size }) + else + @tokens + end + end + + # Cache the result of tokenize. + def tokens + @cached_tokens ||= tokenize + end + + # Traverse the tokens. + def each &block + tokens.each(&block) + end + include Enumerable + + # The current line position of the scanner, starting with 1. + # See also: #column. + # + # Beware, this is implemented inefficiently. It should be used + # for debugging only. + def line pos = self.pos + return 1 if pos <= 0 + binary_string[0...pos].count("\n") + 1 + end + + # The current column position of the scanner, starting with 1. + # See also: #line. + def column pos = self.pos + return 1 if pos <= 0 + pos - (binary_string.rindex(?\n, pos - 1) || -1) + end + + # The string in binary encoding. + # + # To be used with #pos, which is the index of the byte the scanner + # will scan next. + def binary_string + @binary_string ||= + if string.respond_to?(:bytesize) && string.bytesize != string.size + #:nocov: + string.dup.force_encoding('binary') + #:nocov: + else + string + end + end + + protected + + # Can be implemented by subclasses to do some initialization + # that has to be done once per instance. + # + # Use reset for initialization that has to be done once per + # scan. + def setup # :doc: + end + + def set_string_from_source source + case source + when Array + self.string = self.class.normalize(source.join) + when nil + reset + else + self.string = self.class.normalize(source) + end + end + + def set_tokens_from_options options + @tokens = options[:tokens] || @tokens || Tokens.new + @tokens.scanner = self if @tokens.respond_to? :scanner= + end + + # This is the central method, and commonly the only one a + # subclass implements. + # + # Subclasses must implement this method; it must return +tokens+ + # and must only use Tokens#<< for storing scanned tokens! + def scan_tokens tokens, options # :doc: + raise NotImplementedError, "#{self.class}#scan_tokens not implemented." + end + + # Resets the scanner. + def reset_instance + @tokens.clear if @tokens.respond_to?(:clear) && !@options[:keep_tokens] + @cached_tokens = nil + @binary_string = nil if defined? @binary_string + end + + SCAN_ERROR_MESSAGE = <<-MESSAGE + + +***ERROR in %s: %s (after %s tokens) + +tokens: +%s + +%s + +surrounding code: +%p ~~ %p + + +***ERROR*** + + MESSAGE + + def raise_inspect_arguments message, tokens, state, ambit + return File.basename(caller[0]), + message, + tokens_size(tokens), + tokens_last(tokens, 10).map(&:inspect).join("\n"), + scanner_state_info(state), + binary_string[pos - ambit, ambit], + binary_string[pos, ambit] + end + + SCANNER_STATE_INFO = <<-INFO +current line: %d column: %d pos: %d +matched: %p state: %p +bol?: %p, eos?: %p + INFO + + def scanner_state_info state + SCANNER_STATE_INFO % [ + line, column, pos, + matched, state || 'No state given!', + bol?, eos?, + ] + end + + # Scanner error with additional status information + def raise_inspect message, tokens, state = self.state, ambit = 30, backtrace = caller + raise ScanError, SCAN_ERROR_MESSAGE % raise_inspect_arguments(message, tokens, state, ambit), backtrace + end + + def tokens_size tokens + tokens.size if tokens.respond_to?(:size) + end + + def tokens_last tokens, n + tokens.respond_to?(:last) ? tokens.last(n) : [] + end + + # Shorthand for scan_until(/\z/). + # This method also avoids a JRuby 1.9 mode bug. + def scan_rest + rest = self.rest + terminate + rest + end + + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sql.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sql.rb new file mode 100644 index 0000000000..c8725a8f83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/sql.rb @@ -0,0 +1,169 @@ +module CodeRay +module Scanners + + # by Josh Goebel + class SQL < Scanner + + register_for :sql + + KEYWORDS = %w( + all and any as before begin between by case check collate + each else end exists + for foreign from full group having if in inner is join + like not of on or order outer over references + then to union using values when where + left right distinct + ) + + OBJECTS = %w( + database databases table tables column columns fields index constraint + constraints transaction function procedure row key view trigger + ) + + COMMANDS = %w( + add alter comment create delete drop grant insert into select update set + show prompt begin commit rollback replace truncate + ) + + PREDEFINED_TYPES = %w( + char varchar varchar2 enum binary text tinytext mediumtext + longtext blob tinyblob mediumblob longblob timestamp + date time datetime year double decimal float int + integer tinyint mediumint bigint smallint unsigned bit numeric + bool boolean hex bin oct + ) + + PREDEFINED_FUNCTIONS = %w( sum cast substring abs pi count min max avg now ) + + DIRECTIVES = %w( + auto_increment unique default charset initially deferred + deferrable cascade immediate read write asc desc after + primary foreign return engine + ) + + PREDEFINED_CONSTANTS = %w( null true false ) + + IDENT_KIND = WordList::CaseIgnoring.new(:ident). + add(KEYWORDS, :keyword). + add(OBJECTS, :type). + add(COMMANDS, :class). + add(PREDEFINED_TYPES, :predefined_type). + add(PREDEFINED_CONSTANTS, :predefined_constant). + add(PREDEFINED_FUNCTIONS, :predefined). + add(DIRECTIVES, :directive) + + ESCAPE = / [rbfntv\n\\\/'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | . /mx + UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x + + STRING_PREFIXES = /[xnb]|_\w+/i + + STRING_CONTENT_PATTERN = { + '"' => / (?: [^\\"] | "" )+ /x, + "'" => / (?: [^\\'] | '' )+ /x, + '`' => / (?: [^\\`] | `` )+ /x, + } + + def scan_tokens encoder, options + + state = :initial + string_type = nil + string_content = '' + name_expected = false + + until eos? + + if state == :initial + + if match = scan(/ \s+ | \\\n /x) + encoder.text_token match, :space + + elsif match = scan(/(?:--\s?|#).*/) + encoder.text_token match, :comment + + elsif match = scan(%r( /\* (!)? (?: .*? \*/ | .* ) )mx) + encoder.text_token match, self[1] ? :directive : :comment + + elsif match = scan(/ [*\/=<>:;,!&^|()\[\]{}~%] | [-+\.](?!\d) /x) + name_expected = true if match == '.' && check(/[A-Za-z_]/) + encoder.text_token match, :operator + + elsif match = scan(/(#{STRING_PREFIXES})?([`"'])/o) + prefix = self[1] + string_type = self[2] + encoder.begin_group :string + encoder.text_token prefix, :modifier if prefix + match = string_type + state = :string + encoder.text_token match, :delimiter + + elsif match = scan(/ @? [A-Za-z_][A-Za-z_0-9\$]* /x) + encoder.text_token match, name_expected ? :ident : (match[0] == ?@ ? :variable : IDENT_KIND[match]) + name_expected = false + + elsif match = scan(/0[xX][0-9A-Fa-f]+/) + encoder.text_token match, :hex + + elsif match = scan(/0[0-7]+(?![89.eEfF])/) + encoder.text_token match, :octal + + elsif match = scan(/[-+]?(?>\d+)(?![.eEfF])/) + encoder.text_token match, :integer + + elsif match = scan(/[-+]?(?:\d[fF]|\d*\.\d+(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)/) + encoder.text_token match, :float + + elsif match = scan(/\\N/) + encoder.text_token match, :predefined_constant + + else + encoder.text_token getch, :error + + end + + elsif state == :string + if match = scan(STRING_CONTENT_PATTERN[string_type]) + encoder.text_token match, :content + elsif match = scan(/["'`]/) + if string_type == match + if peek(1) == string_type # doubling means escape + encoder.text_token match + getch, :content + else + encoder.text_token match, :delimiter + encoder.end_group :string + state = :initial + string_type = nil + end + else + encoder.text_token match, :content + end + elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) + encoder.text_token match, :char + elsif match = scan(/ \\ . /mox) + encoder.text_token match, :content + elsif match = scan(/ \\ | $ /x) + encoder.text_token match, :error unless match.empty? + encoder.end_group :string + state = :initial + else + raise "else case \" reached; %p not handled." % peek(1), encoder + end + + else + raise 'else-case reached', encoder + + end + + end + + if state == :string + encoder.end_group state + end + + encoder + + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/taskpaper.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/taskpaper.rb new file mode 100644 index 0000000000..42670bcce7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/taskpaper.rb @@ -0,0 +1,36 @@ +module CodeRay +module Scanners + + class Taskpaper < Scanner + + register_for :taskpaper + file_extension 'taskpaper' + + protected + + def scan_tokens encoder, options + until eos? + if match = scan(/\S.*:.*$/) # project + encoder.text_token(match, :namespace) + elsif match = scan(/-.+@done.*/) # completed task + encoder.text_token(match, :done) + elsif match = scan(/-(?:[^@\n]+|@(?!due))*/) # task + encoder.text_token(match, :plain) + elsif match = scan(/@due.*/) # comment + encoder.text_token(match, :important) + elsif match = scan(/.+/) # comment + encoder.text_token(match, :comment) + elsif match = scan(/\s+/) # space + encoder.text_token(match, :space) + else # other + encoder.text_token getch, :error + end + end + + encoder + end + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/text.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/text.rb new file mode 100644 index 0000000000..bde9029785 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/text.rb @@ -0,0 +1,26 @@ +module CodeRay + module Scanners + + # Scanner for plain text. + # + # Yields just one token of the kind :plain. + # + # Alias: +plaintext+, +plain+ + class Text < Scanner + + register_for :text + title 'Plain text' + + KINDS_NOT_LOC = [:plain] # :nodoc: + + protected + + def scan_tokens encoder, options + encoder.text_token string, :plain + encoder + end + + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/xml.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/xml.rb new file mode 100644 index 0000000000..947f16ee13 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/xml.rb @@ -0,0 +1,17 @@ +module CodeRay +module Scanners + + load :html + + # Scanner for XML. + # + # Currently this is the same scanner as Scanners::HTML. + class XML < HTML + + register_for :xml + file_extension 'xml' + + end + +end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/yaml.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/yaml.rb new file mode 100644 index 0000000000..32c8e2cb98 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/scanners/yaml.rb @@ -0,0 +1,140 @@ +module CodeRay +module Scanners + + # Scanner for YAML. + # + # Based on the YAML scanner from Syntax by Jamis Buck. + class YAML < Scanner + + register_for :yaml + file_extension 'yml' + + KINDS_NOT_LOC = :all + + protected + + def scan_tokens encoder, options + + state = :initial + key_indent = string_indent = 0 + + until eos? + + key_indent = nil if bol? + + if match = scan(/ +[\t ]*/) + encoder.text_token match, :space + + elsif match = scan(/\n+/) + encoder.text_token match, :space + state = :initial if match.index(?\n) + + elsif match = scan(/#.*/) + encoder.text_token match, :comment + + elsif bol? and case + when match = scan(/---|\.\.\./) + encoder.begin_group :head + encoder.text_token match, :head + encoder.end_group :head + next + when match = scan(/%.*/) + encoder.text_token match, :doctype + next + end + + elsif state == :value and case + when !check(/(?:"[^"]*")(?=: |:$)/) && match = scan(/"/) + encoder.begin_group :string + encoder.text_token match, :delimiter + encoder.text_token match, :content if (match = scan(/ [^"\\]* (?: \\. [^"\\]* )* /mx)) && !match.empty? + encoder.text_token match, :delimiter if match = scan(/"/) + encoder.end_group :string + next + when match = scan(/[|>][-+]?/) + encoder.begin_group :string + encoder.text_token match, :delimiter + string_indent = key_indent || column(pos - match.size) - 1 + encoder.text_token matched, :content if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) + encoder.end_group :string + next + when match = scan(/(?![!"*&]).+?(?=$|\s+#)/) + encoder.begin_group :string + encoder.text_token match, :content + string_indent = key_indent || column(pos - match.size) - 1 + encoder.text_token matched, :content if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) + encoder.end_group :string + next + end + + elsif case + when match = scan(/[-:](?= |$)/) + state = :value if state == :colon && (match == ':' || match == '-') + state = :value if state == :initial && match == '-' + encoder.text_token match, :operator + next + when match = scan(/[,{}\[\]]/) + encoder.text_token match, :operator + next + when state == :initial && match = scan(/[-\w.()\/ ]*\S(?= *:(?: |$))/) + encoder.text_token match, :key + key_indent = column(pos - match.size) - 1 + state = :colon + next + when match = scan(/(?:"[^"\n]*"|'[^'\n]*')(?= *:(?: |$))/) + encoder.begin_group :key + encoder.text_token match[0,1], :delimiter + encoder.text_token match[1..-2], :content if match.size > 2 + encoder.text_token match[-1,1], :delimiter + encoder.end_group :key + key_indent = column(pos - match.size) - 1 + state = :colon + next + when match = scan(/(![\w\/]+)(:([\w:]+))?/) + encoder.text_token self[1], :type + if self[2] + encoder.text_token ':', :operator + encoder.text_token self[3], :class + end + next + when match = scan(/&\S+/) + encoder.text_token match, :variable + next + when match = scan(/\*\w+/) + encoder.text_token match, :global_variable + next + when match = scan(/< 'debug', # highlight for debugging (white on blue background) + + :annotation => 'annotation', # Groovy, Java + :attribute_name => 'attribute-name', # HTML, CSS + :attribute_value => 'attribute-value', # HTML + :binary => 'binary', # Python, Ruby + :char => 'char', # most scanners, also inside of strings + :class => 'class', # lots of scanners, for different purposes also in CSS + :class_variable => 'class-variable', # Ruby, YAML + :color => 'color', # CSS + :comment => 'comment', # most scanners + :constant => 'constant', # PHP, Ruby + :content => 'content', # inside of strings, most scanners + :decorator => 'decorator', # Python + :definition => 'definition', # CSS + :delimiter => 'delimiter', # inside strings, comments and other types + :directive => 'directive', # lots of scanners + :doctype => 'doctype', # Goorvy, HTML, Ruby, YAML + :docstring => 'docstring', # Python + :done => 'done', # Taskpaper + :entity => 'entity', # HTML + :error => 'error', # invalid token, most scanners + :escape => 'escape', # Ruby (string inline variables like #$foo, #@bar) + :exception => 'exception', # Java, PHP, Python + :filename => 'filename', # Diff + :float => 'float', # most scanners + :function => 'function', # CSS, JavaScript, PHP + :global_variable => 'global-variable', # Ruby, YAML + :hex => 'hex', # hexadecimal number; lots of scanners + :id => 'id', # CSS + :imaginary => 'imaginary', # Python + :important => 'important', # CSS, Taskpaper + :include => 'include', # C, Groovy, Java, Python, Sass + :inline => 'inline', # nested code, eg. inline string evaluation; lots of scanners + :inline_delimiter => 'inline-delimiter', # used instead of :inline > :delimiter FIXME: Why use inline_delimiter? + :instance_variable => 'instance-variable', # Ruby + :integer => 'integer', # most scanners + :key => 'key', # lots of scanners, used together with :value + :keyword => 'keyword', # reserved word that's actually implemented; most scanners + :label => 'label', # C, PHP + :local_variable => 'local-variable', # local and magic variables; some scanners + :map => 'map', # Lua tables + :modifier => 'modifier', # used inside on strings; lots of scanners + :namespace => 'namespace', # Clojure, Java, Taskpaper + :octal => 'octal', # lots of scanners + :predefined => 'predefined', # predefined function: lots of scanners + :predefined_constant => 'predefined-constant',# lots of scanners + :predefined_type => 'predefined-type', # C, Java, PHP + :preprocessor => 'preprocessor', # C, Delphi, HTML + :pseudo_class => 'pseudo-class', # CSS + :regexp => 'regexp', # Groovy, JavaScript, Ruby + :reserved => 'reserved', # most scanners + :shell => 'shell', # Ruby + :string => 'string', # most scanners + :symbol => 'symbol', # Clojure, Ruby, YAML + :tag => 'tag', # CSS, HTML + :type => 'type', # CSS, Java, SQL, YAML + :value => 'value', # used together with :key; CSS, JSON, YAML + :variable => 'variable', # Sass, SQL, YAML + + :change => 'change', # Diff + :delete => 'delete', # Diff + :head => 'head', # Diff, YAML + :insert => 'insert', # Diff + :eyecatcher => 'eyecatcher', # Diff + + :ident => false, # almost all scanners + :operator => false, # almost all scanners + + :space => false, # almost all scanners + :plain => false # almost all scanners + ) + + TokenKinds[:method] = TokenKinds[:function] + TokenKinds[:unknown] = TokenKinds[:plain] +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens.rb new file mode 100644 index 0000000000..b5f78e711a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens.rb @@ -0,0 +1,164 @@ +module CodeRay + + # The Tokens class represents a list of tokens returned from + # a Scanner. It's actually just an Array with a few helper methods. + # + # A token itself is not a special object, just two elements in an Array: + # * the _token_ _text_ (the original source of the token in a String) or + # a _token_ _action_ (begin_group, end_group, begin_line, end_line) + # * the _token_ _kind_ (a Symbol representing the type of the token) + # + # It looks like this: + # + # ..., '# It looks like this', :comment, ... + # ..., '3.1415926', :float, ... + # ..., '$^', :error, ... + # + # Some scanners also yield sub-tokens, represented by special + # token actions, for example :begin_group and :end_group. + # + # The Ruby scanner, for example, splits "a string" into: + # + # [ + # :begin_group, :string, + # '"', :delimiter, + # 'a string', :content, + # '"', :delimiter, + # :end_group, :string + # ] + # + # Tokens can be used to save the output of a Scanners in a simple + # Ruby object that can be send to an Encoder later: + # + # tokens = CodeRay.scan('price = 2.59', :ruby).tokens + # tokens.encode(:html) + # tokens.html + # CodeRay.encoder(:html).encode_tokens(tokens) + # + # Tokens gives you the power to handle pre-scanned code very easily: + # You can serialize it to a JSON string and store it in a database, pass it + # around to encode it more than once, send it to other algorithms... + class Tokens < Array + # Remove Array#filter that is a new alias for Array#select on Ruby 2.6, + # for method_missing called with filter method. + undef_method :filter if instance_methods.include?(:filter) + + # The Scanner instance that created the tokens. + attr_accessor :scanner + + # Encode the tokens using encoder. + # + # encoder can be + # * a plugin name like :html oder 'statistic' + # * an Encoder object + # + # options are passed to the encoder. + def encode encoder, options = {} + encoder = Encoders[encoder].new options if encoder.respond_to? :to_sym + encoder.encode_tokens self, options + end + + # Turn tokens into a string by concatenating them. + def to_s + encode CodeRay::Encoders::Encoder.new + end + + # Redirects unknown methods to encoder calls. + # + # For example, if you call +tokens.html+, the HTML encoder + # is used to highlight the tokens. + def method_missing meth, options = {} + encode meth, options + rescue PluginHost::PluginNotFound + super + end + + # Split the tokens into parts of the given +sizes+. + # + # The result will be an Array of Tokens objects. The parts have + # the text size specified by the parameter. In addition, each + # part closes all opened tokens. This is useful to insert tokens + # betweem them. + # + # This method is used by @Scanner#tokenize@ when called with an Array + # of source strings. The Diff encoder uses it for inline highlighting. + def split_into_parts *sizes + return Array.new(sizes.size) { Tokens.new } if size == 2 && first == '' + parts = [] + opened = [] + content = nil + part = Tokens.new + part_size = 0 + size = sizes.first + i = 0 + for item in self + case content + when nil + content = item + when String + if size && part_size + content.size > size # token must be cut + if part_size < size # some part of the token goes into this part + content = content.dup # content may no be safe to change + part << content.slice!(0, size - part_size) << item + end + # close all open groups and lines... + closing = opened.reverse.flatten.map do |content_or_kind| + case content_or_kind + when :begin_group + :end_group + when :begin_line + :end_line + else + content_or_kind + end + end + part.concat closing + begin + parts << part + part = Tokens.new + size = sizes[i += 1] + end until size.nil? || size > 0 + # ...and open them again. + part.concat opened.flatten + part_size = 0 + redo unless content.empty? + else + part << content << item + part_size += content.size + end + content = nil + when Symbol + case content + when :begin_group, :begin_line + opened << [content, item] + when :end_group, :end_line + opened.pop + else + raise ArgumentError, 'Unknown token action: %p, kind = %p' % [content, item] + end + part << content << item + content = nil + else + raise ArgumentError, 'Token input junk: %p, kind = %p' % [content, item] + end + end + parts << part + parts << Tokens.new while parts.size < sizes.size + parts + end + + # Return the actual number of tokens. + def count + size / 2 + end + + alias text_token push + def begin_group kind; push :begin_group, kind end + def end_group kind; push :end_group, kind end + def begin_line kind; push :begin_line, kind end + def end_line kind; push :end_line, kind end + alias tokens concat + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens_proxy.rb new file mode 100644 index 0000000000..31ff39be91 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/tokens_proxy.rb @@ -0,0 +1,55 @@ +module CodeRay + + # The result of a scan operation is a TokensProxy, but should act like Tokens. + # + # This proxy makes it possible to use the classic CodeRay.scan.encode API + # while still providing the benefits of direct streaming. + class TokensProxy + + attr_accessor :input, :lang, :options, :block + + # Create a new TokensProxy with the arguments of CodeRay.scan. + def initialize input, lang, options = {}, block = nil + @input = input + @lang = lang + @options = options + @block = block + end + + # Call CodeRay.encode if +encoder+ is a Symbol; + # otherwise, convert the receiver to tokens and call encoder.encode_tokens. + def encode encoder, options = {} + if encoder.respond_to? :to_sym + CodeRay.encode(input, lang, encoder, options) + else + encoder.encode_tokens tokens, options + end + end + + # Tries to call encode; + # delegates to tokens otherwise. + def method_missing method, *args, &blk + encode method.to_sym, *args + rescue PluginHost::PluginNotFound + tokens.send(method, *args, &blk) + end + + # The (cached) result of the tokenized input; a Tokens instance. + def tokens + @tokens ||= scanner.tokenize(input) + end + + # A (cached) scanner instance to use for the scan task. + def scanner + @scanner ||= CodeRay.scanner(lang, options, &block) + end + + # Overwrite Struct#each. + def each *args, &blk + tokens.each(*args, &blk) + self + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/version.rb b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/version.rb new file mode 100644 index 0000000000..3c68bd83c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/coderay-1.1.3/lib/coderay/version.rb @@ -0,0 +1,3 @@ +module CodeRay + VERSION = '1.1.3' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/CHANGELOG.md new file mode 100644 index 0000000000..da1e00d44f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/CHANGELOG.md @@ -0,0 +1,528 @@ +## Current + +## 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 longers 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 extenstions +* 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.0.0/gems/concurrent-ruby-1.1.9/Gemfile b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/Gemfile new file mode 100644 index 0000000000..1b8d9fd9b1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/Gemfile @@ -0,0 +1,42 @@ +source 'https://rubygems.org' + +require File.join(File.dirname(__FILE__), 'lib/concurrent-ruby/concurrent/version') +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby-edge/concurrent/edge/version') +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby/concurrent/utility/engine') + +no_path = ENV['NO_PATH'] +options = no_path ? {} : { path: '.' } + +gem 'concurrent-ruby', Concurrent::VERSION, options +gem 'concurrent-ruby-edge', Concurrent::EDGE_VERSION, options +gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri) + +group :development do + gem 'rake', (Concurrent.ruby_version :<, 2, 2, 0) ? '~> 12.0' : '~> 13.0' + gem 'rake-compiler', '~> 1.0', '>= 1.0.7' + 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.7.4' + 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 + +group :benchmarks, optional: true do + gem 'benchmark-ips', '~> 2.7' + gem 'bench9000' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/LICENSE.txt new file mode 100644 index 0000000000..1026f28d0b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/README.md b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/README.md new file mode 100644 index 0000000000..cdf3480246 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/README.md @@ -0,0 +1,408 @@ +# Concurrent Ruby + +[![Gem Version](https://badge.fury.io/rb/concurrent-ruby.svg)](http://badge.fury.io/rb/concurrent-ruby) +[![Build Status](https://travis-ci.org/ruby-concurrency/concurrent-ruby.svg?branch=master)](https://travis-ci.org/ruby-concurrency/concurrent-ruby) +[![Build status](https://ci.appveyor.com/api/projects/status/iq8aboyuu3etad4w?svg=true)](https://ci.appveyor.com/project/rubyconcurrency/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. + +## Thread Safety + +*Concurrent Ruby makes one of the strongest thread safety guarantees of any Ruby concurrency +library, providing consistent behavior and guarantees on all four of the main Ruby interpreters +(MRI/CRuby, JRuby, Rubinius, 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 [memory model](docs-source/synchronization.md) of Ruby which +would provide consistent behaviour and guarantees on all four of the main Ruby interpreters +(MRI/CRuby, JRuby, Rubinius, 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.0 and above +* JRuby 9000 +* TruffleRuby are supported. +* Any Ruby interpreter that is compliant with Ruby 2.0 or newer. + +Actually we still support mri 1.9.3 and jruby 1.7.27 but we are looking at ways how to drop the support. +Java 8 is preferred for JRuby but every Java version on which JRuby 9000 runs is supported. + +The legacy support for Rubinius is kept but it is no longer maintained, if you would like to help +please respond to [#739](https://github.com/ruby-concurrency/concurrent-ruby/issues/739). + +## Usage + +Everything within this gem can be loaded simply by requiring it: + +```ruby +require 'concurrent' +``` + +*Requiring only specific abstractions from Concurrent Ruby is not yet supported.* + +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_stdlib_logger(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 +* Update the Yard documentation + - Add the new version to `docs-source/signpost.md`. Needs to be done only if there are visible changes in the + documentation. + - Run `bundle exec rake yard` to update the master documentation and signpost. + - Run `bundle exec rake yard:` to add or update the documentation of the new version. +* Commit (and push) the changes. +* Use `be 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 + +* [Petr Chalupa](https://github.com/pitr-ch) — Lead maintainer, point-of-contact. +* [Chris Seaton](https://github.com/chrisseaton) — + If Petr is not available Chris can help or poke Petr to pay attention where it is needed. + +### 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 + +* [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.0.0/gems/concurrent-ruby-1.1.9/Rakefile b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/Rakefile new file mode 100644 index 0000000000..4fd002bd67 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/Rakefile @@ -0,0 +1,339 @@ +require_relative 'lib/concurrent-ruby/concurrent/version' +require_relative 'lib/concurrent-ruby-edge/concurrent/edge/version' +require_relative 'lib/concurrent-ruby/concurrent/utility/engine' + +if Concurrent.ruby_version :<, 2, 0, 0 + # @!visibility private + module Kernel + def __dir__ + File.dirname __FILE__ + end + end +end + +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'] && !Concurrent.on_jruby? + +Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' +end + +unless Concurrent.on_jruby? + 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 + +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 + sh 'bundle package' + + # 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 + %w[x86-mingw32 x64-mingw32].each do |plat| + RakeCompilerDock.sh "bundle install --local && bundle exec rake native:#{plat} gem --trace", platform: plat + 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 && !Concurrent.on_jruby? +Gem::PackageTask.new(edge_gemspec) {} if edge_gemspec + +CLEAN.include('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 + --tag ~notravis ] + t.rspec_opts = [*options].join(' ') + end + + desc '* test packaged and installed gems instead of local files' + task :installed do + Dir.chdir(__dir__) do + sh "gem install pkg/concurrent-ruby-#{Concurrent::VERSION}.gem" + sh "gem install pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if Concurrent.on_cruby? + sh "gem install pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" + ENV['NO_PATH'] = 'true' + sh 'bundle update' + sh 'bundle exec rake spec:ci' + end + end + end + + desc 'executed in CI' + task :ci => [:compile, 'spec:ci'] + + task :default => [:clobber, :compile, :spec] +rescue LoadError => e + puts 'RSpec is not installed, skipping test task definitions: ' + e.message +end + +current_yard_version_name = Concurrent::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 + + define_uptodate_task = -> name do + namespace name do + desc "** ensure that #{name} generated documentation is matching the source code" + task :uptodate do + Dir.chdir(__dir__) do + begin + FileUtils.cp_r 'docs', 'docs-copy', verbose: true + Rake::Task["yard:#{name}"].invoke + sh 'diff -r docs/ docs-copy/' do |ok, res| + unless ok + begin + STDOUT.puts "yard:#{name} is not properly generated and committed.", "Continue? (y/n)" + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + end + end + ensure + FileUtils.rm_rf 'docs-copy', verbose: true + end + end + end + end + end + + define_uptodate_task.call current_yard_version_name + define_uptodate_task.call 'master' + 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 => "yard:#{current_yard_version_name}:uptodate" do + 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 + Dir.chdir(__dir__) do + old = ENV['RBENV_VERSION'] + + mri_version = `ruby -e 'puts RUBY_VERSION'`.chomp + jruby_version = File.basename(ENV['CONCURRENT_JRUBY_HOME']) + + puts "Using following version:" + pp mri_version: mri_version, jruby_version: jruby_version + + ENV['RBENV_VERSION'] = mri_version + sh 'rbenv version' + sh 'bundle exec rake spec:installed' + + ENV['RBENV_VERSION'] = jruby_version + sh 'rbenv version' + sh 'bundle exec rake spec:installed' + + puts 'Windows build is untested' + + ENV['RBENV_VERSION'] = old + end + end + + desc '* do all nested steps' + task :publish => ['publish:ask', 'publish:tag', 'publish:rubygems', 'publish:post_steps'] + + namespace :publish do + publish_edge = false + + 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 'It will publish `concurrent-ruby`. 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#{Concurrent::VERSION}" + sh "git push origin v#{Concurrent::VERSION}" + sh "git tag edge-v#{Concurrent::EDGE_VERSION}" if publish_edge + sh "git push origin edge-v#{Concurrent::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-#{Concurrent::VERSION}.gem" + sh "gem push pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" if publish_edge + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x64-mingw32.gem" + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x86-mingw32.gem" + 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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/ConcurrentRubyService.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/ConcurrentRubyService.java new file mode 100644 index 0000000000..fb6be96d37 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java new file mode 100644 index 0000000000..dfa9e7704e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java new file mode 100644 index 0000000000..a09f9162ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java new file mode 100644 index 0000000000..b56607626c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java new file mode 100755 index 0000000000..672bfc048b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java new file mode 100755 index 0000000000..a3e847db26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java @@ -0,0 +1,159 @@ +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.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, IRubyObject value) throws InterruptedException { + this.semaphore.acquire(rubyFixnumToPositiveInt(value, "permits")); + return context.nil; + } + + @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 + public IRubyObject acquire(ThreadContext context) throws InterruptedException { + this.semaphore.acquire(1); + return context.nil; + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context) throws InterruptedException { + return getRuntime().newBoolean(semaphore.tryAcquire(1)); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits) throws InterruptedException { + return getRuntime().newBoolean(semaphore.tryAcquire(rubyFixnumToPositiveInt(permits, "permits"))); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout) throws InterruptedException { + return getRuntime().newBoolean( + semaphore.tryAcquire( + rubyFixnumToPositiveInt(permits, "permits"), + rubyNumericToLong(timeout, "timeout"), + java.util.concurrent.TimeUnit.SECONDS) + ); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context) { + this.semaphore.release(1); + return getRuntime().newBoolean(true); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context, IRubyObject value) { + this.semaphore.release(rubyFixnumToPositiveInt(value, "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 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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java new file mode 100644 index 0000000000..bfcc0d0781 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java @@ -0,0 +1,307 @@ +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 JRUBY_OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyObject(runtime, klazz); + } + }; + + 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", "JRubyObject", + JRubyObject.class, JRUBY_OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "JRubyObject", "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) + public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) { + // 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) + public static IRubyObject instanceVariableGetVolatile( + ThreadContext context, + 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) + public static IRubyObject InstanceVariableSetVolatile( + ThreadContext context, + 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 = "JRubyObject", parent = "AbstractObject") + public static class JRubyObject extends RubyObject { + + public JRubyObject(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + } + + @JRubyClass(name = "Object", parent = "JRubyObject") + public static class Object extends JRubyObject { + + 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 JRubyObject { + + 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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java new file mode 100644 index 0000000000..e11e15aa4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java new file mode 100644 index 0000000000..86aa4eb062 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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 refering 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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java new file mode 100644 index 0000000000..47a923c8d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java new file mode 100644 index 0000000000..93a277fb35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java new file mode 100644 index 0000000000..b7fc5a9375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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 refering 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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java new file mode 100644 index 0000000000..ecf552a23c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java new file mode 100644 index 0000000000..f52164242a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java new file mode 100644 index 0000000000..3ea409ffc4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent-ruby.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent-ruby.rb new file mode 100644 index 0000000000..08917e3bb7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent-ruby.rb @@ -0,0 +1 @@ +require_relative "./concurrent" diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent.rb new file mode 100644 index 0000000000..87de46f1b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/agent.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/agent.rb new file mode 100644 index 0000000000..815dca008c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/agent.rb @@ -0,0 +1,587 @@ +require 'concurrent/configuration' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/thread_local_var' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/observable' +require 'concurrent/synchronization' + +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 timout 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 timout 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/array.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/array.rb new file mode 100644 index 0000000000..60e5b5689d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/array.rb @@ -0,0 +1,66 @@ +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 thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::Array + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyArray < ::Array + include JRuby::Synchronized + end + JRubyArray + + when Concurrent.on_rbx? + require 'monitor' + require 'concurrent/thread_safe/util/data_structures' + + class RbxArray < ::Array + end + + ThreadSafe::Util.make_synchronized_on_rbx RbxArray + RbxArray + + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/async.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/async.rb new file mode 100644 index 0000000000..5e125e4a09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/async.rb @@ -0,0 +1,448 @@ +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) + # + # Instanciate 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 + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atom.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atom.rb new file mode 100644 index 0000000000..8a45730082 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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' + +# @!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 (preferrably) 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb new file mode 100644 index 0000000000..fcdeed7f85 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb @@ -0,0 +1,66 @@ +require 'concurrent/constants' + +module Concurrent + + # @!macro thread_local_var + # @!macro internal_implementation_note + # @!visibility private + class AbstractThreadLocalVar + + # @!macro thread_local_var_method_initialize + 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 + + allocate_storage + end + + # @!macro thread_local_var_method_get + def value + raise NotImplementedError + end + + # @!macro thread_local_var_method_set + def value=(value) + raise NotImplementedError + end + + # @!macro thread_local_var_method_bind + def bind(value, &block) + if block_given? + old_value = self.value + begin + self.value = value + yield + ensure + self.value = old_value + end + end + end + + protected + + # @!visibility private + def allocate_storage + raise NotImplementedError + end + + # @!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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb new file mode 100644 index 0000000000..0b0373dc29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb @@ -0,0 +1,126 @@ +require 'concurrent/atomic/mutex_atomic_boolean' +require 'concurrent/synchronization' + +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 defined?(JavaAtomicBoolean) + JavaAtomicBoolean + when defined?(CAtomicBoolean) + CAtomicBoolean + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb new file mode 100644 index 0000000000..c67166d833 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb @@ -0,0 +1,143 @@ +require 'concurrent/atomic/mutex_atomic_fixnum' +require 'concurrent/synchronization' + +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 defined?(JavaAtomicFixnum) + JavaAtomicFixnum + when defined?(CAtomicFixnum) + CAtomicFixnum + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb new file mode 100644 index 0000000000..f20cd46a52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb @@ -0,0 +1,164 @@ +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb new file mode 100644 index 0000000000..620c0698e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb @@ -0,0 +1,204 @@ +require 'concurrent/synchronization' +require 'concurrent/utility/engine' +require 'concurrent/atomic_reference/numeric_cas_wrapper' + +# Shim for TruffleRuby::AtomicReference +if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference) + # @!visibility private + module TruffleRuby + AtomicReference = Truffle::AtomicReference + end +end + +module Concurrent + + # Define update methods that use direct paths + # + # @!visibility private + # @!macro internal_implementation_note + module AtomicDirectUpdate + + # @!macro atomic_reference_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 + def update + true until compare_and_set(old_value = get, new_value = yield(old_value)) + new_value + end + + # @!macro atomic_reference_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 + def try_update + old_value = get + new_value = yield old_value + + return unless compare_and_set old_value, new_value + + new_value + end + + # @!macro atomic_reference_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 + 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 + + require 'concurrent/atomic_reference/mutex_atomic' + + # @!macro atomic_reference + # + # 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 + # @!macro atomic_reference_method_update + # + # @!method try_update + # @!macro atomic_reference_method_try_update + # + # @!method try_update! + # @!macro atomic_reference_method_try_update! + + + # @!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 + + # @!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 + when Concurrent.on_rbx? + # @note Extends `Rubinius::AtomicReference` version adding aliases + # and numeric logic. + # + # @!visibility private + # @!macro internal_implementation_note + class RbxAtomicReference < Rubinius::AtomicReference + alias_method :_compare_and_set, :compare_and_set + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :value, :get + alias_method :value=, :set + alias_method :swap, :get_and_set + alias_method :compare_and_swap, :compare_and_set + end + RbxAtomicReference + else + MutexAtomicReference + end + private_constant :AtomicReferenceImplementation + + # @!macro atomic_reference + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb new file mode 100644 index 0000000000..d883aed6f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb new file mode 100644 index 0000000000..42f5a94967 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb @@ -0,0 +1,128 @@ +require 'concurrent/synchronization' +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/event.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/event.rb new file mode 100644 index 0000000000..825f38a031 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/event.rb @@ -0,0 +1,109 @@ +require 'thread' +require 'concurrent/synchronization' + +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 ocurred" + # end + # + # t2 = Thread.new do + # puts "t2 calling set" + # event.set + # end + # + # [t1, t2].each(&:join) + # + # # prints: + # # t2 calling set + # # t1 is waiting + # # 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb new file mode 100644 index 0000000000..cb5b35a567 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb @@ -0,0 +1,42 @@ +if Concurrent.on_jruby? + + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb new file mode 100644 index 0000000000..b41018ffed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb @@ -0,0 +1,37 @@ +require 'concurrent/atomic/abstract_thread_local_var' + +if Concurrent.on_jruby? + + module Concurrent + + # @!visibility private + # @!macro internal_implementation_note + class JavaThreadLocalVar < AbstractThreadLocalVar + + # @!macro thread_local_var_method_get + def value + value = @var.get + + if value.nil? + default + elsif value == NULL + nil + else + value + end + end + + # @!macro thread_local_var_method_set + def value=(value) + @var.set(value) + end + + protected + + # @!visibility private + def allocate_storage + @var = java.lang.ThreadLocal.new + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb new file mode 100644 index 0000000000..a033de4cad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb @@ -0,0 +1,62 @@ +require 'concurrent/synchronization' + +module Concurrent + + # @!macro atomic_boolean + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicBoolean < Synchronization::LockableObject + + # @!macro atomic_boolean_method_initialize + def initialize(initial = false) + super() + synchronize { ns_initialize(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 ns_initialize(initial) + @value = !!initial + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb new file mode 100644 index 0000000000..77b91d2dbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb @@ -0,0 +1,75 @@ +require 'concurrent/synchronization' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro atomic_fixnum + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicFixnum < Synchronization::LockableObject + + # @!macro atomic_fixnum_method_initialize + def initialize(initial = 0) + super() + synchronize { ns_initialize(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 ns_initialize(initial) + ns_set(initial) + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb new file mode 100644 index 0000000000..e99744cef6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb @@ -0,0 +1,44 @@ +require 'concurrent/synchronization' +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb new file mode 100644 index 0000000000..2042f73056 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb @@ -0,0 +1,115 @@ +require 'concurrent/synchronization' +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) + nil + 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 + + synchronize do + if timeout.nil? + try_acquire_now(permits) + else + try_acquire_timed(permits, timeout) + end + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb new file mode 100644 index 0000000000..246f21aac3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb @@ -0,0 +1,254 @@ +require 'thread' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/errors' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb new file mode 100644 index 0000000000..42d7f3c3ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb @@ -0,0 +1,379 @@ +require 'thread' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/errors' +require 'concurrent/synchronization' +require 'concurrent/atomic/thread_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 = ThreadLocalVar.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 c == held + # If we successfully swap the RUNNING_WRITER bit on, then we can go ahead + if @Counter.compare_and_set(c, c+RUNNING_WRITER) + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb new file mode 100644 index 0000000000..9a51eb288b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb @@ -0,0 +1,181 @@ +require 'thread' +require 'concurrent/atomic/abstract_thread_local_var' + +module Concurrent + + # @!visibility private + # @!macro internal_implementation_note + class RubyThreadLocalVar < AbstractThreadLocalVar + + # Each thread has a (lazily initialized) array of thread-local variable values + # Each time a new thread-local var is created, we allocate an "index" for it + # For example, if the allocated index is 1, that means slot #1 in EVERY + # thread's thread-local array will be used for the value of that TLV + # + # The good thing about using a per-THREAD structure to hold values, rather + # than a per-TLV structure, 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 TLV is GC'd, 1) we need to recover its index for use + # by other new TLVs (otherwise the thread-local 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 TLV + # when the index is reused) + # Because we need to null out freed slots, we need to keep references to + # ALL the thread-local arrays -- ARRAYS is for that + # But when a Thread is GC'd, we need to drop the reference to its thread-local + # array, so we don't leak memory + + FREE = [] + LOCK = Mutex.new + THREAD_LOCAL_ARRAYS = {} # used as a hash set + + # synchronize when not on MRI + # on MRI using lock in finalizer leads to "can't be called from trap context" error + # so the code is carefully written to be tread-safe on MRI relying on GIL + + if Concurrent.on_cruby? + # @!visibility private + def self.semi_sync(&block) + block.call + end + else + # @!visibility private + def self.semi_sync(&block) + LOCK.synchronize(&block) + end + end + + private_constant :FREE, :LOCK, :THREAD_LOCAL_ARRAYS + + # @!macro thread_local_var_method_get + def value + if (array = get_threadlocal_array) + value = array[@index] + if value.nil? + default + elsif value.equal?(NULL) + nil + else + value + end + else + default + end + end + + # @!macro thread_local_var_method_set + def value=(value) + me = Thread.current + # We could keep the thread-local arrays in a hash, keyed by Thread + # But why? That would require locking + # Using Ruby's built-in thread-local storage is faster + unless (array = get_threadlocal_array(me)) + array = set_threadlocal_array([], me) + self.class.semi_sync { THREAD_LOCAL_ARRAYS[array.object_id] = array } + ObjectSpace.define_finalizer(me, self.class.thread_finalizer(array.object_id)) + end + array[@index] = (value.nil? ? NULL : value) + value + end + + protected + + # @!visibility private + def allocate_storage + @index = FREE.pop || next_index + + ObjectSpace.define_finalizer(self, self.class.thread_local_finalizer(@index)) + end + + # @!visibility private + def self.thread_local_finalizer(index) + proc do + semi_sync do + # The cost of GC'ing a TLV is linear in the number of threads using TLVs + # But that is natural! More threads means more storage is used per TLV + # 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 #value= method + THREAD_LOCAL_ARRAYS.values.each { |array| array[index] = nil } + # free index has to be published after the arrays are cleared + FREE.push(index) + end + end + end + + # @!visibility private + def self.thread_finalizer(id) + proc do + semi_sync do + # The thread which used this thread-local array is now gone + # So don't hold onto a reference to the array (thus blocking GC) + THREAD_LOCAL_ARRAYS.delete(id) + end + end + end + + private + + # noinspection RubyClassVariableUsageInspection + @@next = 0 + # noinspection RubyClassVariableUsageInspection + def next_index + LOCK.synchronize do + result = @@next + @@next += 1 + result + end + end + + if Thread.instance_methods.include?(:thread_variable_get) + + def get_threadlocal_array(thread = Thread.current) + thread.thread_variable_get(:__threadlocal_array__) + end + + def set_threadlocal_array(array, thread = Thread.current) + thread.thread_variable_set(:__threadlocal_array__, array) + end + + else + + def get_threadlocal_array(thread = Thread.current) + thread[:__threadlocal_array__] + end + + def set_threadlocal_array(array, thread = Thread.current) + thread[:__threadlocal_array__] = array + end + end + + # This exists only for use in testing + # @!visibility private + def value_for(thread) + if (array = get_threadlocal_array(thread)) + value = array[@index] + if value.nil? + get_default + elsif value.equal?(NULL) + nil + else + value + end + else + get_default + end + end + + # @!visibility private + def get_default + if @default_block + raise "Cannot use default_for with default block" + else + @default + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/semaphore.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/semaphore.rb new file mode 100644 index 0000000000..1b2bd8c95d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/semaphore.rb @@ -0,0 +1,145 @@ +require 'concurrent/atomic/mutex_semaphore' +require 'concurrent/synchronization' + +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 or is less than zero + + # @!macro semaphore_method_acquire + # + # Acquires the given number of permits from this semaphore, + # blocking until all are available. + # + # @param [Fixnum] permits Number of permits to acquire + # + # @raise [ArgumentError] if `permits` is not an integer or is less than + # one + # + # @return [nil] + + # @!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 + # + # @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 + # one + # + # @return [Boolean] `false` if no permits are available, `true` when + # acquired a permit + + # @!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 one + # + # @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 = case + when defined?(JavaSemaphore) + 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. + # + # @!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 + # + class Semaphore < SemaphoreImplementation + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb new file mode 100644 index 0000000000..100cc8de8f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb @@ -0,0 +1,104 @@ +require 'concurrent/utility/engine' +require 'concurrent/atomic/ruby_thread_local_var' +require 'concurrent/atomic/java_thread_local_var' + +module Concurrent + + ################################################################### + + # @!macro thread_local_var_method_initialize + # + # 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 + + # @!macro thread_local_var_method_get + # + # Returns the value in the current thread's copy of this thread-local variable. + # + # @return [Object] the current value + + # @!macro thread_local_var_method_set + # + # 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 + + # @!macro thread_local_var_method_bind + # + # 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 + + + ################################################################### + + # @!macro thread_local_var_public_api + # + # @!method initialize(default = nil, &default_block) + # @!macro thread_local_var_method_initialize + # + # @!method value + # @!macro thread_local_var_method_get + # + # @!method value=(value) + # @!macro thread_local_var_method_set + # + # @!method bind(value, &block) + # @!macro thread_local_var_method_bind + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + ThreadLocalVarImplementation = case + when Concurrent.on_jruby? + JavaThreadLocalVar + else + RubyThreadLocalVar + end + private_constant :ThreadLocalVarImplementation + + # @!macro thread_local_var + # + # 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. + # + # @!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 + # + # @see https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html Java ThreadLocal + # + # @!macro thread_local_var_public_api + class ThreadLocalVar < ThreadLocalVarImplementation + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb new file mode 100644 index 0000000000..d092aedd5b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb @@ -0,0 +1,56 @@ +module Concurrent + + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicReference < Synchronization::LockableObject + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :compare_and_swap, :compare_and_set + + # @!macro atomic_reference_method_initialize + def initialize(value = nil) + super() + synchronize { ns_initialize(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 + + def ns_initialize(value) + @value = value + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb new file mode 100644 index 0000000000..709a382231 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomics.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/atomics.rb new file mode 100644 index 0000000000..16cbe66101 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb new file mode 100644 index 0000000000..50d52a6237 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb @@ -0,0 +1,107 @@ +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb new file mode 100644 index 0000000000..3f3f7cccd0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb @@ -0,0 +1,111 @@ +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..2be9e4373a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb new file mode 100644 index 0000000000..d003d3cf73 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb @@ -0,0 +1,158 @@ +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb new file mode 100644 index 0000000000..dc5189389d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb @@ -0,0 +1,927 @@ +require 'concurrent/constants' +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/adder' +require 'concurrent/thread_safe/util/cheap_lockable' +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 Collection + + # A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59 + # + # The Ruby port skips out the +TreeBin+ (red-black trees for use in bins whose + # size exceeds a threshold). + # + # A hash table supporting full concurrency of retrievals and high expected + # concurrency for updates. 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. + # + # Retrieval operations generally do not block, so may overlap with update + # operations. 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 + # +nil+) retrieval for that key reporting the updated value.) For aggregate + # operations such as +clear()+, concurrent retrievals may reflect insertion or + # removal of only some entries. Similarly, the +each_pair+ iterator yields + # elements reflecting the state of the hash table at some point at or since + # the start of the +each_pair+. Bear in mind that the results of aggregate + # status methods including +size()+ and +empty?+} 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 :initial_capacity + # initializer argument. An additional optional :load_factor 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. Note that using many keys with + # exactly the same +hash+ is a sure way to slow down performance of any hash + # table. + # + # ## Design overview + # + # The primary design goal of this hash table is to maintain concurrent + # readability (typically method +[]+, but also iteration and related methods) + # while minimizing update contention. Secondary goals are to keep space + # consumption about the same or better than plain +Hash+, and to support high + # initial insertion rates on an empty table by many threads. + # + # Each key-value mapping is held in a +Node+. 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 +Node+s (most + # often, the list has only zero or one +Node+). Table accesses require + # volatile/atomic reads, writes, and CASes. 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 28 bits of each +Node+'s hash field contain a 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 +[]=+ 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 +Concurrent::ThreadSafe::Util::CheapLockable. However, we also need a + # +try_lock+ 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 + # +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+. + # + # 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 +eql?+ 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. + # + # 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 +size_control+, 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. 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.size - 1+) up + # towards the first. Upon seeing a forwarding node, traversals 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. + # + # The element count is maintained using a +Concurrent::ThreadSafe::Util::Adder+, + # 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 +x_if_absent+ 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. + # + # @!visibility private + class AtomicReferenceMapBackend + + # @!visibility private + class Table < Concurrent::ThreadSafe::Util::PowerOfTwoTuple + def cas_new_node(i, hash, key, value) + cas(i, nil, Node.new(hash, key, value)) + end + + def try_to_cas_in_computed(i, hash, key) + succeeded = false + new_value = nil + new_node = Node.new(locked_hash = hash | LOCKED, key, NULL) + if cas(i, nil, new_node) + begin + if NULL == (new_value = yield(NULL)) + was_null = true + else + new_node.value = new_value + end + succeeded = true + ensure + volatile_set(i, nil) if !succeeded || was_null + new_node.unlock_via_hash(locked_hash, hash) + end + end + return succeeded, new_value + end + + def try_lock_via_hash(i, node, node_hash) + node.try_lock_via_hash(node_hash) do + yield if volatile_get(i) == node + end + end + + def delete_node_at(i, node, predecessor_node) + if predecessor_node + predecessor_node.next = node.next + else + volatile_set(i, node.next) + end + end + end + + # Key-value entry. Nodes with a hash field of +MOVED+ are special, and do + # not contain user keys or values. Otherwise, keys are never +nil+, and + # +NULL+ +value+ 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 value, but can only be used after checking value to be +!= NULL+. + # + # @!visibility private + class Node + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :hash, :value, :next + + include Concurrent::ThreadSafe::Util::CheapLockable + + bit_shift = Concurrent::ThreadSafe::Util::FIXNUM_BIT_SIZE - 2 # need 2 bits for ourselves + # Encodings for special uses of Node hash fields. See above for explanation. + MOVED = ('10' << ('0' * bit_shift)).to_i(2) # hash field for forwarding nodes + LOCKED = ('01' << ('0' * bit_shift)).to_i(2) # set/tested only as a bit + WAITING = ('11' << ('0' * bit_shift)).to_i(2) # both bits set/tested together + HASH_BITS = ('00' << ('1' * bit_shift)).to_i(2) # usable bits of normal node hash + + SPIN_LOCK_ATTEMPTS = Concurrent::ThreadSafe::Util::CPU_COUNT > 1 ? Concurrent::ThreadSafe::Util::CPU_COUNT * 2 : 0 + + attr_reader :key + + def initialize(hash, key, value, next_node = nil) + super() + @key = key + self.lazy_set_hash(hash) + self.lazy_set_value(value) + self.next = next_node + end + + # 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 + # +cheap_synchronize+ lock and perform a +cheap_broadcast+. + def try_await_lock(table, i) + if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking? + spins = SPIN_LOCK_ATTEMPTS + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.get + while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash) + if spins >= 0 + if (randomizer = (randomizer >> 1)).even? # spin at random + if (spins -= 1) == 0 + Thread.pass # yield before blocking + else + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? + end + end + elsif cas_hash(my_hash, my_hash | WAITING) + force_acquire_lock(table, i) + break + end + end + end + end + + def key?(key) + @key.eql?(key) + end + + def matches?(key, hash) + pure_hash == hash && key?(key) + end + + def pure_hash + hash & HASH_BITS + end + + def try_lock_via_hash(node_hash = hash) + if cas_hash(node_hash, locked_hash = node_hash | LOCKED) + begin + yield + ensure + unlock_via_hash(locked_hash, node_hash) + end + end + end + + def locked? + self.class.locked_hash?(hash) + end + + def unlock_via_hash(locked_hash, node_hash) + unless cas_hash(locked_hash, node_hash) + self.hash = node_hash + cheap_synchronize { cheap_broadcast } + end + end + + private + def force_acquire_lock(table, i) + cheap_synchronize do + if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING + cheap_wait + else + cheap_broadcast # possibly won race vs signaller + end + end + end + + class << self + def locked_hash?(hash) + (hash & LOCKED) != 0 + end + end + end + + # shorthands + MOVED = Node::MOVED + LOCKED = Node::LOCKED + WAITING = Node::WAITING + HASH_BITS = Node::HASH_BITS + + NOW_RESIZING = -1 + DEFAULT_CAPACITY = 16 + MAX_CAPACITY = Concurrent::ThreadSafe::Util::MAX_INT + + # The buffer size for skipped bins during transfers. The + # value is arbitrary but should be large enough to avoid + # most locking stalls during resizes. + TRANSFER_BUFFER_SIZE = 32 + + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two. + + # 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. + :size_control + + def initialize(options = nil) + super() + @counter = Concurrent::ThreadSafe::Util::Adder.new + initial_capacity = options && options[:initial_capacity] || DEFAULT_CAPACITY + self.size_control = (capacity = table_size_for(initial_capacity)) > MAX_CAPACITY ? MAX_CAPACITY : capacity + end + + def get_or_default(key, else_value = nil) + hash = key_hash(key) + current_table = table + while current_table + node = current_table.volatile_get_by_hash(hash) + current_table = + while node + if (node_hash = node.hash) == MOVED + break node.key + elsif (node_hash & HASH_BITS) == hash && node.key?(key) && NULL != (value = node.value) + return value + end + node = node.next + end + end + else_value + end + + def [](key) + get_or_default(key) + end + + def key?(key) + get_or_default(key, NULL) != NULL + end + + def []=(key, value) + get_and_set(key, value) + value + end + + def compute_if_absent(key) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key) { yield } + if succeeded + increment_size + return new_value + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif NULL != (current_value = find_value_in_node_list(node, key, hash, node_hash & HASH_BITS)) + return current_value + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, value = attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) { yield } + return value if succeeded + end + end + end + + def compute_if_present(key) + new_value = nil + internal_replace(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + new_value + end + + def compute(key) + internal_compute(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + end + + def merge_pair(key, value) + internal_compute(key) do |old_value| + if NULL == old_value || !(value = yield(old_value)).nil? + value + else + NULL + end + end + end + + def replace_pair(key, old_value, new_value) + NULL != internal_replace(key, old_value) { new_value } + end + + def replace_if_exists(key, new_value) + if (result = internal_replace(key) { new_value }) && NULL != result + result + end + end + + def get_and_set(key, value) # internalPut in the original CHMV8 + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + if current_table.cas_new_node(i, hash, key, value) + increment_size + break + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + break old_value if succeeded + end + end + end + + def delete(key) + replace_if_exists(key, NULL) + end + + def delete_pair(key, value) + result = internal_replace(key, value) { NULL } + if result && NULL != result + !!result + else + false + end + end + + def each_pair + return self unless current_table = table + current_table_size = base_size = current_table.size + i = base_index = 0 + while base_index < base_size + if node = current_table.volatile_get(i) + if node.hash == MOVED + current_table = node.key + current_table_size = current_table.size + else + begin + if NULL != (value = node.value) # skip deleted or special nodes + yield node.key, value + end + end while node = node.next + end + end + + if (i_with_base = i + base_size) < current_table_size + i = i_with_base # visit upper slots if present + else + i = base_index += 1 + end + end + self + end + + def size + (sum = @counter.sum) < 0 ? 0 : sum # ignore transient negative values + end + + def empty? + size == 0 + end + + # Implementation for clear. Steps through each bin, removing all nodes. + def clear + return self unless current_table = table + current_table_size = current_table.size + deleted_count = i = 0 + while i < current_table_size + if !(node = current_table.volatile_get(i)) + i += 1 + elsif (node_hash = node.hash) == MOVED + current_table = node.key + current_table_size = current_table.size + elsif Node.locked_hash?(node_hash) + decrement_size(deleted_count) # opportunistically update count + deleted_count = 0 + node.try_await_lock(current_table, i) + else + current_table.try_lock_via_hash(i, node, node_hash) do + begin + deleted_count += 1 if NULL != node.value # recheck under lock + node.value = nil + end while node = node.next + current_table.volatile_set(i, nil) + i += 1 + end + end + end + decrement_size(deleted_count) + self + end + + private + # Internal versions of the insertion methods, each a + # little more complicated than the last. All have + # the same basic structure: + # 1. If table uninitialized, create + # 2. If bin empty, try to CAS new node + # 3. If bin stale, use new table + # 4. Lock and validate; if valid, scan and add or update + # + # The others interweave other checks and/or alternative actions: + # * Plain +get_and_set+ checks for and performs resize after insertion. + # * compute_if_absent prescans for mapping without lock (and fails to add + # if present), which also makes pre-emptive resize checks worthwhile. + # + # Someday when details settle down a bit more, it might be worth + # some factoring to reduce sprawl. + def internal_replace(key, expected_old_value = NULL, &block) + hash = key_hash(key) + current_table = table + while current_table + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + break + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif (node_hash & HASH_BITS) != hash && !node.next # precheck + break # rules out possible existence + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash, &block) + return old_value if succeeded + end + end + NULL + end + + def attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash) + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + old_value = NULL + begin + if node.matches?(key, hash) && NULL != (current_value = node.value) + if NULL == expected_old_value || expected_old_value == current_value # NULL == expected_old_value means whatever value + old_value = current_value + if NULL == (node.value = yield(old_value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + end + end + break + end + + predecessor_node = node + end while node = node.next + + return true, old_value + end + end + + def find_value_in_node_list(node, key, hash, pure_hash) + do_check_for_resize = false + while true + if pure_hash == hash && node.key?(key) && NULL != (value = node.value) + return value + elsif node = node.next + do_check_for_resize = true # at least 2 nodes -> check for resize + pure_hash = node.pure_hash + else + return NULL + end + end + ensure + check_for_resize if do_check_for_resize + end + + def internal_compute(key, &block) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key, &block) + if succeeded + if NULL == new_value + break nil + else + increment_size + break new_value + end + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, new_value = attempt_compute(key, hash, current_table, i, node, node_hash, &block) + break new_value if succeeded + end + end + end + + def attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + while true + if node.matches?(key, hash) && NULL != (value = node.value) + return true, value + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value = yield) + added = true + increment_size + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_compute(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + while true + if node.matches?(key, hash) && NULL != (value = node.value) + if NULL == (node.value = value = yield(value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + value = nil + end + return true, value + end + predecessor_node = node + unless node = node.next + if NULL == (value = yield(NULL)) + value = nil + else + predecessor_node.next = Node.new(hash, key, value) + added = true + increment_size + end + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + node_nesting = nil + current_table.try_lock_via_hash(i, node, node_hash) do + node_nesting = 1 + old_value = nil + found_old_value = false + while node + if node.matches?(key, hash) && NULL != (old_value = node.value) + found_old_value = true + node.value = value + break + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value) + break + end + node_nesting += 1 + end + + return true, old_value if found_old_value + increment_size + true + end + ensure + check_for_resize if node_nesting && (node_nesting > 1 || current_table.size <= 64) + end + + def initialize_copy(other) + super + @counter = Concurrent::ThreadSafe::Util::Adder.new + self.table = nil + self.size_control = (other_table = other.table) ? other_table.size : DEFAULT_CAPACITY + self + end + + def try_await_lock(current_table, i, node) + check_for_resize # try resizing if can't get lock + node.try_await_lock(current_table, i) + end + + def key_hash(key) + key.hash & HASH_BITS + end + + # Returns a power of two table size for the given desired capacity. + def table_size_for(entry_count) + size = 2 + size <<= 1 while size < entry_count + size + end + + # Initializes table, using the size recorded in +size_control+. + def initialize_table + until current_table ||= table + if (size_ctrl = size_control) == NOW_RESIZING + Thread.pass # lost initialization race; just spin + else + try_in_resize_lock(current_table, size_ctrl) do + initial_size = size_ctrl > 0 ? size_ctrl : DEFAULT_CAPACITY + current_table = self.table = Table.new(initial_size) + initial_size - (initial_size >> 2) # 75% load factor + end + end + end + current_table + end + + # 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. + def check_for_resize + while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum + try_in_resize_lock(current_table, size_ctrl) do + self.table = rebuild(current_table) + (table_size << 1) - (table_size >> 1) # 75% load factor + end + end + end + + def try_in_resize_lock(current_table, size_ctrl) + if cas_size_control(size_ctrl, NOW_RESIZING) + begin + if current_table == table # recheck under lock + size_ctrl = yield # get new size_control + end + ensure + self.size_control = size_ctrl + end + end + end + + # Moves and/or copies the nodes in each bin to new table. See above for explanation. + def rebuild(table) + old_table_size = table.size + new_table = table.next_in_size_table + # puts "#{old_table_size} -> #{new_table.size}" + forwarder = Node.new(MOVED, new_table, NULL) + rev_forwarder = nil + locked_indexes = nil # holds bins to revisit; nil until needed + locked_arr_idx = 0 + bin = old_table_size - 1 + i = bin + while true + if !(node = table.volatile_get(i)) + # no lock needed (or available) if bin >= 0, because we're not popping values from locked_indexes until we've run through the whole table + redo unless (bin >= 0 ? table.cas(i, nil, forwarder) : lock_and_clean_up_reverse_forwarders(table, old_table_size, new_table, i, forwarder)) + elsif Node.locked_hash?(node_hash = node.hash) + locked_indexes ||= ::Array.new + if bin < 0 && locked_arr_idx > 0 + locked_arr_idx -= 1 + i, locked_indexes[locked_arr_idx] = locked_indexes[locked_arr_idx], i # swap with another bin + redo + end + if bin < 0 || locked_indexes.size >= TRANSFER_BUFFER_SIZE + node.try_await_lock(table, i) # no other options -- block + redo + end + rev_forwarder ||= Node.new(MOVED, table, NULL) + redo unless table.volatile_get(i) == node && node.locked? # recheck before adding to list + locked_indexes << i + new_table.volatile_set(i, rev_forwarder) + new_table.volatile_set(i + old_table_size, rev_forwarder) + else + redo unless split_old_bin(table, new_table, i, node, node_hash, forwarder) + end + + if bin > 0 + i = (bin -= 1) + elsif locked_indexes && !locked_indexes.empty? + bin = -1 + i = locked_indexes.pop + locked_arr_idx = locked_indexes.size - 1 + else + return new_table + end + end + end + + def lock_and_clean_up_reverse_forwarders(old_table, old_table_size, new_table, i, forwarder) + # transiently use a locked forwarding node + locked_forwarder = Node.new(moved_locked_hash = MOVED | LOCKED, new_table, NULL) + if old_table.cas(i, nil, locked_forwarder) + new_table.volatile_set(i, nil) # kill the potential reverse forwarders + new_table.volatile_set(i + old_table_size, nil) # kill the potential reverse forwarders + old_table.volatile_set(i, forwarder) + locked_forwarder.unlock_via_hash(moved_locked_hash, MOVED) + true + end + end + + # Splits a normal bin with list headed by e into lo and hi parts; installs in given table. + def split_old_bin(table, new_table, i, node, node_hash, forwarder) + table.try_lock_via_hash(i, node, node_hash) do + split_bin(new_table, i, node, node_hash) + table.volatile_set(i, forwarder) + end + end + + def split_bin(new_table, i, node, node_hash) + bit = new_table.size >> 1 # bit to split on + run_bit = node_hash & bit + last_run = nil + low = nil + high = nil + current_node = node + # this optimises for the lowest amount of volatile writes and objects created + while current_node = current_node.next + unless (b = current_node.hash & bit) == run_bit + run_bit = b + last_run = current_node + end + end + if run_bit == 0 + low = last_run + else + high = last_run + end + current_node = node + until current_node == last_run + pure_hash = current_node.pure_hash + if (pure_hash & bit) == 0 + low = Node.new(pure_hash, current_node.key, current_node.value, low) + else + high = Node.new(pure_hash, current_node.key, current_node.value, high) + end + current_node = current_node.next + end + new_table.volatile_set(i, low) + new_table.volatile_set(i + bit, high) + end + + def increment_size + @counter.increment + end + + def decrement_size(by = 1) + @counter.add(-by) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb new file mode 100644 index 0000000000..903c1f2f47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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) + super(options) + @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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb new file mode 100644 index 0000000000..e7c62e6d19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb @@ -0,0 +1,140 @@ +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) + @backend = {} + 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(@backend[key])) + 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 = @backend[key] + @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 + + alias_method :_get, :[] + alias_method :_set, :[]= + private :_get, :_set + private + def initialize_copy(other) + super + @backend = {} + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb new file mode 100644 index 0000000000..190c8d98d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb @@ -0,0 +1,82 @@ +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class SynchronizedMapBackend < NonConcurrentMapBackend + + require 'mutex_m' + include Mutex_m + # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are + # not allowed to call each other. + + def [](key) + synchronize { super } + end + + def []=(key, value) + synchronize { super } + end + + def compute_if_absent(key) + synchronize { super } + end + + def compute_if_present(key) + synchronize { super } + end + + def compute(key) + synchronize { super } + end + + def merge_pair(key, value) + synchronize { super } + end + + def replace_pair(key, old_value, new_value) + synchronize { super } + end + + def replace_if_exists(key, new_value) + synchronize { super } + end + + def get_and_set(key, value) + synchronize { super } + end + + def key?(key) + synchronize { super } + end + + def delete(key) + synchronize { super } + end + + def delete_pair(key, value) + synchronize { super } + end + + def clear + synchronize { super } + end + + def size + synchronize { super } + end + + def get_or_default(key, default_value) + synchronize { super } + end + + private + def dupped_backend + synchronize { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb new file mode 100644 index 0000000000..68a1b3884d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..694cd7ac7c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..322b4ac2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/deprecation.rb new file mode 100644 index 0000000000..35ae4b2c9d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb new file mode 100644 index 0000000000..dc172ba74d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/logging.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/logging.rb new file mode 100644 index 0000000000..2c749996f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/logging.rb @@ -0,0 +1,32 @@ +require 'logger' + +module Concurrent + module Concern + + # Include where logging is needed + # + # @!visibility private + module Logging + include Logger::Severity + + # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger + # @param [Integer] level one of Logger::Severity 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) + #NOTE: Cannot require 'concurrent/configuration' above due to circular references. + # Assume that the gem has been initialized if we've gotten this far. + logger = if defined?(@logger) && @logger + @logger + else + Concurrent.global_logger + end + logger.call level, progname, message, &block + rescue => error + $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" + + "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/obligation.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/obligation.rb new file mode 100644 index 0000000000..2c9ac12003 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/observable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concern/observable.rb new file mode 100644 index 0000000000..b5132714bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concurrent_ruby.jar b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concurrent_ruby.jar new file mode 100644 index 0000000000..887aa1643d Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/concurrent_ruby.jar differ diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/configuration.rb new file mode 100644 index 0000000000..a00dc84403 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/configuration.rb @@ -0,0 +1,188 @@ +require 'thread' +require 'concurrent/delay' +require 'concurrent/errors' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/concern/logging' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/utility/processor_counter' + +module Concurrent + extend Concern::Logging + extend Concern::Deprecation + + autoload :Options, 'concurrent/options' + autoload :TimerSet, 'concurrent/executor/timer_set' + autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor' + + # @return [Logger] Logger with provided level and output. + def self.create_simple_logger(level = Logger::FATAL, output = $stderr) + # 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'), + Logger::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 = Logger::FATAL, output = $stderr) + Concurrent.global_logger = create_simple_logger level, output + end + + # @return [Logger] Logger with provided level and output. + # @deprecated + def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr) + 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 = Logger::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(Logger::WARN)) + private_constant :GLOBAL_LOGGER + + def self.global_logger + GLOBAL_LOGGER.value + end + + def self.global_logger=(value) + GLOBAL_LOGGER.value = value + end + + # @!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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/constants.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/constants.rb new file mode 100644 index 0000000000..676c2afb9a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/dataflow.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/dataflow.rb new file mode 100644 index 0000000000..d55f19d850 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/delay.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/delay.rb new file mode 100644 index 0000000000..83799d03dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/delay.rb @@ -0,0 +1,199 @@ +require 'thread' +require 'concurrent/concern/obligation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/synchronization' + +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 opration 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 execption 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/errors.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/errors.rb new file mode 100644 index 0000000000..b69fec01f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/errors.rb @@ -0,0 +1,69 @@ +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 + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/exchanger.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/exchanger.rb new file mode 100644 index 0000000000..5a99550b33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/exchanger.rb @@ -0,0 +1,352 @@ +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? + + # @!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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb new file mode 100644 index 0000000000..6d0b0474d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb @@ -0,0 +1,128 @@ +require 'concurrent/errors' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/executor_service' +require 'concurrent/synchronization' + +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 + + # Handler which executes the `fallback_policy` once the queue size + # reaches `max_queue`. + # + # @param [Array] args the arguments to the task which is being handled. + # + # @!visibility private + def handle_fallback(*args) + case fallback_policy + when :abort + raise RejectedExecutionError + when :discard + false + when :caller_runs + begin + yield(*args) + rescue => ex + # let it fail + log DEBUG, ex + end + true + else + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb new file mode 100644 index 0000000000..de50ed1791 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/executor_service.rb new file mode 100644 index 0000000000..7e344919e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb new file mode 100644 index 0000000000..b665f6c569 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb @@ -0,0 +1,210 @@ +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_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_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 + + + + + # @!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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb new file mode 100644 index 0000000000..282df7a059 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb new file mode 100644 index 0000000000..4f9769fa3f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb new file mode 100644 index 0000000000..9c0f3100cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb @@ -0,0 +1,103 @@ +if Concurrent.on_jruby? + + require 'concurrent/errors' + require 'concurrent/utility/engine' + 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 handle_fallback(*args, &task) 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? + if @executor.respond_to? :isTerminating + @executor.isTerminating + else + false + end + end + + def ns_shutdown? + @executor.isShutdown || @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 + end + + def newThread(runnable) + thread = java.util.concurrent.Executors.defaultThreadFactory().newThread(runnable) + thread.setDaemon(@daemonize) + return thread + end + end + + private_constant :DaemonThreadFactory + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb new file mode 100644 index 0000000000..7aa24f2d72 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb new file mode 100644 index 0000000000..e67066385e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb @@ -0,0 +1,136 @@ +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_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 + + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb new file mode 100644 index 0000000000..06658d3769 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb @@ -0,0 +1,76 @@ +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? + synchronize do + # If the executor is shut down, reject this task + return handle_fallback(*args, &task) unless running? + ns_execute(*args, &task) + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb new file mode 100644 index 0000000000..916337d4ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb new file mode 100644 index 0000000000..dc20d765b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb @@ -0,0 +1,377 @@ +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 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) + synchronize { ns_ready_worker worker } + end + + # @!visibility private + def worker_not_old_enough(worker) + synchronize { ns_worker_not_old_enough worker } + 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 + + 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 + handle_fallback(*args, &task) + end + + ns_prune_pool if @next_gc_time < Concurrent.monotonic_time + 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, 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, 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? + @ready.push(worker) + else + worker.stop + end + end + end + + # returns back worker to @ready which was not idle for enough time + # + # @!visibility private + def ns_worker_not_old_enough(worker) + # let's put workers coming from idle_test back to the start (as the oldest worker) + @ready.unshift(worker) + true + 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 + return if @pool.size <= @min_length + + last_used = @ready.shift + last_used << :idle_test if last_used + + @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| + last_message = Concurrent.monotonic_time + catch(:stop) do + loop do + + case message = my_queue.pop + when :idle_test + if (Concurrent.monotonic_time - last_message) > my_idletime + my_pool.remove_busy_worker(self) + throw :stop + else + my_pool.worker_not_old_enough(self) + end + + when :stop + my_pool.remove_busy_worker(self) + throw :stop + + else + task, args = message + run_task my_pool, task, args + last_message = Concurrent.monotonic_time + + my_pool.ready_worker(self) + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb new file mode 100644 index 0000000000..414aa641f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb @@ -0,0 +1,35 @@ +require 'concurrent/synchronization' + +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) + synchronize do + success = false + value = reason = nil + + begin + value = @task.call(*args) + success = true + rescue @exception_class => ex + reason = ex + success = false + end + + [success, value, reason] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb new file mode 100644 index 0000000000..f1c38ecfa9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb new file mode 100644 index 0000000000..d314e90616 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb @@ -0,0 +1,107 @@ +require 'concurrent/errors' +require 'concurrent/concern/logging' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb new file mode 100644 index 0000000000..8197781b52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb new file mode 100644 index 0000000000..b87fed5e02 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb @@ -0,0 +1,100 @@ +require 'concurrent/atomics' +require 'concurrent/executor/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb new file mode 100644 index 0000000000..f1474ea9ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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 bahaviors. 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb new file mode 100644 index 0000000000..253d46a9d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/timer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/timer_set.rb new file mode 100644 index 0000000000..0dfaf1288c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/timer_set.rb @@ -0,0 +1,172 @@ +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/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 } + task.executor.post { task.process_task } + else + @condition.wait([diff, 60].min) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executors.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executors.rb new file mode 100644 index 0000000000..eb1972ce69 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/future.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/future.rb new file mode 100644 index 0000000000..1af182ecb2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/hash.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/hash.rb new file mode 100644 index 0000000000..92df66b790 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/hash.rb @@ -0,0 +1,59 @@ +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 thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::Hash + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyHash < ::Hash + include JRuby::Synchronized + end + JRubyHash + + when Concurrent.on_rbx? + require 'monitor' + require 'concurrent/thread_safe/util/data_structures' + + class RbxHash < ::Hash + end + ThreadSafe::Util.make_synchronized_on_rbx RbxHash + RbxHash + + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/immutable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/immutable_struct.rb new file mode 100644 index 0000000000..d2755951f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/immutable_struct.rb @@ -0,0 +1,101 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/ivar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/ivar.rb new file mode 100644 index 0000000000..2a724db467 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/ivar.rb @@ -0,0 +1,207 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/obligation' +require 'concurrent/concern/observable' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/map.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/map.rb new file mode 100644 index 0000000000..00731a3f16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/map.rb @@ -0,0 +1,347 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/synchronization' +require 'concurrent/utility/engine' + +module Concurrent + # @!visibility private + module Collection + + # @!visibility private + MapImplementation = case + when Concurrent.on_jruby? + # noinspection RubyResolve + JRubyMapBackend + when Concurrent.on_cruby? + require 'concurrent/collection/map/mri_map_backend' + MriMapBackend + when Concurrent.on_truffleruby? && defined?(::TruffleRuby::ConcurrentMap) + require 'concurrent/collection/map/truffleruby_map_backend' + TruffleRubyMapBackend + when Concurrent.on_truffleruby? || Concurrent.on_rbx? + require 'concurrent/collection/map/atomic_reference_map_backend' + AtomicReferenceMapBackend + 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 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 + + # + def initialize(options = nil, &block) + if options.kind_of?(::Hash) + validate_options_hash!(options) + else + options = nil + end + + super(options) + @default_proc = block + 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 incorrent +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 + + # Set a value with key + # @param [Object] key + # @param [Object] value + # @return [Object] the new value + def []=(key, value) + super + 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 + # @!macro map.atomic_method_with_block + 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) + alias_method :index, :key if RUBY_VERSION < '1.9' + + # 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/maybe.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/maybe.rb new file mode 100644 index 0000000000..7ba3d3ebb5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/maybe.rb @@ -0,0 +1,229 @@ +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mutable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mutable_struct.rb new file mode 100644 index 0000000000..55361e753f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mutable_struct.rb @@ -0,0 +1,239 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mvar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mvar.rb new file mode 100644 index 0000000000..9034711bf5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/mvar.rb @@ -0,0 +1,242 @@ +require 'concurrent/concern/dereferenceable' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/options.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/options.rb new file mode 100644 index 0000000000..bdd22a9df1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/promise.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/promise.rb new file mode 100644 index 0000000000..e91779173d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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' + # ``` + # + # 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 intialization 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/promises.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/promises.rb new file mode 100644 index 0000000000..76af4d5963 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/promises.rb @@ -0,0 +1,2167 @@ +require 'concurrent/synchronization' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/collection/lock_free_stack' +require 'concurrent/errors' +require 'concurrent/re_include' + +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 @yeild 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 + + # Created resolvable event, user is responsible for resolving the event once by + # {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 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 resolved future with will be either fulfilled with the given value or rejection 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 resolved future with 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 resolved future with 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 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 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 new future which is resolved after all futures_and_or_events are resolved. + # Its value is array of zipped future values. Its reason is 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 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 new future which is resolved after first futures_and_or_events is resolved. + # Its result equals 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 new future which is resolved after first of futures_and_or_events is fulfilled. + # Its result equals result of the first resolved future or if all futures_and_or_events reject, + # it has reason of the last resolved 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 [Future] + def any_event(*futures_and_or_events) + any_event_on default_executor, *futures_and_or_events + end + + # Creates new event which becomes resolved after first of the 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, @DefaultExecutor, 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 + unless resolved? + @Condition.wait @Lock, timeout + 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) + 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, @DefaultExecutor, 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, @DefaultExecutor, 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 returner 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, future, index) + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/re_include.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/re_include.rb new file mode 100644 index 0000000000..516d58cae1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/re_include.rb @@ -0,0 +1,58 @@ +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 + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/scheduled_task.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/scheduled_task.rb new file mode 100644 index 0000000000..90f78b00ce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/scheduled_task.rb @@ -0,0 +1,318 @@ +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' + # require 'thread' # for Queue + # require 'open-uri' # for open(uri) + # + # class Ticker + # def get_year_end_closing(symbol, year) + # uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m" + # data = open(uri) {|f| f.collect{|line| line.strip } } + # data[1].split(',')[4].to_f + # end + # end + # + # # Future + # price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013) } + # price.state #=> :pending + # sleep(1) # do other stuff + # price.value #=> 63.65 + # price.state #=> :fulfilled + # + # # ScheduledTask + # task = Concurrent::ScheduledTask.execute(2){ Ticker.new.get_year_end_closing('INTC', 2013) } + # task.state #=> :pending + # sleep(3) # do other stuff + # task.value #=> 25.96 + # + # @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 instanciation. + # + # @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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/set.rb new file mode 100644 index 0000000000..3bf0c895c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/set.rb @@ -0,0 +1,74 @@ +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_rbx? + require 'monitor' + require 'concurrent/thread_safe/util/data_structures' + + class RbxSet < ::Set + end + + ThreadSafe::Util.make_synchronized_on_rbx RbxSet + RbxSet + + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/settable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/settable_struct.rb new file mode 100644 index 0000000000..00123523cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/settable_struct.rb @@ -0,0 +1,139 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/errors' +require 'concurrent/synchronization' + +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization.rb new file mode 100644 index 0000000000..49c68ebbb3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization.rb @@ -0,0 +1,30 @@ +require 'concurrent/utility/engine' + +require 'concurrent/synchronization/abstract_object' +require 'concurrent/utility/native_extension_loader' # load native parts first +Concurrent.load_native_extensions + +require 'concurrent/synchronization/mri_object' +require 'concurrent/synchronization/jruby_object' +require 'concurrent/synchronization/rbx_object' +require 'concurrent/synchronization/truffleruby_object' +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/volatile' + +require 'concurrent/synchronization/abstract_lockable_object' +require 'concurrent/synchronization/mutex_lockable_object' +require 'concurrent/synchronization/jruby_lockable_object' +require 'concurrent/synchronization/rbx_lockable_object' + +require 'concurrent/synchronization/lockable_object' + +require 'concurrent/synchronization/condition' +require 'concurrent/synchronization/lock' + +module Concurrent + # {include:file:docs-source/synchronization.md} + # {include:file:docs-source/synchronization-notes.md} + module Synchronization + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb new file mode 100644 index 0000000000..bc12603364 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb @@ -0,0 +1,98 @@ +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb new file mode 100644 index 0000000000..532388b2b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb @@ -0,0 +1,24 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + class AbstractObject + + # @abstract has to be implemented based on Ruby runtime + def initialize + raise NotImplementedError + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb new file mode 100644 index 0000000000..1fe90c1649 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/condition.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/condition.rb new file mode 100644 index 0000000000..f704b81ee6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/condition.rb @@ -0,0 +1,60 @@ +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb new file mode 100644 index 0000000000..359a032b7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb @@ -0,0 +1,13 @@ +module Concurrent + module Synchronization + + if Concurrent.on_jruby? && Concurrent.java_extensions_loaded? + + # @!visibility private + # @!macro internal_implementation_note + class JRubyLockableObject < AbstractLockableObject + + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb new file mode 100644 index 0000000000..da91ac50b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb @@ -0,0 +1,45 @@ +module Concurrent + module Synchronization + + if Concurrent.on_jruby? && Concurrent.java_extensions_loaded? + + # @!visibility private + module JRubyAttrVolatile + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def attr_volatile(*names) + names.each do |name| + + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + instance_variable_get_volatile(:#{ivar}) + end + + def #{name}=(value) + instance_variable_set_volatile(:#{ivar}, value) + end + RUBY + + end + names.map { |n| [n, :"#{n}="] }.flatten + end + end + end + + # @!visibility private + # @!macro internal_implementation_note + class JRubyObject < AbstractObject + include JRubyAttrVolatile + + def initialize + # nothing to do + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lock.rb new file mode 100644 index 0000000000..0dbad2eb45 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lock.rb @@ -0,0 +1,36 @@ +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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb new file mode 100644 index 0000000000..cdbe4d4377 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb @@ -0,0 +1,74 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + LockableObjectImplementation = case + when Concurrent.on_cruby? && Concurrent.ruby_version(:<=, 1, 9, 3) + MonitorLockableObject + when Concurrent.on_cruby? && Concurrent.ruby_version(:>, 1, 9, 3) + MutexLockableObject + when Concurrent.on_jruby? + JRubyLockableObject + when Concurrent.on_rbx? + RbxLockableObject + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb new file mode 100644 index 0000000000..4b1d6c2956 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb @@ -0,0 +1,44 @@ +module Concurrent + module Synchronization + + # @!visibility private + module MriAttrVolatile + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + 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 + end + + def 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 + end + + # @!visibility private + # @!macro internal_implementation_note + class MriObject < AbstractObject + include MriAttrVolatile + + def initialize + # nothing to do + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb new file mode 100644 index 0000000000..f17ea507aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb @@ -0,0 +1,88 @@ +module Concurrent + # noinspection RubyInstanceVariableNamingConvention + 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(*defaults) + super(*defaults) + @__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(*defaults) + super(*defaults) + @__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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/object.rb new file mode 100644 index 0000000000..0e62112807 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/object.rb @@ -0,0 +1,183 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + ObjectImplementation = case + when Concurrent.on_cruby? + MriObject + when Concurrent.on_jruby? + JRubyObject + when Concurrent.on_rbx? + RbxObject + when Concurrent.on_truffleruby? + TruffleRubyObject + else + warn 'Possibly unsupported Ruby implementation' + MriObject + end + private_constant :ObjectImplementation + + # 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} + class Object < ObjectImplementation + # 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 + + # By calling this method on a class, it 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. + # @example + # class AClass < Concurrent::Synchronization::Object + # safe_initialization! + # + # def initialize + # @AFinalValue = 'value' # published safely, does not have to be synchronized + # end + # end + # @return [true] + def self.safe_initialization! + # define only once, and not again in children + return if safe_initialization? + + # @!visibility private + def self.new(*args, &block) + object = super(*args, &block) + ensure + object.full_memory_barrier if object + end + + @safe_initialization = true + end + + # @return [true, false] if this class is safely initialized. + def self.safe_initialization? + @safe_initialization = false unless defined? @safe_initialization + @safe_initialization || (superclass.respond_to?(:safe_initialization?) && superclass.safe_initialization?) + 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 oly 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb new file mode 100644 index 0000000000..1c4697c397 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb @@ -0,0 +1,71 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + class RbxLockableObject < AbstractLockableObject + safe_initialization! + + def initialize(*defaults) + super(*defaults) + @__Waiters__ = [] + @__owner__ = nil + end + + def initialize_copy(other) + super + @__Waiters__ = [] + @__owner__ = nil + end + + protected + + def synchronize(&block) + if @__owner__ == Thread.current + yield + else + result = nil + Rubinius.synchronize(self) do + begin + @__owner__ = Thread.current + result = yield + ensure + @__owner__ = nil + end + end + result + end + end + + def ns_wait(timeout = nil) + wchan = Rubinius::Channel.new + + begin + @__Waiters__.push wchan + Rubinius.unlock(self) + signaled = wchan.receive_timeout timeout + ensure + Rubinius.lock(self) + + if !signaled && !@__Waiters__.delete(wchan) + # we timed out, but got signaled afterwards, + # so pass that signal on to the next waiter + @__Waiters__.shift << true unless @__Waiters__.empty? + end + end + + self + end + + def ns_signal + @__Waiters__.shift << true unless @__Waiters__.empty? + self + end + + def ns_broadcast + @__Waiters__.shift << true until @__Waiters__.empty? + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb new file mode 100644 index 0000000000..4b23f2a662 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb @@ -0,0 +1,49 @@ +module Concurrent + module Synchronization + + # @!visibility private + module RbxAttrVolatile + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + Rubinius.memory_barrier + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + Rubinius.memory_barrier + end + RUBY + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + end + + def full_memory_barrier + # Rubinius instance variables are not volatile so we need to insert barrier + # TODO (pitr 26-Nov-2015): check comments like ^ + Rubinius.memory_barrier + end + end + + # @!visibility private + # @!macro internal_implementation_note + class RbxObject < AbstractObject + include RbxAttrVolatile + + def initialize + # nothing to do + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb new file mode 100644 index 0000000000..3919c76d21 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb @@ -0,0 +1,47 @@ +module Concurrent + module Synchronization + + # @!visibility private + module TruffleRubyAttrVolatile + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + full_memory_barrier + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + full_memory_barrier + end + RUBY + end + + names.map { |n| [n, :"#{n}="] }.flatten + end + end + + def full_memory_barrier + TruffleRuby.full_memory_barrier + end + end + + # @!visibility private + # @!macro internal_implementation_note + class TruffleRubyObject < AbstractObject + include TruffleRubyAttrVolatile + + def initialize + # nothing to do + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/volatile.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/volatile.rb new file mode 100644 index 0000000000..9dffa914ae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/synchronization/volatile.rb @@ -0,0 +1,36 @@ +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 + + Volatile = case + when Concurrent.on_cruby? + MriAttrVolatile + when Concurrent.on_jruby? + JRubyAttrVolatile + when Concurrent.on_rbx? + RbxAttrVolatile + when Concurrent.on_truffleruby? + TruffleRubyAttrVolatile + else + MriAttrVolatile + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb new file mode 100644 index 0000000000..92e7c45fc5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb @@ -0,0 +1,50 @@ +require 'delegate' +require 'monitor' + +module Concurrent + unless defined?(SynchronizedDelegator) + + # 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util.rb new file mode 100644 index 0000000000..c67084a26f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb new file mode 100644 index 0000000000..7a6e8d5c0e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.LondAdder 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb new file mode 100644 index 0000000000..d9b4c58186 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb @@ -0,0 +1,118 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/volatile' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ + # with the +ConditionVariable+ bundled in. + # + # Usage: + # class A + # include CheapLockable + # + # def do_exlusively + # cheap_synchronize { yield } + # end + # + # def wait_for_something + # cheap_synchronize do + # cheap_wait until resource_available? + # do_something + # cheap_broadcast # wake up others + # end + # end + # end + # + # @!visibility private + module CheapLockable + private + engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + if engine == 'rbx' + # Making use of the Rubinius' ability to lock via object headers to avoid the overhead of the extra Mutex objects. + def cheap_synchronize + Rubinius.lock(self) + begin + yield + ensure + Rubinius.unlock(self) + end + end + + def cheap_wait + wchan = Rubinius::Channel.new + + begin + waiters = @waiters ||= [] + waiters.push wchan + Rubinius.unlock(self) + signaled = wchan.receive_timeout nil + ensure + Rubinius.lock(self) + + unless signaled or waiters.delete(wchan) + # we timed out, but got signaled afterwards (e.g. while waiting to + # acquire @lock), so pass that signal on to the next waiter + waiters.shift << true unless waiters.empty? + end + end + + self + end + + def cheap_broadcast + waiters = @waiters ||= [] + waiters.shift << true until waiters.empty? + self + end + elsif engine == 'jruby' + # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects + require 'jruby' + + def cheap_synchronize + JRuby.reference0(self).synchronized { yield } + end + + def cheap_wait + JRuby.reference0(self).wait + end + + def cheap_broadcast + JRuby.reference0(self).notify_all + end + else + require 'thread' + + extend Volatile + attr_volatile :mutex + + # Non-reentrant Mutex#syncrhonize + def cheap_synchronize + true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new) + my_mutex.synchronize { yield } + end + + # Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup. + # Must only be called in +cheap_broadcast+'s block. + def cheap_wait + conditional_variable = @conditional_variable ||= ConditionVariable.new + conditional_variable.wait(mutex) + end + + # Wakes up all threads waiting for this object's +cheap_synchronize+ lock. + # Must only be called in +cheap_broadcast+'s block. + def cheap_broadcast + if conditional_variable = @conditional_variable + conditional_variable.broadcast + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb new file mode 100644 index 0000000000..24d039b2f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb @@ -0,0 +1,88 @@ +require 'concurrent/thread_safe/util' + +# 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_rbx(klass) + klass.class_eval do + private + + def _mon_initialize + @_monitor ||= Monitor.new # avoid double initialisation + end + + def self.new(*args) + obj = super(*args) + obj.send(:_mon_initialize) + obj + end + end + + klass.superclass.instance_methods(false).each do |method| + case method + when :new_range, :new_reserved + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + obj = super + obj.send(:_mon_initialize) + obj + end + RUBY + else + 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 + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb new file mode 100644 index 0000000000..b54be39c4c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb new file mode 100644 index 0000000000..4169c3d366 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb new file mode 100644 index 0000000000..cdac2a396a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb new file mode 100644 index 0000000000..bdde2dd8b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/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-localy 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/timer_task.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/timer_task.rb new file mode 100644 index 0000000000..a0b7233335 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/timer_task.rb @@ -0,0 +1,333 @@ +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. `TimerTask` can also be + # configured with a timeout value allowing it to kill a task that runs too + # long. + # + # 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. + # + # 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). If the timeout + # interval is exceeded the observer will receive a `Concurrent::TimeoutError` + # object as the third argument. + # + # @!macro copy_options + # + # @example Basic usage + # task = Concurrent::TimerTask.new{ puts 'Boom!' } + # task.execute + # + # task.execution_interval #=> 60 (default) + # task.timeout_interval #=> 30 (default) + # + # # wait 60 seconds... + # #=> 'Boom!' + # + # task.shutdown #=> true + # + # @example Configuring `:execution_interval` and `:timeout_interval` + # task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do + # puts 'Boom!' + # end + # + # task.execution_interval #=> 5 + # task.timeout_interval #=> 5 + # + # @example Immediate execution with `:run_now` + # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' } + # task.execute + # + # #=> 'Boom!' + # + # @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.times{ print 'Boom! ' } + # print "\n" + # task.execution_interval += 1 + # if task.execution_interval > 5 + # puts 'Stopping...' + # task.shutdown + # end + # end + # + # timer_task.execute # blocking call - this task will stop itself + # #=> 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" + # elsif ex.is_a?(Concurrent::TimeoutError) + # print "(#{time}) Execution timed out\n" + # else + # print "(#{time}) Execution failed with error #{ex}\n" + # end + # end + # end + # + # task = Concurrent::TimerTask.new(execution_interval: 1, timeout_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, timeout_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 + + # Default `:timeout_interval` in seconds. + TIMEOUT_INTERVAL = 30 + + # Create a new TimerTask with the given task and configuration. + # + # @!macro timer_task_initialize + # @param [Hash] opts the options defining task execution. + # @option opts [Integer] :execution_interval number of seconds between + # task executions (default: EXECUTION_INTERVAL) + # @option opts [Integer] :timeout_interval number of seconds a task can + # run before it is considered to have failed (default: TIMEOUT_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) + # + # @!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 [rw] timeout_interval + # @return [Fixnum] Number of seconds the task can run before it is + # considered to have failed. + def timeout_interval + synchronize { @timeout_interval } + 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) + if (value = value.to_f) <= 0.0 + raise ArgumentError.new('must be greater than zero') + else + synchronize { @timeout_interval = value } + end + end + + private :post, :<< + + private + + def ns_initialize(opts, &task) + set_deref_options(opts) + + self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL + self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL + @run_now = opts[:now] || opts[:run_now] + @executor = Concurrent::SafeTaskExecutor.new(task) + @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, args: [Concurrent::Event.new], &method(:execute_task)) + nil + end + + # @!visibility private + def execute_task(completion) + return nil unless @running.true? + ScheduledTask.execute(timeout_interval, args: [completion], &method(:timeout_task)) + _success, value, reason = @executor.execute(self) + if completion.try? + self.value = value + schedule_next_task + time = Time.now + observers.notify_observers do + [time, self.value, reason] + end + end + nil + end + + # @!visibility private + def timeout_task(completion) + return unless @running.true? + if completion.try? + schedule_next_task + observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tuple.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tuple.rb new file mode 100644 index 0000000000..f8c4c25d32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tuple.rb @@ -0,0 +1,86 @@ +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 + + # @!visibility private + Tuple = defined?(Rubinius::Tuple) ? Rubinius::Tuple : ::Array + private_constant :Tuple + + # 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 = Tuple.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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tvar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tvar.rb new file mode 100644 index 0000000000..d6fe3b1a35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/tvar.rb @@ -0,0 +1,261 @@ +require 'set' +require 'concurrent/synchronization' + +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 + @version = 0 + @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_version # :nodoc: + @version + end + + # @!visibility private + def unsafe_increment_version # :nodoc: + @version += 1 + 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 + + class Transaction + + ABORTED = ::Object.new + + ReadLogEntry = Struct.new(:tvar, :version) + + AbortError = Class.new(StandardError) + LeaveError = Class.new(StandardError) + + def initialize + @read_log = [] + @write_log = {} + end + + def read(tvar) + Concurrent::abort_transaction unless valid? + + if @write_log.has_key? tvar + @write_log[tvar] + else + @read_log.push(ReadLogEntry.new(tvar, tvar.unsafe_version)) + tvar.unsafe_value + end + end + + def write(tvar, value) + # Have we already written to this TVar? + + if @write_log.has_key? tvar + # Record the value written + @write_log[tvar] = value + else + # Try to lock the TVar + + unless tvar.unsafe_lock.try_lock + # Someone else is writing to this TVar - abort + Concurrent::abort_transaction + end + + # Record the value written + + @write_log[tvar] = value + + # If we previously read from it, check the version hasn't changed + + @read_log.each do |log_entry| + if log_entry.tvar == tvar and tvar.unsafe_version > log_entry.version + Concurrent::abort_transaction + end + end + end + end + + def abort + unlock + end + + def commit + return false unless valid? + + @write_log.each_pair do |tvar, value| + tvar.unsafe_value = value + tvar.unsafe_increment_version + end + + unlock + + true + end + + def valid? + @read_log.each do |log_entry| + unless @write_log.has_key? log_entry.tvar + if log_entry.tvar.unsafe_version > log_entry.version + return false + end + end + end + + true + end + + def unlock + @write_log.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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/engine.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/engine.rb new file mode 100644 index 0000000000..bc4173e448 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/engine.rb @@ -0,0 +1,56 @@ +module Concurrent + module Utility + + # @!visibility private + module EngineDetector + def on_jruby? + ruby_engine == 'jruby' + end + + def on_jruby_9000? + on_jruby? && ruby_version(JRUBY_VERSION, :>=, 9, 0, 0) + end + + def on_cruby? + ruby_engine == 'ruby' + end + + def on_rbx? + ruby_engine == 'rbx' + 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_engine + defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb new file mode 100644 index 0000000000..c9f4b369a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb @@ -0,0 +1,58 @@ +require 'concurrent/synchronization' + +module Concurrent + + class_definition = Class.new(Synchronization::LockableObject) do + def initialize + @last_time = Time.now.to_f + super() + end + + if defined?(Process::CLOCK_MONOTONIC) + # @!visibility private + def get_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + elsif Concurrent.on_jruby? + # @!visibility private + def get_time + java.lang.System.nanoTime() / 1_000_000_000.0 + end + else + + # @!visibility private + def get_time + synchronize do + now = Time.now.to_f + if @last_time < now + @last_time = now + else # clock has moved back in time + @last_time += 0.000_001 + end + end + end + + end + end + + # Clock that cannot be set and represents monotonic time since + # some unspecified starting point. + # + # @!visibility private + GLOBAL_MONOTONIC_CLOCK = class_definition.new + private_constant :GLOBAL_MONOTONIC_CLOCK + + # @!macro monotonic_get_time + # + # Returns the current time a tracked by the application monotonic clock. + # + # @return [Float] The current monotonic time since some unspecified + # starting point + # + # @!macro monotonic_clock_warning + def monotonic_time + GLOBAL_MONOTONIC_CLOCK.get_time + end + + module_function :monotonic_time +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb new file mode 100644 index 0000000000..a944bd7290 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb @@ -0,0 +1,79 @@ +require 'concurrent/utility/engine' + +module Concurrent + + 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 java_extensions_loaded? + defined?(@java_extensions_loaded) && @java_extensions_loaded + end + + def load_native_extensions + unless defined? Synchronization::AbstractObject + raise 'native_extension_loader loaded before Synchronization::AbstractObject' + end + + 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 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 + diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_integer.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_integer.rb new file mode 100644 index 0000000000..10719e7caa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/native_integer.rb @@ -0,0 +1,53 @@ +module Concurrent + 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.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/processor_counter.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/processor_counter.rb new file mode 100644 index 0000000000..531ca0a3c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/utility/processor_counter.rb @@ -0,0 +1,163 @@ +require 'etc' +require 'rbconfig' +require 'concurrent/delay' + +module Concurrent + module Utility + + # @!visibility private + class ProcessorCounter + def initialize + @processor_count = Delay.new { compute_processor_count } + @physical_processor_count = Delay.new { compute_physical_processor_count } + end + + # 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." Subsequently the result will NOT be + # memoized under JRuby. + # + # Ruby's Etc.nprocessors will be used if available (MRI 2.2+). + # + # On Windows the Win32 API will be queried for the + # `NumberOfLogicalProcessors from Win32_Processor`. This will return the + # total number "logical processors for the current instance of the + # processor", which taked into account hyperthreading. + # + # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev + # * Alpha: /usr/bin/nproc (/proc/cpuinfo exists but cannot be used) + # * BSD: /sbin/sysctl + # * Cygwin: /proc/cpuinfo + # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl + # * HP-UX: /usr/sbin/ioscan + # * IRIX: /usr/sbin/sysconf + # * Linux: /proc/cpuinfo + # * Minix 3+: /proc/cpuinfo + # * Solaris: /usr/sbin/psrinfo + # * Tru64 UNIX: /usr/sbin/psrinfo + # * UnixWare: /usr/sbin/psrinfo + # + # @return [Integer] number of processors seen by the OS or Java runtime + # + # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb + # + # @see http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html#availableProcessors() + # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx + def processor_count + @processor_count.value + 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 physical_processor_count + @physical_processor_count.value + end + + private + + def compute_processor_count + if Concurrent.on_jruby? + java.lang.Runtime.getRuntime.availableProcessors + elsif Etc.respond_to?(:nprocessors) && (nprocessor = Etc.nprocessors rescue nil) + nprocessor + else + os_name = RbConfig::CONFIG["target_os"] + if os_name =~ /mingw|mswin/ + require 'win32ole' + result = WIN32OLE.connect("winmgmts://").ExecQuery( + "select NumberOfLogicalProcessors from Win32_Processor") + result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+) + elsif File.readable?("/proc/cpuinfo") && (cpuinfo_count = IO.read("/proc/cpuinfo").scan(/^processor/).size) > 0 + cpuinfo_count + elsif File.executable?("/usr/bin/nproc") + IO.popen("/usr/bin/nproc --all", &:read).to_i + elsif File.executable?("/usr/bin/hwprefs") + IO.popen("/usr/bin/hwprefs thread_count", &:read).to_i + elsif File.executable?("/usr/sbin/psrinfo") + IO.popen("/usr/sbin/psrinfo", &:read).scan(/^.*on-*line/).size + elsif File.executable?("/usr/sbin/ioscan") + IO.popen("/usr/sbin/ioscan -kC processor", &:read).scan(/^.*processor/).size + elsif File.executable?("/usr/sbin/pmcycles") + IO.popen("/usr/sbin/pmcycles -m", &:read).count("\n") + elsif File.executable?("/usr/sbin/lsdev") + IO.popen("/usr/sbin/lsdev -Cc processor -S 1", &:read).count("\n") + elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i + IO.popen("/usr/sbin/sysconf NPROC_ONLN", &:read).to_i + elsif File.executable?("/usr/sbin/sysctl") + IO.popen("/usr/sbin/sysctl -n hw.ncpu", &:read).to_i + elsif File.executable?("/sbin/sysctl") + IO.popen("/sbin/sysctl -n hw.ncpu", &:read).to_i + else + # TODO (pitr-ch 05-Nov-2016): warn about failures + 1 + end + end + rescue + return 1 + end + + def compute_physical_processor_count + ppc = case RbConfig::CONFIG["target_os"] + when /darwin1/ + 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/ + require 'win32ole' + result_set = WIN32OLE.connect("winmgmts://").ExecQuery( + "select NumberOfCores from Win32_Processor") + result_set.to_enum.collect(&:NumberOfCores).reduce(:+) + else + processor_count + end + # fall back to logical count if physical info is invalid + ppc > 0 ? ppc : processor_count + rescue + return 1 + end + end + end + + # create the default ProcessorCounter on load + @processor_counter = Utility::ProcessorCounter.new + singleton_class.send :attr_reader, :processor_counter + + def self.processor_count + processor_counter.processor_count + end + + def self.physical_processor_count + processor_counter.physical_processor_count + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/version.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/version.rb new file mode 100644 index 0000000000..7bc7970be0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/version.rb @@ -0,0 +1,3 @@ +module Concurrent + VERSION = '1.1.9' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/CHANGELOG.md new file mode 100644 index 0000000000..17a2a64153 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/CHANGELOG.md @@ -0,0 +1,561 @@ +## Current + +## 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 longers 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 extenstions +* 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.0.0/gems/concurrent-ruby-1.2.2/Gemfile b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/Gemfile new file mode 100644 index 0000000000..b336031b75 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/Gemfile @@ -0,0 +1,36 @@ +source 'https://rubygems.org' + +require File.join(File.dirname(__FILE__), 'lib/concurrent-ruby/concurrent/version') +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby-edge/concurrent/edge/version') + +no_path = ENV['NO_PATH'] +options = no_path ? {} : { path: '.' } + +gem 'concurrent-ruby', Concurrent::VERSION, options +gem 'concurrent-ruby-edge', Concurrent::EDGE_VERSION, options +gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri) + +group :development do + gem 'rake', '~> 13.0' + gem 'rake-compiler', '~> 1.0', '>= 1.0.7' + 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.0.0/gems/concurrent-ruby-1.2.2/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/LICENSE.txt new file mode 100644 index 0000000000..1026f28d0b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/README.md b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/README.md new file mode 100644 index 0000000000..15f011b988 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/README.md @@ -0,0 +1,405 @@ +# 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_stdlib_logger(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) +* [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.0.0/gems/concurrent-ruby-1.2.2/Rakefile b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/Rakefile new file mode 100644 index 0000000000..f167f4659e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/Rakefile @@ -0,0 +1,319 @@ +require_relative 'lib/concurrent-ruby/concurrent/version' +require_relative 'lib/concurrent-ruby-edge/concurrent/edge/version' +require_relative 'lib/concurrent-ruby/concurrent/utility/engine' + +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'] && !Concurrent.on_jruby? + +Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' +end + +unless Concurrent.on_jruby? || Concurrent.on_truffleruby? + 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 + +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 + %w[x86-mingw32 x64-mingw32].each do |plat| + RakeCompilerDock.sh( + "bundle install --local && bundle exec rake native:#{plat} gem --trace", + platform: plat, + options: ['--privileged'], # otherwise the directory in the image is empty + runas: false) + 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 && !Concurrent.on_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-#{Concurrent::VERSION}.gem" + sh "gem install pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if Concurrent.on_cruby? + sh "gem install pkg/concurrent-ruby-edge-#{Concurrent::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 = Concurrent::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 + 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 + Dir.chdir(__dir__) do + puts "Testing with the installed gem" + + Bundler.with_original_env do + sh 'ruby -v' + sh 'bundle exec rake spec:installed' + + env = { "PATH" => "#{ENV['CONCURRENT_JRUBY_HOME']}/bin:#{ENV['PATH']}" } + sh env, 'ruby -v' + 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 = true + publish_edge = false + + 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 'It will publish `concurrent-ruby`. 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#{Concurrent::VERSION}" if publish_base + sh "git push origin v#{Concurrent::VERSION}" if publish_base + sh "git tag edge-v#{Concurrent::EDGE_VERSION}" if publish_edge + sh "git push origin edge-v#{Concurrent::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-#{Concurrent::VERSION}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" if publish_edge + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x64-mingw32.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java new file mode 100644 index 0000000000..fb6be96d37 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java new file mode 100644 index 0000000000..dfa9e7704e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java new file mode 100644 index 0000000000..a09f9162ee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java new file mode 100644 index 0000000000..b56607626c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java new file mode 100644 index 0000000000..672bfc048b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java new file mode 100644 index 0000000000..d887f25047 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java new file mode 100644 index 0000000000..f0c75ee407 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java new file mode 100644 index 0000000000..e11e15aa4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java new file mode 100644 index 0000000000..86aa4eb062 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 refering 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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java new file mode 100644 index 0000000000..47a923c8d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java new file mode 100644 index 0000000000..93a277fb35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java new file mode 100644 index 0000000000..b7fc5a9375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 refering 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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java new file mode 100644 index 0000000000..ecf552a23c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java new file mode 100644 index 0000000000..f52164242a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java new file mode 100644 index 0000000000..3ea409ffc4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent-ruby.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent-ruby.rb new file mode 100644 index 0000000000..e9a3dea4ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent.rb new file mode 100644 index 0000000000..87de46f1b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/agent.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/agent.rb new file mode 100644 index 0000000000..2d32926ba1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 timout 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 timout 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/array.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/array.rb new file mode 100644 index 0000000000..96434a288d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/async.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/async.rb new file mode 100644 index 0000000000..f9f8adf00d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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) + # + # Instanciate 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atom.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atom.rb new file mode 100644 index 0000000000..1074006d76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 (preferrably) 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb new file mode 100644 index 0000000000..f775691a2a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb new file mode 100644 index 0000000000..26cd05d869 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb new file mode 100644 index 0000000000..e16be65772 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb new file mode 100644 index 0000000000..bb5fb77459 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb new file mode 100644 index 0000000000..d883aed6f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb new file mode 100644 index 0000000000..9ebe29dd09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/event.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/event.rb new file mode 100644 index 0000000000..ccf84c9d1b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb new file mode 100644 index 0000000000..e90fc24f9e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb new file mode 100644 index 0000000000..3c119bc32c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/locals.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/locals.rb new file mode 100644 index 0000000000..0a276aedd5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb new file mode 100644 index 0000000000..ebf23a2414 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb new file mode 100644 index 0000000000..015996b06f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb new file mode 100644 index 0000000000..0ca395579f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb new file mode 100644 index 0000000000..29aa1caa4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb new file mode 100644 index 0000000000..4347289f1e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb new file mode 100644 index 0000000000..b26bd17a08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb new file mode 100644 index 0000000000..6d72a3a097 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/semaphore.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/semaphore.rb new file mode 100644 index 0000000000..f0799f0f41 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb new file mode 100644 index 0000000000..3b7e12b5bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb new file mode 100644 index 0000000000..5d2d7edd4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb new file mode 100644 index 0000000000..e5e2a6377d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb new file mode 100644 index 0000000000..709a382231 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomics.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomics.rb new file mode 100644 index 0000000000..16cbe66101 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb new file mode 100644 index 0000000000..7c700bd78a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb new file mode 100644 index 0000000000..bcb6750d41 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..2be9e4373a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb new file mode 100644 index 0000000000..3704410ba0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb new file mode 100644 index 0000000000..dc5189389d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb @@ -0,0 +1,927 @@ +require 'concurrent/constants' +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/adder' +require 'concurrent/thread_safe/util/cheap_lockable' +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 Collection + + # A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59 + # + # The Ruby port skips out the +TreeBin+ (red-black trees for use in bins whose + # size exceeds a threshold). + # + # A hash table supporting full concurrency of retrievals and high expected + # concurrency for updates. 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. + # + # Retrieval operations generally do not block, so may overlap with update + # operations. 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 + # +nil+) retrieval for that key reporting the updated value.) For aggregate + # operations such as +clear()+, concurrent retrievals may reflect insertion or + # removal of only some entries. Similarly, the +each_pair+ iterator yields + # elements reflecting the state of the hash table at some point at or since + # the start of the +each_pair+. Bear in mind that the results of aggregate + # status methods including +size()+ and +empty?+} 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 :initial_capacity + # initializer argument. An additional optional :load_factor 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. Note that using many keys with + # exactly the same +hash+ is a sure way to slow down performance of any hash + # table. + # + # ## Design overview + # + # The primary design goal of this hash table is to maintain concurrent + # readability (typically method +[]+, but also iteration and related methods) + # while minimizing update contention. Secondary goals are to keep space + # consumption about the same or better than plain +Hash+, and to support high + # initial insertion rates on an empty table by many threads. + # + # Each key-value mapping is held in a +Node+. 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 +Node+s (most + # often, the list has only zero or one +Node+). Table accesses require + # volatile/atomic reads, writes, and CASes. 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 28 bits of each +Node+'s hash field contain a 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 +[]=+ 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 +Concurrent::ThreadSafe::Util::CheapLockable. However, we also need a + # +try_lock+ 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 + # +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+. + # + # 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 +eql?+ 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. + # + # 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 +size_control+, 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. 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.size - 1+) up + # towards the first. Upon seeing a forwarding node, traversals 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. + # + # The element count is maintained using a +Concurrent::ThreadSafe::Util::Adder+, + # 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 +x_if_absent+ 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. + # + # @!visibility private + class AtomicReferenceMapBackend + + # @!visibility private + class Table < Concurrent::ThreadSafe::Util::PowerOfTwoTuple + def cas_new_node(i, hash, key, value) + cas(i, nil, Node.new(hash, key, value)) + end + + def try_to_cas_in_computed(i, hash, key) + succeeded = false + new_value = nil + new_node = Node.new(locked_hash = hash | LOCKED, key, NULL) + if cas(i, nil, new_node) + begin + if NULL == (new_value = yield(NULL)) + was_null = true + else + new_node.value = new_value + end + succeeded = true + ensure + volatile_set(i, nil) if !succeeded || was_null + new_node.unlock_via_hash(locked_hash, hash) + end + end + return succeeded, new_value + end + + def try_lock_via_hash(i, node, node_hash) + node.try_lock_via_hash(node_hash) do + yield if volatile_get(i) == node + end + end + + def delete_node_at(i, node, predecessor_node) + if predecessor_node + predecessor_node.next = node.next + else + volatile_set(i, node.next) + end + end + end + + # Key-value entry. Nodes with a hash field of +MOVED+ are special, and do + # not contain user keys or values. Otherwise, keys are never +nil+, and + # +NULL+ +value+ 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 value, but can only be used after checking value to be +!= NULL+. + # + # @!visibility private + class Node + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :hash, :value, :next + + include Concurrent::ThreadSafe::Util::CheapLockable + + bit_shift = Concurrent::ThreadSafe::Util::FIXNUM_BIT_SIZE - 2 # need 2 bits for ourselves + # Encodings for special uses of Node hash fields. See above for explanation. + MOVED = ('10' << ('0' * bit_shift)).to_i(2) # hash field for forwarding nodes + LOCKED = ('01' << ('0' * bit_shift)).to_i(2) # set/tested only as a bit + WAITING = ('11' << ('0' * bit_shift)).to_i(2) # both bits set/tested together + HASH_BITS = ('00' << ('1' * bit_shift)).to_i(2) # usable bits of normal node hash + + SPIN_LOCK_ATTEMPTS = Concurrent::ThreadSafe::Util::CPU_COUNT > 1 ? Concurrent::ThreadSafe::Util::CPU_COUNT * 2 : 0 + + attr_reader :key + + def initialize(hash, key, value, next_node = nil) + super() + @key = key + self.lazy_set_hash(hash) + self.lazy_set_value(value) + self.next = next_node + end + + # 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 + # +cheap_synchronize+ lock and perform a +cheap_broadcast+. + def try_await_lock(table, i) + if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking? + spins = SPIN_LOCK_ATTEMPTS + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.get + while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash) + if spins >= 0 + if (randomizer = (randomizer >> 1)).even? # spin at random + if (spins -= 1) == 0 + Thread.pass # yield before blocking + else + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? + end + end + elsif cas_hash(my_hash, my_hash | WAITING) + force_acquire_lock(table, i) + break + end + end + end + end + + def key?(key) + @key.eql?(key) + end + + def matches?(key, hash) + pure_hash == hash && key?(key) + end + + def pure_hash + hash & HASH_BITS + end + + def try_lock_via_hash(node_hash = hash) + if cas_hash(node_hash, locked_hash = node_hash | LOCKED) + begin + yield + ensure + unlock_via_hash(locked_hash, node_hash) + end + end + end + + def locked? + self.class.locked_hash?(hash) + end + + def unlock_via_hash(locked_hash, node_hash) + unless cas_hash(locked_hash, node_hash) + self.hash = node_hash + cheap_synchronize { cheap_broadcast } + end + end + + private + def force_acquire_lock(table, i) + cheap_synchronize do + if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING + cheap_wait + else + cheap_broadcast # possibly won race vs signaller + end + end + end + + class << self + def locked_hash?(hash) + (hash & LOCKED) != 0 + end + end + end + + # shorthands + MOVED = Node::MOVED + LOCKED = Node::LOCKED + WAITING = Node::WAITING + HASH_BITS = Node::HASH_BITS + + NOW_RESIZING = -1 + DEFAULT_CAPACITY = 16 + MAX_CAPACITY = Concurrent::ThreadSafe::Util::MAX_INT + + # The buffer size for skipped bins during transfers. The + # value is arbitrary but should be large enough to avoid + # most locking stalls during resizes. + TRANSFER_BUFFER_SIZE = 32 + + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two. + + # 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. + :size_control + + def initialize(options = nil) + super() + @counter = Concurrent::ThreadSafe::Util::Adder.new + initial_capacity = options && options[:initial_capacity] || DEFAULT_CAPACITY + self.size_control = (capacity = table_size_for(initial_capacity)) > MAX_CAPACITY ? MAX_CAPACITY : capacity + end + + def get_or_default(key, else_value = nil) + hash = key_hash(key) + current_table = table + while current_table + node = current_table.volatile_get_by_hash(hash) + current_table = + while node + if (node_hash = node.hash) == MOVED + break node.key + elsif (node_hash & HASH_BITS) == hash && node.key?(key) && NULL != (value = node.value) + return value + end + node = node.next + end + end + else_value + end + + def [](key) + get_or_default(key) + end + + def key?(key) + get_or_default(key, NULL) != NULL + end + + def []=(key, value) + get_and_set(key, value) + value + end + + def compute_if_absent(key) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key) { yield } + if succeeded + increment_size + return new_value + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif NULL != (current_value = find_value_in_node_list(node, key, hash, node_hash & HASH_BITS)) + return current_value + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, value = attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) { yield } + return value if succeeded + end + end + end + + def compute_if_present(key) + new_value = nil + internal_replace(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + new_value + end + + def compute(key) + internal_compute(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + end + + def merge_pair(key, value) + internal_compute(key) do |old_value| + if NULL == old_value || !(value = yield(old_value)).nil? + value + else + NULL + end + end + end + + def replace_pair(key, old_value, new_value) + NULL != internal_replace(key, old_value) { new_value } + end + + def replace_if_exists(key, new_value) + if (result = internal_replace(key) { new_value }) && NULL != result + result + end + end + + def get_and_set(key, value) # internalPut in the original CHMV8 + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + if current_table.cas_new_node(i, hash, key, value) + increment_size + break + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + break old_value if succeeded + end + end + end + + def delete(key) + replace_if_exists(key, NULL) + end + + def delete_pair(key, value) + result = internal_replace(key, value) { NULL } + if result && NULL != result + !!result + else + false + end + end + + def each_pair + return self unless current_table = table + current_table_size = base_size = current_table.size + i = base_index = 0 + while base_index < base_size + if node = current_table.volatile_get(i) + if node.hash == MOVED + current_table = node.key + current_table_size = current_table.size + else + begin + if NULL != (value = node.value) # skip deleted or special nodes + yield node.key, value + end + end while node = node.next + end + end + + if (i_with_base = i + base_size) < current_table_size + i = i_with_base # visit upper slots if present + else + i = base_index += 1 + end + end + self + end + + def size + (sum = @counter.sum) < 0 ? 0 : sum # ignore transient negative values + end + + def empty? + size == 0 + end + + # Implementation for clear. Steps through each bin, removing all nodes. + def clear + return self unless current_table = table + current_table_size = current_table.size + deleted_count = i = 0 + while i < current_table_size + if !(node = current_table.volatile_get(i)) + i += 1 + elsif (node_hash = node.hash) == MOVED + current_table = node.key + current_table_size = current_table.size + elsif Node.locked_hash?(node_hash) + decrement_size(deleted_count) # opportunistically update count + deleted_count = 0 + node.try_await_lock(current_table, i) + else + current_table.try_lock_via_hash(i, node, node_hash) do + begin + deleted_count += 1 if NULL != node.value # recheck under lock + node.value = nil + end while node = node.next + current_table.volatile_set(i, nil) + i += 1 + end + end + end + decrement_size(deleted_count) + self + end + + private + # Internal versions of the insertion methods, each a + # little more complicated than the last. All have + # the same basic structure: + # 1. If table uninitialized, create + # 2. If bin empty, try to CAS new node + # 3. If bin stale, use new table + # 4. Lock and validate; if valid, scan and add or update + # + # The others interweave other checks and/or alternative actions: + # * Plain +get_and_set+ checks for and performs resize after insertion. + # * compute_if_absent prescans for mapping without lock (and fails to add + # if present), which also makes pre-emptive resize checks worthwhile. + # + # Someday when details settle down a bit more, it might be worth + # some factoring to reduce sprawl. + def internal_replace(key, expected_old_value = NULL, &block) + hash = key_hash(key) + current_table = table + while current_table + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + break + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif (node_hash & HASH_BITS) != hash && !node.next # precheck + break # rules out possible existence + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash, &block) + return old_value if succeeded + end + end + NULL + end + + def attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash) + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + old_value = NULL + begin + if node.matches?(key, hash) && NULL != (current_value = node.value) + if NULL == expected_old_value || expected_old_value == current_value # NULL == expected_old_value means whatever value + old_value = current_value + if NULL == (node.value = yield(old_value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + end + end + break + end + + predecessor_node = node + end while node = node.next + + return true, old_value + end + end + + def find_value_in_node_list(node, key, hash, pure_hash) + do_check_for_resize = false + while true + if pure_hash == hash && node.key?(key) && NULL != (value = node.value) + return value + elsif node = node.next + do_check_for_resize = true # at least 2 nodes -> check for resize + pure_hash = node.pure_hash + else + return NULL + end + end + ensure + check_for_resize if do_check_for_resize + end + + def internal_compute(key, &block) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key, &block) + if succeeded + if NULL == new_value + break nil + else + increment_size + break new_value + end + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, new_value = attempt_compute(key, hash, current_table, i, node, node_hash, &block) + break new_value if succeeded + end + end + end + + def attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + while true + if node.matches?(key, hash) && NULL != (value = node.value) + return true, value + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value = yield) + added = true + increment_size + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_compute(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + while true + if node.matches?(key, hash) && NULL != (value = node.value) + if NULL == (node.value = value = yield(value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + value = nil + end + return true, value + end + predecessor_node = node + unless node = node.next + if NULL == (value = yield(NULL)) + value = nil + else + predecessor_node.next = Node.new(hash, key, value) + added = true + increment_size + end + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + node_nesting = nil + current_table.try_lock_via_hash(i, node, node_hash) do + node_nesting = 1 + old_value = nil + found_old_value = false + while node + if node.matches?(key, hash) && NULL != (old_value = node.value) + found_old_value = true + node.value = value + break + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value) + break + end + node_nesting += 1 + end + + return true, old_value if found_old_value + increment_size + true + end + ensure + check_for_resize if node_nesting && (node_nesting > 1 || current_table.size <= 64) + end + + def initialize_copy(other) + super + @counter = Concurrent::ThreadSafe::Util::Adder.new + self.table = nil + self.size_control = (other_table = other.table) ? other_table.size : DEFAULT_CAPACITY + self + end + + def try_await_lock(current_table, i, node) + check_for_resize # try resizing if can't get lock + node.try_await_lock(current_table, i) + end + + def key_hash(key) + key.hash & HASH_BITS + end + + # Returns a power of two table size for the given desired capacity. + def table_size_for(entry_count) + size = 2 + size <<= 1 while size < entry_count + size + end + + # Initializes table, using the size recorded in +size_control+. + def initialize_table + until current_table ||= table + if (size_ctrl = size_control) == NOW_RESIZING + Thread.pass # lost initialization race; just spin + else + try_in_resize_lock(current_table, size_ctrl) do + initial_size = size_ctrl > 0 ? size_ctrl : DEFAULT_CAPACITY + current_table = self.table = Table.new(initial_size) + initial_size - (initial_size >> 2) # 75% load factor + end + end + end + current_table + end + + # 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. + def check_for_resize + while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum + try_in_resize_lock(current_table, size_ctrl) do + self.table = rebuild(current_table) + (table_size << 1) - (table_size >> 1) # 75% load factor + end + end + end + + def try_in_resize_lock(current_table, size_ctrl) + if cas_size_control(size_ctrl, NOW_RESIZING) + begin + if current_table == table # recheck under lock + size_ctrl = yield # get new size_control + end + ensure + self.size_control = size_ctrl + end + end + end + + # Moves and/or copies the nodes in each bin to new table. See above for explanation. + def rebuild(table) + old_table_size = table.size + new_table = table.next_in_size_table + # puts "#{old_table_size} -> #{new_table.size}" + forwarder = Node.new(MOVED, new_table, NULL) + rev_forwarder = nil + locked_indexes = nil # holds bins to revisit; nil until needed + locked_arr_idx = 0 + bin = old_table_size - 1 + i = bin + while true + if !(node = table.volatile_get(i)) + # no lock needed (or available) if bin >= 0, because we're not popping values from locked_indexes until we've run through the whole table + redo unless (bin >= 0 ? table.cas(i, nil, forwarder) : lock_and_clean_up_reverse_forwarders(table, old_table_size, new_table, i, forwarder)) + elsif Node.locked_hash?(node_hash = node.hash) + locked_indexes ||= ::Array.new + if bin < 0 && locked_arr_idx > 0 + locked_arr_idx -= 1 + i, locked_indexes[locked_arr_idx] = locked_indexes[locked_arr_idx], i # swap with another bin + redo + end + if bin < 0 || locked_indexes.size >= TRANSFER_BUFFER_SIZE + node.try_await_lock(table, i) # no other options -- block + redo + end + rev_forwarder ||= Node.new(MOVED, table, NULL) + redo unless table.volatile_get(i) == node && node.locked? # recheck before adding to list + locked_indexes << i + new_table.volatile_set(i, rev_forwarder) + new_table.volatile_set(i + old_table_size, rev_forwarder) + else + redo unless split_old_bin(table, new_table, i, node, node_hash, forwarder) + end + + if bin > 0 + i = (bin -= 1) + elsif locked_indexes && !locked_indexes.empty? + bin = -1 + i = locked_indexes.pop + locked_arr_idx = locked_indexes.size - 1 + else + return new_table + end + end + end + + def lock_and_clean_up_reverse_forwarders(old_table, old_table_size, new_table, i, forwarder) + # transiently use a locked forwarding node + locked_forwarder = Node.new(moved_locked_hash = MOVED | LOCKED, new_table, NULL) + if old_table.cas(i, nil, locked_forwarder) + new_table.volatile_set(i, nil) # kill the potential reverse forwarders + new_table.volatile_set(i + old_table_size, nil) # kill the potential reverse forwarders + old_table.volatile_set(i, forwarder) + locked_forwarder.unlock_via_hash(moved_locked_hash, MOVED) + true + end + end + + # Splits a normal bin with list headed by e into lo and hi parts; installs in given table. + def split_old_bin(table, new_table, i, node, node_hash, forwarder) + table.try_lock_via_hash(i, node, node_hash) do + split_bin(new_table, i, node, node_hash) + table.volatile_set(i, forwarder) + end + end + + def split_bin(new_table, i, node, node_hash) + bit = new_table.size >> 1 # bit to split on + run_bit = node_hash & bit + last_run = nil + low = nil + high = nil + current_node = node + # this optimises for the lowest amount of volatile writes and objects created + while current_node = current_node.next + unless (b = current_node.hash & bit) == run_bit + run_bit = b + last_run = current_node + end + end + if run_bit == 0 + low = last_run + else + high = last_run + end + current_node = node + until current_node == last_run + pure_hash = current_node.pure_hash + if (pure_hash & bit) == 0 + low = Node.new(pure_hash, current_node.key, current_node.value, low) + else + high = Node.new(pure_hash, current_node.key, current_node.value, high) + end + current_node = current_node.next + end + new_table.volatile_set(i, low) + new_table.volatile_set(i + bit, high) + end + + def increment_size + @counter.increment + end + + def decrement_size(by = 1) + @counter.add(-by) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb new file mode 100644 index 0000000000..e0cf9990c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb new file mode 100644 index 0000000000..ca5fd9b48e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb new file mode 100644 index 0000000000..190c8d98d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb @@ -0,0 +1,82 @@ +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class SynchronizedMapBackend < NonConcurrentMapBackend + + require 'mutex_m' + include Mutex_m + # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are + # not allowed to call each other. + + def [](key) + synchronize { super } + end + + def []=(key, value) + synchronize { super } + end + + def compute_if_absent(key) + synchronize { super } + end + + def compute_if_present(key) + synchronize { super } + end + + def compute(key) + synchronize { super } + end + + def merge_pair(key, value) + synchronize { super } + end + + def replace_pair(key, old_value, new_value) + synchronize { super } + end + + def replace_if_exists(key, new_value) + synchronize { super } + end + + def get_and_set(key, value) + synchronize { super } + end + + def key?(key) + synchronize { super } + end + + def delete(key) + synchronize { super } + end + + def delete_pair(key, value) + synchronize { super } + end + + def clear + synchronize { super } + end + + def size + synchronize { super } + end + + def get_or_default(key, default_value) + synchronize { super } + end + + private + def dupped_backend + synchronize { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb new file mode 100644 index 0000000000..68a1b3884d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..694cd7ac7c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..322b4ac2d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/deprecation.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/deprecation.rb new file mode 100644 index 0000000000..35ae4b2c9d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb new file mode 100644 index 0000000000..dc172ba74d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/logging.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/logging.rb new file mode 100644 index 0000000000..568a539ebf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/logging.rb @@ -0,0 +1,116 @@ +require 'logger' +require 'concurrent/atomic/atomic_reference' + +module Concurrent + module Concern + + # Include where logging is needed + # + # @!visibility private + module Logging + include Logger::Severity + + # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger + # @param [Integer] level one of Logger::Severity 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.configuration.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 + + # @return [Logger] Logger with provided level and output. + def self.create_simple_logger(level = Logger::FATAL, output = $stderr) + # 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'), + Logger::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 = Logger::FATAL, output = $stderr) + Concurrent.global_logger = create_simple_logger level, output + end + + # @return [Logger] Logger with provided level and output. + # @deprecated + def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr) + 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 = Logger::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(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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/obligation.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/obligation.rb new file mode 100644 index 0000000000..2c9ac12003 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/observable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concern/observable.rb new file mode 100644 index 0000000000..b5132714bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concurrent_ruby.jar b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concurrent_ruby.jar new file mode 100644 index 0000000000..a4bda41281 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/concurrent_ruby.jar differ diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/configuration.rb new file mode 100644 index 0000000000..5571d39b0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/constants.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/constants.rb new file mode 100644 index 0000000000..676c2afb9a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/dataflow.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/dataflow.rb new file mode 100644 index 0000000000..d55f19d850 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/delay.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/delay.rb new file mode 100644 index 0000000000..923773cbca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 opration 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/errors.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/errors.rb new file mode 100644 index 0000000000..74f1fc3dda --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/exchanger.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/exchanger.rb new file mode 100644 index 0000000000..a5405d2522 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb new file mode 100644 index 0000000000..ac429531bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb new file mode 100644 index 0000000000..de50ed1791 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/executor_service.rb new file mode 100644 index 0000000000..7e344919e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb new file mode 100644 index 0000000000..4de512a5ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb @@ -0,0 +1,220 @@ +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_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 explicitely 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb new file mode 100644 index 0000000000..282df7a059 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb new file mode 100644 index 0000000000..4f9769fa3f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb new file mode 100644 index 0000000000..9a86385520 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb @@ -0,0 +1,103 @@ +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? + if @executor.respond_to? :isTerminating + @executor.isTerminating + else + false + end + end + + def ns_shutdown? + @executor.isShutdown || @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 + end + + def newThread(runnable) + thread = java.util.concurrent.Executors.defaultThreadFactory().newThread(runnable) + thread.setDaemon(@daemonize) + return thread + end + end + + private_constant :DaemonThreadFactory + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb new file mode 100644 index 0000000000..7aa24f2d72 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb new file mode 100644 index 0000000000..1213a95fb0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb @@ -0,0 +1,140 @@ +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_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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb new file mode 100644 index 0000000000..1f7301b947 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb new file mode 100644 index 0000000000..916337d4ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb new file mode 100644 index 0000000000..298dd7fed0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb @@ -0,0 +1,366 @@ +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 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb new file mode 100644 index 0000000000..f796b8571f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb new file mode 100644 index 0000000000..f1c38ecfa9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb new file mode 100644 index 0000000000..4db7c7f0c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb new file mode 100644 index 0000000000..8197781b52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb new file mode 100644 index 0000000000..0bc62afd38 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb new file mode 100644 index 0000000000..f1474ea9ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 bahaviors. 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb new file mode 100644 index 0000000000..253d46a9d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/timer_set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/timer_set.rb new file mode 100644 index 0000000000..0dfaf1288c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/timer_set.rb @@ -0,0 +1,172 @@ +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/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 } + task.executor.post { task.process_task } + else + @condition.wait([diff, 60].min) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executors.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executors.rb new file mode 100644 index 0000000000..eb1972ce69 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/future.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/future.rb new file mode 100644 index 0000000000..1af182ecb2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/hash.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/hash.rb new file mode 100644 index 0000000000..7902fe9d29 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/hash.rb @@ -0,0 +1,50 @@ +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 thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/immutable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/immutable_struct.rb new file mode 100644 index 0000000000..48462e8375 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/ivar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/ivar.rb new file mode 100644 index 0000000000..4165038f89 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/map.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/map.rb new file mode 100644 index 0000000000..1b22241954 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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/atomic_reference_map_backend' + AtomicReferenceMapBackend + 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 incorrent +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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/maybe.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/maybe.rb new file mode 100644 index 0000000000..317c82b86f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/mutable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/mutable_struct.rb new file mode 100644 index 0000000000..5d0e9b9af5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/mvar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/mvar.rb new file mode 100644 index 0000000000..dfc41950cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/options.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/options.rb new file mode 100644 index 0000000000..bdd22a9df1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/promise.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/promise.rb new file mode 100644 index 0000000000..ccc47dd628 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 intialization 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/promises.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/promises.rb new file mode 100644 index 0000000000..3cd17055ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/promises.rb @@ -0,0 +1,2168 @@ +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' + +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 @yeild 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 + + # Created resolvable event, user is responsible for resolving the event once by + # {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 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 resolved future with will be either fulfilled with the given value or rejection 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 resolved future with 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 resolved future with 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 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 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 new future which is resolved after all futures_and_or_events are resolved. + # Its value is array of zipped future values. Its reason is 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 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 new future which is resolved after first futures_and_or_events is resolved. + # Its result equals 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 new future which is resolved after first of futures_and_or_events is fulfilled. + # Its result equals result of the first resolved future or if all futures_and_or_events reject, + # it has reason of the last resolved 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 new event which becomes resolved after first of the 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, @DefaultExecutor, 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 + unless resolved? + @Condition.wait @Lock, timeout + 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, @DefaultExecutor, 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, @DefaultExecutor, 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/re_include.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/re_include.rb new file mode 100644 index 0000000000..600bc6a535 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/scheduled_task.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/scheduled_task.rb new file mode 100644 index 0000000000..429fc0683c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 instanciation. + # + # @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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/set.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/set.rb new file mode 100644 index 0000000000..eee4effdfd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/settable_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/settable_struct.rb new file mode 100644 index 0000000000..99b85619fd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization.rb new file mode 100644 index 0000000000..6d8cf4bd58 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb new file mode 100644 index 0000000000..d9050b312f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb new file mode 100644 index 0000000000..7cd2decf99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb new file mode 100644 index 0000000000..1fe90c1649 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/condition.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/condition.rb new file mode 100644 index 0000000000..5daa68be8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb new file mode 100644 index 0000000000..139e08d854 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb new file mode 100644 index 0000000000..76930461bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/lock.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/lock.rb new file mode 100644 index 0000000000..f90e0b5f76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb new file mode 100644 index 0000000000..08d2ff66cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb new file mode 100644 index 0000000000..acc9745a2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/object.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/object.rb new file mode 100644 index 0000000000..e839c9f188 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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 oly 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb new file mode 100644 index 0000000000..f785e35229 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/volatile.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/synchronization/volatile.rb new file mode 100644 index 0000000000..46e8ba6a48 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb new file mode 100644 index 0000000000..019d84382d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util.rb new file mode 100644 index 0000000000..c67084a26f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb new file mode 100644 index 0000000000..7a6e8d5c0e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.LondAdder 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb new file mode 100644 index 0000000000..a07678df2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb @@ -0,0 +1,81 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/volatile' +require 'concurrent/utility/engine' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ + # with the +ConditionVariable+ bundled in. + # + # Usage: + # class A + # include CheapLockable + # + # def do_exlusively + # cheap_synchronize { yield } + # end + # + # def wait_for_something + # cheap_synchronize do + # cheap_wait until resource_available? + # do_something + # cheap_broadcast # wake up others + # end + # end + # end + # + # @!visibility private + module CheapLockable + private + if Concurrent.on_jruby? + # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects + require 'jruby' + + def cheap_synchronize + JRuby.reference0(self).synchronized { yield } + end + + def cheap_wait + JRuby.reference0(self).wait + end + + def cheap_broadcast + JRuby.reference0(self).notify_all + end + else + require 'thread' + + extend Volatile + attr_volatile :mutex + + # Non-reentrant Mutex#syncrhonize + def cheap_synchronize + true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new) + my_mutex.synchronize { yield } + end + + # Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup. + # Must only be called in +cheap_broadcast+'s block. + def cheap_wait + conditional_variable = @conditional_variable ||= ConditionVariable.new + conditional_variable.wait(mutex) + end + + # Wakes up all threads waiting for this object's +cheap_synchronize+ lock. + # Must only be called in +cheap_broadcast+'s block. + def cheap_broadcast + if conditional_variable = @conditional_variable + conditional_variable.broadcast + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb new file mode 100644 index 0000000000..01eb98f4aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb new file mode 100644 index 0000000000..b54be39c4c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb new file mode 100644 index 0000000000..4169c3d366 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb new file mode 100644 index 0000000000..cdac2a396a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb new file mode 100644 index 0000000000..bdde2dd8b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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-localy 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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/timer_task.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/timer_task.rb new file mode 100644 index 0000000000..b69cfc8d8a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/timer_task.rb @@ -0,0 +1,311 @@ +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. + # + # 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 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.times{ print 'Boom! ' } + # print "\n" + # task.execution_interval += 1 + # if task.execution_interval > 5 + # puts 'Stopping...' + # task.shutdown + # end + # end + # + # timer_task.execute # blocking call - this task will stop itself + # #=> 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 + + # Default `:timeout_interval` in seconds. + TIMEOUT_INTERVAL = 30 + + # Create a new TimerTask with the given task and configuration. + # + # @!macro timer_task_initialize + # @param [Hash] opts the options defining task execution. + # @option opts [Integer] :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) + # + # @!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 [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[: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] + @executor = Concurrent::SafeTaskExecutor.new(task) + @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, args: [Concurrent::Event.new], &method(:execute_task)) + nil + end + + # @!visibility private + def execute_task(completion) + return nil unless @running.true? + _success, value, reason = @executor.execute(self) + if completion.try? + self.value = value + schedule_next_task + time = Time.now + observers.notify_observers do + [time, self.value, reason] + end + end + nil + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/tuple.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/tuple.rb new file mode 100644 index 0000000000..56212cfd15 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/tvar.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/tvar.rb new file mode 100644 index 0000000000..5d02ef090f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/engine.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/engine.rb new file mode 100644 index 0000000000..0c574b2abb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb new file mode 100644 index 0000000000..1c987d8a41 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb new file mode 100644 index 0000000000..bf7bab354e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/native_integer.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/native_integer.rb new file mode 100644 index 0000000000..de1cdc306a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/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.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/processor_counter.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/processor_counter.rb new file mode 100644 index 0000000000..986e2d5231 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/utility/processor_counter.rb @@ -0,0 +1,110 @@ +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 } + end + + def processor_count + @processor_count.value + end + + def physical_processor_count + @physical_processor_count.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/ + require 'win32ole' + result_set = WIN32OLE.connect("winmgmts://").ExecQuery( + "select NumberOfCores from Win32_Processor") + result_set.to_enum.collect(&:NumberOfCores).reduce(:+) + else + processor_count + end + # fall back to logical count if physical info is invalid + ppc > 0 ? ppc : processor_count + rescue + return 1 + 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." Subsequently the result will NOT be + # memoized 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 +end diff --git a/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/version.rb b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/version.rb new file mode 100644 index 0000000000..d1c098956a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/version.rb @@ -0,0 +1,3 @@ +module Concurrent + VERSION = '1.2.2' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/LICENSE b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/LICENSE new file mode 100644 index 0000000000..bbd76fd03e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2005-2021 Thomas Uehlinger, 2014-2016 Aaron Stone + +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.0.0/gems/daemons-1.4.0/README.md b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/README.md new file mode 100644 index 0000000000..d8b4fa528b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/README.md @@ -0,0 +1,212 @@ +Ruby Daemons +============ +[![Build Status](https://travis-ci.org/thuehlinger/daemons.svg?branch=master)](https://travis-ci.org/thuehlinger/daemons)[![Code Climate](https://codeclimate.com/github/acuppy/daemons/badges/gpa.svg)](https://codeclimate.com/github/acuppy/daemons)[![Test Coverage](https://circleci.com/gh/acuppy/daemons.svg?style=shield&circle-token=a4f96fd41f7682661d6543e30207427ac8870c0d)](https://circleci.com/gh/acuppy/daemons) + +Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server) +to be _run as a daemon_ and to be _controlled by simple start/stop/restart commands_. + +If you want, you can also use daemons to _run blocks of ruby code in a daemon process_ and to control +these processes from the main application. + +Besides this basic functionality, daemons offers many advanced features like _exception backtracing_ +and logging (in case your ruby script crashes) and _monitoring_ and automatic restarting of your processes +if they crash. + +Basic Usage +----------- + +You can use Daemons in four different ways: + +### 1. Create wrapper scripts for your server scripts or applications + +Layout: suppose you have your self-written server `myserver.rb`: + +``` ruby +# this is myserver.rb +# it does nothing really useful at the moment + +loop do + sleep(5) +end +``` + +To use `myserver.rb` in a production environment, you need to be able to +run `myserver.rb` in the _background_ (this means detach it from the console, fork it +in the background, release all directories and file descriptors). + +Just create `myserver_control.rb` like this: + +``` ruby +# this is myserver_control.rb +require 'daemons' + +Daemons.run('myserver.rb') +``` + +And use it like this from the console: + +``` ruby +$ ruby myserver_control.rb start + (myserver.rb is now running in the background) +$ ruby myserver_control.rb restart + (...) +$ ruby myserver_control.rb stop +``` + +For testing purposes you can even run `myserver.rb` _without forking_ in the background: + +``` ruby +$ ruby myserver_control.rb run +``` + +An additional nice feature of Daemons is that you can pass _additional arguments_ to the script that +should be daemonized by seperating them by two _hyphens_: + +``` ruby +$ ruby myserver_control.rb start -- --file=anyfile --a_switch another_argument +``` + + +### 2. Create wrapper scripts that include your server procs + +Layout: suppose you have some code you want to run in the background and control that background process +from a script: + +``` ruby +# this is your code +# it does nothing really useful at the moment + +loop do + sleep(5) +end +``` + +To run this code as a daemon create `myproc_control.rb` like this and include your code: + +``` ruby +# this is myproc_control.rb +require 'daemons' + +Daemons.run_proc('myproc.rb') do + loop do + sleep(5) + end +end +``` + +And use it like this from the console: + +``` ruby +$ ruby myproc_control.rb start + (myproc.rb is now running in the background) +$ ruby myproc_control.rb restart + (...) +$ ruby myproc_control.rb stop +``` + +For testing purposes you can even run `myproc.rb` _without forking_ in the background: + +``` ruby +$ ruby myproc_control.rb run +``` + +### 3. Control a bunch of daemons from another application + +Layout: you have an application `my_app.rb` that wants to run a bunch of +server tasks as daemon processes. + +``` ruby +# this is my_app.rb +require 'daemons' + +task1 = Daemons.call(:multiple => true) do + # first server task + + loop do + conn = accept_conn() + serve(conn) + end +end + +task2 = Daemons.call do + # second server task + + loop do + something_different() + end +end + +# the parent process continues to run + +# we can even control our tasks, for example stop them +task1.stop +task2.stop + +exit +``` + +### 4. Daemonize the currently running process + +Layout: you have an application `my_daemon.rb` that wants to run as a daemon +(but without the ability to be controlled by daemons via start/stop commands) + +``` ruby +# this is my_daemons.rb +require 'daemons' + +# Initialize the app while we're not a daemon +init() + +# Become a daemon +Daemons.daemonize + +# The server loop +loop do + conn = accept_conn() + serve(conn) +end +``` + +For further documentation, refer to the module documentation of Daemons. + +Displaying daemon status +------------------------ + +When daemonizing a process using a wrapper script, as examples 1 and 2 above, +the status can be shown using + +``` ruby +$ ruby myproc_control.rb status +``` + +By default this will display whether or not the daemon is running and, if it +is, its PID. + +A custom message can be shown with + +``` ruby +def custom_show_status(app) + # Display the default status information + app.default_show_status + + puts + puts "PS information" + system("ps -p #{app.pid.pid.to_s}") + + puts + puts "Size of log files" + system("du -hs /path/to/logs") +end + +Daemons.run('myserver.rb', { show_status_callback: :custom_show_status }) +``` + +Documentation +------------------- + +Documentation can be found at http://www.rubydoc.info/gems/daemons. + +Author +------ + +Written 2005-2021 by Thomas Uehlinger, 2014-2016 by Aaron Stone. diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/Releases b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/Releases new file mode 100644 index 0000000000..732cf7fbfb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/Releases @@ -0,0 +1,251 @@ += Daemons Release History + +== Release 1.4.0: May 1, 2021 + +* Allow for customization which signals are sent to stop process (thanks to philister) +* Resolves mismatched indentations (thanks to Luis M Rodriguez) +* Allow to use pry-byebug 3.8.0 (thanks to kamipo) + +== Release 1.3.1: December 14, 2018 + +* Fix undefined local variable or method `pid_delimiter' + +== Release 1.3.0: December 10, 2018 + +* Make logging more configurable. +* Add configuration options for pid file delimters, force_kill_waittime +* All status callback to be anything callable. + +== Release 1.2.6: December 24, 2017 + +* Add links to rubydoc.info documentation. + +== Release 1.2.5: October 22, 2017 + +* In Application#stop, call zap, not cleanup on the pidfile (thanks to wevanscfi) +* Use File.expand_path on and output and log files (thanks to Dave Harris) + +== Release 1.2.4: August 1, 2016 + +* add :shush option +* add :monitor_interval option +* add :log_output_syslog option + +== Release 1.2.3: June 25, 2015 + +* fix: ApplicationGroup now waits on subprocesses in start_all (thanks to tobithiel) + +== Release 1.2.2: March 17, 2015 + +* fix 100% CPU usage bug when using monitor mode. + +== Release 1.2.1: March 10, 2015 + +* increase version number to be able to re-push to rubygems + +== Release 1.2.0: March 8, 2015 + +* add options for custum log file names. +* change pid file name scheme to "#progname_num#{number}.pid" for multiple instances. +* fix call_as_daemon not saving the PID (thanks Roberto Plancarte) +* allow for custom statis messages (thanks to Joseph Haig) +* fix Pid.running? rescuing timeout exceptions (thanks to Geraud Boyer) +* monitor.rb/application.rb/application_group.rb: handle :monitor and :multiple in combination correctly + (thanks to Prakash Murthy). +* pidfile.rb: Handle invalid or empty pid files instead of returning pid 0 (thanks to Aaron Stone) +* run the whole gem through Rubocop (thanks to Aaron Stone) +* gem cleanup (thanks to Aaron Stone) + +== Release 1.1.9: August 10, 2012 + +* daemonize.rb: do srand in the forked child process both in daemonize and call_as_daemon + (thanks to Andrew Havens). + +== Release 1.1.8: February 7, 2012 + +* rename to daemonization.rb to daemonize.rb (and Daemonization to Daemonize) to + ensure compatibility. + +== Release 1.1.7: February 6, 2012 + +* start_proc: Write out the PID file in the newly created proc to avoid race conditions. +* daemonize.rb: remove to simplify licensing (replaced by daemonization.rb). + +== Release 1.1.6: January 18, 2012 + +* Add the :app_name option for the "call" daemonization mode. + +== Release 1.1.5: December 19, 2011 + +* Catch the case where the pidfile is empty but not deleted + and restart the app (thanks to Rich Healey) + +== Release 1.1.4: June 17, 2011 + +* Do not change the umask to 0000 when daemonizing anymore, just leave it as it + was (thanks to Jon Botelho). + +== Release 1.1.3: April 14, 2011 + +* Fixed a bug in Application.stop: the cached pid number needs to + be used to check for the status of a killed process (thanks to Jimmy Sieben). + +== Release 1.1.2: March 29, 2011 + +* Fixed gemspec to include all needed files. + +== Release 1.1.1: March 29, 2011 + +* Make the logging facilities work in :mode => :none (i.e. when calling + Daemons.daemonize) (thanks to the input from Peter Hegedus). + +== Release 1.1.0: June 20, 2010 + +* Honour the options[:app_name] in Daemons.daemonize (thanks to Ryan Tecco). +* Included a new option :stop_proc to specify a proc that will be called when a + daemonized process receives a request to stop (thanks to Dave Dupre). +* Only delete the pidfile if the current pid is the original pid (ghazel). +* Start when restart but no application running (pcreux). +* Silently continue if there is no pidfile (ghazel). +* We now per default wait for processes to stop and + kill them automatically it if they do not stop within a given time + (force_kill_waittime). Use the option --no_wait to not wait for processes to + stop. +* Set log files mode to 0644 (mikehale). +* Set pid file permissions to 0644 (mikehale). +* Added ability to change process uid/gid (mikehale). +* Fix for: If you happen to start a daemon from a process that has open file + descriptors these will stay open. As it is daemonize.rb only closes ruby IO + objects (thanks to Han Holl). +* New reload command (SIGHUP) (thanks to Michael Schuerig). + +== Release 1.0.10: March 21, 2008 + +* By default, we now delete stray pid-files (i.e. pid-files which result for + example from a killed daemon) automatically. This function can be deactivated + by passing :keep_pid_files => true as an option. +* All pid files of :multiple daemons new get deleted correctly upon exit of the + daemons (reported by Han Holl). +* Use the signal 'KILL' instead of 'TERM' on Windows platforms. +* Use exit! in trap('TERM') instead of exit when option :hard_exit is given + (thanks to Han Holl). +* Did some clarification on the exception log. + +== Release 1.0.9: October 29, 2007 + +* fixed a severe bug in the new Pid.running? function: function returned true if + the process did not exist (thanks to Jeremy Lawler). + +== Release 1.0.8: September 24, 2007 + +* new Pid.running? function. Checking whether a process exists by sending + signal '0' (thanks to Dru Nelson). + +== Release 1.0.7: July 7, 2007 + +* Patch to fix wrong ARGV when using :exec (in def start_exec: + Kernel.exec(script(), *(@app_argv || []))) (thanks to Alex McGuire). + +== Release 1.0.6: Mai 8, 2007 + +* New option to pass an ARGV-style array to run and run_proc (thanks to Marc Evans). +* Additional patches for '/var/log' (thanks to Marc Evans). + +== Release 1.0.5: February 24, 2007 + +* Applied patch that makes daemons to use '/var/log' as logfile + directory if you use :dir_mode = :system (thanks to Han Holl). +* Daemons should now work with Ruby 1.9 (at least the basic features). + +== Release 1.0.4: January 17, 2007 + +* Document the :log_output option (thanks to Andrew Kuklewicz). +* Set STDOUT.sync = true when redirecting to a logfile (thanks to Andrew Kuklewicz). +* Should now run also correctly when there is no working 'ps ax' on the system + (thanks to Daniel Kehoe). + +== Release 1.0.3: November 1, 2006 + +* Set the app_name correctly also for the monitor process (thanks to Ilya Novoselov). + +== Release 1.0.2: September 26, 2006 + +* Changed the 'ps -ax' call back to 'ps ax'. +* Fixed the documentation for the :normal :dir_mode. +* As a default for Daemons.run_proc, the pid file is now saved in the current directory. +* In :ontop mode for running a proc (this is equal to calling something like 'ruby ctrl_proc.rb run'), + the proc now runs directly in the calling script, not in a forked process anymore (thanks to Paul Butcher). +* Set $0 to app_name in the daemons (thanks to Ilya Novoselov). + +== Release 1.0.1: August 30, 2006 + +* Fixed a regex for parsing the 'ps ax' system call. (thanks to Garance Alistair Drosehn) + +== Release 1.0.0: August 29, 2006 + +* Fix the parsing of the 'ps ax' system call. (thanks to Garance Alistair Drosehn) + +== Release 0.4.4: February 14, 2006 + +* Several fixes that allow us to use the Daemons::Controller + with a proc instead of wrapping a script file. This gives us all the + PID file management, monitoring, command line options, etc. without having + to specify a path to our script which can be tricky, especially when using + RubyGems. (thanks to John-Mason Shackelford) + +== Release 0.4.3: November 29, 2005 + +* New Option: You can specify the name of the application with :app_name + on calling Daemons.run. This will be used to contruct the name of the pid files + and log files. Defaults to the basename of the script. (thanks to Stephen R. Veit) + +* Bugfix: Handle the case where no controller options are given when calling Daemons, + just options after "--". (thanks to Stephen R. Veit) + + +== Release 0.4.2: November 15, 2005 + +* Bugfix for problem with :normal pid-file directory mode (pid.rb), fixed (thanks to Stephen R. Veit) + + +== Release 0.4.1: September 11, 2005 + +* Bugfix for 'run' command line mode: didn't work anymore in 0.4.0, fixed + + +== Release 0.4.0: July 30, 2005 + +* Two completely new operation modes: + 1. Call a block as a daemon (Daemons.call { my_daemon_code }) + and control it from the parent process. + 2. Daemonize the currently running process (Daemons.daemonize) + plus the already existing mode to control your scripts (Daemons.run("script.rb")) +* Improved documentation (for example "How does the daemonization process work?") +* Improved "simulation mode" (:ontop option) +* Some minor bugfixes + + +== Release 0.3.0: April 21, 2005 + +* New monitor functionality: automatic restarting of your applications if they crash +* 'restart' command fixed +* '--force' command modifier (please refer to the documentation) +* Some more bugfixes and improvements + + +== Release 0.2.1: Mar 21, 2005 + +* Bugfix for a problem with the 'status' command + + +== Release 0.2.0: Mar 21, 2005 + +* Exception backtrace functionality added +* Exec functionality added +* More examples added +* New commands: status, zap + + +== Release 0.0.1: Feb 8, 2005 + +* Initial release diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call.rb new file mode 100644 index 0000000000..c6ecf3b2b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call.rb @@ -0,0 +1,54 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +testfile = File.expand_path(__FILE__) + '.log' + +# On the first call to , an application group (accessible by Daemons.group) +# will be created an the options will be kept within, so you only have to specify +# :multiple once. +# + +options = { + :app_name => 'mytask', +# :ontop => true, + :multiple => true +} + +Daemons.call(options) do + File.open(testfile, 'w') do |f| + f.puts 'test' + end + + loop { puts '1'; sleep 5 } +end +puts 'first task started' + +Daemons.call do + loop { puts '2'; sleep 4 } +end +puts 'second task started' + +# NOTE: this process will exit after 5 seconds +Daemons.call do + puts '3' + sleep 5 +end +puts 'third task started' + +puts 'waiting 20 seconds...' +sleep(20) + +# This call would result in an exception as it will try to kill the third process +# which has already terminated by that time; but using the 'true' parameter forces the +# stop_all procedure. +puts 'trying to stop all tasks...' +Daemons.group.stop_all(true) + +puts 'done' diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call_monitor.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call_monitor.rb new file mode 100644 index 0000000000..de8c72f62b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/call/call_monitor.rb @@ -0,0 +1,51 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +testfile = File.expand_path(__FILE__) + '.log' + +# On the first call to , an application group (accessible by Daemons.group) +# will be created an the options will be kept within, so you only have to specify +# :multiple once. +# + +options = { +# :ontop => true, + :multiple => true, + :monitor => true +} + +Daemons.call(options) do + loop { puts '1'; sleep 20 } +end +puts 'first task started' + +# NOTE: this process will exit after 5 seconds +Daemons.call do + File.open(testfile, 'a') do |f| + f.puts 'started...' + puts '2' + + sleep 5 + + f.puts '...exit' + end +end +puts 'second task started' + +puts 'waiting 100 seconds...' +sleep(100) + +# This call would result in an exception as it will try to kill the third process +# which has already terminated by that time; but using the 'true' parameter forces the +# stop_all procedure. +puts 'trying to stop all tasks...' +Daemons.group.stop_all(true) + +puts 'done' diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/daemonize/daemonize.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/daemonize/daemonize.rb new file mode 100644 index 0000000000..9cd2201b7f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/daemonize/daemonize.rb @@ -0,0 +1,23 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :log_output => true +} + +testfile = File.expand_path(__FILE__) + '.txt' + +Daemons.daemonize(options) + +puts 'some output...' + +File.open(testfile, 'w') do |f| + f.write('test') +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_crash.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_crash.rb new file mode 100644 index 0000000000..ed010282c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_crash.rb @@ -0,0 +1,16 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :log_output => true, + :backtrace => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_crashing.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_custom_logfiles.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_custom_logfiles.rb new file mode 100644 index 0000000000..355fcbbb67 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_custom_logfiles.rb @@ -0,0 +1,18 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :log_output => true, + :backtrace => true, + :output_logfilename => "custom_output.txt", + :logfilename => "custom_log.log" +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_crashing.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exec.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exec.rb new file mode 100644 index 0000000000..cb89c1e429 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exec.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :mode => :exec +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exit.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exit.rb new file mode 100644 index 0000000000..0cdef6a745 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_exit.rb @@ -0,0 +1,14 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_exiting.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_hanging.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_hanging.rb new file mode 100644 index 0000000000..f5cb66ddae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_hanging.rb @@ -0,0 +1,19 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + #:mode => :exec, + :multiple => true, + :no_pidfiles => true, + :force_kill_waittime => 5 + #:force_kill_waittime => -1 # do not wait before killing -9 +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_hanging.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_keep_pid_files.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_keep_pid_files.rb new file mode 100644 index 0000000000..4644e103e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_keep_pid_files.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :keep_pid_files => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor.rb new file mode 100644 index 0000000000..2ff1f3c09e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :monitor => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_crashing.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_multiple.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_multiple.rb new file mode 100644 index 0000000000..ac80bb300c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_multiple.rb @@ -0,0 +1,17 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :multiple => true, + :monitor => true, + :log_output => true, +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_crashing.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_nocrash.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_nocrash.rb new file mode 100644 index 0000000000..3da5459184 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_monitor_nocrash.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :monitor => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_multiple.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_multiple.rb new file mode 100644 index 0000000000..c1a893b053 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_multiple.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :multiple => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_normal.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_normal.rb new file mode 100644 index 0000000000..8fc8abfb3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_normal.rb @@ -0,0 +1,11 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb')) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_ontop.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_ontop.rb new file mode 100644 index 0000000000..54031b0e83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_ontop.rb @@ -0,0 +1,15 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :ontop => true +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_optionparser.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_optionparser.rb new file mode 100644 index 0000000000..1ec7f78127 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_optionparser.rb @@ -0,0 +1,41 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' +require 'optparse' +require 'logger' +require 'ostruct' + +class MyApp < Logger::Application + def initialize(args) + super(self.class) + @options = OpenStruct.new(:daemonize => true) + opts = OptionParser.new do |opts| + opts.banner = 'Usage: myapp [options]' + opts.separator '' + opts.on('-N', '--no-daemonize', "Don't run as a daemon") do + @options.daemonize = false + end + end + @args = opts.parse!(args) + end + + def run + Daemons.run_proc('myapp', :ARGV => @args, :ontop => !@options.daemonize) do + puts "@options.daemonize: #{@options.daemonize}" + $stdout.sync = true + loop do + print '.' + sleep(2) + end + end + end +end + +myapp = MyApp.new(ARGV) +myapp.run diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc.rb new file mode 100644 index 0000000000..6e67c9085a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc.rb @@ -0,0 +1,24 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :multiple => false, + :ontop => false, + :backtrace => true, + :log_output => true, + :monitor => true +} + +Daemons.run_proc('ctrl_proc.rb', options) do + loop do + puts 'ping from proc!' + sleep(3) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_multiple.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_multiple.rb new file mode 100644 index 0000000000..57870a9a4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_multiple.rb @@ -0,0 +1,20 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + :log_output => true, + :multiple => true, +} + +Daemons.run_proc('ctrl_proc_multiple.rb', options) do + puts 'hello' + sleep(5) + puts 'done' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_rand.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_rand.rb new file mode 100644 index 0000000000..ea12bb9e20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_rand.rb @@ -0,0 +1,21 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +Daemons.run_proc('myscript') do + loop do + file = File.open('/tmp/myscript.log', 'a') + file.write(Random.rand) # breaks without seeding + # file.write(Random.new.rand) # works without seeding + # file.write(rand) # also works, but this is Kernel.rand() so its different + file.write("\n") + file.close + sleep 2 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_simple.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_simple.rb new file mode 100644 index 0000000000..095dfa624a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_proc_simple.rb @@ -0,0 +1,16 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +Daemons.run_proc('ctrl_proc_simple.rb') do + loop do + puts 'ping from proc!' + sleep(3) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_slowstop.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_slowstop.rb new file mode 100644 index 0000000000..50ff1e1d6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/ctrl_slowstop.rb @@ -0,0 +1,16 @@ +lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) + +if File.exist?(File.join(lib_dir, 'daemons.rb')) + $LOAD_PATH.unshift lib_dir +else + begin; require 'rubygems'; rescue ::Exception; end +end + +require 'daemons' + +options = { + #:force_kill_waittime => 40 + #:force_kill_waittime => -1 # do not wait before killing -9 +} + +Daemons.run(File.join(File.dirname(__FILE__), 'myserver_slowstop.rb'), options) diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver.rb new file mode 100755 index 0000000000..c43e73fa31 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby + +# This is myserver.rb, an example server that is to be controlled by daemons +# and that does nothing really useful at the moment. +# +# Don't run this script by yourself, it can be controlled by the ctrl*.rb scripts. + +loop do + puts 'ping from myserver.rb!' + sleep(3) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_crashing.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_crashing.rb new file mode 100644 index 0000000000..147f246552 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_crashing.rb @@ -0,0 +1,14 @@ +# This is myserver.rb, an example server that is to be controlled by daemons +# and that does nothing really useful at the moment. +# +# Don't run this script by yourself, it can be controlled by the ctrl*.rb scripts. + +loop do + puts 'ping from myserver.rb!' + puts 'this example server will crash in 10 seconds...' + + sleep(10) + + puts 'CRASH!' + fail 'CRASH!' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_exiting.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_exiting.rb new file mode 100644 index 0000000000..9be6518eae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_exiting.rb @@ -0,0 +1,8 @@ +loop do + puts 'ping from myserver.rb!' + puts 'this example server will exit in 3 seconds...' + + sleep(3) + + Process.exit +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_hanging.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_hanging.rb new file mode 100755 index 0000000000..a6010429f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_hanging.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +# This is myserver.rb, an example server that is to be controlled by daemons +# and that does nothing really useful at the moment. +# +# Don't run this script by yourself, it can be controlled by the ctrl*.rb scripts. + +trap('TERM') do + puts 'received TERM' + + loop do + puts 'hanging!' + sleep(3) + end +end + +loop do + puts 'ping from myserver.rb!' + sleep(3) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_slowstop.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_slowstop.rb new file mode 100755 index 0000000000..65526ab676 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/examples/run/myserver_slowstop.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +# This is myserver_slowstop.rb, an example server that is to be controlled by daemons +# and that does nothing really useful at the moment. +# +# Don't run this script by yourself, it can be controlled by the ctrl*.rb scripts. + +trap('TERM') do + puts 'received TERM' + + # simulate the slow stopping + sleep(10) + + exit +end + +loop do + puts 'ping from myserver.rb!' + sleep(3) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/lib/daemons.rb b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/lib/daemons.rb new file mode 100644 index 0000000000..53cb1c8d88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/daemons-1.4.0/lib/daemons.rb @@ -0,0 +1,325 @@ +require 'optparse' +require 'optparse/time' + +require 'daemons/version' +require 'daemons/pidfile' +require 'daemons/cmdline' +require 'daemons/exceptions' +require 'daemons/monitor' + +require 'daemons/application' +require 'daemons/application_group' +require 'daemons/controller' + +# All functions and classes that Daemons provides reside in this module. +# +# Daemons is normally invoked by one of the following four ways: +# +# 1. Daemons.run(script, options): +# This is used in wrapper-scripts that are supposed to control other ruby scripts or +# external applications. Control is completely passed to the daemons library. +# Such wrapper script need to be invoked with command line options like 'start' or 'stop' +# to do anything useful. +# +# 2. Daemons.run_proc(app_name, options) { (...) }: +# This is used in wrapper-scripts that are supposed to control a proc. +# Control is completely passed to the daemons library. +# Such wrapper scripts need to be invoked with command line options like 'start' or 'stop' +# to do anything useful. +# +# 3. Daemons.call(options) { block }: +# Execute the block in a new daemon. Daemons.call will return immediately +# after spawning the daemon with the new Application object as a return value. +# +# 4. Daemons.daemonize(options): +# Daemonize the currently runnig process, i.e. the calling process will become a daemon. +# +# == What does daemons internally do with my daemons? +# *or*:: why do my daemons crash when they try to open a file? +# *or*:: why can I not see any output from the daemon on the console (when using for example +puts+)? +# +# From a technical aspect of view, daemons does the following when creating a daemon: +# +# 1. Forks a child (and exits the parent process, if needed) +# 2. Becomes a session leader (which detaches the program from +# the controlling terminal). +# 3. Forks another child process and exits first child. This prevents +# the potential of acquiring a controlling terminal. +# 4. Changes the current working directory to "/". +# 5. Clears the file creation mask (sets +umask+ to 0000). +# 6. Closes file descriptors (reopens +$stdout+ and +$stderr+ to point to a logfile if +# possible). +# +# So what does this mean for your daemons: +# - the current directory is '/' +# - you cannot receive any input from the console (for example no +gets+) +# - you cannot output anything from the daemons with +puts+/+print+ unless a logfile is used +# +# == How do PidFiles work? Where are they stored? +# +# Also, you are maybe interested in reading the documentation for the class PidFile. +# There you can find out about how Daemons works internally and how and where the so +# called PidFiles are stored. +# +module Daemons + require 'daemons/daemonize' + + # Passes control to Daemons. + # This is used in wrapper-scripts that are supposed to control other ruby scripts or + # external applications. Control is completely passed to the daemons library. + # Such wrapper script should be invoked with command line options like 'start' or 'stop' + # to do anything useful. + # + # +script+:: This is the path to the script that should be run as a daemon. + # Please note that Daemons runs this script with load + + + +
+

<%=h exception.class %> at <%=h path %>

+

<%=h exception.message %>

+

+ + + + + +
Ruby + <% if first = frames.first %> + <%=h first.filename %>: in <%=h first.function %>, line <%=h frames.first.lineno %> + <% else %> + unknown location + <% end %> +
Web<%=h req.request_method %> <%=h(req.host + path)%>
+ +

Jump to:

+ + + +
+

Traceback (innermost first)

+
    + <% frames.each { |frame| %> +
  • + <%=h frame.filename %>: in <%=h frame.function %> + + <% if frame.context_line %> +
    + <% if frame.pre_context %> +
      + <% frame.pre_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> + +
      +
    1. <%=h frame.context_line %>...
    + + <% if frame.post_context %> +
      + <% frame.post_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> +
    + <% end %> +
  • + <% } %> +
+
+ +
+

Request information

+ +

GET

+ <% if req.GET and not req.GET.empty? %> + + + + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No GET data.

+ <% end %> + +

POST

+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %> + + + + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

<%= no_post_data || "No POST data" %>.

+ <% end %> + + + + <% unless req.cookies.empty? %> + + + + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No cookie data.

+ <% end %> + +

Rack ENV

+ + + + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ +
+ +
+

+ You're seeing this error because you use Rack::ShowExceptions. +

+
+ + + + HTML + + # :startdoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/show_status.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/show_status.rb new file mode 100644 index 0000000000..a99bdaf33a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/show_status.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'erb' + +module Rack + # Rack::ShowStatus catches all empty responses and replaces them + # with a site explaining the error. + # + # Additional details can be put into rack.showstatus.detail + # and will be shown as HTML. If such details exist, the error page + # is always rendered, even if the reply was not empty. + + class ShowStatus + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + empty = headers[CONTENT_LENGTH].to_i <= 0 + + # client or server error, or explicit message + if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL] + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + req = req = Rack::Request.new(env) + + message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s + + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message + + body = @template.result(binding) + size = body.bytesize + [status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]] + else + [status, headers, body] + end + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + +# adapted from Django +# Copyright (c) Django Software Foundation and individual contributors. +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +TEMPLATE = <<'HTML' + + + + + <%=h message %> at <%=h req.script_name + req.path_info %> + + + + +
+

<%=h message %> (<%= status.to_i %>)

+ + + + + + + + + +
Request Method:<%=h req.request_method %>
Request URL:<%=h req.url %>
+
+
+

<%=h detail %>

+
+ +
+

+ You're seeing this error because you use Rack::ShowStatus. +

+
+ + +HTML + + # :startdoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/static.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/static.rb new file mode 100644 index 0000000000..8cb58b2fd7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/static.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +module Rack + + # The Rack::Static middleware intercepts requests for static files + # (javascript files, images, stylesheets, etc) based on the url prefixes or + # route mappings passed in the options, and serves them using a Rack::Files + # object. This allows a Rack stack to serve both static and dynamic content. + # + # Examples: + # + # Serve all requests beginning with /media from the "media" folder located + # in the current directory (ie media/*): + # + # use Rack::Static, :urls => ["/media"] + # + # Same as previous, but instead of returning 404 for missing files under + # /media, call the next middleware: + # + # use Rack::Static, :urls => ["/media"], :cascade => true + # + # Serve all requests beginning with /css or /images from the folder "public" + # in the current directory (ie public/css/* and public/images/*): + # + # use Rack::Static, :urls => ["/css", "/images"], :root => "public" + # + # Serve all requests to / with "index.html" from the folder "public" in the + # current directory (ie public/index.html): + # + # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public' + # + # Serve all requests normally from the folder "public" in the current + # directory but uses index.html as default route for "/" + # + # use Rack::Static, :urls => [""], :root => 'public', :index => + # 'index.html' + # + # Set custom HTTP Headers for based on rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # [rule, {header_field => content, header_field => content}], + # [rule, {header_field => content}] + # ] + # + # Rules for selecting files: + # + # 1) All files + # Provide the :all symbol + # :all => Matches every file + # + # 2) Folders + # Provide the folder path as a string + # '/folder' or '/folder/subfolder' => Matches files in a certain folder + # + # 3) File Extensions + # Provide the file extensions as an array + # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js + # + # 4) Regular Expressions / Regexp + # Provide a regular expression + # %r{\.(?:css|js)\z} => Matches files ending in .css or .js + # /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in + # the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg) + # Note: This Regexp is available as a shortcut, using the :fonts rule + # + # 5) Font Shortcut + # Provide the :fonts symbol + # :fonts => Uses the Regexp rule stated right above to match all common web font endings + # + # Rule Ordering: + # Rules are applied in the order that they are provided. + # List rather general rules above special ones. + # + # Complete example use case including HTTP header rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # # Cache all static files in public caches (e.g. Rack::Cache) + # # as well as in the browser + # [:all, {'Cache-Control' => 'public, max-age=31536000'}], + # + # # Provide web fonts with cross-origin access-control-headers + # # Firefox requires this when serving assets using a Content Delivery Network + # [:fonts, {'Access-Control-Allow-Origin' => '*'}] + # ] + # + class Static + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + def initialize(app, options = {}) + @app = app + @urls = options[:urls] || ["/favicon.ico"] + @index = options[:index] + @gzip = options[:gzip] + @cascade = options[:cascade] + root = options[:root] || Dir.pwd + + # HTTP Headers + @header_rules = options[:header_rules] || [] + # Allow for legacy :cache_control option while prioritizing global header_rules setting + @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control] + + @file_server = Rack::Files.new(root) + end + + def add_index_root?(path) + @index && route_file(path) && path.end_with?('/') + end + + def overwrite_file_path(path) + @urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path) + end + + def route_file(path) + @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 } + end + + def can_serve(path) + route_file(path) || overwrite_file_path(path) + end + + def call(env) + path = env[PATH_INFO] + + if can_serve(path) + if overwrite_file_path(path) + env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path]) + elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING']) + path = env[PATH_INFO] + env[PATH_INFO] += '.gz' + response = @file_server.call(env) + env[PATH_INFO] = path + + if response[0] == 404 + response = nil + elsif response[0] == 304 + # Do nothing, leave headers as is + else + if mime_type = Mime.mime_type(::File.extname(path), 'text/plain') + response[1][CONTENT_TYPE] = mime_type + end + response[1]['Content-Encoding'] = 'gzip' + end + end + + path = env[PATH_INFO] + response ||= @file_server.call(env) + + if @cascade && response[0] == 404 + return @app.call(env) + end + + headers = response[1] + applicable_rules(path).each do |rule, new_headers| + new_headers.each { |field, content| headers[field] = content } + end + + response + else + @app.call(env) + end + end + + # Convert HTTP header rules to HTTP headers + def applicable_rules(path) + @header_rules.find_all do |rule, new_headers| + case rule + when :all + true + when :fonts + /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path) + when String + path = ::Rack::Utils.unescape(path) + path.start_with?(rule) || path.start_with?('/' + rule) + when Array + /\.(#{rule.join('|')})\z/.match?(path) + when Regexp + rule.match?(path) + else + false + end + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb new file mode 100644 index 0000000000..9b04fefc24 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Rack + + # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart) + # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter + # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ + class TempfileReaper + def initialize(app) + @app = app + end + + def call(env) + env[RACK_TEMPFILES] ||= [] + status, headers, body = @app.call(env) + body_proxy = BodyProxy.new(body) do + env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil? + end + [status, headers, body_proxy] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/urlmap.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/urlmap.rb new file mode 100644 index 0000000000..31a642c418 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/urlmap.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'set' + +module Rack + # Rack::URLMap takes a hash mapping urls or paths to apps, and + # dispatches accordingly. Support for HTTP/1.1 host names exists if + # the URLs start with http:// or https://. + # + # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part + # relevant for dispatch is in the SCRIPT_NAME, and the rest in the + # PATH_INFO. This should be taken care of when you need to + # reconstruct the URL in order to create links. + # + # URLMap dispatches in such a way that the longest paths are tried + # first, since they are most specific. + + class URLMap + def initialize(map = {}) + remap(map) + end + + def remap(map) + @known_hosts = Set[] + @mapping = map.map { |location, app| + if location =~ %r{\Ahttps?://(.*?)(/.*)} + host, location = $1, $2 + @known_hosts << host + else + host = nil + end + + unless location[0] == ?/ + raise ArgumentError, "paths need to start with /" + end + + location = location.chomp('/') + match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') + + [host, location, match, app] + }.sort_by do |(host, location, _, _)| + [host ? -host.size : Float::INFINITY, -location.size] + end + end + + def call(env) + path = env[PATH_INFO] + script_name = env[SCRIPT_NAME] + http_host = env[HTTP_HOST] + server_name = env[SERVER_NAME] + server_port = env[SERVER_PORT] + + is_same_server = casecmp?(http_host, server_name) || + casecmp?(http_host, "#{server_name}:#{server_port}") + + is_host_known = @known_hosts.include? http_host + + @mapping.each do |host, location, match, app| + unless casecmp?(http_host, host) \ + || casecmp?(server_name, host) \ + || (!host && is_same_server) \ + || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host + next + end + + next unless m = match.match(path.to_s) + + rest = m[1] + next unless !rest || rest.empty? || rest[0] == ?/ + + env[SCRIPT_NAME] = (script_name + location) + env[PATH_INFO] = rest + + return app.call(env) + end + + [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]] + + ensure + env[PATH_INFO] = path + env[SCRIPT_NAME] = script_name + end + + private + def casecmp?(v1, v2) + # if both nil, or they're the same string + return true if v1 == v2 + + # if either are nil... (but they're not the same) + return false if v1.nil? + return false if v2.nil? + + # otherwise check they're not case-insensitive the same + v1.casecmp(v2).zero? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/utils.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/utils.rb new file mode 100644 index 0000000000..d3b3b1d420 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/utils.rb @@ -0,0 +1,613 @@ +# -*- encoding: binary -*- +# frozen_string_literal: true + +require 'uri' +require 'fileutils' +require 'set' +require 'tempfile' +require 'time' + +require_relative 'query_parser' + +module Rack + # Rack::Utils contains a grab-bag of useful methods for writing web + # applications adopted from all kinds of Ruby libraries. + + module Utils + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + ParameterTypeError = QueryParser::ParameterTypeError + InvalidParameterError = QueryParser::InvalidParameterError + DEFAULT_SEP = QueryParser::DEFAULT_SEP + COMMON_SEP = QueryParser::COMMON_SEP + KeySpaceConstrainedParams = QueryParser::Params + + class << self + attr_accessor :default_query_parser + end + # The default number of bytes to allow parameter keys to take up. + # This helps prevent a rogue client from flooding a Request. + self.default_query_parser = QueryParser.make_default(65536, 100) + + module_function + + # URI escapes. (CGI style space to +) + def escape(s) + URI.encode_www_form_component(s) + end + + # Like URI escaping, but with %20 instead of +. Strictly speaking this is + # true URI escaping. + def escape_path(s) + ::URI::DEFAULT_PARSER.escape s + end + + # Unescapes the **path** component of a URI. See Rack::Utils.unescape for + # unescaping query parameters or form components. + def unescape_path(s) + ::URI::DEFAULT_PARSER.unescape s + end + + # Unescapes a URI escaped string with +encoding+. +encoding+ will be the + # target encoding of the string returned, and it defaults to UTF-8 + def unescape(s, encoding = Encoding::UTF_8) + URI.decode_www_form_component(s, encoding) + end + + class << self + attr_accessor :multipart_part_limit + end + + # The maximum number of parts a request can contain. Accepting too many part + # can lead to the server running out of file handles. + # Set to `0` for no limit. + self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i + + def self.param_depth_limit + default_query_parser.param_depth_limit + end + + def self.param_depth_limit=(v) + self.default_query_parser = self.default_query_parser.new_depth_limit(v) + end + + def self.key_space_limit + default_query_parser.key_space_limit + end + + def self.key_space_limit=(v) + self.default_query_parser = self.default_query_parser.new_space_limit(v) + end + + if defined?(Process::CLOCK_MONOTONIC) + def clock_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + else + # :nocov: + def clock_time + Time.now.to_f + end + # :nocov: + end + + def parse_query(qs, d = nil, &unescaper) + Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper) + end + + def parse_nested_query(qs, d = nil) + Rack::Utils.default_query_parser.parse_nested_query(qs, d) + end + + def build_query(params) + params.map { |k, v| + if v.class == Array + build_query(v.map { |x| [k, x] }) + else + v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}" + end + }.join("&") + end + + def build_nested_query(value, prefix = nil) + case value + when Array + value.map { |v| + build_nested_query(v, "#{prefix}[]") + }.join("&") + when Hash + value.map { |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) + }.delete_if(&:empty?).join('&') + when nil + prefix + else + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{escape(value)}" + end + end + + def q_values(q_value_header) + q_value_header.to_s.split(/\s*,\s*/).map do |part| + value, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if parameters && (md = /\Aq=([\d.]+)/.match(parameters)) + quality = md[1].to_f + end + [value, quality] + end + end + + # Return best accept value to use, based on the algorithm + # in RFC 2616 Section 14. If there are multiple best + # matches (same specificity and quality), the value returned + # is arbitrary. + def best_q_match(q_value_header, available_mimes) + values = q_values(q_value_header) + + matches = values.map do |req_mime, quality| + match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) } + next unless match + [match, quality] + end.compact.sort_by do |match, quality| + (match.split('/', 2).count('*') * -10) + quality + end.last + matches && matches.first + end + + ESCAPE_HTML = { + "&" => "&", + "<" => "<", + ">" => ">", + "'" => "'", + '"' => """, + "/" => "/" + } + + ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) + + # Escape ampersands, brackets and quotes to their HTML/XML entities. + def escape_html(string) + string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } + end + + def select_best_encoding(available_encodings, accept_encoding) + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + + expanded_accept_encoding = [] + + accept_encoding.each do |m, q| + preference = available_encodings.index(m) || available_encodings.size + + if m == "*" + (available_encodings - accept_encoding.map(&:first)).each do |m2| + expanded_accept_encoding << [m2, q, preference] + end + else + expanded_accept_encoding << [m, q, preference] + end + end + + encoding_candidates = expanded_accept_encoding + .sort_by { |_, q, p| [-q, p] } + .map!(&:first) + + unless encoding_candidates.include?("identity") + encoding_candidates.push("identity") + end + + expanded_accept_encoding.each do |m, q| + encoding_candidates.delete(m) if q == 0.0 + end + + (encoding_candidates & available_encodings)[0] + end + + def parse_cookies(env) + parse_cookies_header env[HTTP_COOKIE] + end + + def parse_cookies_header(header) + # According to RFC 6265: + # The syntax for cookie headers only supports semicolons + # User Agent -> Server == + # Cookie: SID=31d4d96e407aad42; lang=en-US + return {} unless header + header.split(/[;] */n).each_with_object({}) do |cookie, cookies| + next if cookie.empty? + key, value = cookie.split('=', 2) + cookies[key] = (unescape(value) rescue value) unless cookies.key?(key) + end + end + + def add_cookie_to_header(header, key, value) + case value + when Hash + domain = "; domain=#{value[:domain]}" if value[:domain] + path = "; path=#{value[:path]}" if value[:path] + max_age = "; max-age=#{value[:max_age]}" if value[:max_age] + expires = "; expires=#{value[:expires].httpdate}" if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only]) + same_site = + case value[:same_site] + when false, nil + nil + when :none, 'None', :None + '; SameSite=None' + when :lax, 'Lax', :Lax + '; SameSite=Lax' + when true, :strict, 'Strict', :Strict + '; SameSite=Strict' + else + raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}" + end + value = value[:value] + end + value = [value] unless Array === value + + cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \ + "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}" + + case header + when nil, '' + cookie + when String + [header, cookie].join("\n") + when Array + (header + [cookie]).join("\n") + else + raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}" + end + end + + def set_cookie_header!(header, key, value) + header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value) + nil + end + + def make_delete_cookie_header(header, key, value) + case header + when nil, '' + cookies = [] + when String + cookies = header.split("\n") + when Array + cookies = header + end + + key = escape(key) + domain = value[:domain] + path = value[:path] + regexp = if domain + if path + /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/ + else + /\A#{key}=.*domain=#{domain}(?:;|$)/ + end + elsif path + /\A#{key}=.*path=#{path}(?:;|$)/ + else + /\A#{key}=/ + end + + cookies.reject! { |cookie| regexp.match? cookie } + + cookies.join("\n") + end + + def delete_cookie_header!(header, key, value = {}) + header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value) + nil + end + + # Adds a cookie that will *remove* a cookie from the client. Hence the + # strange method name. + def add_remove_cookie_to_header(header, key, value = {}) + new_header = make_delete_cookie_header(header, key, value) + + add_cookie_to_header(new_header, key, + { value: '', path: nil, domain: nil, + max_age: '0', + expires: Time.at(0) }.merge(value)) + + end + + def rfc2822(time) + time.rfc2822 + end + + # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead + # of '% %b %Y'. + # It assumes that the time is in GMT to comply to the RFC 2109. + # + # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough + # that I'm certain someone implemented only that option. + # Do not use %a and %b from Time.strptime, it would use localized names for + # weekday and month. + # + def rfc2109(time) + wday = Time::RFC2822_DAY_NAME[time.wday] + mon = Time::RFC2822_MONTH_NAME[time.mon - 1] + time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT") + end + + # Parses the "Range:" header, if present, into an array of Range objects. + # Returns nil if the header is missing or syntactically invalid. + # Returns an empty array if none of the ranges are satisfiable. + def byte_ranges(env, size) + warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE + get_byte_ranges env['HTTP_RANGE'], size + end + + def get_byte_ranges(http_range, size) + # See + return nil unless http_range && http_range =~ /bytes=([^;]+)/ + ranges = [] + $1.split(/,\s*/).each do |range_spec| + return nil unless range_spec =~ /(\d*)-(\d*)/ + r0, r1 = $1, $2 + if r0.empty? + return nil if r1.empty? + # suffix-byte-range-spec, represents trailing suffix of file + r0 = size - r1.to_i + r0 = 0 if r0 < 0 + r1 = size - 1 + else + r0 = r0.to_i + if r1.empty? + r1 = size - 1 + else + r1 = r1.to_i + return nil if r1 < r0 # backwards range is syntactically invalid + r1 = size - 1 if r1 >= size + end + end + ranges << (r0..r1) if r0 <= r1 + end + ranges + end + + # Constant time string comparison. + # + # NOTE: the values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize + + l = a.unpack("C*") + + r, i = 0, -1 + b.each_byte { |v| r |= v ^ l[i += 1] } + r == 0 + end + + # Context allows the use of a compatible middleware at different points + # in a request handling stack. A compatible middleware must define + # #context which should take the arguments env and app. The first of which + # would be the request environment. The second of which would be the rack + # application that the request would be forwarded to. + class Context + attr_reader :for, :app + + def initialize(app_f, app_r) + raise 'running context does not respond to #context' unless app_f.respond_to? :context + @for, @app = app_f, app_r + end + + def call(env) + @for.context(env, @app) + end + + def recontext(app) + self.class.new(@for, app) + end + + def context(env, app = @app) + recontext(app).call(env) + end + end + + # A case-insensitive Hash that preserves the original case of a + # header when set. + # + # @api private + class HeaderHash < Hash # :nodoc: + def self.[](headers) + if headers.is_a?(HeaderHash) && !headers.frozen? + return headers + else + return self.new(headers) + end + end + + def initialize(hash = {}) + super() + @names = {} + hash.each { |k, v| self[k] = v } + end + + # on dup/clone, we need to duplicate @names hash + def initialize_copy(other) + super + @names = other.names.dup + end + + # on clear, we need to clear @names hash + def clear + super + @names.clear + end + + def each + super do |k, v| + yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v) + end + end + + def to_hash + hash = {} + each { |k, v| hash[k] = v } + hash + end + + def [](k) + super(k) || super(@names[k.downcase]) + end + + def []=(k, v) + canonical = k.downcase.freeze + delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary + @names[canonical] = k + super k, v + end + + def delete(k) + canonical = k.downcase + result = super @names.delete(canonical) + result + end + + def include?(k) + super || @names.include?(k.downcase) + end + + alias_method :has_key?, :include? + alias_method :member?, :include? + alias_method :key?, :include? + + def merge!(other) + other.each { |k, v| self[k] = v } + self + end + + def merge(other) + hash = dup + hash.merge! other + end + + def replace(other) + clear + other.each { |k, v| self[k] = v } + self + end + + protected + def names + @names + end + end + + # Every standard HTTP code mapped to the appropriate message. + # Generated with: + # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \ + # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \ + # puts "#{m[1]} => \x27#{m[2].strip}\x27,"' + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 421 => 'Misdirected Request', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Too Early', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable for Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 509 => 'Bandwidth Limit Exceeded', + 510 => 'Not Extended', + 511 => 'Network Authentication Required' + } + + # Responses with HTTP status codes that should not have an entity body + STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])] + + SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message| + [message.downcase.gsub(/\s|-|'/, '_').to_sym, code] + }.flatten] + + def status_code(status) + if status.is_a?(Symbol) + SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } + else + status.to_i + end + end + + PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact) + + def clean_path_info(path_info) + parts = path_info.split PATH_SEPS + + clean = [] + + parts.each do |part| + next if part.empty? || part == '.' + part == '..' ? clean.pop : clean << part + end + + clean_path = clean.join(::File::SEPARATOR) + clean_path.prepend("/") if parts.empty? || parts.first.empty? + clean_path + end + + NULL_BYTE = "\0" + + def valid_path?(path) + path.valid_encoding? && !path.include?(NULL_BYTE) + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/version.rb new file mode 100644 index 0000000000..aad9c5915a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/version.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Copyright (C) 2007-2019 Leah Neukirchen +# +# Rack is freely distributable under the terms of an MIT-style license. +# See MIT-LICENSE or https://opensource.org/licenses/MIT. + +# The Rack main module, serving as a namespace for all core Rack +# modules and classes. +# +# All modules meant for use in your application are autoloaded here, +# so it should be enough just to require 'rack' in your code. + +module Rack + # The Rack protocol version number implemented. + VERSION = [1, 3] + + # Return the Rack protocol version as a dotted string. + def self.version + VERSION.join(".") + end + + RELEASE = "2.2.3" + + # Return the Rack release as a dotted string. + def self.release + RELEASE + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/rack.gemspec b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/rack.gemspec new file mode 100644 index 0000000000..246ed7c639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/rack.gemspec @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'lib/rack/version' + +Gem::Specification.new do |s| + s.name = "rack" + s.version = Rack::RELEASE + s.platform = Gem::Platform::RUBY + s.summary = "A modular Ruby webserver interface." + s.license = "MIT" + + s.description = <<~EOF + Rack provides a minimal, modular and adaptable interface for developing + web applications in Ruby. By wrapping HTTP requests and responses in + the simplest way possible, it unifies and distills the API for web + servers, web frameworks, and software in between (the so-called + middleware) into a single method call. + EOF + + s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] + + %w(MIT-LICENSE rack.gemspec Rakefile README.rdoc SPEC.rdoc) + + s.bindir = 'bin' + s.executables << 'rackup' + s.require_path = 'lib' + s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.md', 'CONTRIBUTING.md'] + + s.author = 'Leah Neukirchen' + s.email = 'leah@vuxu.org' + + s.homepage = 'https://github.com/rack/rack' + + s.required_ruby_version = '>= 2.3.0' + + s.metadata = { + "bug_tracker_uri" => "https://github.com/rack/rack/issues", + "changelog_uri" => "https://github.com/rack/rack/blob/master/CHANGELOG.md", + "documentation_uri" => "https://rubydoc.info/github/rack/rack", + "source_code_uri" => "https://github.com/rack/rack" + } + + s.add_development_dependency 'minitest', "~> 5.0" + s.add_development_dependency 'minitest-sprint' + s.add_development_dependency 'minitest-global_expectations' + s.add_development_dependency 'rake' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CHANGELOG.md new file mode 100644 index 0000000000..b81a68d5b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CHANGELOG.md @@ -0,0 +1,731 @@ +# Changelog + +All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/). + +## [2.2.6.3] - 2023-03-02 + +- [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts + +## [2.2.6.2] - 2022-01-17 + +- [CVE-2022-44570] Fix ReDoS in Rack::Utils.get_byte_ranges + +## [2.2.6.1] - 2022-01-17 + +- [CVE-2022-44571] Fix ReDoS vulnerability in multipart parser +- [CVE-2022-44572] Forbid control characters in attributes (also ReDoS) + +## [2.2.6] - 2022-01-17 + +- Extend `Rack::MethodOverride` to handle `QueryParser::ParamsTooDeepError` error. ([#2011](https://github.com/rack/rack/pull/2011), [@byroot](https://github.com/byroot)) + +## [2.2.5] - 2022-12-27 + +### Fixed + +- `Rack::URLMap` uses non-deprecated form of `Regexp.new`. ([#1998](https://github.com/rack/rack/pull/1998), [@weizheheng](https://github.com/weizheheng)) + +## [2.2.4] - 2022-06-30 + +- Better support for lower case headers in `Rack::ETag` middleware. ([#1919](https://github.com/rack/rack/pull/1919), [@ioquatix](https://github.com/ioquatix)) +- Use custom exception on params too deep error. ([#1838](https://github.com/rack/rack/pull/1838), [@simi](https://github.com/simi)) + +## [2.2.3.1] - 2022-05-27 + +### Security + +- [CVE-2022-30123] Fix shell escaping issue in Common Logger +- [CVE-2022-30122] Restrict parsing of broken MIME attachments + +## [2.2.3] - 2020-02-11 + +### Security + +- [CVE-2020-8184] Only decode cookie values + +## [2.2.2] - 2020-02-11 + +### Fixed + +- Fix incorrect `Rack::Request#host` value. ([#1591](https://github.com/rack/rack/pull/1591), [@ioquatix](https://github.com/ioquatix)) +- Revert `Rack::Handler::Thin` implementation. ([#1583](https://github.com/rack/rack/pull/1583), [@jeremyevans](https://github.com/jeremyevans)) +- Double assignment is still needed to prevent an "unused variable" warning. ([#1589](https://github.com/rack/rack/pull/1589), [@kamipo](https://github.com/kamipo)) +- Fix to handle same_site option for session pool. ([#1587](https://github.com/rack/rack/pull/1587), [@kamipo](https://github.com/kamipo)) + +## [2.2.1] - 2020-02-09 + +### Fixed + +- Rework `Rack::Request#ip` to handle empty `forwarded_for`. ([#1577](https://github.com/rack/rack/pull/1577), [@ioquatix](https://github.com/ioquatix)) + +## [2.2.0] - 2020-02-08 + +### SPEC Changes + +- `rack.session` request environment entry must respond to `to_hash` and return unfrozen Hash. ([@jeremyevans](https://github.com/jeremyevans)) +- Request environment cannot be frozen. ([@jeremyevans](https://github.com/jeremyevans)) +- CGI values in the request environment with non-ASCII characters must use ASCII-8BIT encoding. ([@jeremyevans](https://github.com/jeremyevans)) +- Improve SPEC/lint relating to SERVER_NAME, SERVER_PORT and HTTP_HOST. ([#1561](https://github.com/rack/rack/pull/1561), [@ioquatix](https://github.com/ioquatix)) + +### Added + +- `rackup` supports multiple `-r` options and will require all arguments. ([@jeremyevans](https://github.com/jeremyevans)) +- `Server` supports an array of paths to require for the `:require` option. ([@khotta](https://github.com/khotta)) +- `Files` supports multipart range requests. ([@fatkodima](https://github.com/fatkodima)) +- `Multipart::UploadedFile` supports an IO-like object instead of using the filesystem, using `:filename` and `:io` options. ([@jeremyevans](https://github.com/jeremyevans)) +- `Multipart::UploadedFile` supports keyword arguments `:path`, `:content_type`, and `:binary` in addition to positional arguments. ([@jeremyevans](https://github.com/jeremyevans)) +- `Static` supports a `:cascade` option for calling the app if there is no matching file. ([@jeremyevans](https://github.com/jeremyevans)) +- `Session::Abstract::SessionHash#dig`. ([@jeremyevans](https://github.com/jeremyevans)) +- `Response.[]` and `MockResponse.[]` for creating instances using status, headers, and body. ([@ioquatix](https://github.com/ioquatix)) +- Convenient cache and content type methods for `Rack::Response`. ([#1555](https://github.com/rack/rack/pull/1555), [@ioquatix](https://github.com/ioquatix)) + +### Changed + +- `Request#params` no longer rescues EOFError. ([@jeremyevans](https://github.com/jeremyevans)) +- `Directory` uses a streaming approach, significantly improving time to first byte for large directories. ([@jeremyevans](https://github.com/jeremyevans)) +- `Directory` no longer includes a Parent directory link in the root directory index. ([@jeremyevans](https://github.com/jeremyevans)) +- `QueryParser#parse_nested_query` uses original backtrace when reraising exception with new class. ([@jeremyevans](https://github.com/jeremyevans)) +- `ConditionalGet` follows RFC 7232 precedence if both If-None-Match and If-Modified-Since headers are provided. ([@jeremyevans](https://github.com/jeremyevans)) +- `.ru` files supports the `frozen-string-literal` magic comment. ([@eregon](https://github.com/eregon)) +- Rely on autoload to load constants instead of requiring internal files, make sure to require 'rack' and not just 'rack/...'. ([@jeremyevans](https://github.com/jeremyevans)) +- `Etag` will continue sending ETag even if the response should not be cached. ([@henm](https://github.com/henm)) +- `Request#host_with_port` no longer includes a colon for a missing or empty port. ([@AlexWayfer](https://github.com/AlexWayfer)) +- All handlers uses keywords arguments instead of an options hash argument. ([@ioquatix](https://github.com/ioquatix)) +- `Files` handling of range requests no longer return a body that supports `to_path`, to ensure range requests are handled correctly. ([@jeremyevans](https://github.com/jeremyevans)) +- `Multipart::Generator` only includes `Content-Length` for files with paths, and `Content-Disposition` `filename` if the `UploadedFile` instance has one. ([@jeremyevans](https://github.com/jeremyevans)) +- `Request#ssl?` is true for the `wss` scheme (secure websockets). ([@jeremyevans](https://github.com/jeremyevans)) +- `Rack::HeaderHash` is memoized by default. ([#1549](https://github.com/rack/rack/pull/1549), [@ioquatix](https://github.com/ioquatix)) +- `Rack::Directory` allow directory traversal inside root directory. ([#1417](https://github.com/rack/rack/pull/1417), [@ThomasSevestre](https://github.com/ThomasSevestre)) +- Sort encodings by server preference. ([#1184](https://github.com/rack/rack/pull/1184), [@ioquatix](https://github.com/ioquatix), [@wjordan](https://github.com/wjordan)) +- Rework host/hostname/authority implementation in `Rack::Request`. `#host` and `#host_with_port` have been changed to correctly return IPv6 addresses formatted with square brackets, as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). ([#1561](https://github.com/rack/rack/pull/1561), [@ioquatix](https://github.com/ioquatix)) +- `Rack::Builder` parsing options on first `#\` line is deprecated. ([#1574](https://github.com/rack/rack/pull/1574), [@ioquatix](https://github.com/ioquatix)) + +### Removed + +- `Directory#path` as it was not used and always returned nil. ([@jeremyevans](https://github.com/jeremyevans)) +- `BodyProxy#each` as it was only needed to work around a bug in Ruby <1.9.3. ([@jeremyevans](https://github.com/jeremyevans)) +- `URLMap::INFINITY` and `URLMap::NEGATIVE_INFINITY`, in favor of `Float::INFINITY`. ([@ch1c0t](https://github.com/ch1c0t)) +- Deprecation of `Rack::File`. It will be deprecated again in rack 2.2 or 3.0. ([@rafaelfranca](https://github.com/rafaelfranca)) +- Support for Ruby 2.2 as it is well past EOL. ([@ioquatix](https://github.com/ioquatix)) +- Remove `Rack::Files#response_body` as the implementation was broken. ([#1153](https://github.com/rack/rack/pull/1153), [@ioquatix](https://github.com/ioquatix)) +- Remove `SERVER_ADDR` which was never part of the original SPEC. ([#1573](https://github.com/rack/rack/pull/1573), [@ioquatix](https://github.com/ioquatix)) + +### Fixed + +- `Directory` correctly handles root paths containing glob metacharacters. ([@jeremyevans](https://github.com/jeremyevans)) +- `Cascade` uses a new response object for each call if initialized with no apps. ([@jeremyevans](https://github.com/jeremyevans)) +- `BodyProxy` correctly delegates keyword arguments to the body object on Ruby 2.7+. ([@jeremyevans](https://github.com/jeremyevans)) +- `BodyProxy#method` correctly handles methods delegated to the body object. ([@jeremyevans](https://github.com/jeremyevans)) +- `Request#host` and `Request#host_with_port` handle IPv6 addresses correctly. ([@AlexWayfer](https://github.com/AlexWayfer)) +- `Lint` checks when response hijacking that `rack.hijack` is called with a valid object. ([@jeremyevans](https://github.com/jeremyevans)) +- `Response#write` correctly updates `Content-Length` if initialized with a body. ([@jeremyevans](https://github.com/jeremyevans)) +- `CommonLogger` includes `SCRIPT_NAME` when logging. ([@Erol](https://github.com/Erol)) +- `Utils.parse_nested_query` correctly handles empty queries, using an empty instance of the params class instead of a hash. ([@jeremyevans](https://github.com/jeremyevans)) +- `Directory` correctly escapes paths in links. ([@yous](https://github.com/yous)) +- `Request#delete_cookie` and related `Utils` methods handle `:domain` and `:path` options in same call. ([@jeremyevans](https://github.com/jeremyevans)) +- `Request#delete_cookie` and related `Utils` methods do an exact match on `:domain` and `:path` options. ([@jeremyevans](https://github.com/jeremyevans)) +- `Static` no longer adds headers when a gzipped file request has a 304 response. ([@chooh](https://github.com/chooh)) +- `ContentLength` sets `Content-Length` response header even for bodies not responding to `to_ary`. ([@jeremyevans](https://github.com/jeremyevans)) +- Thin handler supports options passed directly to `Thin::Controllers::Controller`. ([@jeremyevans](https://github.com/jeremyevans)) +- WEBrick handler no longer ignores `:BindAddress` option. ([@jeremyevans](https://github.com/jeremyevans)) +- `ShowExceptions` handles invalid POST data. ([@jeremyevans](https://github.com/jeremyevans)) +- Basic authentication requires a password, even if the password is empty. ([@jeremyevans](https://github.com/jeremyevans)) +- `Lint` checks response is array with 3 elements, per SPEC. ([@jeremyevans](https://github.com/jeremyevans)) +- Support for using `:SSLEnable` option when using WEBrick handler. (Gregor Melhorn) +- Close response body after buffering it when buffering. ([@ioquatix](https://github.com/ioquatix)) +- Only accept `;` as delimiter when parsing cookies. ([@mrageh](https://github.com/mrageh)) +- `Utils::HeaderHash#clear` clears the name mapping as well. ([@raxoft](https://github.com/raxoft)) +- Support for passing `nil` `Rack::Files.new`, which notably fixes Rails' current `ActiveStorage::FileServer` implementation. ([@ioquatix](https://github.com/ioquatix)) + +### Documentation + +- CHANGELOG updates. ([@aupajo](https://github.com/aupajo)) +- Added [CONTRIBUTING](CONTRIBUTING.md). ([@dblock](https://github.com/dblock)) + +## [2.1.2] - 2020-01-27 + +- Fix multipart parser for some files to prevent denial of service ([@aiomaster](https://github.com/aiomaster)) +- Fix `Rack::Builder#use` with keyword arguments ([@kamipo](https://github.com/kamipo)) +- Skip deflating in Rack::Deflater if Content-Length is 0 ([@jeremyevans](https://github.com/jeremyevans)) +- Remove `SessionHash#transform_keys`, no longer needed ([@pavel](https://github.com/pavel)) +- Add to_hash to wrap Hash and Session classes ([@oleh-demyanyuk](https://github.com/oleh-demyanyuk)) +- Handle case where session id key is requested but missing ([@jeremyevans](https://github.com/jeremyevans)) + +## [2.1.1] - 2020-01-12 + +- Remove `Rack::Chunked` from `Rack::Server` default middleware. ([#1475](https://github.com/rack/rack/pull/1475), [@ioquatix](https://github.com/ioquatix)) +- Restore support for code relying on `SessionId#to_s`. ([@jeremyevans](https://github.com/jeremyevans)) + +## [2.1.0] - 2020-01-10 + +### Added + +- Add support for `SameSite=None` cookie value. ([@hennikul](https://github.com/hennikul)) +- Add trailer headers. ([@eileencodes](https://github.com/eileencodes)) +- Add MIME Types for video streaming. ([@styd](https://github.com/styd)) +- Add MIME Type for WASM. ([@buildrtech](https://github.com/buildrtech)) +- Add `Early Hints(103)` to status codes. ([@egtra](https://github.com/egtra)) +- Add `Too Early(425)` to status codes. ([@y-yagi]((https://github.com/y-yagi))) +- Add `Bandwidth Limit Exceeded(509)` to status codes. ([@CJKinni](https://github.com/CJKinni)) +- Add method for custom `ip_filter`. ([@svcastaneda](https://github.com/svcastaneda)) +- Add boot-time profiling capabilities to `rackup`. ([@tenderlove](https://github.com/tenderlove)) +- Add multi mapping support for `X-Accel-Mappings` header. ([@yoshuki](https://github.com/yoshuki)) +- Add `sync: false` option to `Rack::Deflater`. (Eric Wong) +- Add `Builder#freeze_app` to freeze application and all middleware instances. ([@jeremyevans](https://github.com/jeremyevans)) +- Add API to extract cookies from `Rack::MockResponse`. ([@petercline](https://github.com/petercline)) + +### Changed + +- Don't propagate nil values from middleware. ([@ioquatix](https://github.com/ioquatix)) +- Lazily initialize the response body and only buffer it if required. ([@ioquatix](https://github.com/ioquatix)) +- Fix deflater zlib buffer errors on empty body part. ([@felixbuenemann](https://github.com/felixbuenemann)) +- Set `X-Accel-Redirect` to percent-encoded path. ([@diskkid](https://github.com/diskkid)) +- Remove unnecessary buffer growing when parsing multipart. ([@tainoe](https://github.com/tainoe)) +- Expand the root path in `Rack::Static` upon initialization. ([@rosenfeld](https://github.com/rosenfeld)) +- Make `ShowExceptions` work with binary data. ([@axyjo](https://github.com/axyjo)) +- Use buffer string when parsing multipart requests. ([@janko-m](https://github.com/janko-m)) +- Support optional UTF-8 Byte Order Mark (BOM) in config.ru. ([@mikegee](https://github.com/mikegee)) +- Handle `X-Forwarded-For` with optional port. ([@dpritchett](https://github.com/dpritchett)) +- Use `Time#httpdate` format for Expires, as proposed by RFC 7231. ([@nanaya](https://github.com/nanaya)) +- Make `Utils.status_code` raise an error when the status symbol is invalid instead of `500`. ([@adambutler](https://github.com/adambutler)) +- Rename `Request::SCHEME_WHITELIST` to `Request::ALLOWED_SCHEMES`. +- Make `Multipart::Parser.get_filename` accept files with `+` in their name. ([@lucaskanashiro](https://github.com/lucaskanashiro)) +- Add Falcon to the default handler fallbacks. ([@ioquatix](https://github.com/ioquatix)) +- Update codebase to avoid string mutations in preparation for `frozen_string_literals`. ([@pat](https://github.com/pat)) +- Change `MockRequest#env_for` to rely on the input optionally responding to `#size` instead of `#length`. ([@janko](https://github.com/janko)) +- Rename `Rack::File` -> `Rack::Files` and add deprecation notice. ([@postmodern](https://github.com/postmodern)). +- Prefer Base64 “strict encoding” for Base64 cookies. ([@ioquatix](https://github.com/ioquatix)) + +### Removed + +- Remove `to_ary` from Response ([@tenderlove](https://github.com/tenderlove)) +- Deprecate `Rack::Session::Memcache` in favor of `Rack::Session::Dalli` from dalli gem ([@fatkodima](https://github.com/fatkodima)) + +### Fixed + +- Eliminate warnings for Ruby 2.7. ([@osamtimizer](https://github.com/osamtimizer])) + +### Documentation + +- Update broken example in `Session::Abstract::ID` documentation. ([tonytonyjan](https://github.com/tonytonyjan)) +- Add Padrino to the list of frameworks implementing Rack. ([@wikimatze](https://github.com/wikimatze)) +- Remove Mongrel from the suggested server options in the help output. ([@tricknotes](https://github.com/tricknotes)) +- Replace `HISTORY.md` and `NEWS.md` with `CHANGELOG.md`. ([@twitnithegirl](https://github.com/twitnithegirl)) +- CHANGELOG updates. ([@drenmi](https://github.com/Drenmi), [@p8](https://github.com/p8)) + +## [2.0.8] - 2019-12-08 + +### Security + +- [[CVE-2019-16782](https://nvd.nist.gov/vuln/detail/CVE-2019-16782)] Prevent timing attacks targeted at session ID lookup. BREAKING CHANGE: Session ID is now a SessionId instance instead of a String. ([@tenderlove](https://github.com/tenderlove), [@rafaelfranca](https://github.com/rafaelfranca)) + +## [1.6.12] - 2019-12-08 + +### Security + +- [[CVE-2019-16782](https://nvd.nist.gov/vuln/detail/CVE-2019-16782)] Prevent timing attacks targeted at session ID lookup. BREAKING CHANGE: Session ID is now a SessionId instance instead of a String. ([@tenderlove](https://github.com/tenderlove), [@rafaelfranca](https://github.com/rafaelfranca)) + +## [2.0.7] - 2019-04-02 + +### Fixed + +- Remove calls to `#eof?` on Rack input in `Multipart::Parser`, as this breaks the specification. ([@matthewd](https://github.com/matthewd)) +- Preserve forwarded IP addresses for trusted proxy chains. ([@SamSaffron](https://github.com/SamSaffron)) + +## [2.0.6] - 2018-11-05 + +### Fixed + +- [[CVE-2018-16470](https://nvd.nist.gov/vuln/detail/CVE-2018-16470)] Reduce buffer size of `Multipart::Parser` to avoid pathological parsing. ([@tenderlove](https://github.com/tenderlove)) +- Fix a call to a non-existing method `#accepts_html` in the `ShowExceptions` middleware. ([@tomelm](https://github.com/tomelm)) +- [[CVE-2018-16471](https://nvd.nist.gov/vuln/detail/CVE-2018-16471)] Whitelist HTTP and HTTPS schemes in `Request#scheme` to prevent a possible XSS attack. ([@PatrickTulskie](https://github.com/PatrickTulskie)) + +## [2.0.5] - 2018-04-23 + +### Fixed + +- Record errors originating from invalid UTF8 in `MethodOverride` middleware instead of breaking. ([@mclark](https://github.com/mclark)) + +## [2.0.4] - 2018-01-31 + +### Changed + +- Ensure the `Lock` middleware passes the original `env` object. ([@lugray](https://github.com/lugray)) +- Improve performance of `Multipart::Parser` when uploading large files. ([@tompng](https://github.com/tompng)) +- Increase buffer size in `Multipart::Parser` for better performance. ([@jkowens](https://github.com/jkowens)) +- Reduce memory usage of `Multipart::Parser` when uploading large files. ([@tompng](https://github.com/tompng)) +- Replace ConcurrentRuby dependency with native `Queue`. ([@devmchakan](https://github.com/devmchakan)) + +### Fixed + +- Require the correct digest algorithm in the `ETag` middleware. ([@matthewd](https://github.com/matthewd)) + +### Documentation + +- Update homepage links to use SSL. ([@hugoabonizio](https://github.com/hugoabonizio)) + +## [2.0.3] - 2017-05-15 + +### Changed + +- Ensure `env` values are ASCII 8-bit encoded. ([@eileencodes](https://github.com/eileencodes)) + +### Fixed + +- Prevent exceptions when a class with mixins inherits from `Session::Abstract::ID`. ([@jnraine](https://github.com/jnraine)) + +## [2.0.2] - 2017-05-08 + +### Added + +- Allow `Session::Abstract::SessionHash#fetch` to accept a block with a default value. ([@yannvanhalewyn](https://github.com/yannvanhalewyn)) +- Add `Builder#freeze_app` to freeze application and all middleware. ([@jeremyevans](https://github.com/jeremyevans)) + +### Changed + +- Freeze default session options to avoid accidental mutation. ([@kirs](https://github.com/kirs)) +- Detect partial hijack without hash headers. ([@devmchakan](https://github.com/devmchakan)) +- Update tests to use MiniTest 6 matchers. ([@tonytonyjan](https://github.com/tonytonyjan)) +- Allow 205 Reset Content responses to set a Content-Length, as RFC 7231 proposes setting this to 0. ([@devmchakan](https://github.com/devmchakan)) + +### Fixed + +- Handle `NULL` bytes in multipart filenames. ([@casperisfine](https://github.com/casperisfine)) +- Remove warnings due to miscapitalized global. ([@ioquatix](https://github.com/ioquatix)) +- Prevent exceptions caused by a race condition on multi-threaded servers. ([@sophiedeziel](https://github.com/sophiedeziel)) +- Add RDoc as an explicit depencency for `doc` group. ([@tonytonyjan](https://github.com/tonytonyjan)) +- Record errors originating from `Multipart::Parser` in the `MethodOverride` middleware instead of letting them bubble up. ([@carlzulauf](https://github.com/carlzulauf)) +- Remove remaining use of removed `Utils#bytesize` method from the `File` middleware. ([@brauliomartinezlm](https://github.com/brauliomartinezlm)) + +### Removed + +- Remove `deflate` encoding support to reduce caching overhead. ([@devmchakan](https://github.com/devmchakan)) + +### Documentation + +- Update broken example in `Deflater` documentation. ([@mwpastore](https://github.com/mwpastore)) + +## [2.0.1] - 2016-06-30 + +### Changed + +- Remove JSON as an explicit dependency. ([@mperham](https://github.com/mperham)) + + +# History/News Archive +Items below this line are from the previously maintained HISTORY.md and NEWS.md files. + +## [2.0.0.rc1] 2016-05-06 +- Rack::Session::Abstract::ID is deprecated. Please change to use Rack::Session::Abstract::Persisted + +## [2.0.0.alpha] 2015-12-04 +- First-party "SameSite" cookies. Browsers omit SameSite cookies from third-party requests, closing the door on many CSRF attacks. +- Pass `same_site: true` (or `:strict`) to enable: response.set_cookie 'foo', value: 'bar', same_site: true or `same_site: :lax` to use Lax enforcement: response.set_cookie 'foo', value: 'bar', same_site: :lax +- Based on version 7 of the Same-site Cookies internet draft: + https://tools.ietf.org/html/draft-west-first-party-cookies-07 +- Thanks to Ben Toews (@mastahyeti) and Bob Long (@bobjflong) for updating to drafts 5 and 7. +- Add `Rack::Events` middleware for adding event based middleware: middleware that does not care about the response body, but only cares about doing work at particular points in the request / response lifecycle. +- Add `Rack::Request#authority` to calculate the authority under which the response is being made (this will be handy for h2 pushes). +- Add `Rack::Response::Helpers#cache_control` and `cache_control=`. Use this for setting cache control headers on your response objects. +- Add `Rack::Response::Helpers#etag` and `etag=`. Use this for setting etag values on the response. +- Introduce `Rack::Response::Helpers#add_header` to add a value to a multi-valued response header. Implemented in terms of other `Response#*_header` methods, so it's available to any response-like class that includes the `Helpers` module. +- Add `Rack::Request#add_header` to match. +- `Rack::Session::Abstract::ID` IS DEPRECATED. Please switch to `Rack::Session::Abstract::Persisted`. `Rack::Session::Abstract::Persisted` uses a request object rather than the `env` hash. +- Pull `ENV` access inside the request object in to a module. This will help with legacy Request objects that are ENV based but don't want to inherit from Rack::Request +- Move most methods on the `Rack::Request` to a module `Rack::Request::Helpers` and use public API to get values from the request object. This enables users to mix `Rack::Request::Helpers` in to their own objects so they can implement `(get|set|fetch|each)_header` as they see fit (for example a proxy object). +- Files and directories with + in the name are served correctly. Rather than unescaping paths like a form, we unescape with a URI parser using `Rack::Utils.unescape_path`. Fixes #265 +- Tempfiles are automatically closed in the case that there were too + many posted. +- Added methods for manipulating response headers that don't assume + they're stored as a Hash. Response-like classes may include the + Rack::Response::Helpers module if they define these methods: + - Rack::Response#has_header? + - Rack::Response#get_header + - Rack::Response#set_header + - Rack::Response#delete_header +- Introduce Util.get_byte_ranges that will parse the value of the HTTP_RANGE string passed to it without depending on the `env` hash. `byte_ranges` is deprecated in favor of this method. +- Change Session internals to use Request objects for looking up session information. This allows us to only allocate one request object when dealing with session objects (rather than doing it every time we need to manipulate cookies, etc). +- Add `Rack::Request#initialize_copy` so that the env is duped when the request gets duped. +- Added methods for manipulating request specific data. This includes + data set as CGI parameters, and just any arbitrary data the user wants + to associate with a particular request. New methods: + - Rack::Request#has_header? + - Rack::Request#get_header + - Rack::Request#fetch_header + - Rack::Request#each_header + - Rack::Request#set_header + - Rack::Request#delete_header +- lib/rack/utils.rb: add a method for constructing "delete" cookie + headers. This allows us to construct cookie headers without depending + on the side effects of mutating a hash. +- Prevent extremely deep parameters from being parsed. CVE-2015-3225 + +## [1.6.1] 2015-05-06 + - Fix CVE-2014-9490, denial of service attack in OkJson + - Use a monotonic time for Rack::Runtime, if available + - RACK_MULTIPART_LIMIT changed to RACK_MULTIPART_PART_LIMIT (RACK_MULTIPART_LIMIT is deprecated and will be removed in 1.7.0) + +## [1.5.3] 2015-05-06 + - Fix CVE-2014-9490, denial of service attack in OkJson + - Backport bug fixes to 1.5 series + +## [1.6.0] 2014-01-18 + - Response#unauthorized? helper + - Deflater now accepts an options hash to control compression on a per-request level + - Builder#warmup method for app preloading + - Request#accept_language method to extract HTTP_ACCEPT_LANGUAGE + - Add quiet mode of rack server, rackup --quiet + - Update HTTP Status Codes to RFC 7231 + - Less strict header name validation according to RFC 2616 + - SPEC updated to specify headers conform to RFC7230 specification + - Etag correctly marks etags as weak + - Request#port supports multiple x-http-forwarded-proto values + - Utils#multipart_part_limit configures the maximum number of parts a request can contain + - Default host to localhost when in development mode + - Various bugfixes and performance improvements + +## [1.5.2] 2013-02-07 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + - Fix CVE-2013-0262, symlink path traversal in Rack::File + - Add various methods to Session for enhanced Rails compatibility + - Request#trusted_proxy? now only matches whole strings + - Add JSON cookie coder, to be default in Rack 1.6+ due to security concerns + - URLMap host matching in environments that don't set the Host header fixed + - Fix a race condition that could result in overwritten pidfiles + - Various documentation additions + +## [1.4.5] 2013-02-07 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + - Fix CVE-2013-0262, symlink path traversal in Rack::File + +## [1.1.6, 1.2.8, 1.3.10] 2013-02-07 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + +## [1.5.1] 2013-01-28 + - Rack::Lint check_hijack now conforms to other parts of SPEC + - Added hash-like methods to Abstract::ID::SessionHash for compatibility + - Various documentation corrections + +## [1.5.0] 2013-01-21 + - Introduced hijack SPEC, for before-response and after-response hijacking + - SessionHash is no longer a Hash subclass + - Rack::File cache_control parameter is removed, in place of headers options + - Rack::Auth::AbstractRequest#scheme now yields strings, not symbols + - Rack::Utils cookie functions now format expires in RFC 2822 format + - Rack::File now has a default mime type + - rackup -b 'run Rack::Files.new(".")', option provides command line configs + - Rack::Deflater will no longer double encode bodies + - Rack::Mime#match? provides convenience for Accept header matching + - Rack::Utils#q_values provides splitting for Accept headers + - Rack::Utils#best_q_match provides a helper for Accept headers + - Rack::Handler.pick provides convenience for finding available servers + - Puma added to the list of default servers (preferred over Webrick) + - Various middleware now correctly close body when replacing it + - Rack::Request#params is no longer persistent with only GET params + - Rack::Request#update_param and #delete_param provide persistent operations + - Rack::Request#trusted_proxy? now returns true for local unix sockets + - Rack::Response no longer forces Content-Types + - Rack::Sendfile provides local mapping configuration options + - Rack::Utils#rfc2109 provides old netscape style time output + - Updated HTTP status codes + - Ruby 1.8.6 likely no longer passes tests, and is no longer fully supported + +## [1.4.4, 1.3.9, 1.2.7, 1.1.5] 2013-01-13 + - [SEC] Rack::Auth::AbstractRequest no longer symbolizes arbitrary strings + - Fixed erroneous test case in the 1.3.x series + +## [1.4.3] 2013-01-07 + - Security: Prevent unbounded reads in large multipart boundaries + +## [1.3.8] 2013-01-07 + - Security: Prevent unbounded reads in large multipart boundaries + +## [1.4.2] 2013-01-06 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + - Updated URI backports + - Fix URI backport version matching, and silence constant warnings + - Correct parameter parsing with empty values + - Correct rackup '-I' flag, to allow multiple uses + - Correct rackup pidfile handling + - Report rackup line numbers correctly + - Fix request loops caused by non-stale nonces with time limits + - Fix reloader on Windows + - Prevent infinite recursions from Response#to_ary + - Various middleware better conforms to the body close specification + - Updated language for the body close specification + - Additional notes regarding ECMA escape compatibility issues + - Fix the parsing of multiple ranges in range headers + - Prevent errors from empty parameter keys + - Added PATCH verb to Rack::Request + - Various documentation updates + - Fix session merge semantics (fixes rack-test) + - Rack::Static :index can now handle multiple directories + - All tests now utilize Rack::Lint (special thanks to Lars Gierth) + - Rack::File cache_control parameter is now deprecated, and removed by 1.5 + - Correct Rack::Directory script name escaping + - Rack::Static supports header rules for sophisticated configurations + - Multipart parsing now works without a Content-Length header + - New logos courtesy of Zachary Scott! + - Rack::BodyProxy now explicitly defines #each, useful for C extensions + - Cookies that are not URI escaped no longer cause exceptions + +## [1.3.7] 2013-01-06 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + - Updated URI backports + - Fix URI backport version matching, and silence constant warnings + - Correct parameter parsing with empty values + - Correct rackup '-I' flag, to allow multiple uses + - Correct rackup pidfile handling + - Report rackup line numbers correctly + - Fix request loops caused by non-stale nonces with time limits + - Fix reloader on Windows + - Prevent infinite recursions from Response#to_ary + - Various middleware better conforms to the body close specification + - Updated language for the body close specification + - Additional notes regarding ECMA escape compatibility issues + - Fix the parsing of multiple ranges in range headers + +## [1.2.6] 2013-01-06 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + +## [1.1.4] 2013-01-06 + - Add warnings when users do not provide a session secret + +## [1.4.1] 2012-01-22 + - Alter the keyspace limit calculations to reduce issues with nested params + - Add a workaround for multipart parsing where files contain unescaped "%" + - Added Rack::Response::Helpers#method_not_allowed? (code 405) + - Rack::File now returns 404 for illegal directory traversals + - Rack::File now returns 405 for illegal methods (non HEAD/GET) + - Rack::Cascade now catches 405 by default, as well as 404 + - Cookies missing '--' no longer cause an exception to be raised + - Various style changes and documentation spelling errors + - Rack::BodyProxy always ensures to execute its block + - Additional test coverage around cookies and secrets + - Rack::Session::Cookie can now be supplied either secret or old_secret + - Tests are no longer dependent on set order + - Rack::Static no longer defaults to serving index files + - Rack.release was fixed + +## [1.4.0] 2011-12-28 + - Ruby 1.8.6 support has officially been dropped. Not all tests pass. + - Raise sane error messages for broken config.ru + - Allow combining run and map in a config.ru + - Rack::ContentType will not set Content-Type for responses without a body + - Status code 205 does not send a response body + - Rack::Response::Helpers will not rely on instance variables + - Rack::Utils.build_query no longer outputs '=' for nil query values + - Various mime types added + - Rack::MockRequest now supports HEAD + - Rack::Directory now supports files that contain RFC3986 reserved chars + - Rack::File now only supports GET and HEAD requests + - Rack::Server#start now passes the block to Rack::Handler::#run + - Rack::Static now supports an index option + - Added the Teapot status code + - rackup now defaults to Thin instead of Mongrel (if installed) + - Support added for HTTP_X_FORWARDED_SCHEME + - Numerous bug fixes, including many fixes for new and alternate rubies + +## [1.1.3] 2011-12-28 + - Security fix. http://www.ocert.org/advisories/ocert-2011-003.html + Further information here: http://jruby.org/2011/12/27/jruby-1-6-5-1 + +## [1.3.5] 2011-10-17 + - Fix annoying warnings caused by the backport in 1.3.4 + +## [1.3.4] 2011-10-01 + - Backport security fix from 1.9.3, also fixes some roundtrip issues in URI + - Small documentation update + - Fix an issue where BodyProxy could cause an infinite recursion + - Add some supporting files for travis-ci + +## [1.2.4] 2011-09-16 + - Fix a bug with MRI regex engine to prevent XSS by malformed unicode + +## [1.3.3] 2011-09-16 + - Fix bug with broken query parameters in Rack::ShowExceptions + - Rack::Request#cookies no longer swallows exceptions on broken input + - Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine + - Rack::ConditionalGet handles broken If-Modified-Since helpers + +## [1.3.2] 2011-07-16 + - Fix for Rails and rack-test, Rack::Utils#escape calls to_s + +## [1.3.1] 2011-07-13 + - Fix 1.9.1 support + - Fix JRuby support + - Properly handle $KCODE in Rack::Utils.escape + - Make method_missing/respond_to behavior consistent for Rack::Lock, + Rack::Auth::Digest::Request and Rack::Multipart::UploadedFile + - Reenable passing rack.session to session middleware + - Rack::CommonLogger handles streaming responses correctly + - Rack::MockResponse calls close on the body object + - Fix a DOS vector from MRI stdlib backport + +## [1.2.3] 2011-05-22 + - Pulled in relevant bug fixes from 1.3 + - Fixed 1.8.6 support + +## [1.3.0] 2011-05-22 + - Various performance optimizations + - Various multipart fixes + - Various multipart refactors + - Infinite loop fix for multipart + - Test coverage for Rack::Server returns + - Allow files with '..', but not path components that are '..' + - rackup accepts handler-specific options on the command line + - Request#params no longer merges POST into GET (but returns the same) + - Use URI.encode_www_form_component instead. Use core methods for escaping. + - Allow multi-line comments in the config file + - Bug L#94 reported by Nikolai Lugovoi, query parameter unescaping. + - Rack::Response now deletes Content-Length when appropriate + - Rack::Deflater now supports streaming + - Improved Rack::Handler loading and searching + - Support for the PATCH verb + - env['rack.session.options'] now contains session options + - Cookies respect renew + - Session middleware uses SecureRandom.hex + +## [1.2.2, 1.1.2] 2011-03-13 + - Security fix in Rack::Auth::Digest::MD5: when authenticator + returned nil, permission was granted on empty password. + +## [1.2.1] 2010-06-15 + - Make CGI handler rewindable + - Rename spec/ to test/ to not conflict with SPEC on lesser + operating systems + +## [1.2.0] 2010-06-13 + - Removed Camping adapter: Camping 2.0 supports Rack as-is + - Removed parsing of quoted values + - Add Request.trace? and Request.options? + - Add mime-type for .webm and .htc + - Fix HTTP_X_FORWARDED_FOR + - Various multipart fixes + - Switch test suite to bacon + +## [1.1.0] 2010-01-03 + - Moved Auth::OpenID to rack-contrib. + - SPEC change that relaxes Lint slightly to allow subclasses of the + required types + - SPEC change to document rack.input binary mode in greator detail + - SPEC define optional rack.logger specification + - File servers support X-Cascade header + - Imported Config middleware + - Imported ETag middleware + - Imported Runtime middleware + - Imported Sendfile middleware + - New Logger and NullLogger middlewares + - Added mime type for .ogv and .manifest. + - Don't squeeze PATH_INFO slashes + - Use Content-Type to determine POST params parsing + - Update Rack::Utils::HTTP_STATUS_CODES hash + - Add status code lookup utility + - Response should call #to_i on the status + - Add Request#user_agent + - Request#host knows about forwarded host + - Return an empty string for Request#host if HTTP_HOST and + SERVER_NAME are both missing + - Allow MockRequest to accept hash params + - Optimizations to HeaderHash + - Refactored rackup into Rack::Server + - Added Utils.build_nested_query to complement Utils.parse_nested_query + - Added Utils::Multipart.build_multipart to complement + Utils::Multipart.parse_multipart + - Extracted set and delete cookie helpers into Utils so they can be + used outside Response + - Extract parse_query and parse_multipart in Request so subclasses + can change their behavior + - Enforce binary encoding in RewindableInput + - Set correct external_encoding for handlers that don't use RewindableInput + +## [1.0.1] 2009-10-18 + - Bump remainder of rack.versions. + - Support the pure Ruby FCGI implementation. + - Fix for form names containing "=": split first then unescape components + - Fixes the handling of the filename parameter with semicolons in names. + - Add anchor to nested params parsing regexp to prevent stack overflows + - Use more compatible gzip write api instead of "<<". + - Make sure that Reloader doesn't break when executed via ruby -e + - Make sure WEBrick respects the :Host option + - Many Ruby 1.9 fixes. + +## [1.0.0] 2009-04-25 + - SPEC change: Rack::VERSION has been pushed to [1,0]. + - SPEC change: header values must be Strings now, split on "\n". + - SPEC change: Content-Length can be missing, in this case chunked transfer + encoding is used. + - SPEC change: rack.input must be rewindable and support reading into + a buffer, wrap with Rack::RewindableInput if it isn't. + - SPEC change: rack.session is now specified. + - SPEC change: Bodies can now additionally respond to #to_path with + a filename to be served. + - NOTE: String bodies break in 1.9, use an Array consisting of a + single String instead. + - New middleware Rack::Lock. + - New middleware Rack::ContentType. + - Rack::Reloader has been rewritten. + - Major update to Rack::Auth::OpenID. + - Support for nested parameter parsing in Rack::Response. + - Support for redirects in Rack::Response. + - HttpOnly cookie support in Rack::Response. + - The Rakefile has been rewritten. + - Many bugfixes and small improvements. + +## [0.9.1] 2009-01-09 + - Fix directory traversal exploits in Rack::File and Rack::Directory. + +## [0.9] 2009-01-06 + - Rack is now managed by the Rack Core Team. + - Rack::Lint is stricter and follows the HTTP RFCs more closely. + - Added ConditionalGet middleware. + - Added ContentLength middleware. + - Added Deflater middleware. + - Added Head middleware. + - Added MethodOverride middleware. + - Rack::Mime now provides popular MIME-types and their extension. + - Mongrel Header now streams. + - Added Thin handler. + - Official support for swiftiplied Mongrel. + - Secure cookies. + - Made HeaderHash case-preserving. + - Many bugfixes and small improvements. + +## [0.4] 2008-08-21 + - New middleware, Rack::Deflater, by Christoffer Sawicki. + - OpenID authentication now needs ruby-openid 2. + - New Memcache sessions, by blink. + - Explicit EventedMongrel handler, by Joshua Peek + - Rack::Reloader is not loaded in rackup development mode. + - rackup can daemonize with -D. + - Many bugfixes, especially for pool sessions, URLMap, thread safety + and tempfile handling. + - Improved tests. + - Rack moved to Git. + +## [0.3] 2008-02-26 + - LiteSpeed handler, by Adrian Madrid. + - SCGI handler, by Jeremy Evans. + - Pool sessions, by blink. + - OpenID authentication, by blink. + - :Port and :File options for opening FastCGI sockets, by blink. + - Last-Modified HTTP header for Rack::File, by blink. + - Rack::Builder#use now accepts blocks, by Corey Jewett. + (See example/protectedlobster.ru) + - HTTP status 201 can contain a Content-Type and a body now. + - Many bugfixes, especially related to Cookie handling. + +## [0.2] 2007-05-16 + - HTTP Basic authentication. + - Cookie Sessions. + - Static file handler. + - Improved Rack::Request. + - Improved Rack::Response. + - Added Rack::ShowStatus, for better default error messages. + - Bug fixes in the Camping adapter. + - Removed Rails adapter, was too alpha. + +## [0.1] 2007-03-03 diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CONTRIBUTING.md b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CONTRIBUTING.md new file mode 100644 index 0000000000..70a27468e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/CONTRIBUTING.md @@ -0,0 +1,136 @@ +Contributing to Rack +===================== + +Rack is work of [hundreds of contributors](https://github.com/rack/rack/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rack/rack/pulls), [propose features and discuss issues](https://github.com/rack/rack/issues). When in doubt, post to the [rack-devel](http://groups.google.com/group/rack-devel) mailing list. + +#### Fork the Project + +Fork the [project on Github](https://github.com/rack/rack) and check out your copy. + +``` +git clone https://github.com/contributor/rack.git +cd rack +git remote add upstream https://github.com/rack/rack.git +``` + +#### Create a Topic Branch + +Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. + +``` +git checkout master +git pull upstream master +git checkout -b my-feature-branch +``` + +#### Bundle Install and Quick Test + +Ensure that you can build the project and run quick tests. + +``` +bundle install --without extra +bundle exec rake test +``` + +#### Running All Tests + +Install all dependencies. + +``` +bundle install +``` + +Run all tests. + +``` +rake test +``` + +The test suite has no dependencies outside of the core Ruby installation and bacon. + +Some tests will be skipped if a dependency is not found. + +To run the test suite completely, you need: + + * fcgi + * dalli + * thin + +To test Memcache sessions, you need memcached (will be run on port 11211) and dalli installed. + +#### Write Tests + +Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. + +We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. + +#### Write Code + +Implement your feature or bug fix. + +Make sure that `bundle exec rake fulltest` completes without errors. + +#### Write Documentation + +Document any external behavior in the [README](README.rdoc). + +#### Update Changelog + +Add a line to [CHANGELOG](CHANGELOG.md). + +#### Commit Changes + +Make sure git knows your name and email address: + +``` +git config --global user.name "Your Name" +git config --global user.email "contributor@example.com" +``` + +Writing good commit logs is important. A commit log should describe what changed and why. + +``` +git add ... +git commit +``` + +#### Push + +``` +git push origin my-feature-branch +``` + +#### Make a Pull Request + +Go to https://github.com/contributor/rack and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. + +#### Rebase + +If you've been working on a change for a while, rebase with upstream/master. + +``` +git fetch upstream +git rebase upstream/master +git push origin my-feature-branch -f +``` + +#### Make Required Changes + +Amend your previous commit and force push the changes. + +``` +git commit --amend +git push origin my-feature-branch -f +``` + +#### Check on Your Pull Request + +Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. + +#### Be Patient + +It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! + +#### Thank You + +Please do know that we really appreciate and value your time and work. We love you, really. diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/MIT-LICENSE new file mode 100644 index 0000000000..703d118f9a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/MIT-LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (C) 2007-2019 Leah Neukirchen + +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 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.0.0/gems/rack-2.2.6.3/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/README.rdoc new file mode 100644 index 0000000000..cbb257239a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/README.rdoc @@ -0,0 +1,320 @@ += \Rack, a modular Ruby webserver interface + +{rack powers web applications}[https://rack.github.io/] + +{CircleCI}[https://circleci.com/gh/rack/rack] +{Gem Version}[http://badge.fury.io/rb/rack] +{SemVer Stability}[https://dependabot.com/compatibility-score.html?dependency-name=rack&package-manager=bundler&version-scheme=semver] +{Inline docs}[http://inch-ci.org/github/rack/rack] + +\Rack provides a minimal, modular, and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +The exact details of this are described in the \Rack specification, +which all \Rack applications should conform to. + +== Supported web servers + +The included *handlers* connect all kinds of web servers to \Rack: + +* WEBrick[https://github.com/ruby/webrick] +* FCGI +* CGI +* SCGI +* LiteSpeed[https://www.litespeedtech.com/] +* Thin[https://rubygems.org/gems/thin] + +These web servers include \Rack handlers in their distributions: + +* Agoo[https://github.com/ohler55/agoo] +* Falcon[https://github.com/socketry/falcon] +* Iodine[https://github.com/boazsegev/iodine] +* {NGINX Unit}[https://unit.nginx.org/] +* {Phusion Passenger}[https://www.phusionpassenger.com/] (which is mod_rack for Apache and for nginx) +* Puma[https://puma.io/] +* Unicorn[https://yhbt.net/unicorn/] +* uWSGI[https://uwsgi-docs.readthedocs.io/en/latest/] + +Any valid \Rack app will run the same on all these handlers, without +changing anything. + +== Supported web frameworks + +These frameworks and many others support the \Rack API: + +* Camping[http://www.ruby-camping.com/] +* Coset[http://leahneukirchen.org/repos/coset/] +* Hanami[https://hanamirb.org/] +* Padrino[http://padrinorb.com/] +* Ramaze[http://ramaze.net/] +* Roda[https://github.com/jeremyevans/roda] +* {Ruby on Rails}[https://rubyonrails.org/] +* Rum[https://github.com/leahneukirchen/rum] +* Sinatra[http://sinatrarb.com/] +* Utopia[https://github.com/socketry/utopia] +* WABuR[https://github.com/ohler55/wabur] + +== Available middleware shipped with \Rack + +Between the server and the framework, \Rack can be customized to your +applications needs using middleware. \Rack itself ships with the following +middleware: + +* Rack::Chunked, for streaming responses using chunked encoding. +* Rack::CommonLogger, for creating Apache-style logfiles. +* Rack::ConditionalGet, for returning not modified responses when the response + has not changed. +* Rack::Config, for modifying the environment before processing the request. +* Rack::ContentLength, for setting Content-Length header based on body size. +* Rack::ContentType, for setting default Content-Type header for responses. +* Rack::Deflater, for compressing responses with gzip. +* Rack::ETag, for setting ETag header on string bodies. +* Rack::Events, for providing easy hooks when a request is received + and when the response is sent. +* Rack::Files, for serving static files. +* Rack::Head, for returning an empty body for HEAD requests. +* Rack::Lint, for checking conformance to the \Rack API. +* Rack::Lock, for serializing requests using a mutex. +* Rack::Logger, for setting a logger to handle logging errors. +* Rack::MethodOverride, for modifying the request method based on a submitted + parameter. +* Rack::Recursive, for including data from other paths in the application, + and for performing internal redirects. +* Rack::Reloader, for reloading files if they have been modified. +* Rack::Runtime, for including a response header with the time taken to + process the request. +* Rack::Sendfile, for working with web servers that can use optimized + file serving for file system paths. +* Rack::ShowException, for catching unhandled exceptions and + presenting them in a nice and helpful way with clickable backtrace. +* Rack::ShowStatus, for using nice error pages for empty client error + responses. +* Rack::Static, for more configurable serving of static files. +* Rack::TempfileReaper, for removing temporary files creating during a + request. + +All these components use the same interface, which is described in +detail in the \Rack specification. These optional components can be +used in any way you wish. + +== Convenience + +If you want to develop outside of existing frameworks, implement your +own ones, or develop middleware, \Rack provides many helpers to create +\Rack applications quickly and without doing the same web stuff all +over: + +* Rack::Request, which also provides query string parsing and + multipart handling. +* Rack::Response, for convenient generation of HTTP replies and + cookie handling. +* Rack::MockRequest and Rack::MockResponse for efficient and quick + testing of \Rack application without real HTTP round-trips. +* Rack::Cascade, for trying additional \Rack applications if an + application returns a not found or method not supported response. +* Rack::Directory, for serving files under a given directory, with + directory indexes. +* Rack::MediaType, for parsing Content-Type headers. +* Rack::Mime, for determining Content-Type based on file extension. +* Rack::RewindableInput, for making any IO object rewindable, using + a temporary file buffer. +* Rack::URLMap, to route to multiple applications inside the same process. + +== rack-contrib + +The plethora of useful middleware created the need for a project that +collects fresh \Rack middleware. rack-contrib includes a variety of +add-on components for \Rack and it is easy to contribute new modules. + +* https://github.com/rack/rack-contrib + +== rackup + +rackup is a useful tool for running \Rack applications, which uses the +Rack::Builder DSL to configure middleware and build up applications +easily. + +rackup automatically figures out the environment it is run in, and +runs your application as FastCGI, CGI, or WEBrick---all from the +same configuration. + +== Quick start + +Try the lobster! + +Either with the embedded WEBrick starter: + + ruby -Ilib lib/rack/lobster.rb + +Or with rackup: + + bin/rackup -Ilib example/lobster.ru + +By default, the lobster is found at http://localhost:9292. + +== Installing with RubyGems + +A Gem of \Rack is available at {rubygems.org}[https://rubygems.org/gems/rack]. You can install it with: + + gem install rack + +== Usage + +You should require the library: + + require 'rack' + +\Rack uses autoload to automatically load other files \Rack ships with on demand, +so you should not need require paths under +rack+. If you require paths under ++rack+ without requiring +rack+ itself, things may not work correctly. + +== Configuration + +Several parameters can be modified on Rack::Utils to configure \Rack behaviour. + +e.g: + + Rack::Utils.key_space_limit = 128 + +=== key_space_limit + +The default number of bytes to allow all parameters keys in a given parameter hash to take up. +Does not affect nested parameter hashes, so doesn't actually prevent an attacker from using +more than this many bytes for parameter keys. + +Defaults to 65536 characters. + +=== param_depth_limit + +The maximum amount of nesting allowed in parameters. +For example, if set to 3, this query string would be allowed: + + ?a[b][c]=d + +but this query string would not be allowed: + + ?a[b][c][d]=e + +Limiting the depth prevents a possible stack overflow when parsing parameters. + +Defaults to 100. + +=== multipart_file_limit + +The maximum number of parts with a filename a request can contain. +Accepting too many part can lead to the server running out of file handles. + +The default is 128, which means that a single request can't upload more than 128 files at once. + +Set to 0 for no limit. + +Can also be set via the +RACK_MULTIPART_FILE_LIMIT+ environment variable. + +(This is also aliased as +multipart_part_limit+ and +RACK_MULTIPART_PART_LIMIT+ for compatibility) + +=== multipart_total_part_limit + +The maximum total number of parts a request can contain of any type, including +both file and non-file form fields. + +The default is 4096, which means that a single request can't contain more than +4096 parts. + +Set to 0 for no limit. + +Can also be set via the +RACK_MULTIPART_TOTAL_PART_LIMIT+ environment variable. + +== Changelog + +See {CHANGELOG.md}[https://github.com/rack/rack/blob/master/CHANGELOG.md]. + +== Contributing + +See {CONTRIBUTING.md}[https://github.com/rack/rack/blob/master/CONTRIBUTING.md]. + +== Contact + +Please post bugs, suggestions and patches to +the bug tracker at {issues}[https://github.com/rack/rack/issues]. + +Please post security related bugs and suggestions to the core team at + or rack-core@googlegroups.com. This +list is not public. Due to wide usage of the library, it is strongly preferred +that we manage timing in order to provide viable patches at the time of +disclosure. Your assistance in this matter is greatly appreciated. + +Mailing list archives are available at +. + +Git repository (send Git patches to the mailing list): + +* https://github.com/rack/rack + +You are also welcome to join the #rack channel on irc.freenode.net. + +== Thanks + +The \Rack Core Team, consisting of + +* Aaron Patterson (tenderlove[https://github.com/tenderlove]) +* Samuel Williams (ioquatix[https://github.com/ioquatix]) +* Jeremy Evans (jeremyevans[https://github.com/jeremyevans]) +* Eileen Uchitelle (eileencodes[https://github.com/eileencodes]) +* Matthew Draper (matthewd[https://github.com/matthewd]) +* Rafael França (rafaelfranca[https://github.com/rafaelfranca]) + +and the \Rack Alumni + +* Ryan Tomayko (rtomayko[https://github.com/rtomayko]) +* Scytrin dai Kinthra (scytrin[https://github.com/scytrin]) +* Leah Neukirchen (leahneukirchen[https://github.com/leahneukirchen]) +* James Tucker (raggi[https://github.com/raggi]) +* Josh Peek (josh[https://github.com/josh]) +* José Valim (josevalim[https://github.com/josevalim]) +* Michael Fellinger (manveru[https://github.com/manveru]) +* Santiago Pastorino (spastorino[https://github.com/spastorino]) +* Konstantin Haase (rkh[https://github.com/rkh]) + +would like to thank: + +* Adrian Madrid, for the LiteSpeed handler. +* Christoffer Sawicki, for the first Rails adapter and Rack::Deflater. +* Tim Fletcher, for the HTTP authentication code. +* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes. +* Armin Ronacher, for the logo and racktools. +* Alex Beregszaszi, Alexander Kahn, Anil Wadghule, Aredridel, Ben + Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, Tom Robinson, + Phil Hagelberg, S. Brent Faulkner, Bosko Milekic, Daniel Rodríguez + Troitiño, Genki Takiuchi, Geoffrey Grosenbach, Julien Sanchez, Kamal + Fariz Mahyuddin, Masayoshi Takahashi, Patrick Aljordm, Mig, Kazuhiro + Nishiyama, Jon Bardin, Konstantin Haase, Larry Siden, Matias + Korhonen, Sam Ruby, Simon Chiang, Tim Connor, Timur Batyrshin, and + Zach Brock for bug fixing and other improvements. +* Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support + and API improvements. +* Yehuda Katz and Carl Lerche for refactoring rackup. +* Brian Candler, for Rack::ContentType. +* Graham Batty, for improved handler loading. +* Stephen Bannasch, for bug reports and documentation. +* Gary Wright, for proposing a better Rack::Response interface. +* Jonathan Buch, for improvements regarding Rack::Response. +* Armin Röhrl, for tracking down bugs in the Cookie generator. +* Alexander Kellett for testing the Gem and reviewing the announcement. +* Marcus Rückert, for help with configuring and debugging lighttpd. +* The WSGI team for the well-done and documented work they've done and + \Rack builds up on. +* All bug reporters and patch contributors not mentioned above. + +== Links + +\Rack:: +Official \Rack repositories:: +\Rack Bug Tracking:: +rack-devel mailing list:: + +== License + +\Rack is released under the {MIT License}[https://opensource.org/licenses/MIT]. diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/Rakefile new file mode 100644 index 0000000000..237c3f2616 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/Rakefile @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rake/testtask" + +desc "Run all the tests" +task default: :test + +desc "Install gem dependencies" +task :deps do + require 'rubygems' + spec = Gem::Specification.load('rack.gemspec') + spec.dependencies.each do |dep| + reqs = dep.requirements_list + reqs = (["-v"] * reqs.size).zip(reqs).flatten + # Use system over sh, because we want to ignore errors! + system Gem.ruby, "-S", "gem", "install", '--conservative', dep.name, *reqs + end +end + +desc "Make an archive as .tar.gz" +task dist: %w[chmod changelog spec rdoc] do + sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar" + sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC.rdoc ChangeLog doc rack.gemspec" + sh "gzip -f -9 #{release}.tar" +end + +desc "Make an official release" +task :officialrelease do + puts "Official build for #{release}..." + sh "rm -rf stage" + sh "git clone --shared . stage" + sh "cd stage && rake officialrelease_really" + sh "mv stage/#{release}.tar.gz stage/#{release}.gem ." +end + +task officialrelease_really: %w[spec dist gem] do + sh "shasum #{release}.tar.gz #{release}.gem" +end + +def release + "rack-" + File.read('lib/rack/version.rb')[/RELEASE += +([\"\'])([\d][\w\.]+)\1/, 2] +end + +desc "Make binaries executable" +task :chmod do + Dir["bin/*"].each { |binary| File.chmod(0755, binary) } + Dir["test/cgi/test*"].each { |binary| File.chmod(0755, binary) } +end + +desc "Generate a ChangeLog" +task changelog: "ChangeLog" + +file '.git/index' +file "ChangeLog" => '.git/index' do + File.open("ChangeLog", "w") { |out| + log = `git log -z` + log.force_encoding(Encoding::BINARY) + log.split("\0").map { |chunk| + author = chunk[/Author: (.*)/, 1].strip + date = chunk[/Date: (.*)/, 1].strip + desc, detail = $'.strip.split("\n", 2) + detail ||= "" + detail = detail.gsub(/.*darcs-hash:.*/, '') + detail.rstrip! + out.puts "#{date} #{author}" + out.puts " * #{desc.strip}" + out.puts detail unless detail.empty? + out.puts + } + } +end + +desc "Generate Rack Specification" +task spec: "SPEC.rdoc" + +file 'lib/rack/lint.rb' +file "SPEC.rdoc" => 'lib/rack/lint.rb' do + File.open("SPEC.rdoc", "wb") { |file| + IO.foreach("lib/rack/lint.rb") { |line| + if line =~ /^\s*## ?(.*)/ + file.puts $1 + end + } + } +end + +Rake::TestTask.new("test:regular") do |t| + t.libs << "test" + t.test_files = FileList["test/**/*_test.rb", "test/**/spec_*.rb", "test/gemloader.rb"] + t.warning = false + t.verbose = true +end + +desc "Run tests with coverage" +task "test_cov" do + ENV['COVERAGE'] = '1' + Rake::Task['test:regular'].invoke +end + +desc "Run all the fast + platform agnostic tests" +task test: %w[spec test:regular] + +desc "Run all the tests we run on CI" +task ci: :test + +task gem: :spec do + sh "gem build rack.gemspec" +end + +task doc: :rdoc + +desc "Generate RDoc documentation" +task rdoc: %w[changelog spec] do + sh(*%w{rdoc --line-numbers --main README.rdoc + --title 'Rack\ Documentation' --charset utf-8 -U -o doc} + + %w{README.rdoc KNOWN-ISSUES SPEC.rdoc ChangeLog} + + `git ls-files lib/\*\*/\*.rb`.strip.split) + cp "contrib/rdoc.css", "doc/rdoc.css" +end + +task pushdoc: :rdoc do + sh "rsync -avz doc/ rack.rubyforge.org:/var/www/gforge-projects/rack/doc/" +end + +task pushsite: :pushdoc do + sh "cd site && git gc" + sh "rsync -avz site/ rack.rubyforge.org:/var/www/gforge-projects/rack/" + sh "cd site && git push" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/SPEC.rdoc b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/SPEC.rdoc new file mode 100644 index 0000000000..277142376e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/SPEC.rdoc @@ -0,0 +1,288 @@ +This specification aims to formalize the Rack protocol. You +can (and should) use Rack::Lint to enforce it. + +When you develop middleware, be sure to add a Lint before and +after to catch all mistakes. += Rack applications +A Rack application is a Ruby object (not a class) that +responds to +call+. +It takes exactly one argument, the *environment* +and returns an Array of exactly three values: +The *status*, +the *headers*, +and the *body*. +== The Environment +The environment must be an unfrozen instance of Hash that includes +CGI-like headers. The application is free to modify the +environment. + +The environment is required to include these variables +(adopted from PEP333), except when they'd be empty, but see +below. +REQUEST_METHOD:: The HTTP request method, such as + "GET" or "POST". This cannot ever + be an empty string, and so is + always required. +SCRIPT_NAME:: The initial portion of the request + URL's "path" that corresponds to the + application object, so that the + application knows its virtual + "location". This may be an empty + string, if the application corresponds + to the "root" of the server. +PATH_INFO:: The remainder of the request URL's + "path", designating the virtual + "location" of the request's target + within the application. This may be an + empty string, if the request URL targets + the application root and does not have a + trailing slash. This value may be + percent-encoded when originating from + a URL. +QUERY_STRING:: The portion of the request URL that + follows the ?, if any. May be + empty, but is always required! +SERVER_NAME, SERVER_PORT:: + When combined with SCRIPT_NAME and + PATH_INFO, these variables can be + used to complete the URL. Note, however, + that HTTP_HOST, if present, + should be used in preference to + SERVER_NAME for reconstructing + the request URL. + SERVER_NAME and SERVER_PORT + can never be empty strings, and so + are always required. +HTTP_ Variables:: Variables corresponding to the + client-supplied HTTP request + headers (i.e., variables whose + names begin with HTTP_). The + presence or absence of these + variables should correspond with + the presence or absence of the + appropriate HTTP header in the + request. See + {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18] + for specific behavior. +In addition to this, the Rack environment must include these +Rack-specific variables: +rack.version:: The Array representing this version of Rack + See Rack::VERSION, that corresponds to + the version of this SPEC. +rack.url_scheme:: +http+ or +https+, depending on the + request URL. +rack.input:: See below, the input stream. +rack.errors:: See below, the error stream. +rack.multithread:: true if the application object may be + simultaneously invoked by another thread + in the same process, false otherwise. +rack.multiprocess:: true if an equivalent application object + may be simultaneously invoked by another + process, false otherwise. +rack.run_once:: true if the server expects + (but does not guarantee!) that the + application will only be invoked this one + time during the life of its containing + process. Normally, this will only be true + for a server based on CGI + (or something similar). +rack.hijack?:: present and true if the server supports + connection hijacking. See below, hijacking. +rack.hijack:: an object responding to #call that must be + called at least once before using + rack.hijack_io. + It is recommended #call return rack.hijack_io + as well as setting it in env if necessary. +rack.hijack_io:: if rack.hijack? is true, and rack.hijack + has received #call, this will contain + an object resembling an IO. See hijacking. +Additional environment specifications have approved to +standardized middleware APIs. None of these are required to +be implemented by the server. +rack.session:: A hash like interface for storing + request session data. + The store must implement: + store(key, value) (aliased as []=); + fetch(key, default = nil) (aliased as []); + delete(key); + clear; + to_hash (returning unfrozen Hash instance); +rack.logger:: A common object interface for logging messages. + The object must implement: + info(message, &block) + debug(message, &block) + warn(message, &block) + error(message, &block) + fatal(message, &block) +rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. +rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. +The server or the application can store their own data in the +environment, too. The keys must contain at least one dot, +and should be prefixed uniquely. The prefix rack. +is reserved for use with the Rack core distribution and other +accepted specifications and must not be used otherwise. + +The environment must not contain the keys +HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH +(use the versions without HTTP_). +The CGI keys (named without a period) must have String values. +If the string values for CGI keys contain non-ASCII characters, +they should use ASCII-8BIT encoding. +There are the following restrictions: +* rack.version must be an array of Integers. +* rack.url_scheme must either be +http+ or +https+. +* There must be a valid input stream in rack.input. +* There must be a valid error stream in rack.errors. +* There may be a valid hijack stream in rack.hijack_io +* The REQUEST_METHOD must be a valid token. +* The SCRIPT_NAME, if non-empty, must start with / +* The PATH_INFO, if non-empty, must start with / +* The CONTENT_LENGTH, if given, must consist of digits only. +* One of SCRIPT_NAME or PATH_INFO must be + set. PATH_INFO should be / if + SCRIPT_NAME is empty. + SCRIPT_NAME never should be /, but instead be empty. +=== The Input Stream + +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, for Ruby 1.9 compatibility. +The input stream must respond to +gets+, +each+, +read+ and +rewind+. +* +gets+ must be called without arguments and return a string, + or +nil+ on EOF. +* +read+ behaves like IO#read. + Its signature is read([length, [buffer]]). + + If given, +length+ must be a non-negative Integer (>= 0) or +nil+, + and +buffer+ must be a String and may not be nil. + + If +length+ is given and not nil, then this method reads at most + +length+ bytes from the input stream. + + If +length+ is not given or nil, then this method reads + all data until EOF. + + When EOF is reached, this method returns nil if +length+ is given + and not nil, or "" if +length+ is not given or is nil. + + If +buffer+ is given, then the read data will be placed + into +buffer+ instead of a newly created String object. +* +each+ must be called without arguments and only yield Strings. +* +rewind+ must be called without arguments. It rewinds the input + stream back to the beginning. It must not raise Errno::ESPIPE: + that is, it may not be a pipe or a socket. Therefore, handler + developers must buffer the input data into some rewindable object + if the underlying input stream is not rewindable. +* +close+ must never be called on the input stream. +=== The Error Stream +The error stream must respond to +puts+, +write+ and +flush+. +* +puts+ must be called with a single argument that responds to +to_s+. +* +write+ must be called with a single argument that is a String. +* +flush+ must be called without arguments and must be called + in order to make the error appear for sure. +* +close+ must never be called on the error stream. +=== Hijacking +==== Request (before status) +If rack.hijack? is true then rack.hijack must respond to #call. +rack.hijack must return the io that will also be assigned (or is +already present, in rack.hijack_io. + +rack.hijack_io must respond to: +read, write, read_nonblock, write_nonblock, flush, close, +close_read, close_write, closed? + +The semantics of these IO methods must be a best effort match to +those of a normal ruby IO or Socket object, using standard +arguments and raising standard exceptions. Servers are encouraged +to simply pass on real IO objects, although it is recognized that +this approach is not directly compatible with SPDY and HTTP 2.0. + +IO provided in rack.hijack_io should preference the +IO::WaitReadable and IO::WaitWritable APIs wherever supported. + +There is a deliberate lack of full specification around +rack.hijack_io, as semantics will change from server to server. +Users are encouraged to utilize this API with a knowledge of their +server choice, and servers may extend the functionality of +hijack_io to provide additional features to users. The purpose of +rack.hijack is for Rack to "get out of the way", as such, Rack only +provides the minimum of specification and support. + +If rack.hijack? is false, then rack.hijack should not be set. + +If rack.hijack? is false, then rack.hijack_io should not be set. +==== Response (after headers) +It is also possible to hijack a response after the status and headers +have been sent. +In order to do this, an application may set the special header +rack.hijack to an object that responds to call +accepting an argument that conforms to the rack.hijack_io +protocol. + +After the headers have been sent, and this hijack callback has been +called, the application is now responsible for the remaining lifecycle +of the IO. The application is also responsible for maintaining HTTP +semantics. Of specific note, in almost all cases in the current SPEC, +applications will have wanted to specify the header Connection:close in +HTTP/1.1, and not Connection:keep-alive, as there is no protocol for +returning hijacked sockets to the web server. For that purpose, use the +body streaming API instead (progressively yielding strings via each). + +Servers must ignore the body part of the response tuple when +the rack.hijack response API is in use. + +The special response header rack.hijack must only be set +if the request env has rack.hijack? true. +==== Conventions +* Middleware should not use hijack unless it is handling the whole + response. +* Middleware may wrap the IO object for the response pattern. +* Middleware should not wrap the IO object for the request pattern. The + request pattern is intended to provide the hijacker with "raw tcp". +== The Response +=== The Status +This is an HTTP status. When parsed as integer (+to_i+), it must be +greater than or equal to 100. +=== The Headers +The header must respond to +each+, and yield values of key and value. +The header keys must be Strings. +Special headers starting "rack." are for communicating with the +server, and must not be sent back to the client. +The header must not contain a +Status+ key. +The header must conform to RFC7230 token specification, i.e. cannot +contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". +The values of the header must be Strings, +consisting of lines (for multiple header values, e.g. multiple +Set-Cookie values) separated by "\\n". +The lines must not contain characters below 037. +=== The Content-Type +There must not be a Content-Type, when the +Status+ is 1xx, +204 or 304. +=== The Content-Length +There must not be a Content-Length header when the ++Status+ is 1xx, 204 or 304. +=== The Body +The Body must respond to +each+ +and must only yield String values. + +The Body itself should not be an instance of String, as this will +break in Ruby 1.9. + +If the Body responds to +close+, it will be called after iteration. If +the body is replaced by a middleware after action, the original body +must be closed first, if it responds to close. + +If the Body responds to +to_path+, it must return a String +identifying the location of a file whose contents are identical +to that produced by calling +each+; this may be used by the +server as an alternative, possibly more efficient way to +transport the response. + +The Body commonly is an Array of Strings, the application +instance itself, or a File-like object. +== Thanks +Some parts of this specification are adopted from PEP333: Python +Web Server Gateway Interface +v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank +everyone involved in that effort. diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/bin/rackup b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/bin/rackup new file mode 100755 index 0000000000..58988a0b32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/bin/rackup @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "rack" +Rack::Server.start diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.png b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.png new file mode 100644 index 0000000000..0920c4fb0c Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.svg b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.svg new file mode 100644 index 0000000000..0d3f961cc4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack_logo.svg b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack_logo.svg new file mode 100644 index 0000000000..8287c9da09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rack_logo.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rdoc.css b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rdoc.css new file mode 100644 index 0000000000..f1e342f43f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/contrib/rdoc.css @@ -0,0 +1,412 @@ +/* Forked from the Darkfish templates rdoc.css file, much hacked, probably + * imperfect */ + +html { max-width: 960px; margin: 0 auto; } +body { + font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; +} +body.file-popup { + font-size: 90%; + margin-left: 0; +} + +h1 { + color: #4183C4; +} + +:link, +:visited { + color: #4183C4; + text-decoration: none; +} +:link:hover, +:visited:hover { + border-bottom: 1px dotted #4183C4; +} + +pre, pre.description { + font: 12px Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace; + padding: 1em; + overflow: auto; + line-height: 1.4; +} + +/* @group Generic Classes */ + +.initially-hidden { + display: none; +} + +#search-field { + width: 98%; +} + +.missing-docs { + font-size: 120%; + background: white url(images/wrench_orange.png) no-repeat 4px center; + color: #ccc; + line-height: 2em; + border: 1px solid #d00; + opacity: 1; + text-indent: 24px; + letter-spacing: 3px; + font-weight: bold; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +.target-section { + border: 2px solid #dcce90; + border-left-width: 8px; + background: #fff3c2; +} + +/* @end */ + +/* @group Index Page, Standalone file pages */ +.indexpage ul { + line-height: 160%; + list-style: none; +} +.indexpage ul :link, +.indexpage ul :visited { + font-size: 16px; +} + +.indexpage li { + padding-left: 20px; +} + +.indexpage ul > li { + background: url(images/bullet_black.png) no-repeat left 4px; +} +.indexpage li.method { + background: url(images/plugin.png) no-repeat left 4px; +} +.indexpage li.module { + background: url(images/package.png) no-repeat left 4px; +} +.indexpage li.class { + background: url(images/ruby.png) no-repeat left 4px; +} +.indexpage li.file { + background: url(images/page_white_text.png) no-repeat left 4px; +} +.indexpage li li { + background: url(images/tag_blue.png) no-repeat left 4px; +} +.indexpage li .toc-toggle { + width: 16px; + height: 16px; + background: url(images/add.png) no-repeat; +} + +.indexpage li .toc-toggle.open { + background: url(images/delete.png) no-repeat; +} + +/* @end */ + +/* @group Top-Level Structure */ + +.project-section, #file-metadata, #class-metadata { + display: block; + background: #f5f5f5; + margin-bottom: 1em; + padding: 0.5em; +} +.project-section h3, #file-metadata h3, #class-metadata h3 { + margin: 0; +} + +#metadata { + float: left; + width: 280px; +} + +#documentation { + margin: 2em 1em 2em 300px; +} + +#validator-badges { + clear: both; + margin: 1em 1em 2em; + font-size: smaller; +} + +/* @end */ + +/* @group Metadata Section */ + +#metadata ul, +#metadata dl, +#metadata p { + padding: 0px; + list-style: none; +} + +dl.svninfo { + color: #666; + margin: 0; +} +dl.svninfo dt { + font-weight: bold; +} + +ul.link-list li { + white-space: nowrap; +} +ul.link-list .type { + font-size: 8px; + text-transform: uppercase; + color: white; + background: #969696; +} + +/* @end */ + +/* @group Documentation Section */ + +.note-list { + margin: 8px 0; +} + +.label-list { + margin: 8px 1.5em; + border: 1px solid #ccc; +} +.description .label-list { + font-size: 14px; +} + +.note-list dt { + font-weight: bold; +} +.note-list dd { +} + +.label-list dt { + font-weight: bold; + background: #ddd; +} +.label-list dd { +} +.label-list dd + dt, +.note-list dd + dt { + margin-top: 0.7em; +} + +#documentation .section { + font-size: 90%; +} + +#documentation h2.section-header { + color: #333; + font-size: 175%; +} + +.documentation-section-title { + position: relative; +} +.documentation-section-title .section-click-top { + position: absolute; + top: 6px; + right: 12px; + font-size: 10px; + visibility: hidden; +} + +.documentation-section-title:hover .section-click-top { + visibility: visible; +} + +#documentation h3.section-header { + color: #333; + font-size: 150%; +} + +#constants-list > dl, +#attributes-list > dl { + margin: 1em 0 2em; + border: 0; +} +#constants-list > dl dt, +#attributes-list > dl dt { + font-weight: bold; + font-family: Monaco, "Andale Mono"; + background: inherit; +} +#constants-list > dl dt a, +#attributes-list > dl dt a { + color: inherit; +} +#constants-list > dl dd, +#attributes-list > dl dd { + margin: 0 0 1em 0; + color: #666; +} + +.documentation-section h2 { + position: relative; +} + +.documentation-section h2 a { + position: absolute; + top: 8px; + right: 10px; + font-size: 12px; + color: #9b9877; + visibility: hidden; +} + +.documentation-section h2:hover a { + visibility: visible; +} + +/* @group Method Details */ + +#documentation .method-source-code { + display: none; +} + +#documentation .method-detail { + margin: 0.2em 0.2em; + padding: 0.5em; +} +#documentation .method-detail:hover { + background-color: #f5f5f5; +} +#documentation .method-heading { + cursor: pointer; + position: relative; + font-size: 125%; + line-height: 125%; + font-weight: bold; + color: #333; + background: url(images/brick.png) no-repeat left bottom; + padding-left: 20px; +} +#documentation .method-heading :link, +#documentation .method-heading :visited { + color: inherit; +} +#documentation .method-click-advice { + position: absolute; + right: 5px; + font-size: 10px; + color: #aaa; + visibility: hidden; + background: url(images/zoom.png) no-repeat right 5px; + padding-right: 20px; + overflow: show; +} +#documentation .method-heading:hover .method-click-advice { + visibility: visible; +} + +#documentation .method-alias .method-heading { + color: #666; + background: url(images/brick_link.png) no-repeat left bottom; +} + +#documentation .method-description, +#documentation .aliases { + margin: 0 20px; + color: #666; +} + +#documentation .method-description p, +#documentation .aliases p { + line-height: 1.2em; +} + +#documentation .aliases { + font-style: italic; + cursor: default; +} +#documentation .method-description p { + margin-bottom: 0.5em; +} +#documentation .method-description ul { + margin-left: 1.5em; +} + +#documentation .attribute-method-heading { + background: url(images/tag_green.png) no-repeat left bottom; +} +#documentation #attribute-method-details .method-detail:hover { + background-color: transparent; + cursor: default; +} +#documentation .attribute-access-type { + font-size: 60%; + text-transform: uppercase; + vertical-align: super; +} + +.method-section .method-source-code { + background: white; +} + +/* @group Source Code */ + +.ruby-constant .ruby-keyword .ruby-ivar .ruby-operator .ruby-identifier +.ruby-node .ruby-comment .ruby-regexp .ruby-value { + background: transparent; +} + +/* Thanks GitHub!!! */ +.ruby-constant { color: #458; font-weight: bold; } +.ruby-keyword { color: black; font-weight: bold; } +.ruby-ivar { color: teal; } +.ruby-operator { color: #000; } +.ruby-identifier { color: black; } +.ruby-node { color: red; } +.ruby-comment { color: #998; font-weight: bold; } +.ruby-regexp { color: #009926; } +.ruby-value { color: #099; } +.ruby-string { color: red; } + +/* @group search results */ + +#search-section .section-header { + margin: 0; + padding: 0; +} + +#search-results { + width: 100%; + list-style: none; + margin: 0; + padding: 0; +} + +#search-results h1 { + font-size: 1em; + font-weight: normal; + text-shadow: none; +} + +#search-results .current { + background: #eee; +} + +#search-results li { + list-style: none; + line-height: 1em; + padding: 0.5em; + border-bottom: 1px solid black; +} + +#search-results .search-namespace { + font-weight: bold; +} + +#search-results li em { + background: yellow; + font-style: normal; +} + +#search-results pre { + margin: 0.5em; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/lobster.ru b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/lobster.ru new file mode 100644 index 0000000000..901e18a530 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/lobster.ru @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require 'rack/lobster' + +use Rack::ShowExceptions +run Rack::Lobster.new diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.rb new file mode 100644 index 0000000000..fe4f0b0948 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rack' +require 'rack/lobster' + +lobster = Rack::Lobster.new + +protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password| + Rack::Utils.secure_compare('secret', password) +end + +protected_lobster.realm = 'Lobster 2.0' + +pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster)) + +Rack::Server.start app: pretty_protected_lobster, Port: 9292 diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.ru b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.ru new file mode 100644 index 0000000000..0eb243cc6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/example/protectedlobster.ru @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'rack/lobster' + +use Rack::ShowExceptions +use Rack::Auth::Basic, "Lobster 2.0" do |username, password| + Rack::Utils.secure_compare('secret', password) +end + +run Rack::Lobster.new diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack.rb new file mode 100644 index 0000000000..e4494e5bac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +# Copyright (C) 2007-2019 Leah Neukirchen +# +# Rack is freely distributable under the terms of an MIT-style license. +# See MIT-LICENSE or https://opensource.org/licenses/MIT. + +# The Rack main module, serving as a namespace for all core Rack +# modules and classes. +# +# All modules meant for use in your application are autoloaded here, +# so it should be enough just to require 'rack' in your code. + +require_relative 'rack/version' + +module Rack + HTTP_HOST = 'HTTP_HOST' + HTTP_PORT = 'HTTP_PORT' + HTTP_VERSION = 'HTTP_VERSION' + HTTPS = 'HTTPS' + PATH_INFO = 'PATH_INFO' + REQUEST_METHOD = 'REQUEST_METHOD' + REQUEST_PATH = 'REQUEST_PATH' + SCRIPT_NAME = 'SCRIPT_NAME' + QUERY_STRING = 'QUERY_STRING' + SERVER_PROTOCOL = 'SERVER_PROTOCOL' + SERVER_NAME = 'SERVER_NAME' + SERVER_PORT = 'SERVER_PORT' + CACHE_CONTROL = 'Cache-Control' + EXPIRES = 'Expires' + CONTENT_LENGTH = 'Content-Length' + CONTENT_TYPE = 'Content-Type' + SET_COOKIE = 'Set-Cookie' + TRANSFER_ENCODING = 'Transfer-Encoding' + HTTP_COOKIE = 'HTTP_COOKIE' + ETAG = 'ETag' + + # HTTP method verbs + GET = 'GET' + POST = 'POST' + PUT = 'PUT' + PATCH = 'PATCH' + DELETE = 'DELETE' + HEAD = 'HEAD' + OPTIONS = 'OPTIONS' + LINK = 'LINK' + UNLINK = 'UNLINK' + TRACE = 'TRACE' + + # Rack environment variables + RACK_VERSION = 'rack.version' + RACK_TEMPFILES = 'rack.tempfiles' + RACK_ERRORS = 'rack.errors' + RACK_LOGGER = 'rack.logger' + RACK_INPUT = 'rack.input' + RACK_SESSION = 'rack.session' + RACK_SESSION_OPTIONS = 'rack.session.options' + RACK_SHOWSTATUS_DETAIL = 'rack.showstatus.detail' + RACK_MULTITHREAD = 'rack.multithread' + RACK_MULTIPROCESS = 'rack.multiprocess' + RACK_RUNONCE = 'rack.run_once' + RACK_URL_SCHEME = 'rack.url_scheme' + RACK_HIJACK = 'rack.hijack' + RACK_IS_HIJACK = 'rack.hijack?' + RACK_HIJACK_IO = 'rack.hijack_io' + RACK_RECURSIVE_INCLUDE = 'rack.recursive.include' + RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size' + RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory' + RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' + RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' + RACK_REQUEST_FORM_VARS = 'rack.request.form_vars' + RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash' + RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string' + RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' + RACK_REQUEST_QUERY_STRING = 'rack.request.query_string' + RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method' + RACK_SESSION_UNPACKED_COOKIE_DATA = 'rack.session.unpacked_cookie_data' + + autoload :Builder, "rack/builder" + autoload :BodyProxy, "rack/body_proxy" + autoload :Cascade, "rack/cascade" + autoload :Chunked, "rack/chunked" + autoload :CommonLogger, "rack/common_logger" + autoload :ConditionalGet, "rack/conditional_get" + autoload :Config, "rack/config" + autoload :ContentLength, "rack/content_length" + autoload :ContentType, "rack/content_type" + autoload :ETag, "rack/etag" + autoload :Events, "rack/events" + autoload :File, "rack/file" + autoload :Files, "rack/files" + autoload :Deflater, "rack/deflater" + autoload :Directory, "rack/directory" + autoload :ForwardRequest, "rack/recursive" + autoload :Handler, "rack/handler" + autoload :Head, "rack/head" + autoload :Lint, "rack/lint" + autoload :Lock, "rack/lock" + autoload :Logger, "rack/logger" + autoload :MediaType, "rack/media_type" + autoload :MethodOverride, "rack/method_override" + autoload :Mime, "rack/mime" + autoload :NullLogger, "rack/null_logger" + autoload :Recursive, "rack/recursive" + autoload :Reloader, "rack/reloader" + autoload :RewindableInput, "rack/rewindable_input" + autoload :Runtime, "rack/runtime" + autoload :Sendfile, "rack/sendfile" + autoload :Server, "rack/server" + autoload :ShowExceptions, "rack/show_exceptions" + autoload :ShowStatus, "rack/show_status" + autoload :Static, "rack/static" + autoload :TempfileReaper, "rack/tempfile_reaper" + autoload :URLMap, "rack/urlmap" + autoload :Utils, "rack/utils" + autoload :Multipart, "rack/multipart" + + autoload :MockRequest, "rack/mock" + autoload :MockResponse, "rack/mock" + + autoload :Request, "rack/request" + autoload :Response, "rack/response" + + module Auth + autoload :Basic, "rack/auth/basic" + autoload :AbstractRequest, "rack/auth/abstract/request" + autoload :AbstractHandler, "rack/auth/abstract/handler" + module Digest + autoload :MD5, "rack/auth/digest/md5" + autoload :Nonce, "rack/auth/digest/nonce" + autoload :Params, "rack/auth/digest/params" + autoload :Request, "rack/auth/digest/request" + end + end + + module Session + autoload :Cookie, "rack/session/cookie" + autoload :Pool, "rack/session/pool" + autoload :Memcache, "rack/session/memcache" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/handler.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/handler.rb new file mode 100644 index 0000000000..3ed87091c7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/handler.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Rack + module Auth + # Rack::Auth::AbstractHandler implements common authentication functionality. + # + # +realm+ should be set for all handlers. + + class AbstractHandler + + attr_accessor :realm + + def initialize(app, realm = nil, &authenticator) + @app, @realm, @authenticator = app, realm, authenticator + end + + + private + + def unauthorized(www_authenticate = challenge) + return [ 401, + { CONTENT_TYPE => 'text/plain', + CONTENT_LENGTH => '0', + 'WWW-Authenticate' => www_authenticate.to_s }, + [] + ] + end + + def bad_request + return [ 400, + { CONTENT_TYPE => 'text/plain', + CONTENT_LENGTH => '0' }, + [] + ] + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/request.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/request.rb new file mode 100644 index 0000000000..34042c401b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/abstract/request.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Rack + module Auth + class AbstractRequest + + def initialize(env) + @env = env + end + + def request + @request ||= Request.new(@env) + end + + def provided? + !authorization_key.nil? && valid? + end + + def valid? + !@env[authorization_key].nil? + end + + def parts + @parts ||= @env[authorization_key].split(' ', 2) + end + + def scheme + @scheme ||= parts.first && parts.first.downcase + end + + def params + @params ||= parts.last + end + + + private + + AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] + + def authorization_key + @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } + end + + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/basic.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/basic.rb new file mode 100644 index 0000000000..d5b4ea16da --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/basic.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative 'abstract/handler' +require_relative 'abstract/request' +require 'base64' + +module Rack + module Auth + # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. + # + # Initialize with the Rack application that you want protecting, + # and a block that checks if a username and password pair are valid. + # + # See also: example/protectedlobster.rb + + class Basic < AbstractHandler + + def call(env) + auth = Basic::Request.new(env) + + return unauthorized unless auth.provided? + + return bad_request unless auth.basic? + + if valid?(auth) + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + + unauthorized + end + + + private + + def challenge + 'Basic realm="%s"' % realm + end + + def valid?(auth) + @authenticator.call(*auth.credentials) + end + + class Request < Auth::AbstractRequest + def basic? + "basic" == scheme && credentials.length == 2 + end + + def credentials + @credentials ||= Base64.decode64(params).split(':', 2) + end + + def username + credentials.first + end + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/md5.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/md5.rb new file mode 100644 index 0000000000..04b103e258 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/md5.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require_relative '../abstract/handler' +require_relative 'request' +require_relative 'params' +require_relative 'nonce' +require 'digest/md5' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of + # HTTP Digest Authentication, as per RFC 2617. + # + # Initialize with the [Rack] application that you want protecting, + # and a block that looks up a plaintext password for a given username. + # + # +opaque+ needs to be set to a constant base64/hexadecimal string. + # + class MD5 < AbstractHandler + + attr_accessor :opaque + + attr_writer :passwords_hashed + + def initialize(app, realm = nil, opaque = nil, &authenticator) + @passwords_hashed = nil + if opaque.nil? and realm.respond_to? :values_at + realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed + end + super(app, realm, &authenticator) + @opaque = opaque + end + + def passwords_hashed? + !!@passwords_hashed + end + + def call(env) + auth = Request.new(env) + + unless auth.provided? + return unauthorized + end + + if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) + return bad_request + end + + if valid?(auth) + if auth.nonce.stale? + return unauthorized(challenge(stale: true)) + else + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + end + + unauthorized + end + + + private + + QOP = 'auth' + + def params(hash = {}) + Params.new do |params| + params['realm'] = realm + params['nonce'] = Nonce.new.to_s + params['opaque'] = H(opaque) + params['qop'] = QOP + + hash.each { |k, v| params[k] = v } + end + end + + def challenge(hash = {}) + "Digest #{params(hash)}" + end + + def valid?(auth) + valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) + end + + def valid_qop?(auth) + QOP == auth.qop + end + + def valid_opaque?(auth) + H(opaque) == auth.opaque + end + + def valid_nonce?(auth) + auth.nonce.valid? + end + + def valid_digest?(auth) + pw = @authenticator.call(auth.username) + pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response) + end + + def md5(data) + ::Digest::MD5.hexdigest(data) + end + + alias :H :md5 + + def KD(secret, data) + H "#{secret}:#{data}" + end + + def A1(auth, password) + "#{auth.username}:#{auth.realm}:#{password}" + end + + def A2(auth) + "#{auth.method}:#{auth.uri}" + end + + def digest(auth, password) + password_hash = passwords_hashed? ? password : H(A1(auth, password)) + + KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}" + end + + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/nonce.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/nonce.rb new file mode 100644 index 0000000000..3216d973e0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/nonce.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'digest/md5' +require 'base64' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::Nonce is the default nonce generator for the + # Rack::Auth::Digest::MD5 authentication handler. + # + # +private_key+ needs to set to a constant string. + # + # +time_limit+ can be optionally set to an integer (number of seconds), + # to limit the validity of the generated nonces. + + class Nonce + + class << self + attr_accessor :private_key, :time_limit + end + + def self.parse(string) + new(*Base64.decode64(string).split(' ', 2)) + end + + def initialize(timestamp = Time.now, given_digest = nil) + @timestamp, @given_digest = timestamp.to_i, given_digest + end + + def to_s + Base64.encode64("#{@timestamp} #{digest}").strip + end + + def digest + ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}") + end + + def valid? + digest == @given_digest + end + + def stale? + !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit + end + + def fresh? + !stale? + end + + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/params.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/params.rb new file mode 100644 index 0000000000..f611b3c35e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/params.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Rack + module Auth + module Digest + class Params < Hash + + def self.parse(str) + Params[*split_header_value(str).map do |param| + k, v = param.split('=', 2) + [k, dequote(v)] + end.flatten] + end + + def self.dequote(str) # From WEBrick::HTTPUtils + ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup + ret.gsub!(/\\(.)/, "\\1") + ret + end + + def self.split_header_value(str) + str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n) + end + + def initialize + super() + + yield self if block_given? + end + + def [](k) + super k.to_s + end + + def []=(k, v) + super k.to_s, v.to_s + end + + UNQUOTED = ['nc', 'stale'] + + def to_s + map do |k, v| + "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}" + end.join(', ') + end + + def quote(str) # From WEBrick::HTTPUtils + '"' + str.gsub(/[\\\"]/o, "\\\1") + '"' + end + + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/request.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/request.rb new file mode 100644 index 0000000000..7b89b76052 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/auth/digest/request.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative '../abstract/request' +require_relative 'params' +require_relative 'nonce' + +module Rack + module Auth + module Digest + class Request < Auth::AbstractRequest + def method + @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD] + end + + def digest? + "digest" == scheme + end + + def correct_uri? + request.fullpath == uri + end + + def nonce + @nonce ||= Nonce.parse(params['nonce']) + end + + def params + @params ||= Params.parse(parts.last) + end + + def respond_to?(sym, *) + super or params.has_key? sym.to_s + end + + def method_missing(sym, *args) + return super unless params.has_key?(key = sym.to_s) + return params[key] if args.size == 0 + raise ArgumentError, "wrong number of arguments (#{args.size} for 0)" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/body_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/body_proxy.rb new file mode 100644 index 0000000000..cfc0796a61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/body_proxy.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Rack + # Proxy for response bodies allowing calling a block when + # the response body is closed (after the response has been fully + # sent to the client). + class BodyProxy + # Set the response body to wrap, and the block to call when the + # response has been fully sent. + def initialize(body, &block) + @body = body + @block = block + @closed = false + end + + # Return whether the wrapped body responds to the method. + def respond_to_missing?(method_name, include_all = false) + super or @body.respond_to?(method_name, include_all) + end + + # If not already closed, close the wrapped body and + # then call the block the proxy was initialized with. + def close + return if @closed + @closed = true + begin + @body.close if @body.respond_to? :close + ensure + @block.call + end + end + + # Whether the proxy is closed. The proxy starts as not closed, + # and becomes closed on the first call to close. + def closed? + @closed + end + + # Delegate missing methods to the wrapped body. + def method_missing(method_name, *args, &block) + @body.__send__(method_name, *args, &block) + end + ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/builder.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/builder.rb new file mode 100644 index 0000000000..816ecf6208 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/builder.rb @@ -0,0 +1,257 @@ +# frozen_string_literal: true + +module Rack + # Rack::Builder implements a small DSL to iteratively construct Rack + # applications. + # + # Example: + # + # require 'rack/lobster' + # app = Rack::Builder.new do + # use Rack::CommonLogger + # use Rack::ShowExceptions + # map "/lobster" do + # use Rack::Lint + # run Rack::Lobster.new + # end + # end + # + # run app + # + # Or + # + # app = Rack::Builder.app do + # use Rack::CommonLogger + # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } + # end + # + # run app + # + # +use+ adds middleware to the stack, +run+ dispatches to an application. + # You can use +map+ to construct a Rack::URLMap in a convenient way. + + class Builder + + # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom + UTF_8_BOM = '\xef\xbb\xbf' + + # Parse the given config file to get a Rack application. + # + # If the config file ends in +.ru+, it is treated as a + # rackup file and the contents will be treated as if + # specified inside a Rack::Builder block, using the given + # options. + # + # If the config file does not end in +.ru+, it is + # required and Rack will use the basename of the file + # to guess which constant will be the Rack application to run. + # The options given will be ignored in this case. + # + # Examples: + # + # Rack::Builder.parse_file('config.ru') + # # Rack application built using Rack::Builder.new + # + # Rack::Builder.parse_file('app.rb') + # # requires app.rb, which can be anywhere in Ruby's + # # load path. After requiring, assumes App constant + # # contains Rack application + # + # Rack::Builder.parse_file('./my_app.rb') + # # requires ./my_app.rb, which should be in the + # # process's current directory. After requiring, + # # assumes MyApp constant contains Rack application + def self.parse_file(config, opts = Server::Options.new) + if config.end_with?('.ru') + return self.load_file(config, opts) + else + require config + app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join('')) + return app, {} + end + end + + # Load the given file as a rackup file, treating the + # contents as if specified inside a Rack::Builder block. + # + # Treats the first comment at the beginning of a line + # that starts with a backslash as options similar to + # options passed on a rackup command line. + # + # Ignores content in the file after +__END__+, so that + # use of +__END__+ will not result in a syntax error. + # + # Example config.ru file: + # + # $ cat config.ru + # + # #\ -p 9393 + # + # use Rack::ContentLength + # require './app.rb' + # run App + def self.load_file(path, opts = Server::Options.new) + options = {} + + cfgfile = ::File.read(path) + cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8 + + if cfgfile[/^#\\(.*)/] && opts + warn "Parsing options from the first comment line is deprecated!" + options = opts.parse! $1.split(/\s+/) + end + + cfgfile.sub!(/^__END__\n.*\Z/m, '') + app = new_from_string cfgfile, path + + return app, options + end + + # Evaluate the given +builder_script+ string in the context of + # a Rack::Builder block, returning a Rack application. + def self.new_from_string(builder_script, file = "(rackup)") + # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance. + # We cannot use instance_eval(String) as that would resolve constants differently. + binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }') + eval builder_script, binding, file + builder.to_app + end + + # Initialize a new Rack::Builder instance. +default_app+ specifies the + # default application if +run+ is not called later. If a block + # is given, it is evaluted in the context of the instance. + def initialize(default_app = nil, &block) + @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false + instance_eval(&block) if block_given? + end + + # Create a new Rack::Builder instance and return the Rack application + # generated from it. + def self.app(default_app = nil, &block) + self.new(default_app, &block).to_app + end + + # Specifies middleware to use in a stack. + # + # class Middleware + # def initialize(app) + # @app = app + # end + # + # def call(env) + # env["rack.some_header"] = "setting an example" + # @app.call(env) + # end + # end + # + # use Middleware + # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } + # + # All requests through to this application will first be processed by the middleware class. + # The +call+ method in this example sets an additional environment key which then can be + # referenced in the application if required. + def use(middleware, *args, &block) + if @map + mapping, @map = @map, nil + @use << proc { |app| generate_map(app, mapping) } + end + @use << proc { |app| middleware.new(app, *args, &block) } + end + ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true) + + # Takes an argument that is an object that responds to #call and returns a Rack response. + # The simplest form of this is a lambda object: + # + # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } + # + # However this could also be a class: + # + # class Heartbeat + # def self.call(env) + # [200, { "Content-Type" => "text/plain" }, ["OK"]] + # end + # end + # + # run Heartbeat + def run(app) + @run = app + end + + # Takes a lambda or block that is used to warm-up the application. This block is called + # before the Rack application is returned by to_app. + # + # warmup do |app| + # client = Rack::MockRequest.new(app) + # client.get('/') + # end + # + # use SomeMiddleware + # run MyApp + def warmup(prc = nil, &block) + @warmup = prc || block + end + + # Creates a route within the application. Routes under the mapped path will be sent to + # the Rack application specified by run inside the block. Other requests will be sent to the + # default application specified by run outside the block. + # + # Rack::Builder.app do + # map '/heartbeat' do + # run Heartbeat + # end + # run App + # end + # + # The +use+ method can also be used inside the block to specify middleware to run under a specific path: + # + # Rack::Builder.app do + # map '/heartbeat' do + # use Middleware + # run Heartbeat + # end + # run App + # end + # + # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+. + # + # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement + # outside the block. + def map(path, &block) + @map ||= {} + @map[path] = block + end + + # Freeze the app (set using run) and all middleware instances when building the application + # in to_app. + def freeze_app + @freeze_app = true + end + + # Return the Rack application generated by this instance. + def to_app + app = @map ? generate_map(@run, @map) : @run + fail "missing run or map statement" unless app + app.freeze if @freeze_app + app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } } + @warmup.call(app) if @warmup + app + end + + # Call the Rack application generated by this builder instance. Note that + # this rebuilds the Rack application and runs the warmup code (if any) + # every time it is called, so it should not be used if performance is important. + def call(env) + to_app.call(env) + end + + private + + # Generate a URLMap instance by generating new Rack applications for each + # map block in this instance. + def generate_map(default_app, mapping) + mapped = default_app ? { '/' => default_app } : {} + mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app } + URLMap.new(mapped) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/cascade.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/cascade.rb new file mode 100644 index 0000000000..d71274c2b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/cascade.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Rack + # Rack::Cascade tries a request on several apps, and returns the + # first response that is not 404 or 405 (or in a list of configured + # status codes). If all applications tried return one of the configured + # status codes, return the last response. + + class Cascade + # deprecated, no longer used + NotFound = [404, { CONTENT_TYPE => "text/plain" }, []] + + # An array of applications to try in order. + attr_reader :apps + + # Set the apps to send requests to, and what statuses result in + # cascading. Arguments: + # + # apps: An enumerable of rack applications. + # cascade_for: The statuses to use cascading for. If a response is received + # from an app, the next app is tried. + def initialize(apps, cascade_for = [404, 405]) + @apps = [] + apps.each { |app| add app } + + @cascade_for = {} + [*cascade_for].each { |status| @cascade_for[status] = true } + end + + # Call each app in order. If the responses uses a status that requires + # cascading, try the next app. If all responses require cascading, + # return the response from the last app. + def call(env) + return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty? + result = nil + last_body = nil + + @apps.each do |app| + # The SPEC says that the body must be closed after it has been iterated + # by the server, or if it is replaced by a middleware action. Cascade + # replaces the body each time a cascade happens. It is assumed that nil + # does not respond to close, otherwise the previous application body + # will be closed. The final application body will not be closed, as it + # will be passed to the server as a result. + last_body.close if last_body.respond_to? :close + + result = app.call(env) + return result unless @cascade_for.include?(result[0].to_i) + last_body = result[2] + end + + result + end + + # Append an app to the list of apps to cascade. This app will + # be tried last. + def add(app) + @apps << app + end + + # Whether the given app is one of the apps to cascade to. + def include?(app) + @apps.include?(app) + end + + alias_method :<<, :add + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/chunked.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/chunked.rb new file mode 100644 index 0000000000..84c6600140 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/chunked.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module Rack + + # Middleware that applies chunked transfer encoding to response bodies + # when the response does not include a Content-Length header. + # + # This supports the Trailer response header to allow the use of trailing + # headers in the chunked encoding. However, using this requires you manually + # specify a response body that supports a +trailers+ method. Example: + # + # [200, { 'Trailer' => 'Expires'}, ["Hello", "World"]] + # # error raised + # + # body = ["Hello", "World"] + # def body.trailers + # { 'Expires' => Time.now.to_s } + # end + # [200, { 'Trailer' => 'Expires'}, body] + # # No exception raised + class Chunked + include Rack::Utils + + # A body wrapper that emits chunked responses. + class Body + TERM = "\r\n" + TAIL = "0#{TERM}" + + # Store the response body to be chunked. + def initialize(body) + @body = body + end + + # For each element yielded by the response body, yield + # the element in chunked encoding. + def each(&block) + term = TERM + @body.each do |chunk| + size = chunk.bytesize + next if size == 0 + + yield [size.to_s(16), term, chunk.b, term].join + end + yield TAIL + yield_trailers(&block) + yield term + end + + # Close the response body if the response body supports it. + def close + @body.close if @body.respond_to?(:close) + end + + private + + # Do nothing as this class does not support trailer headers. + def yield_trailers + end + end + + # A body wrapper that emits chunked responses and also supports + # sending Trailer headers. Note that the response body provided to + # initialize must have a +trailers+ method that returns a hash + # of trailer headers, and the rack response itself should have a + # Trailer header listing the headers that the +trailers+ method + # will return. + class TrailerBody < Body + private + + # Yield strings for each trailer header. + def yield_trailers + @body.trailers.each_pair do |k, v| + yield "#{k}: #{v}\r\n" + end + end + end + + def initialize(app) + @app = app + end + + # Whether the HTTP version supports chunked encoding (HTTP 1.1 does). + def chunkable_version?(ver) + case ver + # pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have + # a version (nor response headers) + when 'HTTP/1.0', nil, 'HTTP/0.9' + false + else + true + end + end + + # If the rack app returns a response that should have a body, + # but does not have Content-Length or Transfer-Encoding headers, + # modify the response to use chunked Transfer-Encoding. + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash[headers] + + if chunkable_version?(env[SERVER_PROTOCOL]) && + !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) && + !headers[CONTENT_LENGTH] && + !headers[TRANSFER_ENCODING] + + headers[TRANSFER_ENCODING] = 'chunked' + if headers['Trailer'] + body = TrailerBody.new(body) + else + body = Body.new(body) + end + end + + [status, headers, body] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/common_logger.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/common_logger.rb new file mode 100644 index 0000000000..9c6f92147d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/common_logger.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Rack + # Rack::CommonLogger forwards every request to the given +app+, and + # logs a line in the + # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common] + # to the configured logger. + class CommonLogger + # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common + # + # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 - + # + # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % + # + # The actual format is slightly different than the above due to the + # separation of SCRIPT_NAME and PATH_INFO, and because the elapsed + # time in seconds is included at the end. + FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %d %s %0.4f\n} + + # +logger+ can be any object that supports the +write+ or +<<+ methods, + # which includes the standard library Logger. These methods are called + # with a single string argument, the log message. + # If +logger+ is nil, CommonLogger will fall back env['rack.errors']. + def initialize(app, logger = nil) + @app = app + @logger = logger + end + + # Log all requests in common_log format after a response has been + # returned. Note that if the app raises an exception, the request + # will not be logged, so if exception handling middleware are used, + # they should be loaded after this middleware. Additionally, because + # the logging happens after the request body has been fully sent, any + # exceptions raised during the sending of the response body will + # cause the request not to be logged. + def call(env) + began_at = Utils.clock_time + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + body = BodyProxy.new(body) { log(env, status, headers, began_at) } + [status, headers, body] + end + + private + + # Log the request to the configured logger. + def log(env, status, header, began_at) + length = extract_content_length(header) + + msg = FORMAT % [ + env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", + env["REMOTE_USER"] || "-", + Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"), + env[REQUEST_METHOD], + env[SCRIPT_NAME], + env[PATH_INFO], + env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}", + env[SERVER_PROTOCOL], + status.to_s[0..3], + length, + Utils.clock_time - began_at ] + + msg.gsub!(/[^[:print:]\n]/) { |c| "\\x#{c.ord}" } + + logger = @logger || env[RACK_ERRORS] + + # Standard library logger doesn't support write but it supports << which actually + # calls to write on the log device without formatting + if logger.respond_to?(:write) + logger.write(msg) + else + logger << msg + end + end + + # Attempt to determine the content length for the response to + # include it in the logged data. + def extract_content_length(headers) + value = headers[CONTENT_LENGTH] + !value || value.to_s == '0' ? '-' : value + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/conditional_get.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/conditional_get.rb new file mode 100644 index 0000000000..7b7808ac1f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/conditional_get.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Rack + + # Middleware that enables conditional GET using If-None-Match and + # If-Modified-Since. The application should set either or both of the + # Last-Modified or Etag response headers according to RFC 2616. When + # either of the conditions is met, the response body is set to be zero + # length and the response status is set to 304 Not Modified. + # + # Applications that defer response body generation until the body's each + # message is received will avoid response body generation completely when + # a conditional GET matches. + # + # Adapted from Michael Klishin's Merb implementation: + # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb + class ConditionalGet + def initialize(app) + @app = app + end + + # Return empty 304 response if the response has not been + # modified since the last request. + def call(env) + case env[REQUEST_METHOD] + when "GET", "HEAD" + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + if status == 200 && fresh?(env, headers) + status = 304 + headers.delete(CONTENT_TYPE) + headers.delete(CONTENT_LENGTH) + original_body = body + body = Rack::BodyProxy.new([]) do + original_body.close if original_body.respond_to?(:close) + end + end + [status, headers, body] + else + @app.call(env) + end + end + + private + + # Return whether the response has not been modified since the + # last request. + def fresh?(env, headers) + # If-None-Match has priority over If-Modified-Since per RFC 7232 + if none_match = env['HTTP_IF_NONE_MATCH'] + etag_matches?(none_match, headers) + elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since)) + modified_since?(modified_since, headers) + end + end + + # Whether the ETag response header matches the If-None-Match request header. + # If so, the request has not been modified. + def etag_matches?(none_match, headers) + headers['ETag'] == none_match + end + + # Whether the Last-Modified response header matches the If-Modified-Since + # request header. If so, the request has not been modified. + def modified_since?(modified_since, headers) + last_modified = to_rfc2822(headers['Last-Modified']) and + modified_since >= last_modified + end + + # Return a Time object for the given string (which should be in RFC2822 + # format), or nil if the string cannot be parsed. + def to_rfc2822(since) + # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A + # anything shorter is invalid, this avoids exceptions for common cases + # most common being the empty string + if since && since.length >= 16 + # NOTE: there is no trivial way to write this in a non exception way + # _rfc2822 returns a hash but is not that usable + Time.rfc2822(since) rescue nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/config.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/config.rb new file mode 100644 index 0000000000..41f6f7dd57 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/config.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Rack + # Rack::Config modifies the environment using the block given during + # initialization. + # + # Example: + # use Rack::Config do |env| + # env['my-key'] = 'some-value' + # end + class Config + def initialize(app, &block) + @app = app + @block = block + end + + def call(env) + @block.call(env) + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_length.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_length.rb new file mode 100644 index 0000000000..9e2b5fc42a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_length.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Rack + + # Sets the Content-Length header on responses that do not specify + # a Content-Length or Transfer-Encoding header. Note that this + # does not fix responses that have an invalid Content-Length + # header specified. + class ContentLength + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash[headers] + + if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) && + !headers[CONTENT_LENGTH] && + !headers[TRANSFER_ENCODING] + + obody = body + body, length = [], 0 + obody.each { |part| body << part; length += part.bytesize } + + body = BodyProxy.new(body) do + obody.close if obody.respond_to?(:close) + end + + headers[CONTENT_LENGTH] = length.to_s + end + + [status, headers, body] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_type.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_type.rb new file mode 100644 index 0000000000..503f707062 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/content_type.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Rack + + # Sets the Content-Type header on responses which don't have one. + # + # Builder Usage: + # use Rack::ContentType, "text/plain" + # + # When no content type argument is provided, "text/html" is the + # default. + class ContentType + include Rack::Utils + + def initialize(app, content_type = "text/html") + @app, @content_type = app, content_type + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + + unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) + headers[CONTENT_TYPE] ||= @content_type + end + + [status, headers, body] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/core_ext/regexp.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/core_ext/regexp.rb new file mode 100644 index 0000000000..a32fcdf629 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/core_ext/regexp.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Regexp has `match?` since Ruby 2.4 +# so to support Ruby < 2.4 we need to define this method + +module Rack + module RegexpExtensions + refine Regexp do + def match?(string, pos = 0) + !!match(string, pos) + end + end unless //.respond_to?(:match?) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/deflater.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/deflater.rb new file mode 100644 index 0000000000..e177fabb01 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/deflater.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require "zlib" +require "time" # for Time.httpdate + +module Rack + # This middleware enables content encoding of http responses, + # usually for purposes of compression. + # + # Currently supported encodings: + # + # * gzip + # * identity (no transformation) + # + # This middleware automatically detects when encoding is supported + # and allowed. For example no encoding is made when a cache + # directive of 'no-transform' is present, when the response status + # code is one that doesn't allow an entity body, or when the body + # is empty. + # + # Note that despite the name, Deflater does not support the +deflate+ + # encoding. + class Deflater + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + # Creates Rack::Deflater middleware. Options: + # + # :if :: a lambda enabling / disabling deflation based on returned boolean value + # (e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }). + # However, be aware that calling `body.each` inside the block will break cases where `body.each` is not idempotent, + # such as when it is an +IO+ instance. + # :include :: a list of content types that should be compressed. By default, all content types are compressed. + # :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces + # latency for time-sensitive streaming applications, but hurts compression and throughput. + # Defaults to +true+. + def initialize(app, options = {}) + @app = app + @condition = options[:if] + @compressible_types = options[:include] + @sync = options.fetch(:sync, true) + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + + unless should_deflate?(env, status, headers, body) + return [status, headers, body] + end + + request = Request.new(env) + + encoding = Utils.select_best_encoding(%w(gzip identity), + request.accept_encoding) + + # Set the Vary HTTP header. + vary = headers["Vary"].to_s.split(",").map(&:strip) + unless vary.include?("*") || vary.include?("Accept-Encoding") + headers["Vary"] = vary.push("Accept-Encoding").join(",") + end + + case encoding + when "gzip" + headers['Content-Encoding'] = "gzip" + headers.delete(CONTENT_LENGTH) + mtime = headers["Last-Modified"] + mtime = Time.httpdate(mtime).to_i if mtime + [status, headers, GzipStream.new(body, mtime, @sync)] + when "identity" + [status, headers, body] + when nil + message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." + bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) } + [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp] + end + end + + # Body class used for gzip encoded responses. + class GzipStream + # Initialize the gzip stream. Arguments: + # body :: Response body to compress with gzip + # mtime :: The modification time of the body, used to set the + # modification time in the gzip header. + # sync :: Whether to flush each gzip chunk as soon as it is ready. + def initialize(body, mtime, sync) + @body = body + @mtime = mtime + @sync = sync + end + + # Yield gzip compressed strings to the given block. + def each(&block) + @writer = block + gzip = ::Zlib::GzipWriter.new(self) + gzip.mtime = @mtime if @mtime + @body.each { |part| + # Skip empty strings, as they would result in no output, + # and flushing empty parts would raise Zlib::BufError. + next if part.empty? + + gzip.write(part) + gzip.flush if @sync + } + ensure + gzip.close + end + + # Call the block passed to #each with the the gzipped data. + def write(data) + @writer.call(data) + end + + # Close the original body if possible. + def close + @body.close if @body.respond_to?(:close) + end + end + + private + + # Whether the body should be compressed. + def should_deflate?(env, status, headers, body) + # Skip compressing empty entity body responses and responses with + # no-transform set. + if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) || + /\bno-transform\b/.match?(headers['Cache-Control'].to_s) || + headers['Content-Encoding']&.!~(/\bidentity\b/) + return false + end + + # Skip if @compressible_types are given and does not include request's content type + return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/])) + + # Skip if @condition lambda is given and evaluates to false + return false if @condition && !@condition.call(env, status, headers, body) + + # No point in compressing empty body, also handles usage with + # Rack::Sendfile. + return false if headers[CONTENT_LENGTH] == '0' + + true + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/directory.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/directory.rb new file mode 100644 index 0000000000..be72be0144 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/directory.rb @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require 'time' + +module Rack + # Rack::Directory serves entries below the +root+ given, according to the + # path info of the Rack request. If a directory is found, the file's contents + # will be presented in an html based index. If a file is found, the env will + # be passed to the specified +app+. + # + # If +app+ is not specified, a Rack::Files of the same +root+ will be used. + + class Directory + DIR_FILE = "%s%s%s%s\n" + DIR_PAGE_HEADER = <<-PAGE + + %s + + + +

%s

+
+ + + + + + + + PAGE + DIR_PAGE_FOOTER = <<-PAGE +
NameSizeTypeLast Modified
+
+ + PAGE + + # Body class for directory entries, showing an index page with links + # to each file. + class DirectoryBody < Struct.new(:root, :path, :files) + # Yield strings for each part of the directory entry + def each + show_path = Utils.escape_html(path.sub(/^#{root}/, '')) + yield(DIR_PAGE_HEADER % [ show_path, show_path ]) + + unless path.chomp('/') == root + yield(DIR_FILE % DIR_FILE_escape(files.call('..'))) + end + + Dir.foreach(path) do |basename| + next if basename.start_with?('.') + next unless f = files.call(basename) + yield(DIR_FILE % DIR_FILE_escape(f)) + end + + yield(DIR_PAGE_FOOTER) + end + + private + + # Escape each element in the array of html strings. + def DIR_FILE_escape(htmls) + htmls.map { |e| Utils.escape_html(e) } + end + end + + # The root of the directory hierarchy. Only requests for files and + # directories inside of the root directory are supported. + attr_reader :root + + # Set the root directory and application for serving files. + def initialize(root, app = nil) + @root = ::File.expand_path(root) + @app = app || Files.new(@root) + @head = Head.new(method(:get)) + end + + def call(env) + # strip body if this is a HEAD call + @head.call env + end + + # Internals of request handling. Similar to call but does + # not remove body for HEAD requests. + def get(env) + script_name = env[SCRIPT_NAME] + path_info = Utils.unescape_path(env[PATH_INFO]) + + if client_error_response = check_bad_request(path_info) || check_forbidden(path_info) + client_error_response + else + path = ::File.join(@root, path_info) + list_path(env, path, path_info, script_name) + end + end + + # Rack response to use for requests with invalid paths, or nil if path is valid. + def check_bad_request(path_info) + return if Utils.valid_path?(path_info) + + body = "Bad Request\n" + [400, { CONTENT_TYPE => "text/plain", + CONTENT_LENGTH => body.bytesize.to_s, + "X-Cascade" => "pass" }, [body]] + end + + # Rack response to use for requests with paths outside the root, or nil if path is inside the root. + def check_forbidden(path_info) + return unless path_info.include? ".." + return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root) + + body = "Forbidden\n" + [403, { CONTENT_TYPE => "text/plain", + CONTENT_LENGTH => body.bytesize.to_s, + "X-Cascade" => "pass" }, [body]] + end + + # Rack response to use for directories under the root. + def list_directory(path_info, path, script_name) + url_head = (script_name.split('/') + path_info.split('/')).map do |part| + Utils.escape_path part + end + + # Globbing not safe as path could contain glob metacharacters + body = DirectoryBody.new(@root, path, ->(basename) do + stat = stat(::File.join(path, basename)) + next unless stat + + url = ::File.join(*url_head + [Utils.escape_path(basename)]) + mtime = stat.mtime.httpdate + if stat.directory? + type = 'directory' + size = '-' + url << '/' + if basename == '..' + basename = 'Parent Directory' + else + basename << '/' + end + else + type = Mime.mime_type(::File.extname(basename)) + size = filesize_format(stat.size) + end + + [ url, basename, size, type, mtime ] + end) + + [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, body ] + end + + # File::Stat for the given path, but return nil for missing/bad entries. + def stat(path) + ::File.stat(path) + rescue Errno::ENOENT, Errno::ELOOP + return nil + end + + # Rack response to use for files and directories under the root. + # Unreadable and non-file, non-directory entries will get a 404 response. + def list_path(env, path, path_info, script_name) + if (stat = stat(path)) && stat.readable? + return @app.call(env) if stat.file? + return list_directory(path_info, path, script_name) if stat.directory? + end + + entity_not_found(path_info) + end + + # Rack response to use for unreadable and non-file, non-directory entries. + def entity_not_found(path_info) + body = "Entity not found: #{path_info}\n" + [404, { CONTENT_TYPE => "text/plain", + CONTENT_LENGTH => body.bytesize.to_s, + "X-Cascade" => "pass" }, [body]] + end + + # Stolen from Ramaze + FILESIZE_FORMAT = [ + ['%.1fT', 1 << 40], + ['%.1fG', 1 << 30], + ['%.1fM', 1 << 20], + ['%.1fK', 1 << 10], + ] + + # Provide human readable file sizes + def filesize_format(int) + FILESIZE_FORMAT.each do |format, size| + return format % (int.to_f / size) if int >= size + end + + "#{int}B" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/etag.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/etag.rb new file mode 100644 index 0000000000..5039437e1c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/etag.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require_relative '../rack' +require 'digest/sha2' + +module Rack + # Automatically sets the ETag header on all String bodies. + # + # The ETag header is skipped if ETag or Last-Modified headers are sent or if + # a sendfile body (body.responds_to :to_path) is given (since such cases + # should be handled by apache/nginx). + # + # On initialization, you can pass two parameters: a Cache-Control directive + # used when Etag is absent and a directive when it is present. The first + # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate" + class ETag + ETAG_STRING = Rack::ETAG + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + + def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL) + @app = app + @cache_control = cache_control + @no_cache_control = no_cache_control + end + + def call(env) + status, headers, body = @app.call(env) + + headers = Utils::HeaderHash[headers] + + if etag_status?(status) && etag_body?(body) && !skip_caching?(headers) + original_body = body + digest, new_body = digest_body(body) + body = Rack::BodyProxy.new(new_body) do + original_body.close if original_body.respond_to?(:close) + end + headers[ETAG_STRING] = %(W/"#{digest}") if digest + end + + unless headers[CACHE_CONTROL] + if digest + headers[CACHE_CONTROL] = @cache_control if @cache_control + else + headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control + end + end + + [status, headers, body] + end + + private + + def etag_status?(status) + status == 200 || status == 201 + end + + def etag_body?(body) + !body.respond_to?(:to_path) + end + + def skip_caching?(headers) + headers.key?(ETAG_STRING) || headers.key?('Last-Modified') + end + + def digest_body(body) + parts = [] + digest = nil + + body.each do |part| + parts << part + (digest ||= Digest::SHA256.new) << part unless part.empty? + end + + [digest && digest.hexdigest.byteslice(0, 32), parts] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/events.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/events.rb new file mode 100644 index 0000000000..65055fdc51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/events.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +module Rack + ### This middleware provides hooks to certain places in the request / + # response lifecycle. This is so that middleware that don't need to filter + # the response data can safely leave it alone and not have to send messages + # down the traditional "rack stack". + # + # The events are: + # + # * on_start(request, response) + # + # This event is sent at the start of the request, before the next + # middleware in the chain is called. This method is called with a request + # object, and a response object. Right now, the response object is always + # nil, but in the future it may actually be a real response object. + # + # * on_commit(request, response) + # + # The response has been committed. The application has returned, but the + # response has not been sent to the webserver yet. This method is always + # called with a request object and the response object. The response + # object is constructed from the rack triple that the application returned. + # Changes may still be made to the response object at this point. + # + # * on_send(request, response) + # + # The webserver has started iterating over the response body and presumably + # has started sending data over the wire. This method is always called with + # a request object and the response object. The response object is + # constructed from the rack triple that the application returned. Changes + # SHOULD NOT be made to the response object as the webserver has already + # started sending data. Any mutations will likely result in an exception. + # + # * on_finish(request, response) + # + # The webserver has closed the response, and all data has been written to + # the response socket. The request and response object should both be + # read-only at this point. The body MAY NOT be available on the response + # object as it may have been flushed to the socket. + # + # * on_error(request, response, error) + # + # An exception has occurred in the application or an `on_commit` event. + # This method will get the request, the response (if available) and the + # exception that was raised. + # + # ## Order + # + # `on_start` is called on the handlers in the order that they were passed to + # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are + # called in the reverse order. `on_finish` handlers are called inside an + # `ensure` block, so they are guaranteed to be called even if something + # raises an exception. If something raises an exception in a `on_finish` + # method, then nothing is guaranteed. + + class Events + module Abstract + def on_start(req, res) + end + + def on_commit(req, res) + end + + def on_send(req, res) + end + + def on_finish(req, res) + end + + def on_error(req, res, e) + end + end + + class EventedBodyProxy < Rack::BodyProxy # :nodoc: + attr_reader :request, :response + + def initialize(body, request, response, handlers, &block) + super(body, &block) + @request = request + @response = response + @handlers = handlers + end + + def each + @handlers.reverse_each { |handler| handler.on_send request, response } + super + end + end + + class BufferedResponse < Rack::Response::Raw # :nodoc: + attr_reader :body + + def initialize(status, headers, body) + super(status, headers) + @body = body + end + + def to_a; [status, headers, body]; end + end + + def initialize(app, handlers) + @app = app + @handlers = handlers + end + + def call(env) + request = make_request env + on_start request, nil + + begin + status, headers, body = @app.call request.env + response = make_response status, headers, body + on_commit request, response + rescue StandardError => e + on_error request, response, e + on_finish request, response + raise + end + + body = EventedBodyProxy.new(body, request, response, @handlers) do + on_finish request, response + end + [response.status, response.headers, body] + end + + private + + def on_error(request, response, e) + @handlers.reverse_each { |handler| handler.on_error request, response, e } + end + + def on_commit(request, response) + @handlers.reverse_each { |handler| handler.on_commit request, response } + end + + def on_start(request, response) + @handlers.each { |handler| handler.on_start request, nil } + end + + def on_finish(request, response) + @handlers.reverse_each { |handler| handler.on_finish request, response } + end + + def make_request(env) + Rack::Request.new env + end + + def make_response(status, headers, body) + BufferedResponse.new status, headers, body + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/file.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/file.rb new file mode 100644 index 0000000000..fdcf9b3ec0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/file.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require_relative 'files' + +module Rack + File = Files +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/files.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/files.rb new file mode 100644 index 0000000000..e745eb3984 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/files.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require 'time' + +module Rack + # Rack::Files serves files below the +root+ directory given, according to the + # path info of the Rack request. + # e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file + # as http://localhost:9292/passwd + # + # Handlers can detect if bodies are a Rack::Files, and use mechanisms + # like sendfile on the +path+. + + class Files + ALLOWED_VERBS = %w[GET HEAD OPTIONS] + ALLOW_HEADER = ALLOWED_VERBS.join(', ') + MULTIPART_BOUNDARY = 'AaB03x' + + # @todo remove in 3.0 + def self.method_added(name) + if name == :response_body + raise "#{self.class}\#response_body is no longer supported." + end + super + end + + attr_reader :root + + def initialize(root, headers = {}, default_mime = 'text/plain') + @root = (::File.expand_path(root) if root) + @headers = headers + @default_mime = default_mime + @head = Rack::Head.new(lambda { |env| get env }) + end + + def call(env) + # HEAD requests drop the response body, including 4xx error messages. + @head.call env + end + + def get(env) + request = Rack::Request.new env + unless ALLOWED_VERBS.include? request.request_method + return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER }) + end + + path_info = Utils.unescape_path request.path_info + return fail(400, "Bad Request") unless Utils.valid_path?(path_info) + + clean_path_info = Utils.clean_path_info(path_info) + path = ::File.join(@root, clean_path_info) + + available = begin + ::File.file?(path) && ::File.readable?(path) + rescue SystemCallError + # Not sure in what conditions this exception can occur, but this + # is a safe way to handle such an error. + # :nocov: + false + # :nocov: + end + + if available + serving(request, path) + else + fail(404, "File not found: #{path_info}") + end + end + + def serving(request, path) + if request.options? + return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []] + end + last_modified = ::File.mtime(path).httpdate + return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified + + headers = { "Last-Modified" => last_modified } + mime_type = mime_type path, @default_mime + headers[CONTENT_TYPE] = mime_type if mime_type + + # Set custom headers + headers.merge!(@headers) if @headers + + status = 200 + size = filesize path + + ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size) + if ranges.nil? + # No ranges: + ranges = [0..size - 1] + elsif ranges.empty? + # Unsatisfiable. Return error, and file size: + response = fail(416, "Byte range unsatisfiable") + response[1]["Content-Range"] = "bytes */#{size}" + return response + elsif ranges.size >= 1 + # Partial content + partial_content = true + + if ranges.size == 1 + range = ranges[0] + headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}" + else + headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}" + end + + status = 206 + body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size) + size = body.bytesize + end + + headers[CONTENT_LENGTH] = size.to_s + + if request.head? + body = [] + elsif !partial_content + body = Iterator.new(path, ranges, mime_type: mime_type, size: size) + end + + [status, headers, body] + end + + class BaseIterator + attr_reader :path, :ranges, :options + + def initialize(path, ranges, options) + @path = path + @ranges = ranges + @options = options + end + + def each + ::File.open(path, "rb") do |file| + ranges.each do |range| + yield multipart_heading(range) if multipart? + + each_range_part(file, range) do |part| + yield part + end + end + + yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart? + end + end + + def bytesize + size = ranges.inject(0) do |sum, range| + sum += multipart_heading(range).bytesize if multipart? + sum += range.size + end + size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart? + size + end + + def close; end + + private + + def multipart? + ranges.size > 1 + end + + def multipart_heading(range) +<<-EOF +\r +--#{MULTIPART_BOUNDARY}\r +Content-Type: #{options[:mime_type]}\r +Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r +\r +EOF + end + + def each_range_part(file, range) + file.seek(range.begin) + remaining_len = range.end - range.begin + 1 + while remaining_len > 0 + part = file.read([8192, remaining_len].min) + break unless part + remaining_len -= part.length + + yield part + end + end + end + + class Iterator < BaseIterator + alias :to_path :path + end + + private + + def fail(status, body, headers = {}) + body += "\n" + + [ + status, + { + CONTENT_TYPE => "text/plain", + CONTENT_LENGTH => body.size.to_s, + "X-Cascade" => "pass" + }.merge!(headers), + [body] + ] + end + + # The MIME type for the contents of the file located at @path + def mime_type(path, default_mime) + Mime.mime_type(::File.extname(path), default_mime) + end + + def filesize(path) + # We check via File::size? whether this file provides size info + # via stat (e.g. /proc files often don't), otherwise we have to + # figure it out by reading the whole file into memory. + ::File.size?(path) || ::File.read(path).bytesize + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler.rb new file mode 100644 index 0000000000..df17b238dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module Rack + # *Handlers* connect web servers with Rack. + # + # Rack includes Handlers for Thin, WEBrick, FastCGI, CGI, SCGI + # and LiteSpeed. + # + # Handlers usually are activated by calling MyHandler.run(myapp). + # A second optional hash can be passed to include server-specific + # configuration. + module Handler + def self.get(server) + return unless server + server = server.to_s + + unless @handlers.include? server + load_error = try_require('rack/handler', server) + end + + if klass = @handlers[server] + const_get(klass) + else + const_get(server, false) + end + + rescue NameError => name_error + raise load_error || name_error + end + + # Select first available Rack handler given an `Array` of server names. + # Raises `LoadError` if no handler was found. + # + # > pick ['thin', 'webrick'] + # => Rack::Handler::WEBrick + def self.pick(server_names) + server_names = Array(server_names) + server_names.each do |server_name| + begin + return get(server_name.to_s) + rescue LoadError, NameError + end + end + + raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}." + end + + SERVER_NAMES = %w(puma thin falcon webrick).freeze + private_constant :SERVER_NAMES + + def self.default + # Guess. + if ENV.include?("PHP_FCGI_CHILDREN") + Rack::Handler::FastCGI + elsif ENV.include?(REQUEST_METHOD) + Rack::Handler::CGI + elsif ENV.include?("RACK_HANDLER") + self.get(ENV["RACK_HANDLER"]) + else + pick SERVER_NAMES + end + end + + # Transforms server-name constants to their canonical form as filenames, + # then tries to require them but silences the LoadError if not found + # + # Naming convention: + # + # Foo # => 'foo' + # FooBar # => 'foo_bar.rb' + # FooBAR # => 'foobar.rb' + # FOObar # => 'foobar.rb' + # FOOBAR # => 'foobar.rb' + # FooBarBaz # => 'foo_bar_baz.rb' + def self.try_require(prefix, const_name) + file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }. + gsub(/[A-Z]+[^A-Z]/, '_\&').downcase + + require(::File.join(prefix, file)) + nil + rescue LoadError => error + error + end + + def self.register(server, klass) + @handlers ||= {} + @handlers[server.to_s] = klass.to_s + end + + autoload :CGI, "rack/handler/cgi" + autoload :FastCGI, "rack/handler/fastcgi" + autoload :WEBrick, "rack/handler/webrick" + autoload :LSWS, "rack/handler/lsws" + autoload :SCGI, "rack/handler/scgi" + autoload :Thin, "rack/handler/thin" + + register 'cgi', 'Rack::Handler::CGI' + register 'fastcgi', 'Rack::Handler::FastCGI' + register 'webrick', 'Rack::Handler::WEBrick' + register 'lsws', 'Rack::Handler::LSWS' + register 'scgi', 'Rack::Handler::SCGI' + register 'thin', 'Rack::Handler::Thin' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/cgi.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/cgi.rb new file mode 100644 index 0000000000..1c11ab3606 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/cgi.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Rack + module Handler + class CGI + def self.run(app, **options) + $stdin.binmode + serve app + end + + def self.serve(app) + env = ENV.to_hash + env.delete "HTTP_CONTENT_LENGTH" + + env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/" + + env.update( + RACK_VERSION => Rack::VERSION, + RACK_INPUT => Rack::RewindableInput.new($stdin), + RACK_ERRORS => $stderr, + RACK_MULTITHREAD => false, + RACK_MULTIPROCESS => true, + RACK_RUNONCE => true, + RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http" + ) + + env[QUERY_STRING] ||= "" + env[HTTP_VERSION] ||= env[SERVER_PROTOCOL] + env[REQUEST_PATH] ||= "/" + + status, headers, body = app.call(env) + begin + send_headers status, headers + send_body body + ensure + body.close if body.respond_to? :close + end + end + + def self.send_headers(status, headers) + $stdout.print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + $stdout.print "#{k}: #{v}\r\n" + } + } + $stdout.print "\r\n" + $stdout.flush + end + + def self.send_body(body) + body.each { |part| + $stdout.print part + $stdout.flush + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/fastcgi.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/fastcgi.rb new file mode 100644 index 0000000000..1df123e02a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/fastcgi.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'fcgi' +require 'socket' + +if defined? FCGI::Stream + class FCGI::Stream + alias _rack_read_without_buffer read + + def read(n, buffer = nil) + buf = _rack_read_without_buffer n + buffer.replace(buf.to_s) if buffer + buf + end + end +end + +module Rack + module Handler + class FastCGI + def self.run(app, **options) + if options[:File] + STDIN.reopen(UNIXServer.new(options[:File])) + elsif options[:Port] + STDIN.reopen(TCPServer.new(options[:Host], options[:Port])) + end + FCGI.each { |request| + serve request, app + } + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + "File=PATH" => "Creates a Domain socket at PATH instead of a TCP socket. Ignores Host and Port if set.", + } + end + + def self.serve(request, app) + env = request.env + env.delete "HTTP_CONTENT_LENGTH" + + env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/" + + rack_input = RewindableInput.new(request.in) + + env.update( + RACK_VERSION => Rack::VERSION, + RACK_INPUT => rack_input, + RACK_ERRORS => request.err, + RACK_MULTITHREAD => false, + RACK_MULTIPROCESS => true, + RACK_RUNONCE => false, + RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http" + ) + + env[QUERY_STRING] ||= "" + env[HTTP_VERSION] ||= env[SERVER_PROTOCOL] + env[REQUEST_PATH] ||= "/" + env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == "" + env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == "" + + begin + status, headers, body = app.call(env) + begin + send_headers request.out, status, headers + send_body request.out, body + ensure + body.close if body.respond_to? :close + end + ensure + rack_input.close + request.finish + end + end + + def self.send_headers(out, status, headers) + out.print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + out.print "#{k}: #{v}\r\n" + } + } + out.print "\r\n" + out.flush + end + + def self.send_body(out, body) + body.each { |part| + out.print part + out.flush + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/lsws.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/lsws.rb new file mode 100644 index 0000000000..f12090bd62 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/lsws.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'lsapi' + +module Rack + module Handler + class LSWS + def self.run(app, **options) + while LSAPI.accept != nil + serve app + end + end + def self.serve(app) + env = ENV.to_hash + env.delete "HTTP_CONTENT_LENGTH" + env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/" + + rack_input = RewindableInput.new($stdin.read.to_s) + + env.update( + RACK_VERSION => Rack::VERSION, + RACK_INPUT => rack_input, + RACK_ERRORS => $stderr, + RACK_MULTITHREAD => false, + RACK_MULTIPROCESS => true, + RACK_RUNONCE => false, + RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http" + ) + + env[QUERY_STRING] ||= "" + env[HTTP_VERSION] ||= env[SERVER_PROTOCOL] + env[REQUEST_PATH] ||= "/" + status, headers, body = app.call(env) + begin + send_headers status, headers + send_body body + ensure + body.close if body.respond_to? :close + end + ensure + rack_input.close + end + def self.send_headers(status, headers) + print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + print "#{k}: #{v}\r\n" + } + } + print "\r\n" + STDOUT.flush + end + def self.send_body(body) + body.each { |part| + print part + STDOUT.flush + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/scgi.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/scgi.rb new file mode 100644 index 0000000000..e3b8d3c6f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/scgi.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'scgi' +require 'stringio' + +module Rack + module Handler + class SCGI < ::SCGI::Processor + attr_accessor :app + + def self.run(app, **options) + options[:Socket] = UNIXServer.new(options[:File]) if options[:File] + new(options.merge(app: app, + host: options[:Host], + port: options[:Port], + socket: options[:Socket])).listen + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + + def initialize(settings = {}) + @app = settings[:app] + super(settings) + end + + def process_request(request, input_body, socket) + env = Hash[request] + env.delete "HTTP_CONTENT_TYPE" + env.delete "HTTP_CONTENT_LENGTH" + env[REQUEST_PATH], env[QUERY_STRING] = env["REQUEST_URI"].split('?', 2) + env[HTTP_VERSION] ||= env[SERVER_PROTOCOL] + env[PATH_INFO] = env[REQUEST_PATH] + env[QUERY_STRING] ||= "" + env[SCRIPT_NAME] = "" + + rack_input = StringIO.new(input_body) + rack_input.set_encoding(Encoding::BINARY) + + env.update( + RACK_VERSION => Rack::VERSION, + RACK_INPUT => rack_input, + RACK_ERRORS => $stderr, + RACK_MULTITHREAD => true, + RACK_MULTIPROCESS => true, + RACK_RUNONCE => false, + RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http" + ) + + status, headers, body = app.call(env) + begin + socket.write("Status: #{status}\r\n") + headers.each do |k, vs| + vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")} + end + socket.write("\r\n") + body.each {|s| socket.write(s)} + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/thin.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/thin.rb new file mode 100644 index 0000000000..393a6e9869 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/thin.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "thin" +require "thin/server" +require "thin/logging" +require "thin/backends/tcp_server" + +module Rack + module Handler + class Thin + def self.run(app, **options) + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + host = options.delete(:Host) || default_host + port = options.delete(:Port) || 8080 + args = [host, port, app, options] + # Thin versions below 0.8.0 do not support additional options + args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8 + server = ::Thin::Server.new(*args) + yield server if block_given? + server.start + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/webrick.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/webrick.rb new file mode 100644 index 0000000000..d2f389758a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/handler/webrick.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require 'webrick' +require 'stringio' + +# This monkey patch allows for applications to perform their own chunking +# through WEBrick::HTTPResponse if rack is set to true. +class WEBrick::HTTPResponse + attr_accessor :rack + + alias _rack_setup_header setup_header + def setup_header + app_chunking = rack && @header['transfer-encoding'] == 'chunked' + + @chunked = app_chunking if app_chunking + + _rack_setup_header + + @chunked = false if app_chunking + end +end + +module Rack + module Handler + class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet + def self.run(app, **options) + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : nil + + if !options[:BindAddress] || options[:Host] + options[:BindAddress] = options.delete(:Host) || default_host + end + options[:Port] ||= 8080 + if options[:SSLEnable] + require 'webrick/https' + end + + @server = ::WEBrick::HTTPServer.new(options) + @server.mount "/", Rack::Handler::WEBrick, app + yield @server if block_given? + @server.start + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + + def self.shutdown + if @server + @server.shutdown + @server = nil + end + end + + def initialize(server, app) + super server + @app = app + end + + def service(req, res) + res.rack = true + env = req.meta_vars + env.delete_if { |k, v| v.nil? } + + rack_input = StringIO.new(req.body.to_s) + rack_input.set_encoding(Encoding::BINARY) + + env.update( + RACK_VERSION => Rack::VERSION, + RACK_INPUT => rack_input, + RACK_ERRORS => $stderr, + RACK_MULTITHREAD => true, + RACK_MULTIPROCESS => false, + RACK_RUNONCE => false, + RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http", + RACK_IS_HIJACK => true, + RACK_HIJACK => lambda { raise NotImplementedError, "only partial hijack is supported."}, + RACK_HIJACK_IO => nil + ) + + env[HTTP_VERSION] ||= env[SERVER_PROTOCOL] + env[QUERY_STRING] ||= "" + unless env[PATH_INFO] == "" + path, n = req.request_uri.path, env[SCRIPT_NAME].length + env[PATH_INFO] = path[n, path.length - n] + end + env[REQUEST_PATH] ||= [env[SCRIPT_NAME], env[PATH_INFO]].join + + status, headers, body = @app.call(env) + begin + res.status = status.to_i + io_lambda = nil + headers.each { |k, vs| + if k == RACK_HIJACK + io_lambda = vs + elsif k.downcase == "set-cookie" + res.cookies.concat vs.split("\n") + else + # Since WEBrick won't accept repeated headers, + # merge the values per RFC 1945 section 4.2. + res[k] = vs.split("\n").join(", ") + end + } + + if io_lambda + rd, wr = IO.pipe + res.body = rd + res.chunked = true + io_lambda.call wr + elsif body.respond_to?(:to_path) + res.body = ::File.open(body.to_path, 'rb') + else + body.each { |part| + res.body << part + } + end + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/head.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/head.rb new file mode 100644 index 0000000000..8025a27d51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/head.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Rack + # Rack::Head returns an empty body for all HEAD requests. It leaves + # all other requests unchanged. + class Head + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + if env[REQUEST_METHOD] == HEAD + [ + status, headers, Rack::BodyProxy.new([]) do + body.close if body.respond_to? :close + end + ] + else + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lint.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lint.rb new file mode 100644 index 0000000000..67d2eb1294 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lint.rb @@ -0,0 +1,806 @@ +# frozen_string_literal: true + +require 'forwardable' + +module Rack + # Rack::Lint validates your application and the requests and + # responses according to the Rack spec. + + class Lint + def initialize(app) + @app = app + @content_length = nil + end + + # :stopdoc: + + class LintError < RuntimeError; end + module Assertion + def assert(message) + unless yield + raise LintError, message + end + end + end + include Assertion + + ## This specification aims to formalize the Rack protocol. You + ## can (and should) use Rack::Lint to enforce it. + ## + ## When you develop middleware, be sure to add a Lint before and + ## after to catch all mistakes. + + ## = Rack applications + + ## A Rack application is a Ruby object (not a class) that + ## responds to +call+. + def call(env = nil) + dup._call(env) + end + + def _call(env) + ## It takes exactly one argument, the *environment* + assert("No env given") { env } + check_env env + + env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT]) + env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS]) + + ## and returns an Array of exactly three values: + ary = @app.call(env) + assert("response is not an Array, but #{ary.class}") { + ary.kind_of? Array + } + assert("response array has #{ary.size} elements instead of 3") { + ary.size == 3 + } + + status, headers, @body = ary + ## The *status*, + check_status status + ## the *headers*, + check_headers headers + + hijack_proc = check_hijack_response headers, env + if hijack_proc && headers.is_a?(Hash) + headers[RACK_HIJACK] = hijack_proc + end + + ## and the *body*. + check_content_type status, headers + check_content_length status, headers + @head_request = env[REQUEST_METHOD] == HEAD + [status, headers, self] + end + + ## == The Environment + def check_env(env) + ## The environment must be an unfrozen instance of Hash that includes + ## CGI-like headers. The application is free to modify the + ## environment. + assert("env #{env.inspect} is not a Hash, but #{env.class}") { + env.kind_of? Hash + } + assert("env should not be frozen, but is") { + !env.frozen? + } + + ## + ## The environment is required to include these variables + ## (adopted from PEP333), except when they'd be empty, but see + ## below. + + ## REQUEST_METHOD:: The HTTP request method, such as + ## "GET" or "POST". This cannot ever + ## be an empty string, and so is + ## always required. + + ## SCRIPT_NAME:: The initial portion of the request + ## URL's "path" that corresponds to the + ## application object, so that the + ## application knows its virtual + ## "location". This may be an empty + ## string, if the application corresponds + ## to the "root" of the server. + + ## PATH_INFO:: The remainder of the request URL's + ## "path", designating the virtual + ## "location" of the request's target + ## within the application. This may be an + ## empty string, if the request URL targets + ## the application root and does not have a + ## trailing slash. This value may be + ## percent-encoded when originating from + ## a URL. + + ## QUERY_STRING:: The portion of the request URL that + ## follows the ?, if any. May be + ## empty, but is always required! + + ## SERVER_NAME:: When combined with SCRIPT_NAME and + ## PATH_INFO, these variables can be + ## used to complete the URL. Note, however, + ## that HTTP_HOST, if present, + ## should be used in preference to + ## SERVER_NAME for reconstructing + ## the request URL. + ## SERVER_NAME can never be an empty + ## string, and so is always required. + + ## SERVER_PORT:: An optional +Integer+ which is the port the + ## server is running on. Should be specified if + ## the server is running on a non-standard port. + + ## HTTP_ Variables:: Variables corresponding to the + ## client-supplied HTTP request + ## headers (i.e., variables whose + ## names begin with HTTP_). The + ## presence or absence of these + ## variables should correspond with + ## the presence or absence of the + ## appropriate HTTP header in the + ## request. See + ## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18] + ## for specific behavior. + + ## In addition to this, the Rack environment must include these + ## Rack-specific variables: + + ## rack.version:: The Array representing this version of Rack + ## See Rack::VERSION, that corresponds to + ## the version of this SPEC. + + ## rack.url_scheme:: +http+ or +https+, depending on the + ## request URL. + + ## rack.input:: See below, the input stream. + + ## rack.errors:: See below, the error stream. + + ## rack.multithread:: true if the application object may be + ## simultaneously invoked by another thread + ## in the same process, false otherwise. + + ## rack.multiprocess:: true if an equivalent application object + ## may be simultaneously invoked by another + ## process, false otherwise. + + ## rack.run_once:: true if the server expects + ## (but does not guarantee!) that the + ## application will only be invoked this one + ## time during the life of its containing + ## process. Normally, this will only be true + ## for a server based on CGI + ## (or something similar). + + ## rack.hijack?:: present and true if the server supports + ## connection hijacking. See below, hijacking. + + ## rack.hijack:: an object responding to #call that must be + ## called at least once before using + ## rack.hijack_io. + ## It is recommended #call return rack.hijack_io + ## as well as setting it in env if necessary. + + ## rack.hijack_io:: if rack.hijack? is true, and rack.hijack + ## has received #call, this will contain + ## an object resembling an IO. See hijacking. + + ## Additional environment specifications have approved to + ## standardized middleware APIs. None of these are required to + ## be implemented by the server. + + ## rack.session:: A hash like interface for storing + ## request session data. + ## The store must implement: + if session = env[RACK_SESSION] + ## store(key, value) (aliased as []=); + assert("session #{session.inspect} must respond to store and []=") { + session.respond_to?(:store) && session.respond_to?(:[]=) + } + + ## fetch(key, default = nil) (aliased as []); + assert("session #{session.inspect} must respond to fetch and []") { + session.respond_to?(:fetch) && session.respond_to?(:[]) + } + + ## delete(key); + assert("session #{session.inspect} must respond to delete") { + session.respond_to?(:delete) + } + + ## clear; + assert("session #{session.inspect} must respond to clear") { + session.respond_to?(:clear) + } + + ## to_hash (returning unfrozen Hash instance); + assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") { + session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen? + } + end + + ## rack.logger:: A common object interface for logging messages. + ## The object must implement: + if logger = env[RACK_LOGGER] + ## info(message, &block) + assert("logger #{logger.inspect} must respond to info") { + logger.respond_to?(:info) + } + + ## debug(message, &block) + assert("logger #{logger.inspect} must respond to debug") { + logger.respond_to?(:debug) + } + + ## warn(message, &block) + assert("logger #{logger.inspect} must respond to warn") { + logger.respond_to?(:warn) + } + + ## error(message, &block) + assert("logger #{logger.inspect} must respond to error") { + logger.respond_to?(:error) + } + + ## fatal(message, &block) + assert("logger #{logger.inspect} must respond to fatal") { + logger.respond_to?(:fatal) + } + end + + ## rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. + if bufsize = env[RACK_MULTIPART_BUFFER_SIZE] + assert("rack.multipart.buffer_size must be an Integer > 0 if specified") { + bufsize.is_a?(Integer) && bufsize > 0 + } + end + + ## rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. + if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY] + assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) } + env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type| + io = tempfile_factory.call(filename, content_type) + assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) } + io + end + end + + ## The server or the application can store their own data in the + ## environment, too. The keys must contain at least one dot, + ## and should be prefixed uniquely. The prefix rack. + ## is reserved for use with the Rack core distribution and other + ## accepted specifications and must not be used otherwise. + ## + + %w[REQUEST_METHOD SERVER_NAME QUERY_STRING + rack.version rack.input rack.errors + rack.multithread rack.multiprocess rack.run_once].each { |header| + assert("env missing required key #{header}") { env.include? header } + } + + ## The SERVER_PORT must be an Integer if set. + assert("env[SERVER_PORT] is not an Integer") do + server_port = env["SERVER_PORT"] + server_port.nil? || (Integer(server_port) rescue false) + end + + ## The SERVER_NAME must be a valid authority as defined by RFC7540. + assert("#{env[SERVER_NAME]} must be a valid authority") do + URI.parse("http://#{env[SERVER_NAME]}/") rescue false + end + + ## The HTTP_HOST must be a valid authority as defined by RFC7540. + assert("#{env[HTTP_HOST]} must be a valid authority") do + URI.parse("http://#{env[HTTP_HOST]}/") rescue false + end + + ## The environment must not contain the keys + ## HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH + ## (use the versions without HTTP_). + %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header| + assert("env contains #{header}, must use #{header[5, -1]}") { + not env.include? header + } + } + + ## The CGI keys (named without a period) must have String values. + ## If the string values for CGI keys contain non-ASCII characters, + ## they should use ASCII-8BIT encoding. + env.each { |key, value| + next if key.include? "." # Skip extensions + assert("env variable #{key} has non-string value #{value.inspect}") { + value.kind_of? String + } + next if value.encoding == Encoding::ASCII_8BIT + assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") { + value.b !~ /[\x80-\xff]/n + } + } + + ## There are the following restrictions: + + ## * rack.version must be an array of Integers. + assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") { + env[RACK_VERSION].kind_of? Array + } + ## * rack.url_scheme must either be +http+ or +https+. + assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") { + %w[http https].include?(env[RACK_URL_SCHEME]) + } + + ## * There must be a valid input stream in rack.input. + check_input env[RACK_INPUT] + ## * There must be a valid error stream in rack.errors. + check_error env[RACK_ERRORS] + ## * There may be a valid hijack stream in rack.hijack_io + check_hijack env + + ## * The REQUEST_METHOD must be a valid token. + assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}") { + env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/ + } + + ## * The SCRIPT_NAME, if non-empty, must start with / + assert("SCRIPT_NAME must start with /") { + !env.include?(SCRIPT_NAME) || + env[SCRIPT_NAME] == "" || + env[SCRIPT_NAME] =~ /\A\// + } + ## * The PATH_INFO, if non-empty, must start with / + assert("PATH_INFO must start with /") { + !env.include?(PATH_INFO) || + env[PATH_INFO] == "" || + env[PATH_INFO] =~ /\A\// + } + ## * The CONTENT_LENGTH, if given, must consist of digits only. + assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") { + !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/ + } + + ## * One of SCRIPT_NAME or PATH_INFO must be + ## set. PATH_INFO should be / if + ## SCRIPT_NAME is empty. + assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") { + env[SCRIPT_NAME] || env[PATH_INFO] + } + ## SCRIPT_NAME never should be /, but instead be empty. + assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") { + env[SCRIPT_NAME] != "/" + } + end + + ## === The Input Stream + ## + ## The input stream is an IO-like object which contains the raw HTTP + ## POST data. + def check_input(input) + ## When applicable, its external encoding must be "ASCII-8BIT" and it + ## must be opened in binary mode, for Ruby 1.9 compatibility. + assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") { + input.external_encoding == Encoding::ASCII_8BIT + } if input.respond_to?(:external_encoding) + assert("rack.input #{input} is not opened in binary mode") { + input.binmode? + } if input.respond_to?(:binmode?) + + ## The input stream must respond to +gets+, +each+, +read+ and +rewind+. + [:gets, :each, :read, :rewind].each { |method| + assert("rack.input #{input} does not respond to ##{method}") { + input.respond_to? method + } + } + end + + class InputWrapper + include Assertion + + def initialize(input) + @input = input + end + + ## * +gets+ must be called without arguments and return a string, + ## or +nil+ on EOF. + def gets(*args) + assert("rack.input#gets called with arguments") { args.size == 0 } + v = @input.gets + assert("rack.input#gets didn't return a String") { + v.nil? or v.kind_of? String + } + v + end + + ## * +read+ behaves like IO#read. + ## Its signature is read([length, [buffer]]). + ## + ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, + ## and +buffer+ must be a String and may not be nil. + ## + ## If +length+ is given and not nil, then this method reads at most + ## +length+ bytes from the input stream. + ## + ## If +length+ is not given or nil, then this method reads + ## all data until EOF. + ## + ## When EOF is reached, this method returns nil if +length+ is given + ## and not nil, or "" if +length+ is not given or is nil. + ## + ## If +buffer+ is given, then the read data will be placed + ## into +buffer+ instead of a newly created String object. + def read(*args) + assert("rack.input#read called with too many arguments") { + args.size <= 2 + } + if args.size >= 1 + assert("rack.input#read called with non-integer and non-nil length") { + args.first.kind_of?(Integer) || args.first.nil? + } + assert("rack.input#read called with a negative length") { + args.first.nil? || args.first >= 0 + } + end + if args.size >= 2 + assert("rack.input#read called with non-String buffer") { + args[1].kind_of?(String) + } + end + + v = @input.read(*args) + + assert("rack.input#read didn't return nil or a String") { + v.nil? or v.kind_of? String + } + if args[0].nil? + assert("rack.input#read(nil) returned nil on EOF") { + !v.nil? + } + end + + v + end + + ## * +each+ must be called without arguments and only yield Strings. + def each(*args) + assert("rack.input#each called with arguments") { args.size == 0 } + @input.each { |line| + assert("rack.input#each didn't yield a String") { + line.kind_of? String + } + yield line + } + end + + ## * +rewind+ must be called without arguments. It rewinds the input + ## stream back to the beginning. It must not raise Errno::ESPIPE: + ## that is, it may not be a pipe or a socket. Therefore, handler + ## developers must buffer the input data into some rewindable object + ## if the underlying input stream is not rewindable. + def rewind(*args) + assert("rack.input#rewind called with arguments") { args.size == 0 } + assert("rack.input#rewind raised Errno::ESPIPE") { + begin + @input.rewind + true + rescue Errno::ESPIPE + false + end + } + end + + ## * +close+ must never be called on the input stream. + def close(*args) + assert("rack.input#close must not be called") { false } + end + end + + ## === The Error Stream + def check_error(error) + ## The error stream must respond to +puts+, +write+ and +flush+. + [:puts, :write, :flush].each { |method| + assert("rack.error #{error} does not respond to ##{method}") { + error.respond_to? method + } + } + end + + class ErrorWrapper + include Assertion + + def initialize(error) + @error = error + end + + ## * +puts+ must be called with a single argument that responds to +to_s+. + def puts(str) + @error.puts str + end + + ## * +write+ must be called with a single argument that is a String. + def write(str) + assert("rack.errors#write not called with a String") { str.kind_of? String } + @error.write str + end + + ## * +flush+ must be called without arguments and must be called + ## in order to make the error appear for sure. + def flush + @error.flush + end + + ## * +close+ must never be called on the error stream. + def close(*args) + assert("rack.errors#close must not be called") { false } + end + end + + class HijackWrapper + include Assertion + extend Forwardable + + REQUIRED_METHODS = [ + :read, :write, :read_nonblock, :write_nonblock, :flush, :close, + :close_read, :close_write, :closed? + ] + + def_delegators :@io, *REQUIRED_METHODS + + def initialize(io) + @io = io + REQUIRED_METHODS.each do |meth| + assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth } + end + end + end + + ## === Hijacking + # + # AUTHORS: n.b. The trailing whitespace between paragraphs is important and + # should not be removed. The whitespace creates paragraphs in the RDoc + # output. + # + ## ==== Request (before status) + def check_hijack(env) + if env[RACK_IS_HIJACK] + ## If rack.hijack? is true then rack.hijack must respond to #call. + original_hijack = env[RACK_HIJACK] + assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) } + env[RACK_HIJACK] = proc do + ## rack.hijack must return the io that will also be assigned (or is + ## already present, in rack.hijack_io. + io = original_hijack.call + HijackWrapper.new(io) + ## + ## rack.hijack_io must respond to: + ## read, write, read_nonblock, write_nonblock, flush, close, + ## close_read, close_write, closed? + ## + ## The semantics of these IO methods must be a best effort match to + ## those of a normal ruby IO or Socket object, using standard + ## arguments and raising standard exceptions. Servers are encouraged + ## to simply pass on real IO objects, although it is recognized that + ## this approach is not directly compatible with SPDY and HTTP 2.0. + ## + ## IO provided in rack.hijack_io should preference the + ## IO::WaitReadable and IO::WaitWritable APIs wherever supported. + ## + ## There is a deliberate lack of full specification around + ## rack.hijack_io, as semantics will change from server to server. + ## Users are encouraged to utilize this API with a knowledge of their + ## server choice, and servers may extend the functionality of + ## hijack_io to provide additional features to users. The purpose of + ## rack.hijack is for Rack to "get out of the way", as such, Rack only + ## provides the minimum of specification and support. + env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO]) + io + end + else + ## + ## If rack.hijack? is false, then rack.hijack should not be set. + assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? } + ## + ## If rack.hijack? is false, then rack.hijack_io should not be set. + assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? } + end + end + + ## ==== Response (after headers) + ## It is also possible to hijack a response after the status and headers + ## have been sent. + def check_hijack_response(headers, env) + + # this check uses headers like a hash, but the spec only requires + # headers respond to #each + headers = Rack::Utils::HeaderHash[headers] + + ## In order to do this, an application may set the special header + ## rack.hijack to an object that responds to call + ## accepting an argument that conforms to the rack.hijack_io + ## protocol. + ## + ## After the headers have been sent, and this hijack callback has been + ## called, the application is now responsible for the remaining lifecycle + ## of the IO. The application is also responsible for maintaining HTTP + ## semantics. Of specific note, in almost all cases in the current SPEC, + ## applications will have wanted to specify the header Connection:close in + ## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for + ## returning hijacked sockets to the web server. For that purpose, use the + ## body streaming API instead (progressively yielding strings via each). + ## + ## Servers must ignore the body part of the response tuple when + ## the rack.hijack response API is in use. + + if env[RACK_IS_HIJACK] && headers[RACK_HIJACK] + assert('rack.hijack header must respond to #call') { + headers[RACK_HIJACK].respond_to? :call + } + original_hijack = headers[RACK_HIJACK] + proc do |io| + original_hijack.call HijackWrapper.new(io) + end + else + ## + ## The special response header rack.hijack must only be set + ## if the request env has rack.hijack? true. + assert('rack.hijack header must not be present if server does not support hijacking') { + headers[RACK_HIJACK].nil? + } + + nil + end + end + ## ==== Conventions + ## * Middleware should not use hijack unless it is handling the whole + ## response. + ## * Middleware may wrap the IO object for the response pattern. + ## * Middleware should not wrap the IO object for the request pattern. The + ## request pattern is intended to provide the hijacker with "raw tcp". + + ## == The Response + + ## === The Status + def check_status(status) + ## This is an HTTP status. When parsed as integer (+to_i+), it must be + ## greater than or equal to 100. + assert("Status must be >=100 seen as integer") { status.to_i >= 100 } + end + + ## === The Headers + def check_headers(header) + ## The header must respond to +each+, and yield values of key and value. + assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") { + header.respond_to? :each + } + + header.each { |key, value| + ## The header keys must be Strings. + assert("header key must be a string, was #{key.class}") { + key.kind_of? String + } + + ## Special headers starting "rack." are for communicating with the + ## server, and must not be sent back to the client. + next if key =~ /^rack\..+$/ + + ## The header must not contain a +Status+ key. + assert("header must not contain Status") { key.downcase != "status" } + ## The header must conform to RFC7230 token specification, i.e. cannot + ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". + assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ } + + ## The values of the header must be Strings, + assert("a header value must be a String, but the value of " + + "'#{key}' is a #{value.class}") { value.kind_of? String } + ## consisting of lines (for multiple header values, e.g. multiple + ## Set-Cookie values) separated by "\\n". + value.split("\n").each { |item| + ## The lines must not contain characters below 037. + assert("invalid header value #{key}: #{item.inspect}") { + item !~ /[\000-\037]/ + } + } + } + end + + ## === The Content-Type + def check_content_type(status, headers) + headers.each { |key, value| + ## There must not be a Content-Type, when the +Status+ is 1xx, + ## 204 or 304. + if key.downcase == "content-type" + assert("Content-Type header found in #{status} response, not allowed") { + not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i + } + return + end + } + end + + ## === The Content-Length + def check_content_length(status, headers) + headers.each { |key, value| + if key.downcase == 'content-length' + ## There must not be a Content-Length header when the + ## +Status+ is 1xx, 204 or 304. + assert("Content-Length header found in #{status} response, not allowed") { + not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i + } + @content_length = value + end + } + end + + def verify_content_length(bytes) + if @head_request + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 + } + elsif @content_length + assert("Content-Length header was #{@content_length}, but should be #{bytes}") { + @content_length == bytes.to_s + } + end + end + + ## === The Body + def each + @closed = false + bytes = 0 + + ## The Body must respond to +each+ + assert("Response body must respond to each") do + @body.respond_to?(:each) + end + + @body.each { |part| + ## and must only yield String values. + assert("Body yielded non-string value #{part.inspect}") { + part.kind_of? String + } + bytes += part.bytesize + yield part + } + verify_content_length(bytes) + + ## + ## The Body itself should not be an instance of String, as this will + ## break in Ruby 1.9. + ## + ## If the Body responds to +close+, it will be called after iteration. If + ## the body is replaced by a middleware after action, the original body + ## must be closed first, if it responds to close. + # XXX howto: assert("Body has not been closed") { @closed } + + + ## + ## If the Body responds to +to_path+, it must return a String + ## identifying the location of a file whose contents are identical + ## to that produced by calling +each+; this may be used by the + ## server as an alternative, possibly more efficient way to + ## transport the response. + + if @body.respond_to?(:to_path) + assert("The file identified by body.to_path does not exist") { + ::File.exist? @body.to_path + } + end + + ## + ## The Body commonly is an Array of Strings, the application + ## instance itself, or a File-like object. + end + + def close + @closed = true + @body.close if @body.respond_to?(:close) + end + + # :startdoc: + + end +end + +## == Thanks +## Some parts of this specification are adopted from PEP333: Python +## Web Server Gateway Interface +## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank +## everyone involved in that effort. diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lobster.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lobster.rb new file mode 100644 index 0000000000..b86a625de0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lobster.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'zlib' + +module Rack + # Paste has a Pony, Rack has a Lobster! + class Lobster + LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2 + P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0 + t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ + I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0]) + + LambdaLobster = lambda { |env| + if env[QUERY_STRING].include?("flip") + lobster = LobsterString.split("\n"). + map { |line| line.ljust(42).reverse }. + join("\n") + href = "?" + else + lobster = LobsterString + href = "?flip" + end + + content = ["Lobstericious!", + "
", lobster, "
", + "flip!"] + length = content.inject(0) { |a, e| a + e.size }.to_s + [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content] + } + + def call(env) + req = Request.new(env) + if req.GET["flip"] == "left" + lobster = LobsterString.split("\n").map do |line| + line.ljust(42).reverse. + gsub('\\', 'TEMP'). + gsub('/', '\\'). + gsub('TEMP', '/'). + gsub('{', '}'). + gsub('(', ')') + end.join("\n") + href = "?flip=right" + elsif req.GET["flip"] == "crash" + raise "Lobster crashed" + else + lobster = LobsterString + href = "?flip=left" + end + + res = Response.new + res.write "Lobstericious!" + res.write "
"
+      res.write lobster
+      res.write "
" + res.write "

flip!

" + res.write "

crash!

" + res.finish + end + + end +end + +if $0 == __FILE__ + # :nocov: + require_relative '../rack' + Rack::Server.start( + app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292 + ) + # :nocov: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lock.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lock.rb new file mode 100644 index 0000000000..4bae3a9034 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/lock.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'thread' + +module Rack + # Rack::Lock locks every request inside a mutex, so that every request + # will effectively be executed synchronously. + class Lock + def initialize(app, mutex = Mutex.new) + @app, @mutex = app, mutex + end + + def call(env) + @mutex.lock + @env = env + @old_rack_multithread = env[RACK_MULTITHREAD] + begin + response = @app.call(env.merge!(RACK_MULTITHREAD => false)) + returned = response << BodyProxy.new(response.pop) { unlock } + ensure + unlock unless returned + end + end + + private + + def unlock + @mutex.unlock + @env[RACK_MULTITHREAD] = @old_rack_multithread + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/logger.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/logger.rb new file mode 100644 index 0000000000..6c4bede0cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/logger.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'logger' + +module Rack + # Sets up rack.logger to write to rack.errors stream + class Logger + def initialize(app, level = ::Logger::INFO) + @app, @level = app, level + end + + def call(env) + logger = ::Logger.new(env[RACK_ERRORS]) + logger.level = @level + + env[RACK_LOGGER] = logger + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/media_type.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/media_type.rb new file mode 100644 index 0000000000..41937c9947 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/media_type.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Rack + # Rack::MediaType parse media type and parameters out of content_type string + + class MediaType + SPLIT_PATTERN = %r{\s*[;,]\s*} + + class << self + # The media type (type/subtype) portion of the CONTENT_TYPE header + # without any media type parameters. e.g., when CONTENT_TYPE is + # "text/plain;charset=utf-8", the media-type is "text/plain". + # + # For more information on the use of media types in HTTP, see: + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 + def type(content_type) + return nil unless content_type + content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase! + end + + # The media type parameters provided in CONTENT_TYPE as a Hash, or + # an empty Hash if no CONTENT_TYPE or media-type parameters were + # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", + # this method responds with the following Hash: + # { 'charset' => 'utf-8' } + def params(content_type) + return {} if content_type.nil? + + content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh| + k, v = s.split('=', 2) + + hsh[k.tap(&:downcase!)] = strip_doublequotes(v) + end + end + + private + + def strip_doublequotes(str) + (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/method_override.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/method_override.rb new file mode 100644 index 0000000000..b586f5339b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/method_override.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Rack + class MethodOverride + HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK] + + METHOD_OVERRIDE_PARAM_KEY = "_method" + HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE" + ALLOWED_METHODS = %w[POST] + + def initialize(app) + @app = app + end + + def call(env) + if allowed_methods.include?(env[REQUEST_METHOD]) + method = method_override(env) + if HTTP_METHODS.include?(method) + env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD] + env[REQUEST_METHOD] = method + end + end + + @app.call(env) + end + + def method_override(env) + req = Request.new(env) + method = method_override_param(req) || + env[HTTP_METHOD_OVERRIDE_HEADER] + begin + method.to_s.upcase + rescue ArgumentError + env[RACK_ERRORS].puts "Invalid string for method" + end + end + + private + + def allowed_methods + ALLOWED_METHODS + end + + def method_override_param(req) + req.POST[METHOD_OVERRIDE_PARAM_KEY] + rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError + req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params" + rescue EOFError + req.get_header(RACK_ERRORS).puts "Bad request content body" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mime.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mime.rb new file mode 100644 index 0000000000..f6c02c1fd6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mime.rb @@ -0,0 +1,685 @@ +# frozen_string_literal: true + +module Rack + module Mime + # Returns String with mime type if found, otherwise use +fallback+. + # +ext+ should be filename extension in the '.ext' format that + # File.extname(file) returns. + # +fallback+ may be any object + # + # Also see the documentation for MIME_TYPES + # + # Usage: + # Rack::Mime.mime_type('.foo') + # + # This is a shortcut for: + # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream') + + def mime_type(ext, fallback = 'application/octet-stream') + MIME_TYPES.fetch(ext.to_s.downcase, fallback) + end + module_function :mime_type + + # Returns true if the given value is a mime match for the given mime match + # specification, false otherwise. + # + # Rack::Mime.match?('text/html', 'text/*') => true + # Rack::Mime.match?('text/plain', '*') => true + # Rack::Mime.match?('text/html', 'application/json') => false + + def match?(value, matcher) + v1, v2 = value.split('/', 2) + m1, m2 = matcher.split('/', 2) + + (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2) + end + module_function :match? + + # List of most common mime-types, selected various sources + # according to their usefulness in a webserving scope for Ruby + # users. + # + # To amend this list with your local mime.types list you can use: + # + # require 'webrick/httputils' + # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types') + # Rack::Mime::MIME_TYPES.merge!(list) + # + # N.B. On Ubuntu the mime.types file does not include the leading period, so + # users may need to modify the data before merging into the hash. + + MIME_TYPES = { + ".123" => "application/vnd.lotus-1-2-3", + ".3dml" => "text/vnd.in3d.3dml", + ".3g2" => "video/3gpp2", + ".3gp" => "video/3gpp", + ".a" => "application/octet-stream", + ".acc" => "application/vnd.americandynamics.acc", + ".ace" => "application/x-ace-compressed", + ".acu" => "application/vnd.acucobol", + ".aep" => "application/vnd.audiograph", + ".afp" => "application/vnd.ibm.modcap", + ".ai" => "application/postscript", + ".aif" => "audio/x-aiff", + ".aiff" => "audio/x-aiff", + ".ami" => "application/vnd.amiga.ami", + ".appcache" => "text/cache-manifest", + ".apr" => "application/vnd.lotus-approach", + ".asc" => "application/pgp-signature", + ".asf" => "video/x-ms-asf", + ".asm" => "text/x-asm", + ".aso" => "application/vnd.accpac.simply.aso", + ".asx" => "video/x-ms-asf", + ".atc" => "application/vnd.acucorp", + ".atom" => "application/atom+xml", + ".atomcat" => "application/atomcat+xml", + ".atomsvc" => "application/atomsvc+xml", + ".atx" => "application/vnd.antix.game-component", + ".au" => "audio/basic", + ".avi" => "video/x-msvideo", + ".bat" => "application/x-msdownload", + ".bcpio" => "application/x-bcpio", + ".bdm" => "application/vnd.syncml.dm+wbxml", + ".bh2" => "application/vnd.fujitsu.oasysprs", + ".bin" => "application/octet-stream", + ".bmi" => "application/vnd.bmi", + ".bmp" => "image/bmp", + ".box" => "application/vnd.previewsystems.box", + ".btif" => "image/prs.btif", + ".bz" => "application/x-bzip", + ".bz2" => "application/x-bzip2", + ".c" => "text/x-c", + ".c4g" => "application/vnd.clonk.c4group", + ".cab" => "application/vnd.ms-cab-compressed", + ".cc" => "text/x-c", + ".ccxml" => "application/ccxml+xml", + ".cdbcmsg" => "application/vnd.contact.cmsg", + ".cdkey" => "application/vnd.mediastation.cdkey", + ".cdx" => "chemical/x-cdx", + ".cdxml" => "application/vnd.chemdraw+xml", + ".cdy" => "application/vnd.cinderella", + ".cer" => "application/pkix-cert", + ".cgm" => "image/cgm", + ".chat" => "application/x-chat", + ".chm" => "application/vnd.ms-htmlhelp", + ".chrt" => "application/vnd.kde.kchart", + ".cif" => "chemical/x-cif", + ".cii" => "application/vnd.anser-web-certificate-issue-initiation", + ".cil" => "application/vnd.ms-artgalry", + ".cla" => "application/vnd.claymore", + ".class" => "application/octet-stream", + ".clkk" => "application/vnd.crick.clicker.keyboard", + ".clkp" => "application/vnd.crick.clicker.palette", + ".clkt" => "application/vnd.crick.clicker.template", + ".clkw" => "application/vnd.crick.clicker.wordbank", + ".clkx" => "application/vnd.crick.clicker", + ".clp" => "application/x-msclip", + ".cmc" => "application/vnd.cosmocaller", + ".cmdf" => "chemical/x-cmdf", + ".cml" => "chemical/x-cml", + ".cmp" => "application/vnd.yellowriver-custom-menu", + ".cmx" => "image/x-cmx", + ".com" => "application/x-msdownload", + ".conf" => "text/plain", + ".cpio" => "application/x-cpio", + ".cpp" => "text/x-c", + ".cpt" => "application/mac-compactpro", + ".crd" => "application/x-mscardfile", + ".crl" => "application/pkix-crl", + ".crt" => "application/x-x509-ca-cert", + ".csh" => "application/x-csh", + ".csml" => "chemical/x-csml", + ".csp" => "application/vnd.commonspace", + ".css" => "text/css", + ".csv" => "text/csv", + ".curl" => "application/vnd.curl", + ".cww" => "application/prs.cww", + ".cxx" => "text/x-c", + ".daf" => "application/vnd.mobius.daf", + ".davmount" => "application/davmount+xml", + ".dcr" => "application/x-director", + ".dd2" => "application/vnd.oma.dd2+xml", + ".ddd" => "application/vnd.fujixerox.ddd", + ".deb" => "application/x-debian-package", + ".der" => "application/x-x509-ca-cert", + ".dfac" => "application/vnd.dreamfactory", + ".diff" => "text/x-diff", + ".dis" => "application/vnd.mobius.dis", + ".djv" => "image/vnd.djvu", + ".djvu" => "image/vnd.djvu", + ".dll" => "application/x-msdownload", + ".dmg" => "application/octet-stream", + ".dna" => "application/vnd.dna", + ".doc" => "application/msword", + ".docm" => "application/vnd.ms-word.document.macroEnabled.12", + ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".dot" => "application/msword", + ".dotm" => "application/vnd.ms-word.template.macroEnabled.12", + ".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + ".dp" => "application/vnd.osgi.dp", + ".dpg" => "application/vnd.dpgraph", + ".dsc" => "text/prs.lines.tag", + ".dtd" => "application/xml-dtd", + ".dts" => "audio/vnd.dts", + ".dtshd" => "audio/vnd.dts.hd", + ".dv" => "video/x-dv", + ".dvi" => "application/x-dvi", + ".dwf" => "model/vnd.dwf", + ".dwg" => "image/vnd.dwg", + ".dxf" => "image/vnd.dxf", + ".dxp" => "application/vnd.spotfire.dxp", + ".ear" => "application/java-archive", + ".ecelp4800" => "audio/vnd.nuera.ecelp4800", + ".ecelp7470" => "audio/vnd.nuera.ecelp7470", + ".ecelp9600" => "audio/vnd.nuera.ecelp9600", + ".ecma" => "application/ecmascript", + ".edm" => "application/vnd.novadigm.edm", + ".edx" => "application/vnd.novadigm.edx", + ".efif" => "application/vnd.picsel", + ".ei6" => "application/vnd.pg.osasli", + ".eml" => "message/rfc822", + ".eol" => "audio/vnd.digital-winds", + ".eot" => "application/vnd.ms-fontobject", + ".eps" => "application/postscript", + ".es3" => "application/vnd.eszigno3+xml", + ".esf" => "application/vnd.epson.esf", + ".etx" => "text/x-setext", + ".exe" => "application/x-msdownload", + ".ext" => "application/vnd.novadigm.ext", + ".ez" => "application/andrew-inset", + ".ez2" => "application/vnd.ezpix-album", + ".ez3" => "application/vnd.ezpix-package", + ".f" => "text/x-fortran", + ".f77" => "text/x-fortran", + ".f90" => "text/x-fortran", + ".fbs" => "image/vnd.fastbidsheet", + ".fdf" => "application/vnd.fdf", + ".fe_launch" => "application/vnd.denovo.fcselayout-link", + ".fg5" => "application/vnd.fujitsu.oasysgp", + ".fli" => "video/x-fli", + ".flo" => "application/vnd.micrografx.flo", + ".flv" => "video/x-flv", + ".flw" => "application/vnd.kde.kivio", + ".flx" => "text/vnd.fmi.flexstor", + ".fly" => "text/vnd.fly", + ".fm" => "application/vnd.framemaker", + ".fnc" => "application/vnd.frogans.fnc", + ".for" => "text/x-fortran", + ".fpx" => "image/vnd.fpx", + ".fsc" => "application/vnd.fsc.weblaunch", + ".fst" => "image/vnd.fst", + ".ftc" => "application/vnd.fluxtime.clip", + ".fti" => "application/vnd.anser-web-funds-transfer-initiation", + ".fvt" => "video/vnd.fvt", + ".fzs" => "application/vnd.fuzzysheet", + ".g3" => "image/g3fax", + ".gac" => "application/vnd.groove-account", + ".gdl" => "model/vnd.gdl", + ".gem" => "application/octet-stream", + ".gemspec" => "text/x-script.ruby", + ".ghf" => "application/vnd.groove-help", + ".gif" => "image/gif", + ".gim" => "application/vnd.groove-identity-message", + ".gmx" => "application/vnd.gmx", + ".gph" => "application/vnd.flographit", + ".gqf" => "application/vnd.grafeq", + ".gram" => "application/srgs", + ".grv" => "application/vnd.groove-injector", + ".grxml" => "application/srgs+xml", + ".gtar" => "application/x-gtar", + ".gtm" => "application/vnd.groove-tool-message", + ".gtw" => "model/vnd.gtw", + ".gv" => "text/vnd.graphviz", + ".gz" => "application/x-gzip", + ".h" => "text/x-c", + ".h261" => "video/h261", + ".h263" => "video/h263", + ".h264" => "video/h264", + ".hbci" => "application/vnd.hbci", + ".hdf" => "application/x-hdf", + ".hh" => "text/x-c", + ".hlp" => "application/winhlp", + ".hpgl" => "application/vnd.hp-hpgl", + ".hpid" => "application/vnd.hp-hpid", + ".hps" => "application/vnd.hp-hps", + ".hqx" => "application/mac-binhex40", + ".htc" => "text/x-component", + ".htke" => "application/vnd.kenameaapp", + ".htm" => "text/html", + ".html" => "text/html", + ".hvd" => "application/vnd.yamaha.hv-dic", + ".hvp" => "application/vnd.yamaha.hv-voice", + ".hvs" => "application/vnd.yamaha.hv-script", + ".icc" => "application/vnd.iccprofile", + ".ice" => "x-conference/x-cooltalk", + ".ico" => "image/vnd.microsoft.icon", + ".ics" => "text/calendar", + ".ief" => "image/ief", + ".ifb" => "text/calendar", + ".ifm" => "application/vnd.shana.informed.formdata", + ".igl" => "application/vnd.igloader", + ".igs" => "model/iges", + ".igx" => "application/vnd.micrografx.igx", + ".iif" => "application/vnd.shana.informed.interchange", + ".imp" => "application/vnd.accpac.simply.imp", + ".ims" => "application/vnd.ms-ims", + ".ipk" => "application/vnd.shana.informed.package", + ".irm" => "application/vnd.ibm.rights-management", + ".irp" => "application/vnd.irepository.package+xml", + ".iso" => "application/octet-stream", + ".itp" => "application/vnd.shana.informed.formtemplate", + ".ivp" => "application/vnd.immervision-ivp", + ".ivu" => "application/vnd.immervision-ivu", + ".jad" => "text/vnd.sun.j2me.app-descriptor", + ".jam" => "application/vnd.jam", + ".jar" => "application/java-archive", + ".java" => "text/x-java-source", + ".jisp" => "application/vnd.jisp", + ".jlt" => "application/vnd.hp-jlyt", + ".jnlp" => "application/x-java-jnlp-file", + ".joda" => "application/vnd.joost.joda-archive", + ".jp2" => "image/jp2", + ".jpeg" => "image/jpeg", + ".jpg" => "image/jpeg", + ".jpgv" => "video/jpeg", + ".jpm" => "video/jpm", + ".js" => "application/javascript", + ".json" => "application/json", + ".karbon" => "application/vnd.kde.karbon", + ".kfo" => "application/vnd.kde.kformula", + ".kia" => "application/vnd.kidspiration", + ".kml" => "application/vnd.google-earth.kml+xml", + ".kmz" => "application/vnd.google-earth.kmz", + ".kne" => "application/vnd.kinar", + ".kon" => "application/vnd.kde.kontour", + ".kpr" => "application/vnd.kde.kpresenter", + ".ksp" => "application/vnd.kde.kspread", + ".ktz" => "application/vnd.kahootz", + ".kwd" => "application/vnd.kde.kword", + ".latex" => "application/x-latex", + ".lbd" => "application/vnd.llamagraphics.life-balance.desktop", + ".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml", + ".les" => "application/vnd.hhe.lesson-player", + ".link66" => "application/vnd.route66.link66+xml", + ".log" => "text/plain", + ".lostxml" => "application/lost+xml", + ".lrm" => "application/vnd.ms-lrm", + ".ltf" => "application/vnd.frogans.ltf", + ".lvp" => "audio/vnd.lucent.voice", + ".lwp" => "application/vnd.lotus-wordpro", + ".m3u" => "audio/x-mpegurl", + ".m3u8" => "application/x-mpegurl", + ".m4a" => "audio/mp4a-latm", + ".m4v" => "video/mp4", + ".ma" => "application/mathematica", + ".mag" => "application/vnd.ecowin.chart", + ".man" => "text/troff", + ".manifest" => "text/cache-manifest", + ".mathml" => "application/mathml+xml", + ".mbk" => "application/vnd.mobius.mbk", + ".mbox" => "application/mbox", + ".mc1" => "application/vnd.medcalcdata", + ".mcd" => "application/vnd.mcd", + ".mdb" => "application/x-msaccess", + ".mdi" => "image/vnd.ms-modi", + ".mdoc" => "text/troff", + ".me" => "text/troff", + ".mfm" => "application/vnd.mfmp", + ".mgz" => "application/vnd.proteus.magazine", + ".mid" => "audio/midi", + ".midi" => "audio/midi", + ".mif" => "application/vnd.mif", + ".mime" => "message/rfc822", + ".mj2" => "video/mj2", + ".mlp" => "application/vnd.dolby.mlp", + ".mmd" => "application/vnd.chipnuts.karaoke-mmd", + ".mmf" => "application/vnd.smaf", + ".mml" => "application/mathml+xml", + ".mmr" => "image/vnd.fujixerox.edmics-mmr", + ".mng" => "video/x-mng", + ".mny" => "application/x-msmoney", + ".mov" => "video/quicktime", + ".movie" => "video/x-sgi-movie", + ".mp3" => "audio/mpeg", + ".mp4" => "video/mp4", + ".mp4a" => "audio/mp4", + ".mp4s" => "application/mp4", + ".mp4v" => "video/mp4", + ".mpc" => "application/vnd.mophun.certificate", + ".mpd" => "application/dash+xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mpga" => "audio/mpeg", + ".mpkg" => "application/vnd.apple.installer+xml", + ".mpm" => "application/vnd.blueice.multipass", + ".mpn" => "application/vnd.mophun.application", + ".mpp" => "application/vnd.ms-project", + ".mpy" => "application/vnd.ibm.minipay", + ".mqy" => "application/vnd.mobius.mqy", + ".mrc" => "application/marc", + ".ms" => "text/troff", + ".mscml" => "application/mediaservercontrol+xml", + ".mseq" => "application/vnd.mseq", + ".msf" => "application/vnd.epson.msf", + ".msh" => "model/mesh", + ".msi" => "application/x-msdownload", + ".msl" => "application/vnd.mobius.msl", + ".msty" => "application/vnd.muvee.style", + ".mts" => "model/vnd.mts", + ".mus" => "application/vnd.musician", + ".mvb" => "application/x-msmediaview", + ".mwf" => "application/vnd.mfer", + ".mxf" => "application/mxf", + ".mxl" => "application/vnd.recordare.musicxml", + ".mxml" => "application/xv+xml", + ".mxs" => "application/vnd.triscape.mxs", + ".mxu" => "video/vnd.mpegurl", + ".n" => "application/vnd.nokia.n-gage.symbian.install", + ".nc" => "application/x-netcdf", + ".ngdat" => "application/vnd.nokia.n-gage.data", + ".nlu" => "application/vnd.neurolanguage.nlu", + ".nml" => "application/vnd.enliven", + ".nnd" => "application/vnd.noblenet-directory", + ".nns" => "application/vnd.noblenet-sealer", + ".nnw" => "application/vnd.noblenet-web", + ".npx" => "image/vnd.net-fpx", + ".nsf" => "application/vnd.lotus-notes", + ".oa2" => "application/vnd.fujitsu.oasys2", + ".oa3" => "application/vnd.fujitsu.oasys3", + ".oas" => "application/vnd.fujitsu.oasys", + ".obd" => "application/x-msbinder", + ".oda" => "application/oda", + ".odc" => "application/vnd.oasis.opendocument.chart", + ".odf" => "application/vnd.oasis.opendocument.formula", + ".odg" => "application/vnd.oasis.opendocument.graphics", + ".odi" => "application/vnd.oasis.opendocument.image", + ".odp" => "application/vnd.oasis.opendocument.presentation", + ".ods" => "application/vnd.oasis.opendocument.spreadsheet", + ".odt" => "application/vnd.oasis.opendocument.text", + ".oga" => "audio/ogg", + ".ogg" => "application/ogg", + ".ogv" => "video/ogg", + ".ogx" => "application/ogg", + ".org" => "application/vnd.lotus-organizer", + ".otc" => "application/vnd.oasis.opendocument.chart-template", + ".otf" => "application/vnd.oasis.opendocument.formula-template", + ".otg" => "application/vnd.oasis.opendocument.graphics-template", + ".oth" => "application/vnd.oasis.opendocument.text-web", + ".oti" => "application/vnd.oasis.opendocument.image-template", + ".otm" => "application/vnd.oasis.opendocument.text-master", + ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template", + ".ott" => "application/vnd.oasis.opendocument.text-template", + ".oxt" => "application/vnd.openofficeorg.extension", + ".p" => "text/x-pascal", + ".p10" => "application/pkcs10", + ".p12" => "application/x-pkcs12", + ".p7b" => "application/x-pkcs7-certificates", + ".p7m" => "application/pkcs7-mime", + ".p7r" => "application/x-pkcs7-certreqresp", + ".p7s" => "application/pkcs7-signature", + ".pas" => "text/x-pascal", + ".pbd" => "application/vnd.powerbuilder6", + ".pbm" => "image/x-portable-bitmap", + ".pcl" => "application/vnd.hp-pcl", + ".pclxl" => "application/vnd.hp-pclxl", + ".pcx" => "image/x-pcx", + ".pdb" => "chemical/x-pdb", + ".pdf" => "application/pdf", + ".pem" => "application/x-x509-ca-cert", + ".pfr" => "application/font-tdpfr", + ".pgm" => "image/x-portable-graymap", + ".pgn" => "application/x-chess-pgn", + ".pgp" => "application/pgp-encrypted", + ".pic" => "image/x-pict", + ".pict" => "image/pict", + ".pkg" => "application/octet-stream", + ".pki" => "application/pkixcmp", + ".pkipath" => "application/pkix-pkipath", + ".pl" => "text/x-script.perl", + ".plb" => "application/vnd.3gpp.pic-bw-large", + ".plc" => "application/vnd.mobius.plc", + ".plf" => "application/vnd.pocketlearn", + ".pls" => "application/pls+xml", + ".pm" => "text/x-script.perl-module", + ".pml" => "application/vnd.ctc-posml", + ".png" => "image/png", + ".pnm" => "image/x-portable-anymap", + ".pntg" => "image/x-macpaint", + ".portpkg" => "application/vnd.macports.portpkg", + ".pot" => "application/vnd.ms-powerpoint", + ".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12", + ".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template", + ".ppa" => "application/vnd.ms-powerpoint", + ".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12", + ".ppd" => "application/vnd.cups-ppd", + ".ppm" => "image/x-portable-pixmap", + ".pps" => "application/vnd.ms-powerpoint", + ".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", + ".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + ".ppt" => "application/vnd.ms-powerpoint", + ".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".prc" => "application/vnd.palm", + ".pre" => "application/vnd.lotus-freelance", + ".prf" => "application/pics-rules", + ".ps" => "application/postscript", + ".psb" => "application/vnd.3gpp.pic-bw-small", + ".psd" => "image/vnd.adobe.photoshop", + ".ptid" => "application/vnd.pvi.ptid1", + ".pub" => "application/x-mspublisher", + ".pvb" => "application/vnd.3gpp.pic-bw-var", + ".pwn" => "application/vnd.3m.post-it-notes", + ".py" => "text/x-script.python", + ".pya" => "audio/vnd.ms-playready.media.pya", + ".pyv" => "video/vnd.ms-playready.media.pyv", + ".qam" => "application/vnd.epson.quickanime", + ".qbo" => "application/vnd.intu.qbo", + ".qfx" => "application/vnd.intu.qfx", + ".qps" => "application/vnd.publishare-delta-tree", + ".qt" => "video/quicktime", + ".qtif" => "image/x-quicktime", + ".qxd" => "application/vnd.quark.quarkxpress", + ".ra" => "audio/x-pn-realaudio", + ".rake" => "text/x-script.ruby", + ".ram" => "audio/x-pn-realaudio", + ".rar" => "application/x-rar-compressed", + ".ras" => "image/x-cmu-raster", + ".rb" => "text/x-script.ruby", + ".rcprofile" => "application/vnd.ipunplugged.rcprofile", + ".rdf" => "application/rdf+xml", + ".rdz" => "application/vnd.data-vision.rdz", + ".rep" => "application/vnd.businessobjects", + ".rgb" => "image/x-rgb", + ".rif" => "application/reginfo+xml", + ".rl" => "application/resource-lists+xml", + ".rlc" => "image/vnd.fujixerox.edmics-rlc", + ".rld" => "application/resource-lists-diff+xml", + ".rm" => "application/vnd.rn-realmedia", + ".rmp" => "audio/x-pn-realaudio-plugin", + ".rms" => "application/vnd.jcp.javame.midlet-rms", + ".rnc" => "application/relax-ng-compact-syntax", + ".roff" => "text/troff", + ".rpm" => "application/x-redhat-package-manager", + ".rpss" => "application/vnd.nokia.radio-presets", + ".rpst" => "application/vnd.nokia.radio-preset", + ".rq" => "application/sparql-query", + ".rs" => "application/rls-services+xml", + ".rsd" => "application/rsd+xml", + ".rss" => "application/rss+xml", + ".rtf" => "application/rtf", + ".rtx" => "text/richtext", + ".ru" => "text/x-script.ruby", + ".s" => "text/x-asm", + ".saf" => "application/vnd.yamaha.smaf-audio", + ".sbml" => "application/sbml+xml", + ".sc" => "application/vnd.ibm.secure-container", + ".scd" => "application/x-msschedule", + ".scm" => "application/vnd.lotus-screencam", + ".scq" => "application/scvp-cv-request", + ".scs" => "application/scvp-cv-response", + ".sdkm" => "application/vnd.solent.sdkm+xml", + ".sdp" => "application/sdp", + ".see" => "application/vnd.seemail", + ".sema" => "application/vnd.sema", + ".semd" => "application/vnd.semd", + ".semf" => "application/vnd.semf", + ".setpay" => "application/set-payment-initiation", + ".setreg" => "application/set-registration-initiation", + ".sfd" => "application/vnd.hydrostatix.sof-data", + ".sfs" => "application/vnd.spotfire.sfs", + ".sgm" => "text/sgml", + ".sgml" => "text/sgml", + ".sh" => "application/x-sh", + ".shar" => "application/x-shar", + ".shf" => "application/shf+xml", + ".sig" => "application/pgp-signature", + ".sit" => "application/x-stuffit", + ".sitx" => "application/x-stuffitx", + ".skp" => "application/vnd.koan", + ".slt" => "application/vnd.epson.salt", + ".smi" => "application/smil+xml", + ".snd" => "audio/basic", + ".so" => "application/octet-stream", + ".spf" => "application/vnd.yamaha.smaf-phrase", + ".spl" => "application/x-futuresplash", + ".spot" => "text/vnd.in3d.spot", + ".spp" => "application/scvp-vp-response", + ".spq" => "application/scvp-vp-request", + ".src" => "application/x-wais-source", + ".srt" => "text/srt", + ".srx" => "application/sparql-results+xml", + ".sse" => "application/vnd.kodak-descriptor", + ".ssf" => "application/vnd.epson.ssf", + ".ssml" => "application/ssml+xml", + ".stf" => "application/vnd.wt.stf", + ".stk" => "application/hyperstudio", + ".str" => "application/vnd.pg.format", + ".sus" => "application/vnd.sus-calendar", + ".sv4cpio" => "application/x-sv4cpio", + ".sv4crc" => "application/x-sv4crc", + ".svd" => "application/vnd.svd", + ".svg" => "image/svg+xml", + ".svgz" => "image/svg+xml", + ".swf" => "application/x-shockwave-flash", + ".swi" => "application/vnd.arastra.swi", + ".t" => "text/troff", + ".tao" => "application/vnd.tao.intent-module-archive", + ".tar" => "application/x-tar", + ".tbz" => "application/x-bzip-compressed-tar", + ".tcap" => "application/vnd.3gpp2.tcap", + ".tcl" => "application/x-tcl", + ".tex" => "application/x-tex", + ".texi" => "application/x-texinfo", + ".texinfo" => "application/x-texinfo", + ".text" => "text/plain", + ".tif" => "image/tiff", + ".tiff" => "image/tiff", + ".tmo" => "application/vnd.tmobile-livetv", + ".torrent" => "application/x-bittorrent", + ".tpl" => "application/vnd.groove-tool-template", + ".tpt" => "application/vnd.trid.tpt", + ".tr" => "text/troff", + ".tra" => "application/vnd.trueapp", + ".trm" => "application/x-msterminal", + ".ts" => "video/mp2t", + ".tsv" => "text/tab-separated-values", + ".ttf" => "application/octet-stream", + ".twd" => "application/vnd.simtech-mindmapper", + ".txd" => "application/vnd.genomatix.tuxedo", + ".txf" => "application/vnd.mobius.txf", + ".txt" => "text/plain", + ".ufd" => "application/vnd.ufdl", + ".umj" => "application/vnd.umajin", + ".unityweb" => "application/vnd.unity", + ".uoml" => "application/vnd.uoml+xml", + ".uri" => "text/uri-list", + ".ustar" => "application/x-ustar", + ".utz" => "application/vnd.uiq.theme", + ".uu" => "text/x-uuencode", + ".vcd" => "application/x-cdlink", + ".vcf" => "text/x-vcard", + ".vcg" => "application/vnd.groove-vcard", + ".vcs" => "text/x-vcalendar", + ".vcx" => "application/vnd.vcx", + ".vis" => "application/vnd.visionary", + ".viv" => "video/vnd.vivo", + ".vrml" => "model/vrml", + ".vsd" => "application/vnd.visio", + ".vsf" => "application/vnd.vsf", + ".vtt" => "text/vtt", + ".vtu" => "model/vnd.vtu", + ".vxml" => "application/voicexml+xml", + ".war" => "application/java-archive", + ".wasm" => "application/wasm", + ".wav" => "audio/x-wav", + ".wax" => "audio/x-ms-wax", + ".wbmp" => "image/vnd.wap.wbmp", + ".wbs" => "application/vnd.criticaltools.wbs+xml", + ".wbxml" => "application/vnd.wap.wbxml", + ".webm" => "video/webm", + ".wm" => "video/x-ms-wm", + ".wma" => "audio/x-ms-wma", + ".wmd" => "application/x-ms-wmd", + ".wmf" => "application/x-msmetafile", + ".wml" => "text/vnd.wap.wml", + ".wmlc" => "application/vnd.wap.wmlc", + ".wmls" => "text/vnd.wap.wmlscript", + ".wmlsc" => "application/vnd.wap.wmlscriptc", + ".wmv" => "video/x-ms-wmv", + ".wmx" => "video/x-ms-wmx", + ".wmz" => "application/x-ms-wmz", + ".woff" => "application/font-woff", + ".woff2" => "application/font-woff2", + ".wpd" => "application/vnd.wordperfect", + ".wpl" => "application/vnd.ms-wpl", + ".wps" => "application/vnd.ms-works", + ".wqd" => "application/vnd.wqd", + ".wri" => "application/x-mswrite", + ".wrl" => "model/vrml", + ".wsdl" => "application/wsdl+xml", + ".wspolicy" => "application/wspolicy+xml", + ".wtb" => "application/vnd.webturbo", + ".wvx" => "video/x-ms-wvx", + ".x3d" => "application/vnd.hzn-3d-crossword", + ".xar" => "application/vnd.xara", + ".xbd" => "application/vnd.fujixerox.docuworks.binder", + ".xbm" => "image/x-xbitmap", + ".xdm" => "application/vnd.syncml.dm+xml", + ".xdp" => "application/vnd.adobe.xdp+xml", + ".xdw" => "application/vnd.fujixerox.docuworks", + ".xenc" => "application/xenc+xml", + ".xer" => "application/patch-ops-error+xml", + ".xfdf" => "application/vnd.adobe.xfdf", + ".xfdl" => "application/vnd.xfdl", + ".xhtml" => "application/xhtml+xml", + ".xif" => "image/vnd.xiff", + ".xla" => "application/vnd.ms-excel", + ".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12", + ".xls" => "application/vnd.ms-excel", + ".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12", + ".xlt" => "application/vnd.ms-excel", + ".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + ".xml" => "application/xml", + ".xo" => "application/vnd.olpc-sugar", + ".xop" => "application/xop+xml", + ".xpm" => "image/x-xpixmap", + ".xpr" => "application/vnd.is-xpr", + ".xps" => "application/vnd.ms-xpsdocument", + ".xpw" => "application/vnd.intercon.formnet", + ".xsl" => "application/xml", + ".xslt" => "application/xslt+xml", + ".xsm" => "application/vnd.syncml+xml", + ".xspf" => "application/xspf+xml", + ".xul" => "application/vnd.mozilla.xul+xml", + ".xwd" => "image/x-xwindowdump", + ".xyz" => "chemical/x-xyz", + ".yaml" => "text/yaml", + ".yml" => "text/yaml", + ".zaz" => "application/vnd.zzazz.deck+xml", + ".zip" => "application/zip", + ".zmm" => "application/vnd.handheld-entertainment+xml", + } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mock.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mock.rb new file mode 100644 index 0000000000..5b2512ca09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/mock.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +require 'uri' +require 'stringio' +require_relative '../rack' +require 'cgi/cookie' + +module Rack + # Rack::MockRequest helps testing your Rack application without + # actually using HTTP. + # + # After performing a request on a URL with get/post/put/patch/delete, it + # returns a MockResponse with useful helper methods for effective + # testing. + # + # You can pass a hash with additional configuration to the + # get/post/put/patch/delete. + # :input:: A String or IO-like to be used as rack.input. + # :fatal:: Raise a FatalWarning if the app writes to rack.errors. + # :lint:: If true, wrap the application in a Rack::Lint. + + class MockRequest + class FatalWarning < RuntimeError + end + + class FatalWarner + def puts(warning) + raise FatalWarning, warning + end + + def write(warning) + raise FatalWarning, warning + end + + def flush + end + + def string + "" + end + end + + DEFAULT_ENV = { + RACK_VERSION => Rack::VERSION, + RACK_INPUT => StringIO.new, + RACK_ERRORS => StringIO.new, + RACK_MULTITHREAD => true, + RACK_MULTIPROCESS => true, + RACK_RUNONCE => false, + }.freeze + + def initialize(app) + @app = app + end + + # Make a GET request and return a MockResponse. See #request. + def get(uri, opts = {}) request(GET, uri, opts) end + # Make a POST request and return a MockResponse. See #request. + def post(uri, opts = {}) request(POST, uri, opts) end + # Make a PUT request and return a MockResponse. See #request. + def put(uri, opts = {}) request(PUT, uri, opts) end + # Make a PATCH request and return a MockResponse. See #request. + def patch(uri, opts = {}) request(PATCH, uri, opts) end + # Make a DELETE request and return a MockResponse. See #request. + def delete(uri, opts = {}) request(DELETE, uri, opts) end + # Make a HEAD request and return a MockResponse. See #request. + def head(uri, opts = {}) request(HEAD, uri, opts) end + # Make an OPTIONS request and return a MockResponse. See #request. + def options(uri, opts = {}) request(OPTIONS, uri, opts) end + + # Make a request using the given request method for the given + # uri to the rack application and return a MockResponse. + # Options given are passed to MockRequest.env_for. + def request(method = GET, uri = "", opts = {}) + env = self.class.env_for(uri, opts.merge(method: method)) + + if opts[:lint] + app = Rack::Lint.new(@app) + else + app = @app + end + + errors = env[RACK_ERRORS] + status, headers, body = app.call(env) + MockResponse.new(status, headers, body, errors) + ensure + body.close if body.respond_to?(:close) + end + + # For historical reasons, we're pinning to RFC 2396. + # URI::Parser = URI::RFC2396_Parser + def self.parse_uri_rfc2396(uri) + @parser ||= URI::Parser.new + @parser.parse(uri) + end + + # Return the Rack environment used for a request to +uri+. + # All options that are strings are added to the returned environment. + # Options: + # :fatal :: Whether to raise an exception if request outputs to rack.errors + # :input :: The rack.input to set + # :method :: The HTTP request method to use + # :params :: The params to use + # :script_name :: The SCRIPT_NAME to set + def self.env_for(uri = "", opts = {}) + uri = parse_uri_rfc2396(uri) + uri.path = "/#{uri.path}" unless uri.path[0] == ?/ + + env = DEFAULT_ENV.dup + + env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b + env[SERVER_NAME] = (uri.host || "example.org").b + env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b + env[QUERY_STRING] = (uri.query.to_s).b + env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b + env[RACK_URL_SCHEME] = (uri.scheme || "http").b + env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b + + env[SCRIPT_NAME] = opts[:script_name] || "" + + if opts[:fatal] + env[RACK_ERRORS] = FatalWarner.new + else + env[RACK_ERRORS] = StringIO.new + end + + if params = opts[:params] + if env[REQUEST_METHOD] == GET + params = Utils.parse_nested_query(params) if params.is_a?(String) + params.update(Utils.parse_nested_query(env[QUERY_STRING])) + env[QUERY_STRING] = Utils.build_nested_query(params) + elsif !opts.has_key?(:input) + opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded" + if params.is_a?(Hash) + if data = Rack::Multipart.build_multipart(params) + opts[:input] = data + opts["CONTENT_LENGTH"] ||= data.length.to_s + opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}" + else + opts[:input] = Utils.build_nested_query(params) + end + else + opts[:input] = params + end + end + end + + empty_str = String.new + opts[:input] ||= empty_str + if String === opts[:input] + rack_input = StringIO.new(opts[:input]) + else + rack_input = opts[:input] + end + + rack_input.set_encoding(Encoding::BINARY) + env[RACK_INPUT] = rack_input + + env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size) + + opts.each { |field, value| + env[field] = value if String === field + } + + env + end + end + + # Rack::MockResponse provides useful helpers for testing your apps. + # Usually, you don't create the MockResponse on your own, but use + # MockRequest. + + class MockResponse < Rack::Response + class << self + alias [] new + end + + # Headers + attr_reader :original_headers, :cookies + + # Errors + attr_accessor :errors + + def initialize(status, headers, body, errors = StringIO.new("")) + @original_headers = headers + @errors = errors.string if errors.respond_to?(:string) + @cookies = parse_cookies_from_header + + super(body, status, headers) + + buffered_body! + end + + def =~(other) + body =~ other + end + + def match(other) + body.match other + end + + def body + # FIXME: apparently users of MockResponse expect the return value of + # MockResponse#body to be a string. However, the real response object + # returns the body as a list. + # + # See spec_showstatus.rb: + # + # should "not replace existing messages" do + # ... + # res.body.should == "foo!" + # end + buffer = String.new + + super.each do |chunk| + buffer << chunk + end + + return buffer + end + + def empty? + [201, 204, 304].include? status + end + + def cookie(name) + cookies.fetch(name, nil) + end + + private + + def parse_cookies_from_header + cookies = Hash.new + if original_headers.has_key? 'Set-Cookie' + set_cookie_header = original_headers.fetch('Set-Cookie') + set_cookie_header.split("\n").each do |cookie| + cookie_name, cookie_filling = cookie.split('=', 2) + cookie_attributes = identify_cookie_attributes cookie_filling + parsed_cookie = CGI::Cookie.new( + 'name' => cookie_name.strip, + 'value' => cookie_attributes.fetch('value'), + 'path' => cookie_attributes.fetch('path', nil), + 'domain' => cookie_attributes.fetch('domain', nil), + 'expires' => cookie_attributes.fetch('expires', nil), + 'secure' => cookie_attributes.fetch('secure', false) + ) + cookies.store(cookie_name, parsed_cookie) + end + end + cookies + end + + def identify_cookie_attributes(cookie_filling) + cookie_bits = cookie_filling.split(';') + cookie_attributes = Hash.new + cookie_attributes.store('value', cookie_bits[0].strip) + cookie_bits.each do |bit| + if bit.include? '=' + cookie_attribute, attribute_value = bit.split('=') + cookie_attributes.store(cookie_attribute.strip, attribute_value.strip) + if cookie_attribute.include? 'max-age' + cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i) + end + end + if bit.include? 'secure' + cookie_attributes.store('secure', true) + end + end + cookie_attributes + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart.rb new file mode 100644 index 0000000000..fdae808a83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_relative 'multipart/parser' + +module Rack + # A multipart form data parser, adapted from IOWA. + # + # Usually, Rack::Request#POST takes care of calling this. + module Multipart + autoload :UploadedFile, 'rack/multipart/uploaded_file' + autoload :Generator, 'rack/multipart/generator' + + EOL = "\r\n" + MULTIPART_BOUNDARY = "AaB03x" + MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni + TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/ + CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i + VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/ + BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i + MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni + MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni + MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni + # Updated definitions from RFC 2231 + ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]} + ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/ + SECTION = /\*[0-9]+/ + REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/ + REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/ + EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/ + EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/ + EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/ + EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/ + EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/ + EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/ + EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/ + DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/ + RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i + + class << self + def parse_multipart(env, params = Rack::Utils.default_query_parser) + extract_multipart Rack::Request.new(env), params + end + + def extract_multipart(req, params = Rack::Utils.default_query_parser) + io = req.get_header(RACK_INPUT) + io.rewind + content_length = req.content_length + content_length = content_length.to_i if content_length + + tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY + bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE + + info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params + req.set_header(RACK_TEMPFILES, info.tmp_files) + info.params + end + + def build_multipart(params, first = true) + Generator.new(params, first).dump + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/generator.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/generator.rb new file mode 100644 index 0000000000..f798a98c51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/generator.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +module Rack + module Multipart + class Generator + def initialize(params, first = true) + @params, @first = params, first + + if @first && !@params.is_a?(Hash) + raise ArgumentError, "value must be a Hash" + end + end + + def dump + return nil if @first && !multipart? + return flattened_params unless @first + + flattened_params.map do |name, file| + if file.respond_to?(:original_filename) + if file.path + ::File.open(file.path, 'rb') do |f| + f.set_encoding(Encoding::BINARY) + content_for_tempfile(f, file, name) + end + else + content_for_tempfile(file, file, name) + end + else + content_for_other(file, name) + end + end.join << "--#{MULTIPART_BOUNDARY}--\r" + end + + private + def multipart? + query = lambda { |value| + case value + when Array + value.any?(&query) + when Hash + value.values.any?(&query) + when Rack::Multipart::UploadedFile + true + end + } + + @params.values.any?(&query) + end + + def flattened_params + @flattened_params ||= begin + h = Hash.new + @params.each do |key, value| + k = @first ? key.to_s : "[#{key}]" + + case value + when Array + value.map { |v| + Multipart.build_multipart(v, false).each { |subkey, subvalue| + h["#{k}[]#{subkey}"] = subvalue + } + } + when Hash + Multipart.build_multipart(value, false).each { |subkey, subvalue| + h[k + subkey] = subvalue + } + else + h[k] = value + end + end + h + end + end + + def content_for_tempfile(io, file, name) + length = ::File.stat(file.path).size if file.path + filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename +<<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{name}"#{filename}\r +Content-Type: #{file.content_type}\r +#{"Content-Length: #{length}\r\n" if length}\r +#{io.read}\r +EOF + end + + def content_for_other(file, name) +<<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{name}"\r +\r +#{file}\r +EOF + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/parser.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/parser.rb new file mode 100644 index 0000000000..0fc1856031 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/parser.rb @@ -0,0 +1,376 @@ +# frozen_string_literal: true + +require 'strscan' + +module Rack + module Multipart + class MultipartPartLimitError < Errno::EMFILE; end + class MultipartTotalPartLimitError < StandardError; end + + class Parser + (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + BUFSIZE = 1_048_576 + TEXT_PLAIN = "text/plain" + TEMPFILE_FACTORY = lambda { |filename, content_type| + Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))]) + } + + BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/ + + class BoundedIO # :nodoc: + def initialize(io, content_length) + @io = io + @content_length = content_length + @cursor = 0 + end + + def read(size, outbuf = nil) + return if @cursor >= @content_length + + left = @content_length - @cursor + + str = if left < size + @io.read left, outbuf + else + @io.read size, outbuf + end + + if str + @cursor += str.bytesize + else + # Raise an error for mismatching Content-Length and actual contents + raise EOFError, "bad content body" + end + + str + end + + def rewind + @io.rewind + end + end + + MultipartInfo = Struct.new :params, :tmp_files + EMPTY = MultipartInfo.new(nil, []) + + def self.parse_boundary(content_type) + return unless content_type + data = content_type.match(MULTIPART) + return unless data + data[1] + end + + def self.parse(io, content_length, content_type, tmpfile, bufsize, qp) + return EMPTY if 0 == content_length + + boundary = parse_boundary content_type + return EMPTY unless boundary + + io = BoundedIO.new(io, content_length) if content_length + outbuf = String.new + + parser = new(boundary, tmpfile, bufsize, qp) + parser.on_read io.read(bufsize, outbuf) + + loop do + break if parser.state == :DONE + parser.on_read io.read(bufsize, outbuf) + end + + io.rewind + parser.result + end + + class Collector + class MimePart < Struct.new(:body, :head, :filename, :content_type, :name) + def get_data + data = body + if filename == "" + # filename is blank which means no file has been selected + return + elsif filename + body.rewind if body.respond_to?(:rewind) + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + fn = filename.split(/[\/\\]/).last + + data = { filename: fn, type: content_type, + name: name, tempfile: body, head: head } + end + + yield data + end + end + + class BufferPart < MimePart + def file?; false; end + def close; end + end + + class TempfilePart < MimePart + def file?; true; end + def close; body.close; end + end + + include Enumerable + + def initialize(tempfile) + @tempfile = tempfile + @mime_parts = [] + @open_files = 0 + end + + def each + @mime_parts.each { |part| yield part } + end + + def on_mime_head(mime_index, head, filename, content_type, name) + if filename + body = @tempfile.call(filename, content_type) + body.binmode if body.respond_to?(:binmode) + klass = TempfilePart + @open_files += 1 + else + body = String.new + klass = BufferPart + end + + @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name) + + check_part_limits + end + + def on_mime_body(mime_index, content) + @mime_parts[mime_index].body << content + end + + def on_mime_finish(mime_index) + end + + private + + def check_part_limits + file_limit = Utils.multipart_file_limit + part_limit = Utils.multipart_total_part_limit + + if file_limit && file_limit > 0 + if @open_files >= file_limit + @mime_parts.each(&:close) + raise MultipartPartLimitError, 'Maximum file multiparts in content reached' + end + end + + if part_limit && part_limit > 0 + if @mime_parts.size >= part_limit + @mime_parts.each(&:close) + raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached' + end + end + end + end + + attr_reader :state + + def initialize(boundary, tempfile, bufsize, query_parser) + @query_parser = query_parser + @params = query_parser.make_params + @boundary = "--#{boundary}" + @bufsize = bufsize + + @full_boundary = @boundary + @end_boundary = @boundary + '--' + @state = :FAST_FORWARD + @mime_index = 0 + @collector = Collector.new tempfile + + @sbuf = StringScanner.new("".dup) + @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m + @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max + @head_regex = /(.*?#{EOL})#{EOL}/m + end + + def on_read(content) + handle_empty_content!(content) + @sbuf.concat content + run_parser + end + + def result + @collector.each do |part| + part.get_data do |data| + tag_multipart_encoding(part.filename, part.content_type, part.name, data) + @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit) + end + end + MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body) + end + + private + + def run_parser + loop do + case @state + when :FAST_FORWARD + break if handle_fast_forward == :want_read + when :CONSUME_TOKEN + break if handle_consume_token == :want_read + when :MIME_HEAD + break if handle_mime_head == :want_read + when :MIME_BODY + break if handle_mime_body == :want_read + when :DONE + break + end + end + end + + def handle_fast_forward + if consume_boundary + @state = :MIME_HEAD + else + raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize + :want_read + end + end + + def handle_consume_token + tok = consume_boundary + # break if we're at the end of a buffer, but not if it is the end of a field + @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY) + :DONE + else + :MIME_HEAD + end + end + + def handle_mime_head + if @sbuf.scan_until(@head_regex) + head = @sbuf[1] + content_type = head[MULTIPART_CONTENT_TYPE, 1] + if name = head[MULTIPART_CONTENT_DISPOSITION, 1] + name = Rack::Auth::Digest::Params::dequote(name) + else + name = head[MULTIPART_CONTENT_ID, 1] + end + + filename = get_filename(head) + + if name.nil? || name.empty? + name = filename || "#{content_type || TEXT_PLAIN}[]".dup + end + + @collector.on_mime_head @mime_index, head, filename, content_type, name + @state = :MIME_BODY + else + :want_read + end + end + + def handle_mime_body + if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet + body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string + @collector.on_mime_body @mime_index, body + @sbuf.pos += body.length + 2 # skip \r\n after the content + @state = :CONSUME_TOKEN + @mime_index += 1 + else + # Save what we have so far + if @rx_max_size < @sbuf.rest_size + delta = @sbuf.rest_size - @rx_max_size + @collector.on_mime_body @mime_index, @sbuf.peek(delta) + @sbuf.pos += delta + @sbuf.string = @sbuf.rest + end + :want_read + end + end + + def full_boundary; @full_boundary; end + + def consume_boundary + while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX) + case read_buffer.strip + when full_boundary then return :BOUNDARY + when @end_boundary then return :END_BOUNDARY + end + return if @sbuf.eos? + end + end + + def get_filename(head) + filename = nil + case head + when RFC2183 + params = Hash[*head.scan(DISPPARM).flat_map(&:compact)] + + if filename = params['filename'] + filename = $1 if filename =~ /^"(.*)"$/ + elsif filename = params['filename*'] + encoding, _, filename = filename.split("'", 3) + end + when BROKEN + filename = $1 + filename = $1 if filename =~ /^"(.*)"$/ + end + + return unless filename + + if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) } + filename = Utils.unescape_path(filename) + end + + filename.scrub! + + if filename !~ /\\[^\\"]/ + filename = filename.gsub(/\\(.)/, '\1') + end + + if encoding + filename.force_encoding ::Encoding.find(encoding) + end + + filename + end + + CHARSET = "charset" + + def tag_multipart_encoding(filename, content_type, name, body) + name = name.to_s + encoding = Encoding::UTF_8 + + name.force_encoding(encoding) + + return if filename + + if content_type + list = content_type.split(';') + type_subtype = list.first + type_subtype.strip! + if TEXT_PLAIN == type_subtype + rest = list.drop 1 + rest.each do |param| + k, v = param.split('=', 2) + k.strip! + v.strip! + v = v[1..-2] if v.start_with?('"') && v.end_with?('"') + encoding = Encoding.find v if k == CHARSET + end + end + end + + name.force_encoding(encoding) + body.force_encoding(encoding) + end + + def handle_empty_content!(content) + if content.nil? || content.empty? + raise EOFError + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/uploaded_file.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/uploaded_file.rb new file mode 100644 index 0000000000..9eaf691277 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/multipart/uploaded_file.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Rack + module Multipart + class UploadedFile + # The filename, *not* including the path, of the "uploaded" file + attr_reader :original_filename + + # The content type of the "uploaded" file + attr_accessor :content_type + + def initialize(filepath = nil, ct = "text/plain", bin = false, + path: filepath, content_type: ct, binary: bin, filename: nil, io: nil) + if io + @tempfile = io + @original_filename = filename + else + raise "#{path} file does not exist" unless ::File.exist?(path) + @original_filename = filename || ::File.basename(path) + @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY) + @tempfile.binmode if binary + FileUtils.copy_file(path, @tempfile.path) + end + @content_type = content_type + end + + def path + @tempfile.path if @tempfile.respond_to?(:path) + end + alias_method :local_path, :path + + def respond_to?(*args) + super or @tempfile.respond_to?(*args) + end + + def method_missing(method_name, *args, &block) #:nodoc: + @tempfile.__send__(method_name, *args, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/null_logger.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/null_logger.rb new file mode 100644 index 0000000000..3eff73d683 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/null_logger.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Rack + class NullLogger + def initialize(app) + @app = app + end + + def call(env) + env[RACK_LOGGER] = self + @app.call(env) + end + + def info(progname = nil, &block); end + def debug(progname = nil, &block); end + def warn(progname = nil, &block); end + def error(progname = nil, &block); end + def fatal(progname = nil, &block); end + def unknown(progname = nil, &block); end + def info? ; end + def debug? ; end + def warn? ; end + def error? ; end + def fatal? ; end + def level ; end + def progname ; end + def datetime_format ; end + def formatter ; end + def sev_threshold ; end + def level=(level); end + def progname=(progname); end + def datetime_format=(datetime_format); end + def formatter=(formatter); end + def sev_threshold=(sev_threshold); end + def close ; end + def add(severity, message = nil, progname = nil, &block); end + def <<(msg); end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/query_parser.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/query_parser.rb new file mode 100644 index 0000000000..1c3923c32f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/query_parser.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +module Rack + class QueryParser + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + DEFAULT_SEP = /[&;] */n + COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n } + + # ParameterTypeError is the error that is raised when incoming structural + # parameters (parsed by parse_nested_query) contain conflicting types. + class ParameterTypeError < TypeError; end + + # InvalidParameterError is the error that is raised when incoming structural + # parameters (parsed by parse_nested_query) contain invalid format or byte + # sequence. + class InvalidParameterError < ArgumentError; end + + # ParamsTooDeepError is the error that is raised when params are recursively + # nested over the specified limit. + class ParamsTooDeepError < RangeError; end + + def self.make_default(key_space_limit, param_depth_limit) + new Params, key_space_limit, param_depth_limit + end + + attr_reader :key_space_limit, :param_depth_limit + + def initialize(params_class, key_space_limit, param_depth_limit) + @params_class = params_class + @key_space_limit = key_space_limit + @param_depth_limit = param_depth_limit + end + + # Stolen from Mongrel, with some small modifications: + # Parses a query string by breaking it up at the '&' + # and ';' characters. You can also use this to parse + # cookies by changing the characters used in the second + # parameter (which defaults to '&;'). + def parse_query(qs, d = nil, &unescaper) + unescaper ||= method(:unescape) + + params = make_params + + (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + next if p.empty? + k, v = p.split('=', 2).map!(&unescaper) + + if cur = params[k] + if cur.class == Array + params[k] << v + else + params[k] = [cur, v] + end + else + params[k] = v + end + end + + return params.to_h + end + + # parse_nested_query expands a query string into structural types. Supported + # types are Arrays, Hashes and basic value types. It is possible to supply + # query strings with parameters of conflicting types, in this case a + # ParameterTypeError is raised. Users are encouraged to return a 400 in this + # case. + def parse_nested_query(qs, d = nil) + params = make_params + + unless qs.nil? || qs.empty? + (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map! { |s| unescape(s) } + + normalize_params(params, k, v, param_depth_limit) + end + end + + return params.to_h + rescue ArgumentError => e + raise InvalidParameterError, e.message, e.backtrace + end + + # normalize_params recursively expands parameters into structural types. If + # the structural types represented by two different parameter names are in + # conflict, a ParameterTypeError is raised. + def normalize_params(params, name, v, depth) + raise ParamsTooDeepError if depth <= 0 + + name =~ %r(\A[\[\]]*([^\[\]]+)\]*) + k = $1 || '' + after = $' || '' + + if k.empty? + if !v.nil? && name == "[]" + return Array(v) + else + return + end + end + + if after == '' + params[k] = v + 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 + elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) + child_key = $1 + 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) + normalize_params(params[k].last, child_key, v, depth - 1) + else + params[k] << normalize_params(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] = normalize_params(params[k], after, v, depth - 1) + end + + params + end + + def make_params + @params_class.new @key_space_limit + end + + def new_space_limit(key_space_limit) + self.class.new @params_class, key_space_limit, param_depth_limit + end + + def new_depth_limit(param_depth_limit) + self.class.new @params_class, key_space_limit, param_depth_limit + end + + private + + def params_hash_type?(obj) + obj.kind_of?(@params_class) + end + + def params_hash_has_key?(hash, key) + return false if /\[\]/.match?(key) + + 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 + + def unescape(s) + Utils.unescape(s) + end + + class Params + def initialize(limit) + @limit = limit + @size = 0 + @params = {} + end + + def [](key) + @params[key] + end + + def []=(key, value) + @size += key.size if key && !@params.key?(key) + raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit + @params[key] = value + end + + def key?(key) + @params.key?(key) + end + + # Recursively unwraps nested `Params` objects and constructs an object + # of the same shape, but using the objects' internal representations + # (Ruby hashes) in place of the objects. The result is a hash consisting + # purely of Ruby primitives. + # + # Mutation warning! + # + # 1. This method mutates the internal representation of the `Params` + # objects in order to save object allocations. + # + # 2. The value you get back is a reference to the internal hash + # representation, not a copy. + # + # 3. Because the `Params` object's internal representation is mutable + # through the `#[]=` method, it is not thread safe. The result of + # getting the hash representation while another thread is adding a + # key to it is non-deterministic. + # + def to_h + @params.each do |key, value| + case value + when self + # Handle circular references gracefully. + @params[key] = @params + when Params + @params[key] = value.to_h + when Array + value.map! { |v| v.kind_of?(Params) ? v.to_h : v } + else + # Ignore anything that is not a `Params` object or + # a collection that can contain one. + end + end + @params + end + alias_method :to_params_hash, :to_h + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/recursive.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/recursive.rb new file mode 100644 index 0000000000..6971cbfd69 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/recursive.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'uri' + +module Rack + # Rack::ForwardRequest gets caught by Rack::Recursive and redirects + # the current request to the app at +url+. + # + # raise ForwardRequest.new("/not-found") + # + + class ForwardRequest < Exception + attr_reader :url, :env + + def initialize(url, env = {}) + @url = URI(url) + @env = env + + @env[PATH_INFO] = @url.path + @env[QUERY_STRING] = @url.query if @url.query + @env[HTTP_HOST] = @url.host if @url.host + @env[HTTP_PORT] = @url.port if @url.port + @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme + + super "forwarding to #{url}" + end + end + + # Rack::Recursive allows applications called down the chain to + # include data from other applications (by using + # rack['rack.recursive.include'][...] or raise a + # ForwardRequest to redirect internally. + + class Recursive + def initialize(app) + @app = app + end + + def call(env) + dup._call(env) + end + + def _call(env) + @script_name = env[SCRIPT_NAME] + @app.call(env.merge(RACK_RECURSIVE_INCLUDE => method(:include))) + rescue ForwardRequest => req + call(env.merge(req.env)) + end + + def include(env, path) + unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ || + path[@script_name.size].nil?) + raise ArgumentError, "can only include below #{@script_name}, not #{path}" + end + + env = env.merge(PATH_INFO => path, + SCRIPT_NAME => @script_name, + REQUEST_METHOD => GET, + "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "", + RACK_INPUT => StringIO.new("")) + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/reloader.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/reloader.rb new file mode 100644 index 0000000000..2f17f50b83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/reloader.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +# Copyright (C) 2009-2018 Michael Fellinger +# Rack::Reloader is subject to the terms of an MIT-style license. +# See MIT-LICENSE or https://opensource.org/licenses/MIT. + +require 'pathname' + +module Rack + + # High performant source reloader + # + # This class acts as Rack middleware. + # + # What makes it especially suited for use in a production environment is that + # any file will only be checked once and there will only be made one system + # call stat(2). + # + # Please note that this will not reload files in the background, it does so + # only when actively called. + # + # It is performing a check/reload cycle at the start of every request, but + # also respects a cool down time, during which nothing will be done. + class Reloader + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + def initialize(app, cooldown = 10, backend = Stat) + @app = app + @cooldown = cooldown + @last = (Time.now - cooldown) + @cache = {} + @mtimes = {} + @reload_mutex = Mutex.new + + extend backend + end + + def call(env) + if @cooldown and Time.now > @last + @cooldown + if Thread.list.size > 1 + @reload_mutex.synchronize{ reload! } + else + reload! + end + + @last = Time.now + end + + @app.call(env) + end + + def reload!(stderr = $stderr) + rotation do |file, mtime| + previous_mtime = @mtimes[file] ||= mtime + safe_load(file, mtime, stderr) if mtime > previous_mtime + end + end + + # A safe Kernel::load, issuing the hooks depending on the results + def safe_load(file, mtime, stderr = $stderr) + load(file) + stderr.puts "#{self.class}: reloaded `#{file}'" + file + rescue LoadError, SyntaxError => ex + stderr.puts ex + ensure + @mtimes[file] = mtime + end + + module Stat + def rotation + files = [$0, *$LOADED_FEATURES].uniq + paths = ['./', *$LOAD_PATH].uniq + + files.map{|file| + next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files + + found, stat = figure_path(file, paths) + next unless found && stat && mtime = stat.mtime + + @cache[file] = found + + yield(found, mtime) + }.compact + end + + # Takes a relative or absolute +file+ name, a couple possible +paths+ that + # the +file+ might reside in. Returns the full path and File::Stat for the + # path. + def figure_path(file, paths) + found = @cache[file] + found = file if !found and Pathname.new(file).absolute? + found, stat = safe_stat(found) + return found, stat if found + + paths.find do |possible_path| + path = ::File.join(possible_path, file) + found, stat = safe_stat(path) + return ::File.expand_path(found), stat if found + end + + return false, false + end + + def safe_stat(file) + return unless file + stat = ::File.stat(file) + return file, stat if stat.file? + rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH + @cache.delete(file) and false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/request.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/request.rb new file mode 100644 index 0000000000..750a0dc44f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/request.rb @@ -0,0 +1,659 @@ +# frozen_string_literal: true + +module Rack + # Rack::Request provides a convenient interface to a Rack + # environment. It is stateless, the environment +env+ passed to the + # constructor will be directly modified. + # + # req = Rack::Request.new(env) + # req.post? + # req.params["data"] + + class Request + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + class << self + attr_accessor :ip_filter + end + + self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) } + ALLOWED_SCHEMES = %w(https http).freeze + SCHEME_WHITELIST = ALLOWED_SCHEMES + if Object.respond_to?(:deprecate_constant) + deprecate_constant :SCHEME_WHITELIST + end + + def initialize(env) + @params = nil + super(env) + end + + def params + @params ||= super + end + + def update_param(k, v) + super + @params = nil + end + + def delete_param(k) + v = super + @params = nil + v + end + + module Env + # The environment of the request. + attr_reader :env + + def initialize(env) + @env = env + super() + end + + # Predicate method to test to see if `name` has been set as request + # specific data + def has_header?(name) + @env.key? name + end + + # Get a request specific value for `name`. + def get_header(name) + @env[name] + end + + # If a block is given, it yields to the block if the value hasn't been set + # on the request. + def fetch_header(name, &block) + @env.fetch(name, &block) + end + + # Loops through each key / value pair in the request specific data. + def each_header(&block) + @env.each(&block) + end + + # Set a request specific value for `name` to `v` + def set_header(name, v) + @env[name] = v + end + + # Add a header that may have multiple values. + # + # Example: + # request.add_header 'Accept', 'image/png' + # request.add_header 'Accept', '*/*' + # + # assert_equal 'image/png,*/*', request.get_header('Accept') + # + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + def add_header(key, v) + if v.nil? + get_header key + elsif has_header? key + set_header key, "#{get_header key},#{v}" + else + set_header key, v + end + end + + # Delete a request specific value for `name`. + def delete_header(name) + @env.delete name + end + + def initialize_copy(other) + @env = other.env.dup + end + end + + module Helpers + # The set of form-data media-types. Requests that do not indicate + # one of the media types present in this list will not be eligible + # for form-data / param parsing. + FORM_DATA_MEDIA_TYPES = [ + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ] + + # The set of media-types. Requests that do not indicate + # one of the media types present in this list will not be eligible + # for param parsing like soap attachments or generic multiparts + PARSEABLE_DATA_MEDIA_TYPES = [ + 'multipart/related', + 'multipart/mixed' + ] + + # Default ports depending on scheme. Used to decide whether or not + # to include the port in a generated URI. + DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } + + # The address of the client which connected to the proxy. + HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR' + + # The contents of the host/:authority header sent to the proxy. + HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST' + + # The value of the scheme sent to the proxy. + HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME' + + # The protocol used to connect to the proxy. + HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO' + + # The port used to connect to the proxy. + HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT' + + # Another way for specifing https scheme was used. + HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL' + + def body; get_header(RACK_INPUT) end + def script_name; get_header(SCRIPT_NAME).to_s end + def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end + + def path_info; get_header(PATH_INFO).to_s end + def path_info=(s); set_header(PATH_INFO, s.to_s) end + + def request_method; get_header(REQUEST_METHOD) end + def query_string; get_header(QUERY_STRING).to_s end + def content_length; get_header('CONTENT_LENGTH') end + def logger; get_header(RACK_LOGGER) end + def user_agent; get_header('HTTP_USER_AGENT') end + def multithread?; get_header(RACK_MULTITHREAD) end + + # the referer of the client + def referer; get_header('HTTP_REFERER') end + alias referrer referer + + def session + fetch_header(RACK_SESSION) do |k| + set_header RACK_SESSION, default_session + end + end + + def session_options + fetch_header(RACK_SESSION_OPTIONS) do |k| + set_header RACK_SESSION_OPTIONS, {} + end + end + + # Checks the HTTP request method (or verb) to see if it was of type DELETE + def delete?; request_method == DELETE end + + # Checks the HTTP request method (or verb) to see if it was of type GET + def get?; request_method == GET end + + # Checks the HTTP request method (or verb) to see if it was of type HEAD + def head?; request_method == HEAD end + + # Checks the HTTP request method (or verb) to see if it was of type OPTIONS + def options?; request_method == OPTIONS end + + # Checks the HTTP request method (or verb) to see if it was of type LINK + def link?; request_method == LINK end + + # Checks the HTTP request method (or verb) to see if it was of type PATCH + def patch?; request_method == PATCH end + + # Checks the HTTP request method (or verb) to see if it was of type POST + def post?; request_method == POST end + + # Checks the HTTP request method (or verb) to see if it was of type PUT + def put?; request_method == PUT end + + # Checks the HTTP request method (or verb) to see if it was of type TRACE + def trace?; request_method == TRACE end + + # Checks the HTTP request method (or verb) to see if it was of type UNLINK + def unlink?; request_method == UNLINK end + + def scheme + if get_header(HTTPS) == 'on' + 'https' + elsif get_header(HTTP_X_FORWARDED_SSL) == 'on' + 'https' + elsif forwarded_scheme + forwarded_scheme + else + get_header(RACK_URL_SCHEME) + end + end + + # The authority of the incoming request as defined by RFC3976. + # https://tools.ietf.org/html/rfc3986#section-3.2 + # + # In HTTP/1, this is the `host` header. + # In HTTP/2, this is the `:authority` pseudo-header. + def authority + forwarded_authority || host_authority || server_authority + end + + # The authority as defined by the `SERVER_NAME` and `SERVER_PORT` + # variables. + def server_authority + host = self.server_name + port = self.server_port + + if host + if port + "#{host}:#{port}" + else + host + end + end + end + + def server_name + get_header(SERVER_NAME) + end + + def server_port + if port = get_header(SERVER_PORT) + Integer(port) + end + end + + def cookies + hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key| + set_header(key, {}) + end + + string = get_header(HTTP_COOKIE) + + unless string == get_header(RACK_REQUEST_COOKIE_STRING) + hash.replace Utils.parse_cookies_header(string) + set_header(RACK_REQUEST_COOKIE_STRING, string) + end + + hash + end + + def content_type + content_type = get_header('CONTENT_TYPE') + content_type.nil? || content_type.empty? ? nil : content_type + end + + def xhr? + get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" + end + + # The `HTTP_HOST` header. + def host_authority + get_header(HTTP_HOST) + end + + def host_with_port(authority = self.authority) + host, _, port = split_authority(authority) + + if port == DEFAULT_PORTS[self.scheme] + host + else + authority + end + end + + # Returns a formatted host, suitable for being used in a URI. + def host + split_authority(self.authority)[0] + end + + # Returns an address suitable for being to resolve to an address. + # In the case of a domain name or IPv4 address, the result is the same + # as +host+. In the case of IPv6 or future address formats, the square + # brackets are removed. + def hostname + split_authority(self.authority)[1] + end + + def port + if authority = self.authority + _, _, port = split_authority(self.authority) + + if port + return port + end + end + + if forwarded_port = self.forwarded_port + return forwarded_port.first + end + + if scheme = self.scheme + if port = DEFAULT_PORTS[self.scheme] + return port + end + end + + self.server_port + end + + def forwarded_for + if value = get_header(HTTP_X_FORWARDED_FOR) + split_header(value).map do |authority| + split_authority(wrap_ipv6(authority))[1] + end + end + end + + def forwarded_port + if value = get_header(HTTP_X_FORWARDED_PORT) + split_header(value).map(&:to_i) + end + end + + def forwarded_authority + if value = get_header(HTTP_X_FORWARDED_HOST) + wrap_ipv6(split_header(value).first) + end + end + + def ssl? + scheme == 'https' || scheme == 'wss' + end + + def ip + remote_addresses = split_header(get_header('REMOTE_ADDR')) + external_addresses = reject_trusted_ip_addresses(remote_addresses) + + unless external_addresses.empty? + return external_addresses.first + end + + if forwarded_for = self.forwarded_for + unless forwarded_for.empty? + # The forwarded for addresses are ordered: client, proxy1, proxy2. + # So we reject all the trusted addresses (proxy*) and return the + # last client. Or if we trust everyone, we just return the first + # address. + return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first + end + end + + # If all the addresses are trusted, and we aren't forwarded, just return + # the first remote address, which represents the source of the request. + remote_addresses.first + end + + # The media type (type/subtype) portion of the CONTENT_TYPE header + # without any media type parameters. e.g., when CONTENT_TYPE is + # "text/plain;charset=utf-8", the media-type is "text/plain". + # + # For more information on the use of media types in HTTP, see: + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 + def media_type + MediaType.type(content_type) + end + + # The media type parameters provided in CONTENT_TYPE as a Hash, or + # an empty Hash if no CONTENT_TYPE or media-type parameters were + # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", + # this method responds with the following Hash: + # { 'charset' => 'utf-8' } + def media_type_params + MediaType.params(content_type) + end + + # The character set of the request body if a "charset" media type + # parameter was given, or nil if no "charset" was specified. Note + # that, per RFC2616, text/* media types that specify no explicit + # charset are to be considered ISO-8859-1. + def content_charset + media_type_params['charset'] + 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 also assumed to contain form-data when no + # Content-Type header is provided and the request_method is POST. + def form_data? + type = media_type + meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD) + + (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type) + end + + # Determine whether the request body contains data by checking + # the request media_type against registered parse-data media-types + def parseable_data? + PARSEABLE_DATA_MEDIA_TYPES.include?(media_type) + end + + # Returns the data received in the query string. + def GET + if get_header(RACK_REQUEST_QUERY_STRING) == query_string + get_header(RACK_REQUEST_QUERY_HASH) + else + query_hash = parse_query(query_string, '&;') + set_header(RACK_REQUEST_QUERY_STRING, query_string) + set_header(RACK_REQUEST_QUERY_HASH, query_hash) + end + end + + # Returns the data received in the request body. + # + # This method support both application/x-www-form-urlencoded and + # multipart/form-data. + def POST + if get_header(RACK_INPUT).nil? + raise "Missing rack.input" + elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT) + get_header(RACK_REQUEST_FORM_HASH) + elsif form_data? || parseable_data? + unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart) + form_vars = get_header(RACK_INPUT).read + + # Fix for Safari Ajax postings that always append \0 + # form_vars.sub!(/\0\z/, '') # performance replacement: + form_vars.slice!(-1) if form_vars.end_with?("\0") + + set_header RACK_REQUEST_FORM_VARS, form_vars + set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&') + + get_header(RACK_INPUT).rewind + end + set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) + get_header RACK_REQUEST_FORM_HASH + else + {} + end + end + + # The union of GET and POST data. + # + # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. + def params + self.GET.merge(self.POST) + end + + # Destructively update a parameter, whether it's in GET and/or POST. Returns nil. + # + # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET. + # + # env['rack.input'] is not touched. + def update_param(k, v) + found = false + if self.GET.has_key?(k) + found = true + self.GET[k] = v + end + if self.POST.has_key?(k) + found = true + self.POST[k] = v + end + unless found + self.GET[k] = v + end + end + + # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter. + # + # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works. + # + # env['rack.input'] is not touched. + def delete_param(k) + post_value, get_value = self.POST.delete(k), self.GET.delete(k) + post_value || get_value + end + + def base_url + "#{scheme}://#{host_with_port}" + end + + # Tries to return a remake of the original request URL as a string. + def url + base_url + fullpath + end + + def path + script_name + path_info + end + + def fullpath + query_string.empty? ? path : "#{path}?#{query_string}" + end + + def accept_encoding + parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING")) + end + + def accept_language + parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE")) + end + + def trusted_proxy?(ip) + Rack::Request.ip_filter.call(ip) + end + + # shortcut for request.params[key] + def [](key) + if $VERBOSE + warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead") + end + + params[key.to_s] + end + + # shortcut for request.params[key] = value + # + # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. + def []=(key, value) + if $VERBOSE + warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead") + end + + params[key.to_s] = value + end + + # like Hash#values_at + def values_at(*keys) + keys.map { |key| params[key] } + end + + private + + def default_session; {}; end + + # Assist with compatibility when processing `X-Forwarded-For`. + def wrap_ipv6(host) + # Even thought IPv6 addresses should be wrapped in square brackets, + # sometimes this is not done in various legacy/underspecified headers. + # So we try to fix this situation for compatibility reasons. + + # Try to detect IPv6 addresses which aren't escaped yet: + if !host.start_with?('[') && host.count(':') > 1 + "[#{host}]" + else + host + end + end + + def parse_http_accept_header(header) + header.to_s.split(/\s*,\s*/).map do |part| + attribute, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if parameters and /\Aq=([\d.]+)/ =~ parameters + quality = $1.to_f + end + [attribute, quality] + end + end + + def query_parser + Utils.default_query_parser + end + + def parse_query(qs, d = '&') + query_parser.parse_nested_query(qs, d) + end + + def parse_multipart + Rack::Multipart.extract_multipart(self, query_parser) + end + + def split_header(value) + value ? value.strip.split(/[,\s]+/) : [] + end + + AUTHORITY = /^ + # The host: + (? + # An IPv6 address: + (\[(?.*)\]) + | + # An IPv4 address: + (?[\d\.]+) + | + # A hostname: + (?[a-zA-Z0-9\.\-]+) + ) + # The optional port: + (:(?\d+))? + $/x + + private_constant :AUTHORITY + + def split_authority(authority) + if match = AUTHORITY.match(authority) + if address = match[:ip6] + return match[:host], address, match[:port]&.to_i + else + return match[:host], match[:host], match[:port]&.to_i + end + end + + # Give up! + return authority, authority, nil + end + + def reject_trusted_ip_addresses(ip_addresses) + ip_addresses.reject { |ip| trusted_proxy?(ip) } + end + + def forwarded_scheme + allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) || + allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))) + end + + def allowed_scheme(header) + header if ALLOWED_SCHEMES.include?(header) + end + + def extract_proto_header(header) + if header + if (comma_index = header.index(',')) + header[0, comma_index] + else + header + end + end + end + end + + include Env + include Helpers + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/response.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/response.rb new file mode 100644 index 0000000000..fd6d2f5d55 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/response.rb @@ -0,0 +1,318 @@ +# frozen_string_literal: true + +require 'time' + +module Rack + # Rack::Response provides a convenient interface to create a Rack + # response. + # + # It allows setting of headers and cookies, and provides useful + # defaults (an OK response with empty headers and body). + # + # You can use Response#write to iteratively generate your response, + # but note that this is buffered by Rack::Response until you call + # +finish+. +finish+ however can take a block inside which calls to + # +write+ are synchronous with the Rack response. + # + # Your application's +call+ should end returning Response#finish. + class Response + def self.[](status, headers, body) + self.new(body, status, headers) + end + + CHUNKED = 'chunked' + STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY + + attr_accessor :length, :status, :body + attr_reader :headers + + # @deprecated Use {#headers} instead. + alias header headers + + # Initialize the response object with the specified body, status + # and headers. + # + # @param body [nil, #each, #to_str] the response body. + # @param status [Integer] the integer status as defined by the + # HTTP protocol RFCs. + # @param headers [#each] a list of key-value header pairs which + # conform to the HTTP protocol RFCs. + # + # Providing a body which responds to #to_str is legacy behaviour. + def initialize(body = nil, status = 200, headers = {}) + @status = status.to_i + @headers = Utils::HeaderHash[headers] + + @writer = self.method(:append) + + @block = nil + + # Keep track of whether we have expanded the user supplied body. + if body.nil? + @body = [] + @buffered = true + @length = 0 + elsif body.respond_to?(:to_str) + @body = [body] + @buffered = true + @length = body.to_str.bytesize + else + @body = body + @buffered = false + @length = 0 + end + + yield self if block_given? + end + + def redirect(target, status = 302) + self.status = status + self.location = target + end + + def chunked? + CHUNKED == get_header(TRANSFER_ENCODING) + end + + # Generate a response array consistent with the requirements of the SPEC. + # @return [Array] a 3-tuple suitable of `[status, headers, body]` + # which is suitable to be returned from the middleware `#call(env)` method. + def finish(&block) + if STATUS_WITH_NO_ENTITY_BODY[status.to_i] + delete_header CONTENT_TYPE + delete_header CONTENT_LENGTH + close + return [@status, @headers, []] + else + if block_given? + @block = block + return [@status, @headers, self] + else + return [@status, @headers, @body] + end + end + end + + alias to_a finish # For *response + + def each(&callback) + @body.each(&callback) + @buffered = true + + if @block + @writer = callback + @block.call(self) + end + end + + # Append to body and update Content-Length. + # + # NOTE: Do not mix #write and direct #body access! + # + def write(chunk) + buffered_body! + + @writer.call(chunk.to_s) + end + + def close + @body.close if @body.respond_to?(:close) + end + + def empty? + @block == nil && @body.empty? + 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 + + alias :[] :get_header + alias :[]= :set_header + + module Helpers + def invalid?; status < 100 || status >= 600; end + + def informational?; status >= 100 && status < 200; end + def successful?; status >= 200 && status < 300; end + def redirection?; status >= 300 && status < 400; end + def client_error?; status >= 400 && status < 500; end + def server_error?; status >= 500 && status < 600; end + + def ok?; status == 200; end + def created?; status == 201; end + def accepted?; status == 202; end + def no_content?; status == 204; end + def moved_permanently?; status == 301; end + def bad_request?; status == 400; end + def unauthorized?; status == 401; end + def forbidden?; status == 403; end + def not_found?; status == 404; end + def method_not_allowed?; status == 405; end + def precondition_failed?; status == 412; end + def unprocessable?; status == 422; end + + def redirect?; [301, 302, 303, 307, 308].include? status; end + + def include?(header) + has_header? header + end + + # Add a header that may have multiple values. + # + # Example: + # response.add_header 'Vary', 'Accept-Encoding' + # response.add_header 'Vary', 'Cookie' + # + # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary') + # + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + def add_header(key, v) + if v.nil? + get_header key + elsif has_header? key + set_header key, "#{get_header key},#{v}" + else + set_header key, v + end + end + + # Get the content type of the response. + def content_type + get_header CONTENT_TYPE + end + + # Set the content type of the response. + def content_type=(content_type) + set_header CONTENT_TYPE, content_type + end + + def media_type + MediaType.type(content_type) + end + + def media_type_params + MediaType.params(content_type) + end + + def content_length + cl = get_header CONTENT_LENGTH + cl ? cl.to_i : cl + end + + def location + get_header "Location" + end + + def location=(location) + set_header "Location", location + end + + def set_cookie(key, value) + cookie_header = get_header SET_COOKIE + set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value) + end + + def delete_cookie(key, value = {}) + set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value) + end + + def set_cookie_header + get_header SET_COOKIE + end + + def set_cookie_header=(v) + set_header SET_COOKIE, v + end + + def cache_control + get_header CACHE_CONTROL + end + + def cache_control=(v) + set_header CACHE_CONTROL, v + end + + # Specifies that the content shouldn't be cached. Overrides `cache!` if already called. + def do_not_cache! + set_header CACHE_CONTROL, "no-cache, must-revalidate" + set_header EXPIRES, Time.now.httpdate + end + + # Specify that the content should be cached. + # @param duration [Integer] The number of seconds until the cache expires. + # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store". + def cache!(duration = 3600, directive: "public") + unless headers[CACHE_CONTROL] =~ /no-cache/ + set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}" + set_header EXPIRES, (Time.now + duration).httpdate + end + end + + def etag + get_header ETAG + end + + def etag=(v) + set_header ETAG, v + end + + protected + + def buffered_body! + return if @buffered + + if @body.is_a?(Array) + # The user supplied body was an array: + @body = @body.compact + @body.each do |part| + @length += part.to_s.bytesize + end + else + # Turn the user supplied body into a buffered array: + body = @body + @body = Array.new + + body.each do |part| + @writer.call(part.to_s) + end + + body.close if body.respond_to?(:close) + end + + @buffered = true + end + + def append(chunk) + @body << chunk + + unless chunked? + @length += chunk.bytesize + set_header(CONTENT_LENGTH, @length.to_s) + end + + return chunk + end + end + + include Helpers + + class Raw + include Helpers + + attr_reader :headers + attr_accessor :status + + def initialize(status, headers) + @status = status + @headers = headers + 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 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/rewindable_input.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/rewindable_input.rb new file mode 100644 index 0000000000..91b9d1eb36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/rewindable_input.rb @@ -0,0 +1,94 @@ +# -*- encoding: binary -*- +# frozen_string_literal: true + +require 'tempfile' + +module Rack + # Class which can make any IO object rewindable, including non-rewindable ones. It does + # this by buffering the data into a tempfile, which is rewindable. + # + # rack.input is required to be rewindable, so if your input stream IO is non-rewindable + # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class + # to easily make it rewindable. + # + # Don't forget to call #close when you're done. This frees up temporary resources that + # RewindableInput uses, though it does *not* close the original IO object. + class RewindableInput + def initialize(io) + @io = io + @rewindable_io = nil + @unlinked = false + end + + def gets + make_rewindable unless @rewindable_io + @rewindable_io.gets + end + + def read(*args) + make_rewindable unless @rewindable_io + @rewindable_io.read(*args) + end + + def each(&block) + make_rewindable unless @rewindable_io + @rewindable_io.each(&block) + end + + def rewind + make_rewindable unless @rewindable_io + @rewindable_io.rewind + end + + # Closes this RewindableInput object without closing the originally + # wrapped IO object. Cleans up any temporary resources that this RewindableInput + # has created. + # + # This method may be called multiple times. It does nothing on subsequent calls. + def close + if @rewindable_io + if @unlinked + @rewindable_io.close + else + @rewindable_io.close! + end + @rewindable_io = nil + end + end + + private + + def make_rewindable + # Buffer all data into a tempfile. Since this tempfile is private to this + # RewindableInput object, we chmod it so that nobody else can read or write + # it. On POSIX filesystems we also unlink the file so that it doesn't + # even have a file entry on the filesystem anymore, though we can still + # access it because we have the file handle open. + @rewindable_io = Tempfile.new('RackRewindableInput') + @rewindable_io.chmod(0000) + @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding) + @rewindable_io.binmode + if filesystem_has_posix_semantics? + raise 'Unlink failed. IO closed.' if @rewindable_io.closed? + @unlinked = true + end + + buffer = "".dup + while @io.read(1024 * 4, buffer) + entire_buffer_written_out = false + while !entire_buffer_written_out + written = @rewindable_io.write(buffer) + entire_buffer_written_out = written == buffer.bytesize + if !entire_buffer_written_out + buffer.slice!(0 .. written - 1) + end + end + end + @rewindable_io.rewind + end + + def filesystem_has_posix_semantics? + RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/ + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/runtime.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/runtime.rb new file mode 100644 index 0000000000..d9b2d8ed19 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/runtime.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Rack + # Sets an "X-Runtime" response header, indicating the response + # time of the request, in seconds + # + # You can put it right before the application to see the processing + # time, or before all the other middlewares to include time for them, + # too. + class Runtime + FORMAT_STRING = "%0.6f" # :nodoc: + HEADER_NAME = "X-Runtime" # :nodoc: + + def initialize(app, name = nil) + @app = app + @header_name = HEADER_NAME + @header_name += "-#{name}" if name + end + + def call(env) + start_time = Utils.clock_time + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + + request_time = Utils.clock_time - start_time + + unless headers.key?(@header_name) + headers[@header_name] = FORMAT_STRING % request_time + end + + [status, headers, body] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/sendfile.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/sendfile.rb new file mode 100644 index 0000000000..3d5e786ff7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/sendfile.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +module Rack + + # = Sendfile + # + # The Sendfile middleware intercepts responses whose body is being + # served from a file and replaces it with a server specific X-Sendfile + # header. The web server is then responsible for writing the file contents + # to the client. This can dramatically reduce the amount of work required + # by the Ruby backend and takes advantage of the web server's optimized file + # delivery code. + # + # In order to take advantage of this middleware, the response body must + # respond to +to_path+ and the request must include an X-Sendfile-Type + # header. Rack::Files and other components implement +to_path+ so there's + # rarely anything you need to do in your application. The X-Sendfile-Type + # header is typically set in your web servers configuration. The following + # sections attempt to document + # + # === Nginx + # + # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile + # but requires parts of the filesystem to be mapped into a private URL + # hierarchy. + # + # The following example shows the Nginx configuration required to create + # a private "/files/" area, enable X-Accel-Redirect, and pass the special + # X-Sendfile-Type and X-Accel-Mapping headers to the backend: + # + # location ~ /files/(.*) { + # internal; + # alias /var/www/$1; + # } + # + # location / { + # proxy_redirect off; + # + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # + # proxy_set_header X-Sendfile-Type X-Accel-Redirect; + # proxy_set_header X-Accel-Mapping /var/www/=/files/; + # + # proxy_pass http://127.0.0.1:8080/; + # } + # + # Note that the X-Sendfile-Type header must be set exactly as shown above. + # The X-Accel-Mapping header should specify the location on the file system, + # followed by an equals sign (=), followed name of the private URL pattern + # that it maps to. The middleware performs a simple substitution on the + # resulting path. + # + # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile + # + # === lighttpd + # + # Lighttpd has supported some variation of the X-Sendfile header for some + # time, although only recent version support X-Sendfile in a reverse proxy + # configuration. + # + # $HTTP["host"] == "example.com" { + # proxy-core.protocol = "http" + # proxy-core.balancer = "round-robin" + # proxy-core.backends = ( + # "127.0.0.1:8000", + # "127.0.0.1:8001", + # ... + # ) + # + # proxy-core.allow-x-sendfile = "enable" + # proxy-core.rewrite-request = ( + # "X-Sendfile-Type" => (".*" => "X-Sendfile") + # ) + # } + # + # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore + # + # === Apache + # + # X-Sendfile is supported under Apache 2.x using a separate module: + # + # https://tn123.org/mod_xsendfile/ + # + # Once the module is compiled and installed, you can enable it using + # XSendFile config directive: + # + # RequestHeader Set X-Sendfile-Type X-Sendfile + # ProxyPassReverse / http://localhost:8001/ + # XSendFile on + # + # === Mapping parameter + # + # The third parameter allows for an overriding extension of the + # X-Accel-Mapping header. Mappings should be provided in tuples of internal to + # external. The internal values may contain regular expression syntax, they + # will be matched with case indifference. + + class Sendfile + def initialize(app, variation = nil, mappings = []) + @app = app + @variation = variation + @mappings = mappings.map do |internal, external| + [/^#{internal}/i, external] + end + end + + def call(env) + status, headers, body = @app.call(env) + if body.respond_to?(:to_path) + case type = variation(env) + when 'X-Accel-Redirect' + path = ::File.expand_path(body.to_path) + if url = map_accel_path(env, path) + headers[CONTENT_LENGTH] = '0' + # '?' must be percent-encoded because it is not query string but a part of path + headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F') + obody = body + body = Rack::BodyProxy.new([]) do + obody.close if obody.respond_to?(:close) + end + else + env[RACK_ERRORS].puts "X-Accel-Mapping header missing" + end + when 'X-Sendfile', 'X-Lighttpd-Send-File' + path = ::File.expand_path(body.to_path) + headers[CONTENT_LENGTH] = '0' + headers[type] = path + obody = body + body = Rack::BodyProxy.new([]) do + obody.close if obody.respond_to?(:close) + end + when '', nil + else + env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n" + end + end + [status, headers, body] + end + + private + def variation(env) + @variation || + env['sendfile.type'] || + env['HTTP_X_SENDFILE_TYPE'] + end + + def map_accel_path(env, path) + if mapping = @mappings.find { |internal, _| internal =~ path } + path.sub(*mapping) + elsif mapping = env['HTTP_X_ACCEL_MAPPING'] + mapping.split(',').map(&:strip).each do |m| + internal, external = m.split('=', 2).map(&:strip) + new_path = path.sub(/^#{internal}/i, external) + return new_path unless path == new_path + end + path + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/server.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/server.rb new file mode 100644 index 0000000000..c1f2f5caa3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/server.rb @@ -0,0 +1,466 @@ +# frozen_string_literal: true + +require 'optparse' +require 'fileutils' + +module Rack + + class Server + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + class Options + def parse!(args) + options = {} + opt_parser = OptionParser.new("", 24, ' ') do |opts| + opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]" + + opts.separator "" + opts.separator "Ruby options:" + + lineno = 1 + opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line| + eval line, TOPLEVEL_BINDING, "-e", lineno + lineno += 1 + } + + opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") { + options[:debug] = true + } + opts.on("-w", "--warn", "turn warnings on for your script") { + options[:warn] = true + } + opts.on("-q", "--quiet", "turn off logging") { + options[:quiet] = true + } + + opts.on("-I", "--include PATH", + "specify $LOAD_PATH (may be used more than once)") { |path| + (options[:include] ||= []).concat(path.split(":")) + } + + opts.on("-r", "--require LIBRARY", + "require the library, before executing your script") { |library| + (options[:require] ||= []) << library + } + + opts.separator "" + opts.separator "Rack options:" + opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line| + options[:builder] = line + } + + opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s| + options[:server] = s + } + + opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host| + options[:Host] = host + } + + opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port| + options[:Port] = port + } + + opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name| + name, value = name.split('=', 2) + value = true if value.nil? + options[name.to_sym] = value + } + + opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e| + options[:environment] = e + } + + opts.on("-D", "--daemonize", "run daemonized in the background") { |d| + options[:daemonize] = d ? true : false + } + + opts.on("-P", "--pid FILE", "file to store PID") { |f| + options[:pid] = ::File.expand_path(f) + } + + opts.separator "" + opts.separator "Profiling options:" + + opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e| + options[:heapfile] = e + end + + opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e| + options[:profile_file] = e + end + + opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e| + { cpu: true, wall: true, object: true }.fetch(e.to_sym) do + raise OptionParser::InvalidOption, "unknown profile mode: #{e}" + end + options[:profile_mode] = e.to_sym + end + + opts.separator "" + opts.separator "Common options:" + + opts.on_tail("-h", "-?", "--help", "Show this message") do + puts opts + puts handler_opts(options) + + exit + end + + opts.on_tail("--version", "Show version") do + puts "Rack #{Rack.version} (Release: #{Rack.release})" + exit + end + end + + begin + opt_parser.parse! args + rescue OptionParser::InvalidOption => e + warn e.message + abort opt_parser.to_s + end + + options[:config] = args.last if args.last && !args.last.empty? + options + end + + def handler_opts(options) + begin + info = [] + server = Rack::Handler.get(options[:server]) || Rack::Handler.default + if server && server.respond_to?(:valid_options) + info << "" + info << "Server-specific options for #{server.name}:" + + has_options = false + server.valid_options.each do |name, description| + next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own. + info << " -O %-21s %s" % [name, description] + has_options = true + end + return "" if !has_options + end + info.join("\n") + rescue NameError, LoadError + return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options" + end + end + end + + # Start a new rack server (like running rackup). This will parse ARGV and + # provide standard ARGV rackup options, defaulting to load 'config.ru'. + # + # Providing an options hash will prevent ARGV parsing and will not include + # any default options. + # + # This method can be used to very easily launch a CGI application, for + # example: + # + # Rack::Server.start( + # :app => lambda do |e| + # [200, {'Content-Type' => 'text/html'}, ['hello world']] + # end, + # :server => 'cgi' + # ) + # + # Further options available here are documented on Rack::Server#initialize + def self.start(options = nil) + new(options).start + end + + attr_writer :options + + # Options may include: + # * :app + # a rack application to run (overrides :config and :builder) + # * :builder + # a string to evaluate a Rack::Builder from + # * :config + # a rackup configuration file path to load (.ru) + # * :environment + # this selects the middleware that will be wrapped around + # your application. Default options available are: + # - development: CommonLogger, ShowExceptions, and Lint + # - deployment: CommonLogger + # - none: no extra middleware + # note: when the server is a cgi server, CommonLogger is not included. + # * :server + # choose a specific Rack::Handler, e.g. cgi, fcgi, webrick + # * :daemonize + # if true, the server will daemonize itself (fork, detach, etc) + # * :pid + # path to write a pid file after daemonize + # * :Host + # the host address to bind to (used by supporting Rack::Handler) + # * :Port + # the port to bind to (used by supporting Rack::Handler) + # * :AccessLog + # webrick access log options (or supporting Rack::Handler) + # * :debug + # turn on debug output ($DEBUG = true) + # * :warn + # turn on warnings ($-w = true) + # * :include + # add given paths to $LOAD_PATH + # * :require + # require the given libraries + # + # Additional options for profiling app initialization include: + # * :heapfile + # location for ObjectSpace.dump_all to write the output to + # * :profile_file + # location for CPU/Memory (StackProf) profile output (defaults to a tempfile) + # * :profile_mode + # StackProf profile mode (cpu|wall|object) + def initialize(options = nil) + @ignore_options = [] + + if options + @use_default_options = false + @options = options + @app = options[:app] if options[:app] + else + argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV + @use_default_options = true + @options = parse_options(argv) + end + end + + def options + merged_options = @use_default_options ? default_options.merge(@options) : @options + merged_options.reject { |k, v| @ignore_options.include?(k) } + end + + def default_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + environment: environment, + pid: nil, + Port: 9292, + Host: default_host, + AccessLog: [], + config: "config.ru" + } + end + + def app + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config + end + + class << self + def logging_middleware + lambda { |server| + /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr] + } + end + + def default_middleware_by_environment + m = Hash.new {|h, k| h[k] = []} + m["deployment"] = [ + [Rack::ContentLength], + logging_middleware, + [Rack::TempfileReaper] + ] + m["development"] = [ + [Rack::ContentLength], + logging_middleware, + [Rack::ShowExceptions], + [Rack::Lint], + [Rack::TempfileReaper] + ] + + m + end + + def middleware + default_middleware_by_environment + end + end + + def middleware + self.class.middleware + end + + def start(&block) + if options[:warn] + $-w = true + end + + if includes = options[:include] + $LOAD_PATH.unshift(*includes) + end + + Array(options[:require]).each do |library| + require library + end + + if options[:debug] + $DEBUG = true + require 'pp' + p options[:server] + pp wrapped_app + pp app + end + + check_pid! if options[:pid] + + # Touch the wrapped app, so that the config.ru is loaded before + # daemonization (i.e. before chdir, etc). + handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do + wrapped_app + end + + daemonize_app if options[:daemonize] + + write_pid if options[:pid] + + trap(:INT) do + if server.respond_to?(:shutdown) + server.shutdown + else + exit + end + end + + server.run(wrapped_app, **options, &block) + end + + def server + @_server ||= Rack::Handler.get(options[:server]) + + unless @_server + @_server = Rack::Handler.default + + # We already speak FastCGI + @ignore_options = [:File, :Port] if @_server.to_s == 'Rack::Handler::FastCGI' + end + + @_server + end + + private + def build_app_and_options_from_config + if !::File.exist? options[:config] + abort "configuration #{options[:config]} not found" + end + + app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) + @options.merge!(options) { |key, old, new| old } + app + end + + def handle_profiling(heapfile, profile_mode, filename) + if heapfile + require "objspace" + ObjectSpace.trace_object_allocations_start + yield + GC.start + ::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) } + exit + end + + if profile_mode + require "stackprof" + require "tempfile" + + make_profile_name(filename) do |filename| + ::File.open(filename, "w") do |f| + StackProf.run(mode: profile_mode, out: f) do + yield + end + puts "Profile written to: #{filename}" + end + end + exit + end + + yield + end + + def make_profile_name(filename) + if filename + yield filename + else + ::Dir::Tmpname.create("profile.dump") do |tmpname, _, _| + yield tmpname + end + end + end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end + + def parse_options(args) + # Don't evaluate CGI ISINDEX parameters. + # http://www.meb.uni-bonn.de/docs/cgi/cl.html + args.clear if ENV.include?(REQUEST_METHOD) + + @options = opt_parser.parse!(args) + @options[:config] = ::File.expand_path(options[:config]) + ENV["RACK_ENV"] = options[:environment] + @options + end + + def opt_parser + Options.new + end + + def build_app(app) + middleware[options[:environment]].reverse_each do |middleware| + middleware = middleware.call(self) if middleware.respond_to?(:call) + next unless middleware + klass, *args = middleware + app = klass.new(app, *args) + end + app + end + + def wrapped_app + @wrapped_app ||= build_app app + end + + def daemonize_app + # Cannot be covered as it forks + # :nocov: + Process.daemon + # :nocov: + end + + def write_pid + ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") } + at_exit { ::FileUtils.rm_f(options[:pid]) } + rescue Errno::EEXIST + check_pid! + retry + end + + def check_pid! + case pidfile_process_status + when :running, :not_owned + $stderr.puts "A server is already running. Check #{options[:pid]}." + exit(1) + when :dead + ::File.delete(options[:pid]) + end + end + + def pidfile_process_status + return :exited unless ::File.exist?(options[:pid]) + + pid = ::File.read(options[:pid]).to_i + return :dead if pid == 0 + + Process.kill(0, pid) + :running + rescue Errno::ESRCH + :dead + rescue Errno::EPERM + :not_owned + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/abstract/id.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/abstract/id.rb new file mode 100644 index 0000000000..638bd3b3b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/abstract/id.rb @@ -0,0 +1,523 @@ +# frozen_string_literal: true + +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net +# bugrep: Andreas Zehnder + +require_relative '../../../rack' +require 'time' +require 'securerandom' +require 'digest/sha2' + +module Rack + + module Session + + class SessionId + ID_VERSION = 2 + + attr_reader :public_id + + def initialize(public_id) + @public_id = public_id + end + + def private_id + "#{ID_VERSION}::#{hash_sid(public_id)}" + end + + alias :cookie_value :public_id + alias :to_s :public_id + + def empty?; false; end + def inspect; public_id.inspect; end + + private + + def hash_sid(sid) + Digest::SHA256.hexdigest(sid) + end + end + + module Abstract + # SessionHash is responsible to lazily load the session from store. + + class SessionHash + include Enumerable + attr_writer :id + + Unspecified = Object.new + + def self.find(req) + req.get_header RACK_SESSION + end + + def self.set(req, session) + req.set_header RACK_SESSION, session + end + + def self.set_options(req, options) + req.set_header RACK_SESSION_OPTIONS, options.dup + end + + def initialize(store, req) + @store = store + @req = req + @loaded = false + end + + def id + return @id if @loaded or instance_variable_defined?(:@id) + @id = @store.send(:extract_session_id, @req) + end + + def options + @req.session_options + end + + def each(&block) + load_for_read! + @data.each(&block) + end + + def [](key) + load_for_read! + @data[key.to_s] + end + + def dig(key, *keys) + load_for_read! + @data.dig(key.to_s, *keys) + end + + def fetch(key, default = Unspecified, &block) + load_for_read! + if default == Unspecified + @data.fetch(key.to_s, &block) + else + @data.fetch(key.to_s, default, &block) + end + end + + def has_key?(key) + load_for_read! + @data.has_key?(key.to_s) + end + alias :key? :has_key? + alias :include? :has_key? + + def []=(key, value) + load_for_write! + @data[key.to_s] = value + end + alias :store :[]= + + def clear + load_for_write! + @data.clear + end + + def destroy + clear + @id = @store.send(:delete_session, @req, id, options) + end + + def to_hash + load_for_read! + @data.dup + end + + def update(hash) + load_for_write! + @data.update(stringify_keys(hash)) + end + alias :merge! :update + + def replace(hash) + load_for_write! + @data.replace(stringify_keys(hash)) + end + + def delete(key) + load_for_write! + @data.delete(key.to_s) + end + + def inspect + if loaded? + @data.inspect + else + "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>" + end + end + + def exists? + return @exists if instance_variable_defined?(:@exists) + @data = {} + @exists = @store.send(:session_exists?, @req) + end + + def loaded? + @loaded + end + + def empty? + load_for_read! + @data.empty? + end + + def keys + load_for_read! + @data.keys + end + + def values + load_for_read! + @data.values + end + + private + + def load_for_read! + load! if !loaded? && exists? + end + + def load_for_write! + load! unless loaded? + end + + def load! + @id, session = @store.send(:load_session, @req) + @data = stringify_keys(session) + @loaded = true + end + + def stringify_keys(other) + # Use transform_keys after dropping Ruby 2.4 support + hash = {} + other.to_hash.each do |key, value| + hash[key.to_s] = value + end + hash + end + end + + # ID sets up a basic framework for implementing an id based sessioning + # service. Cookies sent to the client for maintaining sessions will only + # contain an id reference. Only #find_session, #write_session and + # #delete_session are required to be overwritten. + # + # All parameters are optional. + # * :key determines the name of the cookie, by default it is + # 'rack.session' + # * :path, :domain, :expire_after, :secure, and :httponly set the related + # cookie options as by Rack::Response#set_cookie + # * :skip will not a set a cookie in the response nor update the session state + # * :defer will not set a cookie in the response but still update the session + # state if it is used with a backend + # * :renew (implementation dependent) will prompt the generation of a new + # session id, and migration of data to be referenced at the new id. If + # :defer is set, it will be overridden and the cookie will be set. + # * :sidbits sets the number of bits in length that a generated session + # id will be. + # + # These options can be set on a per request basis, at the location of + # env['rack.session.options']. Additionally the id of the + # session can be found within the options hash at the key :id. It is + # highly not recommended to change its value. + # + # Is Rack::Utils::Context compatible. + # + # Not included by default; you must require 'rack/session/abstract/id' + # to use. + + class Persisted + DEFAULT_OPTIONS = { + key: RACK_SESSION, + path: '/', + domain: nil, + expire_after: nil, + secure: false, + httponly: true, + defer: false, + renew: false, + sidbits: 128, + cookie_only: true, + secure_random: ::SecureRandom + }.freeze + + attr_reader :key, :default_options, :sid_secure + + def initialize(app, options = {}) + @app = app + @default_options = self.class::DEFAULT_OPTIONS.merge(options) + @key = @default_options.delete(:key) + @cookie_only = @default_options.delete(:cookie_only) + @same_site = @default_options.delete(:same_site) + initialize_sid + end + + def call(env) + context(env) + end + + def context(env, app = @app) + req = make_request env + prepare_session(req) + status, headers, body = app.call(req.env) + res = Rack::Response::Raw.new status, headers + commit_session(req, res) + [status, headers, body] + end + + private + + def make_request(env) + Rack::Request.new env + end + + def initialize_sid + @sidbits = @default_options[:sidbits] + @sid_secure = @default_options[:secure_random] + @sid_length = @sidbits / 4 + end + + # Generate a new session id using Ruby #rand. The size of the + # session id is controlled by the :sidbits option. + # Monkey patch this to use custom methods for session id generation. + + def generate_sid(secure = @sid_secure) + if secure + secure.hex(@sid_length) + else + "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1) + end + rescue NotImplementedError + generate_sid(false) + end + + # Sets the lazy session at 'rack.session' and places options and session + # metadata into 'rack.session.options'. + + def prepare_session(req) + session_was = req.get_header RACK_SESSION + session = session_class.new(self, req) + req.set_header RACK_SESSION, session + req.set_header RACK_SESSION_OPTIONS, @default_options.dup + session.merge! session_was if session_was + end + + # Extracts the session id from provided cookies and passes it and the + # environment to #find_session. + + def load_session(req) + sid = current_session_id(req) + sid, session = find_session(req, sid) + [sid, session || {}] + end + + # Extract session id from request object. + + def extract_session_id(request) + sid = request.cookies[@key] + sid ||= request.params[@key] unless @cookie_only + sid + end + + # Returns the current session id from the SessionHash. + + def current_session_id(req) + req.get_header(RACK_SESSION).id + end + + # Check if the session exists or not. + + def session_exists?(req) + value = current_session_id(req) + value && !value.empty? + end + + # Session should be committed if it was loaded, any of specific options like :renew, :drop + # or :expire_after was given and the security permissions match. Skips if skip is given. + + def commit_session?(req, session, options) + if options[:skip] + false + else + has_session = loaded_session?(session) || forced_session_update?(session, options) + has_session && security_matches?(req, options) + end + end + + def loaded_session?(session) + !session.is_a?(session_class) || session.loaded? + end + + def forced_session_update?(session, options) + force_options?(options) && session && !session.empty? + end + + def force_options?(options) + options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any? + end + + def security_matches?(request, options) + return true unless options[:secure] + request.ssl? + end + + # Acquires the session from the environment and the session id from + # the session options and passes them to #write_session. If successful + # and the :defer option is not true, a cookie will be added to the + # response with the session's id. + + def commit_session(req, res) + session = req.get_header RACK_SESSION + options = session.options + + if options[:drop] || options[:renew] + session_id = delete_session(req, session.id || generate_sid, options) + return unless session_id + end + + return unless commit_session?(req, session, options) + + session.send(:load!) unless loaded_session?(session) + session_id ||= session.id + session_data = session.to_hash.delete_if { |k, v| v.nil? } + + if not data = write_session(req, session_id, session_data, options) + req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.") + elsif options[:defer] and not options[:renew] + req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE + else + cookie = Hash.new + cookie[:value] = cookie_value(data) + cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after] + cookie[:expires] = Time.now + options[:max_age] if options[:max_age] + + if @same_site.respond_to? :call + cookie[:same_site] = @same_site.call(req, res) + else + cookie[:same_site] = @same_site + end + set_cookie(req, res, cookie.merge!(options)) + end + end + public :commit_session + + def cookie_value(data) + data + end + + # Sets the cookie back to the client with session id. We skip the cookie + # setting if the value didn't change (sid is the same) or expires was given. + + def set_cookie(request, res, cookie) + if request.cookies[@key] != cookie[:value] || cookie[:expires] + res.set_cookie_header = + Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie) + end + end + + # Allow subclasses to prepare_session for different Session classes + + def session_class + SessionHash + end + + # All thread safety and session retrieval procedures should occur here. + # Should return [session_id, session]. + # If nil is provided as the session id, generation of a new valid id + # should occur within. + + def find_session(env, sid) + raise '#find_session not implemented.' + end + + # All thread safety and session storage procedures should occur here. + # Must return the session id if the session was saved successfully, or + # false if the session could not be saved. + + def write_session(req, sid, session, options) + raise '#write_session not implemented.' + end + + # All thread safety and session destroy procedures should occur here. + # Should return a new session id or nil if options[:drop] + + def delete_session(req, sid, options) + raise '#delete_session not implemented' + end + end + + class PersistedSecure < Persisted + class SecureSessionHash < SessionHash + def [](key) + if key == "session_id" + load_for_read! + id.public_id if id + else + super + end + end + end + + def generate_sid(*) + public_id = super + + SessionId.new(public_id) + end + + def extract_session_id(*) + public_id = super + public_id && SessionId.new(public_id) + end + + private + + def session_class + SecureSessionHash + end + + def cookie_value(data) + data.cookie_value + end + end + + class ID < Persisted + def self.inherited(klass) + k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID } + unless k.instance_variable_defined?(:"@_rack_warned") + warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE + k.instance_variable_set(:"@_rack_warned", true) + end + super + end + + # All thread safety and session retrieval procedures should occur here. + # Should return [session_id, session]. + # If nil is provided as the session id, generation of a new valid id + # should occur within. + + def find_session(req, sid) + get_session req.env, sid + end + + # All thread safety and session storage procedures should occur here. + # Must return the session id if the session was saved successfully, or + # false if the session could not be saved. + + def write_session(req, sid, session, options) + set_session req.env, sid, session, options + end + + # All thread safety and session destroy procedures should occur here. + # Should return a new session id or nil if options[:drop] + + def delete_session(req, sid, options) + destroy_session req.env, sid, options + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/cookie.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/cookie.rb new file mode 100644 index 0000000000..bb541396f7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/cookie.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +require 'openssl' +require 'zlib' +require_relative 'abstract/id' +require 'json' +require 'base64' + +module Rack + + module Session + + # Rack::Session::Cookie provides simple cookie based session management. + # By default, the session is a Ruby Hash stored as base64 encoded marshalled + # data set to :key (default: rack.session). The object that encodes the + # session data is configurable and must respond to +encode+ and +decode+. + # Both methods must take a string and return a string. + # + # When the secret key is set, cookie data is checked for data integrity. + # The old secret key is also accepted and allows graceful secret rotation. + # + # Example: + # + # use Rack::Session::Cookie, :key => 'rack.session', + # :domain => 'foo.com', + # :path => '/', + # :expire_after => 2592000, + # :secret => 'change_me', + # :old_secret => 'also_change_me' + # + # All parameters are optional. + # + # Example of a cookie with no encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Rack::Session::Cookie::Identity.new + # }) + # + # Example of a cookie with custom encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Class.new { + # def encode(str); str.reverse; end + # def decode(str); str.reverse; end + # }.new + # }) + # + + class Cookie < Abstract::PersistedSecure + # Encode session cookies as Base64 + class Base64 + def encode(str) + ::Base64.strict_encode64(str) + end + + def decode(str) + ::Base64.decode64(str) + end + + # Encode session cookies as Marshaled Base64 data + class Marshal < Base64 + def encode(str) + super(::Marshal.dump(str)) + end + + def decode(str) + return unless str + ::Marshal.load(super(str)) rescue nil + end + end + + # N.B. Unlike other encoding methods, the contained objects must be a + # valid JSON composite type, either a Hash or an Array. + class JSON < Base64 + def encode(obj) + super(::JSON.dump(obj)) + end + + def decode(str) + return unless str + ::JSON.parse(super(str)) rescue nil + end + end + + class ZipJSON < Base64 + def encode(obj) + super(Zlib::Deflate.deflate(::JSON.dump(obj))) + end + + def decode(str) + return unless str + ::JSON.parse(Zlib::Inflate.inflate(super(str))) + rescue + nil + end + end + end + + # Use no encoding for session cookies + class Identity + def encode(str); str; end + def decode(str); str; end + end + + attr_reader :coder + + def initialize(app, options = {}) + @secrets = options.values_at(:secret, :old_secret).compact + @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1) + + warn <<-MSG unless secure?(options) + SECURITY WARNING: No secret option provided to Rack::Session::Cookie. + This poses a security threat. It is strongly recommended that you + provide a secret to prevent exploits that may be possible from crafted + cookies. This will not be supported in future versions of Rack, and + future versions will even invalidate your existing user cookies. + + Called from: #{caller[0]}. + MSG + @coder = options[:coder] ||= Base64::Marshal.new + super(app, options.merge!(cookie_only: true)) + end + + private + + def find_session(req, sid) + data = unpacked_cookie_data(req) + data = persistent_session_id!(data) + [data["session_id"], data] + end + + def extract_session_id(request) + unpacked_cookie_data(request)["session_id"] + end + + def unpacked_cookie_data(request) + request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k| + session_data = request.cookies[@key] + + if @secrets.size > 0 && session_data + session_data, _, digest = session_data.rpartition('--') + session_data = nil unless digest_match?(session_data, digest) + end + + request.set_header(k, coder.decode(session_data) || {}) + end + end + + def persistent_session_id!(data, sid = nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + + class SessionId < DelegateClass(Session::SessionId) + attr_reader :cookie_value + + def initialize(session_id, cookie_value) + super(session_id) + @cookie_value = cookie_value + end + end + + def write_session(req, session_id, session, options) + session = session.merge("session_id" => session_id) + session_data = coder.encode(session) + + if @secrets.first + session_data << "--#{generate_hmac(session_data, @secrets.first)}" + end + + if session_data.size > (4096 - @key.size) + req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.") + nil + else + SessionId.new(session_id, session_data) + end + end + + def delete_session(req, session_id, options) + # Nothing to do here, data is in the client + generate_sid unless options[:drop] + end + + def digest_match?(data, digest) + return unless data && digest + @secrets.any? do |secret| + Rack::Utils.secure_compare(digest, generate_hmac(data, secret)) + end + end + + def generate_hmac(data, secret) + OpenSSL::HMAC.hexdigest(@hmac.new, secret, data) + end + + def secure?(options) + @secrets.size >= 1 || + (options[:coder] && options[:let_coder_handle_secure_encoding]) + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/memcache.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/memcache.rb new file mode 100644 index 0000000000..6a60117407 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/memcache.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'rack/session/dalli' + +module Rack + module Session + warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead." + Memcache = Dalli + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/pool.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/pool.rb new file mode 100644 index 0000000000..4885605f5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/session/pool.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net +# THANKS: +# apeiros, for session id generation, expiry setup, and threadiness +# sergio, threadiness and bugreps + +require_relative 'abstract/id' +require 'thread' + +module Rack + module Session + # Rack::Session::Pool provides simple cookie based session management. + # Session data is stored in a hash held by @pool. + # In the context of a multithreaded environment, sessions being + # committed to the pool is done in a merging manner. + # + # The :drop option is available in rack.session.options if you wish to + # explicitly remove the session from the session cache. + # + # Example: + # myapp = MyRackApp.new + # sessioned = Rack::Session::Pool.new(myapp, + # :domain => 'foo.com', + # :expire_after => 2592000 + # ) + # Rack::Handler::WEBrick.run sessioned + + class Pool < Abstract::PersistedSecure + attr_reader :mutex, :pool + DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge drop: false + + def initialize(app, options = {}) + super + @pool = Hash.new + @mutex = Mutex.new + end + + def generate_sid + loop do + sid = super + break sid unless @pool.key? sid.private_id + end + end + + def find_session(req, sid) + with_lock(req) do + unless sid and session = get_session_with_fallback(sid) + sid, session = generate_sid, {} + @pool.store sid.private_id, session + end + [sid, session] + end + end + + def write_session(req, session_id, new_session, options) + with_lock(req) do + @pool.store session_id.private_id, new_session + session_id + end + end + + def delete_session(req, session_id, options) + with_lock(req) do + @pool.delete(session_id.public_id) + @pool.delete(session_id.private_id) + generate_sid unless options[:drop] + end + end + + def with_lock(req) + @mutex.lock if req.multithread? + yield + ensure + @mutex.unlock if @mutex.locked? + end + + private + + def get_session_with_fallback(sid) + @pool[sid.private_id] || @pool[sid.public_id] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_exceptions.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_exceptions.rb new file mode 100644 index 0000000000..07e6038806 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_exceptions.rb @@ -0,0 +1,390 @@ +# frozen_string_literal: true + +require 'ostruct' +require 'erb' + +module Rack + # Rack::ShowExceptions catches all exceptions raised from the app it + # wraps. It shows a useful backtrace with the sourcefile and + # clickable context, the whole Rack environment and the request + # data. + # + # Be careful when you use this on public-facing sites as it could + # reveal information helpful to attackers. + + class ShowExceptions + CONTEXT = 7 + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + rescue StandardError, LoadError, SyntaxError => e + exception_string = dump_exception(e) + + env[RACK_ERRORS].puts(exception_string) + env[RACK_ERRORS].flush + + if accepts_html?(env) + content_type = "text/html" + body = pretty(env, e) + else + content_type = "text/plain" + body = exception_string + end + + [ + 500, + { + CONTENT_TYPE => content_type, + CONTENT_LENGTH => body.bytesize.to_s, + }, + [body], + ] + end + + def prefers_plaintext?(env) + !accepts_html?(env) + end + + def accepts_html?(env) + Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html]) + end + private :accepts_html? + + def dump_exception(exception) + string = "#{exception.class}: #{exception.message}\n".dup + string << exception.backtrace.map { |l| "\t#{l}" }.join("\n") + string + end + + def pretty(env, exception) + req = Rack::Request.new(env) + + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + path = path = (req.script_name + req.path_info).squeeze("/") + + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + frames = frames = exception.backtrace.map { |line| + frame = OpenStruct.new + if line =~ /(.*?):(\d+)(:in `(.*)')?/ + frame.filename = $1 + frame.lineno = $2.to_i + frame.function = $4 + + begin + lineno = frame.lineno - 1 + lines = ::File.readlines(frame.filename) + frame.pre_context_lineno = [lineno - CONTEXT, 0].max + frame.pre_context = lines[frame.pre_context_lineno...lineno] + frame.context_line = lines[lineno].chomp + frame.post_context_lineno = [lineno + CONTEXT, lines.size].min + frame.post_context = lines[lineno + 1..frame.post_context_lineno] + rescue + end + + frame + else + nil + end + }.compact + + template.result(binding) + end + + def template + TEMPLATE + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + + # adapted from Django + # Copyright (c) Django Software Foundation and individual contributors. + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, '')) + + + + + + <%=h exception.class %> at <%=h path %> + + + + + +
+

<%=h exception.class %> at <%=h path %>

+

<%=h exception.message %>

+ + + + + + +
Ruby + <% if first = frames.first %> + <%=h first.filename %>: in <%=h first.function %>, line <%=h frames.first.lineno %> + <% else %> + unknown location + <% end %> +
Web<%=h req.request_method %> <%=h(req.host + path)%>
+ +

Jump to:

+ +
+ +
+

Traceback (innermost first)

+
    + <% frames.each { |frame| %> +
  • + <%=h frame.filename %>: in <%=h frame.function %> + + <% if frame.context_line %> +
    + <% if frame.pre_context %> +
      + <% frame.pre_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> + +
      +
    1. <%=h frame.context_line %>...
    + + <% if frame.post_context %> +
      + <% frame.post_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> +
    + <% end %> +
  • + <% } %> +
+
+ +
+

Request information

+ +

GET

+ <% if req.GET and not req.GET.empty? %> + + + + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No GET data.

+ <% end %> + +

POST

+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %> + + + + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

<%= no_post_data || "No POST data" %>.

+ <% end %> + + + + <% unless req.cookies.empty? %> + + + + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No cookie data.

+ <% end %> + +

Rack ENV

+ + + + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ +
+ +
+

+ You're seeing this error because you use Rack::ShowExceptions. +

+
+ + + + HTML + + # :startdoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_status.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_status.rb new file mode 100644 index 0000000000..a99bdaf33a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/show_status.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'erb' + +module Rack + # Rack::ShowStatus catches all empty responses and replaces them + # with a site explaining the error. + # + # Additional details can be put into rack.showstatus.detail + # and will be shown as HTML. If such details exist, the error page + # is always rendered, even if the reply was not empty. + + class ShowStatus + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash[headers] + empty = headers[CONTENT_LENGTH].to_i <= 0 + + # client or server error, or explicit message + if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL] + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + req = req = Rack::Request.new(env) + + message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s + + # This double assignment is to prevent an "unused variable" warning. + # Yes, it is dumb, but I don't like Ruby yelling at me. + detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message + + body = @template.result(binding) + size = body.bytesize + [status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]] + else + [status, headers, body] + end + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + +# adapted from Django +# Copyright (c) Django Software Foundation and individual contributors. +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +TEMPLATE = <<'HTML' + + + + + <%=h message %> at <%=h req.script_name + req.path_info %> + + + + +
+

<%=h message %> (<%= status.to_i %>)

+ + + + + + + + + +
Request Method:<%=h req.request_method %>
Request URL:<%=h req.url %>
+
+
+

<%=h detail %>

+
+ +
+

+ You're seeing this error because you use Rack::ShowStatus. +

+
+ + +HTML + + # :startdoc: + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/static.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/static.rb new file mode 100644 index 0000000000..8cb58b2fd7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/static.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +module Rack + + # The Rack::Static middleware intercepts requests for static files + # (javascript files, images, stylesheets, etc) based on the url prefixes or + # route mappings passed in the options, and serves them using a Rack::Files + # object. This allows a Rack stack to serve both static and dynamic content. + # + # Examples: + # + # Serve all requests beginning with /media from the "media" folder located + # in the current directory (ie media/*): + # + # use Rack::Static, :urls => ["/media"] + # + # Same as previous, but instead of returning 404 for missing files under + # /media, call the next middleware: + # + # use Rack::Static, :urls => ["/media"], :cascade => true + # + # Serve all requests beginning with /css or /images from the folder "public" + # in the current directory (ie public/css/* and public/images/*): + # + # use Rack::Static, :urls => ["/css", "/images"], :root => "public" + # + # Serve all requests to / with "index.html" from the folder "public" in the + # current directory (ie public/index.html): + # + # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public' + # + # Serve all requests normally from the folder "public" in the current + # directory but uses index.html as default route for "/" + # + # use Rack::Static, :urls => [""], :root => 'public', :index => + # 'index.html' + # + # Set custom HTTP Headers for based on rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # [rule, {header_field => content, header_field => content}], + # [rule, {header_field => content}] + # ] + # + # Rules for selecting files: + # + # 1) All files + # Provide the :all symbol + # :all => Matches every file + # + # 2) Folders + # Provide the folder path as a string + # '/folder' or '/folder/subfolder' => Matches files in a certain folder + # + # 3) File Extensions + # Provide the file extensions as an array + # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js + # + # 4) Regular Expressions / Regexp + # Provide a regular expression + # %r{\.(?:css|js)\z} => Matches files ending in .css or .js + # /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in + # the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg) + # Note: This Regexp is available as a shortcut, using the :fonts rule + # + # 5) Font Shortcut + # Provide the :fonts symbol + # :fonts => Uses the Regexp rule stated right above to match all common web font endings + # + # Rule Ordering: + # Rules are applied in the order that they are provided. + # List rather general rules above special ones. + # + # Complete example use case including HTTP header rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # # Cache all static files in public caches (e.g. Rack::Cache) + # # as well as in the browser + # [:all, {'Cache-Control' => 'public, max-age=31536000'}], + # + # # Provide web fonts with cross-origin access-control-headers + # # Firefox requires this when serving assets using a Content Delivery Network + # [:fonts, {'Access-Control-Allow-Origin' => '*'}] + # ] + # + class Static + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + def initialize(app, options = {}) + @app = app + @urls = options[:urls] || ["/favicon.ico"] + @index = options[:index] + @gzip = options[:gzip] + @cascade = options[:cascade] + root = options[:root] || Dir.pwd + + # HTTP Headers + @header_rules = options[:header_rules] || [] + # Allow for legacy :cache_control option while prioritizing global header_rules setting + @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control] + + @file_server = Rack::Files.new(root) + end + + def add_index_root?(path) + @index && route_file(path) && path.end_with?('/') + end + + def overwrite_file_path(path) + @urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path) + end + + def route_file(path) + @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 } + end + + def can_serve(path) + route_file(path) || overwrite_file_path(path) + end + + def call(env) + path = env[PATH_INFO] + + if can_serve(path) + if overwrite_file_path(path) + env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path]) + elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING']) + path = env[PATH_INFO] + env[PATH_INFO] += '.gz' + response = @file_server.call(env) + env[PATH_INFO] = path + + if response[0] == 404 + response = nil + elsif response[0] == 304 + # Do nothing, leave headers as is + else + if mime_type = Mime.mime_type(::File.extname(path), 'text/plain') + response[1][CONTENT_TYPE] = mime_type + end + response[1]['Content-Encoding'] = 'gzip' + end + end + + path = env[PATH_INFO] + response ||= @file_server.call(env) + + if @cascade && response[0] == 404 + return @app.call(env) + end + + headers = response[1] + applicable_rules(path).each do |rule, new_headers| + new_headers.each { |field, content| headers[field] = content } + end + + response + else + @app.call(env) + end + end + + # Convert HTTP header rules to HTTP headers + def applicable_rules(path) + @header_rules.find_all do |rule, new_headers| + case rule + when :all + true + when :fonts + /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path) + when String + path = ::Rack::Utils.unescape(path) + path.start_with?(rule) || path.start_with?('/' + rule) + when Array + /\.(#{rule.join('|')})\z/.match?(path) + when Regexp + rule.match?(path) + else + false + end + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/tempfile_reaper.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/tempfile_reaper.rb new file mode 100644 index 0000000000..9b04fefc24 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/tempfile_reaper.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Rack + + # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart) + # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter + # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ + class TempfileReaper + def initialize(app) + @app = app + end + + def call(env) + env[RACK_TEMPFILES] ||= [] + status, headers, body = @app.call(env) + body_proxy = BodyProxy.new(body) do + env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil? + end + [status, headers, body_proxy] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/urlmap.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/urlmap.rb new file mode 100644 index 0000000000..8462f92067 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/urlmap.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'set' + +module Rack + # Rack::URLMap takes a hash mapping urls or paths to apps, and + # dispatches accordingly. Support for HTTP/1.1 host names exists if + # the URLs start with http:// or https://. + # + # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part + # relevant for dispatch is in the SCRIPT_NAME, and the rest in the + # PATH_INFO. This should be taken care of when you need to + # reconstruct the URL in order to create links. + # + # URLMap dispatches in such a way that the longest paths are tried + # first, since they are most specific. + + class URLMap + def initialize(map = {}) + remap(map) + end + + def remap(map) + @known_hosts = Set[] + @mapping = map.map { |location, app| + if location =~ %r{\Ahttps?://(.*?)(/.*)} + host, location = $1, $2 + @known_hosts << host + else + host = nil + end + + unless location[0] == ?/ + raise ArgumentError, "paths need to start with /" + end + + location = location.chomp('/') + match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING) + + [host, location, match, app] + }.sort_by do |(host, location, _, _)| + [host ? -host.size : Float::INFINITY, -location.size] + end + end + + def call(env) + path = env[PATH_INFO] + script_name = env[SCRIPT_NAME] + http_host = env[HTTP_HOST] + server_name = env[SERVER_NAME] + server_port = env[SERVER_PORT] + + is_same_server = casecmp?(http_host, server_name) || + casecmp?(http_host, "#{server_name}:#{server_port}") + + is_host_known = @known_hosts.include? http_host + + @mapping.each do |host, location, match, app| + unless casecmp?(http_host, host) \ + || casecmp?(server_name, host) \ + || (!host && is_same_server) \ + || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host + next + end + + next unless m = match.match(path.to_s) + + rest = m[1] + next unless !rest || rest.empty? || rest[0] == ?/ + + env[SCRIPT_NAME] = (script_name + location) + env[PATH_INFO] = rest + + return app.call(env) + end + + [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]] + + ensure + env[PATH_INFO] = path + env[SCRIPT_NAME] = script_name + end + + private + def casecmp?(v1, v2) + # if both nil, or they're the same string + return true if v1 == v2 + + # if either are nil... (but they're not the same) + return false if v1.nil? + return false if v2.nil? + + # otherwise check they're not case-insensitive the same + v1.casecmp(v2).zero? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/utils.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/utils.rb new file mode 100644 index 0000000000..c8e61ea180 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/utils.rb @@ -0,0 +1,628 @@ +# -*- encoding: binary -*- +# frozen_string_literal: true + +require 'uri' +require 'fileutils' +require 'set' +require 'tempfile' +require 'time' + +require_relative 'query_parser' + +module Rack + # Rack::Utils contains a grab-bag of useful methods for writing web + # applications adopted from all kinds of Ruby libraries. + + module Utils + (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' + + ParameterTypeError = QueryParser::ParameterTypeError + InvalidParameterError = QueryParser::InvalidParameterError + DEFAULT_SEP = QueryParser::DEFAULT_SEP + COMMON_SEP = QueryParser::COMMON_SEP + KeySpaceConstrainedParams = QueryParser::Params + + RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] + RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] + + class << self + attr_accessor :default_query_parser + end + # The default number of bytes to allow parameter keys to take up. + # This helps prevent a rogue client from flooding a Request. + self.default_query_parser = QueryParser.make_default(65536, 100) + + module_function + + # URI escapes. (CGI style space to +) + def escape(s) + URI.encode_www_form_component(s) + end + + # Like URI escaping, but with %20 instead of +. Strictly speaking this is + # true URI escaping. + def escape_path(s) + ::URI::DEFAULT_PARSER.escape s + end + + # Unescapes the **path** component of a URI. See Rack::Utils.unescape for + # unescaping query parameters or form components. + def unescape_path(s) + ::URI::DEFAULT_PARSER.unescape s + end + + # Unescapes a URI escaped string with +encoding+. +encoding+ will be the + # target encoding of the string returned, and it defaults to UTF-8 + def unescape(s, encoding = Encoding::UTF_8) + URI.decode_www_form_component(s, encoding) + end + + class << self + attr_accessor :multipart_total_part_limit + + attr_accessor :multipart_file_limit + + # multipart_part_limit is the original name of multipart_file_limit, but + # the limit only counts parts with filenames. + alias multipart_part_limit multipart_file_limit + alias multipart_part_limit= multipart_file_limit= + end + + # The maximum number of file parts a request can contain. Accepting too + # many parts can lead to the server running out of file handles. + # Set to `0` for no limit. + self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i + + # The maximum total number of parts a request can contain. Accepting too + # many can lead to excessive memory use and parsing time. + self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i + + def self.param_depth_limit + default_query_parser.param_depth_limit + end + + def self.param_depth_limit=(v) + self.default_query_parser = self.default_query_parser.new_depth_limit(v) + end + + def self.key_space_limit + default_query_parser.key_space_limit + end + + def self.key_space_limit=(v) + self.default_query_parser = self.default_query_parser.new_space_limit(v) + end + + if defined?(Process::CLOCK_MONOTONIC) + def clock_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + else + # :nocov: + def clock_time + Time.now.to_f + end + # :nocov: + end + + def parse_query(qs, d = nil, &unescaper) + Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper) + end + + def parse_nested_query(qs, d = nil) + Rack::Utils.default_query_parser.parse_nested_query(qs, d) + end + + def build_query(params) + params.map { |k, v| + if v.class == Array + build_query(v.map { |x| [k, x] }) + else + v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}" + end + }.join("&") + end + + def build_nested_query(value, prefix = nil) + case value + when Array + value.map { |v| + build_nested_query(v, "#{prefix}[]") + }.join("&") + when Hash + value.map { |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) + }.delete_if(&:empty?).join('&') + when nil + prefix + else + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{escape(value)}" + end + end + + def q_values(q_value_header) + q_value_header.to_s.split(/\s*,\s*/).map do |part| + value, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if parameters && (md = /\Aq=([\d.]+)/.match(parameters)) + quality = md[1].to_f + end + [value, quality] + end + end + + # Return best accept value to use, based on the algorithm + # in RFC 2616 Section 14. If there are multiple best + # matches (same specificity and quality), the value returned + # is arbitrary. + def best_q_match(q_value_header, available_mimes) + values = q_values(q_value_header) + + matches = values.map do |req_mime, quality| + match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) } + next unless match + [match, quality] + end.compact.sort_by do |match, quality| + (match.split('/', 2).count('*') * -10) + quality + end.last + matches && matches.first + end + + ESCAPE_HTML = { + "&" => "&", + "<" => "<", + ">" => ">", + "'" => "'", + '"' => """, + "/" => "/" + } + + ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) + + # Escape ampersands, brackets and quotes to their HTML/XML entities. + def escape_html(string) + string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } + end + + def select_best_encoding(available_encodings, accept_encoding) + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + + expanded_accept_encoding = [] + + accept_encoding.each do |m, q| + preference = available_encodings.index(m) || available_encodings.size + + if m == "*" + (available_encodings - accept_encoding.map(&:first)).each do |m2| + expanded_accept_encoding << [m2, q, preference] + end + else + expanded_accept_encoding << [m, q, preference] + end + end + + encoding_candidates = expanded_accept_encoding + .sort_by { |_, q, p| [-q, p] } + .map!(&:first) + + unless encoding_candidates.include?("identity") + encoding_candidates.push("identity") + end + + expanded_accept_encoding.each do |m, q| + encoding_candidates.delete(m) if q == 0.0 + end + + (encoding_candidates & available_encodings)[0] + end + + def parse_cookies(env) + parse_cookies_header env[HTTP_COOKIE] + end + + def parse_cookies_header(header) + # According to RFC 6265: + # The syntax for cookie headers only supports semicolons + # User Agent -> Server == + # Cookie: SID=31d4d96e407aad42; lang=en-US + return {} unless header + header.split(/[;] */n).each_with_object({}) do |cookie, cookies| + next if cookie.empty? + key, value = cookie.split('=', 2) + cookies[key] = (unescape(value) rescue value) unless cookies.key?(key) + end + end + + def add_cookie_to_header(header, key, value) + case value + when Hash + domain = "; domain=#{value[:domain]}" if value[:domain] + path = "; path=#{value[:path]}" if value[:path] + max_age = "; max-age=#{value[:max_age]}" if value[:max_age] + expires = "; expires=#{value[:expires].httpdate}" if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only]) + same_site = + case value[:same_site] + when false, nil + nil + when :none, 'None', :None + '; SameSite=None' + when :lax, 'Lax', :Lax + '; SameSite=Lax' + when true, :strict, 'Strict', :Strict + '; SameSite=Strict' + else + raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}" + end + value = value[:value] + end + value = [value] unless Array === value + + cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \ + "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}" + + case header + when nil, '' + cookie + when String + [header, cookie].join("\n") + when Array + (header + [cookie]).join("\n") + else + raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}" + end + end + + def set_cookie_header!(header, key, value) + header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value) + nil + end + + def make_delete_cookie_header(header, key, value) + case header + when nil, '' + cookies = [] + when String + cookies = header.split("\n") + when Array + cookies = header + end + + key = escape(key) + domain = value[:domain] + path = value[:path] + regexp = if domain + if path + /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/ + else + /\A#{key}=.*domain=#{domain}(?:;|$)/ + end + elsif path + /\A#{key}=.*path=#{path}(?:;|$)/ + else + /\A#{key}=/ + end + + cookies.reject! { |cookie| regexp.match? cookie } + + cookies.join("\n") + end + + def delete_cookie_header!(header, key, value = {}) + header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value) + nil + end + + # Adds a cookie that will *remove* a cookie from the client. Hence the + # strange method name. + def add_remove_cookie_to_header(header, key, value = {}) + new_header = make_delete_cookie_header(header, key, value) + + add_cookie_to_header(new_header, key, + { value: '', path: nil, domain: nil, + max_age: '0', + expires: Time.at(0) }.merge(value)) + + end + + def rfc2822(time) + time.rfc2822 + end + + # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead + # of '% %b %Y'. + # It assumes that the time is in GMT to comply to the RFC 2109. + # + # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough + # that I'm certain someone implemented only that option. + # Do not use %a and %b from Time.strptime, it would use localized names for + # weekday and month. + # + def rfc2109(time) + wday = RFC2822_DAY_NAME[time.wday] + mon = RFC2822_MONTH_NAME[time.mon - 1] + time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT") + end + + # Parses the "Range:" header, if present, into an array of Range objects. + # Returns nil if the header is missing or syntactically invalid. + # Returns an empty array if none of the ranges are satisfiable. + def byte_ranges(env, size) + warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE + get_byte_ranges env['HTTP_RANGE'], size + end + + def get_byte_ranges(http_range, size) + # See + return nil unless http_range && http_range =~ /bytes=([^;]+)/ + ranges = [] + $1.split(/,\s*/).each do |range_spec| + return nil unless range_spec.include?('-') + range = range_spec.split('-') + r0, r1 = range[0], range[1] + if r0.nil? || r0.empty? + return nil if r1.nil? + # suffix-byte-range-spec, represents trailing suffix of file + r0 = size - r1.to_i + r0 = 0 if r0 < 0 + r1 = size - 1 + else + r0 = r0.to_i + if r1.nil? + r1 = size - 1 + else + r1 = r1.to_i + return nil if r1 < r0 # backwards range is syntactically invalid + r1 = size - 1 if r1 >= size + end + end + ranges << (r0..r1) if r0 <= r1 + end + ranges + end + + # Constant time string comparison. + # + # NOTE: the values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize + + l = a.unpack("C*") + + r, i = 0, -1 + b.each_byte { |v| r |= v ^ l[i += 1] } + r == 0 + end + + # Context allows the use of a compatible middleware at different points + # in a request handling stack. A compatible middleware must define + # #context which should take the arguments env and app. The first of which + # would be the request environment. The second of which would be the rack + # application that the request would be forwarded to. + class Context + attr_reader :for, :app + + def initialize(app_f, app_r) + raise 'running context does not respond to #context' unless app_f.respond_to? :context + @for, @app = app_f, app_r + end + + def call(env) + @for.context(env, @app) + end + + def recontext(app) + self.class.new(@for, app) + end + + def context(env, app = @app) + recontext(app).call(env) + end + end + + # A case-insensitive Hash that preserves the original case of a + # header when set. + # + # @api private + class HeaderHash < Hash # :nodoc: + def self.[](headers) + if headers.is_a?(HeaderHash) && !headers.frozen? + return headers + else + return self.new(headers) + end + end + + def initialize(hash = {}) + super() + @names = {} + hash.each { |k, v| self[k] = v } + end + + # on dup/clone, we need to duplicate @names hash + def initialize_copy(other) + super + @names = other.names.dup + end + + # on clear, we need to clear @names hash + def clear + super + @names.clear + end + + def each + super do |k, v| + yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v) + end + end + + def to_hash + hash = {} + each { |k, v| hash[k] = v } + hash + end + + def [](k) + super(k) || super(@names[k.downcase]) + end + + def []=(k, v) + canonical = k.downcase.freeze + delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary + @names[canonical] = k + super k, v + end + + def delete(k) + canonical = k.downcase + result = super @names.delete(canonical) + result + end + + def include?(k) + super || @names.include?(k.downcase) + end + + alias_method :has_key?, :include? + alias_method :member?, :include? + alias_method :key?, :include? + + def merge!(other) + other.each { |k, v| self[k] = v } + self + end + + def merge(other) + hash = dup + hash.merge! other + end + + def replace(other) + clear + other.each { |k, v| self[k] = v } + self + end + + protected + def names + @names + end + end + + # Every standard HTTP code mapped to the appropriate message. + # Generated with: + # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \ + # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \ + # puts "#{m[1]} => \x27#{m[2].strip}\x27,"' + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 421 => 'Misdirected Request', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Too Early', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable for Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 509 => 'Bandwidth Limit Exceeded', + 510 => 'Not Extended', + 511 => 'Network Authentication Required' + } + + # Responses with HTTP status codes that should not have an entity body + STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])] + + SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message| + [message.downcase.gsub(/\s|-|'/, '_').to_sym, code] + }.flatten] + + def status_code(status) + if status.is_a?(Symbol) + SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } + else + status.to_i + end + end + + PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact) + + def clean_path_info(path_info) + parts = path_info.split PATH_SEPS + + clean = [] + + parts.each do |part| + next if part.empty? || part == '.' + part == '..' ? clean.pop : clean << part + end + + clean_path = clean.join(::File::SEPARATOR) + clean_path.prepend("/") if parts.empty? || parts.first.empty? + clean_path + end + + NULL_BYTE = "\0" + + def valid_path?(path) + path.valid_encoding? && !path.include?(NULL_BYTE) + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/version.rb new file mode 100644 index 0000000000..0c38a46d61 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/lib/rack/version.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Copyright (C) 2007-2019 Leah Neukirchen +# +# Rack is freely distributable under the terms of an MIT-style license. +# See MIT-LICENSE or https://opensource.org/licenses/MIT. + +# The Rack main module, serving as a namespace for all core Rack +# modules and classes. +# +# All modules meant for use in your application are autoloaded here, +# so it should be enough just to require 'rack' in your code. + +module Rack + # The Rack protocol version number implemented. + VERSION = [1, 3] + + # Return the Rack protocol version as a dotted string. + def self.version + VERSION.join(".") + end + + RELEASE = "2.2.6.3" + + # Return the Rack release as a dotted string. + def self.release + RELEASE + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/rack.gemspec b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/rack.gemspec new file mode 100644 index 0000000000..246ed7c639 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-2.2.6.3/rack.gemspec @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'lib/rack/version' + +Gem::Specification.new do |s| + s.name = "rack" + s.version = Rack::RELEASE + s.platform = Gem::Platform::RUBY + s.summary = "A modular Ruby webserver interface." + s.license = "MIT" + + s.description = <<~EOF + Rack provides a minimal, modular and adaptable interface for developing + web applications in Ruby. By wrapping HTTP requests and responses in + the simplest way possible, it unifies and distills the API for web + servers, web frameworks, and software in between (the so-called + middleware) into a single method call. + EOF + + s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] + + %w(MIT-LICENSE rack.gemspec Rakefile README.rdoc SPEC.rdoc) + + s.bindir = 'bin' + s.executables << 'rackup' + s.require_path = 'lib' + s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.md', 'CONTRIBUTING.md'] + + s.author = 'Leah Neukirchen' + s.email = 'leah@vuxu.org' + + s.homepage = 'https://github.com/rack/rack' + + s.required_ruby_version = '>= 2.3.0' + + s.metadata = { + "bug_tracker_uri" => "https://github.com/rack/rack/issues", + "changelog_uri" => "https://github.com/rack/rack/blob/master/CHANGELOG.md", + "documentation_uri" => "https://rubydoc.info/github/rack/rack", + "source_code_uri" => "https://github.com/rack/rack" + } + + s.add_development_dependency 'minitest', "~> 5.0" + s.add_development_dependency 'minitest-sprint' + s.add_development_dependency 'minitest-global_expectations' + s.add_development_dependency 'rake' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/AUTHORS b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/AUTHORS new file mode 100644 index 0000000000..b6aaa92d5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/AUTHORS @@ -0,0 +1,27 @@ +Ryan Tomayko +Joshua Peek +Jeremy Kemper +mynyml +Cameron Walters +Jon Crosby +Matt Todd +Pirmin Kalberer +Rune Botten +Pratik Naik +Paul Sadauskas +Jeremy Evans +Michael Fellinger +Geoff Buesing +Nicolas Mérouze +Cyril Rohr +Harry Vangberg +James Rosen +Mislav Marohnić +Ben Brinckerhoff +Rafael Souza +Stephen Delano +TJ Holowaychuk +anupom syam +ichverstehe +kubicek +Jordi Polo diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/COPYING b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/COPYING new file mode 100644 index 0000000000..f3cfeea58c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/COPYING @@ -0,0 +1,19 @@ +The MIT License (MIT) +Copyright (c) 2008 The Committers + +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 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.0.0/gems/rack-contrib-2.3.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/README.md new file mode 100644 index 0000000000..2efc8e573a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/README.md @@ -0,0 +1,107 @@ +# Contributed Rack Middleware and Utilities + +This package includes a variety of add-on components for Rack, a Ruby web server +interface: + +* `Rack::Access` - Limits access based on IP address +* `Rack::Backstage` - Returns content of specified file if it exists, which makes it convenient for putting up maintenance pages. +* `Rack::BounceFavicon` - Returns a 404 for requests to `/favicon.ico` +* `Rack::CSSHTTPRequest` - Adds CSSHTTPRequest support by encoding responses as CSS for cross-site AJAX-style data loading +* `Rack::Callbacks` - Implements DSL for pure before/after filter like Middlewares. +* `Rack::Cookies` - Adds simple cookie jar hash to env +* `Rack::Deflect` - Helps protect against DoS attacks. +* `Rack::Evil` - Lets the rack application return a response to the client from any place. +* `Rack::HostMeta` - Configures `/host-meta` using a block +* `Rack::JSONBodyParser` - Adds JSON request bodies to the Rack parameters hash. +* `Rack::JSONP` - Adds JSON-P support by stripping out the callback param and padding the response with the appropriate callback format. +* `Rack::LazyConditionalGet` - Caches a global `Last-Modified` date and updates it each time there is a request that is not `GET` or `HEAD`. +* `Rack::LighttpdScriptNameFix` - Fixes how lighttpd sets the `SCRIPT_NAME` and `PATH_INFO` variables in certain configurations. +* `Rack::Locale` - Detects the client locale using the Accept-Language request header and sets a `rack.locale` variable in the environment. +* `Rack::MailExceptions` - Rescues exceptions raised from the app and sends a useful email with the exception, stacktrace, and contents of the environment. +* `Rack::NestedParams` - parses form params with subscripts (e.g., * "`post[title]=Hello`") into a nested/recursive Hash structure (based on Rails' implementation). +* `Rack::NotFound` - A default 404 application. +* `Rack::PostBodyContentTypeParser` - [Deprecated]: Adds support for JSON request bodies. The Rack parameter hash is populated by deserializing the JSON data provided in the request body when the Content-Type is application/json +* `Rack::Printout` - Prints the environment and the response per request +* `Rack::ProcTitle` - Displays request information in process title (`$0`) for monitoring/inspection with ps(1). +* `Rack::Profiler` - Uses ruby-prof to measure request time. +* `Rack::RelativeRedirect` - Transforms relative paths in redirects to absolute URLs. +* `Rack::ResponseCache` - Caches responses to requests without query strings to Disk or a user provided Ruby object. Similar to Rails' page caching. +* `Rack::ResponseHeaders` - Manipulates response headers object at runtime +* `Rack::Signals` - Installs signal handlers that are safely processed after a request +* `Rack::SimpleEndpoint` - Creates simple endpoints with routing rules, similar to Sinatra actions +* `Rack::StaticCache` - Modifies the response headers to facilitiate client and proxy caching for static files that minimizes http requests and improves overall load times for second time visitors. +* `Rack::TimeZone` - Detects the client's timezone using JavaScript and sets a variable in Rack's environment with the offset from UTC. +* `Rack::TryStatic` - Tries to match request to a static file + +### Use + +Git is the quickest way to the rack-contrib sources: + + git clone git://github.com/rack/rack-contrib.git + +Gems are available too: + + gem install rack-contrib + +Requiring `'rack/contrib'` will add autoloads to the Rack modules for all of the +components included. The following example shows what a simple rackup +(`config.ru`) file might look like: + +```ruby +require 'rack' +require 'rack/contrib' + +use Rack::Profiler if ENV['RACK_ENV'] == 'development' + +use Rack::ETag +use Rack::MailExceptions + +run theapp +``` + +#### Versioning + +This package is [semver compliant](https://semver.org); you should use a +pessimistic version constraint (`~>`) against the relevant `2.x` version of +this gem. + +This version of `rack-contrib` is only compatible with `rack` 2.x. If you +are using `rack` 1.x, you will need to use `rack-contrib` 1.x. A suitable +pessimistic version constraint (`~>`) is recommended. + + +### Testing + +To contribute to the project, begin by cloning the repo and installing the necessary gems: + + gem install json rack ruby-prof test-spec test-unit + +To run the entire test suite, run + + rake test + +To run a specific component's tests run + + specrb -Ilib:test -w test/spec_rack_thecomponent.rb + +This works on ruby 1.8.7 but has problems under ruby 1.9.x. + +TODO: instructions for 1.9.x and include bundler + +### Criteria for inclusion +The criteria for middleware being included in this project are roughly as follows: +* For patterns that are very common, provide a reference implementation so that other projects do not have to reinvent the wheel. +* For patterns that are very useful or interesting, provide a well-done implementation. +* The middleware fits in 1 code file and is relatively small. Currently all middleware in the project are < 150 LOC. +* The middleware doesn't have any dependencies other than rack and the ruby standard library. + +These criteria were introduced several years after the start of the project, so some of the included middleware may not meet all of them. In particular, several middleware have external dependencies. It is possible that in some future release of rack-contrib, middleware with external depencies will be removed from the project. + +When submitting code keep the above criteria in mind and also see the code +guidelines in CONTRIBUTING.md. + +### Links + +* rack-contrib on GitHub:: +* Rack:: +* Rack On GitHub:: diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib.rb new file mode 100644 index 0000000000..6d7ad96371 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rack' + +module Rack + module Contrib + def self.release + require "git-version-bump" + GVB.version + rescue LoadError + begin + Gem::Specification.find_by_name("rack-contrib").version.to_s + rescue Gem::LoadError + "0.0.0.1.ENOGEM" + end + end + end + + autoload :Access, "rack/contrib/access" + autoload :BounceFavicon, "rack/contrib/bounce_favicon" + autoload :Cookies, "rack/contrib/cookies" + autoload :CSSHTTPRequest, "rack/contrib/csshttprequest" + autoload :Deflect, "rack/contrib/deflect" + autoload :EnforceValidEncoding, "rack/contrib/enforce_valid_encoding" + autoload :ExpectationCascade, "rack/contrib/expectation_cascade" + autoload :HostMeta, "rack/contrib/host_meta" + autoload :GarbageCollector, "rack/contrib/garbagecollector" + autoload :JSONP, "rack/contrib/jsonp" + autoload :JSONBodyParser, "rack/contrib/json_body_parser" + autoload :LazyConditionalGet, "rack/contrib/lazy_conditional_get" + autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix" + autoload :Locale, "rack/contrib/locale" + autoload :MailExceptions, "rack/contrib/mailexceptions" + autoload :PostBodyContentTypeParser, "rack/contrib/post_body_content_type_parser" + autoload :ProcTitle, "rack/contrib/proctitle" + autoload :Profiler, "rack/contrib/profiler" + autoload :ResponseHeaders, "rack/contrib/response_headers" + autoload :Signals, "rack/contrib/signals" + autoload :SimpleEndpoint, "rack/contrib/simple_endpoint" + autoload :TimeZone, "rack/contrib/time_zone" + autoload :Evil, "rack/contrib/evil" + autoload :Callbacks, "rack/contrib/callbacks" + autoload :NestedParams, "rack/contrib/nested_params" + autoload :Config, "rack/contrib/config" + autoload :NotFound, "rack/contrib/not_found" + autoload :ResponseCache, "rack/contrib/response_cache" + autoload :RelativeRedirect, "rack/contrib/relative_redirect" + autoload :StaticCache, "rack/contrib/static_cache" + autoload :TryStatic, "rack/contrib/try_static" + autoload :Printout, "rack/contrib/printout" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/access.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/access.rb new file mode 100644 index 0000000000..bd1e48ae4d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/access.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "ipaddr" + +module Rack + + ## + # Rack middleware for limiting access based on IP address + # + # + # === Options: + # + # path => ipmasks ipmasks: Array of remote addresses which are allowed to access + # + # === Examples: + # + # use Rack::Access, '/backend' => [ '127.0.0.1', '192.168.1.0/24' ] + # + # + + class Access + + attr_reader :options + + def initialize(app, options = {}) + @app = app + mapping = options.empty? ? {"/" => ["127.0.0.1"]} : options + @mapping = remap(mapping) + end + + def remap(mapping) + mapping.map { |location, ipmasks| + if location =~ %r{\Ahttps?://(.*?)(/.*)} + host, location = $1, $2 + else + host = nil + end + + unless location[0] == ?/ + raise ArgumentError, "paths need to start with /" + end + location = location.chomp('/') + match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') + + ipmasks.collect! do |ipmask| + ipmask.is_a?(IPAddr) ? ipmask : IPAddr.new(ipmask) + end + [host, location, match, ipmasks] + }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first + end + + def call(env) + request = Request.new(env) + ipmasks = ipmasks_for_path(env) + return forbidden! unless ip_authorized?(request, ipmasks) + status, headers, body = @app.call(env) + [status, headers, body] + end + + def ipmasks_for_path(env) + path = env["PATH_INFO"].to_s + hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') + @mapping.each do |host, location, match, ipmasks| + next unless (hHost == host || sName == host \ + || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) + next unless path =~ match && rest = $1 + next unless rest.empty? || rest[0] == ?/ + + return ipmasks + end + nil + end + + def forbidden! + [403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []] + end + + def ip_authorized?(request, ipmasks) + return true if ipmasks.nil? + + ipmasks.any? do |ip_mask| + ip_mask.include?(IPAddr.new(request.ip)) + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/backstage.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/backstage.rb new file mode 100644 index 0000000000..86d61cde2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/backstage.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Rack + class Backstage + File = ::File + + def initialize(app, path) + @app = app + @file = File.expand_path(path) + end + + def call(env) + if File.exists?(@file) + content = File.read(@file) + length = content.bytesize.to_s + [503, {'Content-Type' => 'text/html', 'Content-Length' => length}, [content]] + else + @app.call(env) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/bounce_favicon.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/bounce_favicon.rb new file mode 100644 index 0000000000..7319c2c7bf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/bounce_favicon.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Rack + # Bounce those annoying favicon.ico requests + class BounceFavicon + def initialize(app) + @app = app + end + + def call(env) + if env["PATH_INFO"] == "/favicon.ico" + [404, {"Content-Type" => "text/html", "Content-Length" => "0"}, []] + else + @app.call(env) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/callbacks.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/callbacks.rb new file mode 100644 index 0000000000..f734d79cea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/callbacks.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Rack + class Callbacks + def initialize(&block) + @before = [] + @after = [] + instance_eval(&block) if block_given? + end + + def before(middleware, *args, &block) + if block_given? + @before << middleware.new(*args, &block) + else + @before << middleware.new(*args) + end + end + + def after(middleware, *args, &block) + if block_given? + @after << middleware.new(*args, &block) + else + @after << middleware.new(*args) + end + end + + def run(app) + @app = app + end + + def call(env) + @before.each {|c| c.call(env) } + + response = @app.call(env) + + @after.inject(response) {|r, c| c.call(r) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/common_cookies.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/common_cookies.rb new file mode 100644 index 0000000000..2223b0af4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/common_cookies.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Rack + # Rack middleware to use common cookies across domain and subdomains. + class CommonCookies + DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...|..\....)$/ + LOCALHOST_OR_IP_REGEXP = /^([\d.]+|localhost)$/ + PORT = /:\d+$/ + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + + host = env['HTTP_HOST'].sub PORT, '' + share_cookie(headers, host) + + [status, headers, body] + end + + private + + def domain(host) + host =~ DOMAIN_REGEXP + ".#{$1}.#{$2}" + end + + def share_cookie(headers, host) + headers['Set-Cookie'] &&= common_cookie(headers, host) if host !~ LOCALHOST_OR_IP_REGEXP + end + + def cookie(headers) + cookies = headers['Set-Cookie'] + cookies.is_a?(Array) ? cookies.join("\n") : cookies + end + + def common_cookie(headers, host) + cookie(headers).gsub(/; domain=[^;]*/, '').gsub(/$/, "; domain=#{domain(host)}") + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/config.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/config.rb new file mode 100644 index 0000000000..063f8ba295 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/config.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require 'rack' +require 'rack/config' diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/cookies.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/cookies.rb new file mode 100644 index 0000000000..b0ded0f7f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/cookies.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Rack + class Cookies + class CookieJar < Hash + def initialize(cookies) + @set_cookies = {} + @delete_cookies = {} + super() + update(cookies) + end + + def [](name) + super(name.to_s) + end + + def []=(key, options) + unless options.is_a?(Hash) + options = { :value => options } + end + + options[:path] ||= '/' + @set_cookies[key] = options + super(key.to_s, options[:value]) + end + + def delete(key, options = {}) + options[:path] ||= '/' + @delete_cookies[key] = options + super(key.to_s) + end + + def finish!(resp) + @set_cookies.each { |k, v| resp.set_cookie(k, v) } + @delete_cookies.each { |k, v| resp.delete_cookie(k, v) } + end + end + + def initialize(app) + @app = app + end + + def call(env) + req = Request.new(env) + env['rack.cookies'] = cookies = CookieJar.new(req.cookies) + status, headers, body = @app.call(env) + resp = Response.new(body, status, headers) + cookies.finish!(resp) + resp.to_a + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/csshttprequest.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/csshttprequest.rb new file mode 100644 index 0000000000..1d76f05d26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/csshttprequest.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'csshttprequest' + +module Rack + + # A Rack middleware for providing CSSHTTPRequest responses. + class CSSHTTPRequest + + def initialize(app) + @app = app + end + + # Proxies the request to the application then encodes the response with + # the CSSHTTPRequest encoder + def call(env) + status, headers, response = @app.call(env) + headers = Utils::HeaderHash.new(headers) + + if chr_request?(env) + encoded_response = encode(response) + modify_headers!(headers, encoded_response) + response = [encoded_response] + end + [status, headers, response] + end + + def chr_request?(env) + env['csshttprequest.chr'] ||= + !(/\.chr$/.match(env['PATH_INFO'])).nil? || Rack::Request.new(env).params['_format'] == 'chr' + end + + def encode(body) + ::CSSHTTPRequest.encode(body.to_enum.to_a.join) + end + + def modify_headers!(headers, encoded_response) + headers['Content-Length'] = encoded_response.bytesize.to_s + headers['Content-Type'] = 'text/css' + nil + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/deflect.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/deflect.rb new file mode 100644 index 0000000000..3786ec2532 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/deflect.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'thread' + +# TODO: optional stats +# TODO: performance +# TODO: clean up tests + +module Rack + + ## + # Rack middleware for protecting against Denial-of-service attacks + # http://en.wikipedia.org/wiki/Denial-of-service_attack. + # + # This middleware is designed for small deployments, which most likely + # are not utilizing load balancing from other software or hardware. Deflect + # current supports the following functionality: + # + # * Saturation prevention (small DoS attacks, or request abuse) + # * Blacklisting of remote addresses + # * Whitelisting of remote addresses + # * Logging + # + # === Options: + # + # :log When false logging will be bypassed, otherwise pass an object responding to #puts + # :log_format Alter the logging format + # :log_date_format Alter the logging date format + # :request_threshold Number of requests allowed within the set :interval. Defaults to 100 + # :interval Duration in seconds until the request counter is reset. Defaults to 5 + # :block_duration Duration in seconds that a remote address will be blocked. Defaults to 900 (15 minutes) + # :whitelist Array of remote addresses which bypass Deflect. NOTE: this does not block others + # :blacklist Array of remote addresses immediately considered malicious + # + # === Examples: + # + # use Rack::Deflect, :log => $stdout, :request_threshold => 20, :interval => 2, :block_duration => 60 + # + # CREDIT: TJ Holowaychuk + # + + class Deflect + + attr_reader :options + + def initialize app, options = {} + @mutex = Mutex.new + @remote_addr_map = {} + @app, @options = app, { + :log => false, + :log_format => 'deflect(%s): %s', + :log_date_format => '%m/%d/%Y', + :request_threshold => 100, + :interval => 5, + :block_duration => 900, + :whitelist => [], + :blacklist => [] + }.merge(options) + end + + def call env + return deflect! if deflect? env + status, headers, body = @app.call env + [status, headers, body] + end + + def deflect! + [403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []] + end + + def deflect? env + remote_addr = env['REMOTE_ADDR'] + return false if options[:whitelist].include? remote_addr + return true if options[:blacklist].include? remote_addr + sync { watch(remote_addr) } + end + + def log message + return unless options[:log] + options[:log].puts(options[:log_format] % [Time.now.strftime(options[:log_date_format]), message]) + end + + def sync &block + @mutex.synchronize(&block) + end + + def map(remote_addr) + @remote_addr_map[remote_addr] ||= { + :expires => Time.now + options[:interval], + :requests => 0 + } + end + + def watch(remote_addr) + increment_requests(remote_addr) + clear!(remote_addr) if watch_expired?(remote_addr) and not blocked?(remote_addr) + clear!(remote_addr) if blocked?(remote_addr) and block_expired?(remote_addr) + block!(remote_addr) if watching?(remote_addr) and exceeded_request_threshold?(remote_addr) + blocked?(remote_addr) + end + + def block!(remote_addr) + return if blocked?(remote_addr) + log "blocked #{remote_addr}" + map(remote_addr)[:block_expires] = Time.now + options[:block_duration] + end + + def blocked?(remote_addr) + map(remote_addr).has_key? :block_expires + end + + def block_expired?(remote_addr) + map(remote_addr)[:block_expires] < Time.now rescue false + end + + def watching?(remote_addr) + @remote_addr_map.has_key? remote_addr + end + + def clear!(remote_addr) + return unless watching?(remote_addr) + log "released #{remote_addr}" if blocked?(remote_addr) + @remote_addr_map.delete remote_addr + end + + def increment_requests(remote_addr) + map(remote_addr)[:requests] += 1 + end + + def exceeded_request_threshold?(remote_addr) + map(remote_addr)[:requests] > options[:request_threshold] + end + + def watch_expired?(remote_addr) + map(remote_addr)[:expires] <= Time.now + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/enforce_valid_encoding.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/enforce_valid_encoding.rb new file mode 100644 index 0000000000..19e54bcf5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/enforce_valid_encoding.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Rack + # Ensure that the path and query string presented to the application + # contains only valid characters. If the validation fails, then a + # 400 Bad Request response is returned immediately. + # + # Requires: Ruby 1.9 + # + class EnforceValidEncoding + def initialize app + @app = app + end + + def call env + full_path = (env.fetch('PATH_INFO', '') + env.fetch('QUERY_STRING', '')) + if full_path.force_encoding("US-ASCII").valid_encoding? && + Rack::Utils.unescape(full_path).valid_encoding? + @app.call env + else + [400, {'Content-Type'=>'text/plain'}, ['Bad Request']] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/evil.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/evil.rb new file mode 100644 index 0000000000..627581c962 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/evil.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Rack + class Evil + # Lets you return a response to the client immediately from anywhere ( M V or C ) in the code. + def initialize(app) + @app = app + end + + def call(env) + catch(:response) { @app.call(env) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/expectation_cascade.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/expectation_cascade.rb new file mode 100644 index 0000000000..8f678bf271 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/expectation_cascade.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Rack + class ExpectationCascade + Expect = "HTTP_EXPECT".freeze + ContinueExpectation = "100-continue".freeze + + ExpectationFailed = [417, {"Content-Type" => "text/html"}, []].freeze + NotFound = [404, {"Content-Type" => "text/html"}, []].freeze + + attr_reader :apps + + def initialize + @apps = [] + yield self if block_given? + end + + def call(env) + set_expectation = env[Expect] != ContinueExpectation + env[Expect] = ContinueExpectation if set_expectation + @apps.each do |app| + result = app.call(env) + return result unless result[0].to_i == 417 + end + set_expectation ? NotFound : ExpectationFailed + ensure + env.delete(Expect) if set_expectation + end + + def <<(app) + @apps << app + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/garbagecollector.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/garbagecollector.rb new file mode 100644 index 0000000000..2c349df1c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/garbagecollector.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Rack + # Forces garbage collection after each request. + class GarbageCollector + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + ensure + GC.start + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/host_meta.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/host_meta.rb new file mode 100644 index 0000000000..5a1cbbe0b4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/host_meta.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Rack + + # Rack middleware implementing the IETF draft: "Host Metadata for the Web" + # including support for Link-Pattern elements as described in the IETF draft: + # "Link-based Resource Descriptor Discovery." + # + # Usage: + # use Rack::HostMeta do + # link :uri => '/robots.txt', :rel => 'robots' + # link :uri => '/w3c/p3p.xml', :rel => 'privacy', :type => 'application/p3p.xml' + # link :pattern => '{uri};json_schema', :rel => 'describedby', :type => 'application/x-schema+json' + # end + # + # See also: + # http://tools.ietf.org/html/draft-nottingham-site-meta + # http://tools.ietf.org/html/draft-hammer-discovery + # + # TODO: + # Accept POST operations allowing downstream services to register themselves + # + class HostMeta + def initialize(app, &block) + @app = app + @lines = [] + instance_eval(&block) + @response = @lines.join("\n") + end + + def call(env) + if env['PATH_INFO'] == '/host-meta' + [200, {'Content-Type' => 'application/host-meta'}, [@response]] + else + @app.call(env) + end + end + + protected + + def link(config) + line = config[:uri] ? "Link: <#{config[:uri]}>;" : "Link-Pattern: <#{config[:pattern]}>;" + fragments = [] + fragments << "rel=\"#{config[:rel]}\"" if config[:rel] + fragments << "type=\"#{config[:type]}\"" if config[:type] + @lines << "#{line} #{fragments.join("; ")}" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/json_body_parser.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/json_body_parser.rb new file mode 100644 index 0000000000..77023a2b92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/json_body_parser.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'json' + +module Rack + # A Rack middleware that makes JSON-encoded request bodies available in the + # request.params hash. By default it parses POST, PATCH, and PUT requests + # whose media type is application/json. You can configure it to match + # any verb or media type via the :verbs and :media options. + # + # + # == Examples: + # + # === Parse POST and GET requests only + # use Rack::JSONBodyParser, verbs: ['POST', 'GET'] + # + # === Parse POST|PATCH|PUT requests whose Content-Type matches 'json' + # use Rack::JSONBodyParser, media: /json/ + # + # === Parse POST requests whose Content-Type is 'application/json' or 'application/vnd+json' + # use Rack::JSONBodyParser, verbs: ['POST'], media: ['application/json', 'application/vnd.api+json'] + # + class JSONBodyParser + CONTENT_TYPE_MATCHERS = { + String => lambda { |option, header| + Rack::MediaType.type(header) == option + }, + Array => lambda { |options, header| + media_type = Rack::MediaType.type(header) + options.any? { |opt| media_type == opt } + }, + Regexp => lambda { + if //.respond_to?(:match?) + # Use Ruby's fast regex matcher when available + ->(option, header) { option.match? header } + else + # Fall back to the slower matcher for rubies older than 2.4 + ->(option, header) { option.match header } + end + }.call(), + }.freeze + + DEFAULT_PARSER = ->(body) { JSON.parse(body, create_additions: false) } + + def initialize( + app, + verbs: %w[POST PATCH PUT], + media: 'application/json', + &block + ) + @app = app + @verbs, @media = verbs, media + @matcher = CONTENT_TYPE_MATCHERS.fetch(@media.class) + @parser = block || DEFAULT_PARSER + end + + def call(env) + begin + if @verbs.include?(env[Rack::REQUEST_METHOD]) && + @matcher.call(@media, env['CONTENT_TYPE']) + + update_form_hash_with_json_body(env) + end + rescue JSON::ParserError + body = { error: 'Failed to parse body as JSON' }.to_json + header = { 'Content-Type' => 'application/json' } + return Rack::Response.new(body, 400, header).finish + end + @app.call(env) + end + + private + + def update_form_hash_with_json_body(env) + body = env[Rack::RACK_INPUT] + return unless (body_content = body.read) && !body_content.empty? + + body.rewind # somebody might try to read this stream + env.update( + Rack::RACK_REQUEST_FORM_HASH => @parser.call(body_content), + Rack::RACK_REQUEST_FORM_INPUT => body + ) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/jsonp.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/jsonp.rb new file mode 100644 index 0000000000..f0800972ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/jsonp.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module Rack + + # A Rack middleware for providing JSON-P support. + # + # Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution. + # + class JSONP + include Rack::Utils + + VALID_CALLBACK = /\A[a-zA-Z_$](?:\.?[\w$])*\z/ + + # These hold the Unicode characters \u2028 and \u2029. + # + # They are defined in constants for Ruby 1.8 compatibility. + # + # In 1.8 + # "\u2028" # => "u2028" + # "\u2029" # => "u2029" + # In 1.9 + # "\342\200\250" # => "\u2028" + # "\342\200\251" # => "\u2029" + U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"] + + def initialize(app) + @app = app + end + + # Proxies the request to the application, stripping out the JSON-P callback + # method and padding the response with the appropriate callback format if + # the returned body is application/json + # + # Changes nothing if no callback param is specified. + # + def call(env) + request = Rack::Request.new(env) + + status, headers, response = @app.call(env) + + if STATUS_WITH_NO_ENTITY_BODY.include?(status) + return status, headers, response + end + + headers = HeaderHash.new(headers) + + if is_json?(headers) && has_callback?(request) + callback = request.params['callback'] + return bad_request unless valid_callback?(callback) + + response = pad(callback, response) + + # No longer json, its javascript! + headers['Content-Type'] = headers['Content-Type'].gsub('json', 'javascript') + + # Set new Content-Length, if it was set before we mutated the response body + if headers['Content-Length'] + length = response.map(&:bytesize).reduce(0, :+) + headers['Content-Length'] = length.to_s + end + end + + [status, headers, response] + end + + private + + def is_json?(headers) + headers.key?('Content-Type') && headers['Content-Type'].include?('application/json') + end + + def has_callback?(request) + request.params.include?('callback') and not request.params['callback'].to_s.empty? + end + + # See: + # http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names + # + # NOTE: Supports dots (.) since callbacks are often in objects: + # + def valid_callback?(callback) + callback =~ VALID_CALLBACK + end + + # Pads the response with the appropriate callback format according to the + # JSON-P spec/requirements. + # + # The Rack response spec indicates that it should be enumerable. The + # method of combining all of the data into a single string makes sense + # since JSON is returned as a full string. + # + def pad(callback, response) + body = response.to_enum.map do |s| + # U+2028 and U+2029 are allowed inside strings in JSON (as all literal + # Unicode characters) but JavaScript defines them as newline + # seperators. Because no literal newlines are allowed in a string, this + # causes a ParseError in the browser. We work around this issue by + # replacing them with the escaped version. This should be safe because + # according to the JSON spec, these characters are *only* valid inside + # a string and should therefore not be present any other places. + s.gsub(U2028, '\u2028').gsub(U2029, '\u2029') + end.join + + # https://github.com/rack/rack-contrib/issues/46 + response.close if response.respond_to?(:close) + + ["/**/#{callback}(#{body})"] + end + + def bad_request(body = "Bad Request") + [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ] + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lazy_conditional_get.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lazy_conditional_get.rb new file mode 100644 index 0000000000..d7c6a90a90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lazy_conditional_get.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module Rack + + ## + # This middleware is like Rack::ConditionalGet except that it does + # not have to go down the rack stack and build the resource to check + # the modification date or the ETag. + # + # Instead it makes the assumption that only non-reading requests can + # potentially change the content, meaning any request which is not + # GET or HEAD. Each time you make one of these request, the date is cached + # and any resource is considered identical until the next non-reading request. + # + # Basically you use it this way: + # + # ``` ruby + # use Rack::LazyConditionalGet + # ``` + # + # Although if you have multiple instances, it is better to use something like + # memcached. An argument can be passed to give the cache object. By default + # it is just a Hash. But it can take other objects, including objects which + # respond to `:get` and `:set`. Here is how you would use it with Dalli. + # + # ``` Ruby + # dalli_client = Dalli::Client.new + # use Rack::LazyConditionalGet, dalli_client + # ``` + # + # By default, the middleware only delegates to Rack::ConditionalGet to avoid + # any unwanted behaviour. You have to set a header to any resource which you + # want to be cached. And it will be cached until the next "potential update" + # of your site, that is whenever the next POST/PUT/PATCH/DELETE request + # is received. + # + # The header is `Rack-Lazy-Conditional-Get`. You have to set it to 'yes' + # if you want the middleware to set `Last-Modified` for you. + # + # Bear in mind that if you set `Last-Modified` as well, the middleware will + # not change it. + # + # Regarding the POST/PUT/PATCH/DELETE... requests, they will always reset your + # global modification date. But if you have one of these request and you + # know for sure that it does not modify the cached content, you can set the + # `Rack-Lazy-Conditional-Get` on response to `skip`. This will not update the + # global modification date. + # + # NOTE: This will not work properly in a multi-threaded environment with + # default cache object. A provided cache object should ensure thread-safety + # of the `get`/`set`/`[]`/`[]=` methods. + + class LazyConditionalGet + + KEY = 'global_last_modified'.freeze + READ_METHODS = ['GET','HEAD'] + + def self.new(*) + # This code automatically uses `Rack::ConditionalGet` before + # our middleware. It is equivalent to: + # + # ``` ruby + # use Rack::ConditionalGet + # use Rack::LazyConditionalGet + # ``` + ::Rack::ConditionalGet.new(super) + end + + def initialize app, cache={} + @app = app + @cache = cache + update_cache + end + + def call env + if reading? env and fresh? env + return [304, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []] + end + + status, headers, body = @app.call env + headers = Utils::HeaderHash.new(headers) + + update_cache unless (reading?(env) or skipping?(headers)) + headers['Last-Modified'] = cached_value if stampable? headers + [status, headers, body] + end + + private + + def fresh? env + env['HTTP_IF_MODIFIED_SINCE'] == cached_value + end + + def reading? env + READ_METHODS.include?(env['REQUEST_METHOD']) + end + + def skipping? headers + headers['Rack-Lazy-Conditional-Get'] == 'skip' + end + + def stampable? headers + !headers.has_key?('Last-Modified') and headers['Rack-Lazy-Conditional-Get'] == 'yes' + end + + def update_cache + stamp = Time.now.httpdate + if @cache.respond_to?(:set) + @cache.set(KEY,stamp) + else + @cache[KEY] = stamp + end + end + + def cached_value + @cache.respond_to?(:get) ? @cache.get(KEY) : @cache[KEY] + end + + end + +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lighttpd_script_name_fix.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lighttpd_script_name_fix.rb new file mode 100644 index 0000000000..fb2efbf76f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/lighttpd_script_name_fix.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Rack + # Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your + # FastCGI app at "/". This middleware fixes this issue. + + class LighttpdScriptNameFix + def initialize(app) + @app = app + end + + def call(env) + env["PATH_INFO"] = env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s + env["SCRIPT_NAME"] = "" + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/locale.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/locale.rb new file mode 100644 index 0000000000..ea1344e4ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/locale.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'i18n' + +module Rack + class Locale + def initialize(app) + @app = app + end + + def call(env) + locale_to_restore = I18n.locale + + locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"]) + locale ||= I18n.default_locale + + env['rack.locale'] = I18n.locale = locale.to_s + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + + unless headers['Content-Language'] + headers['Content-Language'] = locale.to_s + end + + [status, headers, body] + ensure + I18n.locale = locale_to_restore + end + + private + + # Accept-Language header is covered mainly by RFC 7231 + # https://tools.ietf.org/html/rfc7231 + # + # Related sections: + # + # * https://tools.ietf.org/html/rfc7231#section-5.3.1 + # * https://tools.ietf.org/html/rfc7231#section-5.3.5 + # * https://tools.ietf.org/html/rfc4647#section-3.4 + # + # There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616) + # + # Edge cases: + # + # * Value can be a comma separated list with optional whitespaces: + # Accept-Language: da, en-gb;q=0.8, en;q=0.7 + # + # * Quality value can contain optional whitespaces as well: + # Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2 + # + # * Quality prefix 'q=' can be in upper case (Q=) + # + # * Ignore case when match locale with I18n available locales + # + def user_preferred_locale(header) + return if header.nil? + + locales = header.gsub(/\s+/, '').split(",").map do |language_tag| + locale, quality = language_tag.split(/;q=/i) + quality = quality ? quality.to_f : 1.0 + [locale, quality] + end.reject do |(locale, quality)| + locale == '*' || quality == 0 + end.sort_by do |(_, quality)| + quality + end.map(&:first) + + return if locales.empty? + + if I18n.enforce_available_locales + locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } } + if locale + I18n.available_locales.find { |al| match?(al, locale) } + end + else + locales.last + end + end + + def match?(s1, s2) + s1.to_s.casecmp(s2.to_s) == 0 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/mailexceptions.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/mailexceptions.rb new file mode 100644 index 0000000000..b72e7bfceb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/mailexceptions.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require 'net/smtp' +require 'mail' +require 'erb' + +module Rack + # Catches all exceptions raised from the app it wraps and + # sends a useful email with the exception, stacktrace, and + # contents of the environment. + + # use smtp + # use Rack::MailExceptions do |mail| + # mail.to 'test@gmail.com' + # mail.smtp :address => 'mail.test.com', :user_name => 'test@test.com', :password => 'test' + # end + # use sendmail + # use Rack::MailExceptions do |mail| + # mail.to 'test@gmail.com' + # mail.smtp false + # end + + class MailExceptions + attr_reader :config + + def initialize(app) + @app = app + @config = { + :to => nil, + :from => ENV['USER'] || 'rack@localhost', + :subject => '[exception] %s', + :smtp => { + :address => 'localhost', + :domain => 'localhost', + :port => 25, + :authentication => :login, + :user_name => nil, + :password => nil + } + } + @template = ERB.new(TEMPLATE) + @test_mode = false + yield self if block_given? + end + + def call(env) + status, headers, body = + begin + @app.call(env) + rescue => boom + send_notification boom, env + raise + end + send_notification env['mail.exception'], env if env['mail.exception'] + [status, headers, body] + end + + %w[to from subject].each do |meth| + define_method(meth) { |value| @config[meth.to_sym] = value } + end + + def smtp(settings={}) + if settings + @config[:smtp].merge! settings + else + @config[:smtp] = nil + end + end + + def enable_test_mode + @test_mode = true + end + + def disable_test_mode + @test_mode = false + end + + private + def generate_mail(exception, env) + Mail.new({ + :from => config[:from], + :to => config[:to], + :subject => config[:subject] % [exception.to_s], + :body => @template.result(binding), + :charset => "UTF-8" + }) + end + + def send_notification(exception, env) + mail = generate_mail(exception, env) + if @test_mode + mail.delivery_method :test + elsif config[:smtp] + smtp = config[:smtp] + # for backward compatibility, replace the :server key with :address + address = smtp.delete :server + smtp[:address] = address if address + mail.delivery_method :smtp, smtp + else + mail.delivery_method :sendmail + end + mail.deliver! + env['mail.sent'] = true + mail + end + + def extract_body(env) + if io = env['rack.input'] + io.rewind + io.read + end + end + + TEMPLATE = (<<-'EMAIL').gsub(/^ {4}/, '') + A <%= exception.class.to_s %> occured: <%= exception.to_s %> + <% if body = extract_body(env) %> + + =================================================================== + Request Body: + =================================================================== + + <%= body.gsub(/^/, ' ') %> + <% end %> + + =================================================================== + Rack Environment: + =================================================================== + + PID: <%= $$ %> + PWD: <%= Dir.getwd %> + + <%= env.to_a. + sort{|a,b| a.first <=> b.first}. + map do |k,v| + if k == 'HTTP_AUTHORIZATION' and v =~ /^Basic / + v = 'Basic *filtered*' + end + "%-25s%p" % [k+':', v] + end. + join("\n ") %> + + <% if exception.respond_to?(:backtrace) %> + =================================================================== + Backtrace: + =================================================================== + + <%= exception.backtrace.join("\n ") %> + <% end %> + EMAIL + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/nested_params.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/nested_params.rb new file mode 100644 index 0000000000..d2644f13ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/nested_params.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rack/utils' + +module Rack + # Rack middleware for parsing POST/PUT body data into nested parameters + class NestedParams + + CONTENT_TYPE = 'CONTENT_TYPE'.freeze + POST_BODY = 'rack.input'.freeze + FORM_INPUT = 'rack.request.form_input'.freeze + FORM_HASH = 'rack.request.form_hash'.freeze + FORM_VARS = 'rack.request.form_vars'.freeze + + # supported content type + URL_ENCODED = 'application/x-www-form-urlencoded'.freeze + + def initialize(app) + @app = app + end + + def call(env) + if form_vars = env[FORM_VARS] + Rack::Utils.parse_nested_query(form_vars) + elsif env[CONTENT_TYPE] == URL_ENCODED + post_body = env[POST_BODY] + env[FORM_INPUT] = post_body + env[FORM_HASH] = Rack::Utils.parse_nested_query(post_body.read) + post_body.rewind + end + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/not_found.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/not_found.rb new file mode 100644 index 0000000000..11d24cd42d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/not_found.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Rack + # Rack::NotFound is a default endpoint. Optionally initialize with the + # path to a custom 404 page, to override the standard response body. + # + # Examples: + # + # Serve default 404 response: + # run Rack::NotFound.new + # + # Serve a custom 404 page: + # run Rack::NotFound.new('path/to/your/404.html') + + class NotFound + F = ::File + + def initialize(path = nil, content_type = 'text/html') + if path.nil? + @content = "Not found\n" + else + @content = F.read(path) + end + @length = @content.bytesize.to_s + + @content_type = content_type + end + + def call(env) + [404, {'Content-Type' => @content_type, 'Content-Length' => @length}, [@content]] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/post_body_content_type_parser.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/post_body_content_type_parser.rb new file mode 100644 index 0000000000..6a8b3c3ea4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/post_body_content_type_parser.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +begin + require 'json' +rescue LoadError => e + require 'json/pure' +end + +module Rack + + # DEPRECATED: JSONBodyParser is a drop-in replacement that is faster and more configurable. + # + # A Rack middleware for parsing POST/PUT body data when Content-Type is + # not one of the standard supported types, like application/json. + # + # === How to use the middleware + # + # Example of simple +config.ru+ file: + # + # require 'rack' + # require 'rack/contrib' + # + # use ::Rack::PostBodyContentTypeParser + # + # app = lambda do |env| + # request = Rack::Request.new(env) + # body = "Hello #{request.params['name']}" + # [200, {'Content-Type' => 'text/plain'}, [body]] + # end + # + # run app + # + # Example with passing block argument: + # + # use ::Rack::PostBodyContentTypeParser do |body| + # { 'params' => JSON.parse(body) } + # end + # + # Example with passing proc argument: + # + # parser = ->(body) { { 'params' => JSON.parse(body) } } + # use ::Rack::PostBodyContentTypeParser, &parser + # + # + # === Failed JSON parsing + # + # Returns "400 Bad request" response if invalid JSON document was sent: + # + # Raw HTTP response: + # + # HTTP/1.1 400 Bad Request + # Content-Type: text/plain + # Content-Length: 28 + # + # failed to parse body as JSON + # + class PostBodyContentTypeParser + + # Constants + # + CONTENT_TYPE = 'CONTENT_TYPE'.freeze + POST_BODY = 'rack.input'.freeze + FORM_INPUT = 'rack.request.form_input'.freeze + FORM_HASH = 'rack.request.form_hash'.freeze + + # Supported Content-Types + # + APPLICATION_JSON = 'application/json'.freeze + + def initialize(app, &block) + warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement." + @app = app + @block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) } + end + + def call(env) + if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0 + env[POST_BODY].rewind # somebody might try to read this stream + env.update(FORM_HASH => @block.call(body), FORM_INPUT => env[POST_BODY]) + end + @app.call(env) + rescue JSON::ParserError + bad_request('failed to parse body as JSON') + end + + def bad_request(body = 'Bad Request') + [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/printout.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/printout.rb new file mode 100644 index 0000000000..54f84f7289 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/printout.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Rack + #prints the environment and request for simple debugging + class Printout + def initialize(app) + @app = app + end + + def call(env) + # See http://rack.rubyforge.org/doc/SPEC.html for details + puts "**********\n Environment\n **************" + puts env.inspect + + puts "**********\n Response\n **************" + response = @app.call(env) + puts response.inspect + + puts "**********\n Response contents\n **************" + response[2].each do |chunk| + puts chunk + end + puts "\n \n" + return response + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/proctitle.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/proctitle.rb new file mode 100644 index 0000000000..a853e4136d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/proctitle.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Rack + # Middleware to update the process title ($0) with information about the + # current request. Based loosely on: + # - http://purefiction.net/mongrel_proctitle/ + # - http://github.com/grempe/thin-proctitle/tree/master + # + # NOTE: This will not work properly in a multi-threaded environment. + class ProcTitle + F = ::File + PROGNAME = F.basename($0) + + def initialize(app) + @app = app + @appname = Dir.pwd.split('/').reverse. + find { |name| name !~ /^(\d+|current|releases)$/ } || PROGNAME + @requests = 0 + $0 = "#{PROGNAME} [#{@appname}] init ..." + end + + def call(env) + host, port = env['SERVER_NAME'], env['SERVER_PORT'] + meth, path = env['REQUEST_METHOD'], env['PATH_INFO'] + @requests += 1 + $0 = "#{PROGNAME} [#{@appname}/#{port}] (#{@requests}) " \ + "#{meth} #{path}" + + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/profiler.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/profiler.rb new file mode 100644 index 0000000000..2dac848d83 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/profiler.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'ruby-prof' + +module Rack + # Set the profile=process_time query parameter to download a + # calltree profile of the request. + # + # Pass the :printer option to pick a different result format. Note that + # some printers (such as CallTreePrinter) have broken the + # `AbstractPrinter` API, and thus will not work. Bug reports to + # `ruby-prof`, please, not us. + # + # You can cause every request to be run multiple times by passing the + # `:times` option to the `use Rack::Profiler` call. You can also run a + # given request multiple times, by setting the `profiler_runs` query + # parameter in the request URL. + # + class Profiler + MODES = %w(process_time wall_time cpu_time + allocations memory gc_runs gc_time) + + DEFAULT_PRINTER = :call_stack + + CONTENT_TYPES = Hash.new('application/octet-stream').merge( + 'RubyProf::FlatPrinter' => 'text/plain', + 'RubyProf::GraphPrinter' => 'text/plain', + 'RubyProf::GraphHtmlPrinter' => 'text/html', + 'RubyProf::CallStackPrinter' => 'text/html') + + # Accepts a :printer => [:call_stack|:call_tree|:graph_html|:graph|:flat] + # option defaulting to :call_stack. + def initialize(app, options = {}) + @app = app + @printer = parse_printer(options[:printer] || DEFAULT_PRINTER) + @times = (options[:times] || 1).to_i + end + + def call(env) + if mode = profiling?(env) + profile(env, mode) + else + @app.call(env) + end + end + + private + def profiling?(env) + unless ::RubyProf.running? + request = Rack::Request.new(env.clone) + if mode = request.params.delete('profile') + if ::RubyProf.const_defined?(mode.upcase) + mode + else + env['rack.errors'].write "Invalid RubyProf measure_mode: " + + "#{mode}. Use one of #{MODES.to_a.join(', ')}" + false + end + end + end + end + + def profile(env, mode) + ::RubyProf.measure_mode = ::RubyProf.const_get(mode.upcase) + + GC.enable_stats if GC.respond_to?(:enable_stats) + request = Rack::Request.new(env.clone) + runs = (request.params['profiler_runs'] || @times).to_i + result = ::RubyProf.profile do + runs.times { @app.call(env) } + end + GC.disable_stats if GC.respond_to?(:disable_stats) + + [200, headers(@printer, env, mode), print(@printer, result)] + end + + def print(printer, result) + body = StringIO.new + printer.new(result).print(body, :min_percent => 0.01) + body.rewind + body + end + + def headers(printer, env, mode) + headers = { 'Content-Type' => CONTENT_TYPES[printer.name] } + if printer == ::RubyProf::CallTreePrinter + filename = ::File.basename(env['PATH_INFO']) + headers['Content-Disposition'] = + %(attachment; filename="#{filename}.#{mode}.tree") + end + headers + end + + def parse_printer(printer) + if printer.is_a?(Class) + printer + else + name = "#{camel_case(printer)}Printer" + if ::RubyProf.const_defined?(name) + ::RubyProf.const_get(name) + else + ::RubyProf::FlatPrinter + end + end + end + + def camel_case(word) + word.to_s.gsub(/(?:^|_)(.)/) { $1.upcase } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/relative_redirect.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/relative_redirect.rb new file mode 100644 index 0000000000..67c564b9c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/relative_redirect.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rack' + +# Rack::RelativeRedirect is a simple middleware that converts relative paths in +# redirects in absolute urls, so they conform to RFC2616. It allows the user to +# specify the absolute path to use (with a sensible default), and handles +# relative paths (those that don't start with a slash) as well. +class Rack::RelativeRedirect + SCHEME_MAP = {'http'=>'80', 'https'=>'443'} + # The default proc used if a block is not provided to .new + # Just uses the url scheme of the request and the server name. + DEFAULT_ABSOLUTE_PROC = proc do |env, res| + port = env['SERVER_PORT'] + scheme = env['rack.url_scheme'] + "#{scheme}://#{env['SERVER_NAME']}#{":#{port}" unless SCHEME_MAP[scheme] == port}" + end + + # Initialize a new RelativeRedirect object with the given arguments. Arguments: + # * app : The next middleware in the chain. This is always called. + # * &block : If provided, it is called with the environment and the response + # from the next middleware. It should return a string representing the scheme + # and server name (such as 'http://example.org'). + def initialize(app, &block) + @app = app + @absolute_proc = block || DEFAULT_ABSOLUTE_PROC + end + + # Call the next middleware with the environment. If the request was a + # redirect (response status 301, 302, or 303), and the location header does + # not start with an http or https url scheme, call the block provided by new + # and use that to make the Location header an absolute url. If the Location + # does not start with a slash, make location relative to the path requested. + def call(env) + status, headers, body = @app.call(env) + headers = Rack::Utils::HeaderHash.new(headers) + + if [301,302,303, 307,308].include?(status) and loc = headers['Location'] and !%r{\Ahttps?://}o.match(loc) + absolute = @absolute_proc.call(env, [status, headers, body]) + headers['Location'] = if %r{\A/}.match(loc) + "#{absolute}#{loc}" + else + "#{absolute}#{File.dirname(Rack::Utils.unescape(env['PATH_INFO']))}/#{loc}" + end + end + + [status, headers, body] + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_cache.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_cache.rb new file mode 100644 index 0000000000..2b966e761f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_cache.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'rack' + +# Rack::ResponseCache is a Rack middleware that caches responses for successful +# GET requests with no query string to disk or any ruby object that has an +# []= method (so it works with memcached). As with Rails' page caching, this +# middleware only writes to the cache -- it never reads. The logic of whether a +# cached response should be served is left either to your web server, via +# something like the try_files directive in nginx, or to your +# cache-reading middleware of choice, mounted before Rack::ResponseCache in the +# stack. +class Rack::ResponseCache + # The default proc used if a block is not provided to .new + # It unescapes the PATH_INFO of the environment, and makes sure that it doesn't + # include '..'. If the Content-Type of the response is text/(html|css|xml), + # return a path with the appropriate extension (.html, .css, or .xml). + # If the path ends with a / and the Content-Type is text/html, change the basename + # of the path to index.html. + DEFAULT_PATH_PROC = proc do |env, res| + path = Rack::Utils.unescape(env['PATH_INFO']) + headers = res[1] + content_type = headers['Content-Type'] + + if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(content_type) + type = match[1] + path = "#{path}.#{type}" unless /\.#{type}\z/.match(path) + path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html' + path + end + end + + # Initialize a new ResponseCache object with the given arguments. Arguments: + # * app : The next middleware in the chain. This is always called. + # * cache : The place to cache responses. If a string is provided, a disk + # cache is used, and all cached files will use this directory as the root directory. + # If anything other than a string is provided, it should respond to []=, which will + # be called with a path string and a body value (the 3rd element of the response). + # * &block : If provided, it is called with the environment and the response from the next middleware. + # It should return nil or false if the path should not be cached, and should return + # the pathname to use as a string if the result should be cached. + # If not provided, the DEFAULT_PATH_PROC is used. + def initialize(app, cache, &block) + @app = app + @cache = cache + @path_proc = block || DEFAULT_PATH_PROC + end + + # Call the next middleware with the environment. If the request was successful (response status 200), + # was a GET request, and had an empty query string, call the block set up in initialize to get the path. + # If the cache is a string, create any necessary middle directories, and cache the file in the appropriate + # subdirectory of cache. Otherwise, cache the body of the response as the value with the path as the key. + def call(env) + status, headers, body = @app.call(env) + headers = Rack::Utils::HeaderHash.new(headers) + + if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and status == 200 and path = @path_proc.call(env, [status, headers, body]) + if @cache.is_a?(String) + path = File.join(@cache, path) if @cache + FileUtils.mkdir_p(File.dirname(path)) + File.open(path, 'wb'){|f| body.each{|c| f.write(c)}} + else + @cache[path] = body + end + end + + [status, headers, body] + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_headers.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_headers.rb new file mode 100644 index 0000000000..e634c4bafd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/response_headers.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Rack + # Allows you to tap into the response headers. Yields a Rack::Utils::HeaderHash + # of current response headers to the block. Example: + # + # use Rack::ResponseHeaders do |headers| + # headers['X-Foo'] = 'bar' + # headers.delete('X-Baz') + # end + # + class ResponseHeaders + def initialize(app, &block) + @app = app + @block = block + end + + def call(env) + response = @app.call(env) + headers = Utils::HeaderHash.new(response[1]) + @block.call(headers) + response[1] = headers + response + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/route_exceptions.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/route_exceptions.rb new file mode 100644 index 0000000000..d71f019b80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/route_exceptions.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Rack + class RouteExceptions + ROUTES = [ + [Exception, '/error/internal'] + ] + + PATH_INFO = 'rack.route_exceptions.path_info'.freeze + EXCEPTION = 'rack.route_exceptions.exception'.freeze + RETURNED = 'rack.route_exceptions.returned'.freeze + + class << self + def route(exception, to) + ROUTES.delete_if{|k,v| k == exception } + ROUTES << [exception, to] + end + + alias []= route + end + + def initialize(app) + @app = app + end + + def call(env, try_again = true) + returned = @app.call(env) + rescue Exception => exception + raise(exception) unless try_again + + ROUTES.each do |klass, to| + next unless klass === exception + return route(to, env, returned, exception) + end + + raise(exception) + end + + def route(to, env, returned, exception) + env.merge!( + PATH_INFO => env['PATH_INFO'], + EXCEPTION => exception, + RETURNED => returned + ) + + env['PATH_INFO'] = to + + call(env, try_again = false) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/runtime.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/runtime.rb new file mode 100644 index 0000000000..052e17788f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/runtime.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require 'rack' +require 'rack/runtime' diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/signals.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/signals.rb new file mode 100644 index 0000000000..d61daf8e6a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/signals.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Rack + # Installs signal handlers that are safely processed after a request + # + # NOTE: This middleware should not be used in a threaded environment + # + # use Rack::Signals.new do + # trap 'INT', lambda { + # puts "Exiting now" + # exit + # } + # + # trap_when_ready 'USR1', lambda { + # puts "Exiting when ready" + # exit + # } + # end + class Signals + class BodyWithCallback + def initialize(body, callback) + @body, @callback = body, callback + end + + def each(&block) + @body.each(&block) + @callback.call + end + + def close + @body.close if @body.respond_to?(:close) + end + end + + def initialize(app, &block) + @app = app + @processing = false + @when_ready = nil + instance_eval(&block) + end + + def call(env) + begin + @processing, @when_ready = true, nil + status, headers, body = @app.call(env) + + if handler = @when_ready + body = BodyWithCallback.new(body, handler) + @when_ready = nil + end + ensure + @processing = false + end + + [status, headers, body] + end + + def trap_when_ready(signal, handler) + when_ready_handler = lambda { |signal| + if @processing + @when_ready = lambda { handler.call(signal) } + else + handler.call(signal) + end + } + trap(signal, when_ready_handler) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/simple_endpoint.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/simple_endpoint.rb new file mode 100644 index 0000000000..d58bc3e365 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/simple_endpoint.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Rack + # Create simple endpoints with routing rules, similar to Sinatra actions. + # + # Simplest example: + # + # use Rack::SimpleEndpoint, '/ping_monitor' do + # 'pong' + # end + # + # The value returned from the block will be written to the response body, so + # the above example will return "pong" when the request path is /ping_monitor. + # + # HTTP verb requirements can optionally be specified: + # + # use Rack::SimpleEndpoint, '/foo' => :get do + # 'only GET requests will match' + # end + # + # use Rack::SimpleEndpoint, '/bar' => [:get, :post] do + # 'only GET and POST requests will match' + # end + # + # Rack::Request and Rack::Response objects are yielded to block: + # + # use Rack::SimpleEndpoint, '/json' do |req, res| + # res['Content-Type'] = 'application/json' + # %({"foo": "#{req[:foo]}"}) + # end + # + # When path is a Regexp, match data object is yielded as third argument to block + # + # use Rack::SimpleEndpoint, %r{^/(john|paul|george|ringo)} do |req, res, match| + # "Hello, #{match[1]}" + # end + # + # A :pass symbol returned from block will not return a response; control will continue down the + # Rack stack: + # + # use Rack::SimpleEndpoint, '/api_key' do |req, res| + # req.env['myapp.user'].authorized? ? '12345' : :pass + # end + # + # # Unauthorized access to /api_key will be handled by PublicApp + # run PublicApp + class SimpleEndpoint + def initialize(app, arg, &block) + @app = app + @path = extract_path(arg) + @verbs = extract_verbs(arg) + @block = block + end + + def call(env) + match = match_path(env['PATH_INFO']) + if match && valid_method?(env['REQUEST_METHOD']) + req, res = Request.new(env), Response.new + body = @block.call(req, res, (match unless match == true)) + body == :pass ? @app.call(env) : (res.write(body); res.finish) + else + @app.call(env) + end + end + + private + def extract_path(arg) + arg.is_a?(Hash) ? arg.keys.first : arg + end + + def extract_verbs(arg) + arg.is_a?(Hash) ? [arg.values.first].flatten.map {|verb| verb.to_s.upcase} : [] + end + + def match_path(path) + @path.is_a?(Regexp) ? @path.match(path.to_s) : @path == path.to_s + end + + def valid_method?(method) + @verbs.empty? || @verbs.include?(method) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/static_cache.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/static_cache.rb new file mode 100644 index 0000000000..027af4f575 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/static_cache.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'time' + +module Rack + + # + # The Rack::StaticCache middleware automatically adds, removes and modifies + # stuffs in response headers to facilitiate client and proxy caching for static files + # that minimizes http requests and improves overall load times for second time visitors. + # + # Once a static content is stored in a client/proxy the only way to enforce the browser + # to fetch the latest content and ignore the cache is to rename the static file. + # + # Alternatively, we can add a version number into the URL to the content to bypass + # the caches. Rack::StaticCache by default handles version numbers in the filename. + # As an example, + # http://yoursite.com/images/test-1.0.0.png and http://yoursite.com/images/test-2.0.0.png + # both reffers to the same image file http://yoursite.com/images/test.png + # + # Another way to bypass the cache is adding the version number in a field-value pair in the + # URL query string. As an example, http://yoursite.com/images/test.png?v=1.0.0 + # In that case, set the option :versioning to false to avoid unnecessary regexp calculations. + # + # It's better to keep the current version number in some config file and use it in every static + # content's URL. So each time we modify our static contents, we just have to change the version + # number to enforce the browser to fetch the latest content. + # + # You can use Rack::Deflater along with Rack::StaticCache for further improvements in page loading time. + # + # If you'd like to use a non-standard version identifier in your URLs, you + # can set the regex to remove with the `:version_regex` option. If you + # want to capture something after the regex (such as file extensions), you + # should capture that as `\1`. All other captured subexpressions will be + # discarded. You may find the `?:` capture modifier helpful. + # + # Examples: + # use Rack::StaticCache, :urls => ["/images", "/css", "/js", "/documents*"], :root => "statics" + # will serve all requests beginning with /images, /css or /js from the + # directory "statics/images", "statics/css", "statics/js". + # All the files from these directories will have modified headers to enable client/proxy caching, + # except the files from the directory "documents". Append a * (star) at the end of the pattern + # if you want to disable caching for any pattern . In that case, plain static contents will be served with + # default headers. + # + # use Rack::StaticCache, :urls => ["/images"], :duration => 2, :versioning => false + # will serve all requests beginning with /images under the current directory (default for the option :root + # is current directory). All the contents served will have cache expiration duration set to 2 years in headers + # (default for :duration is 1 year), and StaticCache will not compute any versioning logics (default for + # :versioning is true) + # + + + class StaticCache + + def initialize(app, options={}) + @app = app + @urls = options[:urls] + @no_cache = {} + @urls.collect! do |url| + if url =~ /\*$/ + url_prefix = url.sub(/\*$/, '') + @no_cache[url_prefix] = 1 + url_prefix + else + url + end + end + root = options[:root] || Dir.pwd + @file_server = Rack::File.new(root) + @cache_duration = options[:duration] || 1 + @versioning_enabled = options.fetch(:versioning, true) + if @versioning_enabled + @version_regex = options.fetch(:version_regex, /-[\d.]+([.][a-zA-Z][\w]+)?$/) + end + @duration_in_seconds = self.duration_in_seconds + end + + def call(env) + path = env["PATH_INFO"] + url = @urls.detect{ |u| path.index(u) == 0 } + if url.nil? + @app.call(env) + else + if @versioning_enabled + path.sub!(@version_regex, '\1') + end + + status, headers, body = @file_server.call(env) + headers = Utils::HeaderHash.new(headers) + + if @no_cache[url].nil? + headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public" + headers['Expires'] = duration_in_words + end + headers['Date'] = Time.now.httpdate + [status, headers, body] + end + end + + def duration_in_words + (Time.now.utc + self.duration_in_seconds).httpdate + end + + def duration_in_seconds + (60 * 60 * 24 * 365 * @cache_duration).to_i + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/time_zone.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/time_zone.rb new file mode 100644 index 0000000000..33c624e121 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/time_zone.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Rack + class TimeZone + Javascript = <<-EOJ + function setTimezoneCookie() { + var offset = (new Date()).getTimezoneOffset() + var date = new Date(); + date.setTime(date.getTime()+3600000); + document.cookie = "utc_offset="+offset+"; expires="+date.toGMTString();+"; path=/"; + } +EOJ + + def initialize(app) + @app = app + end + + def call(env) + request = Rack::Request.new(env) + if utc_offset = request.cookies["utc_offset"] + env["rack.timezone.utc_offset"] = -(utc_offset.to_i * 60) + end + + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/try_static.rb b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/try_static.rb new file mode 100644 index 0000000000..c182eebdd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-contrib-2.3.0/lib/rack/contrib/try_static.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Rack + + # The Rack::TryStatic middleware delegates requests to Rack::Static middleware + # trying to match a static file + # + # Examples + # + # use Rack::TryStatic, + # :root => "public", # static files root dir + # :urls => %w[/], # match all requests + # :try => ['.html', 'index.html', '/index.html'] # try these postfixes sequentially + # + # uses same options as Rack::Static with extra :try option which is an array + # of postfixes to find desired file + + class TryStatic + + def initialize(app, options) + @app = app + @try = ['', *options[:try]] + @static = ::Rack::Static.new( + lambda { |_| [404, {}, []] }, + options) + end + + def call(env) + orig_path = env['PATH_INFO'] + found = nil + @try.each do |path| + resp = @static.call(env.merge!({'PATH_INFO' => orig_path + path})) + break if !(403..405).include?(resp[0]) && found = resp + end + found or @app.call(env.merge!('PATH_INFO' => orig_path)) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/.travis.yml b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/.travis.yml new file mode 100644 index 0000000000..acd10dab99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/.travis.yml @@ -0,0 +1,8 @@ +language: ruby +sudo: false +rvm: + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/CHANGELOG.md new file mode 100644 index 0000000000..e7fc50a1b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/CHANGELOG.md @@ -0,0 +1,88 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## 1.1.1 - 2019-12-29 +### Changed +- Allow //* to match // and / paths + +## 1.1.0 - 2019-11-19 +### Changed +- Use Rack::Utils.escape_path instead of Rack::Utils.escape +- Require Rack 2.0 for escape_path method +- Don't try to clean path if invalid. +- Return 400 (Bad Request) on preflights with invalid path + +## 1.0.6 - 2019-11-14 +### Changed +- Use Rack::Utils.escape to make compat with Rack 1.6.0 + +## 1.0.5 - 2019-11-14 +### Changed +- Update Gem spec to require rack >= 1.6.0 + +## 1.0.4 - 2019-11-13 +### Security +- Escape and resolve path before evaluating resource rules (thanks to Colby Morgan) + +## 1.0.3 - 2019-03-24 +### Changed +- Don't send 'Content-Type' header with pre-flight requests +- Allow ruby array for vary header config + +## 1.0.2 - 2017-10-22 +### Fixed +- Automatically allow simple headers when headers are set + +## 1.0.1 - 2017-07-18 +### Fixed +- Allow lambda origin configuration + +## 1.0.0 - 2017-07-15 +### Security +- Don't implicitly accept 'null' origins when 'file://' is specified +(https://github.com/cyu/rack-cors/pull/134) +- Ignore '' origins (https://github.com/cyu/rack-cors/issues/139) +- Default credentials option on resources to false +(https://github.com/cyu/rack-cors/issues/95) +- Don't allow credentials option to be true if '*' is specified is origin +(https://github.com/cyu/rack-cors/pull/142) +- Don't reflect Origin header when '*' is specified as origin +(https://github.com/cyu/rack-cors/pull/142) + +### Fixed +- Don't respond immediately on non-matching preflight requests instead of +sending them through the app (https://github.com/cyu/rack-cors/pull/106) + +## 0.4.1 - 2017-02-01 +### Fixed +- Return miss result in X-Rack-CORS instead of incorrectly returning +preflight-hit + +## 0.4.0 - 2015-04-15 +### Changed +- Don't set HTTP_ORIGIN with HTTP_X_ORIGIN if nil + +### Added +- Calculate vary headers for non-CORS resources +- Support custom vary headers for resource +- Support :if option for resource +- Support :any as a possible value for :methods option + +### Fixed +- Don't symbolize incoming HTTP request methods + +## 0.3.1 - 2014-12-27 +### Changed +- Changed the env key to rack.cors to avoid Rack::Lint warnings + +## 0.3.0 - 2014-10-19 +### Added +- Added support for defining a logger with a Proc +- Return a X-Rack-CORS header when in debug mode detailing how Rack::Cors +processed a request +- Added support for non HTTP/HTTPS origins when just a domain is specified + +### Changed +- Changed the log level of the fallback logger to DEBUG +- Print warning when attempting to use :any as an allowed method +- Treat incoming `Origin: null` headers as file:// diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Gemfile new file mode 100644 index 0000000000..9b9a01c2d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in rack-cors.gemspec +gemspec + +gem 'pry-byebug', '~> 3.6.0' diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/LICENSE.txt new file mode 100644 index 0000000000..1aff8f8170 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 Calvin Yu + +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.0.0/gems/rack-cors-1.1.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/README.md new file mode 100644 index 0000000000..b8e0b1d4ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/README.md @@ -0,0 +1,139 @@ +# Rack CORS Middleware [![Build Status](https://travis-ci.org/cyu/rack-cors.svg?branch=master)](https://travis-ci.org/cyu/rack-cors) + +`Rack::Cors` provides support for Cross-Origin Resource Sharing (CORS) for Rack compatible web applications. + +The [CORS spec](http://www.w3.org/TR/cors/) allows web applications to make cross domain AJAX calls without using workarounds such as JSONP. See [Cross-domain Ajax with Cross-Origin Resource Sharing](http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/) + +## Installation + +Install the gem: + +`gem install rack-cors` + +Or in your Gemfile: + +```ruby +gem 'rack-cors' +``` + + +## Configuration + +### Rails Configuration +Put something like the code below in `config/application.rb` of your Rails application. For example, this will allow GET, POST or OPTIONS requests from any origin on any resource. + +```ruby +module YourApp + class Application < Rails::Application + # ... + + # Rails 5 + + config.middleware.insert_before 0, Rack::Cors do + allow do + origins '*' + resource '*', headers: :any, methods: [:get, :post, :options] + end + end + + # Rails 3/4 + + config.middleware.insert_before 0, "Rack::Cors" do + allow do + origins '*' + resource '*', headers: :any, methods: [:get, :post, :options] + end + end + end +end +``` + +We use `insert_before` to make sure `Rack::Cors` runs at the beginning of the stack to make sure it isn't interfered with by other middleware (see `Rack::Cache` note in **Common Gotchas** section). Check out the [rails 4 example](https://github.com/cyu/rack-cors/tree/master/examples/rails4) and [rails 3 example](https://github.com/cyu/rack-cors/tree/master/examples/rails3). + +See The [Rails Guide to Rack](http://guides.rubyonrails.org/rails_on_rack.html) for more details on rack middlewares or watch the [railscast](http://railscasts.com/episodes/151-rack-middleware). + +### Rack Configuration + +NOTE: If you're running Rails, updating in `config/application.rb` should be enough. There is no need to update `config.ru` as well. + +In `config.ru`, configure `Rack::Cors` by passing a block to the `use` command: + +```ruby +use Rack::Cors do + allow do + origins 'localhost:3000', '127.0.0.1:3000', + /\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/ + # regular expressions can be used here + + resource '/file/list_all/', :headers => 'x-domain-token' + resource '/file/at/*', + methods: [:get, :post, :delete, :put, :patch, :options, :head], + headers: 'x-domain-token', + expose: ['Some-Custom-Response-Header'], + max_age: 600 + # headers to expose + end + + allow do + origins '*' + resource '/public/*', headers: :any, methods: :get + + # Only allow a request for a specific host + resource '/api/v1/*', + headers: :any, + methods: :get, + if: proc { |env| env['HTTP_HOST'] == 'api.example.com' } + end +end +``` + +### Configuration Reference + +#### Middleware Options +* **debug** (boolean): Enables debug logging and `X-Rack-CORS` HTTP headers for debugging. +* **logger** (Object or Proc): Specify the logger to log to. If a proc is provided, it will be called when a logger is needed. This is helpful in cases where the logger is initialized after `Rack::Cors` is initially configured, like `Rails.logger`. + +#### Origin +Origins can be specified as a string, a regular expression, or as '\*' to allow all origins. + +**\*SECURITY NOTE:** Be careful when using regular expressions to not accidentally be too inclusive. For example, the expression `/https:\/\/example\.com/` will match the domain *example.com.randomdomainname.co.uk*. It is recommended that any regular expression be enclosed with start & end string anchors (`\A\z`). + +Additionally, origins can be specified dynamically via a block of the following form: +```ruby + origins { |source, env| true || false } +``` + +A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`). To include all of a directory's files and the files in its subdirectories, use this form: `/assets/**/*`. A resource can take the following options: + +* **methods** (string or array or `:any`): The HTTP methods allowed for the resource. +* **headers** (string or array or `:any`): The HTTP headers that will be allowed in the CORS resource request. Use `:any` to allow for any headers in the actual request. +* **expose** (string or array): The HTTP headers in the resource response can be exposed to the client. +* **credentials** (boolean, default: `false`): Sets the `Access-Control-Allow-Credentials` response header. **Note:** If a wildcard (`*`) origin is specified, this option cannot be set to `true`. Read this [security article](http://web-in-security.blogspot.de/2017/07/cors-misconfigurations-on-large-scale.html) for more information. +* **max_age** (number): Sets the `Access-Control-Max-Age` response header. +* **if** (Proc): If the result of the proc is true, will process the request as a valid CORS request. +* **vary** (string or array): A list of HTTP headers to add to the 'Vary' header. + + +## Common Gotchas + +Incorrect positioning of `Rack::Cors` in the middleware stack can produce unexpected results. The Rails example above will put it above all middleware which should cover most issues. + +Here are some common cases: + +* **Serving static files.** Insert this middleware before `ActionDispatch::Static` so that static files are served with the proper CORS headers (see note below for a caveat). **NOTE:** that this might not work in production environments as static files are usually served from the web server (Nginx, Apache) and not the Rails container. + +* **Caching in the middleware.** Insert this middleware before `Rack::Cache` so that the proper CORS headers are written and not cached ones. + +* **Authentication via Warden** Warden will return immediately if a resource that requires authentication is accessed without authentication. If `Warden::Manager`is in the stack before `Rack::Cors`, it will return without the correct CORS headers being applied, resulting in a failed CORS request. Be sure to insert this middleware before 'Warden::Manager`. + +To determine where to put the CORS middleware in the Rack stack, run the following command: + +```bash +bundle exec rake middleware +``` + +In many cases, the Rack stack will be different running in production environments. For example, the `ActionDispatch::Static` middleware will not be part of the stack if `config.serve_static_assets = false`. You can run the following command to see what your middleware stack looks like in production: + +```bash +RAILS_ENV=production bundle exec rake middleware +``` diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Rakefile new file mode 100644 index 0000000000..4bd7eed255 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/Rakefile @@ -0,0 +1,21 @@ +require "bundler/gem_tasks" + +require 'rake/testtask' +Rake::TestTask.new(:test) do |test| + test.libs << 'lib' << 'test' + test.pattern = 'test/**/*_test.rb' + test.verbose = true +end + +task :default => :test + +require 'rdoc/task' +Rake::RDocTask.new do |rdoc| + version = File.exist?('VERSION') ? File.read('VERSION') : "" + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "rack-cors #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors.rb b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors.rb new file mode 100644 index 0000000000..dcd3b2567d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors.rb @@ -0,0 +1,472 @@ +require 'logger' + +module Rack + class Cors + HTTP_ORIGIN = 'HTTP_ORIGIN'.freeze + HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'.freeze + + HTTP_ACCESS_CONTROL_REQUEST_METHOD = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'.freeze + HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'.freeze + + PATH_INFO = 'PATH_INFO'.freeze + REQUEST_METHOD = 'REQUEST_METHOD'.freeze + + RACK_LOGGER = 'rack.logger'.freeze + RACK_CORS = + # retaining the old key for backwards compatibility + ENV_KEY = 'rack.cors'.freeze + + OPTIONS = 'OPTIONS'.freeze + VARY = 'Vary'.freeze + + DEFAULT_VARY_HEADERS = ['Origin'].freeze + + # All CORS routes need to accept CORS simple headers at all times + # {https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers} + CORS_SIMPLE_HEADERS = ['accept', 'accept-language', 'content-language', 'content-type'].freeze + + def initialize(app, opts={}, &block) + @app = app + @debug_mode = !!opts[:debug] + @logger = @logger_proc = nil + + if logger = opts[:logger] + if logger.respond_to? :call + @logger_proc = opts[:logger] + else + @logger = logger + end + end + + if block_given? + if block.arity == 1 + block.call(self) + else + instance_eval(&block) + end + end + end + + def debug? + @debug_mode + end + + def allow(&block) + all_resources << (resources = Resources.new) + + if block.arity == 1 + block.call(resources) + else + resources.instance_eval(&block) + end + end + + def call(env) + env[HTTP_ORIGIN] ||= env[HTTP_X_ORIGIN] if env[HTTP_X_ORIGIN] + + path = evaluate_path(env) + + add_headers = nil + if env[HTTP_ORIGIN] + debug(env) do + [ 'Incoming Headers:', + " Origin: #{env[HTTP_ORIGIN]}", + " Path-Info: #{path}", + " Access-Control-Request-Method: #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]}", + " Access-Control-Request-Headers: #{env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]}" + ].join("\n") + end + + if env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD] + return [400, {}, []] unless Rack::Utils.valid_path?(path) + headers = process_preflight(env, path) + debug(env) do + "Preflight Headers:\n" + + headers.collect{|kv| " #{kv.join(': ')}"}.join("\n") + end + return [200, headers, []] + else + add_headers = process_cors(env, path) + end + else + Result.miss(env, Result::MISS_NO_ORIGIN) + end + + # This call must be done BEFORE calling the app because for some reason + # env[PATH_INFO] gets changed after that and it won't match. (At least + # in rails 4.1.6) + vary_resource = resource_for_path(path) + + status, headers, body = @app.call env + + if add_headers + headers = add_headers.merge(headers) + debug(env) do + add_headers.each_pair do |key, value| + if headers.has_key?(key) + headers["X-Rack-CORS-Original-#{key}"] = value + end + end + end + end + + # Vary header should ALWAYS mention Origin if there's ANY chance for the + # response to be different depending on the Origin header value. + # Better explained here: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/ + if vary_resource + vary = headers[VARY] + cors_vary_headers = if vary_resource.vary_headers && vary_resource.vary_headers.any? + vary_resource.vary_headers + else + DEFAULT_VARY_HEADERS + end + headers[VARY] = ((vary ? ([vary].flatten.map { |v| v.split(/,\s*/) }.flatten) : []) + cors_vary_headers).uniq.join(', ') + end + + if debug? && result = env[RACK_CORS] + result.append_header(headers) + end + + [status, headers, body] + end + + protected + def debug(env, message = nil, &block) + (@logger || select_logger(env)).debug(message, &block) if debug? + end + + def select_logger(env) + @logger = if @logger_proc + logger_proc = @logger_proc + @logger_proc = nil + logger_proc.call + + elsif defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger + Rails.logger + + elsif env[RACK_LOGGER] + env[RACK_LOGGER] + + else + ::Logger.new(STDOUT).tap { |logger| logger.level = ::Logger::Severity::DEBUG } + end + end + + def evaluate_path(env) + path = env[PATH_INFO] + + if path + path = Rack::Utils.unescape_path(path) + + if Rack::Utils.valid_path?(path) + path = Rack::Utils.clean_path_info(path) + end + end + + path + end + + def all_resources + @all_resources ||= [] + end + + def process_preflight(env, path) + result = Result.preflight(env) + + resource, error = match_resource(path, env) + unless resource + result.miss(error) + return {} + end + + return resource.process_preflight(env, result) + end + + def process_cors(env, path) + resource, error = match_resource(path, env) + if resource + Result.hit(env) + cors = resource.to_headers(env) + cors + + else + Result.miss(env, error) + nil + end + end + + def resource_for_path(path_info) + all_resources.each do |r| + if found = r.resource_for_path(path_info) + return found + end + end + nil + end + + def match_resource(path, env) + origin = env[HTTP_ORIGIN] + + origin_matched = false + all_resources.each do |r| + if r.allow_origin?(origin, env) + origin_matched = true + if found = r.match_resource(path, env) + return [found, nil] + end + end + end + + [nil, origin_matched ? Result::MISS_NO_PATH : Result::MISS_NO_ORIGIN] + end + + class Result + HEADER_KEY = 'X-Rack-CORS'.freeze + + MISS_NO_ORIGIN = 'no-origin'.freeze + MISS_NO_PATH = 'no-path'.freeze + + MISS_NO_METHOD = 'no-method'.freeze + MISS_DENY_METHOD = 'deny-method'.freeze + MISS_DENY_HEADER = 'deny-header'.freeze + + attr_accessor :preflight, :hit, :miss_reason + + def hit? + !!hit + end + + def preflight? + !!preflight + end + + def miss(reason) + self.hit = false + self.miss_reason = reason + end + + def self.hit(env) + r = Result.new + r.preflight = false + r.hit = true + env[RACK_CORS] = r + end + + def self.miss(env, reason) + r = Result.new + r.preflight = false + r.hit = false + r.miss_reason = reason + env[RACK_CORS] = r + end + + def self.preflight(env) + r = Result.new + r.preflight = true + env[RACK_CORS] = r + end + + + def append_header(headers) + headers[HEADER_KEY] = if hit? + preflight? ? 'preflight-hit' : 'hit' + else + [ + (preflight? ? 'preflight-miss' : 'miss'), + miss_reason + ].join('; ') + end + end + end + + class Resources + + attr_reader :resources + + def initialize + @origins = [] + @resources = [] + @public_resources = false + end + + def origins(*args, &blk) + @origins = args.flatten.reject{ |s| s == '' }.map do |n| + case n + when Proc, + Regexp, + /^https?:\/\//, + 'file://' then n + when '*' then @public_resources = true; n + else Regexp.compile("^[a-z][a-z0-9.+-]*:\\\/\\\/#{Regexp.quote(n)}$") + end + end.flatten + @origins.push(blk) if blk + end + + def resource(path, opts={}) + @resources << Resource.new(public_resources?, path, opts) + end + + def public_resources? + @public_resources + end + + def allow_origin?(source,env = {}) + return true if public_resources? + + return !! @origins.detect do |origin| + if origin.is_a?(Proc) + origin.call(source,env) + else + origin === source + end + end + end + + def match_resource(path, env) + @resources.detect { |r| r.match?(path, env) } + end + + def resource_for_path(path) + @resources.detect { |r| r.matches_path?(path) } + end + + end + + class Resource + class CorsMisconfigurationError < StandardError + def message + "Allowing credentials for wildcard origins is insecure."\ + " Please specify more restrictive origins or set 'credentials' to false in your CORS configuration." + end + end + + attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers + + def initialize(public_resource, path, opts={}) + raise CorsMisconfigurationError if public_resource && opts[:credentials] == true + + self.path = path + self.credentials = public_resource ? false : (opts[:credentials] == true) + self.max_age = opts[:max_age] || 7200 + self.pattern = compile(path) + self.if_proc = opts[:if] + self.vary_headers = opts[:vary] && [opts[:vary]].flatten + @public_resource = public_resource + + self.headers = case opts[:headers] + when :any then :any + when nil then nil + else + [opts[:headers]].flatten.collect{|h| h.downcase} + end + + self.methods = case opts[:methods] + when :any then [:get, :head, :post, :put, :patch, :delete, :options] + else + ensure_enum(opts[:methods]) || [:get] + end.map{|e| e.to_s } + + self.expose = opts[:expose] ? [opts[:expose]].flatten : nil + end + + def matches_path?(path) + pattern =~ path + end + + def match?(path, env) + matches_path?(path) && (if_proc.nil? || if_proc.call(env)) + end + + def process_preflight(env, result) + headers = {} + + request_method = env[HTTP_ACCESS_CONTROL_REQUEST_METHOD] + if request_method.nil? + result.miss(Result::MISS_NO_METHOD) and return headers + end + if !methods.include?(request_method.downcase) + result.miss(Result::MISS_DENY_METHOD) and return headers + end + + request_headers = env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] + if request_headers && !allow_headers?(request_headers) + result.miss(Result::MISS_DENY_HEADER) and return headers + end + + result.hit = true + headers.merge(to_preflight_headers(env)) + end + + def to_headers(env) + h = { + 'Access-Control-Allow-Origin' => origin_for_response_header(env[HTTP_ORIGIN]), + 'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '), + 'Access-Control-Expose-Headers' => expose.nil? ? '' : expose.join(', '), + 'Access-Control-Max-Age' => max_age.to_s } + h['Access-Control-Allow-Credentials'] = 'true' if credentials + h + end + + protected + def public_resource? + @public_resource + end + + def origin_for_response_header(origin) + return '*' if public_resource? + origin + end + + def to_preflight_headers(env) + h = to_headers(env) + if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] + h.merge!('Access-Control-Allow-Headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]) + end + h + end + + def allow_headers?(request_headers) + headers = self.headers || [] + if headers == :any + return true + end + request_headers = request_headers.split(/,\s*/) if request_headers.kind_of?(String) + request_headers.all? do |header| + header = header.downcase + CORS_SIMPLE_HEADERS.include?(header) || headers.include?(header) + end + end + + def ensure_enum(v) + return nil if v.nil? + [v].flatten + end + + def compile(path) + if path.respond_to? :to_str + special_chars = %w{. + ( )} + pattern = + path.to_str.gsub(/((:\w+)|\/\*|[\*#{special_chars.join}])/) do |match| + case match + when "/*" + "\\/?(.*?)" + when "*" + "(.*?)" + when *special_chars + Regexp.escape(match) + else + "([^/?&#]+)" + end + end + /^#{pattern}$/ + elsif path.respond_to? :match + path + else + raise TypeError, path + end + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors/version.rb new file mode 100644 index 0000000000..254320c562 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors/version.rb @@ -0,0 +1,5 @@ +module Rack + class Cors + VERSION = "1.1.1" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/rack-cors.gemspec b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/rack-cors.gemspec new file mode 100644 index 0000000000..f5b33f60c6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/rack-cors.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rack/cors/version' + +Gem::Specification.new do |spec| + spec.name = "rack-cors" + spec.version = Rack::Cors::VERSION + spec.authors = ["Calvin Yu"] + spec.email = ["me@sourcebender.com"] + spec.description = %q{Middleware that will make Rack-based apps CORS compatible. Fork the project here: https://github.com/cyu/rack-cors} + spec.summary = %q{Middleware for enabling Cross-Origin Resource Sharing in Rack apps} + spec.homepage = "https://github.com/cyu/rack-cors" + spec.license = "MIT" + + spec.files = `git ls-files`.split($/).reject { |f| f == '.gitignore' or f =~ /^examples/ } + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + spec.add_dependency "rack", ">= 2.0.0" + spec.add_development_dependency "bundler", ">= 1.16.0", '< 3' + spec.add_development_dependency "rake", "~> 12.3.0" + spec.add_development_dependency "minitest", "~> 5.11.0" + spec.add_development_dependency "mocha", "~> 1.6.0" + spec.add_development_dependency "rack-test", "~> 1.1.0" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/expect.js b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/expect.js new file mode 100644 index 0000000000..ff7d025a46 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/expect.js @@ -0,0 +1,1286 @@ +(function (global, module) { + + var exports = module.exports; + + /** + * Exports. + */ + + module.exports = expect; + expect.Assertion = Assertion; + + /** + * Exports version. + */ + + expect.version = '0.1.2'; + + /** + * Possible assertion flags. + */ + + var flags = { + not: ['to', 'be', 'have', 'include', 'only'] + , to: ['be', 'have', 'include', 'only', 'not'] + , only: ['have'] + , have: ['own'] + , be: ['an'] + }; + + function expect (obj) { + return new Assertion(obj); + } + + /** + * Constructor + * + * @api private + */ + + function Assertion (obj, flag, parent) { + this.obj = obj; + this.flags = {}; + + if (undefined != parent) { + this.flags[flag] = true; + + for (var i in parent.flags) { + if (parent.flags.hasOwnProperty(i)) { + this.flags[i] = true; + } + } + } + + var $flags = flag ? flags[flag] : keys(flags) + , self = this + + if ($flags) { + for (var i = 0, l = $flags.length; i < l; i++) { + // avoid recursion + if (this.flags[$flags[i]]) continue; + + var name = $flags[i] + , assertion = new Assertion(this.obj, name, this) + + if ('function' == typeof Assertion.prototype[name]) { + // clone the function, make sure we dont touch the prot reference + var old = this[name]; + this[name] = function () { + return old.apply(self, arguments); + } + + for (var fn in Assertion.prototype) { + if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { + this[name][fn] = bind(assertion[fn], assertion); + } + } + } else { + this[name] = assertion; + } + } + } + }; + + /** + * Performs an assertion + * + * @api private + */ + + Assertion.prototype.assert = function (truth, msg, error, expected) { + var msg = this.flags.not ? error : msg + , ok = this.flags.not ? !truth : truth + , err; + + if (!ok) { + err = new Error(msg.call(this)); + if (arguments.length > 3) { + err.actual = this.obj; + err.expected = expected; + err.showDiff = true; + } + throw err; + } + + this.and = new Assertion(this.obj); + }; + + /** + * Check if the value is truthy + * + * @api public + */ + + Assertion.prototype.ok = function () { + this.assert( + !!this.obj + , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } + , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); + }; + + /** + * Creates an anonymous function which calls fn with arguments. + * + * @api public + */ + + Assertion.prototype.withArgs = function() { + expect(this.obj).to.be.a('function'); + var fn = this.obj; + var args = Array.prototype.slice.call(arguments); + return expect(function() { fn.apply(null, args); }); + } + + /** + * Assert that the function throws. + * + * @param {Function|RegExp} callback, or regexp to match error string against + * @api public + */ + + Assertion.prototype.throwError = + Assertion.prototype.throwException = function (fn) { + expect(this.obj).to.be.a('function'); + + var thrown = false + , not = this.flags.not + + try { + this.obj(); + } catch (e) { + if (isRegExp(fn)) { + var subject = 'string' == typeof e ? e : e.message; + if (not) { + expect(subject).to.not.match(fn); + } else { + expect(subject).to.match(fn); + } + } else if ('function' == typeof fn) { + fn(e); + } + thrown = true; + } + + if (isRegExp(fn) && not) { + // in the presence of a matcher, ensure the `not` only applies to + // the matching. + this.flags.not = false; + } + + var name = this.obj.name || 'fn'; + this.assert( + thrown + , function(){ return 'expected ' + name + ' to throw an exception' } + , function(){ return 'expected ' + name + ' not to throw an exception' }); + }; + + /** + * Checks if the array is empty. + * + * @api public + */ + + Assertion.prototype.empty = function () { + var expectation; + + if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { + if ('number' == typeof this.obj.length) { + expectation = !this.obj.length; + } else { + expectation = !keys(this.obj).length; + } + } else { + if ('string' != typeof this.obj) { + expect(this.obj).to.be.an('object'); + } + + expect(this.obj).to.have.property('length'); + expectation = !this.obj.length; + } + + this.assert( + expectation + , function(){ return 'expected ' + i(this.obj) + ' to be empty' } + , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); + return this; + }; + + /** + * Checks if the obj exactly equals another. + * + * @api public + */ + + Assertion.prototype.be = + Assertion.prototype.equal = function (obj) { + this.assert( + obj === this.obj + , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } + , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); + return this; + }; + + /** + * Checks if the obj sortof equals another. + * + * @api public + */ + + Assertion.prototype.eql = function (obj) { + this.assert( + expect.eql(this.obj, obj) + , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } + , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) } + , obj); + return this; + }; + + /** + * Assert within start to finish (inclusive). + * + * @param {Number} start + * @param {Number} finish + * @api public + */ + + Assertion.prototype.within = function (start, finish) { + var range = start + '..' + finish; + this.assert( + this.obj >= start && this.obj <= finish + , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } + , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); + return this; + }; + + /** + * Assert typeof / instance of + * + * @api public + */ + + Assertion.prototype.a = + Assertion.prototype.an = function (type) { + if ('string' == typeof type) { + // proper english in error msg + var n = /^[aeiou]/.test(type) ? 'n' : ''; + + // typeof with support for 'array' + this.assert( + 'array' == type ? isArray(this.obj) : + 'regexp' == type ? isRegExp(this.obj) : + 'object' == type + ? 'object' == typeof this.obj && null !== this.obj + : type == typeof this.obj + , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } + , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); + } else { + // instanceof + var name = type.name || 'supplied constructor'; + this.assert( + this.obj instanceof type + , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } + , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); + } + + return this; + }; + + /** + * Assert numeric value above _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.greaterThan = + Assertion.prototype.above = function (n) { + this.assert( + this.obj > n + , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } + , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); + return this; + }; + + /** + * Assert numeric value below _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.lessThan = + Assertion.prototype.below = function (n) { + this.assert( + this.obj < n + , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } + , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); + return this; + }; + + /** + * Assert string value matches _regexp_. + * + * @param {RegExp} regexp + * @api public + */ + + Assertion.prototype.match = function (regexp) { + this.assert( + regexp.exec(this.obj) + , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } + , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); + return this; + }; + + /** + * Assert property "length" exists and has value of _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.length = function (n) { + expect(this.obj).to.have.property('length'); + var len = this.obj.length; + this.assert( + n == len + , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } + , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); + return this; + }; + + /** + * Assert property _name_ exists, with optional _val_. + * + * @param {String} name + * @param {Mixed} val + * @api public + */ + + Assertion.prototype.property = function (name, val) { + if (this.flags.own) { + this.assert( + Object.prototype.hasOwnProperty.call(this.obj, name) + , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } + , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); + return this; + } + + if (this.flags.not && undefined !== val) { + if (undefined === this.obj[name]) { + throw new Error(i(this.obj) + ' has no property ' + i(name)); + } + } else { + var hasProp; + try { + hasProp = name in this.obj + } catch (e) { + hasProp = undefined !== this.obj[name] + } + + this.assert( + hasProp + , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } + , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); + } + + if (undefined !== val) { + this.assert( + val === this.obj[name] + , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) + + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } + , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) + + ' of ' + i(val) }); + } + + this.obj = this.obj[name]; + return this; + }; + + /** + * Assert that the array contains _obj_ or string contains _obj_. + * + * @param {Mixed} obj|string + * @api public + */ + + Assertion.prototype.string = + Assertion.prototype.contain = function (obj) { + if ('string' == typeof this.obj) { + this.assert( + ~this.obj.indexOf(obj) + , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } + , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); + } else { + this.assert( + ~indexOf(this.obj, obj) + , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } + , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); + } + return this; + }; + + /** + * Assert exact keys or inclusion of keys by using + * the `.own` modifier. + * + * @param {Array|String ...} keys + * @api public + */ + + Assertion.prototype.key = + Assertion.prototype.keys = function ($keys) { + var str + , ok = true; + + $keys = isArray($keys) + ? $keys + : Array.prototype.slice.call(arguments); + + if (!$keys.length) throw new Error('keys required'); + + var actual = keys(this.obj) + , len = $keys.length; + + // Inclusion + ok = every($keys, function (key) { + return ~indexOf(actual, key); + }); + + // Strict + if (!this.flags.not && this.flags.only) { + ok = ok && $keys.length == actual.length; + } + + // Key string + if (len > 1) { + $keys = map($keys, function (key) { + return i(key); + }); + var last = $keys.pop(); + str = $keys.join(', ') + ', and ' + last; + } else { + str = i($keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (!this.flags.only ? 'include ' : 'only have ') + str; + + // Assertion + this.assert( + ok + , function(){ return 'expected ' + i(this.obj) + ' to ' + str } + , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); + + return this; + }; + /** + * Assert a failure. + * + * @param {String ...} custom message + * @api public + */ + Assertion.prototype.fail = function (msg) { + var error = function() { return msg || "explicit failure"; } + this.assert(false, error, error); + return this; + }; + + /** + * Function bind implementation. + */ + + function bind (fn, scope) { + return function () { + return fn.apply(scope, arguments); + } + } + + /** + * Array every compatibility + * + * @see bit.ly/5Fq1N2 + * @api public + */ + + function every (arr, fn, thisObj) { + var scope = thisObj || global; + for (var i = 0, j = arr.length; i < j; ++i) { + if (!fn.call(scope, arr[i], i, arr)) { + return false; + } + } + return true; + }; + + /** + * Array indexOf compatibility. + * + * @see bit.ly/a5Dxa2 + * @api public + */ + + function indexOf (arr, o, i) { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(arr, o, i); + } + + if (arr.length === undefined) { + return -1; + } + + for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 + ; i < j && arr[i] !== o; i++); + + return j <= i ? -1 : i; + }; + + // https://gist.github.com/1044128/ + var getOuterHTML = function(element) { + if ('outerHTML' in element) return element.outerHTML; + var ns = "http://www.w3.org/1999/xhtml"; + var container = document.createElementNS(ns, '_'); + var elemProto = (window.HTMLElement || window.Element).prototype; + var xmlSerializer = new XMLSerializer(); + var html; + if (document.xmlVersion) { + return xmlSerializer.serializeToString(element); + } else { + container.appendChild(element.cloneNode(false)); + html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); + container.innerHTML = ''; + return html; + } + }; + + // Returns true if object is a DOM element. + var isDOMElement = function (object) { + if (typeof HTMLElement === 'object') { + return object instanceof HTMLElement; + } else { + return object && + typeof object === 'object' && + object.nodeType === 1 && + typeof object.nodeName === 'string'; + } + }; + + /** + * Inspects an object. + * + * @see taken from node.js `util` module (copyright Joyent, MIT license) + * @api private + */ + + function i (obj, showHidden, depth) { + var seen = []; + + function stylize (str) { + return str; + }; + + function format (value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value !== exports && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + return value.inspect(recurseTimes); + } + + // Primitive types cannot have properties + switch (typeof value) { + case 'undefined': + return stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return stylize(simple, 'string'); + + case 'number': + return stylize('' + value, 'number'); + + case 'boolean': + return stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return stylize('null', 'null'); + } + + if (isDOMElement(value)) { + return getOuterHTML(value); + } + + // Look up the keys of the object. + var visible_keys = keys(value); + var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; + + // Functions without properties can be shortcutted. + if (typeof value === 'function' && $keys.length === 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + var name = value.name ? ': ' + value.name : ''; + return stylize('[Function' + name + ']', 'special'); + } + } + + // Dates without properties can be shortcutted + if (isDate(value) && $keys.length === 0) { + return stylize(value.toUTCString(), 'date'); + } + + // Error objects can be shortcutted + if (value instanceof Error) { + return stylize("["+value.toString()+"]", 'Error'); + } + + var base, type, braces; + // Determine the object type + if (isArray(value)) { + type = 'Array'; + braces = ['[', ']']; + } else { + type = 'Object'; + braces = ['{', '}']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var n = value.name ? ': ' + value.name : ''; + base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + } else { + base = ''; + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + value.toUTCString(); + } + + if ($keys.length === 0) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + return stylize('[Object]', 'special'); + } + } + + seen.push(value); + + var output = map($keys, function (key) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = stylize('[Getter/Setter]', 'special'); + } else { + str = stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = stylize('[Setter]', 'special'); + } + } + } + if (indexOf(visible_keys, key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (indexOf(seen, value[key]) < 0) { + if (recurseTimes === null) { + str = format(value[key]); + } else { + str = format(value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (isArray(value)) { + str = map(str.split('\n'), function (line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + map(str.split('\n'), function (line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (type === 'Array' && key.match(/^\d+$/)) { + return str; + } + name = json.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = stylize(name, 'string'); + } + } + + return name + ': ' + str; + }); + + seen.pop(); + + var numLinesEst = 0; + var length = reduce(output, function (prev, cur) { + numLinesEst++; + if (indexOf(cur, '\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 50) { + output = braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + + } else { + output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + return output; + } + return format(obj, (typeof depth === 'undefined' ? 2 : depth)); + }; + + expect.stringify = i; + + function isArray (ar) { + return Object.prototype.toString.call(ar) == '[object Array]'; + }; + + function isRegExp(re) { + var s; + try { + s = '' + re; + } catch (e) { + return false; + } + + return re instanceof RegExp || // easy case + // duck-type for context-switching evalcx case + typeof(re) === 'function' && + re.constructor.name === 'RegExp' && + re.compile && + re.test && + re.exec && + s.match(/^\/.*\/[gim]{0,3}$/); + }; + + function isDate(d) { + if (d instanceof Date) return true; + return false; + }; + + function keys (obj) { + if (Object.keys) { + return Object.keys(obj); + } + + var keys = []; + + for (var i in obj) { + if (Object.prototype.hasOwnProperty.call(obj, i)) { + keys.push(i); + } + } + + return keys; + } + + function map (arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } + + var other= new Array(arr.length); + + for (var i= 0, n = arr.length; i= 2) { + var rv = arguments[1]; + } else { + do { + if (i in this) { + rv = this[i++]; + break; + } + + // if array contains no values, no initial value to return + if (++i >= len) + throw new TypeError(); + } while (true); + } + + for (; i < len; i++) { + if (i in this) + rv = fun.call(null, rv, this[i], i, this); + } + + return rv; + }; + + /** + * Asserts deep equality + * + * @see taken from node.js `assert` module (copyright Joyent, MIT license) + * @api private + */ + + expect.eql = function eql (actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + } else if ('undefined' != typeof Buffer + && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == "object", + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + // If both are regular expression use the special `regExpEquiv` method + // to determine equivalence. + } else if (isRegExp(actual) && isRegExp(expected)) { + return regExpEquiv(actual, expected); + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical "prototype" property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } + } + + function isUndefinedOrNull (value) { + return value === null || value === undefined; + } + + function isArguments (object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; + } + + function regExpEquiv (a, b) { + return a.source === b.source && a.global === b.global && + a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; + } + + function objEquiv (a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical "prototype" property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return expect.eql(a, b); + } + try{ + var ka = keys(a), + kb = keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!expect.eql(a[key], b[key])) + return false; + } + return true; + } + + var json = (function () { + "use strict"; + + if ('object' == typeof JSON && JSON.parse && JSON.stringify) { + return { + parse: nativeJSON.parse + , stringify: nativeJSON.stringify + } + } + + var JSON = {}; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + function date(d, key) { + return isFinite(d.valueOf()) ? + d.getUTCFullYear() + '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + 'T' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()) + ':' + + f(d.getUTCSeconds()) + 'Z' : null; + }; + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + + // Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + + if (value instanceof Date) { + value = date(key); + } + + // If we were called with a replacer function, then call the replacer to + // obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + + // What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + + // JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + // If the type is 'object', we might be dealing with an object or an array or + // null. + + case 'object': + + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. + + if (!value) { + return 'null'; + } + + // Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + + // Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + // If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + + // Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + + // If the JSON object does not yet have a stringify method, give it one. + + JSON.stringify = function (value, replacer, space) { + + // The stringify method takes a value and an optional replacer, and an optional + // space parameter, and returns a JSON text. The replacer can be a function + // that can replace values, or an array of strings that will select the keys. + // A default replacer method can be provided. Use of the space parameter can + // produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + + // If the space parameter is a number, make an indent string containing that + // many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + + // If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + + // If there is a replacer, it must be a function or an array. + // Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + + return str('', {'': value}); + }; + + // If the JSON object does not yet have a parse method, give it one. + + JSON.parse = function (text, reviver) { + // The parse method takes a text and an optional reviver function, and returns + // a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + + // The walk method is used to recursively walk the resulting structure so + // that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + + // Parsing happens in four stages. In the first stage, we replace certain + // Unicode characters with escape sequences. JavaScript handles many characters + // incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + + // In the second stage, we run the text against regular expressions that look + // for non-JSON patterns. We are especially concerned with '()' and 'new' + // because they can cause invocation, and '=' because it can cause mutation. + // But just to be safe, we want to reject all unexpected forms. + + // We split the second stage into 4 regexp operations in order to work around + // crippling inefficiencies in IE's and Safari's regexp engines. First we + // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we + // replace all simple value tokens with ']' characters. Third, we delete all + // open brackets that follow a colon or comma or that begin the text. Finally, + // we look to see that the remaining characters are only whitespace or ']' or + // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + // In the third stage we use the eval function to compile the text into a + // JavaScript structure. The '{' operator is subject to a syntactic ambiguity + // in JavaScript: it can begin a block or an object literal. We wrap the text + // in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + + // In the optional fourth stage, we recursively walk the new structure, passing + // each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + + // If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + + return JSON; + })(); + + if ('undefined' != typeof window) { + window.expect = module.exports; + } + +})( + this + , 'undefined' != typeof module ? module : {exports: {}} +); diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.css b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.css new file mode 100644 index 0000000000..e6aa700fa6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.css @@ -0,0 +1,250 @@ +@charset "utf-8"; + +body { + margin:0; +} + +#mocha { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 60px 50px; +} + +#mocha ul, #mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, #mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha .hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial, sans-serif; +} + +#mocha .test.pass.medium .duration { + background: #C09853; +} + +#mocha .test.pass.slow .duration { + background: #B94A48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: white; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-error { + color: #c00; + font-size: 1.5em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +#mocha code .comment { color: #ddd } +#mocha code .init { color: #2F6FAD } +#mocha code .string { color: #5890AD } +#mocha code .keyword { color: #8A6343 } +#mocha code .number { color: #2F6FAD } + +@media screen and (max-device-width: 480px) { + #mocha { + margin: 60px 0px; + } + + #mocha #stats { + position: absolute; + } +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.js b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.js new file mode 100644 index 0000000000..2b35a88302 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/mocha.js @@ -0,0 +1,5373 @@ +;(function(){ + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!mod.exports) { + mod.exports = {}; + mod.call(mod.exports, mod, mod.exports, require.relative(path)); + } + return mod.exports; + } + +require.modules = {}; + +require.resolve = function (path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p.charAt(0)) return require(p); + + var path = parent.split('/') + , segs = p.split('/'); + path.pop(); + + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + } +}; + +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ +/* See license.txt for terms of usage */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +var JsDiff = (function() { + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, "&"); + n = n.replace(//g, ">"); + n = n.replace(/"/g, """); + + return n; + } + + + var fbDiff = function(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + }; + fbDiff.prototype = { + diff: function(oldString, newString) { + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString == oldString) { + return [{ value: newString }]; + } + if (!newString) { + return [{ value: oldString, removed: true }]; + } + if (!oldString) { + return [{ value: newString, added: true }]; + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { + return bestPath[0].components; + } + + for (var editLength = 1; editLength <= maxEditLength; editLength++) { + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + var basePath; + var addPath = bestPath[diagonalPath-1], + removePath = bestPath[diagonalPath+1]; + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath-1] = undefined; + } + + var canAdd = addPath && addPath.newPos+1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + this.pushComponent(basePath.components, oldString[oldPos], undefined, true); + } else { + basePath = clonePath(addPath); + basePath.newPos++; + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); + } + + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); + + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + return basePath.components; + } else { + bestPath[diagonalPath] = basePath; + } + } + } + }, + + pushComponent: function(components, value, added, removed) { + var last = components[components.length-1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length-1] = + {value: this.join(last.value, value), added: added, removed: removed }; + } else { + components.push({value: value, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath; + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + newPos++; + oldPos++; + + this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + } + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { + return true; + } else { + return left == right; + } + }, + join: function(left, right) { + return left + right; + }, + tokenize: function(value) { + return value; + } + }; + + var CharDiff = new fbDiff(); + + var WordDiff = new fbDiff(true); + WordDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new fbDiff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new fbDiff(); + LineDiff.tokenize = function(value) { + return value.split(/^/m); + }; + + return { + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + ret.push("Index: " + fileName); + ret.push("==================================================================="); + ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); + ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); + + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length-1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function(entry) { return ' ' + entry; }); + } + function eofNL(curRange, i, current) { + var last = diff[diff.length-2], + isLast = i === diff.length-2, + isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); + + // Figure out if this is the last line for the given file and missing NL + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, "").split("\n"); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i-1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); + eofNL(curRange, i, current); + + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length-2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) + + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) + + " @@"); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; newRangeStart = 0; curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + convertChangesToXML: function(changes){ + var ret = []; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + } + return ret.join(""); + } + }; +})(); + +if (typeof module !== "undefined") { + module.exports = JsDiff; +} + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * Event emitter constructor. + * + * @api public + */ + +function EventEmitter(){}; + +/** + * Adds a listener. + * + * @api public + */ + +EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; +}; + +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +/** + * Adds a volatile listener. + * + * @api public + */ + +EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; +}; + +/** + * Removes a listener. + * + * @api public + */ + +EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; +}; + +/** + * Removes all listeners for an event. + * + * @api public + */ + +EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; +}; + +/** + * Gets all listeners for a certain event. + * + * @api public + */ + +EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; +}; + +/** + * Emits an event. + * + * @api public + */ + +EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + if (0 == arguments.length) return this._runnable; + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test slowness threshold `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.slow = function(ms){ + this.runnable().slow(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_runnable' == key) return; + if ('test' == key) return; + return val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Hook.prototype = new F; +Hook.prototype.constructor = Hook; + + +/** + * Get or set the test `err`. + * + * @param {Error} err + * @return {Error} + * @api public + */ + +Hook.prototype.error = function(err){ + if (0 == arguments.length) { + var err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending describe. + */ + + context.xdescribe = + context.xcontext = + context.describe.skip = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn){ + var suite = context.describe(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn){ + var suite = suites[0]; + if (suite.pending) var fn = null; + var test = new Test(title, fn); + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn){ + var test = context.it(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.xit = + context.xspecify = + context.it.skip = function(title){ + context.it(title); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; + +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path') + , utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `bail` bail on the first test failure + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `grep` string or regexp to filter tests with + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.grep(options.grep); + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.bail(options.bail); + this.reporter(options.reporter); + if (options.timeout) this.timeout(options.timeout); + if (options.slow) this.slow(options.slow); +} + +/** + * Enable or disable bailing on the first failure. + * + * @param {Boolean} [bail] + * @api public + */ + +Mocha.prototype.bail = function(bail){ + if (0 == arguments.length) bail = true; + this.suite.bail(bail); + return this; +}; + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "dot". + * + * @param {String|Function} reporter name or constructor + * @api public + */ + +Mocha.prototype.reporter = function(reporter){ + if ('function' == typeof reporter) { + this._reporter = reporter; + } else { + reporter = reporter || 'dot'; + try { + this._reporter = require('./reporters/' + reporter); + } catch (err) { + this._reporter = require(reporter); + } + if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + } + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(fn){ + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha' + , title: 'Passed' + , image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @param {RegExp|String} re + * @return {Mocha} + * @api public + */ + +Mocha.prototype.grep = function(re){ + this.options.grep = 'string' == typeof re + ? new RegExp(utils.escapeRegexp(re)) + : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.invert = function(){ + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.ignoreLeaks = function(){ + this.options.ignoreLeaks = true; + return this; +}; + +/** + * Enable global leak checking. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.checkLeaks = function(){ + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Enable growl support. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.growl = function(){ + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @param {Array|String} globals + * @return {Mocha} + * @api public + */ + +Mocha.prototype.globals = function(globals){ + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @param {Number} timeout + * @return {Mocha} + * @api public + */ + +Mocha.prototype.timeout = function(timeout){ + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @param {Number} slow + * @return {Mocha} + * @api public + */ + +Mocha.prototype.slow = function(slow){ + this.suite.slow(slow); + return this; +}; + +/** + * Makes all tests async (accepting a callback) + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.asyncOnly = function(){ + this.options.asyncOnly = true; + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + if (this.files.length) this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = false !== options.ignoreLeaks; + runner.asyncOnly = options.asyncOnly; + if (options.grep) runner.grep(options.grep, options.invert); + if (options.globals) runner.globals(options.globals); + if (options.growl) this._growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("ms.js", function(module, exports, require){ + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; + +/** + * Parse or format the given `val`. + * + * @param {String|Number} val + * @return {String|Number} + * @api public + */ + +module.exports = function(val){ + if ('string' == typeof val) return parse(val); + return format(val); +} + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!m) return; + var n = parseFloat(m[1]); + var type = (m[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * 31557600000; + case 'days': + case 'day': + case 'd': + return n * 86400000; + case 'hours': + case 'hour': + case 'h': + return n * 3600000; + case 'minutes': + case 'minute': + case 'm': + return n * 60000; + case 'seconds': + case 'second': + case 's': + return n * 1000; + case 'ms': + return n; + } +} + +/** + * Format the given `ms`. + * + * @param {Number} ms + * @return {String} + * @api public + */ + +function format(ms) { + if (ms == d) return Math.round(ms / d) + ' day'; + if (ms > d) return Math.round(ms / d) + ' days'; + if (ms == h) return Math.round(ms / h) + ' hour'; + if (ms > h) return Math.round(ms / h) + ' hours'; + if (ms == m) return Math.round(ms / m) + ' minute'; + if (ms > m) return Math.round(ms / m) + ' minutes'; + if (ms == s) return Math.round(ms / s) + ' second'; + if (ms > s) return Math.round(ms / s) + ' seconds'; + return ms + ' ms'; +} +}); // module: ms.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff') + , ms = require('../ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if ('win32' == process.platform) { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\u001b[?25l'); + }, + + show: function(){ + process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\u001b[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected + , escape = true; + + // explicitly show diff + if (err.showDiff) { + escape = false; + err.actual = actual = JSON.stringify(actual, null, 2); + err.expected = expected = JSON.stringify(expected, null, 2); + } + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + msg = errorDiff(err, 'Words', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.stats = stats; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = test.slow() / 2; + test.speed = test.duration > test.slow() + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); + + runner.on('pending', function(){ + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt + , tests; + + console.log(); + + function pluralize(n) { + return 1 == n ? 'test' : 'tests'; + } + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ' + exports.symbols.err) + + color('fail', ' %d of %d %s failed') + + color('light', ':') + + console.error(fmt, + stats.failures, + this.runner.total, + pluralize(this.runner.total)); + + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ') + + color('green', ' %d %s complete') + + color('light', ' (%s)'); + + console.log(fmt, + stats.tests || 0, + pluralize(stats.tests), + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d %s pending'); + + console.log(fmt, stats.pending, pluralize(stats.pending)); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type, escape) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (escape) { + str.value = str.value + .replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); + } + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), utils.escape(suite.title)); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', Base.symbols.dot)); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(color(test.speed, Base.symbols.dot)); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', Base.symbols.dot)); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Dot.prototype = new F; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for `n`. + * + * @return {String} + * @api private + */ + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner, root) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , passesLink = items[1].getElementsByTagName('a')[0] + , failures = items[2].getElementsByTagName('em')[0] + , failuresLink = items[2].getElementsByTagName('a')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , report = fragment('
    ') + , stack = [report] + , progress + , ctx + + root = root || document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + // pass toggle + on(passesLink, 'click', function(){ + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test pass'); + }); + + // failure toggle + on(failuresLink, 'click', function(){ + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test fail'); + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var url = '?grep=' + encodeURIComponent(suite.fullTitle()); + var el = fragment('
  • %s

  • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / this.total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); + } else if (test.pending) { + var el = fragment('
  • %e

  • ', test.title); + } else { + var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
    %e
    ', str)); + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + */ + +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (0 == els.length) suites[i].className += ' hidden'; + } +} + +/** + * Unhide .hidden suites. + */ + +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + for (var i = 0; i < els.length; ++i) { + els[i].className = els[i].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Nyan = require('./nyan'); +exports.XUnit = require('./xunit'); +exports.Markdown = require('./markdown'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.Teamcity = require('./teamcity'); + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + ret.files.sort(function(a, b) { + return a.filename.localeCompare(b.filename); + }); + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Landing.prototype = new F; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' '+Base.symbols.dot) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +List.prototype = new F; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Min.prototype = new F; +Min.prototype.constructor = Min; + + +}); // module: reporters/min.js + +require.register("reporters/nyan.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , rainbowColors = this.rainbowColors = self.generateColors() + , colorIndex = this.colorIndex = 0 + , numerOfLines = this.numberOfLines = 4 + , trajectories = this.trajectories = [[], [], [], []] + , nyanCatWidth = this.nyanCatWidth = 11 + , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) + , scoreboardWidth = this.scoreboardWidth = 5 + , tick = this.tick = 0 + , n = 0; + + runner.on('start', function(){ + Base.cursor.hide(); + self.draw('start'); + }); + + runner.on('pending', function(test){ + self.draw('pending'); + }); + + runner.on('pass', function(test){ + self.draw('pass'); + }); + + runner.on('fail', function(test, err){ + self.draw('fail'); + }); + + runner.on('end', function(){ + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) write('\n'); + self.epilogue(); + }); +} + +/** + * Draw the nyan cat with runner `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.draw = function(status){ + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(status); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function(){ + var stats = this.stats; + var colors = Base.colors; + + function draw(color, n) { + write(' '); + write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write('\n'); + } + + draw(colors.green, stats.passes); + draw(colors.fail, stats.failures); + draw(colors.pending, stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function(){ + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function(){ + var self = this; + + this.trajectories.forEach(function(line, index) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat with `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.drawNyanCat = function(status) { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var color = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(color); + write('_,------,'); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(color); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + switch (status) { + case 'pass': + face = '( ^ .^)'; + break; + case 'fail': + face = '( o .o)'; + break; + default: + face = '( - .-)'; + } + write(tail + '|' + padding + face + ' '); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Move cursor up `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @return {Array} + * @api private + */ + +NyanCat.prototype.generateColors = function(){ + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +NyanCat.prototype.rainbowify = function(str){ + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + */ + +function write(string) { + process.stdout.write(string); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +NyanCat.prototype = new F; +NyanCat.prototype.constructor = NyanCat; + + +}); // module: reporters/nyan.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || Base.symbols.dot; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + complete++; + var incomplete = total - complete + , percent = complete / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Progress.prototype = new F; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Spec.prototype = new F; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 1 + , passes = 0 + , failures = 0; + + runner.on('start', function(){ + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + passes++; + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + failures++; + console.log('not ok %d %s', n, title(test)); + if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); + }); + + runner.on('end', function(){ + console.log('# tests ' + (passes + failures)); + console.log('# pass ' + passes); + console.log('# fail ' + failures); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str + .replace(/\|/g, "||") + .replace(/\n/g, "|n") + .replace(/\r/g, "|r") + .replace(/\[/g, "|[") + .replace(/\]/g, "|]") + .replace(/\u0085/g, "|x") + .replace(/\u2028/g, "|l") + .replace(/\u2029/g, "|p") + .replace(/'/g, "|'"); +} + +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('pass', function(test){ + tests.push(test); + }); + + runner.on('fail', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +XUnit.prototype = new F; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runnable') + , milliseconds = require('./ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Object#toString(). + */ + +var toString = Object.prototype.toString; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this._slow = 75; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runnable.prototype = new F; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Set & get slow `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @return {String} + * @api private + */ + +Runnable.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_' == key[0]) return; + if ('parent' == key) return '#'; + if ('ctx' == key) return '#'; + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple(err) { + if (emitted) return; + emitted = true; + self.emit('error', err || new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(err); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + if (this.asyncOnly) { + return done(new Error('--async-only option in use without declaring `done()`')); + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runner') + , Test = require('./test') + , utils = require('./utils') + , filter = utils.filter + , keys = utils.keys; + +/** + * Non-enumerable globals. + */ + +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date' +]; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(this.globalProps().concat(['errno'])); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @api private + */ + +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runner.prototype = new F; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @param {RegExp} re + * @param {Boolean} invert + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re, invert){ + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @param {Suite} suite + * @return {Number} + * @api public + */ + +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test){ + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (match) total++; + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @return {Array} + * @api private + */ + +Runner.prototype.globalProps = function() { + var props = utils.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~utils.indexOf(props, globals[i])) continue; + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + var ok = this._globals; + var globals = this.globalProps(); + var isNode = process.kill; + var leaks; + + // check length - 2 ('errno' and 'location' globals) + if (isNode && 1 == ok.length - globals.length) return + else if (2 == ok.length - globals.length) return; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + + if ('string' == typeof err) { + err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + } + + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) self.fail(self.test, testError); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + Runner.immediately(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + if (this.asyncOnly) test.asyncOnly = true; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests.slice() + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (!match) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var total = this.grepTotal(suite) + , self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + + if (!total) return fn(); + + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception %s', err.message); + var runnable = this.currentRunnable; + if (!runnable || 'failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + function uncaught(err){ + self.uncaught(err); + } + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @param {Array} ok + * @param {Array} globals + * @return {Array} + * @api private + */ + +function filterLeaks(ok, globals) { + return filter(globals, function(key){ + // Firefox and Chrome exposes iframes as index inside the window object + if (/^d+/.test(key)) return false; + var matched = filter(ok, function(ok){ + if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) return true; + return key == ok; + }); + return matched.length == 0 && (!global.navigator || 'onerror' !== key); + }); +} + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:suite') + , milliseconds = require('./ms') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) suite.pending = true; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._slow = 75; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Suite.prototype = new F; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find + * all tests. Applies a function in the format + * `fn(test)`. + * + * @param {Function} fn + * @return {Suite} + * @api private + */ + +Suite.prototype.eachTest = function(fn){ + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite){ + suite.eachTest(fn); + }); + return this; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Test.prototype = new F; +Test.prototype.constructor = Test; + + +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Escape regular expression characters in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.escapeRegexp = function(str){ + return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew *(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +}); // module: utils.js + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = window.Date; +var setTimeout = window.setTimeout; +var setInterval = window.setInterval; +var clearTimeout = window.clearTimeout; +var clearInterval = window.clearInterval; + +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +var process = {}; +process.exit = function(status){}; +process.stdout = {}; +global = window; + +/** + * Remove uncaughtException listener. + */ + +process.removeListener = function(e){ + if ('uncaughtException' == e) { + window.onerror = null; + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + window.onerror = function(err, url, line){ + fn(new Error(err + ' (' + url + ':' + line + ')')); + }; + } +}; + +/** + * Expose mocha. + */ + +var Mocha = window.Mocha = require('mocha'), + mocha = window.mocha = new Mocha({ reporter: 'html' }); + +var immediateQueue = [] + , immediateTimeout; + +function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } +} + +/** + * High-performance override of Runner.immediately. + */ + +Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } +}; + +/** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + +mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', window, null, this); + return this; +}; + +/** + * Setup mocha with the given setting options. + */ + +mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; +}; + +/** + * Run mocha, returning the Runner. + */ + +mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(window.location.search || ''); + if (query.grep) mocha.grep(query.grep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(){ + Mocha.utils.highlightTags('code'); + if (fn) fn(); + }); +}; +})(); \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/runner.html b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/runner.html new file mode 100644 index 0000000000..aa26cfa126 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/runner.html @@ -0,0 +1,20 @@ + + + + Mocha Tests + + + +
    + + + + + + + + diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.coffee b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.coffee new file mode 100644 index 0000000000..0b710db0a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.coffee @@ -0,0 +1,47 @@ +CORS_SERVER = '127.0.0.1.xip.io:9292' + +describe 'CORS', -> + + it 'should allow access to dynamic resource', (done) -> + $.get "http://#{CORS_SERVER}/", (data, status, xhr) -> + expect(data).to.eql('Hello world') + done() + + it 'should allow PUT access to dynamic resource', (done) -> + $.ajax("http://#{CORS_SERVER}/", type: 'PUT').done (data, textStatus, jqXHR) -> + expect(data).to.eql('Hello world') + done() + + it 'should allow PATCH access to dynamic resource', (done) -> + $.ajax("http://#{CORS_SERVER}/", type: 'PATCH').done (data, textStatus, jqXHR) -> + expect(data).to.eql('Hello world') + done() + + it 'should allow HEAD access to dynamic resource', (done) -> + $.ajax("http://#{CORS_SERVER}/", type: 'HEAD').done (data, textStatus, jqXHR) -> + expect(jqXHR.status).to.eql(200) + done() + + it 'should allow DELETE access to dynamic resource', (done) -> + $.ajax("http://#{CORS_SERVER}/", type: 'DELETE').done (data, textStatus, jqXHR) -> + expect(data).to.eql('Hello world') + done() + + it 'should allow OPTIONS access to dynamic resource', (done) -> + $.ajax("http://#{CORS_SERVER}/", type: 'OPTIONS').done (data, textStatus, jqXHR) -> + expect(jqXHR.status).to.eql(200) + done() + + it 'should allow access to static resource', (done) -> + $.get "http://#{CORS_SERVER}/static.txt", (data, status, xhr) -> + expect($.trim(data)).to.eql("hello world") + done() + + it 'should allow post resource', (done) -> + $.ajax + type: 'POST' + url: "http://#{CORS_SERVER}/cors" + beforeSend: (xhr) -> xhr.setRequestHeader('X-Requested-With', 'XMLHTTPRequest') + success:(data, status, xhr) -> + expect($.trim(data)).to.eql("OK!") + done() diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.js b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.js new file mode 100644 index 0000000000..f140430c87 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/cors/test.cors.js @@ -0,0 +1,75 @@ +// Generated by CoffeeScript 2.3.1 +(function() { + var CORS_SERVER; + + CORS_SERVER = '127.0.0.1.xip.io:9292'; + + describe('CORS', function() { + it('should allow access to dynamic resource', function(done) { + return $.get(`http://${CORS_SERVER}/`, function(data, status, xhr) { + expect(data).to.eql('Hello world'); + return done(); + }); + }); + it('should allow PUT access to dynamic resource', function(done) { + return $.ajax(`http://${CORS_SERVER}/`, { + type: 'PUT' + }).done(function(data, textStatus, jqXHR) { + expect(data).to.eql('Hello world'); + return done(); + }); + }); + it('should allow PATCH access to dynamic resource', function(done) { + return $.ajax(`http://${CORS_SERVER}/`, { + type: 'PATCH' + }).done(function(data, textStatus, jqXHR) { + expect(data).to.eql('Hello world'); + return done(); + }); + }); + it('should allow HEAD access to dynamic resource', function(done) { + return $.ajax(`http://${CORS_SERVER}/`, { + type: 'HEAD' + }).done(function(data, textStatus, jqXHR) { + expect(jqXHR.status).to.eql(200); + return done(); + }); + }); + it('should allow DELETE access to dynamic resource', function(done) { + return $.ajax(`http://${CORS_SERVER}/`, { + type: 'DELETE' + }).done(function(data, textStatus, jqXHR) { + expect(data).to.eql('Hello world'); + return done(); + }); + }); + it('should allow OPTIONS access to dynamic resource', function(done) { + return $.ajax(`http://${CORS_SERVER}/`, { + type: 'OPTIONS' + }).done(function(data, textStatus, jqXHR) { + expect(jqXHR.status).to.eql(200); + return done(); + }); + }); + it('should allow access to static resource', function(done) { + return $.get(`http://${CORS_SERVER}/static.txt`, function(data, status, xhr) { + expect($.trim(data)).to.eql("hello world"); + return done(); + }); + }); + return it('should allow post resource', function(done) { + return $.ajax({ + type: 'POST', + url: `http://${CORS_SERVER}/cors`, + beforeSend: function(xhr) { + return xhr.setRequestHeader('X-Requested-With', 'XMLHTTPRequest'); + }, + success: function(data, status, xhr) { + expect($.trim(data)).to.eql("OK!"); + return done(); + } + }); + }); + }); + +}).call(this); diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/cors_test.rb b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/cors_test.rb new file mode 100644 index 0000000000..3c9ffed806 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/cors_test.rb @@ -0,0 +1,534 @@ +require 'minitest/autorun' +require 'rack/test' +require 'mocha/setup' +require 'rack/cors' +require 'ostruct' + +Rack::Test::Session.class_eval do + unless defined? :options + def options(uri, params = {}, env = {}, &block) + env = env_for(uri, env.merge(:method => "OPTIONS", :params => params)) + process_request(uri, env, &block) + end + end +end + +Rack::Test::Methods.class_eval do + def_delegator :current_session, :options +end + +module MiniTest::Assertions + def assert_cors_success(response) + assert !response.headers['Access-Control-Allow-Origin'].nil?, "Expected a successful CORS response" + end + + def assert_not_cors_success(response) + assert response.headers['Access-Control-Allow-Origin'].nil?, "Expected a failed CORS response" + end +end + +class CaptureResult + def initialize(app, options = {}) + @app = app + @result_holder = options[:holder] + end + + def call(env) + response = @app.call(env) + @result_holder.cors_result = env[Rack::Cors::RACK_CORS] + return response + end +end + +class FakeProxy + def initialize(app, options = {}) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers['Vary'] = %w(Origin User-Agent) + [status, headers, body] + end +end + +Rack::MockResponse.infect_an_assertion :assert_cors_success, :must_render_cors_success, :only_one_argument +Rack::MockResponse.infect_an_assertion :assert_not_cors_success, :wont_render_cors_success, :only_one_argument + +describe Rack::Cors do + include Rack::Test::Methods + + attr_accessor :cors_result + + def load_app(name, options = {}) + test = self + Rack::Builder.new do + use CaptureResult, :holder => test + eval File.read(File.dirname(__FILE__) + "/#{name}.ru") + use FakeProxy if options[:proxy] + map('/') do + run proc { |env| + [200, {'Content-Type' => 'text/html'}, ['success']] + } + end + end + end + + let(:app) { load_app('test') } + + it 'should support simple CORS request' do + successful_cors_request + cors_result.must_be :hit + end + + it "should not return CORS headers if Origin header isn't present" do + get '/' + last_response.wont_render_cors_success + cors_result.wont_be :hit + end + + it 'should support OPTIONS CORS request' do + successful_cors_request '/options', :method => :options + end + + it 'should support regex origins configuration' do + successful_cors_request :origin => 'http://192.168.0.1:1234' + end + + it 'should support subdomain example' do + successful_cors_request :origin => 'http://subdomain.example.com' + end + + it 'should support proc origins configuration' do + successful_cors_request '/proc-origin', :origin => 'http://10.10.10.10:3000' + end + + it 'should support lambda origin configuration' do + successful_cors_request '/lambda-origin', :origin => 'http://10.10.10.10:3000' + end + + it 'should support proc origins configuration (inverse)' do + cors_request '/proc-origin', :origin => 'http://bad.guy' + last_response.wont_render_cors_success + end + + it 'should not mix up path rules across origins' do + header 'Origin', 'http://10.10.10.10:3000' + get '/' # / is configured in a separate rule block + last_response.wont_render_cors_success + end + + it 'should support alternative X-Origin header' do + header 'X-Origin', 'http://localhost:3000' + get '/' + last_response.must_render_cors_success + end + + it 'should support expose header configuration' do + successful_cors_request '/expose_single_header' + last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test' + end + + it 'should support expose multiple header configuration' do + successful_cors_request '/expose_multiple_headers' + last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2' + end + + # Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/ + it "should add Vary header if resource matches even if Origin header isn't present" do + get '/' + last_response.wont_render_cors_success + last_response.headers['Vary'].must_equal 'Origin' + end + + it "should add Vary header based on :vary option" do + successful_cors_request '/vary_test' + last_response.headers['Vary'].must_equal 'Origin, Host' + end + + it "decode URL and resolve paths before resource matching" do + header 'Origin', 'http://localhost:3000' + get '/public/a/..%2F..%2Fprivate/stuff' + last_response.wont_render_cors_success + end + + describe 'with array of upstream Vary headers' do + let(:app) { load_app('test', { proxy: true }) } + + it 'should add to them' do + successful_cors_request '/vary_test' + last_response.headers['Vary'].must_equal 'Origin, User-Agent, Host' + end + end + + it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do + successful_cors_request '/', :origin => "http://192.168.0.3:8080" + last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080' + last_response.headers['Vary'].wont_be_nil + end + + it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do + successful_cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080" + last_response.headers['Access-Control-Allow-Origin'].must_equal '*' + last_response.headers['Vary'].must_equal 'Origin' + end + + it 'should support multi allow configurations for the same resource' do + successful_cors_request '/multi-allow-config', :origin => "http://mucho-grande.com" + last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://mucho-grande.com' + last_response.headers['Vary'].must_equal 'Origin' + + successful_cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080" + last_response.headers['Access-Control-Allow-Origin'].must_equal '*' + last_response.headers['Vary'].must_equal 'Origin' + end + + it "should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present" do + options '/get-only' + last_response.headers['Access-Control-Allow-Origin'].must_be_nil + end + + it "should not apply CORS headers if it does not match conditional on resource" do + header 'Origin', 'http://192.168.0.1:1234' + get '/conditional' + last_response.wont_render_cors_success + end + + it "should apply CORS headers if it does match conditional on resource" do + header 'X-OK', '1' + successful_cors_request '/conditional', :origin => 'http://192.168.0.1:1234' + end + + it "should not allow everything if Origin is configured as blank string" do + cors_request '/blank-origin', origin: "http://example.net" + last_response.wont_render_cors_success + end + + it "should not allow credentials for public resources" do + successful_cors_request '/public' + last_response.headers['Access-Control-Allow-Credentials'].must_be_nil + end + + describe 'logging' do + it 'should not log debug messages if debug option is false' do + app = mock + app.stubs(:call).returns(200, {}, ['']) + + logger = mock + logger.expects(:debug).never + + cors = Rack::Cors.new(app, :debug => false, :logger => logger) {} + cors.send(:debug, {}, 'testing') + end + + it 'should log debug messages if debug option is true' do + app = mock + app.stubs(:call).returns(200, {}, ['']) + + logger = mock + logger.expects(:debug) + + cors = Rack::Cors.new(app, :debug => true, :logger => logger) {} + cors.send(:debug, {}, 'testing') + end + + it 'should use rack.logger if available' do + app = mock + app.stubs(:call).returns([200, {}, ['']]) + + logger = mock + logger.expects(:debug).at_least_once + + cors = Rack::Cors.new(app, :debug => true) {} + cors.call({'rack.logger' => logger, 'HTTP_ORIGIN' => 'test.com'}) + end + + it 'should use logger proc' do + app = mock + app.stubs(:call).returns([200, {}, ['']]) + + logger = mock + logger.expects(:debug) + + cors = Rack::Cors.new(app, :debug => true, :logger => proc { logger }) {} + cors.call({'HTTP_ORIGIN' => 'test.com'}) + end + + describe 'with Rails setup' do + after do + ::Rails.logger = nil if defined?(::Rails) + end + + it 'should use Rails.logger if available' do + app = mock + app.stubs(:call).returns([200, {}, ['']]) + + logger = mock + logger.expects(:debug) + + ::Rails = OpenStruct.new(:logger => logger) + + cors = Rack::Cors.new(app, :debug => true) {} + cors.call({'HTTP_ORIGIN' => 'test.com'}) + end + end + + it 'should use Logger if none is set' do + app = mock + app.stubs(:call).returns([200, {}, ['']]) + + logger = mock + Logger.expects(:new).returns(logger) + logger.expects(:tap).returns(logger) + logger.expects(:debug) + + cors = Rack::Cors.new(app, :debug => true) {} + cors.call({'HTTP_ORIGIN' => 'test.com'}) + end + end + + describe 'preflight requests' do + it 'should fail if origin is invalid' do + preflight_request('http://allyourdataarebelongtous.com', '/') + last_response.wont_render_cors_success + cors_result.wont_be :hit + cors_result.must_be :preflight + end + + it 'should fail if Access-Control-Request-Method is not allowed' do + preflight_request('http://localhost:3000', '/get-only', :method => :post) + last_response.wont_render_cors_success + cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD + cors_result.wont_be :hit + cors_result.must_be :preflight + end + + it 'should fail if header is not allowed' do + preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey') + last_response.wont_render_cors_success + cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_HEADER + cors_result.wont_be :hit + cors_result.must_be :preflight + end + + it 'should allow any header if headers = :any' do + preflight_request('http://localhost:3000', '/', :headers => 'Fooey') + last_response.must_render_cors_success + end + + it 'should allow any method if methods = :any' do + preflight_request('http://localhost:3000', '/', :methods => :any) + last_response.must_render_cors_success + end + + it 'allows PATCH method' do + preflight_request('http://localhost:3000', '/', :methods => [ :patch ]) + last_response.must_render_cors_success + end + + it 'should allow header case insensitive match' do + preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token') + last_response.must_render_cors_success + end + + it 'should allow multiple headers match' do + # Webkit style + preflight_request('http://localhost:3000', '/two_headers', :headers => 'X-Requested-With, X-Domain-Token') + last_response.must_render_cors_success + + # Gecko style + preflight_request('http://localhost:3000', '/two_headers', :headers => 'x-requested-with,x-domain-token') + last_response.must_render_cors_success + end + + it "should allow '*' origins to allow any origin" do + preflight_request('http://locohost:3000', '/public') + last_response.must_render_cors_success + last_response.headers['Access-Control-Allow-Origin'].must_equal '*' + end + + it "should allow '//' resource if match pattern is //*" do + preflight_request('http://localhost:3000', '/wildcard/') + last_response.must_render_cors_success + last_response.headers['Access-Control-Allow-Origin'].wont_equal nil + end + + it "should allow '/' resource if match pattern is //*" do + preflight_request('http://localhost:3000', '/wildcard') + last_response.must_render_cors_success + last_response.headers['Access-Control-Allow-Origin'].wont_equal nil + end + + it "should allow '*' origin to allow any origin, and set '*' if no credentials required" do + preflight_request('http://locohost:3000', '/public_without_credentials') + last_response.must_render_cors_success + last_response.headers['Access-Control-Allow-Origin'].must_equal '*' + end + + it 'should return "file://" as header with "file://" as origin' do + preflight_request('file://', '/') + last_response.must_render_cors_success + last_response.headers['Access-Control-Allow-Origin'].must_equal 'file://' + end + + describe '' do + + let(:app) do + test = self + Rack::Builder.new do + use CaptureResult, holder: test + use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do + allow do + origins '*' + resource '/', :methods => :post + end + end + map('/') do + run ->(env) { [500, {}, ['FAIL!']] } + end + end + end + + it "should not send failed preflight requests thru the app" do + preflight_request('http://localhost', '/', :method => :unsupported) + last_response.wont_render_cors_success + last_response.status.must_equal 200 + cors_result.must_be :preflight + cors_result.wont_be :hit + cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD + end + end + end + + describe "with insecure configuration" do + let(:app) { load_app('insecure') } + + it "should raise an error" do + proc { cors_request '/public' }.must_raise Rack::Cors::Resource::CorsMisconfigurationError + end + end + + describe "with non HTTP config" do + let(:app) { load_app("non_http") } + + it 'should support non http/https origins' do + successful_cors_request '/public', origin: 'content://com.company.app' + end + end + + describe 'Rack::Lint' do + def app + @app ||= Rack::Builder.new do + use Rack::Cors + use Rack::Lint + run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] } + end + end + + it 'is lint-compliant with non-CORS request' do + get '/' + last_response.status.must_equal 200 + end + end + + describe 'with app overriding CORS header' do + let(:app) do + Rack::Builder.new do + use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do + allow do + origins '*' + resource '/' + end + end + map('/') do + run ->(env) { [200, {'Access-Control-Allow-Origin' => 'http://foo.net'}, ['success']] } + end + end + end + + it "should return app header" do + successful_cors_request origin: "http://example.net" + last_response.headers['Access-Control-Allow-Origin'].must_equal "http://foo.net" + end + + it "should return original headers if in debug" do + successful_cors_request origin: "http://example.net" + last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal "*" + end + end + + describe 'with headers set to nil' do + let(:app) do + Rack::Builder.new do + use Rack::Cors do + allow do + origins '*' + resource '/', headers: nil + end + end + map('/') do + run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] } + end + end + end + + it 'should succeed with CORS simple headers' do + preflight_request('http://localhost:3000', '/', :headers => 'Accept') + last_response.must_render_cors_success + end + end + + describe 'with custom allowed headers' do + let(:app) do + Rack::Builder.new do + use Rack::Cors do + allow do + origins '*' + resource '/', headers: [] + end + end + map('/') do + run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] } + end + end + end + + it 'should succeed with CORS simple headers' do + preflight_request('http://localhost:3000', '/', :headers => 'Accept') + last_response.must_render_cors_success + preflight_request('http://localhost:3000', '/', :headers => 'Accept-Language') + last_response.must_render_cors_success + preflight_request('http://localhost:3000', '/', :headers => 'Content-Type') + last_response.must_render_cors_success + preflight_request('http://localhost:3000', '/', :headers => 'Content-Language') + last_response.must_render_cors_success + end + end + + protected + def cors_request(*args) + path = args.first.is_a?(String) ? args.first : '/' + + opts = { :method => :get, :origin => 'http://localhost:3000' } + opts.merge! args.last if args.last.is_a?(Hash) + + header 'Origin', opts[:origin] + current_session.__send__ opts[:method], path, {}, test: self + end + + def successful_cors_request(*args) + cors_request(*args) + last_response.must_render_cors_success + end + + def preflight_request(origin, path, opts = {}) + header 'Origin', origin + unless opts.key?(:method) && opts[:method].nil? + header 'Access-Control-Request-Method', opts[:method] ? opts[:method].to_s.upcase : 'GET' + end + if opts[:headers] + header 'Access-Control-Request-Headers', opts[:headers] + end + options path + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/dsl_test.rb b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/dsl_test.rb new file mode 100644 index 0000000000..074d754177 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/dsl_test.rb @@ -0,0 +1,69 @@ +require 'rubygems' +require 'minitest/autorun' +require 'rack/cors' + + +describe Rack::Cors, 'DSL' do + it 'should support explicit config object dsl mode' do + cors = Rack::Cors.new(Proc.new {}) do |cfg| + cfg.allow do |allow| + allow.origins 'localhost:3000', '127.0.0.1:3000' do |source,env| + source == "http://10.10.10.10:3000" && + env["USER_AGENT"] == "test-agent" + end + allow.resource '/get-only', :methods => :get + allow.resource '/', :headers => :any + end + end + resources = cors.send :all_resources + + resources.length.must_equal 1 + resources.first.allow_origin?('http://localhost:3000').must_equal true + resources.first.allow_origin?('http://10.10.10.10:3000',{"USER_AGENT" => "test-agent" }).must_equal true + resources.first.allow_origin?('http://10.10.10.10:3001',{"USER_AGENT" => "test-agent" }).wont_equal true + resources.first.allow_origin?('http://10.10.10.10:3000',{"USER_AGENT" => "other-agent"}).wont_equal true + end + + it 'should support implicit config object dsl mode' do + cors = Rack::Cors.new(Proc.new {}) do + allow do + origins 'localhost:3000', '127.0.0.1:3000' do |source,env| + source == "http://10.10.10.10:3000" && + env["USER_AGENT"] == "test-agent" + end + resource '/get-only', :methods => :get + resource '/', :headers => :any + end + end + resources = cors.send :all_resources + + resources.length.must_equal 1 + resources.first.allow_origin?('http://localhost:3000').must_equal true + resources.first.allow_origin?('http://10.10.10.10:3000',{"USER_AGENT" => "test-agent" }).must_equal true + resources.first.allow_origin?('http://10.10.10.10:3001',{"USER_AGENT" => "test-agent" }).wont_equal true + resources.first.allow_origin?('http://10.10.10.10:3000',{"USER_AGENT" => "other-agent"}).wont_equal true + end + + it 'should support "file://" origin' do + cors = Rack::Cors.new(Proc.new {}) do + allow do + origins 'file://' + resource '/', :headers => :any + end + end + resources = cors.send :all_resources + + resources.first.allow_origin?('file://').must_equal true + end + + it 'should default credentials option to false' do + cors = Rack::Cors.new(Proc.new {}) do + allow do + origins 'example.net' + resource '/', :headers => :any + end + end + resources = cors.send :all_resources + resources.first.resources.first.credentials.must_equal false + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/insecure.ru b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/insecure.ru new file mode 100644 index 0000000000..0fb3e97734 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/insecure.ru @@ -0,0 +1,8 @@ +require 'rack/cors' + +use Rack::Cors do + allow do + origins '*' + resource '/public', credentials: true + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/non_http.ru b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/non_http.ru new file mode 100644 index 0000000000..39f1bd9eaa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/non_http.ru @@ -0,0 +1,8 @@ +require 'rack/cors' + +use Rack::Cors do + allow do + origins 'com.company.app' + resource '/public' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/test.ru b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/test.ru new file mode 100644 index 0000000000..d441ac380f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-cors-1.1.1/test/unit/test.ru @@ -0,0 +1,63 @@ +require 'rack/cors' + +#use Rack::Cors, :debug => true, :logger => ::Logger.new(STDOUT) do +use Rack::Lint +use Rack::Cors do + allow do + origins 'localhost:3000', + '127.0.0.1:3000', + /http:\/\/192\.168\.0\.\d{1,3}(:\d+)?/, + 'file://', + /http:\/\/(.*?)\.example\.com/ + + resource '/get-only', :methods => :get + resource '/', :headers => :any, :methods => :any + resource '/options', :methods => :options + resource '/single_header', :headers => 'x-domain-token' + resource '/two_headers', :headers => %w{x-domain-token x-requested-with} + resource '/expose_single_header', :expose => 'expose-test' + resource '/expose_multiple_headers', :expose => %w{expose-test-1 expose-test-2} + resource '/conditional', :methods => :get, :if => proc { |env| !!env['HTTP_X_OK'] } + resource '/vary_test', :methods => :get, :vary => %w{ Origin Host } + resource '/patch_test', :methods => :patch + resource '/wildcard/*', :methods => :any + # resource '/file/at/*', + # :methods => [:get, :post, :put, :delete], + # :headers => :any, + # :max_age => 0 + end + + allow do + origins do |source,env| + source.end_with?("10.10.10.10:3000") + end + resource '/proc-origin' + end + + allow do + origins -> (source, env) { source.end_with?("10.10.10.10:3000") } + resource '/lambda-origin' + end + + allow do + origins '*' + resource '/public' + resource '/public/*' + resource '/public_without_credentials', :credentials => false + end + + allow do + origins 'mucho-grande.com' + resource '/multi-allow-config', :max_age => 600 + end + + allow do + origins '*' + resource '/multi-allow-config', :max_age => 300, :credentials => false + end + + allow do + origins '' + resource '/blank-origin' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Gemfile new file mode 100644 index 0000000000..66d7b03505 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Gemfile @@ -0,0 +1,13 @@ +source "https://rubygems.org" +# encoding: utf-8 + +gem 'rake' + +rack_version = ENV['rack'].to_s +rack_version = nil if rack_version.empty? or rack_version == 'stable' +rack_version = {:github => 'rack/rack'} if rack_version == 'master' +gem 'rack', rack_version + +gem 'sinatra', path: '..' + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/License b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/License new file mode 100644 index 0000000000..29cdd92f75 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/License @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2011-2017 Konstantin Haase +Copyright (c) 2015-2017 Zachary Scott + +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.0.0/gems/rack-protection-2.1.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/README.md new file mode 100644 index 0000000000..82fbf9842f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/README.md @@ -0,0 +1,118 @@ +# Rack::Protection + +This gem protects against typical web attacks. +Should work for all Rack apps, including Rails. + +# Usage + +Use all protections you probably want to use: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection +run MyApp +``` + +Skip a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection, :except => :path_traversal +run MyApp +``` + +Use a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection::AuthenticityToken +run MyApp +``` + +# Prevented Attacks + +## Cross Site Request Forgery + +Prevented by: + +* [`Rack::Protection::AuthenticityToken`][authenticity-token] (not included by `use Rack::Protection`) +* [`Rack::Protection::FormToken`][form-token] (not included by `use Rack::Protection`) +* [`Rack::Protection::JsonCsrf`][json-csrf] +* [`Rack::Protection::RemoteReferrer`][remote-referrer] (not included by `use Rack::Protection`) +* [`Rack::Protection::RemoteToken`][remote-token] +* [`Rack::Protection::HttpOrigin`][http-origin] + +## Cross Site Scripting + +Prevented by: + +* [`Rack::Protection::EscapedParams`][escaped-params] (not included by `use Rack::Protection`) +* [`Rack::Protection::XSSHeader`][xss-header] (Internet Explorer and Chrome only) +* [`Rack::Protection::ContentSecurityPolicy`][content-security-policy] + +## Clickjacking + +Prevented by: + +* [`Rack::Protection::FrameOptions`][frame-options] + +## Directory Traversal + +Prevented by: + +* [`Rack::Protection::PathTraversal`][path-traversal] + +## Session Hijacking + +Prevented by: + +* [`Rack::Protection::SessionHijacking`][session-hijacking] + +## Cookie Tossing + +Prevented by: +* [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`) + +## IP Spoofing + +Prevented by: + +* [`Rack::Protection::IPSpoofing`][ip-spoofing] + +## Helps to protect against protocol downgrade attacks and cookie hijacking + +Prevented by: + +* [`Rack::Protection::StrictTransport`][strict-transport] (not included by `use Rack::Protection`) + +# Installation + + gem install rack-protection + +# Instrumentation + +Instrumentation is enabled by passing in an instrumenter as an option. +``` +use Rack::Protection, instrumenter: ActiveSupport::Notifications +``` + +The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'. + +[authenticity-token]: http://www.sinatrarb.com/protection/authenticity_token +[content-security-policy]: http://www.sinatrarb.com/protection/content_security_policy +[cookie-tossing]: http://www.sinatrarb.com/protection/cookie_tossing +[escaped-params]: http://www.sinatrarb.com/protection/escaped_params +[form-token]: http://www.sinatrarb.com/protection/form_token +[frame-options]: http://www.sinatrarb.com/protection/frame_options +[http-origin]: http://www.sinatrarb.com/protection/http_origin +[ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing +[json-csrf]: http://www.sinatrarb.com/protection/json_csrf +[path-traversal]: http://www.sinatrarb.com/protection/path_traversal +[remote-referrer]: http://www.sinatrarb.com/protection/remote_referrer +[remote-token]: http://www.sinatrarb.com/protection/remote_token +[session-hijacking]: http://www.sinatrarb.com/protection/session_hijacking +[strict-transport]: http://www.sinatrarb.com/protection/strict_transport +[xss-header]: http://www.sinatrarb.com/protection/xss_header diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Rakefile new file mode 100644 index 0000000000..5bab1699c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/Rakefile @@ -0,0 +1,72 @@ +# encoding: utf-8 +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) + +begin + require 'bundler' + Bundler::GemHelper.install_tasks +rescue LoadError => e + $stderr.puts e +end + +desc "run specs" +task(:spec) { ruby '-S rspec spec' } + +namespace :doc do + task :readmes do + Dir.glob 'lib/rack/protection/*.rb' do |file| + excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb] + next if excluded_files.include?(file) + doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") + file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" + Dir.mkdir "doc" unless File.directory? "doc" + puts "writing #{file}" + File.open(file, "w") { |f| f << doc } + end + end + + task :index do + doc = File.read("README.md") + file = "doc/rack-protection-readme.md" + Dir.mkdir "doc" unless File.directory? "doc" + puts "writing #{file}" + File.open(file, "w") { |f| f << doc } + end + + task :all => [:readmes, :index] +end + +desc "generate documentation" +task :doc => 'doc:all' + +desc "generate gemspec" +task 'rack-protection.gemspec' do + require 'rack/protection/version' + content = File.binread 'rack-protection.gemspec' + + # fetch data + fields = { + :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), + :email => ["mail@zzak.io", "konstantin.haase@gmail.com"], + :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*'] + } + + # insert data + fields.each do |field, values| + updated = " s.#{field} = [" + updated << values.map { |v| "\n %p" % v }.join(',') + updated << "\n ]" + content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) + end + + # set version + content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\"" + + # escape unicode + content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c } + + File.open('rack-protection.gemspec', 'w') { |f| f << content } +end + +task :gemspec => 'rack-protection.gemspec' +task :default => :spec +task :test => :spec diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack-protection.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack-protection.rb new file mode 100644 index 0000000000..1633086e2c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack-protection.rb @@ -0,0 +1 @@ +require "rack/protection" diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection.rb new file mode 100644 index 0000000000..be2c03f323 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection.rb @@ -0,0 +1,57 @@ +require 'rack/protection/version' +require 'rack' + +module Rack + module Protection + autoload :AuthenticityToken, 'rack/protection/authenticity_token' + autoload :Base, 'rack/protection/base' + autoload :CookieTossing, 'rack/protection/cookie_tossing' + autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy' + autoload :EscapedParams, 'rack/protection/escaped_params' + autoload :FormToken, 'rack/protection/form_token' + autoload :FrameOptions, 'rack/protection/frame_options' + autoload :HttpOrigin, 'rack/protection/http_origin' + autoload :IPSpoofing, 'rack/protection/ip_spoofing' + autoload :JsonCsrf, 'rack/protection/json_csrf' + autoload :PathTraversal, 'rack/protection/path_traversal' + autoload :ReferrerPolicy, 'rack/protection/referrer_policy' + autoload :RemoteReferrer, 'rack/protection/remote_referrer' + autoload :RemoteToken, 'rack/protection/remote_token' + autoload :SessionHijacking, 'rack/protection/session_hijacking' + autoload :StrictTransport, 'rack/protection/strict_transport' + autoload :XSSHeader, 'rack/protection/xss_header' + + def self.new(app, options = {}) + # does not include: RemoteReferrer, AuthenticityToken and FormToken + except = Array options[:except] + use_these = Array options[:use] + + if options.fetch(:without_session, false) + except += [:session_hijacking, :remote_token] + end + + Rack::Builder.new do + # Off by default, unless added + use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token + use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy + use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing + use ::Rack::Protection::EscapedParams, options if use_these.include? :escaped_params + use ::Rack::Protection::FormToken, options if use_these.include? :form_token + use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy + use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer + use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport + + # On by default, unless skipped + use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options + use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin + use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing + use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf + use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal + use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token + use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking + use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header + run app + end.to_app + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/authenticity_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/authenticity_token.rb new file mode 100644 index 0000000000..23a7784256 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/authenticity_token.rb @@ -0,0 +1,203 @@ +require 'rack/protection' +require 'securerandom' +require 'base64' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # This middleware only accepts requests other than GET, + # HEAD, OPTIONS, TRACE if their given access + # token matches the token included in the session. + # + # It checks the X-CSRF-Token header and the POST form + # data. + # + # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. + # + # == Options + # + # [:authenticity_param] the name of the param that should contain + # the token on a request. Default value: + # "authenticity_token" + # + # == Example: Forms application + # + # To show what the AuthenticityToken does, this section includes a sample + # program which shows two forms. One with, and one without a CSRF token + # The one without CSRF token field will get a 403 Forbidden response. + # + # Install the gem, then run the program: + # + # gem install 'rack-protection' + # ruby server.rb + # + # Here is server.rb: + # + # require 'rack/protection' + # + # app = Rack::Builder.app do + # use Rack::Session::Cookie, secret: 'secret' + # use Rack::Protection::AuthenticityToken + # + # run -> (env) do + # [200, {}, [ + # <<~EOS + # + # + # + # + # rack-protection minimal example + # + # + #

    Without Authenticity Token

    + #

    This takes you to Forbidden

    + #
    + # + # + #
    + # + #

    With Authenticity Token

    + #

    This successfully takes you to back to this form.

    + #
    + # + # + # + #
    + # + # + # EOS + # ]] + # end + # end + # + # Rack::Handler::WEBrick.run app + # + # == Example: Customize which POST parameter holds the token + # + # To customize the authenticity parameter for form data, use the + # :authenticity_param option: + # use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name' + class AuthenticityToken < Base + TOKEN_LENGTH = 32 + + default_options :authenticity_param => 'authenticity_token', + :allow_if => nil + + def self.token(session) + self.new(nil).mask_authenticity_token(session) + end + + def self.random_token + SecureRandom.base64(TOKEN_LENGTH) + end + + def accepts?(env) + session = session env + set_token(session) + + safe?(env) || + valid_token?(session, env['HTTP_X_CSRF_TOKEN']) || + valid_token?(session, Request.new(env).params[options[:authenticity_param]]) || + ( options[:allow_if] && options[:allow_if].call(env) ) + end + + def mask_authenticity_token(session) + token = set_token(session) + mask_token(token) + end + + private + + def set_token(session) + session[:csrf] ||= self.class.random_token + end + + # Checks the client's masked token to see if it matches the + # session token. + def valid_token?(session, token) + return false if token.nil? || token.empty? + + begin + token = decode_token(token) + rescue ArgumentError # encoded_masked_token is invalid Base64 + return false + end + + # See if it's actually a masked token or not. We should be able + # to handle any unmasked tokens that we've issued without error. + + if unmasked_token?(token) + compare_with_real_token token, session + + elsif masked_token?(token) + token = unmask_token(token) + + compare_with_real_token token, session + + else + false # Token is malformed + end + end + + # Creates a masked version of the authenticity token that varies + # on each request. The masking is used to mitigate SSL attacks + # like BREACH. + def mask_token(token) + token = decode_token(token) + one_time_pad = SecureRandom.random_bytes(token.length) + encrypted_token = xor_byte_strings(one_time_pad, token) + masked_token = one_time_pad + encrypted_token + encode_token(masked_token) + end + + # Essentially the inverse of +mask_token+. + def unmask_token(masked_token) + # Split the token into the one-time pad and the encrypted + # value and decrypt it + token_length = masked_token.length / 2 + one_time_pad = masked_token[0...token_length] + encrypted_token = masked_token[token_length..-1] + xor_byte_strings(one_time_pad, encrypted_token) + end + + def unmasked_token?(token) + token.length == TOKEN_LENGTH + end + + def masked_token?(token) + token.length == TOKEN_LENGTH * 2 + end + + def compare_with_real_token(token, session) + secure_compare(token, real_token(session)) + end + + def real_token(session) + decode_token(session[:csrf]) + end + + def encode_token(token) + Base64.strict_encode64(token) + end + + def decode_token(token) + Base64.strict_decode64(token) + end + + def xor_byte_strings(s1, s2) + s2 = s2.dup + size = s1.bytesize + i = 0 + while i < size + s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i)) + i += 1 + end + s2 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb new file mode 100644 index 0000000000..83e724069b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb @@ -0,0 +1,126 @@ +require 'rack/protection' +require 'rack/utils' +require 'digest' +require 'logger' +require 'uri' + +module Rack + module Protection + class Base + DEFAULT_OPTIONS = { + :reaction => :default_reaction, :logging => true, + :message => 'Forbidden', :encryptor => Digest::SHA1, + :session_key => 'rack.session', :status => 403, + :allow_empty_referrer => true, + :report_key => "protection.failed", + :html_types => %w[text/html application/xhtml text/xml application/xml] + } + + attr_reader :app, :options + + def self.default_options(options) + define_method(:default_options) { super().merge(options) } + end + + def self.default_reaction(reaction) + alias_method(:default_reaction, reaction) + end + + def default_options + DEFAULT_OPTIONS + end + + def initialize(app, options = {}) + @app, @options = app, default_options.merge(options) + end + + def safe?(env) + %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD'] + end + + def accepts?(env) + raise NotImplementedError, "#{self.class} implementation pending" + end + + def call(env) + unless accepts? env + instrument env + result = react env + end + result or app.call(env) + end + + def react(env) + result = send(options[:reaction], env) + result if Array === result and result.size == 3 + end + + def warn(env, message) + return unless options[:logging] + l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) + l.warn(message) + end + + def instrument(env) + return unless i = options[:instrumenter] + env['rack.protection.attack'] = self.class.name.split('::').last.downcase + i.instrument('rack.protection', env) + end + + def deny(env) + warn env, "attack prevented by #{self.class}" + [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] + end + + def report(env) + warn env, "attack reported by #{self.class}" + env[options[:report_key]] = true + end + + def session?(env) + env.include? options[:session_key] + end + + def session(env) + return env[options[:session_key]] if session? env + fail "you need to set up a session middleware *before* #{self.class}" + end + + def drop_session(env) + session(env).clear if session? env + end + + def referrer(env) + ref = env['HTTP_REFERER'].to_s + return if !options[:allow_empty_referrer] and ref.empty? + URI.parse(ref).host || Request.new(env).host + rescue URI::InvalidURIError + end + + def origin(env) + env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN'] + end + + def random_string(secure = defined? SecureRandom) + secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) + rescue NotImplementedError + random_string false + end + + def encrypt(value) + options[:encryptor].hexdigest value.to_s + end + + def secure_compare(a, b) + Rack::Utils.secure_compare(a.to_s, b.to_s) + end + + alias default_reaction deny + + def html?(headers) + return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } + options[:html_types].include? header.last[/^\w+\/\w+/] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/content_security_policy.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/content_security_policy.rb new file mode 100644 index 0000000000..91eea925ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/content_security_policy.rb @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: XSS and others + # Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+ + # + # Description:: Content Security Policy, a mechanism web applications + # can use to mitigate a broad class of content injection + # vulnerabilities, such as cross-site scripting (XSS). + # Content Security Policy is a declarative policy that lets + # the authors (or server administrators) of a web application + # inform the client about the sources from which the + # application expects to load resources. + # + # More info:: W3C CSP Level 1 : https://www.w3.org/TR/CSP1/ (deprecated) + # W3C CSP Level 2 : https://www.w3.org/TR/CSP2/ (current) + # W3C CSP Level 3 : https://www.w3.org/TR/CSP3/ (draft) + # https://developer.mozilla.org/en-US/docs/Web/Security/CSP + # http://caniuse.com/#search=ContentSecurityPolicy + # http://content-security-policy.com/ + # https://securityheaders.io + # https://scotthelme.co.uk/csp-cheat-sheet/ + # http://www.html5rocks.com/en/tutorials/security/content-security-policy/ + # + # Sets the 'Content-Security-Policy[-Report-Only]' header. + # + # Options: ContentSecurityPolicy configuration is a complex topic with + # several levels of support that has evolved over time. + # See the W3C documentation and the links in the more info + # section for CSP usage examples and best practices. The + # CSP3 directives in the 'NO_ARG_DIRECTIVES' constant need to be + # presented in the options hash with a boolean 'true' in order + # to be used in a policy. + # + class ContentSecurityPolicy < Base + default_options default_src: "'self'", report_only: false + + DIRECTIVES = %i(base_uri child_src connect_src default_src + font_src form_action frame_ancestors frame_src + img_src manifest_src media_src object_src + plugin_types referrer reflected_xss report_to + report_uri require_sri_for sandbox script_src + style_src worker_src webrtc_src navigate_to + prefetch_src).freeze + + NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener + upgrade_insecure_requests).freeze + + def csp_policy + directives = [] + + DIRECTIVES.each do |d| + if options.key?(d) + directives << "#{d.to_s.sub(/_/, '-')} #{options[d]}" + end + end + + # Set these key values to boolean 'true' to include in policy + NO_ARG_DIRECTIVES.each do |d| + if options.key?(d) && options[d].is_a?(TrueClass) + directives << d.to_s.tr('_', '-') + end + end + + directives.compact.sort.join('; ') + end + + def call(env) + status, headers, body = @app.call(env) + header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy' + headers[header] ||= csp_policy if html? headers + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/cookie_tossing.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/cookie_tossing.rb new file mode 100644 index 0000000000..10614d5261 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/cookie_tossing.rb @@ -0,0 +1,75 @@ +require 'rack/protection' +require 'pathname' + +module Rack + module Protection + ## + # Prevented attack:: Cookie Tossing + # Supported browsers:: all + # More infos:: https://github.com/blog/1466-yummy-cookies-across-domains + # + # Does not accept HTTP requests if the HTTP_COOKIE header contains more than one + # session cookie. This does not protect against a cookie overflow attack. + # + # Options: + # + # session_key:: The name of the session cookie (default: 'rack.session') + class CookieTossing < Base + default_reaction :deny + + def call(env) + status, headers, body = super + response = Rack::Response.new(body, status, headers) + request = Rack::Request.new(env) + remove_bad_cookies(request, response) + response.finish + end + + def accepts?(env) + cookie_header = env['HTTP_COOKIE'] + cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s } + cookies.each do |k, v| + if k == session_key && Array(v).size > 1 + bad_cookies << k + elsif k != session_key && Rack::Utils.unescape(k) == session_key + bad_cookies << k + end + end + bad_cookies.empty? + end + + def remove_bad_cookies(request, response) + return if bad_cookies.empty? + paths = cookie_paths(request.path) + bad_cookies.each do |name| + paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) } + end + end + + def redirect(env) + request = Request.new(env) + warn env, "attack prevented by #{self.class}" + [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []] + end + + def bad_cookies + @bad_cookies ||= [] + end + + def cookie_paths(path) + path = '/' if path.to_s.empty? + paths = [] + Pathname.new(path).descend { |p| paths << p.to_s } + paths + end + + def empty_cookie(host, path) + {:value => '', :domain => host, :path => path, :expires => Time.at(0)} + end + + def session_key + @session_key ||= options[:session_key] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/escaped_params.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/escaped_params.rb new file mode 100644 index 0000000000..6706d1030e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/escaped_params.rb @@ -0,0 +1,89 @@ +require 'rack/protection' +require 'rack/utils' +require 'tempfile' + +begin + require 'escape_utils' +rescue LoadError +end + +module Rack + module Protection + ## + # Prevented attack:: XSS + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting + # + # Automatically escapes Rack::Request#params so they can be embedded in HTML + # or JavaScript without any further issues. Calls +html_safe+ on the escaped + # strings if defined, to avoid double-escaping in Rails. + # + # Options: + # escape:: What escaping modes to use, should be Symbol or Array of Symbols. + # Available: :html (default), :javascript, :url + class EscapedParams < Base + extend Rack::Utils + + class << self + alias escape_url escape + public :escape_html + end + + default_options :escape => :html, + :escaper => defined?(EscapeUtils) ? EscapeUtils : self + + def initialize(*) + super + + modes = Array options[:escape] + @escaper = options[:escaper] + @html = modes.include? :html + @javascript = modes.include? :javascript + @url = modes.include? :url + + if @javascript and not @escaper.respond_to? :escape_javascript + fail("Use EscapeUtils for JavaScript escaping.") + end + end + + def call(env) + request = Request.new(env) + get_was = handle(request.GET) + post_was = handle(request.POST) rescue nil + app.call env + ensure + request.GET.replace get_was if get_was + request.POST.replace post_was if post_was + end + + def handle(hash) + was = hash.dup + hash.replace escape(hash) + was + end + + def escape(object) + case object + when Hash then escape_hash(object) + when Array then object.map { |o| escape(o) } + when String then escape_string(object) + when Tempfile then object + else nil + end + end + + def escape_hash(hash) + hash = hash.dup + hash.each { |k,v| hash[k] = escape(v) } + hash + end + + def escape_string(str) + str = @escaper.escape_url(str) if @url + str = @escaper.escape_html(str) if @html + str = @escaper.escape_javascript(str) if @javascript + str + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/form_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/form_token.rb new file mode 100644 index 0000000000..994740f83f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/form_token.rb @@ -0,0 +1,23 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts submitted forms if a given access token matches the token + # included in the session. Does not expect such a token from Ajax request. + # + # This middleware is not used when using the Rack::Protection collection, + # since it might be a security issue, depending on your application + # + # Compatible with rack-csrf. + class FormToken < AuthenticityToken + def accepts?(env) + env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb new file mode 100644 index 0000000000..bce75c47e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb @@ -0,0 +1,37 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Clickjacking + # Supported browsers:: Internet Explorer 8, Firefox 3.6.9, Opera 10.50, + # Safari 4.0, Chrome 4.1.249.1042 and later + # More infos:: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header + # + # Sets X-Frame-Options header to tell the browser avoid embedding the page + # in a frame. + # + # Options: + # + # frame_options:: Defines who should be allowed to embed the page in a + # frame. Use :deny to forbid any embedding, :sameorigin + # to allow embedding from the same origin (default). + class FrameOptions < Base + default_options :frame_options => :sameorigin + + def frame_options + @frame_options ||= begin + frame_options = options[:frame_options] + frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str + frame_options.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['X-Frame-Options'] ||= frame_options if html? headers + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/http_origin.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/http_origin.rb new file mode 100644 index 0000000000..e25f1a9ccd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/http_origin.rb @@ -0,0 +1,47 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: Google Chrome 2, Safari 4 and later + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # http://tools.ietf.org/html/draft-abarth-origin + # + # Does not accept unsafe HTTP requests when value of Origin HTTP request header + # does not match default or permitted URIs. + # + # If you want to permit a specific domain, you can pass in as the `:permitted_origins` option: + # + # use Rack::Protection, permitted_origins: ["http://localhost:3000", "http://127.0.01:3000"] + # + # The `:allow_if` option can also be set to a proc to use custom allow/deny logic. + class HttpOrigin < Base + DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } + default_reaction :deny + default_options :allow_if => nil + + def base_url(env) + request = Rack::Request.new(env) + port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme] + "#{request.scheme}://#{request.host}#{port}" + end + + def accepts?(env) + return true if safe? env + return true unless origin = env['HTTP_ORIGIN'] + return true if base_url(env) == origin + return true if options[:allow_if] && options[:allow_if].call(env) + + if options.key? :origin_whitelist + warn "Rack::Protection origin_whitelist option is deprecated and will be removed, " \ + "use permitted_origins instead.\n" + end + + permitted_origins = options[:permitted_origins] || options[:origin_whitelist] + Array(permitted_origins).include? origin + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/ip_spoofing.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/ip_spoofing.rb new file mode 100644 index 0000000000..3e404ad7c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/ip_spoofing.rb @@ -0,0 +1,23 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: IP spoofing + # Supported browsers:: all + # More infos:: http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ + # + # Detect (some) IP spoofing attacks. + class IPSpoofing < Base + default_reaction :deny + + def accepts?(env) + return true unless env.include? 'HTTP_X_FORWARDED_FOR' + ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) + return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] + return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] + true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb new file mode 100644 index 0000000000..5ec826b719 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb @@ -0,0 +1,57 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://flask.pocoo.org/docs/0.10/security/#json-security + # http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + # + # JSON GET APIs are vulnerable to being embedded as JavaScript when the + # Array prototype has been patched to track data. Checks the referrer + # even on GET requests if the content type is JSON. + # + # If request includes Origin HTTP header, defers to HttpOrigin to determine + # if the request is safe. Please refer to the documentation for more info. + # + # The `:allow_if` option can be set to a proc to use custom allow/deny logic. + class JsonCsrf < Base + default_options :allow_if => nil + + alias react deny + + def call(env) + request = Request.new(env) + status, headers, body = app.call(env) + + if has_vector?(request, headers) + warn env, "attack prevented by #{self.class}" + + react_and_close(env, body) or [status, headers, body] + else + [status, headers, body] + end + end + + def has_vector?(request, headers) + return false if request.xhr? + return false if options[:allow_if] && options[:allow_if].call(request.env) + return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ + origin(request.env).nil? and referrer(request.env) != request.host + end + + def react_and_close(env, body) + reaction = react(env) + + close_body(body) if reaction + + reaction + end + + def close_body(body) + body.close if body.respond_to?(:close) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb new file mode 100644 index 0000000000..4e8c94ca95 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb @@ -0,0 +1,42 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Directory traversal + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Directory_traversal + # + # Unescapes '/' and '.', expands +path_info+. + # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. + class PathTraversal < Base + def call(env) + path_was = env["PATH_INFO"] + env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? + app.call env + ensure + env["PATH_INFO"] = path_was + end + + def cleanup(path) + encoding = path.encoding + dot = '.'.encode(encoding) + slash = '/'.encode(encoding) + backslash = '\\'.encode(encoding) + + parts = [] + unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash) + unescaped = unescaped.gsub(backslash, slash) + + unescaped.split(slash).each do |part| + next if part.empty? or part == dot + part == '..' ? parts.pop : parts << part + end + + cleaned = slash + parts.join(slash) + cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} + cleaned + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/referrer_policy.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/referrer_policy.rb new file mode 100644 index 0000000000..f977d72e4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/referrer_policy.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Secret leakage, third party tracking + # Supported browsers:: mixed support + # More infos:: https://www.w3.org/TR/referrer-policy/ + # https://caniuse.com/#search=referrer-policy + # + # Sets Referrer-Policy header to tell the browser to limit the Referer header. + # + # Options: + # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin') + class ReferrerPolicy < Base + default_options :referrer_policy => 'strict-origin-when-cross-origin' + + def call(env) + status, headers, body = @app.call(env) + headers['Referrer-Policy'] ||= options[:referrer_policy] + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_referrer.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_referrer.rb new file mode 100644 index 0000000000..5375ebc3d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_referrer.rb @@ -0,0 +1,20 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Does not accept unsafe HTTP requests if the Referer [sic] header is set to + # a different host. + class RemoteReferrer < Base + default_reaction :deny + + def accepts?(env) + safe?(env) or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_token.rb new file mode 100644 index 0000000000..f3922c8739 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/remote_token.rb @@ -0,0 +1,22 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts unsafe HTTP requests if a given access token matches the token + # included in the session *or* the request comes from the same origin. + # + # Compatible with rack-csrf. + class RemoteToken < AuthenticityToken + default_reaction :deny + + def accepts?(env) + super or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/session_hijacking.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/session_hijacking.rb new file mode 100644 index 0000000000..a7dd54b742 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/session_hijacking.rb @@ -0,0 +1,36 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Session Hijacking + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Session_hijacking + # + # Tracks request properties like the user agent in the session and empties + # the session if those properties change. This essentially prevents attacks + # from Firesheep. Since all headers taken into consideration can be + # spoofed, too, this will not prevent determined hijacking attempts. + class SessionHijacking < Base + default_reaction :drop_session + default_options :tracking_key => :tracking, :encrypt_tracking => true, + :track => %w[HTTP_USER_AGENT] + + def accepts?(env) + session = session env + key = options[:tracking_key] + if session.include? key + session[key].all? { |k,v| v == encrypt(env[k]) } + else + session[key] = {} + options[:track].each { |k| session[key][k] = encrypt(env[k]) } + end + end + + def encrypt(value) + value = value.to_s.downcase + options[:encrypt_tracking] ? super(value) : value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/strict_transport.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/strict_transport.rb new file mode 100644 index 0000000000..b4283bab36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/strict_transport.rb @@ -0,0 +1,39 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Protects against against protocol downgrade attacks and cookie hijacking. + # Supported browsers:: all + # More infos:: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security + # + # browser will prevent any communications from being sent over HTTP + # to the specified domain and will instead send all communications over HTTPS. + # It also prevents HTTPS click through prompts on browsers. + # + # Options: + # + # max_age:: How long future requests to the domain should go over HTTPS; specified in seconds + # include_subdomains:: If all present and future subdomains will be HTTPS + # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/ + + class StrictTransport < Base + default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false + + def strict_transport + @strict_transport ||= begin + strict_transport = 'max-age=' + options[:max_age].to_s + strict_transport += '; includeSubDomains' if options[:include_subdomains] + strict_transport += '; preload' if options[:preload] + strict_transport.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['Strict-Transport-Security'] ||= strict_transport + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/version.rb new file mode 100644 index 0000000000..872af0c01c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/version.rb @@ -0,0 +1,5 @@ +module Rack + module Protection + VERSION = '2.1.0' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb new file mode 100644 index 0000000000..eb6f92fe36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Non-permanent XSS + # Supported browsers:: Internet Explorer 8+ and Chrome + # More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx + # + # Sets X-XSS-Protection header to tell the browser to block attacks. + # + # Options: + # xss_mode:: How the browser should prevent the attack (default: :block) + class XSSHeader < Base + default_options :xss_mode => :block, :nosniff => true + + def call(env) + status, headers, body = @app.call(env) + headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers + headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff] + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/rack-protection.gemspec b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/rack-protection.gemspec new file mode 100644 index 0000000000..817649d44c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/rack-protection.gemspec @@ -0,0 +1,40 @@ +version = File.read(File.expand_path("../../VERSION", __FILE__)).strip + +Gem::Specification.new do |s| + # general infos + s.name = "rack-protection" + s.version = version + s.description = "Protect against typical web attacks, works with all Rack apps, including Rails." + s.homepage = "http://sinatrarb.com/protection/" + s.summary = s.description + s.license = 'MIT' + s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] + s.email = "sinatrarb@googlegroups.com" + s.files = Dir["lib/**/*.rb"] + [ + "License", + "README.md", + "Rakefile", + "Gemfile", + "rack-protection.gemspec" + ] + + if s.respond_to?(:metadata) + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', + 'homepage_uri' => 'http://sinatrarb.com/protection/', + 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection' + } + else + raise <<-EOF +RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: + gem install rubygems-update + update_rubygems: + gem update --system +EOF + end + + # dependencies + s.add_dependency "rack" + s.add_development_dependency "rack-test" + s.add_development_dependency "rspec", "~> 3.6" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Gemfile new file mode 100644 index 0000000000..66d7b03505 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Gemfile @@ -0,0 +1,13 @@ +source "https://rubygems.org" +# encoding: utf-8 + +gem 'rake' + +rack_version = ENV['rack'].to_s +rack_version = nil if rack_version.empty? or rack_version == 'stable' +rack_version = {:github => 'rack/rack'} if rack_version == 'master' +gem 'rack', rack_version + +gem 'sinatra', path: '..' + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/License b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/License new file mode 100644 index 0000000000..29cdd92f75 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/License @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2011-2017 Konstantin Haase +Copyright (c) 2015-2017 Zachary Scott + +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.0.0/gems/rack-protection-2.2.4/README.md b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/README.md new file mode 100644 index 0000000000..82fbf9842f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/README.md @@ -0,0 +1,118 @@ +# Rack::Protection + +This gem protects against typical web attacks. +Should work for all Rack apps, including Rails. + +# Usage + +Use all protections you probably want to use: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection +run MyApp +``` + +Skip a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection, :except => :path_traversal +run MyApp +``` + +Use a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection::AuthenticityToken +run MyApp +``` + +# Prevented Attacks + +## Cross Site Request Forgery + +Prevented by: + +* [`Rack::Protection::AuthenticityToken`][authenticity-token] (not included by `use Rack::Protection`) +* [`Rack::Protection::FormToken`][form-token] (not included by `use Rack::Protection`) +* [`Rack::Protection::JsonCsrf`][json-csrf] +* [`Rack::Protection::RemoteReferrer`][remote-referrer] (not included by `use Rack::Protection`) +* [`Rack::Protection::RemoteToken`][remote-token] +* [`Rack::Protection::HttpOrigin`][http-origin] + +## Cross Site Scripting + +Prevented by: + +* [`Rack::Protection::EscapedParams`][escaped-params] (not included by `use Rack::Protection`) +* [`Rack::Protection::XSSHeader`][xss-header] (Internet Explorer and Chrome only) +* [`Rack::Protection::ContentSecurityPolicy`][content-security-policy] + +## Clickjacking + +Prevented by: + +* [`Rack::Protection::FrameOptions`][frame-options] + +## Directory Traversal + +Prevented by: + +* [`Rack::Protection::PathTraversal`][path-traversal] + +## Session Hijacking + +Prevented by: + +* [`Rack::Protection::SessionHijacking`][session-hijacking] + +## Cookie Tossing + +Prevented by: +* [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`) + +## IP Spoofing + +Prevented by: + +* [`Rack::Protection::IPSpoofing`][ip-spoofing] + +## Helps to protect against protocol downgrade attacks and cookie hijacking + +Prevented by: + +* [`Rack::Protection::StrictTransport`][strict-transport] (not included by `use Rack::Protection`) + +# Installation + + gem install rack-protection + +# Instrumentation + +Instrumentation is enabled by passing in an instrumenter as an option. +``` +use Rack::Protection, instrumenter: ActiveSupport::Notifications +``` + +The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'. + +[authenticity-token]: http://www.sinatrarb.com/protection/authenticity_token +[content-security-policy]: http://www.sinatrarb.com/protection/content_security_policy +[cookie-tossing]: http://www.sinatrarb.com/protection/cookie_tossing +[escaped-params]: http://www.sinatrarb.com/protection/escaped_params +[form-token]: http://www.sinatrarb.com/protection/form_token +[frame-options]: http://www.sinatrarb.com/protection/frame_options +[http-origin]: http://www.sinatrarb.com/protection/http_origin +[ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing +[json-csrf]: http://www.sinatrarb.com/protection/json_csrf +[path-traversal]: http://www.sinatrarb.com/protection/path_traversal +[remote-referrer]: http://www.sinatrarb.com/protection/remote_referrer +[remote-token]: http://www.sinatrarb.com/protection/remote_token +[session-hijacking]: http://www.sinatrarb.com/protection/session_hijacking +[strict-transport]: http://www.sinatrarb.com/protection/strict_transport +[xss-header]: http://www.sinatrarb.com/protection/xss_header diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Rakefile new file mode 100644 index 0000000000..5bab1699c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/Rakefile @@ -0,0 +1,72 @@ +# encoding: utf-8 +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) + +begin + require 'bundler' + Bundler::GemHelper.install_tasks +rescue LoadError => e + $stderr.puts e +end + +desc "run specs" +task(:spec) { ruby '-S rspec spec' } + +namespace :doc do + task :readmes do + Dir.glob 'lib/rack/protection/*.rb' do |file| + excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb] + next if excluded_files.include?(file) + doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") + file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" + Dir.mkdir "doc" unless File.directory? "doc" + puts "writing #{file}" + File.open(file, "w") { |f| f << doc } + end + end + + task :index do + doc = File.read("README.md") + file = "doc/rack-protection-readme.md" + Dir.mkdir "doc" unless File.directory? "doc" + puts "writing #{file}" + File.open(file, "w") { |f| f << doc } + end + + task :all => [:readmes, :index] +end + +desc "generate documentation" +task :doc => 'doc:all' + +desc "generate gemspec" +task 'rack-protection.gemspec' do + require 'rack/protection/version' + content = File.binread 'rack-protection.gemspec' + + # fetch data + fields = { + :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), + :email => ["mail@zzak.io", "konstantin.haase@gmail.com"], + :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*'] + } + + # insert data + fields.each do |field, values| + updated = " s.#{field} = [" + updated << values.map { |v| "\n %p" % v }.join(',') + updated << "\n ]" + content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) + end + + # set version + content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\"" + + # escape unicode + content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c } + + File.open('rack-protection.gemspec', 'w') { |f| f << content } +end + +task :gemspec => 'rack-protection.gemspec' +task :default => :spec +task :test => :spec diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack-protection.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack-protection.rb new file mode 100644 index 0000000000..1633086e2c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack-protection.rb @@ -0,0 +1 @@ +require "rack/protection" diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection.rb new file mode 100644 index 0000000000..be2c03f323 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection.rb @@ -0,0 +1,57 @@ +require 'rack/protection/version' +require 'rack' + +module Rack + module Protection + autoload :AuthenticityToken, 'rack/protection/authenticity_token' + autoload :Base, 'rack/protection/base' + autoload :CookieTossing, 'rack/protection/cookie_tossing' + autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy' + autoload :EscapedParams, 'rack/protection/escaped_params' + autoload :FormToken, 'rack/protection/form_token' + autoload :FrameOptions, 'rack/protection/frame_options' + autoload :HttpOrigin, 'rack/protection/http_origin' + autoload :IPSpoofing, 'rack/protection/ip_spoofing' + autoload :JsonCsrf, 'rack/protection/json_csrf' + autoload :PathTraversal, 'rack/protection/path_traversal' + autoload :ReferrerPolicy, 'rack/protection/referrer_policy' + autoload :RemoteReferrer, 'rack/protection/remote_referrer' + autoload :RemoteToken, 'rack/protection/remote_token' + autoload :SessionHijacking, 'rack/protection/session_hijacking' + autoload :StrictTransport, 'rack/protection/strict_transport' + autoload :XSSHeader, 'rack/protection/xss_header' + + def self.new(app, options = {}) + # does not include: RemoteReferrer, AuthenticityToken and FormToken + except = Array options[:except] + use_these = Array options[:use] + + if options.fetch(:without_session, false) + except += [:session_hijacking, :remote_token] + end + + Rack::Builder.new do + # Off by default, unless added + use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token + use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy + use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing + use ::Rack::Protection::EscapedParams, options if use_these.include? :escaped_params + use ::Rack::Protection::FormToken, options if use_these.include? :form_token + use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy + use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer + use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport + + # On by default, unless skipped + use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options + use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin + use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing + use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf + use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal + use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token + use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking + use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header + run app + end.to_app + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/authenticity_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/authenticity_token.rb new file mode 100644 index 0000000000..d7445e2369 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/authenticity_token.rb @@ -0,0 +1,251 @@ +require 'rack/protection' +require 'securerandom' +require 'openssl' +require 'base64' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # This middleware only accepts requests other than GET, + # HEAD, OPTIONS, TRACE if their given access + # token matches the token included in the session. + # + # It checks the X-CSRF-Token header and the POST form + # data. + # + # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. + # + # == Options + # + # [:authenticity_param] the name of the param that should contain + # the token on a request. Default value: + # "authenticity_token" + # + # [:key] the name of the param that should contain + # the token in the session. Default value: + # :csrf + # + # [:allow_if] a proc for custom allow/deny logic. Default value: + # nil + # + # == Example: Forms application + # + # To show what the AuthenticityToken does, this section includes a sample + # program which shows two forms. One with, and one without a CSRF token + # The one without CSRF token field will get a 403 Forbidden response. + # + # Install the gem, then run the program: + # + # gem install 'rack-protection' + # ruby server.rb + # + # Here is server.rb: + # + # require 'rack/protection' + # + # app = Rack::Builder.app do + # use Rack::Session::Cookie, secret: 'secret' + # use Rack::Protection::AuthenticityToken + # + # run -> (env) do + # [200, {}, [ + # <<~EOS + # + # + # + # + # rack-protection minimal example + # + # + #

    Without Authenticity Token

    + #

    This takes you to Forbidden

    + #
    + # + # + #
    + # + #

    With Authenticity Token

    + #

    This successfully takes you to back to this form.

    + #
    + # + # + # + #
    + # + # + # EOS + # ]] + # end + # end + # + # Rack::Handler::WEBrick.run app + # + # == Example: Customize which POST parameter holds the token + # + # To customize the authenticity parameter for form data, use the + # :authenticity_param option: + # use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name' + class AuthenticityToken < Base + TOKEN_LENGTH = 32 + + default_options :authenticity_param => 'authenticity_token', + :key => :csrf, + :allow_if => nil + + def self.token(session, path: nil, method: :post) + self.new(nil).mask_authenticity_token(session, path: path, method: method) + end + + def self.random_token + SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false) + end + + def accepts?(env) + session = session(env) + set_token(session) + + safe?(env) || + valid_token?(env, env['HTTP_X_CSRF_TOKEN']) || + valid_token?(env, Request.new(env).params[options[:authenticity_param]]) || + ( options[:allow_if] && options[:allow_if].call(env) ) + rescue + false + end + + def mask_authenticity_token(session, path: nil, method: :post) + set_token(session) + + token = if path && method + per_form_token(session, path, method) + else + global_token(session) + end + + mask_token(token) + end + + GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token' + private_constant :GLOBAL_TOKEN_IDENTIFIER + + private + + def set_token(session) + session[options[:key]] ||= self.class.random_token + end + + # Checks the client's masked token to see if it matches the + # session token. + def valid_token?(env, token) + return false if token.nil? || token.empty? + + session = session(env) + + begin + token = decode_token(token) + rescue ArgumentError # encoded_masked_token is invalid Base64 + return false + end + + # See if it's actually a masked token or not. We should be able + # to handle any unmasked tokens that we've issued without error. + + if unmasked_token?(token) + compare_with_real_token(token, session) + elsif masked_token?(token) + token = unmask_token(token) + + compare_with_global_token(token, session) || + compare_with_real_token(token, session) || + compare_with_per_form_token(token, session, Request.new(env)) + else + false # Token is malformed + end + end + + # Creates a masked version of the authenticity token that varies + # on each request. The masking is used to mitigate SSL attacks + # like BREACH. + def mask_token(token) + one_time_pad = SecureRandom.random_bytes(token.length) + encrypted_token = xor_byte_strings(one_time_pad, token) + masked_token = one_time_pad + encrypted_token + encode_token(masked_token) + end + + # Essentially the inverse of +mask_token+. + def unmask_token(masked_token) + # Split the token into the one-time pad and the encrypted + # value and decrypt it + token_length = masked_token.length / 2 + one_time_pad = masked_token[0...token_length] + encrypted_token = masked_token[token_length..-1] + xor_byte_strings(one_time_pad, encrypted_token) + end + + def unmasked_token?(token) + token.length == TOKEN_LENGTH + end + + def masked_token?(token) + token.length == TOKEN_LENGTH * 2 + end + + def compare_with_real_token(token, session) + secure_compare(token, real_token(session)) + end + + def compare_with_global_token(token, session) + secure_compare(token, global_token(session)) + end + + def compare_with_per_form_token(token, session, request) + secure_compare(token, + per_form_token(session, request.path.chomp('/'), request.request_method) + ) + end + + def real_token(session) + decode_token(session[options[:key]]) + end + + def global_token(session) + token_hmac(session, GLOBAL_TOKEN_IDENTIFIER) + end + + def per_form_token(session, path, method) + token_hmac(session, "#{path}##{method.downcase}") + end + + def encode_token(token) + Base64.urlsafe_encode64(token) + end + + def decode_token(token) + Base64.urlsafe_decode64(token) + end + + def token_hmac(session, identifier) + OpenSSL::HMAC.digest( + OpenSSL::Digest::SHA256.new, + real_token(session), + identifier + ) + end + + def xor_byte_strings(s1, s2) + s2 = s2.dup + size = s1.bytesize + i = 0 + while i < size + s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i)) + i += 1 + end + s2 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/base.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/base.rb new file mode 100644 index 0000000000..83e724069b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/base.rb @@ -0,0 +1,126 @@ +require 'rack/protection' +require 'rack/utils' +require 'digest' +require 'logger' +require 'uri' + +module Rack + module Protection + class Base + DEFAULT_OPTIONS = { + :reaction => :default_reaction, :logging => true, + :message => 'Forbidden', :encryptor => Digest::SHA1, + :session_key => 'rack.session', :status => 403, + :allow_empty_referrer => true, + :report_key => "protection.failed", + :html_types => %w[text/html application/xhtml text/xml application/xml] + } + + attr_reader :app, :options + + def self.default_options(options) + define_method(:default_options) { super().merge(options) } + end + + def self.default_reaction(reaction) + alias_method(:default_reaction, reaction) + end + + def default_options + DEFAULT_OPTIONS + end + + def initialize(app, options = {}) + @app, @options = app, default_options.merge(options) + end + + def safe?(env) + %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD'] + end + + def accepts?(env) + raise NotImplementedError, "#{self.class} implementation pending" + end + + def call(env) + unless accepts? env + instrument env + result = react env + end + result or app.call(env) + end + + def react(env) + result = send(options[:reaction], env) + result if Array === result and result.size == 3 + end + + def warn(env, message) + return unless options[:logging] + l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) + l.warn(message) + end + + def instrument(env) + return unless i = options[:instrumenter] + env['rack.protection.attack'] = self.class.name.split('::').last.downcase + i.instrument('rack.protection', env) + end + + def deny(env) + warn env, "attack prevented by #{self.class}" + [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] + end + + def report(env) + warn env, "attack reported by #{self.class}" + env[options[:report_key]] = true + end + + def session?(env) + env.include? options[:session_key] + end + + def session(env) + return env[options[:session_key]] if session? env + fail "you need to set up a session middleware *before* #{self.class}" + end + + def drop_session(env) + session(env).clear if session? env + end + + def referrer(env) + ref = env['HTTP_REFERER'].to_s + return if !options[:allow_empty_referrer] and ref.empty? + URI.parse(ref).host || Request.new(env).host + rescue URI::InvalidURIError + end + + def origin(env) + env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN'] + end + + def random_string(secure = defined? SecureRandom) + secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) + rescue NotImplementedError + random_string false + end + + def encrypt(value) + options[:encryptor].hexdigest value.to_s + end + + def secure_compare(a, b) + Rack::Utils.secure_compare(a.to_s, b.to_s) + end + + alias default_reaction deny + + def html?(headers) + return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } + options[:html_types].include? header.last[/^\w+\/\w+/] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/content_security_policy.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/content_security_policy.rb new file mode 100644 index 0000000000..91eea925ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/content_security_policy.rb @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: XSS and others + # Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+ + # + # Description:: Content Security Policy, a mechanism web applications + # can use to mitigate a broad class of content injection + # vulnerabilities, such as cross-site scripting (XSS). + # Content Security Policy is a declarative policy that lets + # the authors (or server administrators) of a web application + # inform the client about the sources from which the + # application expects to load resources. + # + # More info:: W3C CSP Level 1 : https://www.w3.org/TR/CSP1/ (deprecated) + # W3C CSP Level 2 : https://www.w3.org/TR/CSP2/ (current) + # W3C CSP Level 3 : https://www.w3.org/TR/CSP3/ (draft) + # https://developer.mozilla.org/en-US/docs/Web/Security/CSP + # http://caniuse.com/#search=ContentSecurityPolicy + # http://content-security-policy.com/ + # https://securityheaders.io + # https://scotthelme.co.uk/csp-cheat-sheet/ + # http://www.html5rocks.com/en/tutorials/security/content-security-policy/ + # + # Sets the 'Content-Security-Policy[-Report-Only]' header. + # + # Options: ContentSecurityPolicy configuration is a complex topic with + # several levels of support that has evolved over time. + # See the W3C documentation and the links in the more info + # section for CSP usage examples and best practices. The + # CSP3 directives in the 'NO_ARG_DIRECTIVES' constant need to be + # presented in the options hash with a boolean 'true' in order + # to be used in a policy. + # + class ContentSecurityPolicy < Base + default_options default_src: "'self'", report_only: false + + DIRECTIVES = %i(base_uri child_src connect_src default_src + font_src form_action frame_ancestors frame_src + img_src manifest_src media_src object_src + plugin_types referrer reflected_xss report_to + report_uri require_sri_for sandbox script_src + style_src worker_src webrtc_src navigate_to + prefetch_src).freeze + + NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener + upgrade_insecure_requests).freeze + + def csp_policy + directives = [] + + DIRECTIVES.each do |d| + if options.key?(d) + directives << "#{d.to_s.sub(/_/, '-')} #{options[d]}" + end + end + + # Set these key values to boolean 'true' to include in policy + NO_ARG_DIRECTIVES.each do |d| + if options.key?(d) && options[d].is_a?(TrueClass) + directives << d.to_s.tr('_', '-') + end + end + + directives.compact.sort.join('; ') + end + + def call(env) + status, headers, body = @app.call(env) + header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy' + headers[header] ||= csp_policy if html? headers + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/cookie_tossing.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/cookie_tossing.rb new file mode 100644 index 0000000000..10614d5261 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/cookie_tossing.rb @@ -0,0 +1,75 @@ +require 'rack/protection' +require 'pathname' + +module Rack + module Protection + ## + # Prevented attack:: Cookie Tossing + # Supported browsers:: all + # More infos:: https://github.com/blog/1466-yummy-cookies-across-domains + # + # Does not accept HTTP requests if the HTTP_COOKIE header contains more than one + # session cookie. This does not protect against a cookie overflow attack. + # + # Options: + # + # session_key:: The name of the session cookie (default: 'rack.session') + class CookieTossing < Base + default_reaction :deny + + def call(env) + status, headers, body = super + response = Rack::Response.new(body, status, headers) + request = Rack::Request.new(env) + remove_bad_cookies(request, response) + response.finish + end + + def accepts?(env) + cookie_header = env['HTTP_COOKIE'] + cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s } + cookies.each do |k, v| + if k == session_key && Array(v).size > 1 + bad_cookies << k + elsif k != session_key && Rack::Utils.unescape(k) == session_key + bad_cookies << k + end + end + bad_cookies.empty? + end + + def remove_bad_cookies(request, response) + return if bad_cookies.empty? + paths = cookie_paths(request.path) + bad_cookies.each do |name| + paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) } + end + end + + def redirect(env) + request = Request.new(env) + warn env, "attack prevented by #{self.class}" + [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []] + end + + def bad_cookies + @bad_cookies ||= [] + end + + def cookie_paths(path) + path = '/' if path.to_s.empty? + paths = [] + Pathname.new(path).descend { |p| paths << p.to_s } + paths + end + + def empty_cookie(host, path) + {:value => '', :domain => host, :path => path, :expires => Time.at(0)} + end + + def session_key + @session_key ||= options[:session_key] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/escaped_params.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/escaped_params.rb new file mode 100644 index 0000000000..6706d1030e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/escaped_params.rb @@ -0,0 +1,89 @@ +require 'rack/protection' +require 'rack/utils' +require 'tempfile' + +begin + require 'escape_utils' +rescue LoadError +end + +module Rack + module Protection + ## + # Prevented attack:: XSS + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting + # + # Automatically escapes Rack::Request#params so they can be embedded in HTML + # or JavaScript without any further issues. Calls +html_safe+ on the escaped + # strings if defined, to avoid double-escaping in Rails. + # + # Options: + # escape:: What escaping modes to use, should be Symbol or Array of Symbols. + # Available: :html (default), :javascript, :url + class EscapedParams < Base + extend Rack::Utils + + class << self + alias escape_url escape + public :escape_html + end + + default_options :escape => :html, + :escaper => defined?(EscapeUtils) ? EscapeUtils : self + + def initialize(*) + super + + modes = Array options[:escape] + @escaper = options[:escaper] + @html = modes.include? :html + @javascript = modes.include? :javascript + @url = modes.include? :url + + if @javascript and not @escaper.respond_to? :escape_javascript + fail("Use EscapeUtils for JavaScript escaping.") + end + end + + def call(env) + request = Request.new(env) + get_was = handle(request.GET) + post_was = handle(request.POST) rescue nil + app.call env + ensure + request.GET.replace get_was if get_was + request.POST.replace post_was if post_was + end + + def handle(hash) + was = hash.dup + hash.replace escape(hash) + was + end + + def escape(object) + case object + when Hash then escape_hash(object) + when Array then object.map { |o| escape(o) } + when String then escape_string(object) + when Tempfile then object + else nil + end + end + + def escape_hash(hash) + hash = hash.dup + hash.each { |k,v| hash[k] = escape(v) } + hash + end + + def escape_string(str) + str = @escaper.escape_url(str) if @url + str = @escaper.escape_html(str) if @html + str = @escaper.escape_javascript(str) if @javascript + str + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/form_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/form_token.rb new file mode 100644 index 0000000000..994740f83f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/form_token.rb @@ -0,0 +1,23 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts submitted forms if a given access token matches the token + # included in the session. Does not expect such a token from Ajax request. + # + # This middleware is not used when using the Rack::Protection collection, + # since it might be a security issue, depending on your application + # + # Compatible with rack-csrf. + class FormToken < AuthenticityToken + def accepts?(env) + env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/frame_options.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/frame_options.rb new file mode 100644 index 0000000000..bce75c47e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/frame_options.rb @@ -0,0 +1,37 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Clickjacking + # Supported browsers:: Internet Explorer 8, Firefox 3.6.9, Opera 10.50, + # Safari 4.0, Chrome 4.1.249.1042 and later + # More infos:: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header + # + # Sets X-Frame-Options header to tell the browser avoid embedding the page + # in a frame. + # + # Options: + # + # frame_options:: Defines who should be allowed to embed the page in a + # frame. Use :deny to forbid any embedding, :sameorigin + # to allow embedding from the same origin (default). + class FrameOptions < Base + default_options :frame_options => :sameorigin + + def frame_options + @frame_options ||= begin + frame_options = options[:frame_options] + frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str + frame_options.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['X-Frame-Options'] ||= frame_options if html? headers + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/http_origin.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/http_origin.rb new file mode 100644 index 0000000000..034b7ac3bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/http_origin.rb @@ -0,0 +1,47 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: Google Chrome 2, Safari 4 and later + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # http://tools.ietf.org/html/draft-abarth-origin + # + # Does not accept unsafe HTTP requests when value of Origin HTTP request header + # does not match default or permitted URIs. + # + # If you want to permit a specific domain, you can pass in as the `:permitted_origins` option: + # + # use Rack::Protection, permitted_origins: ["http://localhost:3000", "http://127.0.01:3000"] + # + # The `:allow_if` option can also be set to a proc to use custom allow/deny logic. + class HttpOrigin < Base + DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } + default_reaction :deny + default_options :allow_if => nil + + def base_url(env) + request = Rack::Request.new(env) + port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme] + "#{request.scheme}://#{request.host}#{port}" + end + + def accepts?(env) + return true if safe? env + return true unless origin = env['HTTP_ORIGIN'] + return true if base_url(env) == origin + return true if options[:allow_if] && options[:allow_if].call(env) + + if options.key? :origin_whitelist + warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \ + "use permitted_origins instead.\n" + end + + permitted_origins = options[:permitted_origins] || options[:origin_whitelist] + Array(permitted_origins).include? origin + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/ip_spoofing.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/ip_spoofing.rb new file mode 100644 index 0000000000..6775f0c01d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/ip_spoofing.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: IP spoofing + # Supported browsers:: all + # More infos:: http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ + # + # Detect (some) IP spoofing attacks. + class IPSpoofing < Base + default_reaction :deny + + def accepts?(env) + return true unless env.include? 'HTTP_X_FORWARDED_FOR' + + ips = env['HTTP_X_FORWARDED_FOR'].split(',').map(&:strip) + return false if env.include?('HTTP_CLIENT_IP') && (!ips.include? env['HTTP_CLIENT_IP']) + return false if env.include?('HTTP_X_REAL_IP') && (!ips.include? env['HTTP_X_REAL_IP']) + + true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/json_csrf.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/json_csrf.rb new file mode 100644 index 0000000000..5ec826b719 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/json_csrf.rb @@ -0,0 +1,57 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://flask.pocoo.org/docs/0.10/security/#json-security + # http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + # + # JSON GET APIs are vulnerable to being embedded as JavaScript when the + # Array prototype has been patched to track data. Checks the referrer + # even on GET requests if the content type is JSON. + # + # If request includes Origin HTTP header, defers to HttpOrigin to determine + # if the request is safe. Please refer to the documentation for more info. + # + # The `:allow_if` option can be set to a proc to use custom allow/deny logic. + class JsonCsrf < Base + default_options :allow_if => nil + + alias react deny + + def call(env) + request = Request.new(env) + status, headers, body = app.call(env) + + if has_vector?(request, headers) + warn env, "attack prevented by #{self.class}" + + react_and_close(env, body) or [status, headers, body] + else + [status, headers, body] + end + end + + def has_vector?(request, headers) + return false if request.xhr? + return false if options[:allow_if] && options[:allow_if].call(request.env) + return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ + origin(request.env).nil? and referrer(request.env) != request.host + end + + def react_and_close(env, body) + reaction = react(env) + + close_body(body) if reaction + + reaction + end + + def close_body(body) + body.close if body.respond_to?(:close) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/path_traversal.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/path_traversal.rb new file mode 100644 index 0000000000..4e8c94ca95 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/path_traversal.rb @@ -0,0 +1,42 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Directory traversal + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Directory_traversal + # + # Unescapes '/' and '.', expands +path_info+. + # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. + class PathTraversal < Base + def call(env) + path_was = env["PATH_INFO"] + env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? + app.call env + ensure + env["PATH_INFO"] = path_was + end + + def cleanup(path) + encoding = path.encoding + dot = '.'.encode(encoding) + slash = '/'.encode(encoding) + backslash = '\\'.encode(encoding) + + parts = [] + unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash) + unescaped = unescaped.gsub(backslash, slash) + + unescaped.split(slash).each do |part| + next if part.empty? or part == dot + part == '..' ? parts.pop : parts << part + end + + cleaned = slash + parts.join(slash) + cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} + cleaned + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/referrer_policy.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/referrer_policy.rb new file mode 100644 index 0000000000..f977d72e4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/referrer_policy.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Secret leakage, third party tracking + # Supported browsers:: mixed support + # More infos:: https://www.w3.org/TR/referrer-policy/ + # https://caniuse.com/#search=referrer-policy + # + # Sets Referrer-Policy header to tell the browser to limit the Referer header. + # + # Options: + # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin') + class ReferrerPolicy < Base + default_options :referrer_policy => 'strict-origin-when-cross-origin' + + def call(env) + status, headers, body = @app.call(env) + headers['Referrer-Policy'] ||= options[:referrer_policy] + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_referrer.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_referrer.rb new file mode 100644 index 0000000000..5375ebc3d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_referrer.rb @@ -0,0 +1,20 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Does not accept unsafe HTTP requests if the Referer [sic] header is set to + # a different host. + class RemoteReferrer < Base + default_reaction :deny + + def accepts?(env) + safe?(env) or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_token.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_token.rb new file mode 100644 index 0000000000..f3922c8739 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/remote_token.rb @@ -0,0 +1,22 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts unsafe HTTP requests if a given access token matches the token + # included in the session *or* the request comes from the same origin. + # + # Compatible with rack-csrf. + class RemoteToken < AuthenticityToken + default_reaction :deny + + def accepts?(env) + super or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/session_hijacking.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/session_hijacking.rb new file mode 100644 index 0000000000..a7dd54b742 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/session_hijacking.rb @@ -0,0 +1,36 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Session Hijacking + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Session_hijacking + # + # Tracks request properties like the user agent in the session and empties + # the session if those properties change. This essentially prevents attacks + # from Firesheep. Since all headers taken into consideration can be + # spoofed, too, this will not prevent determined hijacking attempts. + class SessionHijacking < Base + default_reaction :drop_session + default_options :tracking_key => :tracking, :encrypt_tracking => true, + :track => %w[HTTP_USER_AGENT] + + def accepts?(env) + session = session env + key = options[:tracking_key] + if session.include? key + session[key].all? { |k,v| v == encrypt(env[k]) } + else + session[key] = {} + options[:track].each { |k| session[key][k] = encrypt(env[k]) } + end + end + + def encrypt(value) + value = value.to_s.downcase + options[:encrypt_tracking] ? super(value) : value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/strict_transport.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/strict_transport.rb new file mode 100644 index 0000000000..b4283bab36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/strict_transport.rb @@ -0,0 +1,39 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Protects against against protocol downgrade attacks and cookie hijacking. + # Supported browsers:: all + # More infos:: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security + # + # browser will prevent any communications from being sent over HTTP + # to the specified domain and will instead send all communications over HTTPS. + # It also prevents HTTPS click through prompts on browsers. + # + # Options: + # + # max_age:: How long future requests to the domain should go over HTTPS; specified in seconds + # include_subdomains:: If all present and future subdomains will be HTTPS + # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/ + + class StrictTransport < Base + default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false + + def strict_transport + @strict_transport ||= begin + strict_transport = 'max-age=' + options[:max_age].to_s + strict_transport += '; includeSubDomains' if options[:include_subdomains] + strict_transport += '; preload' if options[:preload] + strict_transport.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['Strict-Transport-Security'] ||= strict_transport + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/version.rb new file mode 100644 index 0000000000..64a2fc0553 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/version.rb @@ -0,0 +1,5 @@ +module Rack + module Protection + VERSION = '2.2.3' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/xss_header.rb b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/xss_header.rb new file mode 100644 index 0000000000..eb6f92fe36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/lib/rack/protection/xss_header.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Non-permanent XSS + # Supported browsers:: Internet Explorer 8+ and Chrome + # More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx + # + # Sets X-XSS-Protection header to tell the browser to block attacks. + # + # Options: + # xss_mode:: How the browser should prevent the attack (default: :block) + class XSSHeader < Base + default_options :xss_mode => :block, :nosniff => true + + def call(env) + status, headers, body = @app.call(env) + headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers + headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff] + [status, headers, body] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/rack-protection.gemspec b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/rack-protection.gemspec new file mode 100644 index 0000000000..817649d44c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.2.4/rack-protection.gemspec @@ -0,0 +1,40 @@ +version = File.read(File.expand_path("../../VERSION", __FILE__)).strip + +Gem::Specification.new do |s| + # general infos + s.name = "rack-protection" + s.version = version + s.description = "Protect against typical web attacks, works with all Rack apps, including Rails." + s.homepage = "http://sinatrarb.com/protection/" + s.summary = s.description + s.license = 'MIT' + s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] + s.email = "sinatrarb@googlegroups.com" + s.files = Dir["lib/**/*.rb"] + [ + "License", + "README.md", + "Rakefile", + "Gemfile", + "rack-protection.gemspec" + ] + + if s.respond_to?(:metadata) + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', + 'homepage_uri' => 'http://sinatrarb.com/protection/', + 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection' + } + else + raise <<-EOF +RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: + gem install rubygems-update + update_rubygems: + gem update --system +EOF + end + + # dependencies + s.add_dependency "rack" + s.add_development_dependency "rack-test" + s.add_development_dependency "rspec", "~> 3.6" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/History.md b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/History.md new file mode 100644 index 0000000000..8e707ebf7c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/History.md @@ -0,0 +1,256 @@ +## 1.1.0 / 2018-07-21 + +* Breaking changes: + * None + +* Minor enhancements / new functionality: + * [GitHub] Added configuration for Stale (Per Lundberg #232) + * follow_direct: Include rack.session.options (Mark Edmondson #233) + * [CI] Add simplecov (fatkodima #227) + +Bug fixes: + * Follow relative locations correctly. (Samuel Williams #230) + +## 1.0.0 / 2018-03-27 + +* Breaking changes: + * Always set CONTENT_TYPE for non-GET requests + (Per Lundberg #223) + +* Minor enhancements / bug fixes: + * Create tempfile using the basename without extension + (Edouard Chin #201) + * Save `session` during `follow_redirect!` + (Alexander Popov #218) + * Document how to use URL params with DELETE method + (Timur Platonov #220) + +## 0.8.3 / 2018-02-27 + +* Bug fixes: + * Do not set Content-Type if params are explicitly set to nil + (Bartek Bułat #212). Fixes #200. + * Fix `UploadedFile#new` regression + (Per Lundberg #215) + +* Minor enhancements + * [CI] Test against Ruby 2.5 (Nicolas Leger #217) + +## 0.8.2 / 2017-11-21 + +* Bug fixes: + * Bugfix for `UploadedFile.new` unintended API breakage. + (Per Lundberg #210) + +## 0.8.0 / 2017-11-20 + +* Known Issue + * In `UploadedFile.new`, when passing e.g. a `Pathname` object, + errors can be raised (eg. `ArgumentError: Missing original_filename + for IO`, or `NoMethodError: undefined method 'size'`) See #207, #209. +* Minor enhancements + * Add a required_ruby_version of >= 2.2.2, similar to rack 2.0.1. + (Samuel Giddins #194) + * Remove new line from basic auth. (Felix Kleinschmidt #185) + * Rubocop fixes (Per Lundberg #196) + * Add how to install rack-test from github to README. (Jun Aruga #189) + * Update CodeClimate badges (Toshimaru #195) + * Add the ability to create Test::UploadedFile instances without + the file system (Adam Milligan #149) + * Add custom_request, remove duplication (Johannes Barre #184) + * README.md: Added note about how to post JSON (Per Lundberg #198) + * README.md: Added version badge (Per Lundberg #199) +* Bug fixes + * Bugfix for Cookies with multiple paths (Kyle Welsby #197) + +## 0.7.0 / 2017-07-10 + +* Major enhancements + * The project URL changed to https://github.com/rack-test/rack-test + (Per Lundberg, Dennis Sivia, Jun Aruga) + * Rack 2 compatible. (Trevor Wennblom #81, Vít Ondruch, Jun Aruga #151) +* Minor enhancements + * Port to RSpec 3. (Murahashi [Matt] Kenichi #70, Antonio Terceiro #134) + * Add Travis CI (Johannes Barre #108, Jun Aruga #161) + * Don't append an ampersand when params are empty (sbilharz, #157) + * Allow symbol access to cookies (Anorlondo448 #156) + * README: Added Travis badge (Olivier Lacan, Per Lundberg #146) + * `Rack::Test::Utils#build_multipart`: Allow passing a third parameter + to force multipart (Koen Punt #142) + * Allow better testing of cookies (Stephen Best #133) + * make `build_multipart` work without mixing in `Rack::Test::Utils` + (Aaron Patterson #131) + * Add license to gemspec (Jordi Massaguer Pla #72, Anatol Pomozov #89, + Anatol Pomozov #90, Johannes Barre #109, Mandaryn #115, + Chris Marshall #120, Robert Reiz #126, Nic Benders #127, Nic Benders #130) + * Feature/bulk pr for readme updates (Patrick Mulder #65, + Troels Knak-Nielsen #74, Jeff Casimir #76) + * Switch README format to Markdown (Dennis Sivia #176) + * Convert History.txt to Markdown (Dennis Sivia #179) + * Stop generating gemspec file. (Jun Aruga #181) + * Fix errors at rake docs and whitespace. (Jun Aruga #183) + * Ensure Rack::Test::UploadedFile closes its tempfile file descriptor + on GC (Michael de Silva #180) + * Change codeclimate URL correctly. (Jun Aruga #186) +* Bug fixes + * Initialize digest_username before using it. (Guo Xiang Tan #116, + John Drago #124, Mike Perham #154) + * Do not set Content-Type for DELETE requests (David Celis #132) + * Adds support for empty arrays in params. (Cedric Röck, Tim Masliuchenko + #125) + * Update README code example quotes to be consistent. (Dmitry Gritsay #112) + * Update README not to recommend installing gem with sudo. (T.J. Schuck #87) + * Set scheme when using ENV to enable SSL (Neil Ang #155) + * Reuse request method and parameters on HTTP 307 redirect. (Martin Mauch + #138) + +## 0.6.3 / 2015-01-09 + +* Minor enhancements + * Expose an env helper for persistently configuring the env as needed + (Darío Javier Cravero #80) + * Expose the tempfile of UploadedFile (Sytse Sijbrandij #67) +* Bug fixes + * Improve support for arrays of hashes in multipart forms (Murray Steele #69) + * Improve test for query strings (Paul Grayson #66) + +## 0.6.2 / 2012-09-27 + +* Minor enhancements + * Support HTTP PATCH method (Marjan Krekoten' #33) + * Preserve the exact query string when possible (Paul Grayson #63) + * Add a #delete method to CookieJar (Paul Grayson #63) +* Bug fixes + * Fix HTTP Digest authentication when the URI has query params + * Don't append default ports to HTTP_HOST (David Lee #57) + +## 0.6.1 / 2011-07-27 + +* Bug fixes + * Fix support for params with arrays in multipart forms (Joel Chippindale) + * Add `respond_to?` to `Rack::Test::UploadedFile` to match `method_missing` (Josh Nichols) + * Set the Referer header on requests issued by follow_redirect! (Ryan Bigg) + +## 0.6.0 / 2011-05-03 + +* Bug fixes + * Add support for HTTP OPTIONS verb (Paolo "Nusco" Perrotta) + * Call #finish on MockResponses if it's available (Aaron Patterson) + * Allow HTTP_HOST to be set via #header (Geoff Buesing) + +## 0.5.7 / 2011-01-01 +* Bug fixes + * If no URI is present, include all cookies (Pratik Naik) + +## 0.5.6 / 2010-09-25 + +* Bug fixes + * Use parse_nested_query for parsing URI like Rack does (Eugene Bolshakov) + * Don't depend on ActiveSupport extension to String (Bryan Helmkamp) + * Do not overwrite HTTP_HOST if it is set (Krekoten' Marjan) + +## 0.5.5 / 2010-09-22 + +* Bug fixes + * Fix encoding of file uploads on Ruby 1.9 (Alan Kennedy) + * Set env["HTTP_HOST"] when making requests (Istvan Hoka) + +## 0.5.4 / 2010-05-26 + +* Bug fixes + * Don't stomp on Content-Type's supplied via #header (Bryan Helmkamp) + * Fixed build_multipart to allow for arrays of files (Louis Rose) + * Don't raise an error if raw cookies contain a blank line (John Reilly) + * Handle parameter names with brackets properly (Tanner Donovan) + +## 0.5.3 / 2009-11-27 + +* Bug fixes + * Fix cookie matching for subdomains (Marcin Kulik) + +## 0.5.2 / 2009-11-13 + +* Bug fixes + * Call close on response body after iteration, not before (Simon Rozet) + * Add missing require for time in cookie_jar.rb (Jerry West) + +## 0.5.1 / 2009-10-27 + +* Bug fixes + * Escape cookie values (John Pignata) + * Close the response body after each request, as per the Rack spec (Elomar França) + +## 0.5.0 / 2009-09-19 + +* Bug fixes + * Set HTTP_X_REQUESTED_WITH in the Rack env when a request is made with :xhr => true (Ben Sales) + * Set headers in the Rack env in HTTP_USER_AGENT form + * Rack::Test now generates no Ruby warnings + +## 0.4.2 / 2009-09-01 + +* Minor enhancements + * Merge in rack/master's build_multipart method which covers additional cases + * Accept raw :params string input and merge it with the query string + * Stringify and upcase request method (e.g. :post => "POST") (Josh Peek) +* Bug fixes + * Properly convert hashes with nil values (e.g. :foo => nil becomes simply "foo", not "foo=") + * Prepend a slash to the URI path if it doesn't start with one (Josh Peek) + * Requiring Rack-Test never modifies the Ruby load path anymore (Josh Peek) + * Fixed using multiple cookies in a string on Ruby 1.8 (Tuomas Kareinen and Hermanni Hyytiälä) + +## 0.4.1 / 2009-08-06 + +* Minor enhancements + * Support initializing a `Rack::Test::Session` with an app in addition to + a `Rack::MockSession` + * Allow CONTENT_TYPE to be specified in the env and not overwritten when + sending a POST or PUT + +## 0.4.0 / 2009-06-25 + +* Minor enhancements + * Expose hook for building `Rack::MockSessions` for frameworks that need + to configure them before use + * Support passing in arrays of raw cookies in addition to a newline + separated string + * Support after_request callbacks in MockSession for things like running + background jobs + * Allow multiple named sessions using with_session + * Initialize `Rack::Test::Sessions` with `Rack::MockSessions` instead of apps. + This change should help integration with other Ruby web frameworks + (like Merb). + * Support sending bodies for PUT requests (Larry Diehl) + +## 0.3.0 / 2009-05-17 + +* Major enhancements + * Ruby 1.9 compatible (Simon Rozet, Michael Fellinger) +* Minor enhancements + * Add `CookieJar#[]` and `CookieJar#[]=` methods + * Make the default host configurable + * Use `Rack::Lint` and fix errors (Simon Rozet) + * Extract `Rack::MockSession` from `Rack::Test::Session` to handle tracking + the last request and response and the cookie jar + * Add #set_cookie and #clear_cookies methods + * Rename #authorize to #basic_authorize (#authorize remains as an alias) + (Simon Rozet) + +## 0.2.0 / 2009-04-26 + +Because `#last_response` is now a `MockResponse` instead of a `Rack::Response`, `#last_response.body` +now returns a string instead of an array. + +* Major enhancements + * Support multipart requests via the UploadedFile class (thanks, Rails) +* Minor enhancements + * Updated for Rack 1.0 + * Don't require rubygems (See http://gist.github.com/54177) + * Support HTTP Digest authentication with the `#digest_authorize` method + * `#last_response` returns a `MockResponse` instead of a Response + (Michael Fellinger) + +## 0.1.0 / 2009-03-02 + +* 1 major enhancement + * Birthday! diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/MIT-LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/MIT-LICENSE.txt new file mode 100644 index 0000000000..f387441cbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/MIT-LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2008-2009 Bryan Helmkamp, Engine Yard 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.0.0/gems/rack-test-1.1.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/README.md new file mode 100644 index 0000000000..18fd0fe683 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/README.md @@ -0,0 +1,150 @@ +# Rack::Test +[![Gem Version](https://badge.fury.io/rb/rack-test.svg)](https://badge.fury.io/rb/rack-test) +[](https://travis-ci.org/rack-test/rack-test) +[![Code Climate](https://codeclimate.com/github/rack-test/rack-test/badges/gpa.svg)](https://codeclimate.com/github/rack-test/rack-test) +[![Test Coverage](https://codeclimate.com/github/rack-test/rack-test/badges/coverage.svg)](https://codeclimate.com/github/rack-test/rack-test/coverage) + +Code: https://github.com/rack-test/rack-test + +## Description + +Rack::Test is a small, simple testing API for Rack apps. It can be used on its +own or as a reusable starting point for Web frameworks and testing libraries +to build on. + +## Features + +* Maintains a cookie jar across requests +* Easily follow redirects when desired +* Set request headers to be used by all subsequent requests +* Small footprint. Approximately 200 LOC + +## Supported platforms + +* 2.2.2+ +* 2.3 +* 2.4 +* JRuby 9.1.+ + +If you are using Ruby 1.8, 1.9 or JRuby 1.7, use rack-test 0.6.3. + +## Known incompatibilites + +* `rack-test >= 0.71` _does not_ work with older Capybara versions (`< 2.17`). See [#214](https://github.com/rack-test/rack-test/issues/214) for more details. + +## Examples +(The examples use `Test::Unit` but it's equally possible to use `rack-test` with other testing frameworks like `rspec`.) + +```ruby +require "test/unit" +require "rack/test" + +class HomepageTest < Test::Unit::TestCase + include Rack::Test::Methods + + def app + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['All responses are OK']] } + builder = Rack::Builder.new + builder.run app + end + + def test_response_is_ok + get '/' + + assert last_response.ok? + assert_equal last_response.body, 'All responses are OK' + end + + def set_request_headers + header 'Accept-Charset', 'utf-8' + get '/' + + assert last_response.ok? + assert_equal last_response.body, 'All responses are OK' + end + + def test_response_is_ok_for_other_paths + get '/other_paths' + + assert last_response.ok? + assert_equal last_response.body, 'All responses are OK' + end + + def post_with_json + # No assertion in this, we just demonstrate how you can post a JSON-encoded string. + # By default, Rack::Test will use HTTP form encoding if you pass in a Hash as the + # parameters, so make sure that `json` below is already a JSON-serialized string. + post(uri, json, { 'CONTENT_TYPE' => 'application/json' }) + end + + def delete_with_url_params_and_body + delete '/?foo=bar', JSON.generate('baz' => 'zot') + end +end +``` + +If you want to test one app in isolation, you just return that app as shown above. But if you want to test the entire app stack, including middlewares, cascades etc. you need to parse the app defined in config.ru. + +```ruby +OUTER_APP = Rack::Builder.parse_file("config.ru").first + +class TestApp < Test::Unit::TestCase + include Rack::Test::Methods + + def app + OUTER_APP + end + + def test_root + get "/" + assert last_response.ok? + end +end +``` + + +## Install + +To install the latest release as a gem: + +``` +gem install rack-test +``` + +Or via Bundler: + +``` +gem 'rack-test' +``` + +Or to install unreleased version via Bundler: + +``` +gem 'rack-test', github: 'rack-test', branch: 'master' +``` + +## Authors + +- Contributions from Bryan Helmkamp, Simon Rozet, Pat Nakajima and others +- Much of the original code was extracted from Merb 1.0's request helper + +## License +`rack-test` is released under the [MIT License](MIT-LICENSE.txt). + +## Contribution + +Contributions are welcome. Please make sure to: + +* Use a regular forking workflow +* Write tests for the new or changed behaviour +* Provide an explanation/motivation in your commit message / PR message +* Ensure History.txt is updated + +## Releasing + +* Ensure `History.md` is up-to-date +* Bump VERSION in lib/rack/test/version.rb +* `git commit . -m 'Release 1.1.0'` +* `git push` +* bundle exec thor :release +* Updated the [GitHub releases page](https://github.com/rack-test/rack-test/releases) diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb new file mode 100644 index 0000000000..350c3d69a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb @@ -0,0 +1,63 @@ +module Rack + class MockSession # :nodoc: + attr_writer :cookie_jar + attr_reader :default_host + + def initialize(app, default_host = Rack::Test::DEFAULT_HOST) + @app = app + @after_request = [] + @default_host = default_host + @last_request = nil + @last_response = nil + end + + def after_request(&block) + @after_request << block + end + + def clear_cookies + @cookie_jar = Rack::Test::CookieJar.new([], @default_host) + end + + def set_cookie(cookie, uri = nil) + cookie_jar.merge(cookie, uri) + end + + def request(uri, env) + env['HTTP_COOKIE'] ||= cookie_jar.for(uri) + @last_request = Rack::Request.new(env) + status, headers, body = @app.call(@last_request.env) + + @last_response = MockResponse.new(status, headers, body, env['rack.errors'].flush) + body.close if body.respond_to?(:close) + + cookie_jar.merge(last_response.headers['Set-Cookie'], uri) + + @after_request.each(&:call) + + if @last_response.respond_to?(:finish) + @last_response.finish + else + @last_response + end + end + + # Return the last request issued in the session. Raises an error if no + # requests have been sent yet. + def last_request + raise Rack::Test::Error, 'No request yet. Request a page first.' unless @last_request + @last_request + end + + # Return the last response received in the session. Raises an error if + # no requests have been sent yet. + def last_response + raise Rack::Test::Error, 'No response yet. Request a page first.' unless @last_response + @last_response + end + + def cookie_jar + @cookie_jar ||= Rack::Test::CookieJar.new([], @default_host) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb new file mode 100644 index 0000000000..b34c192b7a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb @@ -0,0 +1,334 @@ +require 'uri' +require 'rack' +require 'rack/mock_session' +require 'rack/test/cookie_jar' +require 'rack/test/mock_digest_request' +require 'rack/test/utils' +require 'rack/test/methods' +require 'rack/test/uploaded_file' +require 'rack/test/version' + +module Rack + module Test + DEFAULT_HOST = 'example.org'.freeze + MULTIPART_BOUNDARY = '----------XnJLe9ZIbbGUYtzPQJ16u1'.freeze + + # The common base class for exceptions raised by Rack::Test + class Error < StandardError; end + + # This class represents a series of requests issued to a Rack app, sharing + # a single cookie jar + # + # Rack::Test::Session's methods are most often called through Rack::Test::Methods, + # which will automatically build a session when it's first used. + class Session + extend Forwardable + include Rack::Test::Utils + + def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request + + # Creates a Rack::Test::Session for a given Rack app or Rack::MockSession. + # + # Note: Generally, you won't need to initialize a Rack::Test::Session directly. + # Instead, you should include Rack::Test::Methods into your testing context. + # (See README.rdoc for an example) + def initialize(mock_session) + @headers = {} + @env = {} + @digest_username = nil + @digest_password = nil + + @rack_mock_session = if mock_session.is_a?(MockSession) + mock_session + else + MockSession.new(mock_session) + end + + @default_host = @rack_mock_session.default_host + end + + # Issue a GET request for the given URI with the given params and Rack + # environment. Stores the issues request object in #last_request and + # the app's response in #last_response. Yield #last_response to a block + # if given. + # + # Example: + # get "/" + def get(uri, params = {}, env = {}, &block) + custom_request('GET', uri, params, env, &block) + end + + # Issue a POST request for the given URI. See #get + # + # Example: + # post "/signup", "name" => "Bryan" + def post(uri, params = {}, env = {}, &block) + custom_request('POST', uri, params, env, &block) + end + + # Issue a PUT request for the given URI. See #get + # + # Example: + # put "/" + def put(uri, params = {}, env = {}, &block) + custom_request('PUT', uri, params, env, &block) + end + + # Issue a PATCH request for the given URI. See #get + # + # Example: + # patch "/" + def patch(uri, params = {}, env = {}, &block) + custom_request('PATCH', uri, params, env, &block) + end + + # Issue a DELETE request for the given URI. See #get + # + # Example: + # delete "/" + def delete(uri, params = {}, env = {}, &block) + custom_request('DELETE', uri, params, env, &block) + end + + # Issue an OPTIONS request for the given URI. See #get + # + # Example: + # options "/" + def options(uri, params = {}, env = {}, &block) + custom_request('OPTIONS', uri, params, env, &block) + end + + # Issue a HEAD request for the given URI. See #get + # + # Example: + # head "/" + def head(uri, params = {}, env = {}, &block) + custom_request('HEAD', uri, params, env, &block) + end + + # Issue a request to the Rack app for the given URI and optional Rack + # environment. Stores the issues request object in #last_request and + # the app's response in #last_response. Yield #last_response to a block + # if given. + # + # Example: + # request "/" + def request(uri, env = {}, &block) + uri = parse_uri(uri, env) + env = env_for(uri, env) + process_request(uri, env, &block) + end + + # Issue a request using the given verb for the given URI. See #get + # + # Example: + # custom_request "LINK", "/" + def custom_request(verb, uri, params = {}, env = {}, &block) + uri = parse_uri(uri, env) + env = env_for(uri, env.merge(method: verb.to_s.upcase, params: params)) + process_request(uri, env, &block) + end + + # Set a header to be included on all subsequent requests through the + # session. Use a value of nil to remove a previously configured header. + # + # In accordance with the Rack spec, headers will be included in the Rack + # environment hash in HTTP_USER_AGENT form. + # + # Example: + # header "User-Agent", "Firefox" + def header(name, value) + if value.nil? + @headers.delete(name) + else + @headers[name] = value + end + end + + # Set an env var to be included on all subsequent requests through the + # session. Use a value of nil to remove a previously configured env. + # + # Example: + # env "rack.session", {:csrf => 'token'} + def env(name, value) + if value.nil? + @env.delete(name) + else + @env[name] = value + end + end + + # Set the username and password for HTTP Basic authorization, to be + # included in subsequent requests in the HTTP_AUTHORIZATION header. + # + # Example: + # basic_authorize "bryan", "secret" + def basic_authorize(username, password) + encoded_login = ["#{username}:#{password}"].pack('m0') + header('Authorization', "Basic #{encoded_login}") + end + + alias authorize basic_authorize + + # Set the username and password for HTTP Digest authorization, to be + # included in subsequent requests in the HTTP_AUTHORIZATION header. + # + # Example: + # digest_authorize "bryan", "secret" + def digest_authorize(username, password) + @digest_username = username + @digest_password = password + end + + # Rack::Test will not follow any redirects automatically. This method + # will follow the redirect returned (including setting the Referer header + # on the new request) in the last response. If the last response was not + # a redirect, an error will be raised. + def follow_redirect! + unless last_response.redirect? + raise Error, 'Last response was not a redirect. Cannot follow_redirect!' + end + request_method, params = + if last_response.status == 307 + [last_request.request_method.downcase.to_sym, last_request.params] + else + [:get, {}] + end + + # Compute the next location by appending the location header with the + # last request, as per https://tools.ietf.org/html/rfc7231#section-7.1.2 + # Adding two absolute locations returns the right-hand location + next_location = URI.parse(last_request.url) + URI.parse(last_response['Location']) + + send( + request_method, next_location.to_s, params, + 'HTTP_REFERER' => last_request.url, + 'rack.session' => last_request.session, + 'rack.session.options' => last_request.session_options + ) + end + + private + + def parse_uri(path, env) + URI.parse(path).tap do |uri| + uri.path = "/#{uri.path}" unless uri.path[0] == '/' + uri.host ||= @default_host + uri.scheme ||= 'https' if env['HTTPS'] == 'on' + end + end + + def env_for(uri, env) + env = default_env.merge(env) + + env['HTTP_HOST'] ||= [uri.host, (uri.port if uri.port != uri.default_port)].compact.join(':') + + env.update('HTTPS' => 'on') if URI::HTTPS === uri + env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' if env[:xhr] + + # TODO: Remove this after Rack 1.1 has been released. + # Stringifying and upcasing methods has be commit upstream + env['REQUEST_METHOD'] ||= env[:method] ? env[:method].to_s.upcase : 'GET' + + params = env.delete(:params) do {} end + + if env['REQUEST_METHOD'] == 'GET' + # merge :params with the query string + if params + params = parse_nested_query(params) if params.is_a?(String) + + uri.query = [uri.query, build_nested_query(params)].compact.reject { |v| v == '' }.join('&') + end + elsif !env.key?(:input) + env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded' + + if params.is_a?(Hash) + if data = build_multipart(params) + env[:input] = data + env['CONTENT_LENGTH'] ||= data.length.to_s + env['CONTENT_TYPE'] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}" + else + # NB: We do not need to set CONTENT_LENGTH here; + # Rack::ContentLength will determine it automatically. + env[:input] = params_to_string(params) + end + else + env[:input] = params + end + end + + set_cookie(env.delete(:cookie), uri) if env.key?(:cookie) + + Rack::MockRequest.env_for(uri.to_s, env) + end + + def process_request(uri, env) + @rack_mock_session.request(uri, env) + + if retry_with_digest_auth?(env) + auth_env = env.merge('HTTP_AUTHORIZATION' => digest_auth_header, + 'rack-test.digest_auth_retry' => true) + auth_env.delete('rack.request') + process_request(uri.path, auth_env) + else + yield last_response if block_given? + + last_response + end + end + + def digest_auth_header + challenge = last_response['WWW-Authenticate'].split(' ', 2).last + params = Rack::Auth::Digest::Params.parse(challenge) + + params.merge!('username' => @digest_username, + 'nc' => '00000001', + 'cnonce' => 'nonsensenonce', + 'uri' => last_request.fullpath, + 'method' => last_request.env['REQUEST_METHOD']) + + params['response'] = MockDigestRequest.new(params).response(@digest_password) + + "Digest #{params}" + end + + def retry_with_digest_auth?(env) + last_response.status == 401 && + digest_auth_configured? && + !env['rack-test.digest_auth_retry'] + end + + def digest_auth_configured? + @digest_username + end + + def default_env + { 'rack.test' => true, 'REMOTE_ADDR' => '127.0.0.1' }.merge(@env).merge(headers_for_env) + end + + def headers_for_env + converted_headers = {} + + @headers.each do |name, value| + env_key = name.upcase.tr('-', '_') + env_key = 'HTTP_' + env_key unless env_key == 'CONTENT_TYPE' + converted_headers[env_key] = value + end + + converted_headers + end + + def params_to_string(params) + case params + when Hash then build_nested_query(params) + when nil then '' + else params + end + end + end + + def self.encoding_aware_strings? + defined?(Encoding) && ''.respond_to?(:encode) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/cookie_jar.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/cookie_jar.rb new file mode 100644 index 0000000000..b8d0d7b4d5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/cookie_jar.rb @@ -0,0 +1,194 @@ +require 'uri' +require 'time' + +module Rack + module Test + class Cookie # :nodoc: + include Rack::Utils + + # :api: private + attr_reader :name, :value + + # :api: private + def initialize(raw, uri = nil, default_host = DEFAULT_HOST) + @default_host = default_host + uri ||= default_uri + + # separate the name / value pair from the cookie options + @name_value_raw, options = raw.split(/[;,] */n, 2) + + @name, @value = parse_query(@name_value_raw, ';').to_a.first + @options = parse_query(options, ';') + + @options['domain'] ||= (uri.host || default_host) + @options['path'] ||= uri.path.sub(/\/[^\/]*\Z/, '') + end + + def replaces?(other) + [name.downcase, domain, path] == [other.name.downcase, other.domain, other.path] + end + + # :api: private + def raw + @name_value_raw + end + + # :api: private + def empty? + @value.nil? || @value.empty? + end + + # :api: private + def domain + @options['domain'] + end + + def secure? + @options.key?('secure') + end + + def http_only? + @options.key?('HttpOnly') + end + + # :api: private + def path + ([*@options['path']].first.split(',').first || '/').strip + end + + # :api: private + def expires + Time.parse(@options['expires']) if @options['expires'] + end + + # :api: private + def expired? + expires && expires < Time.now + end + + # :api: private + def valid?(uri) + uri ||= default_uri + + uri.host = @default_host if uri.host.nil? + + real_domain = domain =~ /^\./ ? domain[1..-1] : domain + (!secure? || (secure? && uri.scheme == 'https')) && + uri.host =~ Regexp.new("#{Regexp.escape(real_domain)}$", Regexp::IGNORECASE) && + uri.path =~ Regexp.new("^#{Regexp.escape(path)}") + end + + # :api: private + def matches?(uri) + !expired? && valid?(uri) + end + + # :api: private + def <=>(other) + # Orders the cookies from least specific to most + [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse] + end + + def to_h + @options.merge( + 'value' => @value, + 'HttpOnly' => http_only?, + 'secure' => secure? + ) + end + alias to_hash to_h + + protected + + def default_uri + URI.parse('//' + @default_host + '/') + end + end + + class CookieJar # :nodoc: + DELIMITER = '; '.freeze + + # :api: private + def initialize(cookies = [], default_host = DEFAULT_HOST) + @default_host = default_host + @cookies = cookies + @cookies.sort! + end + + def [](name) + cookies = hash_for(nil) + # TODO: Should be case insensitive + cookies[name.to_s] && cookies[name.to_s].value + end + + def []=(name, value) + merge("#{name}=#{Rack::Utils.escape(value)}") + end + + def get_cookie(name) + hash_for(nil).fetch(name, nil) + end + + def delete(name) + @cookies.reject! do |cookie| + cookie.name == name + end + end + + def merge(raw_cookies, uri = nil) + return unless raw_cookies + + if raw_cookies.is_a? String + raw_cookies = raw_cookies.split("\n") + raw_cookies.reject!(&:empty?) + end + + raw_cookies.each do |raw_cookie| + cookie = Cookie.new(raw_cookie, uri, @default_host) + self << cookie if cookie.valid?(uri) + end + end + + def <<(new_cookie) + @cookies.reject! do |existing_cookie| + new_cookie.replaces?(existing_cookie) + end + + @cookies << new_cookie + @cookies.sort! + end + + # :api: private + def for(uri) + hash_for(uri).values.map(&:raw).join(DELIMITER) + end + + def to_hash + cookies = {} + + hash_for(nil).each do |name, cookie| + cookies[name] = cookie.value + end + + cookies + end + + protected + + def hash_for(uri = nil) + cookies = {} + + # The cookies are sorted by most specific first. So, we loop through + # all the cookies in order and add it to a hash by cookie name if + # the cookie can be sent to the current URI. It's added to the hash + # so that when we are done, the cookies will be unique by name and + # we'll have grabbed the most specific to the URI. + @cookies.each do |cookie| + cookies[cookie.name] = cookie if !uri || cookie.matches?(uri) + end + + cookies + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/methods.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/methods.rb new file mode 100644 index 0000000000..dde029252d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/methods.rb @@ -0,0 +1,83 @@ +require 'forwardable' + +module Rack + module Test + # This module serves as the primary integration point for using Rack::Test + # in a testing environment. It depends on an app method being defined in the + # same context, and provides the Rack::Test API methods (see Rack::Test::Session + # for their documentation). + # + # Example: + # + # class HomepageTest < Test::Unit::TestCase + # include Rack::Test::Methods + # + # def app + # MyApp.new + # end + # end + module Methods + extend Forwardable + + def rack_mock_session(name = :default) # :nodoc: + return build_rack_mock_session unless name + + @_rack_mock_sessions ||= {} + @_rack_mock_sessions[name] ||= build_rack_mock_session + end + + def build_rack_mock_session # :nodoc: + Rack::MockSession.new(app) + end + + def rack_test_session(name = :default) # :nodoc: + return build_rack_test_session(name) unless name + + @_rack_test_sessions ||= {} + @_rack_test_sessions[name] ||= build_rack_test_session(name) + end + + def build_rack_test_session(name) # :nodoc: + Rack::Test::Session.new(rack_mock_session(name)) + end + + def current_session # :nodoc: + rack_test_session(_current_session_names.last) + end + + def with_session(name) # :nodoc: + _current_session_names.push(name) + yield rack_test_session(name) + _current_session_names.pop + end + + def _current_session_names # :nodoc: + @_current_session_names ||= [:default] + end + + METHODS = %i[ + request + get + post + put + patch + delete + options + head + custom_request + follow_redirect! + header + env + set_cookie + clear_cookies + authorize + basic_authorize + digest_authorize + last_response + last_request + ].freeze + + def_delegators :current_session, *METHODS + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/mock_digest_request.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/mock_digest_request.rb new file mode 100644 index 0000000000..bda24ff135 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/mock_digest_request.rb @@ -0,0 +1,25 @@ +module Rack + module Test + class MockDigestRequest # :nodoc: + def initialize(params) + @params = params + end + + def method_missing(sym) + if @params.key? k = sym.to_s + return @params[k] + end + + super + end + + def method + @params['method'] + end + + def response(password) + Rack::Auth::Digest::MD5.new(nil).send :digest, self, password + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/uploaded_file.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/uploaded_file.rb new file mode 100644 index 0000000000..d77fa353f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/uploaded_file.rb @@ -0,0 +1,85 @@ +require 'fileutils' +require 'pathname' +require 'tempfile' + +module Rack + module Test + # Wraps a Tempfile with a content type. Including one or more UploadedFile's + # in the params causes Rack::Test to build and issue a multipart request. + # + # Example: + # post "/photos", "file" => Rack::Test::UploadedFile.new("me.jpg", "image/jpeg") + class UploadedFile + # The filename, *not* including the path, of the "uploaded" file + attr_reader :original_filename + + # The tempfile + attr_reader :tempfile + + # The content type of the "uploaded" file + attr_accessor :content_type + + # Creates a new UploadedFile instance. + # + # @param content [IO, Pathname, String, StringIO] a path to a file, or an {IO} or {StringIO} object representing the + # file. + # @param content_type [String] + # @param binary [Boolean] an optional flag that indicates whether the file should be open in binary mode or not. + # @param original_filename [String] an optional parameter that provides the original filename if `content` is a StringIO + # object. Not used for other kind of `content` objects. + def initialize(content, content_type = 'text/plain', binary = false, original_filename: nil) + if original_filename + initialize_from_stringio(content, original_filename) + else + initialize_from_file_path(content) + end + @content_type = content_type + @tempfile.binmode if binary + end + + def path + tempfile.path + end + + alias local_path path + + def method_missing(method_name, *args, &block) #:nodoc: + tempfile.public_send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) #:nodoc: + tempfile.respond_to?(method_name, include_private) || super + end + + def self.finalize(file) + proc { actually_finalize file } + end + + def self.actually_finalize(file) + file.close + file.unlink + end + + private + + def initialize_from_stringio(stringio, original_filename) + @tempfile = stringio + @original_filename = original_filename || raise(ArgumentError, 'Missing `original_filename` for StringIO object') + end + + def initialize_from_file_path(path) + raise "#{path} file does not exist" unless ::File.exist?(path) + + @original_filename = ::File.basename(path) + extension = ::File.extname(@original_filename) + + @tempfile = Tempfile.new([::File.basename(@original_filename, extension), extension]) + @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding) + + ObjectSpace.define_finalizer(self, self.class.finalize(@tempfile)) + + FileUtils.copy_file(path, @tempfile.path) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/utils.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/utils.rb new file mode 100644 index 0000000000..5a9d88727e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/utils.rb @@ -0,0 +1,144 @@ +module Rack + module Test + module Utils # :nodoc: + include Rack::Utils + extend Rack::Utils + + def build_nested_query(value, prefix = nil) + case value + when Array + if value.empty? + "#{prefix}[]=" + else + value.map do |v| + prefix = "#{prefix}[]" unless unescape(prefix) =~ /\[\]$/ + build_nested_query(v, prefix.to_s) + end.join('&') + end + when Hash + value.map do |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) + end.join('&') + when NilClass + prefix.to_s + else + "#{prefix}=#{escape(value)}" + end + end + module_function :build_nested_query + + def build_multipart(params, first = true, multipart = false) + if first + raise ArgumentError, 'value must be a Hash' unless params.is_a?(Hash) + + query = lambda { |value| + case value + when Array + value.each(&query) + when Hash + value.values.each(&query) + when UploadedFile + multipart = true + end + } + params.values.each(&query) + return nil unless multipart + end + + flattened_params = {} + + params.each do |key, value| + k = first ? key.to_s : "[#{key}]" + + case value + when Array + value.map do |v| + if v.is_a?(Hash) + nested_params = {} + build_multipart(v, false).each do |subkey, subvalue| + nested_params[subkey] = subvalue + end + flattened_params["#{k}[]"] ||= [] + flattened_params["#{k}[]"] << nested_params + else + flattened_params["#{k}[]"] = value + end + end + when Hash + build_multipart(value, false).each do |subkey, subvalue| + flattened_params[k + subkey] = subvalue + end + else + flattened_params[k] = value + end + end + + if first + build_parts(flattened_params) + else + flattened_params + end + end + module_function :build_multipart + + private + + def build_parts(parameters) + get_parts(parameters).join + "--#{MULTIPART_BOUNDARY}--\r" + end + module_function :build_parts + + def get_parts(parameters) + parameters.map do |name, value| + if name =~ /\[\]\Z/ && value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) } + value.map do |hash| + new_value = {} + hash.each { |k, v| new_value[name + k] = v } + get_parts(new_value).join + end.join + else + if value.respond_to?(:original_filename) + build_file_part(name, value) + + elsif value.is_a?(Array) && value.all? { |v| v.respond_to?(:original_filename) } + value.map do |v| + build_file_part(name, v) + end.join + + else + primitive_part = build_primitive_part(name, value) + Rack::Test.encoding_aware_strings? ? primitive_part.force_encoding('BINARY') : primitive_part + end + end + end + end + module_function :get_parts + + def build_primitive_part(parameter_name, value) + value = [value] unless value.is_a? Array + value.map do |v| + <<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{parameter_name}"\r +\r +#{v}\r +EOF + end.join + end + module_function :build_primitive_part + + def build_file_part(parameter_name, uploaded_file) + uploaded_file.set_encoding(Encoding::BINARY) if uploaded_file.respond_to?(:set_encoding) + <<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{parameter_name}"; filename="#{escape(uploaded_file.original_filename)}"\r +Content-Type: #{uploaded_file.content_type}\r +Content-Length: #{uploaded_file.size}\r +\r +#{uploaded_file.read}\r +EOF + end + module_function :build_file_part + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/version.rb b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/version.rb new file mode 100644 index 0000000000..b8140df2ff --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test/version.rb @@ -0,0 +1,5 @@ +module Rack + module Test + VERSION = '1.1.0'.freeze + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/History.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/History.rdoc new file mode 100644 index 0000000000..b8751f9b63 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/History.rdoc @@ -0,0 +1,2403 @@ +=== 13.0.6 + +* Additional fix for #389 + Pull request #390 by hsbt + +=== 13.0.5 + +* Fixed the regression of #388 + Pull request #389 by hsbt + +=== 13.0.4 + +* Fix rake test loader swallowing useful error information. + Pull request #367 by deivid-rodriguez +* Add -C/--directory option the same as GNU make. + Pull request #376 by nobu + +=== 13.0.3 + +* Fix breaking change of execution order on TestTask. + Pull request #368 by ysakasin + +=== 13.0.2 + +==== Enhancements + +* Fix tests to work with current FileUtils + Pull Request #358 by jeremyevans +* Simplify default rake test loader + Pull Request #357 by deivid-rodriguez +* Update rdoc + Pull Request #366 by bahasalien +* Update broken links to rake articles from Avdi in README + Pull Request #360 by svl7 + +=== 13.0.1 + +==== Bug fixes + +* Fixed bug: Reenabled task raises previous exception on second invokation + Pull Request #271 by thorsteneckel +* Fix an incorrectly resolved arg pattern + Pull Request #327 by mjbellantoni + +=== 13.0.0 + +==== Enhancements + +* Follows recent changes on keyword arguments in ruby 2.7. + Pull Request #326 by nobu +* Make `PackageTask` be able to omit parent directory while packing files + Pull Request #310 by tonytonyjan +* Add order only dependency + Pull Request #269 by take-cheeze + +==== Compatibility changes + +* Drop old ruby versions(< 2.2) + +=== 12.3.3 + +==== Bug fixes + +* Use the application's name in error message if a task is not found. + Pull Request #303 by tmatilai + +==== Enhancements: + +* Use File.open explicitly. + +=== 12.3.2 + +==== Bug fixes + +* Fixed test fails caused by 2.6 warnings. + Pull Request #297 by hsbt + +==== Enhancements: + +* Rdoc improvements. + Pull Request #293 by colby-swandale +* Improve multitask performance. + Pull Request #273 by jsm +* Add alias `prereqs`. + Pull Request #268 by take-cheeze + +=== 12.3.1 + +==== Bug fixes + +* Support did_you_mean >= v1.2.0 which has a breaking change on formatters. + Pull request #262 by FUJI Goro. + +==== Enhancements: + +* Don't run task if it depends on already invoked but failed task. + Pull request #252 by Gonzalo Rodriguez. +* Make space trimming consistent for all task arguments. + Pull request #259 by Gonzalo Rodriguez. +* Removes duplicated inclusion of Rake::DSL in tests. + Pull request #254 by Gonzalo Rodriguez. +* Re-raise a LoadError that didn't come from require in the test loader. + Pull request #250 by Dylan Thacker-Smith. + +=== 12.3.0 + +==== Compatibility Changes + +* Bump `required_ruby_version` to Ruby 2.0.0. Rake has already + removed support for Ruby 1.9.x. + +==== Enhancements: + +* Support `test-bundled-gems` task on ruby core. + +=== 12.2.1 + +==== Bug fixes + +* Fixed to break Capistrano::Application on capistrano3. + +=== 12.2.0 + +==== Enhancements: + +* Make rake easier to use as a library + Pull request #211 by @drbrain +* Fix quadratic performance in FileTask#out_of_date? + Pull request #224 by @doudou +* Clarify output when printing nested exception traces + Pull request #232 by @urbanautomaton + +==== Bug fixes + +* Account for a file that match 2 or more patterns. + Pull request #231 by @styd + +=== 12.1.0 + +==== Enhancements: + +* Added did_you_mean feature for invalid rake task. + Pull request #221 by @xtina-starr +* Enabled to dependency chained by extensions. Pull request #39 by Petr Skocik. +* Make all of string literals to frozen objects on Ruby 2.4 or later. + +==== Bug fixes + +* Typo fixes in rakefile.rdoc. Pull request #180 by Yuta Kurotaki. +* Fix unexpected behavior of file task with dryrun option. + Pull request #183 by @aycabta. +* Make LoadError from running tests more obvious. Pull request #195 + by Eric Hodel. +* Fix unexpected TypeError with hash style option. Pull request #202 + by Kuniaki IGARASHI. + +=== 12.0.0 + +==== Compatibility Changes + +* Removed arguments on clear #157 by Jesse Bowes +* Removed `rake/contrib` packages. These are extracted to `rake-contrib` gem. +* Removed deprecated method named `last\_comment`. + +==== Enhancements: + +* Re-use trace option on `cleanup` task. #164 by Brian Henderson +* Actions adore keyword arguments #174 by Josh Cheek +* Rake::TaskArguments#key? alias of #has_key? #175 by Paul Annesley + +=== 11.3.0 / 2016-09-20 + +==== Enhancements: + +* Remove to reference `Fixnum` constant. Pull request #160 by nobu + +=== 11.2.2 / 2016-06-12 + +==== Bug fixes + +* Fix unexpected behavior with multiple dependencies on Rake::TestTask + +=== 11.2.1 / 2016-06-12 + +==== Bug fixes + +* Fix regression of dependencies handling on Rake::TestTask. Report #139 + +=== 11.2.0 / 2016-06-11 + +==== Bug fixes + +* Fix unexpected cut-out behavior on task description using triple dots + and exclamation. Report #106 from Stephan Kämper and Pull request #134 by Lee +* Fix empty argument assignment with `with_defaults` option. Pull request #135 + by bakunyo +* Ignore to use `hwprefs` on Darwin platform. Use sysctl now. Report #128 + +==== Enhancements + +* Spawn options for sh Pull equest #138 by Eric Hodel. +* Allow to specify dependencies(prerequisites) for Rake::TestTask + Pull request #117 by Tim Maslyuchenko +* Use Bundler task instead of hoe for gem release. +* Remove explicitly load to rubygems for Ruby 1.8. +* Unify to declare `Rake::VERSION`. +* Support xz format for PackageTask. + +=== 11.1.2 / 2016-03-28 + +==== Bug fixes + +* Remove `-W` option when Rake::TestTask#verbose enabled. It's misunderstanding + specification change with Rake 11. Partly revert #67 + +=== 11.1.1 / 2016-03-14 + +==== Bug fixes + +* Use `-W` instead of `--verbose` when Rake::TestTask#verbose enabled. + JRuby doesn't have `--verbose` option. + +=== 11.1.0 / 2016-03-11 + +==== Compatibility Changes + +* Revert to remove `last\_comment`. It will remove Rake 12. + +=== 11.0.1 / 2016-03-09 + +==== Bug fixes + +* Fixed packaging manifest. + +=== 11.0.0 / 2016-03-09 + +==== Bug fixes + +* Correctly handle bad encoding in exception messages. Pull request #113 + by Tomer Brisker +* Fix verbose option at TestTask. Pull request #67 by Mike Blumtritt + +==== Enhancements + +* Make FileList#exclude more analogous to FileList#include. +* Use IO.open instead of Open3.popen3 for CPU counter. +* Make Rake::Task#already_invoked publicly accessible. + Pull request #93 by Joe Rafaniello +* Lookup prerequisites with same name outside of scope instead of + matching self. Pull request #96 by Sandy Vanderbleek +* Make FileList#pathmap behave like String#pathmap. + Pull request #61 by Daniel Tamai +* Add fetch method to task arguments. + Pull request #12 by Chris Keathley +* Use ruby warnings by default. Pull request #97 by Harold Giménez + +==== Compatibility Changes + +* Removed to support Ruby 1.8.x +* Removed constant named `RAKEVERSION` +* Removed Rake::AltSystem +* Removed Rake::RubyForgePublisher +* Removed Rake::TaskManager#last\_comment. Use last\_description. +* Removed Rake::TaskLib#paste +* Removed Top-level SshDirPublisher, SshFreshDirPublisher, SshFilePublisher + and CompositePublisher from lib/rake/contrib/publisher.rb +* Removed "rake/runtest.rb" + +=== 10.5.0 / 2016-01-13 + +==== Enhancements + +* Removed monkey patching for Ruby 1.8. Pull request #46 by Pablo Herrero. +* Inheritance class of Rake::FileList returns always self class. + Pull request #74 by Thomas Scholz + +=== 10.4.2 / 2014-12-02 + +==== Bug fixes + +* Rake no longer edits ARGV. This allows you to re-exec rake from a rake + task. Pull requset #9 by Matt Palmer. +* Documented how Rake::DSL#desc handles sentences in task descriptions. + Issue #7 by Raza Sayed. +* Fixed test error on 1.9.3 with legacy RubyGems. Issue #8 by Matt Palmer. +* Deleted duplicated History entry. Pull request #10 by Yuji Yamamoto. + +=== 10.4.1 / 2014-12-01 + +==== Bug fixes + +* Reverted fix for #277 as it caused numerous issues for rake users. + rails/spring issue #366 by Gustavo Dutra. + +=== 10.4.0 / 2014-11-22 + +==== Enhancements + +* Upgraded to minitest 5. Pull request #292 by Teo Ljungberg. +* Added support for Pathname in rake tasks. Pull request #271 by Randy + Coulman. +* Rake now ignores falsy dependencies which allows for easier programmatic + creation of tasks. Pull request #273 by Manav. +* Rake no longer edits ARGV. This allows you to re-exec rake from a rake + task. Issue #277 by Matt Palmer. +* Etc.nprocessors is used for counting the number of CPUs. + +==== Bug fixes + +* Updated rake manpage. Issue #283 by Nathan Long, pull request #291 by + skittleys. +* Add Rake::LATE to allow rebuilding of files that depend on deleted files. + Bug #286, pull request #287 by David Grayson. +* Fix relinking of files when repackaging. Bug #276 by Muenze. +* Fixed some typos. Pull request #280 by Jed Northridge. +* Try counting CPUs via cpuinfo if host_os was not matched. Pull request + #282 by Edouard B. + +=== 10.3.2 / 2014-05-15 + +==== Bug fixes + +* Rake no longer infinitely loops when showing exception causes that refer to + each other. Bug #272 by Chris Bandy. +* Fixed documentation typos. Bug #275 by Jake Worth. + +=== 10.3.1 / 2014-04-17 + +==== Bug fixes + +* Really stop reporting an error when cleaning already-deleted files. Pull + request #269 by Randy Coulman +* Fixed infinite loop when cleaning already-deleted files on windows. + +=== 10.3 / 2014-04-15 + +==== Enhancements + +* Added --build-all option to rake which treats all file prerequisites as + out-of-date. Pull request #254 by Andrew Gilbert. +* Added Rake::NameSpace#scope. Issue #263 by Jon San Miguel. + +==== Bug fixes + +* Suppress org.jruby package files in rake error messages for JRuby users. + Issue #213 by Charles Nutter. +* Fixed typo, removed extra "h". Pull request #267 by Hsing-Hui Hsu. +* Rake no longer reports an error when cleaning already-deleted files. Pull + request #266 by Randy Coulman. +* Consume stderr while determining CPU count to avoid hang. Issue #268 by + Albert Sun. + +=== 10.2.2 / 2014-03-27 + +==== Bug fixes + +* Restored Ruby 1.8.7 compatibility + +=== 10.2.1 / 2014-03-25 + +==== Bug fixes + +* File tasks including a ':' are now top-level tasks again. Issue #262 by + Josh Holtrop. +* Use sysctl for CPU count for all BSDs. Pull request #261 by Joshua Stein. +* Fixed CPU detection for unknown platforms. + +=== 10.2.0 / 2014-03-24 + +==== Enhancements + +* Rake now requires Ruby 1.9 or newer. For me, this is a breaking change, but + it seems that Jim planned to release it with Rake 10.2. See also pull + request #247 by Philip Arndt. +* Rake now allows you to declare tasks under a namespace like: + + task 'a:b' do ... end + + Pull request #232 by Judson Lester. +* Task#source defaults to the first prerequisite in non-rule tasks. Pull + request #215 by Avdi Grimm. +* Rake now automatically rebuilds and reloads imported files. Pull request + #209 by Randy Coulman. +* The rake task arguments can contain escaped commas. Pull request #214 by + Filip Hrbek. +* Rake now prints the exception class on errors. Patch #251 by David Cornu. + +==== Bug fixes + +* Fixed typos. Pull request #256 by Valera Rozuvan, #250 via Jake Worth, #260 + by Zachary Scott. +* Fixed documentation for calling tasks with arguments. Pull request #235 by + John Varghese. +* Clarified `rake -f` usage message. Pull request #252 by Marco Pfatschbacher. +* Fixed a test failure on windows. Pull request #231 by Hiroshi Shirosaki. +* Fixed corrupted rake.1.gz. Pull request #225 by Michel Boaventura. +* Fixed bug in can\_detect\_signals? in test. Patch from #243 by Alexey + Borzenkov. + +=== 10.1.1 + +* Use http://github.com/jimweirich/rake instead of http://rake.rubyforge.org for + canonical project url. + +=== 10.1.0 + +==== Changes + +===== New Features + +* Add support for variable length task argument lists. If more actual + arguments are supplied than named arguments, then the extra + arguments values will be in args.extras. + +* Application name is not displayed in the help banner. (Previously + "rake" was hardcoded, now rake-based applications can display their + own names). + +===== Bug Fixes + +Bug fixes include: + +* Fix backtrace suppression issues. + +* Rules now explicit get task arguments passed to them. + +* Rename FileList#exclude? to FileList#exclude\_from\_list? to avoid + conflict with new Rails method. + +* Clean / Clobber tasks now report failure to remove files. + +* Plus heaps of internal code cleanup. + +==== Thanks + +As usual, it was input from users that drove a lot of these changes. +The following people contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Michael Nikitochkin (general code cleanup) +* Vipul A M (general code cleanup) +* Dennis Bell (variable length task argument lists) +* Jacob Swanner (rules arguments) +* Rafael Rosa Fu (documentation typo) +* Stuart Nelson (install.rb fixes) +* Lee Hambley (application name in help banner) + +-- Jim Weirich + +=== 10.0.3 + + "Jim, when will Rake reach version 1.0?" + +Over the past several years I've been asked that question at +conferences, panels and over twitter. Due to historical reasons (or +maybe just plain laziness) Rake has (incorrectly) been treating the +second digit of the version as the major release number. So in my head +Rake was already at version 9. + +Well, it's time to fix things. This next version of Rake drops old, +crufty, backwards compatibility hacks such as top level constants, DSL +methods defined in Object and numerous other features that are just no +longer desired. It's also time to drop the leading zero from the +version number as well and call this new version of rake what it +really is: Version 10. + +So, welcome to Rake 10.0! + +Rake 10 is actually feature identical to the latest version of Rake 9 +(that would be the version spelled 0.9.3), *except* that Rake 10 drops +all the sundry deprecated features that have accumulated over the years. + +If your Rakefile is up to date and current with all the new features +of Rake 10, you are ready to go. If your Rakefile still uses a few +deprecated feeatures, feel free to use Rake 9 (0.9.3) with the same +feature set. Just be aware that future features will be in Rake 10 +family line. + +==== Changes + +As mentioned above, there are no new features in Rake 10. However, +there are a number of features missing: + +* Classic namespaces are now gone. Rake is no longer able to reflect + the options settings in the global variables ($rakefile, $show\_tasks, + $show\_prereqs, $trace, $dryrun and $silent). The + --classic-namespace option is no longer supported. + +* Global constants are no longer supported. This includes + Task, FileTask, FileCreationTask and + RakeApp). The constant missing hook to warn about using + global rake constants has been removed. + +* The Rake DSL methods (task, file, directory, etc) are in their own + module (Rake::DSL). The stub versions of these methods (that printed + warnings) in Object have been removed. However, the DSL methods are + added to the top-level main object. Since main is + not in the inheritance tree, the presence of the DSL methods in main + should be low impact on other libraries. + + If you want to use the Rake DSL commands from your own code, just + include Rake::DSL into your own classes and modules. + +* The deprecated syntax for task arguments (the one using + :needs) has been removed. + +* The --reduce-compat flag has been removed (it's not needed + anymore). + +* The deprecated rake/sys.rb library has been removed. + +* The deprecated rake/rdoctask.rb library has been removed. + RDoc supplies its own rake task now. + +* The deprecated rake/gempackagetask.rb library has been + removed. Gem supplies its own package task now. + +There is one small behavioral change: + +* Non-file tasks now always report the current time as their time + stamp. This is different from the previous behavior where non-file + tasks reported current time only if there were no prerequisites, and + the max prerequisite timestamp otherwise. This lead to inconsistent + and surprising behavior when adding prerequisites to tasks that in + turn were prequisites to file tasks. The new behavior is more + consistent and predictable. + +==== Changes (from 0.9.3, 0.9.4, 0.9.5) + +Since Rake 10 includes the changes from the last version of Rake 9, +we'll repeat the changes for versions 0.9.3 through 0.9.5 here. + +===== New Features (in 0.9.3) + +* Multitask tasks now use a thread pool. Use -j to limit the number of + available threads. + +* Use -m to turn regular tasks into multitasks (use at your own risk). + +* You can now do "Rake.add_rakelib 'dir'" in your Rakefile to + programatically add rake task libraries. + +* You can specific backtrace suppression patterns (see + --suppress-backtrace) + +* Directory tasks can now take prerequisites and actions + +* Use --backtrace to request a full backtrace without the task trace. + +* You can say "--backtrace=stdout" and "--trace=stdout" to route trace + output to standard output rather than standard error. + +* Optional 'phony' target (enable with 'require 'rake/phony'") for + special purpose builds. + +* Task#clear now clears task comments as well as actions and + prerequisites. Task#clear_comment will specifically target comments. + +* The --all option will force -T and -D to consider all the tasks, + with and without descriptions. + +===== Bug Fixes (in 0.9.3) + +* Semi-colons in windows rakefile paths now work. + +* Improved Control-C support when invoking multiple test suites. + +* egrep method now reads files in text mode (better support for + Windows) + +* Better deprecation line number reporting. + +* The -W option now works with all tasks, whether they have a + description or not. + +* File globs in rake should not be sorted alphabetically, independent + of file system and platform. + +* Numerous internal improvements. + +* Documentation typos and fixes. + +===== Bug Fixes (in 0.9.4) + +* Exit status with failing tests is not correctly set to non-zero. + +* Simplified syntax for phony task (for older versions of RDoc). + +* Stand alone FileList usage gets glob function (without loading in + extra dependencies) + +===== Bug Fixes (in 0.9.5) + +* --trace and --backtrace no longer swallow following task names. + +==== Thanks + +As usual, it was input from users that drove a lot of these changes. The +following people contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 10.0.2 + +==== Changes + +===== Bug Fixes + +* --trace and --backtrace no longer swallow following task names. + +==== Thanks + +As usual, it was input from users that drove a lot of these changes. The +following people contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 10.0.1 + +==== Changes + +===== Bug Fixes + +* Exit status with failing tests is not correctly set to non-zero. + +* Simplified syntax for phony task (for older versions of RDoc). + +* Stand alone FileList usage gets glob function (without loading in + extra dependencies) + +==== Thanks + +As usual, it was input from users that drove a lot of these changes. The +following people contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 10.0.0 + + "Jim, when will Rake reach version 1.0?" + +Over the past several years I've been asked that question at +conferences, panels and over twitter. Due to historical reasons (or +maybe just plain laziness) Rake has (incorrectly) been treating the +second digit of the version as the major release number. So in my head +Rake was already at version 9. + +Well, it's time to fix things. This next version of Rake drops old, +crufty, backwards compatibility hacks such as top level constants, DSL +methods defined in Object and numerous other features that are just no +longer desired. It's also time to drop the leading zero from the +version number as well and call this new version of rake what it +really is: Version 10. + +So, welcome to Rake 10.0! + +Rake 10 is actually feature identical to the latest version of Rake 9 +(that would be the version spelled 0.9.3), *except* that Rake 10 drops +all the sundry deprecated features that have accumulated over the years. + +If your Rakefile is up to date and current with all the new features +of Rake 10, you are ready to go. If your Rakefile still uses a few +deprecated feeatures, feel free to use Rake 9 (0.9.3) with the same +feature set. Just be aware that future features will be in Rake 10 +family line. + +==== Changes in 10.0 + +As mentioned above, there are no new features in Rake 10. However, +there are a number of features missing: + +* Classic namespaces are now gone. Rake is no longer able to reflect + the options settings in the global variables ($rakefile, $show\_tasks, + $show\_prereqs, $trace, $dryrun and $silent). The + --classic-namespace option is no longer supported. + +* Global constants are no longer supported. This includes + Task, FileTask, FileCreationTask and + RakeApp). The constant missing hook to warn about using + global rake constants has been removed. + +* The Rake DSL methods (task, file, directory, etc) are in their own + module (Rake::DSL). The stub versions of these methods (that printed + warnings) in Object have been removed. However, the DSL methods are + added to the top-level main object. Since main is + not in the inheritance tree, the presence of the DSL methods in main + should be low impact on other libraries. + + If you want to use the Rake DSL commands from your own code, just + include Rake::DSL into your own classes and modules. + +* The deprecated syntax for task arguments (the one using + :needs) has been removed. + +* The --reduce-compat flag has been removed (it's not needed + anymore). + +* The deprecated rake/sys.rb library has been removed. + +* The deprecated rake/rdoctask.rb library has been removed. + RDoc supplies its own rake task now. + +* The deprecated rake/gempackagetask.rb library has been + removed. Gem supplies its own package task now. + +There is one small behavioral change: + +* Non-file tasks now always report the current time as their time + stamp. This is different from the previous behavior where non-file + tasks reported current time only if there were no prerequisites, and + the max prerequisite timestamp otherwise. This lead to inconsistent + and surprising behavior when adding prerequisites to tasks that in + turn were prequisites to file tasks. The new behavior is more + consistent and predictable. + +==== Changes (from 0.9.3) + +Since Rake 10 includes the changes from the last version of Rake 9, +we'll repeat the changes for version 0.9.3 here. + +===== New Features + +* Multitask tasks now use a thread pool. Use -j to limit the number of + available threads. + +* Use -m to turn regular tasks into multitasks (use at your own risk). + +* You can now do "Rake.add_rakelib 'dir'" in your Rakefile to + programatically add rake task libraries. + +* You can specific backtrace suppression patterns (see + --suppress-backtrace) + +* Directory tasks can now take prerequisites and actions + +* Use --backtrace to request a full backtrace without the task trace. + +* You can say "--backtrace=stdout" and "--trace=stdout" to route trace + output to standard output rather than standard error. + +* Optional 'phony' target (enable with 'require 'rake/phony'") for + special purpose builds. + +* Task#clear now clears task comments as well as actions and + prerequisites. Task#clear_comment will specifically target comments. + +* The --all option will force -T and -D to consider all the tasks, + with and without descriptions. + +===== Bug Fixes + +* Semi-colons in windows rakefile paths now work. + +* Improved Control-C support when invoking multiple test suites. + +* egrep method now reads files in text mode (better support for + Windows) + +* Better deprecation line number reporting. + +* The -W option now works with all tasks, whether they have a + description or not. + +* File globs in rake should not be sorted alphabetically, independent + of file system and platform. + +* Numerous internal improvements. + +* Documentation typos and fixes. + + +==== Thanks + +As usual, it was input from users that drove a lot of these changes. The +following people contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 0.9.6 + +Rake version 0.9.6 contains a number of fixes mainly for merging +Rake into the Ruby source tree and fixing tests. + +==== Changes + +===== Bug Fixes (0.9.6) + +* Better trace output when using a multi-threaded Rakefile. +* Arg parsing is now consistent for tasks and multitasks. +* Skip exit code test in versions of Ruby that don't support it well. + +Changes for better integration with the Ruby source tree: + +* Fix version literal for Ruby source tree build. +* Better loading of libraries for testing in Ruby build. +* Use the ruby version provided by Ruby's tests. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 0.9.5 + +Rake version 0.9.5 contains a number of bug fixes. + +==== Changes + +===== Bug Fixes (0.9.5) + +* --trace and --backtrace no longer swallow following task names. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 0.9.4 + +Rake version 0.9.4 contains a number of bug fixes. + +==== Changes + +===== Bug Fixes (0.9.4) + +* Exit status with failing tests is not correctly set to non-zero. + +* Simplified syntax for phony task (for older versions of RDoc). + +* Stand alone FileList usage gets glob function (without loading in + extra dependencies) + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== 0.9.3 + +Rake version 0.9.3 contains some new, backwards compatible features and +a number of bug fixes. + +==== Changes + +===== New Features + +* Multitask tasks now use a thread pool. Use -j to limit the number of + available threads. + +* Use -m to turn regular tasks into multitasks (use at your own risk). + +* You can now do "Rake.add_rakelib 'dir'" in your Rakefile to + programatically add rake task libraries. + +* You can specific backtrace suppression patterns (see + --suppress-backtrace) + +* Directory tasks can now take prerequisites and actions + +* Use --backtrace to request a full backtrace without the task trace. + +* You can say "--backtrace=stdout" and "--trace=stdout" to route trace + output to standard output rather than standard error. + +* Optional 'phony' target (enable with 'require 'rake/phony'") for + special purpose builds. + +* Task#clear now clears task comments as well as actions and + prerequisites. Task#clear_comment will specifically target comments. + +* The --all option will force -T and -D to consider all the tasks, + with and without descriptions. + +===== Bug Fixes + +* Semi-colons in windows rakefile paths now work. + +* Improved Control-C support when invoking multiple test suites. + +* egrep method now reads files in text mode (better support for + Windows) + +* Better deprecation line number reporting. + +* The -W option now works with all tasks, whether they have a + description or not. + +* File globs in rake should not be sorted alphabetically, independent + of file system and platform. + +* Numerous internal improvements. + +* Documentation typos and fixes. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Aaron Patterson +* Dylan Smith +* Jo Liss +* Jonas Pfenniger +* Kazuki Tsujimoto +* Michael Bishop +* Michael Elufimov +* NAKAMURA Usaku +* Ryan Davis +* Sam Grönblom +* Sam Phippen +* Sergio Wong +* Tay Ray Chuan +* grosser +* quix + +Also, many thanks to Eric Hodel for assisting with getting this release +out the door. + +-- Jim Weirich + +=== Rake 0.9.2.2 + +Rake version 0.9.2.2 is mainly bug fixes. + +==== Changes + +* The rake test loader now removes arguments it has processed. Issue #51 +* Rake::TaskArguments now responds to #values\_at +* RakeFileUtils.verbose_flag = nil silences output the same as 0.8.7 +* Rake tests are now directory-independent +* Rake tests are no longer require flexmock +* Commands constant is no longer polluting top level namespace. +* Show only the interesting portion of the backtrace by default (James M. Lawrence). +* Added --reduce-compat option to remove backward compatible DSL hacks (James M. Lawrence). + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence (quix) +* Roger Pack +* Cezary Baginski +* Sean Scot August Moon +* R.T. Lechow +* Alex Chaffee +* James Tucker +* Matthias Lüdtke +* Santiago Pastorino + +Also, bit thanks to Eric Hodel for assisting with getting this release +out the door (where "assisting" includes, but is not by any means +limited to, "pushing" me to get it done). + +-- Jim Weirich + +=== 0.9.2 + +Rake version 0.9.2 has a few small fixes. See below for details. + +==== Changes + +* Support for Ruby 1.8.6 was fixed. +* Global DSL warnings now honor --no-deprecate + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence (quix) +* Roger Pack +* Cezary Baginski +* Sean Scot August Moon +* R.T. Lechow +* Alex Chaffee +* James Tucker +* Matthias Lüdtke +* Santiago Pastorino + +Also, bit thanks to Eric Hodel for assisting with getting this release +out the door (where "assisting" includes, but is not by any means +limited to, "pushing" me to get it done). + +-- Jim Weirich + +=== 0.9.1 + +Rake version 0.9.1 has a number of bug fixes and enhancments (see +below for more details). Additionally, the internals have be slightly +restructured and improved. + +==== Changes + +Rake 0.9.1 adds back the global DSL methods, but with deprecation +messages. This allows Rake 0.9.1 to be used with older rakefiles with +warning messages. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence (quix) +* Roger Pack +* Cezary Baginski +* Sean Scot August Moon +* R.T. Lechow +* Alex Chaffee +* James Tucker +* Matthias Lüdtke +* Santiago Pastorino + +Also, bit thanks to Eric Hodel for assisting with getting this release +out the door (where "assisting" includes, but is not by any means +limited to, "pushing" me to get it done). + +-- Jim Weirich + +=== 0.9.0 + +Rake version 0.9.0 has a number of bug fixes and enhancments (see +below for more details). Additionally, the internals have be slightly +restructured and improved. + +==== Changes + +===== New Features / Enhancements / Bug Fixes in Version 0.9.0 + +* Rake now warns when the deprecated :needs syntax used (and suggests + the proper syntax in the warning). + +* Moved Rake DSL commands to top level ruby object 'main'. Rake DSL + commands are no longer private methods in Object. (Suggested by + James M. Lawrence/quix) + +* Rake now uses case-insensitive comparisons to find the Rakefile on Windows. + Based on patch by Roger Pack. + +* Rake now requires (instead of loads) files in the test task. Patch by Cezary + Baginski. + +* Fixed typos. Patches by Sean Scot August Moon and R.T. Lechow. + +* Rake now prints the Rakefile directory only when it's different from the + current directory. Patch by Alex Chaffee. + +* Improved rakefile_location discovery on Windows. Patch by James Tucker. + +* Rake now recognizes "Windows Server" as a windows system. Patch by Matthias + Lüdtke + +* Rake::RDocTask is deprecated. Use RDoc::Task from RDoc 2.4.2+ (require + 'rdoc/task') + +* Rake::GemPackageTask is deprecated. Use Gem::PackageTask (require + 'rubygems/package\_task') + +* Rake now outputs various messages to $stderr instead of $stdout. + +* Rake no longer emits warnings for Config. Patch by Santiago Pastorino. + +* Removed Rake's DSL methods from the top level scope. If you need to + call 'task :xzy' in your code, include Rake::DSL into your class, or + put the code in a Rake::DSL.environment do ... end block. + +* Split rake.rb into individual files. + +* Support for the --where (-W) flag for showing where a task is defined. + +* Fixed quoting in test task. + (http://onestepback.org/redmine/issues/show/44, + http://www.pivotaltracker.com/story/show/1223138) + +* Fixed the silent option parsing problem. + (http://onestepback.org/redmine/issues/show/47) + +* Fixed :verbose=>false flag on sh and ruby commands. + +* Rake command line options may be given by default in a RAKEOPT + environment variable. + +* Errors in Rake will now display the task invocation chain in effect + at the time of the error. + +* Accepted change by warnickr to not expand test patterns in shell + (allowing more files in the test suite). + +* Fixed that file tasks did not perform prereq lookups in scope + (Redmine #57). + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence (quix) +* Roger Pack +* Cezary Baginski +* Sean Scot August Moon +* R.T. Lechow +* Alex Chaffee +* James Tucker +* Matthias Lüdtke +* Santiago Pastorino + +Also, bit thanks to Eric Hodel for assisting with getting this release +out the door (where "assisting" includes, but is not by any means +limited to, "pushing" me to get it done). + +-- Jim Weirich + + +=== 0.8.7 + +Rake version 0.8.5 introduced greatly improved support for executing +commands on Windows. The "sh" command now has the same semantics on +Windows that it has on Unix based platforms. + +Rake version 0.8.6 includes minor fixes the the RDoc generation. +Rake version 0.8.7 includes a minor fix for JRuby running on windows. + +==== Changes + +===== New Features / Enhancements in Version 0.8.5 + +* Improved implementation of the Rake system command for Windows. + (patch from James M. Lawrence/quix) + +* Support for Ruby 1.9's improved system command. (patch from James + M. Lawrence/quix) + +* Rake now includes the configured extension when invoking an + executable (Config::CONFIG['EXEEXT]) + +===== Bug Fixes in Version 0.8.5 + +* Environment variable keys are now correctly cased (it matters in + some implementations). + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Charles Nutter + +-- Jim Weirich + +=== 0.8.6 + +Rake version 0.8.5 introduced greatly improved support for executing +commands on Windows. The "sh" command now has the same semantics on +Windows that it has on Unix based platforms. + +Rake version 0.8.5 includes minor fixes the the RDoc generation. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence/quix +* Luis Lavena + +-- Jim Weirich + +=== 0.8.5 + +Rake version 0.8.5 is a new release of Rake with greatly improved +support for executing commands on Windows. The "sh" command now has +the same semantics on Windows that it has on Unix based platforms. + +==== Changes + +===== New Features / Enhancements in Version 0.8.5 + +* Improved implementation of the Rake system command for Windows. + (patch from James M. Lawrence/quix) + +* Support for Ruby 1.9's improved system command. (patch from James + M. Lawrence/quix) + +* Rake now includes the configured extension when invoking an + executable (Config::CONFIG['EXEEXT]) + +===== Bug Fixes in Version 0.8.5 + +* Environment variable keys are now correctly cased (it matters in + some implementations). + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence/quix +* Luis Lavena + +-- Jim Weirich + +=== 0.8.4 + +Rake version 0.8.4 is a bug-fix release of rake. + +NOTE: The version of Rake that comes with Ruby 1.9 has diverged + slightly from the core Rake code base. Rake 0.8.4 will work + with Ruby 1.9, but is not a strict upgrade for the Rake that + comes with Ruby 1.9. A (near) future release of Rake will unify + those two codebases. + +==== Letter Writing Campaign + +Thanks to Aaron Patterson (@tenderlove) and Eric Hodel (@drbrain) for +their encouraging support in organizing a letter writing campaign to +lobby for the "Warning Free" release of rake 0.8.4. A special callout +goes to Jonathan D. Lord, Sr (Dr. Wingnut) whose postcard was the +first to actually reach me. (see +http://tenderlovemaking.com/2009/02/26/we-need-a-new-version-of-rake/ +for details) + +==== Changes + +===== New Features / Enhancements in Version 0.8.4 + +* Case is preserved on rakefile names. (patch from James + M. Lawrence/quix) + +* Improved Rakefile case insensitivity testing (patch from Luis + Lavena). + +* Windows system dir search order is now: HOME, HOMEDRIVE + HOMEPATH, + APPDATA, USERPROFILE (patch from Luis Lavena) + +* MingGW is now recognized as a windows platform. (patch from Luis + Lavena) + +===== Bug Fixes in Version 0.8.4 + +* Removed reference to manage_gem to fix the warning produced by the + gem package task. + +* Fixed stray ARGV option problem that was interfering with + Test::Unit::Runner. (patch from Pivotal Labs) + +===== Infrastructure Improvements in Version 0.8.4 + +* Numerous fixes to the windows test suite (patch from Luis Lavena). + +* Improved Rakefile case insensitivity testing (patch from Luis + Lavena). + +* Better support for windows paths in the test task (patch from Simon + Chiang/bahuvrihi) + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* James M. Lawrence/quix +* Luis Lavena +* Pivotal Labs +* Simon Chiang/bahuvrihi + +-- Jim Weirich + +=== 0.8.3 + +Rake version 0.8.3 is a bug-fix release of rake. + +==== Changes + +===== Bug Fixes in Version 0.8.3 + +* Enhanced the system directory detection in windows. We now check + HOMEDRIVE/HOMEPATH and USERPROFILE if APPDATA isn't found. (Patch + supplied by James Tucker). Rake no long aborts if it can't find the + directory. + +* Added fix to handle ruby installations in directories with spaces in + their name. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Edwin Pratomo +* Gavin Stark +* Adam Q. Salter +* Adam Majer +* Emanuel Indermühle +* Ittay Dror +* Bheeshmar Redheendran (for spending an afternoon with me debugging + windows issues) + +-- Jim Weirich + + +=== 0.8.2 + +Rake version 0.8.2 is a new release of rake that includes a number of +new features and numerous bug fixes. + +==== Changes + +===== New Features in Version 0.8.2 + +* Switched from getoptlong to optparse (patches supplied by Edwin + Pratomo). + +* The -T option will now attempt to dynamically sense the size of the + terminal. The -T output will only self-truncate if the output is a + tty. However, if RAKE_COLUMNS is explicitly set, it will be honored + in any case. (Patch provided by Gavin Stark). + +* The following public methods have been added to rake task objects: + + * task.clear -- Clear both the prerequisites and actions of the + target rake task. + * task.clear_prerequisites -- Clear all the existing prerequisites + from the target rake task. + * task.clear_actions -- Clear all the existing actions from the + target rake task. + * task.reenable -- Re-enable a task, allowing its actions to be + executed again if the task is invoked. + +* Changed RDoc test task to have no default template. This makes it + easier for the tempate to pick up the template from the environment. + +* Default values for task arguments can easily be specified with the + :with_defaults method. (Idea for default argument merging supplied + by (Adam Q. Salter) + +===== Bug Fixes in Version 0.8.2 + +* Fixed bug in package task so that it will include the subdir + directory in the package for testing. (Bug found by Adam Majer) + +* Fixed filename dependency order bug in test\_inspect\_pending and + test\_to\_s\_pending. (Bug found by Adam Majer) + +* Fixed check for file utils options to make them immune to the + symbol/string differences. (Patch supplied by Edwin Pratomo) + +* Fixed bug with rules involving multiple source, where only the first + dependency of a rule has any effect (Patch supplied by Emanuel + Indermühle) + +* FileList#clone and FileList#dup have better sematics w.r.t. taint + and freeze. + +* Changed from using Mutex to Monitor. Evidently Mutex causes thread + join errors when Ruby is compiled with -disable-pthreads. (Patch + supplied by Ittay Dror) + +* Fixed bug in makefile parser that had problems with extra spaces in + file task names. (Patch supplied by Ittay Dror) + +==== Other changes in Version 0.8.2 + +* Added ENV var to rake's own Rakefile to prevent OS X from including + extended attribute junk in the rake package tar file. (Bug found by + Adam Majer) + +* Added a performance patch for reading large makefile dependency + files. (Patch supplied by Ittay Dror) + +==== Task Argument Examples + +Prior to version 0.8.0, rake was only able to handle command line +arguments of the form NAME=VALUE that were passed into Rake via the +ENV hash. Many folks had asked for some kind of simple command line +arguments, perhaps using "--" to separate regular task names from +argument values on the command line. The problem is that there was no +easy way to associate positional arguments on the command line with +different tasks. Suppose both tasks :a and :b expect a command line +argument: does the first value go with :a? What if :b is run first? +Should it then get the first command line argument. + +Rake 0.8.0 solves this problem by explicitly passing values directly +to the tasks that need them. For example, if I had a release task +that required a version number, I could say: + + rake release[0.8.2] + +And the string "0.8.2" will be passed to the :release task. Multiple +arguments can be passed by separating them with a comma, for example: + + rake name[john,doe] + +Just a few words of caution. The rake task name and its arguments +need to be a single command line argument to rake. This generally +means no spaces. If spaces are needed, then the entire rake + +argument string should be quoted. Something like this: + + rake "name[billy bob, smith]" + +(Quoting rules vary between operating systems and shells, so make sure +you consult the proper docs for your OS/shell). + +===== Tasks that Expect Parameters + +Parameters are only given to tasks that are setup to expect them. In +order to handle named parameters, the task declaration syntax for +tasks has been extended slightly. + +For example, a task that needs a first name and last name might be +declared as: + + task :name, :first_name, :last_name + +The first argument is still the name of the task (:name in this case). +The next to argumements are the names of the parameters expected by +:name (:first_name and :last_name in the example). + +To access the values of the parameters, the block defining the task +behaviour can now accept a second parameter: + + task :name, :first_name, :last_name do |t, args| + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +The first argument of the block "t" is always bound to the current +task object. The second argument "args" is an open-struct like object +that allows access to the task arguments. Extra command line +arguments to a task are ignored. Missing command line arguments are +given the nil value. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Edwin Pratomo +* Gavin Stark +* Adam Q. Salter +* Adam Majer +* Emanuel Indermühle +* Ittay Dror +* Bheeshmar Redheendran (for spending an afternoon with me debugging + windows issues) + +-- Jim Weirich + +=== 0.8.0/0.8.1 + +Rake version 0.8.0 is a new release of rake that includes serveral new +features. + +==== Changes + +===== New Features in Version 0.8.0 + +* Tasks can now receive command line parameters. See the examples + below for more details. + +* Comments are limited to 80 columns on output, but full comments can + be seen by using the -D parameter. (feature suggested by Jamis + Buck). + +* Explicit exit(n) calls will now set the exit status to n. (patch + provided by Stephen Touset). + +* Rake is now compatible with Ruby 1.9. + +Version 0.8.1 is a minor update that includes additional Ruby 1.9 +compatibility fixes. + +==== Task Argument Examples + +Prior to version 0.8.0, rake was only able to handle command line +arguments of the form NAME=VALUE that were passed into Rake via the +ENV hash. Many folks had asked for some kind of simple command line +arguments, perhaps using "--" to separate regular task names from +argument values on the command line. The problem is that there was no +easy way to associate positional arguments on the command line with +different tasks. Suppose both tasks :a and :b expect a command line +argument: does the first value go with :a? What if :b is run first? +Should it then get the first command line argument. + +Rake 0.8.0 solves this problem by explicitly passing values directly +to the tasks that need them. For example, if I had a release task +that required a version number, I could say: + + rake release[0.8.0] + +And the string "0.8.0" will be passed to the :release task. Multiple +arguments can be passed by separating them with a comma, for example: + + rake name[john,doe] + +Just a few words of caution. The rake task name and its arguments +need to be a single command line argument to rake. This generally +means no spaces. If spaces are needed, then the entire rake + +argument string should be quoted. Something like this: + + rake "name[billy bob, smith]" + +(Quoting rules vary between operating systems and shells, so make sure +you consult the proper docs for your OS/shell). + +===== Tasks that Expect Parameters + +Parameters are only given to tasks that are setup to expect them. In +order to handle named parameters, the task declaration syntax for +tasks has been extended slightly. + +For example, a task that needs a first name and last name might be +declared as: + + task :name, :first_name, :last_name + +The first argument is still the name of the task (:name in this case). +The next to argumements are the names of the parameters expected by +:name (:first_name and :last_name in the example). + +To access the values of the parameters, the block defining the task +behaviour can now accept a second parameter: + + task :name, :first_name, :last_name do |t, args| + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +The first argument of the block "t" is always bound to the current +task object. The second argument "args" is an open-struct like object +that allows access to the task arguments. Extra command line +arguments to a task are ignored. Missing command line arguments are +given the nil value. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +* Jamis Buck (for comment formatting suggestions) +* Stephen Touset (for exit status patch). + +-- Jim Weirich + + +=== 0.7.3 + +Rake version 0.7.3 is a minor release that includes some refactoring to better +support custom Rake applications. + +==== Changes + +===== New Features in Version 0.7.3 + +* Added the +init+ and +top_level+ methods to make the creation of custom Rake applications a bit easier. E.g. + + gem 'rake', ">= 0.7.3" + require 'rake' + + Rake.application.init('myrake') + + task :default do + something_interesting + end + + Rake.application.top_level + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +-- Jim Weirich + + +=== 0.7.2 + + +Version 0.7.2 supplies a bug fix and a few minor enhancements. In +particular, the new version fixes an incompatibility with the soon to +be released Ruby 1.8.6. We strongly recommend upgrading to Rake 0.7.2 +in order to be compatible with the new version of Ruby. + +==== Changes + +===== Bug Fixes in 0.7.2 + +There are quite a number of bug fixes in the new 0.7.2 version of +Rake: + +* Removed dependency on internal fu_xxx functions from FileUtils. + +* Error messages are now send to stderr rather than stdout (from + Payton Quackenbush). + +* Better error handling on invalid command line arguments (from Payton + Quackenbush). + +* Fixed some bugs where the application object was going to the global + appliation instead of using its own data. + +* Fixed the method name leak from FileUtils (bug found by Glenn + Vanderburg). + +* Added test for noop, bad_option and verbose flags to sh command. + +* Added a description to the gem task in GemPackageTask. + +* Fixed a bug when rules have multiple prerequisites (patch by Joel + VanderWerf) + +* Added the handful of RakeFileUtils to the private method as well. + +===== New Features in 0.7.2 + +The following new features are available in Rake version 0.7.2: + +* Added square and curly bracket patterns to FileList#include (Tilman + Sauerbeck). + +* FileLists can now pass a block to FileList#exclude to exclude files + based on calculated values. + +* Added plain filename support to rule dependents (suggested by Nobu + Nakada). + +* Added pathmap support to rule dependents. In other words, if a + pathmap format (beginning with a '%') is given as a Rake rule + dependent, then the name of the depend will be the name of the + target with the pathmap format applied. + +* Added a 'tasks' method to a namespace to get a list of tasks + associated with the namespace. + +* Added tar_command and zip_command options to the Package task. + +* The clean task will no longer delete 'core' if it is a directory. + +===== Internal Rake Improvements + +The following changes will are mainly internal improvements and +refactorings and have little effect on the end user. But they may be +of interest to the general public. + +* Added rcov task and updated unit testing for better code coverage. + +* Added a 'shame' task to the Rakefile. + +* Added rake_extension to handle detection of extension collisions. + +* Added a protected 'require "rubygems"' to test/test_application to + unbreak cruisecontrol.rb. + +* Removed rake\_dup. Now we just simply rescue a bad dup. + +* Refactored the FileList reject logic to remove duplication. + +* Removed if \_\_FILE\_\_ at the end of the rake.rb file. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. +The following people either contributed patches, made suggestions or +made otherwise helpful comments. Thanks to ... + +* Payton Quackenbush -- For several error handling improvements. + +* Glenn Vanderburg -- For finding and fixing the method name leak from + FileUtils. + +* Joel VanderWerf -- for finding and fixing a bug in the handling of + multiple prerequisites. + +* Tilman Sauerbeck -- For some enhancing FileList to support more + advanced file globbing. + +* Nobu Nakada -- For suggesting plain file name support to rule dependents. + +-- Jim Weirich + +=== 0.7.1 + +Version 0.7.1 supplies a bug fix and a few minor enhancements. + +==== Changes + +===== Bug Fixes in 0.7.1 + +* Changes in the exception reported for the FileUtils.ln caused + safe_ln to fail with a NotImplementedError. Rake 0.7.1 will now + catch that error or any StandardError and properly fall back to + using +cp+. + +===== New Features in 0.7.1 + +* You can filter the results of the --task option by supplying an + optional regular expression. This allows the user to easily find a + particular task name in a long list of possible names. + +* Transforming procs in a rule may now return a list of prerequisites. + This allows more flexible rule formation. + +* FileList and String now support a +pathmap+ melthod that makes the + transforming paths a bit easier. See the API docs for +pathmap+ for + details. + +* The -f option without a value will disable the search for a + Rakefile. This allows the Rakefile to be defined entirely in a + library (and loaded with the -r option). The current working + directory is not changed when this is done. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. +The following people either contributed patches, made suggestions or +made otherwise helpful comments. Thanks to ... + +* James Britt and Assaph Mehr for reporting and helping to debug the + safe_ln issue. + +-- Jim Weirich + + +=== 0.7.0 + +These changes for Rake have been brewing for a long time. Here they +are, I hope you enjoy them. + +==== Changes + +===== New Features + +* Name space support for task names (see below). +* Prerequisites can be executed in parallel (see below). +* Added safe_ln support for openAFS (via Ludvig Omholt). +* RDoc defaults to internal (in-process) invocation. The old behavior + is still available by setting the +external+ flag to true. +* Rakefiles are now loaded with the expanded path to prevent + accidental pollution from the Ruby load path. +* Task objects my now be used in prerequisite lists directly. +* Task objects (in addition to task names) may now be included in the + prerequisite list of a task. +* Internals cleanup and refactoring. + +===== Bug Fixes + +* Compatibility fixes for Ruby 1.8.4 FileUtils changes. + +===== Namespaces + +Tasks can now be nested inside their own namespaces. Tasks within one +namespace will not accidentally interfer with tasks named in a different +namespace. + +For example: + + namespace "main" do + task :build do + # Build the main program + end + end + + namespace "samples" do + task :build do + # Build the sample programs + end + end + + task :build_all => ["main:build", "samples:build"] + +Even though both tasks are named :build, they are separate tasks in +their own namespaces. The :build_all task (defined in the toplevel +namespace) references both build tasks in its prerequisites. + +You may invoke each of the individual build tasks with the following +commands: + + rake main:build + rake samples:build + +Or invoke both via the :build_all command: + + rake build_all + +Namespaces may be nested arbitrarily. Since the name of file tasks +correspond to the name of a file in the external file system, +FileTasks are not affected by the namespaces. + +See the Rakefile format documentation (in the Rake API documents) for +more information. + +===== Parallel Tasks + +Sometimes you have several tasks that can be executed in parallel. By +specifying these tasks as prerequisites to a +multitask+ task. + +In the following example the tasks copy\_src, copy\_doc and copy\_bin +will all execute in parallel in their own thread. + + multitask :copy_files => [:copy_src, :copy_doc, :copy_bin] do + puts "All Copies Complete" + end + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. +The following people either contributed patches, made suggestions or +made otherwise helpful comments. Thanks to ... + +* Doug Young (inspiration for the parallel task) +* David Heinemeier Hansson (for --trace message enhancement and for + pushing for namespace support). +* Ludvig Omholt (for the openAFS fix) + +-- Jim Weirich + +=== 0.6.1 + +* Rebuilt 0.6.0 gem without signing. + +=== 0.6.0 + +Its time for some long requested enhancements and lots of bug fixes +... And a whole new web page. + +==== New Web Page + +The primary documentation for rake has moved from the RubyForge based +wiki to its own Hieraki based web site. Constant spam on the wiki +made it a difficult to keep clean. The new site will be easier to +update and organize. + +Check out the new documentation at: http://docs.rubyrake.org + +We will be adding new documentation to the site as time goes on. + +In addition to the new docs page, make sure you check out Martin +Fowlers article on rake at http://martinfowler.com/articles/rake.html + +==== Changes + +===== New Features + +* Multiple prerequisites on Rake rules now allowed. However, keep the + following in mind: + + 1. All the prerequisites of a rule must be available before a rule + is triggered, where "enabled" means (a) an existing file, (b) a + defined rule, or (c) another rule which also must be + trigger-able. + 2. Rules are checked in order of definition, so it is important to + order your rules properly. If a file can be created by two + different rules, put the more specific rule first (otherwise the + more general rule will trigger first and the specific one will + never be triggered). + 3. The source method now returns the name of the first + prerequisite listed in the rule. sources returns the + names of all the rule prerequisites, ordered as they are defined + in the rule. If the task has other prerequisites not defined in + the rule (but defined in an explicit task definition), then they + will _not_ be included in the sources list. + +* FileLists may now use the egrep command. This popular enhancement + is now a core part of the FileList object. If you want to get a + list of all your to-dos, fixmes and TBD comments, add the following + to your Rakefile. + + desc "Look for TODO and FIXME tags in the code" + task :todo do + FileList['**/*.rb'].egrep /#.*(FIXME|TODO|TBD)/ + end + +* The investigation method was added to task object to dump + out some important values. This makes it a bit easier to debug Rake + tasks. + + For example, if you are having problems with a particular task, just + print it out: + + task :huh do + puts Rake::Task['huh'].investigation + end + +* The Rake::TestTask class now supports a "ruby\_opts" option to pass + arbitrary ruby options to a test subprocess. + +===== Some Incompatibilities + +* When using the ruby command to start a Ruby subprocess, the + Ruby interpreter that is currently running rake is used by default. + This makes it easier to use rake in an environment with multiple + ruby installation. (Previously, the first ruby command found in the + PATH was used). + + If you wish to chose a different Ruby interpreter, you can + explicitly choose the interpreter via the sh command. + +* The major rake classes (Task, FileTask, FileCreationTask, RakeApp) + have been moved out of the toplevel scope and are now accessible as + Rake::Task, Rake::FileTask, Rake::FileCreationTask and + Rake::Application. If your Rakefile + directly references any one of these tasks, you may: + + 1. Update your Rakefile to use the new classnames + 2. Use the --classic-namespace option on the rake command to get the + old behavior, + 3. Add require 'rake/classic_namespace' to the + Rakefile to get the old behavior. + + rake will print a rather annoying warning whenever a + deprecated class name is referenced without enabling classic + namespace. + +===== Bug Fixes + +* Several unit tests and functional tests were fixed to run better + under windows. + +* Directory tasks are now a specialized version of a File task. A + directory task will only be triggered if it doesn't exist. It will + not be triggered if it is out of date w.r.t. any of its + prerequisites. + +* Fixed a bug in the Rake::GemPackageTask class so that the gem now + properly contains the platform name. + +* Fixed a bug where a prerequisite on a file task would cause + an exception if the prerequisite did not exist. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. +The following people either contributed patches, made suggestions or +made otherwise helpful comments. Thanks to ... + +* Greg Fast (better ruby_opt test options) +* Kelly Felkins (requested by better namespace support) +* Martin Fowler (suggested Task.investigation) +* Stuart Jansen (send initial patch for multiple prerequisites). +* Masao Mutch (better support for non-ruby Gem platforms) +* Philipp Neubeck (patch for file task exception fix) + +-- Jim Weirich + +=== 0.5.4 + +Time for some minor bug fixes and small enhancements + +==== Changes + +Here are the changes for version 0.5.4 ... + +* Added double quotes to the test runner. This allows the location of + the tests (and runner) to be in a directory path that contains + spaces (e.g. "C:/Program Files/ruby/bin"). +* Added .svn to default ignore list. Now subversion project metadata + is automatically ignored by Rake's FileList. +* Updated FileList#include to support nested arrays and filelists. + FileLists are flat lists of file names. Using a FileList in an + include will flatten out the nested file names. + +== Thanks + +As usual, it was input from users that drove a alot of these changes. +Thanks to ... + +* Tilman Sauerbeck for the nested FileList suggestion. +* Josh Knowles for pointing out the spaces in directory name problem. + +-- Jim Weirich + +=== 0.5.3 + +Although it has only been two weeks since the last release, we have +enough updates to the Rake program to make it time for another +release. + +==== Changes + +Here are the changes for version 0.5.3 ... + +* FileLists have been extensively changed so that they mimic the + behavior of real arrays even more closely. In particular, + operations on FileLists that return a new collection (e.g. collect, + reject) will now return a FileList rather than an array. In + addition, several places where FileLists were not properly expanded + before use have been fixed. +* A method (+ext+) to simplify the handling of file extensions was + added to String and to Array. +* The 'testrb' script in test/unit tends to silently swallow syntax + errors in test suites. Because of that, the default test loader is + now a rake-provided script. You can still use 'testrb' by setting + the loader flag in the test task to :testrb. (See the API documents + for TestTask for all the loader flag values). +* FileUtil methods (e.g. cp, mv, install) are now declared to be + private. This will cut down on the interference with user defined + methods of the same name. +* Fixed the verbose flag in the TestTask so that the test code is + controlled by the flag. Also shortened up some failure messages. + (Thanks to Tobias Luetke for the suggestion). +* Rules will now properly detect a task that can generate a source + file. Previously rules would only consider source files that were + already present. +* Added an +import+ command that allows Rake to dynamically import + dependendencies into a running Rake session. The +import+ command + can run tasks to update the dependency file before loading them. + Dependency files can be in rake or make format, allowing rake to + work with tools designed to generate dependencies for make. + +==== Thanks + +As usual, it was input from users that drove a alot of these changes. +Thanks to ... + +* Brian Gernhardt for the rules fix (especially for the patience to + explain the problem to me until I got what he was talking about). +* Stefan Lang for pointing out problems in the dark corners of the + FileList implementation. +* Alexey Verkhovsky pointing out the silently swallows syntax errors + in tests. +* Tobias Luetke for beautifying the test task output. +* Sam Roberts for some of the ideas behind dependency loading. + +-- Jim Weirich + + +=== 0.5.0 + +It has been a long time in coming, but we finally have a new version +of Rake available. + +==== Changes + +* Fixed documentation that was lacking the Rake module name (Tilman + Sauerbeck). +* Added tar.gz and tar.bz2 support to package task (Tilman Sauerbeck). +* Recursive rules are now supported (Tilman Sauerbeck). +* Added warning option for the Test Task (requested by Eric Hodel). +* The jamis rdoc template is only used if it exists. +* Added fix for Ruby 1.8.2 test/unit and rails problem. +* Added contributed rake man file (Jani Monoses). +* Added Brian Candler's fix for problems in --trace and --dry-run + mode. + +==== Thanks + +Lots of people provided input to this release. Thanks to Tilman +Sauerbeck for numerous patches, documentation fixes and suggestions. +And for also pushing me to get this release out. Also, thanks to +Brian Candler for the finding and fixing --trace/dry-run fix. That +was an obscure bug. Also to Eric Hodel for some good suggestions. + +-- Jim Weirich + +=== 0.4.15 + +==== Changes + +Version 0.4.15 is a bug fix update for the Ruby 1.8.2 compatibility +changes. This release includes: + +* Fixed a bug that prevented the TESTOPTS flag from working with the + revised for 1.8.2 test task. +* Updated the docs on --trace to indicate that it also enables a full + backtrace on errors. +* Several fixes for new warnings generated. + +==== Mini-Roadmap + +I will continue to issue Rake updates in the 0.4.xx series as new +Ruby-1.8.2 issues become manifest. Once the codebase stabilizes, I +will release a 0.5.0 version incorporating all the changes. If you +are not using Ruby-1.8.2 and wish to avoid version churn, I recommend +staying with a release prior to Rake-0.4.14. + +=== 0.4.14 + +Version 0.4.14 is a compatibility fix to allow Rake's test task to +work under Ruby 1.8.2. A change in the Test::Unit autorun feature +prevented Rake from running any tests. This release fixes the +problem. + +Rake 0.4.14 is the recommended release for anyone using Ruby 1.8.2. + +=== 0.4.13 + +* Fixed the dry-run flag so it is operating again. +* Multiple arguments to sh and ruby commands will not be interpreted + by the shell (patch provided by Jonathan Paisley). + +=== 0.4.12 + +* Added --silent (-s) to suppress the (in directory) rake message. + +=== 0.4.11 + +* Changed the "don't know how to rake" message (finally) +* Changes references to a literal "Rakefile" to reference the global + variable $rakefile (which contains the actual name of the rakefile). + +=== 0.4.10 + +* Added block support to the "sh" command, allowing users to take + special actions on the result of the system call. E.g. + + sh "shell_command" do |ok, res| + puts "Program returned #{res.exitstatus}" if ! ok + end + +=== 0.4.9 + +* Switched to Jamis Buck's RDoc template. +* Removed autorequire from Rake's gem spec. This prevents the Rake + libraries from loading while using rails. + +=== 0.4.8 + +* Added support for .rb versions of Rakefile. +* Removed \\\n's from test task. +* Fixed Ruby 1.9 compatibility issue with FileList. + +=== 0.4.7 + +* Fixed problem in FileList that caused Ruby 1.9 to go into infinite + recursion. Since to_a was removed from Object, it does not need to + added back into the list of methods to rewrite in FileList. (Thanks + to Kent Sibilev for pointing this out). + +=== 0.4.6 +* Removed test version of ln in FileUtils that prevented safe_ln from + using ln. + +=== 0.4.5 +* Upgraded comments in TestTask. +* FileList to_s and inspect now automatically resolve pending changes. +* FileList#exclude properly returns the FileList. + +=== 0.4.4 +* Fixed initialization problem with @comment. +* Now using multi -r technique in TestTask. Switch Rakefile back to + using the built-in test task macros because the rake runtime is no + longer needed. +* Added 'TEST=filename' and 'TESTOPTS=options' to the Test Task + macros. +* Allow a +test_files+ attribute in test tasks. This allows more + flexibility in specifying test files. + +=== 0.4.3 +* Fixed Comment leakage. + +=== 0.4.2 +* Added safe_ln that falls back to a copy if a file link is not supported. +* Package builder now uses safe\_ln. + +=== 0.4.1 +* Task comments are now additive, combined with "/". +* Works with (soon to be released) rubygems 0.6.2 (or 0.7.0) + +=== 0.4.0 +* FileList now uses deferred loading. The file system is not searched + until the first call that needs the file names. +* VAR=VALUE options are now accepted on the command line and are + treated like environment variables. The values may be tested in a + Rakefile by referencing ENV['VAR']. +* File.mtime is now used (instead of File.new().mtime). + +=== 0.3.2.x + +* Removed some hidden dependencies on rubygems. Tests now will test + gems only if they are installed. +* Removed Sys from some example files. I believe that is that last + reference to Sys outside of the contrib area. +* Updated all copyright notices to include 2004. + +=== 0.3.2 + +* GEM Installation now works with the application stub. + +=== 0.3.1 + +* FileLists now automatically ignore CVS, .bak, ! +* GEM Installation now works. + +=== 0.3.0 + +Promoted 0.2.10. + +=== 0.2.10 +General + +* Added title to Rake's rdocs +* Contrib packages are no longer included in the documentation. + +RDoc Issues + +* Removed default for the '--main' option +* Fixed rendering of the rdoc options +* Fixed clean/clobber confusion with rerdoc +* 'title' attribute added + +Package Task Library Issues + +* Version (or explicit :noversion) is required. +* +package_file+ attribute is now writable + +FileList Issues + +* Dropped bang version of exclude. Now using ant-like include/exclude semantics. +* Enabled the "yield self" idiom in FileList#initialize. + +=== 0.2.9 + +This version contains numerous changes as the RubyConf.new(2003) +presentation was being prepared. The changes include: + +* The monolithic rubyapp task library is in the process of being + dropped in favor of lighter weight task libraries. + +=== 0.2.7 + +* Added "desc" for task descriptions. +* -T will now display tasks with descriptions. +* -P will display tasks and prerequisites. +* Dropped the Sys module in favor of the 1.8.x FileUtils module. Sys + is still supported in the contrib area. + +=== 0.2.6 + +* Moved to RubyForge + +=== 0.2.5 + +* Switched to standard ruby app builder. +* Added no_match option to file matcher. + +=== 0.2.4 + +* Fixed indir, which neglected to actually change directories. + +=== 0.2.3 + +* Added rake module for a help target +* Added 'for\_files' to Sys +* Added a $rakefile constant +* Added test for selecting proper rule with multiple targets. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/MIT-LICENSE b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/MIT-LICENSE new file mode 100644 index 0000000000..4292f3b3c7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) Jim Weirich + +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.0.0/gems/rake-13.0.6/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/README.rdoc new file mode 100644 index 0000000000..0bcaef0006 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/README.rdoc @@ -0,0 +1,155 @@ += RAKE -- Ruby Make + +home :: https://github.com/ruby/rake +bugs :: https://github.com/ruby/rake/issues +docs :: https://ruby.github.io/rake + +== Description + +Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. + +Rake has the following features: + +* Rakefiles (rake's version of Makefiles) are completely defined in + standard Ruby syntax. No XML files to edit. No quirky Makefile + syntax to worry about (is that a tab or a space?) + +* Users can specify tasks with prerequisites. + +* Rake supports rule patterns to synthesize implicit tasks. + +* Flexible FileLists that act like arrays but know about manipulating + file names and paths. + +* A library of prepackaged tasks to make building rakefiles easier. For example, + tasks for building tarballs. (Formerly + tasks for building RDoc, Gems, and publishing to FTP were included in rake but they're now + available in RDoc, RubyGems, and rake-contrib respectively.) + +* Supports parallel execution of tasks. + +== Installation + +=== Gem Installation + +Download and install rake with the following. + + gem install rake + +== Usage + +=== Simple Example + +First, you must write a "Rakefile" file which contains the build rules. Here's +a simple example: + + task default: %w[test] + + task :test do + ruby "test/unittest.rb" + end + +This Rakefile has two tasks: + +* A task named "test", which -- upon invocation -- will run a unit test file + in Ruby. +* A task named "default". This task does nothing by itself, but it has exactly + one dependency, namely the "test" task. Invoking the "default" task will + cause Rake to invoke the "test" task as well. + +Running the "rake" command without any options will cause it to run the +"default" task in the Rakefile: + + % ls + Rakefile test/ + % rake + (in /home/some_user/Projects/rake) + ruby test/unittest.rb + ....unit test output here... + +Type "rake --help" for all available options. + +== Resources + +=== Rake Information + +* {Rake command-line}[link:doc/command_line_usage.rdoc] +* {Writing Rakefiles}[link:doc/rakefile.rdoc] +* The original {Rake announcement}[link:doc/rational.rdoc] +* Rake {glossary}[link:doc/glossary.rdoc] + +=== Presentations and Articles about Rake + +* Avdi Grimm's rake series: + 1. {Rake Basics}[https://avdi.codes/rake-part-1-basics/] + 2. {Rake File Lists}[https://avdi.codes/rake-part-2-file-lists-2/] + 3. {Rake Rules}[https://avdi.codes/rake-part-3-rules/] + 4. {Rake Pathmap}[https://avdi.codes/rake-part-4-pathmap/] + 5. {File Operations}[https://avdi.codes/rake-part-5-file-operations/] + 6. {Clean and Clobber}[https://avdi.codes/rake-part-6-clean-and-clobber/] + 7. {MultiTask}[https://avdi.codes/rake-part-7-multitask/] +* {Jim Weirich's 2003 RubyConf presentation}[https://web.archive.org/web/20140221123354/http://onestepback.org/articles/buildingwithrake/] +* Martin Fowler's article on Rake: https://martinfowler.com/articles/rake.html + +== Other Make Re-envisionings ... + +Rake is a late entry in the make replacement field. Here are links to +other projects with similar (and not so similar) goals. + +* https://directory.fsf.org/wiki/Bras -- Bras, one of earliest + implementations of "make in a scripting language". +* http://www.a-a-p.org -- Make in Python +* https://ant.apache.org -- The Ant project +* https://search.cpan.org/search?query=PerlBuildSystem -- The Perl Build System +* https://www.rubydoc.info/gems/rant/0.5.7/frames -- Rant, another Ruby make tool. + +== Credits + +[Jim Weirich] Who originally created Rake. + +[Ryan Dlugosz] For the initial conversation that sparked Rake. + +[Nobuyoshi Nakada ] For the initial patch for rule support. + +[Tilman Sauerbeck ] For the recursive rule patch. + +[Eric Hodel] For aid in maintaining rake. + +[Hiroshi SHIBATA] Maintainer of Rake 10.X and Rake 11.X + +== License + +Rake is available under an MIT-style license. + +:include: MIT-LICENSE + +--- + += Other stuff + +Author:: Jim Weirich +Requires:: Ruby 2.0.0 or later +License:: Copyright Jim Weirich. + Released under an MIT-style license. See the MIT-LICENSE + file included in the distribution. + +== Warranty + +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. + +== Historical + +Rake was originally created by Jim Weirich, who unfortunately passed away in +February 2014. This repository was originally hosted at +{github.com/jimweirich/rake}[https://github.com/jimweirich/rake/], however +with his passing, has been moved to {ruby/rake}[https://github.com/ruby/rake]. + +You can view Jim's last commit here: +https://github.com/jimweirich/rake/tree/336559f28f55bce418e2ebcc0a57548dcbac4025 + +You can {read more about Jim}[https://en.wikipedia.org/wiki/Jim_Weirich] at Wikipedia. + +Thank you for this great tool, Jim. We'll remember you. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/command_line_usage.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/command_line_usage.rdoc new file mode 100644 index 0000000000..105d6c8e99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/command_line_usage.rdoc @@ -0,0 +1,158 @@ += Rake Command Line Usage + +Rake is invoked from the command line using: + + % rake [options ...] [VAR=VALUE ...] [targets ...] + +Options are: + +[name=value] + Set the environment variable name to value + during the execution of the rake command. You can access + the value by using ENV['name']. + +[--all (-A)] + Used in combination with the -T and -D options, will force + those options to show all the tasks, even the ones without comments. + +[--backtrace{=_output_} (-n)] + Enable a full backtrace (i.e. like --trace, but without the task + tracing details). The _output_ parameter is optional, but if + specified it controls where the backtrace output is sent. If + _output_ is stdout, then backtrace output is directed to + standard output. If _output_ is stderr, or if it is + missing, then the backtrace output is sent to standard error. + +[--comments] + Used in combination with the -W options to force the output to + contain commented options only. This is the reverse of + --all. + +[--describe _pattern_ (-D)] + Describe the tasks (matching optional PATTERN), then exit. + +[--dry-run (-n)] + Do a dry run. Print the tasks invoked and executed, but do not + actually execute any of the actions. + +[--execute _code_ (-e)] + Execute some Ruby code and exit. + +[--execute-print _code_ (-p)] + Execute some Ruby code, print the result, and exit. + +[--execute-continue _code_ (-E)] + Execute some Ruby code, then continue with normal task processing. + +[--help (-H)] + Display some help text and exit. + +[--jobs _number_ (-j)] + + Specifies the maximum number of concurrent threads allowed. Rake + will allocate threads as needed up to this maximum number. + + If omitted, Rake will attempt to estimate the number of CPUs on + the system and add 4 to that number. + + The concurrent threads are used to execute the multitask + prerequisites. Also see the -m option which turns all + tasks into multitasks. + + Sample values: + (no -j) : Allow up to (# of CPUs + 4) number of threads + --jobs : Allow unlimited number of threads + --jobs=1 : Allow only one thread (the main thread) + --jobs=16 : Allow up to 16 concurrent threads + +[--job-stats _level_] + + Display job statistics at the completion of the run. By default, + this will display the requested number of active threads (from the + -j options) and the maximum number of threads in play at any given + time. + + If the optional _level_ is history, then a complete trace + of task history will be displayed on standard output. + +[--libdir _directory_ (-I)] + Add _directory_ to the list of directories searched for require. + +[--multitask (-m)] + Treat all tasks as multitasks. ('make/drake' semantics) + +[--nosearch (-N)] + Do not search for a Rakefile in parent directories. + +[--prereqs (-P)] + Display a list of all tasks and their immediate prerequisites. + +[--quiet (-q)] + Do not echo commands from FileUtils. + +[--rakefile _filename_ (-f)] + Use _filename_ as the name of the rakefile. The default rakefile + names are +rakefile+ and +Rakefile+ (with +rakefile+ taking + precedence). If the rakefile is not found in the current + directory, +rake+ will search parent directories for a match. The + directory where the Rakefile is found will become the current + directory for the actions executed in the Rakefile. + +[--rakelibdir _rakelibdir_ (-R)] + Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib') + +[--require _name_ (-r)] + Require _name_ before executing the Rakefile. + +[--rules] + Trace the rules resolution. + +[--silent (-s)] + Like --quiet, but also suppresses the 'in directory' announcement. + +[--suppress-backtrace _pattern_ ] + Line matching the regular expression _pattern_ will be removed + from the backtrace output. Note that the --backtrace option is the + full backtrace without these lines suppressed. + +[--system (-g)] + Use the system wide (global) rakefiles. The project Rakefile is + ignored. By default, the system wide rakefiles are used only if no + project Rakefile is found. On Unix-like system, the system wide + rake files are located in $HOME/.rake. On a windows system they + are stored in $APPDATA/Rake. + +[--no-system (-G)] + Use the project level Rakefile, ignoring the system-wide (global) + rakefiles. + +[--tasks pattern (-T)] + Display a list of the major tasks and their comments. Comments + are defined using the "desc" command. If a pattern is given, then + only tasks matching the pattern are displayed. + +[--trace{=_output_} (-t)] + Turn on invoke/execute tracing. Also enable full backtrace on + errors. The _output_ parameter is optional, but if specified it + controls where the trace output is sent. If _output_ is + stdout, then trace output is directed to standard output. + If _output_ is stderr, or if it is missing, then trace + output is sent to standard error. + +[--verbose (-v)] + Echo the Sys commands to standard output. + +[--version (-V)] + Display the program version and exit. + +[--where pattern (-W)] + Display tasks that match pattern and the file and line + number where the task is defined. By default this option will + display all tasks, not just the tasks that have descriptions. + +[--no-deprecation-warnings (-X)] + Do not display the deprecation warnings. + +In addition, any command line option of the form +VAR=VALUE will be added to the environment hash +ENV and may be tested in the Rakefile. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile1 b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile1 new file mode 100644 index 0000000000..39f8bcceb0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile1 @@ -0,0 +1,38 @@ +# Example Rakefile -*- ruby -*- + +task :default => [:main] + +file "a.o" => ["a.c"] do |t| + src = t.name.sub(/\.o$/, '.c') + sh "gcc #{src} -c -o #{t.name}" +end + +file "b.o" => ["b.c"] do |t| + src = t.name.sub(/\.o$/, '.c') + sh "gcc #{src} -c -o #{t.name}" +end + +file "main.o" => ["main.c"] do |t| + src = t.name.sub(/\.o$/, '.c') + sh "gcc #{src} -c -o #{t.name}" +end + +OBJFILES = ["a.o", "b.o", "main.o"] +task :obj => OBJFILES + +file "main" => OBJFILES do |t| + sh "gcc -o #{t.name} main.o a.o b.o" +end + +task :clean do + rm_f FileList['*.o'] + Dir['*~'].each { |fn| rm_f fn } +end + +task :clobber => [:clean] do + rm_f "main" +end + +task :run => ["main"] do + sh "./main" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile2 b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile2 new file mode 100644 index 0000000000..35310eceb5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/Rakefile2 @@ -0,0 +1,35 @@ +# Example Rakefile -*- ruby -*- +# Using the power of Ruby + +task :default => [:main] + +def ext(fn, newext) + fn.sub(/\.[^.]+$/, newext) +end + +SRCFILES = Dir['*.c'] +OBJFILES = SRCFILES.collect { |fn| ext(fn,".o") } + +OBJFILES.each do |objfile| + srcfile = ext(objfile, ".c") + file objfile => [srcfile] do |t| + sh "gcc #{srcfile} -c -o #{t.name}" + end +end + +file "main" => OBJFILES do |t| + sh "gcc -o #{t.name} main.o a.o b.o" +end + +task :clean do + rm_f FileList['*.o'] + Dir['*~'].each { |fn| rm_f fn } +end + +task :clobber => [:clean] do + rm_f "main" +end + +task :run => ["main"] do + sh "./main" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/a.c b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/a.c new file mode 100644 index 0000000000..620e6f8007 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/a.c @@ -0,0 +1,6 @@ +#include + +void a() +{ + printf ("In function a\n"); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/b.c b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/b.c new file mode 100644 index 0000000000..9b24aa1273 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/b.c @@ -0,0 +1,6 @@ +#include + +void b() +{ + printf ("In function b\n"); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/main.c b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/main.c new file mode 100644 index 0000000000..a04558a251 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/example/main.c @@ -0,0 +1,11 @@ +#include + +extern void a(); +extern void b(); + +int main () +{ + a(); + b(); + return 0; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/glossary.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/glossary.rdoc new file mode 100644 index 0000000000..9d592b02ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/glossary.rdoc @@ -0,0 +1,42 @@ += Glossary + +action :: + Code to be executed in order to perform a task. Actions in a Rakefile are + specified in a code block. (Usually delimited by +do+/+end+ pairs.) + +execute :: + When a task is executed, all of its actions are performed in the order they + were defined. Note that, unlike invoke, execute always + executes the actions (without invoking or executing the prerequisites). + +file task (Rake::FileTask) :: + A file task is a task whose purpose is to create a file (which has the same + name as the task). When invoked, a file task will only execute if one or + more of the following conditions are true. + + 1. The associated file does not exist. + 2. A prerequisite has a later time stamp than the existing file. + + Because normal Tasks always have the current time as timestamp, a FileTask + that has a normal Task prerequisite will always execute. + +invoke :: + When a task is invoked, first we check to see if it has been invoked before. + If it has been, then nothing else is done. If this is the first time it has + been invoked, then we invoke each of its prerequisites. Finally, we check + to see if we need to execute the actions of this task by calling + Rake::Task#needed?. If the task is needed, we execute its actions. + + NOTE: Prerequisites are still invoked even if the task is not needed. + +prerequisites :: + Every task has a (possibly empty) set of prerequisites. A prerequisite P to + Task T is itself a task that must be invoked before Task T. + +rule :: + A rule is a recipe for synthesizing a task when no task is explicitly + defined. Rules generally synthesize file tasks. + +task (Rake::Task) :: + The basic unit of work in a Rakefile. A task has a name, a set of + prerequisites, and a list of actions to be performed. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/jamis.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/jamis.rb new file mode 100644 index 0000000000..531aa75739 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/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.0.0/gems/rake-13.0.6/doc/proto_rake.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/proto_rake.rdoc new file mode 100644 index 0000000000..a9e33d11da --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/proto_rake.rdoc @@ -0,0 +1,127 @@ += Original Prototype Rake + +This is the original 100 line prototype rake program. + +--- + #!/usr/bin/env ruby + + require 'ftools' + + class Task + TASKS = Hash.new + + attr_reader :prerequisites + + def initialize(task_name) + @name = task_name + @prerequisites = [] + @actions = [] + end + + def enhance(deps=nil, &block) + @prerequisites |= deps if deps + @actions << block if block_given? + self + end + + def name + @name.to_s + end + + def invoke + @prerequisites.each { |n| Task[n].invoke } + execute if needed? + end + + def execute + return if @triggered + @triggered = true + @actions.collect { |act| result = act.call(self) }.last + end + + def needed? + true + end + + def timestamp + Time.now + end + + class << self + def [](task_name) + TASKS[intern(task_name)] or fail "Don't know how to rake #{task_name}" + end + + def define_task(args, &block) + case args + when Hash + fail "Too Many Target Names: #{args.keys.join(' ')}" if args.size > 1 + fail "No Task Name Given" if args.size < 1 + task_name = args.keys[0] + deps = args[task_name] + else + task_name = args + deps = [] + end + deps = deps.collect {|d| intern(d) } + get(task_name).enhance(deps, &block) + end + + def get(task_name) + name = intern(task_name) + TASKS[name] ||= self.new(name) + end + + def intern(task_name) + (Symbol === task_name) ? task_name : task_name.intern + end + end + end + + class FileTask < Task + def needed? + return true unless File.exist?(name) + latest_prereq = @prerequisites.collect{|n| Task[n].timestamp}.max + return false if latest_prereq.nil? + timestamp < latest_prereq + end + + def timestamp + File.new(name.to_s).mtime + end + end + + def task(args, &block) + Task.define_task(args, &block) + end + + def file(args, &block) + FileTask.define_task(args, &block) + end + + def sys(cmd) + puts cmd + system(cmd) or fail "Command Failed: [#{cmd}]" + end + + def rake + begin + here = Dir.pwd + while ! File.exist?("Rakefile") + Dir.chdir("..") + fail "No Rakefile found" if Dir.pwd == here + here = Dir.pwd + end + puts "(in #{Dir.pwd})" + load "./Rakefile" + ARGV.push("default") if ARGV.size == 0 + ARGV.each { |task_name| Task[task_name].invoke } + rescue Exception => ex + puts "rake aborted ... #{ex.message}" + puts ex.backtrace.find {|str| str =~ /Rakefile/ } || "" + end + end + + if __FILE__ == $0 then + rake + end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rake.1 b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rake.1 new file mode 100644 index 0000000000..c6bfa25c09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rake.1 @@ -0,0 +1,156 @@ +.Dd June 12, 2016 +.Dt RAKE 1 +.Os rake 11.2.2 +.Sh NAME +.Nm rake +.Nd make-like build utility for Ruby +.Sh SYNOPSIS +.Nm +.Op Fl f Ar rakefile +.Op Ar options +.Ar targets ... +.Sh DESCRIPTION +.Nm +is a +.Xr make 1 Ns -like +build utility for Ruby. +Tasks and dependencies are specified in standard Ruby syntax. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m , Fl -multitask +Treat all tasks as multitasks. +.It Fl B , Fl -build-all +Build all prerequisites, including those which are up\-to\-date. +.It Fl j , Fl -jobs Ar num_jobs +Specifies the maximum number of tasks to execute in parallel (default is number of CPU cores + 4). +.El +.Ss Modules +.Bl -tag -width Ds +.It Fl I , Fl -libdir Ar libdir +Include +.Ar libdir +in the search path for required modules. +.It Fl r , Fl -require Ar module +Require +.Ar module +before executing +.Pa rakefile . +.El +.Ss Rakefile location +.Bl -tag -width Ds +.It Fl f , Fl -rakefile Ar filename +Use +.Ar filename +as the rakefile to search for. +.It Fl N , Fl -no-search , Fl -nosearch +Do not search parent directories for the Rakefile. +.It Fl G , Fl -no-system , Fl -nosystem +Use standard project Rakefile search paths, ignore system wide rakefiles. +.It Fl R , Fl -rakelib Ar rakelibdir , Fl -rakelibdir Ar rakelibdir +Auto-import any .rake files in +.Ar rakelibdir +(default is +.Sq rakelib ) +.It Fl g , Fl -system +Use system-wide (global) rakefiles (usually +.Pa ~/.rake/*.rake ) . +.El +.Ss Debugging +.Bl -tag -width Ds +.It Fl -backtrace Ns = Ns Ar out +Enable full backtrace. +.Ar out +can be +.Dv stderr +(default) or +.Dv stdout . +.It Fl t , Fl -trace Ns = Ns Ar out +Turn on invoke/execute tracing, enable full backtrace. +.Ar out +can be +.Dv stderr +(default) or +.Dv stdout . +.It Fl -suppress-backtrace Ar pattern +Suppress backtrace lines matching regexp +.Ar pattern . +Ignored if +.Fl -trace +is on. +.It Fl -rules +Trace the rules resolution. +.It Fl n , Fl -dry-run +Do a dry run without executing actions. +.It Fl T , Fl -tasks Op Ar pattern +Display the tasks (matching optional +.Ar pattern ) +with descriptions, then exit. +.It Fl D , Fl -describe Op Ar pattern +Describe the tasks (matching optional +.Ar pattern ) , +then exit. +.It Fl W , Fl -where Op Ar pattern +Describe the tasks (matching optional +.Ar pattern ) , +then exit. +.It Fl P , Fl -prereqs +Display the tasks and dependencies, then exit. +.It Fl e , Fl -execute Ar code +Execute some Ruby code and exit. +.It Fl p , Fl -execute-print Ar code +Execute some Ruby code, print the result, then exit. +.It Fl E , Fl -execute-continue Ar code +Execute some Ruby code, then continue with normal task processing. +.El +.Ss Information +.Bl -tag -width Ds +.It Fl v , Fl -verbose +Log message to standard output. +.It Fl q , Fl -quiet +Do not log messages to standard output. +.It Fl s , Fl -silent +Like +.Fl -quiet , +but also suppresses the +.Sq in directory +announcement. +.It Fl X , Fl -no-deprecation-warnings +Disable the deprecation warnings. +.It Fl -comments +Show commented tasks only +.It Fl A , Fl -all +Show all tasks, even uncommented ones (in combination with +.Fl T +or +.Fl D ) +.It Fl -job-stats Op Ar level +Display job statistics. +If +.Ar level +is +.Sq history , +displays a complete job list. +.It Fl V , Fl -version +Display the program version. +.It Fl h , Fl H , Fl -help +Display a help message. +.El +.Sh SEE ALSO +The complete documentation for +.Nm rake +has been installed at +.Pa /usr/share/doc/rake-doc/html/index.html . +It is also available online at +.Lk https://ruby.github.io/rake . +.Sh AUTHORS +.An -nosplit +.Nm +was written by +.An Jim Weirich Aq Mt jim@weirichhouse.org . +.Pp +This manual was created by +.An Caitlin Matos Aq Mt caitlin.matos@zoho.com +for the Debian project (but may be used by others). +It was inspired by the manual by +.An Jani Monoses Aq Mt jani@iv.ro +for the Ubuntu project. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rakefile.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rakefile.rdoc new file mode 100644 index 0000000000..4014306a11 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rakefile.rdoc @@ -0,0 +1,622 @@ += Rakefile Format + +First of all, there is no special format for a Rakefile. A Rakefile +contains executable Ruby code. Anything legal in a ruby script is +allowed in a Rakefile. + +Now that we understand there is no special syntax in a Rakefile, there +are some conventions that are used in a Rakefile that are a little +unusual in a typical Ruby program. Since a Rakefile is tailored to +specifying tasks and actions, the idioms used in a Rakefile are +designed to support that. + +So, what goes into a Rakefile? + +== Tasks + +Tasks are the main unit of work in a Rakefile. Tasks have a name +(usually given as a symbol or a string), a list of prerequisites (more +symbols or strings) and a list of actions (given as a block). + +=== Simple Tasks + +A task is declared by using the +task+ method. +task+ takes a single +parameter that is the name of the task. + + task :name + +=== Tasks with Prerequisites + +Any prerequisites are given as a list (enclosed in square brackets) +following the name and an arrow (=>). + + task name: [:prereq1, :prereq2] + +*NOTE:* Although this syntax looks a little funky, it is legal +Ruby. We are constructing a hash where the key is :name and the value +for that key is the list of prerequisites. It is equivalent to the +following ... + + hash = Hash.new + hash[:name] = [:prereq1, :prereq2] + task(hash) + +You can also use strings for task names and prerequisites, rake doesn't care. +This is the same task definition: + + task 'name' => %w[prereq1 prereq2] + +As is this: + + task name: %w[prereq1 prereq2] + +We'll prefer this style for regular tasks with prerequisites throughout the +rest of the document. Using an array of strings for the prerequisites means +you will need to make fewer changes if you need to move tasks into namespaces +or perform other refactorings. + +=== Tasks with Actions + +Actions are defined by passing a block to the +task+ method. Any Ruby +code can be placed in the block. The block may reference the task +object via the block parameter. + + task name: [:prereq1, :prereq2] do |t| + # actions (may reference t) + end + +=== Multiple Definitions + +A task may be specified more than once. Each specification adds its +prerequisites and actions to the existing definition. This allows one +part of a rakefile to specify the actions and a different rakefile +(perhaps separately generated) to specify the dependencies. + +For example, the following is equivalent to the single task +specification given above. + + task :name + task name: :prereq1 + task name: %w[prereq2] + task :name do |t| + # actions + end + +== File Tasks + +Some tasks are designed to create a file from one or more other files. +Tasks that generate these files may be skipped if the file already +exists. File tasks are used to specify file creation tasks. + +File tasks are declared using the +file+ method (instead of the +task+ +method). In addition, file tasks are usually named with a string +rather than a symbol. + +The following file task creates a executable program (named +prog+) +given two object files named +a.o+ and +b.o+. The tasks +for creating +a.o+ and +b.o+ are not shown. + + file "prog" => ["a.o", "b.o"] do |t| + sh "cc -o #{t.name} #{t.prerequisites.join(' ')}" + end + +== Directory Tasks + +It is common to need to create directories upon demand. The ++directory+ convenience method is a short-hand for creating a FileTask +that creates the directory. For example, the following declaration +... + + directory "testdata/examples/doc" + +is equivalent to ... + + file "testdata" do |t| mkdir t.name end + file "testdata/examples" => ["testdata"] do |t| mkdir t.name end + file "testdata/examples/doc" => ["testdata/examples"] do |t| mkdir t.name end + +The +directory+ method does not accept prerequisites or actions, but +both prerequisites and actions can be added later. For example ... + + directory "testdata" + file "testdata" => ["otherdata"] + file "testdata" do + cp Dir["standard_data/*.data"], "testdata" + end + +== Tasks with Parallel Prerequisites + +Rake allows parallel execution of prerequisites using the following syntax: + + multitask copy_files: %w[copy_src copy_doc copy_bin] do + puts "All Copies Complete" + end + +In this example, +copy_files+ is a normal rake task. Its actions are +executed whenever all of its prerequisites are done. The big +difference is that the prerequisites (+copy_src+, +copy_bin+ and ++copy_doc+) are executed in parallel. Each of the prerequisites are +run in their own Ruby thread, possibly allowing faster overall runtime. + +=== Secondary Prerequisites + +If any of the primary prerequisites of a multitask have common secondary +prerequisites, all of the primary/parallel prerequisites will wait +until the common prerequisites have been run. + +For example, if the copy_xxx tasks have the +following prerequisites: + + task copy_src: :prep_for_copy + task copy_bin: :prep_for_copy + task copy_doc: :prep_for_copy + +Then the +prep_for_copy+ task is run before starting all the copies in +parallel. Once +prep_for_copy+ is complete, +copy_src+, +copy_bin+, +and +copy_doc+ are all run in parallel. Note that +prep_for_copy+ is +run only once, even though it is referenced in multiple threads. + +=== Thread Safety + +The Rake internal data structures are thread-safe with respect +to the multitask parallel execution, so there is no need for the user +to do extra synchronization for Rake's benefit. However, if there are +user data structures shared between the parallel prerequisites, the +user must do whatever is necessary to prevent race conditions. + +== Tasks with Arguments + +Prior to version 0.8.0, rake was only able to handle command line +arguments of the form NAME=VALUE that were passed into Rake via the +ENV hash. Many folks had asked for some kind of simple command line +arguments, perhaps using "--" to separate regular task names from +argument values on the command line. The problem is that there was no +easy way to associate positional arguments on the command line with +different tasks. Suppose both tasks :a and :b expect a command line +argument: does the first value go with :a? What if :b is run first? +Should it then get the first command line argument. + +Rake 0.8.0 solves this problem by explicitly passing values directly +to the tasks that need them. For example, if I had a release task +that required a version number, I could say: + + rake release[0.8.2] + +And the string "0.8.2" will be passed to the :release task. Multiple +arguments can be passed by separating them with a comma, for example: + + rake name[john,doe] + +Just a few words of caution. The rake task name and its arguments +need to be a single command line argument to rake. This generally +means no spaces. If spaces are needed, then the entire name + +argument string should be quoted. Something like this: + + rake "name[billy bob, smith]" + +(Quoting rules vary between operating systems and shells, so make sure +you consult the proper docs for your OS/shell). + +=== Tasks that Expect Parameters + +Parameters are only given to tasks that are setup to expect them. In +order to handle named parameters, the task declaration syntax for +tasks has been extended slightly. + +For example, a task that needs a first name and last name might be +declared as: + + task :name, [:first_name, :last_name] + +The first argument is still the name of the task (:name in this case). +The next two arguments are the names of the parameters expected by +:name in an array (:first_name and :last_name in the example). + +To access the values of the parameters, the block defining the task +behaviour can now accept a second parameter: + + task :name, [:first_name, :last_name] do |t, args| + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +The first argument of the block "t" is always bound to the current +task object. The second argument "args" is an open-struct like object +that allows access to the task arguments. Extra command line +arguments to a task are ignored. + +If you wish to specify default values for the arguments, you can use +the with_defaults method in the task body. Here is the above example +where we specify default values for the first and last names: + + task :name, [:first_name, :last_name] do |t, args| + args.with_defaults(:first_name => "John", :last_name => "Dough") + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +=== Tasks that Expect Parameters and Have Prerequisites + +Tasks that use parameters have a slightly different format for +prerequisites. Use the arrow notation to indicate the prerequisites +for tasks with arguments. For example: + + task :name, [:first_name, :last_name] => [:pre_name] do |t, args| + args.with_defaults(:first_name => "John", :last_name => "Dough") + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +=== Tasks that take Variable-length Parameters + +Tasks that need to handle a list of values as a parameter can use the +extras method of the args variable. This allows for tasks that can +loop over a variable number of values, and its compatible with named +parameters as well: + + task :email, [:message] do |t, args| + mail = Mail.new(args.message) + recipients = args.extras + recipients.each do |target| + mail.send_to(target) + end + end + +There is also the convenience method to_a that returns all parameters +in the sequential order they were given, including those associated +with named parameters. + +=== Deprecated Task Parameters Format + +There is an older format for declaring task parameters that omitted +the task argument array and used the :needs keyword to introduce the +dependencies. That format is still supported for compatibility, but +is not recommended for use. The older format may be dropped in future +versions of rake. + +== Accessing Task Programmatically + +Sometimes it is useful to manipulate tasks programmatically in a +Rakefile. To find a task object use Rake::Task.[]. + +=== Programmatic Task Example + +For example, the following Rakefile defines two tasks. The :doit task +simply prints a simple "DONE" message. The :dont class will lookup +the doit class and remove (clear) all of its prerequisites and +actions. + + task :doit do + puts "DONE" + end + + task :dont do + Rake::Task[:doit].clear + end + +Running this example: + + $ rake doit + (in /Users/jim/working/git/rake/x) + DONE + $ rake dont doit + (in /Users/jim/working/git/rake/x) + $ + +The ability to programmatically manipulate tasks gives rake very +powerful meta-programming capabilities w.r.t. task execution, but +should be used with caution. + +== Rules + +When a file is named as a prerequisite, but does not have a file task +defined for it, Rake will attempt to synthesize a task by looking at a +list of rules supplied in the Rakefile. + +Suppose we were trying to invoke task "mycode.o", but no task is +defined for it. But the rakefile has a rule that look like this ... + + rule '.o' => ['.c'] do |t| + sh "cc #{t.source} -c -o #{t.name}" + end + +This rule will synthesize any task that ends in ".o". It has a +prerequisite a source file with an extension of ".c" must exist. If +Rake is able to find a file named "mycode.c", it will automatically +create a task that builds "mycode.o" from "mycode.c". + +If the file "mycode.c" does not exist, rake will attempt +to recursively synthesize a rule for it. + +When a task is synthesized from a rule, the +source+ attribute of the +task is set to the matching source file. This allows us to write +rules with actions that reference the source file. + +=== Advanced Rules + +Any regular expression may be used as the rule pattern. Additionally, +a proc may be used to calculate the name of the source file. This +allows for complex patterns and sources. + +The following rule is equivalent to the example above. + + rule( /\.o$/ => [ + proc {|task_name| task_name.sub(/\.[^.]+$/, '.c') } + ]) do |t| + sh "cc #{t.source} -c -o #{t.name}" + end + +*NOTE:* Because of a _quirk_ in Ruby syntax, parenthesis are +required on *rule* when the first argument is a regular expression. + +The following rule might be used for Java files ... + + rule '.class' => [ + proc { |tn| tn.sub(/\.class$/, '.java').sub(/^classes\//, 'src/') } + ] do |t| + java_compile(t.source, t.name) + end + +*NOTE:* +java_compile+ is a hypothetical method that invokes the +java compiler. + +== Importing Dependencies + +Any ruby file (including other rakefiles) can be included with a +standard Ruby +require+ command. The rules and declarations in the +required file are just added to the definitions already accumulated. + +Because the files are loaded _before_ the rake targets are evaluated, +the loaded files must be "ready to go" when the rake command is +invoked. This makes generated dependency files difficult to use. By +the time rake gets around to updating the dependencies file, it is too +late to load it. + +The +import+ command addresses this by specifying a file to be loaded +_after_ the main rakefile is loaded, but _before_ any targets on the +command line are invoked. In addition, if the file name matches an +explicit task, that task is invoked before loading the file. This +allows dependency files to be generated and used in a single rake +command invocation. + +Example: + + require 'rake/loaders/makefile' + + file ".depends.mf" => [SRC_LIST] do |t| + sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}" + end + + import ".depends.mf" + +If ".depends" does not exist, or is out of date w.r.t. the source +files, a new ".depends" file is generated using +makedepend+ before +loading. + +== Comments + +Standard Ruby comments (beginning with "#") can be used anywhere it is +legal in Ruby source code, including comments for tasks and rules. +However, if you wish a task to be described using the "-T" switch, +then you need to use the +desc+ command to describe the task. + +Example: + + desc "Create a distribution package" + task package: %w[ ... ] do ... end + +The "-T" switch (or "--tasks" if you like to spell things out) will +display a list of tasks that have a description. If you use +desc+ to +describe your major tasks, you have a semi-automatic way of generating +a summary of your Rake file. + + $ rake -T + (in /home/.../rake) + rake clean # Remove any temporary products. + rake clobber # Remove any generated file. + rake clobber_rdoc # Remove rdoc products + rake contrib_test # Run tests for contrib_test + rake default # Default Task + rake install # Install the application + rake lines # Count lines in the main rake file + rake rdoc # Build the rdoc HTML Files + rake rerdoc # Force a rebuild of the RDOC files + rake test # Run tests + rake testall # Run all test targets + +Only tasks with descriptions will be displayed with the "-T" switch. +Use "-P" (or "--prereqs") to get a list of all tasks and their +prerequisites. + +== Namespaces + +As projects grow (and along with it, the number of tasks), it is +common for task names to begin to clash. For example, if you might +have a main program and a set of sample programs built by a single +Rakefile. By placing the tasks related to the main program in one +namespace, and the tasks for building the sample programs in a +different namespace, the task names will not interfere with each other. + +For example: + + namespace "main" do + task :build do + # Build the main program + end + end + + namespace "samples" do + task :build do + # Build the sample programs + end + end + + task build: %w[main:build samples:build] + +Referencing a task in a separate namespace can be achieved by +prefixing the task name with the namespace and a colon +(e.g. "main:build" refers to the :build task in the +main+ namespace). +Nested namespaces are supported. + +Note that the name given in the +task+ command is always the unadorned +task name without any namespace prefixes. The +task+ command always +defines a task in the current namespace. + +=== FileTasks + +File task names are not scoped by the namespace command. Since the +name of a file task is the name of an actual file in the file system, +it makes little sense to include file task names in name space. +Directory tasks (created by the +directory+ command) are a type of +file task and are also not affected by namespaces. + +=== Name Resolution + +When looking up a task name, rake will start with the current +namespace and attempt to find the name there. If it fails to find a +name in the current namespace, it will search the parent namespaces +until a match is found (or an error occurs if there is no match). + +The "rake" namespace is a special implicit namespace that refers to +the toplevel names. + +If a task name begins with a "^" character, the name resolution will +start in the parent namespace. Multiple "^" characters are allowed. + +Here is an example file with multiple :run tasks and how various names +resolve in different locations. + + task :run + + namespace "one" do + task :run + + namespace "two" do + task :run + + # :run => "one:two:run" + # "two:run" => "one:two:run" + # "one:two:run" => "one:two:run" + # "one:run" => "one:run" + # "^run" => "one:run" + # "^^run" => "rake:run" (the top level task) + # "rake:run" => "rake:run" (the top level task) + end + + # :run => "one:run" + # "two:run" => "one:two:run" + # "^run" => "rake:run" + end + + # :run => "rake:run" + # "one:run" => "one:run" + # "one:two:run" => "one:two:run" + +== FileLists + +FileLists are the way Rake manages lists of files. You can treat a +FileList as an array of strings for the most part, but FileLists +support some additional operations. + +=== Creating a FileList + +Creating a file list is easy. Just give it the list of file names: + + fl = FileList['file1.rb', file2.rb'] + +Or give it a glob pattern: + + fl = FileList['*.rb'] + +== Odds and Ends + +=== do/end versus { } + +Blocks may be specified with either a +do+/+end+ pair, or with curly +braces in Ruby. We _strongly_ recommend using +do+/+end+ to specify the +actions for tasks and rules. Because the rakefile idiom tends to +leave off parentheses on the task/file/rule methods, unusual +ambiguities can arise when using curly braces. + +For example, suppose that the method +object_files+ returns a list of +object files in a project. Now we use +object_files+ as the +prerequisites in a rule specified with actions in curly braces. + + # DON'T DO THIS! + file "prog" => object_files { + # Actions are expected here (but it doesn't work)! + } + +Because curly braces have a higher precedence than +do+/+end+, the +block is associated with the +object_files+ method rather than the ++file+ method. + +This is the proper way to specify the task ... + + # THIS IS FINE + file "prog" => object_files do + # Actions go here + end + +== Rakefile Path + +When issuing the +rake+ command in a terminal, Rake will look +for a Rakefile in the current directory. If a Rakefile is not found, +it will search parent directories until one is found. + +For example, if a Rakefile resides in the +project/+ directory, +moving deeper into the project's directory tree will not have an adverse +effect on rake tasks: + + $ pwd + /home/user/project + + $ cd lib/foo/bar + $ pwd + /home/user/project/lib/foo/bar + + $ rake run_pwd + /home/user/project + +As far as rake is concerned, all tasks are run from the directory in +which the Rakefile resides. + +=== Multiple Rake Files + +Not all tasks need to be included in a single Rakefile. Additional +rake files (with the file extension "+.rake+") may be placed in ++rakelib+ directory located at the top level of a project (i.e. +the same directory that contains the main +Rakefile+). + +Also, rails projects may include additional rake files in the ++lib/tasks+ directory. + +=== Clean and Clobber Tasks + +Through require 'rake/clean' Rake provides +clean+ and +clobber+ +tasks: + ++clean+ :: + Clean up the project by deleting scratch files and backup files. Add files + to the +CLEAN+ FileList to have the +clean+ target handle them. + ++clobber+ :: + Clobber all generated and non-source files in a project. The task depends + on +clean+, so all the +CLEAN+ files will be deleted as well as files in the + +CLOBBER+ FileList. The intent of this task is to return a project to its + pristine, just unpacked state. + +You can add file names or glob patterns to both the +CLEAN+ and +CLOBBER+ +lists. + +=== Phony Task + +The phony task can be used as a dependency to allow file-based tasks to use +non-file-based-tasks as prerequisites without forcing them to rebuild. You +can require 'rake/phony' to add the +phony+ task. + +---- + +== See + +* README.rdoc -- Main documentation for Rake. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rational.rdoc b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rational.rdoc new file mode 100644 index 0000000000..0e1c33873d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/doc/rational.rdoc @@ -0,0 +1,151 @@ += Why rake? + +Ok, let me state from the beginning that I never intended to write this +code. I'm not convinced it is useful, and I'm not convinced anyone +would even be interested in it. All I can say is that Why's onion truck +must by been passing through the Ohio valley. + +What am I talking about? ... A Ruby version of Make. + +See, I can sense you cringing already, and I agree. The world certainly +doesn't need yet another reworking of the "make" program. I mean, we +already have "ant". Isn't that enough? + +It started yesterday. I was helping a coworker fix a problem in one of +the Makefiles we use in our project. Not a particularly tough problem, +but during the course of the conversation I began lamenting some of the +shortcomings of make. In particular, in one of my makefiles I wanted to +determine the name of a file dynamically and had to resort to some +simple scripting (in Ruby) to make it work. "Wouldn't it be nice if you +could just use Ruby inside a Makefile" I said. + +My coworker (a recent convert to Ruby) agreed, but wondered what it +would look like. So I sketched the following on the whiteboard... + + "What if you could specify the make tasks in Ruby, like this ..." + + task "build" do + java_compile(...args, etc ...) + end + + "The task function would register "build" as a target to be made, + and the block would be the action executed whenever the build + system determined that it was time to do the build target." + +We agreed that would be cool, but writing make from scratch would be WAY +too much work. And that was the end of that! + +... Except I couldn't get the thought out of my head. What exactly +would be needed to make the about syntax work as a make file? Hmmm, you +would need to register the tasks, you need some way of specifying +dependencies between tasks, and some way of kicking off the process. +Hey! What if we did ... and fifteen minutes later I had a working +prototype of Ruby make, complete with dependencies and actions. + +I showed the code to my coworker and we had a good laugh. It was just +about a page worth of code that reproduced an amazing amount of the +functionality of make. We were both truly stunned with the power of +Ruby. + +But it didn't do everything make did. In particular, it didn't have +timestamp based file dependencies (where a file is rebuilt if any of its +prerequisite files have a later timestamp). Obviously THAT would be a +pain to add and so Ruby Make would remain an interesting experiment. + +... Except as I walked back to my desk, I started thinking about what +file based dependencies would really need. Rats! I was hooked again, +and by adding a new class and two new methods, file/timestamp +dependencies were implemented. + +Ok, now I was really hooked. Last night (during CSI!) I massaged the +code and cleaned it up a bit. The result is a bare-bones replacement +for make in exactly 100 lines of code. + +For the curious, you can see it at ... +* doc/proto_rake.rdoc + +Oh, about the name. When I wrote the example Ruby Make task on my +whiteboard, my coworker exclaimed "Oh! I have the perfect name: Rake ... +Get it? Ruby-Make. Rake!" He said he envisioned the tasks as leaves +and Rake would clean them up ... or something like that. Anyways, the +name stuck. + +Some quick examples ... + +A simple task to delete backup files ... + + task :clean do + Dir['*~'].each {|fn| rm fn rescue nil} + end + +Note that task names are symbols (they are slightly easier to type +than quoted strings ... but you may use quoted string if you would +rather). Rake makes the methods of the FileUtils module directly +available, so we take advantage of the rm command. Also note +the use of "rescue nil" to trap and ignore errors in the rm +command. + +To run it, just type "rake clean". Rake will automatically find a +Rakefile in the current directory (or above!) and will invoke the +targets named on the command line. If there are no targets explicitly +named, rake will invoke the task "default". + +Here's another task with dependencies ... + + task :clobber => [:clean] do + rm_r "tempdir" + end + +Task :clobber depends upon task :clean, so :clean will be run before +:clobber is executed. + +Files are specified by using the "file" command. It is similar to the +task command, except that the task name represents a file, and the task +will be run only if the file doesn't exist, or if its modification time +is earlier than any of its prerequisites. + +Here is a file based dependency that will compile "hello.cc" to +"hello.o". + + file "hello.cc" + file "hello.o" => ["hello.cc"] do |t| + srcfile = t.name.sub(/\.o$/, ".cc") + sh %{g++ #{srcfile} -c -o #{t.name}} + end + +I normally specify file tasks with string (rather than symbols). Some +file names can't be represented by symbols. Plus it makes the +distinction between them more clear to the casual reader. + +Currently writing a task for each and every file in the project would be +tedious at best. I envision a set of libraries to make this job +easier. For instance, perhaps something like this ... + + require 'rake/ctools' + Dir['*.c'].each do |fn| + c_source_file(fn) + end + +where "c_source_file" will create all the tasks need to compile all the +C source files in a directory. Any number of useful libraries could be +created for rake. + +That's it. There's no documentation (other than whats in this +message). Does this sound interesting to anyone? If so, I'll continue +to clean it up and write it up and publish it on RAA. Otherwise, I'll +leave it as an interesting exercise and a tribute to the power of Ruby. + +Why /might/ rake be interesting to Ruby programmers. I don't know, +perhaps ... + +* No weird make syntax (only weird Ruby syntax :-) +* No need to edit or read XML (a la ant) +* Platform independent build scripts. +* Will run anywhere Ruby exists, so no need to have "make" installed. + If you stay away from the "sys" command and use things like + 'ftools', you can have a perfectly platform independent + build script. Also rake is only 100 lines of code, so it can + easily be packaged along with the rest of your code. + +So ... Sorry for the long rambling message. Like I said, I never +intended to write this code at all. diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/exe/rake b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/exe/rake new file mode 100755 index 0000000000..a00975f303 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/exe/rake @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +#-- +# Copyright (c) 2003, 2004, 2005, 2006, 2007 Jim Weirich +# +# 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 "rake" + +Rake.application.run diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake.rb new file mode 100644 index 0000000000..0dfd05315f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true +#-- +# Copyright 2003-2010 by 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. +#++ + +module Rake; end + +require "rake/version" + +require "rbconfig" +require "fileutils" +require "singleton" +require "monitor" +require "optparse" +require "ostruct" + +require "rake/ext/string" + +require "rake/win32" + +require "rake/linked_list" +require "rake/cpu_counter" +require "rake/scope" +require "rake/task_argument_error" +require "rake/rule_recursion_overflow_error" +require "rake/rake_module" +require "rake/trace_output" +require "rake/pseudo_status" +require "rake/task_arguments" +require "rake/invocation_chain" +require "rake/task" +require "rake/file_task" +require "rake/file_creation_task" +require "rake/multi_task" +require "rake/dsl_definition" +require "rake/file_utils_ext" +require "rake/file_list" +require "rake/default_loader" +require "rake/early_time" +require "rake/late_time" +require "rake/name_space" +require "rake/task_manager" +require "rake/application" +require "rake/backtrace" + +$trace = false + +# :stopdoc: +# +# Some top level Constants. + +FileList = Rake::FileList +RakeFileUtils = Rake::FileUtilsExt diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb new file mode 100644 index 0000000000..02586ad5e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb @@ -0,0 +1,831 @@ +# frozen_string_literal: true +require "optparse" + +require "rake/task_manager" +require "rake/file_list" +require "rake/thread_pool" +require "rake/thread_history_display" +require "rake/trace_output" +require "rake/win32" + +module Rake + + CommandLineOptionError = Class.new(StandardError) + + ## + # Rake main application object. When invoking +rake+ from the + # command line, a Rake::Application object is created and run. + + class Application + include TaskManager + include TraceOutput + + # The name of the application (typically 'rake') + attr_reader :name + + # The original directory where rake was invoked. + attr_reader :original_dir + + # Name of the actual rakefile used. + attr_reader :rakefile + + # Number of columns on the terminal + attr_accessor :terminal_columns + + # List of the top level task names (task names from the command line). + attr_reader :top_level_tasks + + # Override the detected TTY output state (mostly for testing) + attr_writer :tty_output + + DEFAULT_RAKEFILES = [ + "rakefile", + "Rakefile", + "rakefile.rb", + "Rakefile.rb" + ].freeze + + # Initialize a Rake::Application object. + def initialize + super + @name = "rake" + @rakefiles = DEFAULT_RAKEFILES.dup + @rakefile = nil + @pending_imports = [] + @imported = [] + @loaders = {} + @default_loader = Rake::DefaultLoader.new + @original_dir = Dir.pwd + @top_level_tasks = [] + add_loader("rb", DefaultLoader.new) + add_loader("rf", DefaultLoader.new) + add_loader("rake", DefaultLoader.new) + @tty_output = STDOUT.tty? + @terminal_columns = ENV["RAKE_COLUMNS"].to_i + + set_default_options + end + + # Run the Rake application. The run method performs the following + # three steps: + # + # * Initialize the command line options (+init+). + # * Define the tasks (+load_rakefile+). + # * Run the top level tasks (+top_level+). + # + # If you wish to build a custom rake command, you should call + # +init+ on your application. Then define any tasks. Finally, + # call +top_level+ to run your top level tasks. + def run(argv = ARGV) + standard_exception_handling do + init "rake", argv + load_rakefile + top_level + end + end + + # Initialize the command line parameters and app name. + def init(app_name="rake", argv = ARGV) + standard_exception_handling do + @name = app_name + begin + args = handle_options argv + rescue ArgumentError + # Backward compatibility for capistrano + args = handle_options + end + collect_command_line_tasks(args) + end + end + + # Find the rakefile and then load it and any pending imports. + def load_rakefile + standard_exception_handling do + raw_load_rakefile + end + end + + # Run the top level tasks of a Rake application. + def top_level + run_with_threads do + if options.show_tasks + display_tasks_and_comments + elsif options.show_prereqs + display_prerequisites + else + top_level_tasks.each { |task_name| invoke_task(task_name) } + end + end + end + + # Run the given block with the thread startup and shutdown. + def run_with_threads + thread_pool.gather_history if options.job_stats == :history + + yield + + thread_pool.join + if options.job_stats + stats = thread_pool.statistics + puts "Maximum active threads: #{stats[:max_active_threads]} + main" + puts "Total threads in play: #{stats[:total_threads_in_play]} + main" + end + ThreadHistoryDisplay.new(thread_pool.history).show if + options.job_stats == :history + end + + # Add a loader to handle imported files ending in the extension + # +ext+. + def add_loader(ext, loader) + ext = ".#{ext}" unless ext =~ /^\./ + @loaders[ext] = loader + end + + # Application options from the command line + def options + @options ||= OpenStruct.new + end + + # Return the thread pool used for multithreaded processing. + def thread_pool # :nodoc: + @thread_pool ||= ThreadPool.new(options.thread_pool_size || Rake.suggested_thread_count-1) + end + + # internal ---------------------------------------------------------------- + + # Invokes a task with arguments that are extracted from +task_string+ + def invoke_task(task_string) # :nodoc: + name, args = parse_task_string(task_string) + t = self[name] + t.invoke(*args) + end + + def parse_task_string(string) # :nodoc: + /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s + + name = $1 + remaining_args = $2 + + return string, [] unless name + return name, [] if remaining_args.empty? + + args = [] + + begin + /\s*((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args + + remaining_args = $2 + args << $1.gsub(/\\(.)/, '\1') + end while remaining_args + + return name, args + end + + # Provide standard exception handling for the given block. + def standard_exception_handling # :nodoc: + yield + rescue SystemExit + # Exit silently with current status + raise + rescue OptionParser::InvalidOption => ex + $stderr.puts ex.message + exit(false) + rescue Exception => ex + # Exit with error message + display_error_message(ex) + exit_because_of_exception(ex) + end + + # Exit the program because of an unhandled exception. + # (may be overridden by subclasses) + def exit_because_of_exception(ex) # :nodoc: + exit(false) + end + + # Display the error message that caused the exception. + def display_error_message(ex) # :nodoc: + trace "#{name} aborted!" + display_exception_details(ex) + trace "Tasks: #{ex.chain}" if has_chain?(ex) + trace "(See full trace by running task with --trace)" unless + options.backtrace + end + + def display_exception_details(ex) # :nodoc: + display_exception_details_seen << ex + + display_exception_message_details(ex) + display_exception_backtrace(ex) + display_cause_details(ex.cause) if has_cause?(ex) + end + + def display_cause_details(ex) # :nodoc: + return if display_exception_details_seen.include? ex + + trace "\nCaused by:" + display_exception_details(ex) + end + + def display_exception_details_seen # :nodoc: + Thread.current[:rake_display_exception_details_seen] ||= [] + end + + def has_cause?(ex) # :nodoc: + ex.respond_to?(:cause) && ex.cause + end + + def display_exception_message_details(ex) # :nodoc: + if ex.instance_of?(RuntimeError) + trace ex.message + else + trace "#{ex.class.name}: #{ex.message}" + end + end + + def display_exception_backtrace(ex) # :nodoc: + if options.backtrace + trace ex.backtrace.join("\n") + else + trace Backtrace.collapse(ex.backtrace).join("\n") + end + end + + # Warn about deprecated usage. + # + # Example: + # Rake.application.deprecate("import", "Rake.import", caller.first) + # + def deprecate(old_usage, new_usage, call_site) # :nodoc: + unless options.ignore_deprecate + $stderr.puts "WARNING: '#{old_usage}' is deprecated. " + + "Please use '#{new_usage}' instead.\n" + + " at #{call_site}" + end + end + + # Does the exception have a task invocation chain? + def has_chain?(exception) # :nodoc: + exception.respond_to?(:chain) && exception.chain + end + private :has_chain? + + # True if one of the files in RAKEFILES is in the current directory. + # If a match is found, it is copied into @rakefile. + def have_rakefile # :nodoc: + @rakefiles.each do |fn| + if File.exist?(fn) + others = FileList.glob(fn, File::FNM_CASEFOLD) + return others.size == 1 ? others.first : fn + elsif fn == "" + return fn + end + end + return nil + end + + # True if we are outputting to TTY, false otherwise + def tty_output? # :nodoc: + @tty_output + end + + # We will truncate output if we are outputting to a TTY or if we've been + # given an explicit column width to honor + def truncate_output? # :nodoc: + tty_output? || @terminal_columns.nonzero? + end + + # Display the tasks and comments. + def display_tasks_and_comments # :nodoc: + displayable_tasks = tasks.select { |t| + (options.show_all_tasks || t.comment) && + t.name =~ options.show_task_pattern + } + case options.show_tasks + when :tasks + width = displayable_tasks.map { |t| t.name_with_args.length }.max || 10 + if truncate_output? + max_column = terminal_width - name.size - width - 7 + else + max_column = nil + end + + displayable_tasks.each do |t| + printf("#{name} %-#{width}s # %s\n", + t.name_with_args, + max_column ? truncate(t.comment, max_column) : t.comment) + end + when :describe + displayable_tasks.each do |t| + puts "#{name} #{t.name_with_args}" + comment = t.full_comment || "" + comment.split("\n").each do |line| + puts " #{line}" + end + puts + end + when :lines + displayable_tasks.each do |t| + t.locations.each do |loc| + printf "#{name} %-30s %s\n", t.name_with_args, loc + end + end + else + fail "Unknown show task mode: '#{options.show_tasks}'" + end + end + + def terminal_width # :nodoc: + if @terminal_columns.nonzero? + result = @terminal_columns + else + result = unix? ? dynamic_width : 80 + end + (result < 10) ? 80 : result + rescue + 80 + end + + # Calculate the dynamic width of the + def dynamic_width # :nodoc: + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty # :nodoc: + %x{stty size 2>/dev/null}.split[1].to_i + end + + def dynamic_width_tput # :nodoc: + %x{tput cols 2>/dev/null}.to_i + end + + def unix? # :nodoc: + RbConfig::CONFIG["host_os"] =~ + /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def windows? # :nodoc: + Win32.windows? + end + + def truncate(string, width) # :nodoc: + if string.nil? + "" + elsif string.length <= width + string + else + (string[0, width - 3] || "") + "..." + end + end + + # Display the tasks and prerequisites + def display_prerequisites # :nodoc: + tasks.each do |t| + puts "#{name} #{t.name}" + t.prerequisites.each { |pre| puts " #{pre}" } + end + end + + def trace(*strings) # :nodoc: + options.trace_output ||= $stderr + trace_on(options.trace_output, *strings) + end + + def sort_options(options) # :nodoc: + options.sort_by { |opt| + opt.select { |o| o.is_a?(String) && o =~ /^-/ }.map(&:downcase).sort.reverse + } + end + private :sort_options + + # A list of all the standard options used in rake, suitable for + # passing to OptionParser. + def standard_rake_options # :nodoc: + sort_options( + [ + ["--all", "-A", + "Show all tasks, even uncommented ones (in combination with -T or -D)", + lambda { |value| + options.show_all_tasks = value + } + ], + ["--backtrace=[OUT]", + "Enable full backtrace. OUT can be stderr (default) or stdout.", + lambda { |value| + options.backtrace = true + select_trace_output(options, "backtrace", value) + } + ], + ["--build-all", "-B", + "Build all prerequisites, including those which are up-to-date.", + lambda { |value| + options.build_all = true + } + ], + ["--comments", + "Show commented tasks only", + lambda { |value| + options.show_all_tasks = !value + } + ], + ["--describe", "-D [PATTERN]", + "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + select_tasks_to_show(options, :describe, value) + } + ], + ["--directory", "-C [DIRECTORY]", + "Change to DIRECTORY before doing anything.", + lambda { |value| + Dir.chdir value + @original_dir = Dir.pwd + } + ], + ["--dry-run", "-n", + "Do a dry run without executing actions.", + lambda { |value| + Rake.verbose(true) + Rake.nowrite(true) + options.dryrun = true + options.trace = true + } + ], + ["--execute", "-e CODE", + "Execute some Ruby code and exit.", + lambda { |value| + eval(value) + exit + } + ], + ["--execute-print", "-p CODE", + "Execute some Ruby code, print the result, then exit.", + lambda { |value| + puts eval(value) + exit + } + ], + ["--execute-continue", "-E CODE", + "Execute some Ruby code, " + + "then continue with normal task processing.", + lambda { |value| eval(value) } + ], + ["--jobs", "-j [NUMBER]", + "Specifies the maximum number of tasks to execute in parallel. " + + "(default is number of CPU cores + 4)", + lambda { |value| + if value.nil? || value == "" + value = Float::INFINITY + elsif value =~ /^\d+$/ + value = value.to_i + else + value = Rake.suggested_thread_count + end + value = 1 if value < 1 + options.thread_pool_size = value - 1 + } + ], + ["--job-stats [LEVEL]", + "Display job statistics. " + + "LEVEL=history displays a complete job list", + lambda { |value| + if value =~ /^history/i + options.job_stats = :history + else + options.job_stats = true + end + } + ], + ["--libdir", "-I LIBDIR", + "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ["--multitask", "-m", + "Treat all tasks as multitasks.", + lambda { |value| options.always_multitask = true } + ], + ["--no-search", "--nosearch", + "-N", "Do not search parent directories for the Rakefile.", + lambda { |value| options.nosearch = true } + ], + ["--prereqs", "-P", + "Display the tasks and dependencies, then exit.", + lambda { |value| options.show_prereqs = true } + ], + ["--quiet", "-q", + "Do not log messages to standard output.", + lambda { |value| Rake.verbose(false) } + ], + ["--rakefile", "-f [FILENAME]", + "Use FILENAME as the rakefile to search for.", + lambda { |value| + value ||= "" + @rakefiles.clear + @rakefiles << value + } + ], + ["--rakelibdir", "--rakelib", "-R RAKELIBDIR", + "Auto-import any .rake files in RAKELIBDIR. " + + "(default is 'rakelib')", + lambda { |value| + options.rakelib = value.split(File::PATH_SEPARATOR) + } + ], + ["--require", "-r MODULE", + "Require MODULE before executing rakefile.", + lambda { |value| + begin + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError + raise ex + end + end + } + ], + ["--rules", + "Trace the rules resolution.", + lambda { |value| options.trace_rules = true } + ], + ["--silent", "-s", + "Like --quiet, but also suppresses the " + + "'in directory' announcement.", + lambda { |value| + Rake.verbose(false) + options.silent = true + } + ], + ["--suppress-backtrace PATTERN", + "Suppress backtrace lines matching regexp PATTERN. " + + "Ignored if --trace is on.", + lambda { |value| + options.suppress_backtrace_pattern = Regexp.new(value) + } + ], + ["--system", "-g", + "Using system wide (global) rakefiles " + + "(usually '~/.rake/*.rake').", + lambda { |value| options.load_system = true } + ], + ["--no-system", "--nosystem", "-G", + "Use standard project Rakefile search paths, " + + "ignore system wide rakefiles.", + lambda { |value| options.ignore_system = true } + ], + ["--tasks", "-T [PATTERN]", + "Display the tasks (matching optional PATTERN) " + + "with descriptions, then exit. " + + "-AT combination displays all of tasks contained no description.", + lambda { |value| + select_tasks_to_show(options, :tasks, value) + } + ], + ["--trace=[OUT]", "-t", + "Turn on invoke/execute tracing, enable full backtrace. " + + "OUT can be stderr (default) or stdout.", + lambda { |value| + options.trace = true + options.backtrace = true + select_trace_output(options, "trace", value) + Rake.verbose(true) + } + ], + ["--verbose", "-v", + "Log message to standard output.", + lambda { |value| Rake.verbose(true) } + ], + ["--version", "-V", + "Display the program version.", + lambda { |value| + puts "rake, version #{Rake::VERSION}" + exit + } + ], + ["--where", "-W [PATTERN]", + "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + select_tasks_to_show(options, :lines, value) + options.show_all_tasks = true + } + ], + ["--no-deprecation-warnings", "-X", + "Disable the deprecation warnings.", + lambda { |value| + options.ignore_deprecate = true + } + ], + ]) + end + + def select_tasks_to_show(options, show_tasks, value) # :nodoc: + options.show_tasks = show_tasks + options.show_task_pattern = Regexp.new(value || "") + Rake::TaskManager.record_task_metadata = true + end + private :select_tasks_to_show + + def select_trace_output(options, trace_option, value) # :nodoc: + value = value.strip unless value.nil? + case value + when "stdout" + options.trace_output = $stdout + when "stderr", nil + options.trace_output = $stderr + else + fail CommandLineOptionError, + "Unrecognized --#{trace_option} option '#{value}'" + end + end + private :select_trace_output + + # Read and handle the command line options. Returns the command line + # arguments that we didn't understand, which should (in theory) be just + # task names and env vars. + def handle_options(argv) # :nodoc: + set_default_options + + OptionParser.new do |opts| + opts.banner = "#{Rake.application.name} [-f rakefile] {options} targets..." + opts.separator "" + opts.separator "Options are ..." + + opts.on_tail("-h", "--help", "-H", "Display this help message.") do + puts opts + exit + end + + standard_rake_options.each { |args| opts.on(*args) } + opts.environment("RAKEOPT") + end.parse(argv) + end + + # Similar to the regular Ruby +require+ command, but will check + # for *.rake files in addition to *.rb files. + def rake_require(file_name, paths=$LOAD_PATH, loaded=$") # :nodoc: + fn = file_name + ".rake" + return false if loaded.include?(fn) + paths.each do |path| + full_path = File.join(path, fn) + if File.exist?(full_path) + Rake.load_rakefile(full_path) + loaded << fn + return true + end + end + fail LoadError, "Can't find #{file_name}" + end + + def find_rakefile_location # :nodoc: + here = Dir.pwd + until (fn = have_rakefile) + Dir.chdir("..") + return nil if Dir.pwd == here || options.nosearch + here = Dir.pwd + end + [fn, here] + ensure + Dir.chdir(Rake.original_dir) + end + + def print_rakefile_directory(location) # :nodoc: + $stderr.puts "(in #{Dir.pwd})" unless + options.silent or original_dir == location + end + + def raw_load_rakefile # :nodoc: + rakefile, location = find_rakefile_location + if (!options.ignore_system) && + (options.load_system || rakefile.nil?) && + system_dir && File.directory?(system_dir) + print_rakefile_directory(location) + glob("#{system_dir}/*.rake") do |name| + add_import name + end + else + fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if + rakefile.nil? + @rakefile = rakefile + Dir.chdir(location) + print_rakefile_directory(location) + Rake.load_rakefile(File.expand_path(@rakefile)) if + @rakefile && @rakefile != "" + options.rakelib.each do |rlib| + glob("#{rlib}/*.rake") do |name| + add_import name + end + end + end + load_imports + end + + def glob(path, &block) # :nodoc: + FileList.glob(path.tr("\\", "/")).each(&block) + end + private :glob + + # The directory path containing the system wide rakefiles. + def system_dir # :nodoc: + @system_dir ||= + begin + if ENV["RAKE_SYSTEM"] + ENV["RAKE_SYSTEM"] + else + standard_system_dir + end + end + end + + # The standard directory containing system wide rake files. + if Win32.windows? + def standard_system_dir #:nodoc: + Win32.win32_system_dir + end + else + def standard_system_dir #:nodoc: + File.join(File.expand_path("~"), ".rake") + end + end + private :standard_system_dir + + # Collect the list of tasks on the command line. If no tasks are + # given, return a list containing only the default task. + # Environmental assignments are processed at this time as well. + # + # `args` is the list of arguments to peruse to get the list of tasks. + # It should be the command line that was given to rake, less any + # recognised command-line options, which OptionParser.parse will + # have taken care of already. + def collect_command_line_tasks(args) # :nodoc: + @top_level_tasks = [] + args.each do |arg| + if arg =~ /^(\w+)=(.*)$/m + ENV[$1] = $2 + else + @top_level_tasks << arg unless arg =~ /^-/ + end + end + @top_level_tasks.push(default_task_name) if @top_level_tasks.empty? + end + + # Default task name ("default"). + # (May be overridden by subclasses) + def default_task_name # :nodoc: + "default" + end + + # Add a file to the list of files to be imported. + def add_import(fn) # :nodoc: + @pending_imports << fn + end + + # Load the pending list of imported files. + def load_imports # :nodoc: + while fn = @pending_imports.shift + next if @imported.member?(fn) + fn_task = lookup(fn) and fn_task.invoke + ext = File.extname(fn) + loader = @loaders[ext] || @default_loader + loader.load(fn) + if fn_task = lookup(fn) and fn_task.needed? + fn_task.reenable + fn_task.invoke + loader.load(fn) + end + @imported << fn + end + end + + def rakefile_location(backtrace=caller) # :nodoc: + backtrace.map { |t| t[/([^:]+):/, 1] } + + re = /^#{@rakefile}$/ + re = /#{re.source}/i if windows? + + backtrace.find { |str| str =~ re } || "" + end + + def set_default_options # :nodoc: + options.always_multitask = false + options.backtrace = false + options.build_all = false + options.dryrun = false + options.ignore_deprecate = false + options.ignore_system = false + options.job_stats = false + options.load_system = false + options.nosearch = false + options.rakelib = %w[rakelib] + options.show_all_tasks = false + options.show_prereqs = false + options.show_task_pattern = nil + options.show_tasks = nil + options.silent = false + options.suppress_backtrace_pattern = nil + options.thread_pool_size = Rake.suggested_thread_count + options.trace = false + options.trace_output = $stderr + options.trace_rules = false + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/backtrace.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/backtrace.rb new file mode 100644 index 0000000000..31ff054506 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/backtrace.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Rake + module Backtrace # :nodoc: all + SYS_KEYS = RbConfig::CONFIG.keys.grep(/(?:[a-z]prefix|libdir)\z/) + SYS_PATHS = RbConfig::CONFIG.values_at(*SYS_KEYS).uniq + + [ File.join(File.dirname(__FILE__), "..") ] + + SUPPRESSED_PATHS = SYS_PATHS. + map { |s| s.tr("\\", "/") }. + map { |f| File.expand_path(f) }. + reject { |s| s.nil? || s =~ /^ *$/ } + SUPPRESSED_PATHS_RE = SUPPRESSED_PATHS.map { |f| Regexp.quote(f) }.join("|") + SUPPRESSED_PATHS_RE << "|^org\\/jruby\\/\\w+\\.java" if + Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == "jruby" + + SUPPRESS_PATTERN = %r!(\A(#{SUPPRESSED_PATHS_RE})|bin/rake:\d+)!i + + def self.collapse(backtrace) + pattern = Rake.application.options.suppress_backtrace_pattern || + SUPPRESS_PATTERN + backtrace.reject { |elem| elem =~ pattern } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/clean.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/clean.rb new file mode 100644 index 0000000000..b52e832a90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/clean.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +# The 'rake/clean' file defines two file lists (CLEAN and CLOBBER) and +# two rake tasks (:clean and :clobber). +# +# [:clean] Clean up the project by deleting scratch files and backup +# files. Add files to the CLEAN file list to have the :clean +# target handle them. +# +# [:clobber] Clobber all generated and non-source files in a project. +# The task depends on :clean, so all the clean files will +# be deleted as well as files in the CLOBBER file list. +# The intent of this task is to return a project to its +# pristine, just unpacked state. + +require "rake" + +# :stopdoc: + +module Rake + module Cleaner + extend FileUtils + + module_function + + def cleanup_files(file_names) + file_names.each do |file_name| + cleanup(file_name) + end + end + + def cleanup(file_name, **opts) + begin + opts = { verbose: Rake.application.options.trace }.merge(opts) + rm_r file_name, **opts + rescue StandardError => ex + puts "Failed to remove #{file_name}: #{ex}" unless file_already_gone?(file_name) + end + end + + def file_already_gone?(file_name) + return false if File.exist?(file_name) + + path = file_name + prev = nil + + while path = File.dirname(path) + return false if cant_be_deleted?(path) + break if [prev, "."].include?(path) + prev = path + end + true + end + private_class_method :file_already_gone? + + def cant_be_deleted?(path_name) + File.exist?(path_name) && + (!File.readable?(path_name) || !File.executable?(path_name)) + end + private_class_method :cant_be_deleted? + end +end + +CLEAN = ::Rake::FileList["**/*~", "**/*.bak", "**/core"] +CLEAN.clear_exclude.exclude { |fn| + fn.pathmap("%f").downcase == "core" && File.directory?(fn) +} + +desc "Remove any temporary products." +task :clean do + Rake::Cleaner.cleanup_files(CLEAN) +end + +CLOBBER = ::Rake::FileList.new + +desc "Remove any generated files." +task clobber: [:clean] do + Rake::Cleaner.cleanup_files(CLOBBER) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cloneable.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cloneable.rb new file mode 100644 index 0000000000..eddb77e2f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cloneable.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Rake + ## + # Mixin for creating easily cloned objects. + + module Cloneable # :nodoc: + # The hook that is invoked by 'clone' and 'dup' methods. + def initialize_copy(source) + super + source.instance_variables.each do |var| + src_value = source.instance_variable_get(var) + value = src_value.clone rescue src_value + instance_variable_set(var, value) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cpu_counter.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cpu_counter.rb new file mode 100644 index 0000000000..564a628591 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/cpu_counter.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +module Rake + + # Based on a script at: + # http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed + class CpuCounter # :nodoc: all + def self.count + new.count_with_default + end + + def count_with_default(default=4) + count || default + rescue StandardError + default + end + + begin + require "etc" + rescue LoadError + else + if Etc.respond_to?(:nprocessors) + def count + return Etc.nprocessors + end + end + end + end +end + +unless Rake::CpuCounter.method_defined?(:count) + Rake::CpuCounter.class_eval <<-'end;', __FILE__, __LINE__+1 + require 'rbconfig' + + def count + if RUBY_PLATFORM == 'java' + count_via_java_runtime + else + case RbConfig::CONFIG['host_os'] + when /linux/ + count_via_cpuinfo + when /darwin|bsd/ + count_via_sysctl + when /mswin|mingw/ + count_via_win32 + else + # Try everything + count_via_win32 || + count_via_sysctl || + count_via_cpuinfo + end + end + end + + def count_via_java_runtime + Java::Java.lang.Runtime.getRuntime.availableProcessors + rescue StandardError + nil + end + + def count_via_win32 + require 'win32ole' + wmi = WIN32OLE.connect("winmgmts://") + cpu = wmi.ExecQuery("select NumberOfCores from Win32_Processor") # TODO count hyper-threaded in this + cpu.to_enum.first.NumberOfCores + rescue StandardError, LoadError + nil + end + + def count_via_cpuinfo + open('/proc/cpuinfo') { |f| f.readlines }.grep(/processor/).size + rescue StandardError + nil + end + + def count_via_sysctl + run 'sysctl', '-n', 'hw.ncpu' + end + + def run(command, *args) + cmd = resolve_command(command) + if cmd + IO.popen [cmd, *args] do |io| + io.read.to_i + end + else + nil + end + end + + def resolve_command(command) + look_for_command("/usr/sbin", command) || + look_for_command("/sbin", command) || + in_path_command(command) + end + + def look_for_command(dir, command) + path = File.join(dir, command) + File.exist?(path) ? path : nil + end + + def in_path_command(command) + IO.popen ['which', command] do |io| + io.eof? ? nil : command + end + end + end; +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/default_loader.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/default_loader.rb new file mode 100644 index 0000000000..d3b4650d32 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/default_loader.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Rake + + # Default Rakefile loader used by +import+. + class DefaultLoader + + ## + # Loads a rakefile into the current application from +fn+ + + def load(fn) + Rake.load_rakefile(File.expand_path(fn)) + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/dsl_definition.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/dsl_definition.rb new file mode 100644 index 0000000000..c80464020b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/dsl_definition.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true +# Rake DSL functions. +require "rake/file_utils_ext" + +module Rake + + ## + # DSL is a module that provides #task, #desc, #namespace, etc. Use this + # when you'd like to use rake outside the top level scope. + # + # For a Rakefile you run from the command line this module is automatically + # included. + + module DSL + + #-- + # Include the FileUtils file manipulation functions in the top + # level module, but mark them private so that they don't + # unintentionally define methods on other objects. + #++ + + include FileUtilsExt + private(*FileUtils.instance_methods(false)) + private(*FileUtilsExt.instance_methods(false)) + + private + + # :call-seq: + # task(task_name) + # task(task_name: dependencies) + # task(task_name, arguments => dependencies) + # + # Declare a basic task. The +task_name+ is always the first argument. If + # the task name contains a ":" it is defined in that namespace. + # + # The +dependencies+ may be a single task name or an Array of task names. + # The +argument+ (a single name) or +arguments+ (an Array of names) define + # the arguments provided to the task. + # + # The task, argument and dependency names may be either symbols or + # strings. + # + # A task with a single dependency: + # + # task clobber: %w[clean] do + # rm_rf "html" + # end + # + # A task with an argument and a dependency: + # + # task :package, [:version] => :test do |t, args| + # # ... + # end + # + # To invoke this task from the command line: + # + # $ rake package[1.2.3] + # + def task(*args, &block) # :doc: + Rake::Task.define_task(*args, &block) + end + + # Declare a file task. + # + # Example: + # file "config.cfg" => ["config.template"] do + # open("config.cfg", "w") do |outfile| + # open("config.template") do |infile| + # while line = infile.gets + # outfile.puts line + # end + # end + # end + # end + # + def file(*args, &block) # :doc: + Rake::FileTask.define_task(*args, &block) + end + + # Declare a file creation task. + # (Mainly used for the directory command). + def file_create(*args, &block) + Rake::FileCreationTask.define_task(*args, &block) + end + + # Declare a set of files tasks to create the given directories on + # demand. + # + # Example: + # directory "testdata/doc" + # + def directory(*args, &block) # :doc: + result = file_create(*args, &block) + dir, _ = *Rake.application.resolve_args(args) + dir = Rake.from_pathname(dir) + Rake.each_dir_parent(dir) do |d| + file_create d do |t| + mkdir_p t.name unless File.exist?(t.name) + end + end + result + end + + # Declare a task that performs its prerequisites in + # parallel. Multitasks does *not* guarantee that its prerequisites + # will execute in any given order (which is obvious when you think + # about it) + # + # Example: + # multitask deploy: %w[deploy_gem deploy_rdoc] + # + def multitask(*args, &block) # :doc: + Rake::MultiTask.define_task(*args, &block) + end + + # Create a new rake namespace and use it for evaluating the given + # block. Returns a NameSpace object that can be used to lookup + # tasks defined in the namespace. + # + # Example: + # + # ns = namespace "nested" do + # # the "nested:run" task + # task :run + # end + # task_run = ns[:run] # find :run in the given namespace. + # + # Tasks can also be defined in a namespace by using a ":" in the task + # name: + # + # task "nested:test" do + # # ... + # end + # + def namespace(name=nil, &block) # :doc: + name = name.to_s if name.kind_of?(Symbol) + name = name.to_str if name.respond_to?(:to_str) + unless name.kind_of?(String) || name.nil? + raise ArgumentError, "Expected a String or Symbol for a namespace name" + end + Rake.application.in_namespace(name, &block) + end + + # Declare a rule for auto-tasks. + # + # Example: + # rule '.o' => '.c' do |t| + # sh 'cc', '-o', t.name, t.source + # end + # + def rule(*args, &block) # :doc: + Rake::Task.create_rule(*args, &block) + end + + # Describes the next rake task. Duplicate descriptions are discarded. + # Descriptions are shown with rake -T (up to the first + # sentence) and rake -D (the entire description). + # + # Example: + # desc "Run the Unit Tests" + # task test: [:build] + # # ... run tests + # end + # + def desc(description) # :doc: + Rake.application.last_description = description + end + + # Import the partial Rakefiles +fn+. Imported files are loaded + # _after_ the current file is completely loaded. This allows the + # import statement to appear anywhere in the importing file, and yet + # allowing the imported files to depend on objects defined in the + # importing file. + # + # A common use of the import statement is to include files + # containing dependency declarations. + # + # See also the --rakelibdir command line option. + # + # Example: + # import ".depend", "my_rules" + # + def import(*fns) # :doc: + fns.each do |fn| + Rake.application.add_import(fn) + end + end + end + extend FileUtilsExt +end + +# Extend the main object with the DSL commands. This allows top-level +# calls to task, etc. to work from a Rakefile without polluting the +# object inheritance tree. +self.extend Rake::DSL diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/early_time.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/early_time.rb new file mode 100644 index 0000000000..80cc6bfade --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/early_time.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module Rake + + # EarlyTime is a fake timestamp that occurs _before_ any other time value. + class EarlyTime + include Comparable + include Singleton + + ## + # The EarlyTime always comes before +other+! + + def <=>(other) + -1 + end + + def to_s # :nodoc: + "" + end + end + + EARLY = EarlyTime.instance +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/core.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/core.rb new file mode 100644 index 0000000000..226f2125b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/core.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class Module + # Check for an existing method in the current class before extending. If + # the method already exists, then a warning is printed and the extension is + # not added. Otherwise the block is yielded and any definitions in the + # block will take effect. + # + # Usage: + # + # class String + # rake_extension("xyz") do + # def xyz + # ... + # end + # end + # end + # + def rake_extension(method) # :nodoc: + if method_defined?(method) + $stderr.puts "WARNING: Possible conflict with Rake extension: " + + "#{self}##{method} already exists" + else + yield + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/string.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/string.rb new file mode 100644 index 0000000000..c70236ae9c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/ext/string.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true +require "rake/ext/core" + +class String + + rake_extension("ext") do + # Replace the file extension with +newext+. If there is no extension on + # the string, append the new extension to the end. If the new extension + # is not given, or is the empty string, remove any existing extension. + # + # +ext+ is a user added method for the String class. + # + # This String extension comes from Rake + def ext(newext="") + return self.dup if [".", ".."].include? self + if newext != "" + newext = "." + newext unless newext =~ /^\./ + end + self.chomp(File.extname(self)) << newext + end + end + + rake_extension("pathmap") do + # Explode a path into individual components. Used by +pathmap+. + # + # This String extension comes from Rake + def pathmap_explode + head, tail = File.split(self) + return [self] if head == self + return [tail] if head == "." || tail == "/" + return [head, tail] if head == "/" + return head.pathmap_explode + [tail] + end + protected :pathmap_explode + + # Extract a partial path from the path. Include +n+ directories from the + # front end (left hand side) if +n+ is positive. Include |+n+| + # directories from the back end (right hand side) if +n+ is negative. + # + # This String extension comes from Rake + def pathmap_partial(n) + dirs = File.dirname(self).pathmap_explode + partial_dirs = + if n > 0 + dirs[0...n] + elsif n < 0 + dirs.reverse[0...-n].reverse + else + "." + end + File.join(partial_dirs) + end + protected :pathmap_partial + + # Perform the pathmap replacement operations on the given path. The + # patterns take the form 'pat1,rep1;pat2,rep2...'. + # + # This String extension comes from Rake + def pathmap_replace(patterns, &block) + result = self + patterns.split(";").each do |pair| + pattern, replacement = pair.split(",") + pattern = Regexp.new(pattern) + if replacement == "*" && block_given? + result = result.sub(pattern, &block) + elsif replacement + result = result.sub(pattern, replacement) + else + result = result.sub(pattern, "") + end + end + result + end + protected :pathmap_replace + + # Map the path according to the given specification. The specification + # controls the details of the mapping. The following special patterns are + # recognized: + # + # %p :: The complete path. + # %f :: The base file name of the path, with its file extension, + # but without any directories. + # %n :: The file name of the path without its file extension. + # %d :: The directory list of the path. + # %x :: The file extension of the path. An empty string if there + # is no extension. + # %X :: Everything *but* the file extension. + # %s :: The alternate file separator if defined, otherwise use # + # the standard file separator. + # %% :: A percent sign. + # + # The %d specifier can also have a numeric prefix (e.g. '%2d'). + # If the number is positive, only return (up to) +n+ directories in the + # path, starting from the left hand side. If +n+ is negative, return (up + # to) +n+ directories from the right hand side of the path. + # + # Examples: + # + # 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b' + # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d' + # + # Also the %d, %p, %f, %n, + # %x, and %X operators can take a pattern/replacement + # argument to perform simple string substitutions on a particular part of + # the path. The pattern and replacement are separated by a comma and are + # enclosed by curly braces. The replacement spec comes after the % + # character but before the operator letter. (e.g. "%{old,new}d"). + # Multiple replacement specs should be separated by semi-colons (e.g. + # "%{old,new;src,bin}d"). + # + # Regular expressions may be used for the pattern, and back refs may be + # used in the replacement text. Curly braces, commas and semi-colons are + # excluded from both the pattern and replacement text (let's keep parsing + # reasonable). + # + # For example: + # + # "src/org/onestepback/proj/A.java".pathmap("%{^src,class}X.class") + # + # returns: + # + # "class/org/onestepback/proj/A.class" + # + # If the replacement text is '*', then a block may be provided to perform + # some arbitrary calculation for the replacement. + # + # For example: + # + # "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext| + # ext.downcase + # } + # + # Returns: + # + # "/path/to/file.txt" + # + # This String extension comes from Rake + def pathmap(spec=nil, &block) + return self if spec.nil? + result = "".dup + spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag| + case frag + when "%f" + result << File.basename(self) + when "%n" + result << File.basename(self).ext + when "%d" + result << File.dirname(self) + when "%x" + result << File.extname(self) + when "%X" + result << self.ext + when "%p" + result << self + when "%s" + result << (File::ALT_SEPARATOR || File::SEPARATOR) + when "%-" + # do nothing + when "%%" + result << "%" + when /%(-?\d+)d/ + result << pathmap_partial($1.to_i) + when /^%\{([^}]*)\}(\d*[dpfnxX])/ + patterns, operator = $1, $2 + result << pathmap("%" + operator).pathmap_replace(patterns, &block) + when /^%/ + fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'" + else + result << frag + end + end + result + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_creation_task.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_creation_task.rb new file mode 100644 index 0000000000..5a4c684925 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_creation_task.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require "rake/file_task" +require "rake/early_time" + +module Rake + + # A FileCreationTask is a file task that when used as a dependency will be + # needed if and only if the file has not been created. Once created, it is + # not re-triggered if any of its dependencies are newer, nor does trigger + # any rebuilds of tasks that depend on it whenever it is updated. + # + class FileCreationTask < FileTask + # Is this file task needed? Yes if it doesn't exist. + def needed? + !File.exist?(name) + end + + # Time stamp for file creation task. This time stamp is earlier + # than any other time stamp. + def timestamp + Rake::EARLY + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_list.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_list.rb new file mode 100644 index 0000000000..22c339f243 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_list.rb @@ -0,0 +1,435 @@ +# frozen_string_literal: true +require "rake/cloneable" +require "rake/file_utils_ext" +require "rake/ext/string" + +module Rake + + ## + # A FileList is essentially an array with a few helper methods defined to + # make file manipulation a bit easier. + # + # FileLists are lazy. When given a list of glob patterns for possible files + # to be included in the file list, instead of searching the file structures + # to find the files, a FileList holds the pattern for latter use. + # + # This allows us to define a number of FileList to match any number of + # files, but only search out the actual files when then FileList itself is + # actually used. The key is that the first time an element of the + # FileList/Array is requested, the pending patterns are resolved into a real + # list of file names. + # + class FileList + + include Cloneable + + # == Method Delegation + # + # The lazy evaluation magic of FileLists happens by implementing all the + # array specific methods to call +resolve+ before delegating the heavy + # lifting to an embedded array object (@items). + # + # In addition, there are two kinds of delegation calls. The regular kind + # delegates to the @items array and returns the result directly. Well, + # almost directly. It checks if the returned value is the @items object + # itself, and if so will return the FileList object instead. + # + # The second kind of delegation call is used in methods that normally + # return a new Array object. We want to capture the return value of these + # methods and wrap them in a new FileList object. We enumerate these + # methods in the +SPECIAL_RETURN+ list below. + + # List of array methods (that are not in +Object+) that need to be + # delegated. + ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map(&:to_s) + + # List of additional methods that must be delegated. + MUST_DEFINE = %w[inspect <=>] + + # List of methods that should not be delegated here (we define special + # versions of them explicitly below). + MUST_NOT_DEFINE = %w[to_a to_ary partition * <<] + + # List of delegated methods that return new array values which need + # wrapping. + SPECIAL_RETURN = %w[ + map collect sort sort_by select find_all reject grep + compact flatten uniq values_at + + - & | + ] + + DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).map(&:to_s).sort.uniq + + # Now do the delegation. + DELEGATING_METHODS.each do |sym| + if SPECIAL_RETURN.include?(sym) + ln = __LINE__ + 1 + class_eval %{ + def #{sym}(*args, &block) + resolve + result = @items.send(:#{sym}, *args, &block) + self.class.new.import(result) + end + }, __FILE__, ln + else + ln = __LINE__ + 1 + class_eval %{ + def #{sym}(*args, &block) + resolve + result = @items.send(:#{sym}, *args, &block) + result.object_id == @items.object_id ? self : result + end + }, __FILE__, ln + end + end + + GLOB_PATTERN = %r{[*?\[\{]} + + # Create a file list from the globbable patterns given. If you wish to + # perform multiple includes or excludes at object build time, use the + # "yield self" pattern. + # + # Example: + # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb') + # + # pkg_files = FileList.new('lib/**/*') do |fl| + # fl.exclude(/\bCVS\b/) + # end + # + def initialize(*patterns) + @pending_add = [] + @pending = false + @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup + @exclude_procs = DEFAULT_IGNORE_PROCS.dup + @items = [] + patterns.each { |pattern| include(pattern) } + yield self if block_given? + end + + # Add file names defined by glob patterns to the file list. If an array + # is given, add each element of the array. + # + # Example: + # file_list.include("*.java", "*.cfg") + # file_list.include %w( math.c lib.h *.o ) + # + def include(*filenames) + # TODO: check for pending + filenames.each do |fn| + if fn.respond_to? :to_ary + include(*fn.to_ary) + else + @pending_add << Rake.from_pathname(fn) + end + end + @pending = true + self + end + alias :add :include + + # Register a list of file name patterns that should be excluded from the + # list. Patterns may be regular expressions, glob patterns or regular + # strings. In addition, a block given to exclude will remove entries that + # return true when given to the block. + # + # Note that glob patterns are expanded against the file system. If a file + # is explicitly added to a file list, but does not exist in the file + # system, then an glob pattern in the exclude list will not exclude the + # file. + # + # Examples: + # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c'] + # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c'] + # + # If "a.c" is a file, then ... + # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c'] + # + # If "a.c" is not a file, then ... + # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c'] + # + def exclude(*patterns, &block) + patterns.each do |pat| + if pat.respond_to? :to_ary + exclude(*pat.to_ary) + else + @exclude_patterns << Rake.from_pathname(pat) + end + end + @exclude_procs << block if block_given? + resolve_exclude unless @pending + self + end + + # Clear all the exclude patterns so that we exclude nothing. + def clear_exclude + @exclude_patterns = [] + @exclude_procs = [] + self + end + + # A FileList is equal through array equality. + def ==(array) + to_ary == array + end + + # Return the internal array object. + def to_a + resolve + @items + end + + # Return the internal array object. + def to_ary + to_a + end + + # Lie about our class. + def is_a?(klass) + klass == Array || super(klass) + end + alias kind_of? is_a? + + # Redefine * to return either a string or a new file list. + def *(other) + result = @items * other + case result + when Array + self.class.new.import(result) + else + result + end + end + + def <<(obj) + resolve + @items << Rake.from_pathname(obj) + self + end + + # Resolve all the pending adds now. + def resolve + if @pending + @pending = false + @pending_add.each do |fn| resolve_add(fn) end + @pending_add = [] + resolve_exclude + end + self + end + + def resolve_add(fn) # :nodoc: + case fn + when GLOB_PATTERN + add_matching(fn) + else + self << fn + end + end + private :resolve_add + + def resolve_exclude # :nodoc: + reject! { |fn| excluded_from_list?(fn) } + self + end + private :resolve_exclude + + # Return a new FileList with the results of running +sub+ against each + # element of the original list. + # + # Example: + # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] + # + def sub(pat, rep) + inject(self.class.new) { |res, fn| res << fn.sub(pat, rep) } + end + + # Return a new FileList with the results of running +gsub+ against each + # element of the original list. + # + # Example: + # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\") + # => ['lib\\test\\file', 'x\\y'] + # + def gsub(pat, rep) + inject(self.class.new) { |res, fn| res << fn.gsub(pat, rep) } + end + + # Same as +sub+ except that the original file list is modified. + def sub!(pat, rep) + each_with_index { |fn, i| self[i] = fn.sub(pat, rep) } + self + end + + # Same as +gsub+ except that the original file list is modified. + def gsub!(pat, rep) + each_with_index { |fn, i| self[i] = fn.gsub(pat, rep) } + self + end + + # Apply the pathmap spec to each of the included file names, returning a + # new file list with the modified paths. (See String#pathmap for + # details.) + def pathmap(spec=nil, &block) + collect { |fn| fn.pathmap(spec, &block) } + end + + # Return a new FileList with String#ext method applied to + # each member of the array. + # + # This method is a shortcut for: + # + # array.collect { |item| item.ext(newext) } + # + # +ext+ is a user added method for the Array class. + def ext(newext="") + collect { |fn| fn.ext(newext) } + end + + # Grep each of the files in the filelist using the given pattern. If a + # block is given, call the block on each matching line, passing the file + # name, line number, and the matching line of text. If no block is given, + # a standard emacs style file:linenumber:line message will be printed to + # standard out. Returns the number of matched items. + def egrep(pattern, *options) + matched = 0 + each do |fn| + begin + File.open(fn, "r", *options) do |inf| + count = 0 + inf.each do |line| + count += 1 + if pattern.match(line) + matched += 1 + if block_given? + yield fn, count, line + else + puts "#{fn}:#{count}:#{line}" + end + end + end + end + rescue StandardError => ex + $stderr.puts "Error while processing '#{fn}': #{ex}" + end + end + matched + end + + # Return a new file list that only contains file names from the current + # file list that exist on the file system. + def existing + select { |fn| File.exist?(fn) }.uniq + end + + # Modify the current file list so that it contains only file name that + # exist on the file system. + def existing! + resolve + @items = @items.select { |fn| File.exist?(fn) }.uniq + self + end + + # FileList version of partition. Needed because the nested arrays should + # be FileLists in this version. + def partition(&block) # :nodoc: + resolve + result = @items.partition(&block) + [ + self.class.new.import(result[0]), + self.class.new.import(result[1]), + ] + end + + # Convert a FileList to a string by joining all elements with a space. + def to_s + resolve + self.join(" ") + end + + # Add matching glob patterns. + def add_matching(pattern) + self.class.glob(pattern).each do |fn| + self << fn unless excluded_from_list?(fn) + end + end + private :add_matching + + # Should the given file name be excluded from the list? + # + # NOTE: This method was formerly named "exclude?", but Rails + # introduced an exclude? method as an array method and setup a + # conflict with file list. We renamed the method to avoid + # confusion. If you were using "FileList#exclude?" in your user + # code, you will need to update. + def excluded_from_list?(fn) + return true if @exclude_patterns.any? do |pat| + case pat + when Regexp + fn =~ pat + when GLOB_PATTERN + flags = File::FNM_PATHNAME + # Ruby <= 1.9.3 does not support File::FNM_EXTGLOB + flags |= File::FNM_EXTGLOB if defined? File::FNM_EXTGLOB + File.fnmatch?(pat, fn, flags) + else + fn == pat + end + end + @exclude_procs.any? { |p| p.call(fn) } + end + + DEFAULT_IGNORE_PATTERNS = [ + /(^|[\/\\])CVS([\/\\]|$)/, + /(^|[\/\\])\.svn([\/\\]|$)/, + /\.bak$/, + /~$/ + ] + DEFAULT_IGNORE_PROCS = [ + proc { |fn| fn =~ /(^|[\/\\])core$/ && !File.directory?(fn) } + ] + + def import(array) # :nodoc: + @items = array + self + end + + class << self + # Create a new file list including the files listed. Similar to: + # + # FileList.new(*args) + def [](*args) + new(*args) + end + + # Get a sorted list of files matching the pattern. This method + # should be preferred to Dir[pattern] and Dir.glob(pattern) because + # the files returned are guaranteed to be sorted. + def glob(pattern, *args) + Dir.glob(pattern, *args).sort + end + end + end +end + +module Rake + class << self + + # Yield each file or directory component. + def each_dir_parent(dir) # :nodoc: + old_length = nil + while dir != "." && dir.length != old_length + yield(dir) + old_length = dir.length + dir = File.dirname(dir) + end + end + + # Convert Pathname and Pathname-like objects to strings; + # leave everything else alone + def from_pathname(path) # :nodoc: + path = path.to_path if path.respond_to?(:to_path) + path = path.to_str if path.respond_to?(:to_str) + path + end + end +end # module Rake diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_task.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_task.rb new file mode 100644 index 0000000000..db790e39f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_task.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require "rake/task" +require "rake/early_time" + +module Rake + + # A FileTask is a task that includes time based dependencies. If any of a + # FileTask's prerequisites have a timestamp that is later than the file + # represented by this task, then the file must be rebuilt (using the + # supplied actions). + # + class FileTask < Task + + # Is this file task needed? Yes if it doesn't exist, or if its time stamp + # is out of date. + def needed? + !File.exist?(name) || out_of_date?(timestamp) || @application.options.build_all + end + + # Time stamp for file task. + def timestamp + if File.exist?(name) + File.mtime(name.to_s) + else + Rake::LATE + end + end + + private + + # Are there any prerequisites with a later time than the given time stamp? + def out_of_date?(stamp) + all_prerequisite_tasks.any? { |prereq| + prereq_task = application[prereq, @scope] + if prereq_task.instance_of?(Rake::FileTask) + prereq_task.timestamp > stamp || @application.options.build_all + else + prereq_task.timestamp > stamp + end + } + end + + # ---------------------------------------------------------------- + # Task class methods. + # + class << self + # Apply the scope to the task name according to the rules for this kind + # of task. File based tasks ignore the scope when creating the name. + def scope_name(scope, task_name) + Rake.from_pathname(task_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils.rb new file mode 100644 index 0000000000..e979eedb2d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true +require "rbconfig" +require "fileutils" + +#-- +# This a FileUtils extension that defines several additional commands to be +# added to the FileUtils utility functions. +module FileUtils + # Path to the currently running Ruby program + RUBY = ENV["RUBY"] || File.join( + RbConfig::CONFIG["bindir"], + RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]). + sub(/.*\s.*/m, '"\&"') + + # Run the system command +cmd+. If multiple arguments are given the command + # is run directly (without the shell, same semantics as Kernel::exec and + # Kernel::system). + # + # It is recommended you use the multiple argument form over interpolating + # user input for both usability and security reasons. With the multiple + # argument form you can easily process files with spaces or other shell + # reserved characters in them. With the multiple argument form your rake + # tasks are not vulnerable to users providing an argument like + # ; rm # -rf /. + # + # If a block is given, upon command completion the block is called with an + # OK flag (true on a zero exit status) and a Process::Status object. + # Without a block a RuntimeError is raised when the command exits non-zero. + # + # Examples: + # + # sh 'ls -ltr' + # + # sh 'ls', 'file with spaces' + # + # # check exit status after command runs + # sh %{grep pattern file} do |ok, res| + # if !ok + # puts "pattern not found (status = #{res.exitstatus})" + # end + # end + # + def sh(*cmd, &block) + options = (Hash === cmd.last) ? cmd.pop : {} + shell_runner = block_given? ? block : create_shell_runner(cmd) + + set_verbose_option(options) + verbose = options.delete :verbose + noop = options.delete(:noop) || Rake::FileUtilsExt.nowrite_flag + + Rake.rake_output_message sh_show_command cmd if verbose + + unless noop + res = (Hash === cmd.last) ? system(*cmd) : system(*cmd, options) + status = $? + status = Rake::PseudoStatus.new(1) if !res && status.nil? + shell_runner.call(res, status) + end + end + + def create_shell_runner(cmd) # :nodoc: + show_command = sh_show_command cmd + show_command = show_command[0, 42] + "..." unless $trace + + lambda do |ok, status| + ok or + fail "Command failed with status (#{status.exitstatus}): " + + "[#{show_command}]" + end + end + private :create_shell_runner + + def sh_show_command(cmd) # :nodoc: + cmd = cmd.dup + + if Hash === cmd.first + env = cmd.first + env = env.map { |name, value| "#{name}=#{value}" }.join " " + cmd[0] = env + end + + cmd.join " " + end + private :sh_show_command + + def set_verbose_option(options) # :nodoc: + unless options.key? :verbose + options[:verbose] = + (Rake::FileUtilsExt.verbose_flag == Rake::FileUtilsExt::DEFAULT) || + Rake::FileUtilsExt.verbose_flag + end + end + private :set_verbose_option + + # Run a Ruby interpreter with the given arguments. + # + # Example: + # ruby %{-pe '$_.upcase!' 1 + sh(RUBY, *args, **options, &block) + else + sh("#{RUBY} #{args.first}", **options, &block) + end + end + + LN_SUPPORTED = [true] + + # Attempt to do a normal file link, but fall back to a copy if the link + # fails. + def safe_ln(*args, **options) + if LN_SUPPORTED[0] + begin + return options.empty? ? ln(*args) : ln(*args, **options) + rescue StandardError, NotImplementedError + LN_SUPPORTED[0] = false + end + end + options.empty? ? cp(*args) : cp(*args, **options) + end + + # Split a file path into individual directory names. + # + # Example: + # split_all("a/b/c") => ['a', 'b', 'c'] + # + def split_all(path) + head, tail = File.split(path) + return [tail] if head == "." || tail == "/" + return [head, tail] if head == "/" + return split_all(head) + [tail] + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils_ext.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils_ext.rb new file mode 100644 index 0000000000..e91ad595f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/file_utils_ext.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true +require "rake/file_utils" + +module Rake + # + # FileUtilsExt provides a custom version of the FileUtils methods + # that respond to the verbose and nowrite + # commands. + # + module FileUtilsExt + include FileUtils + + class << self + attr_accessor :verbose_flag, :nowrite_flag + end + + DEFAULT = Object.new + + FileUtilsExt.verbose_flag = DEFAULT + FileUtilsExt.nowrite_flag = false + + FileUtils.commands.each do |name| + opts = FileUtils.options_of name + default_options = [] + if opts.include?("verbose") + default_options << "verbose: FileUtilsExt.verbose_flag" + end + if opts.include?("noop") + default_options << "noop: FileUtilsExt.nowrite_flag" + end + + next if default_options.empty? + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args, **options, &block) + super(*args, + #{default_options.join(', ')}, + **options, &block) + end + EOS + end + + # Get/set the verbose flag controlling output from the FileUtils + # utilities. If verbose is true, then the utility method is + # echoed to standard output. + # + # Examples: + # verbose # return the current value of the + # # verbose flag + # verbose(v) # set the verbose flag to _v_. + # verbose(v) { code } # Execute code with the verbose flag set + # # temporarily to _v_. Return to the + # # original value when code is done. + def verbose(value=nil) + oldvalue = FileUtilsExt.verbose_flag + FileUtilsExt.verbose_flag = value unless value.nil? + if block_given? + begin + yield + ensure + FileUtilsExt.verbose_flag = oldvalue + end + end + FileUtilsExt.verbose_flag + end + + # Get/set the nowrite flag controlling output from the FileUtils + # utilities. If verbose is true, then the utility method is + # echoed to standard output. + # + # Examples: + # nowrite # return the current value of the + # # nowrite flag + # nowrite(v) # set the nowrite flag to _v_. + # nowrite(v) { code } # Execute code with the nowrite flag set + # # temporarily to _v_. Return to the + # # original value when code is done. + def nowrite(value=nil) + oldvalue = FileUtilsExt.nowrite_flag + FileUtilsExt.nowrite_flag = value unless value.nil? + if block_given? + begin + yield + ensure + FileUtilsExt.nowrite_flag = oldvalue + end + end + oldvalue + end + + # Use this function to prevent potentially destructive ruby code + # from running when the :nowrite flag is set. + # + # Example: + # + # when_writing("Building Project") do + # project.build + # end + # + # The following code will build the project under normal + # conditions. If the nowrite(true) flag is set, then the example + # will print: + # + # DRYRUN: Building Project + # + # instead of actually building the project. + # + def when_writing(msg=nil) + if FileUtilsExt.nowrite_flag + $stderr.puts "DRYRUN: #{msg}" if msg + else + yield + end + end + + # Send the message to the default rake output (which is $stderr). + def rake_output_message(message) + $stderr.puts(message) + end + + # Check that the options do not contain options not listed in + # +optdecl+. An ArgumentError exception is thrown if non-declared + # options are found. + def rake_check_options(options, *optdecl) + h = options.dup + optdecl.each do |name| + h.delete name + end + raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless + h.empty? + end + + extend self + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_chain.rb new file mode 100644 index 0000000000..44a9954965 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_chain.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true +module Rake + + # InvocationChain tracks the chain of task invocations to detect + # circular dependencies. + class InvocationChain < LinkedList + + # Is the invocation already in the chain? + def member?(invocation) + head == invocation || tail.member?(invocation) + end + + # Append an invocation to the chain of invocations. It is an error + # if the invocation already listed. + def append(invocation) + if member?(invocation) + fail RuntimeError, "Circular dependency detected: #{to_s} => #{invocation}" + end + conj(invocation) + end + + # Convert to string, ie: TOP => invocation => invocation + def to_s + "#{prefix}#{head}" + end + + # Class level append. + def self.append(invocation, chain) + chain.append(invocation) + end + + private + + def prefix + "#{tail} => " + end + + # Null object for an empty chain. + class EmptyInvocationChain < LinkedList::EmptyLinkedList + @parent = InvocationChain + + def member?(obj) + false + end + + def append(invocation) + conj(invocation) + end + + def to_s + "TOP" + end + end + + EMPTY = EmptyInvocationChain.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_exception_mixin.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_exception_mixin.rb new file mode 100644 index 0000000000..b0d307a482 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/invocation_exception_mixin.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Rake + module InvocationExceptionMixin + # Return the invocation chain (list of Rake tasks) that were in + # effect when this exception was detected by rake. May be null if + # no tasks were active. + def chain + @rake_invocation_chain ||= nil + end + + # Set the invocation chain in effect when this exception was + # detected. + def chain=(value) + @rake_invocation_chain = value + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/late_time.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/late_time.rb new file mode 100644 index 0000000000..8fe024943b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/late_time.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +module Rake + # LateTime is a fake timestamp that occurs _after_ any other time value. + class LateTime + include Comparable + include Singleton + + def <=>(other) + 1 + end + + def to_s + "" + end + end + + LATE = LateTime.instance +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/linked_list.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/linked_list.rb new file mode 100644 index 0000000000..11fa46f0d3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/linked_list.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true +module Rake + + # Polylithic linked list structure used to implement several data + # structures in Rake. + class LinkedList + include Enumerable + attr_reader :head, :tail + + # Polymorphically add a new element to the head of a list. The + # type of head node will be the same list type as the tail. + def conj(item) + self.class.cons(item, self) + end + + # Is the list empty? + # .make guards against a list being empty making any instantiated LinkedList + # object not empty by default + # You should consider overriding this method if you implement your own .make method + def empty? + false + end + + # Lists are structurally equivalent. + def ==(other) + current = self + while !current.empty? && !other.empty? + return false if current.head != other.head + current = current.tail + other = other.tail + end + current.empty? && other.empty? + end + + # Convert to string: LL(item, item...) + def to_s + items = map(&:to_s).join(", ") + "LL(#{items})" + end + + # Same as +to_s+, but with inspected items. + def inspect + items = map(&:inspect).join(", ") + "LL(#{items})" + end + + # For each item in the list. + def each + current = self + while !current.empty? + yield(current.head) + current = current.tail + end + self + end + + # Make a list out of the given arguments. This method is + # polymorphic + def self.make(*args) + # return an EmptyLinkedList if there are no arguments + return empty if !args || args.empty? + + # build a LinkedList by starting at the tail and iterating + # through each argument + # inject takes an EmptyLinkedList to start + args.reverse.inject(empty) do |list, item| + list = cons(item, list) + list # return the newly created list for each item in the block + end + end + + # Cons a new head onto the tail list. + def self.cons(head, tail) + new(head, tail) + end + + # The standard empty list class for the given LinkedList class. + def self.empty + self::EMPTY + end + + protected + + def initialize(head, tail=EMPTY) + @head = head + @tail = tail + end + + # Represent an empty list, using the Null Object Pattern. + # + # When inheriting from the LinkedList class, you should implement + # a type specific Empty class as well. Make sure you set the class + # instance variable @parent to the associated list class (this + # allows conj, cons and make to work polymorphically). + class EmptyLinkedList < LinkedList + @parent = LinkedList + + def initialize + end + + def empty? + true + end + + def self.cons(head, tail) + @parent.cons(head, tail) + end + end + + EMPTY = EmptyLinkedList.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/loaders/makefile.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/loaders/makefile.rb new file mode 100644 index 0000000000..46f4beaad5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/loaders/makefile.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +module Rake + + # Makefile loader to be used with the import file loader. Use this to + # import dependencies from make dependency tools: + # + # require 'rake/loaders/makefile' + # + # file ".depends.mf" => [SRC_LIST] do |t| + # sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}" + # end + # + # import ".depends.mf" + # + # See {Importing Dependencies}[link:doc/rakefile_rdoc.html#label-Importing+Dependencies] + # for further details. + + class MakefileLoader + include Rake::DSL + + SPACE_MARK = "\0" # :nodoc: + + # Load the makefile dependencies in +fn+. + def load(fn) # :nodoc: + lines = File.read fn + lines.gsub!(/\\ /, SPACE_MARK) + lines.gsub!(/#[^\n]*\n/m, "") + lines.gsub!(/\\\n/, " ") + lines.each_line do |line| + process_line(line) + end + end + + private + + # Process one logical line of makefile data. + def process_line(line) # :nodoc: + file_tasks, args = line.split(":", 2) + return if args.nil? + dependents = args.split.map { |d| respace(d) } + file_tasks.scan(/\S+/) do |file_task| + file_task = respace(file_task) + file file_task => dependents + end + end + + def respace(str) # :nodoc: + str.tr SPACE_MARK, " " + end + end + + # Install the handler + Rake.application.add_loader("mf", MakefileLoader.new) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/multi_task.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/multi_task.rb new file mode 100644 index 0000000000..3ae363cbea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/multi_task.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +module Rake + + # Same as a regular task, but the immediate prerequisites are done in + # parallel using Ruby threads. + # + class MultiTask < Task + private + + def invoke_prerequisites(task_args, invocation_chain) # :nodoc: + invoke_prerequisites_concurrently(task_args, invocation_chain) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/name_space.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/name_space.rb new file mode 100644 index 0000000000..32f8139fc1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/name_space.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +## +# The NameSpace class will lookup task names in the scope defined by a +# +namespace+ command. + +class Rake::NameSpace + + ## + # Create a namespace lookup object using the given task manager + # and the list of scopes. + + def initialize(task_manager, scope_list) + @task_manager = task_manager + @scope = scope_list.dup + end + + ## + # Lookup a task named +name+ in the namespace. + + def [](name) + @task_manager.lookup(name, @scope) + end + + ## + # The scope of the namespace (a LinkedList) + + def scope + @scope.dup + end + + ## + # Return the list of tasks defined in this and nested namespaces. + + def tasks + @task_manager.tasks_in_scope(@scope) + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/packagetask.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/packagetask.rb new file mode 100644 index 0000000000..aeff81c29a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/packagetask.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true +# Define a package task library to aid in the definition of +# redistributable package files. + +require "rake" +require "rake/tasklib" + +module Rake + + # Create a packaging task that will package the project into + # distributable files (e.g zip archive or tar files). + # + # The PackageTask will create the following targets: + # + # +:package+ :: + # Create all the requested package files. + # + # +:clobber_package+ :: + # Delete all the package files. This target is automatically + # added to the main clobber target. + # + # +:repackage+ :: + # Rebuild the package files from scratch, even if they are not out + # of date. + # + # "package_dir/name-version.tgz" :: + # Create a gzipped tar package (if need_tar is true). + # + # "package_dir/name-version.tar.gz" :: + # Create a gzipped tar package (if need_tar_gz is true). + # + # "package_dir/name-version.tar.bz2" :: + # Create a bzip2'd tar package (if need_tar_bz2 is true). + # + # "package_dir/name-version.zip" :: + # Create a zip package archive (if need_zip is true). + # + # Example: + # + # Rake::PackageTask.new("rake", "1.2.3") do |p| + # p.need_tar = true + # p.package_files.include("lib/**/*.rb") + # end + # + class PackageTask < TaskLib + # Name of the package (from the GEM Spec). + attr_accessor :name + + # Version of the package (e.g. '1.3.2'). + attr_accessor :version + + # Directory used to store the package files (default is 'pkg'). + attr_accessor :package_dir + + # True if a gzipped tar file (tgz) should be produced (default is + # false). + attr_accessor :need_tar + + # True if a gzipped tar file (tar.gz) should be produced (default + # is false). + attr_accessor :need_tar_gz + + # True if a bzip2'd tar file (tar.bz2) should be produced (default + # is false). + attr_accessor :need_tar_bz2 + + # True if a xz'd tar file (tar.xz) should be produced (default is false) + attr_accessor :need_tar_xz + + # True if a zip file should be produced (default is false) + attr_accessor :need_zip + + # List of files to be included in the package. + attr_accessor :package_files + + # Tar command for gzipped or bzip2ed archives. The default is 'tar'. + attr_accessor :tar_command + + # Zip command for zipped archives. The default is 'zip'. + attr_accessor :zip_command + + # True if parent directory should be omited (default is false) + attr_accessor :without_parent_dir + + # Create a Package Task with the given name and version. Use +:noversion+ + # as the version to build a package without a version or to provide a + # fully-versioned package name. + + def initialize(name=nil, version=nil) + init(name, version) + yield self if block_given? + define unless name.nil? + end + + # Initialization that bypasses the "yield self" and "define" step. + def init(name, version) + @name = name + @version = version + @package_files = Rake::FileList.new + @package_dir = "pkg" + @need_tar = false + @need_tar_gz = false + @need_tar_bz2 = false + @need_tar_xz = false + @need_zip = false + @tar_command = "tar" + @zip_command = "zip" + @without_parent_dir = false + end + + # Create the tasks defined by this task library. + def define + fail "Version required (or :noversion)" if @version.nil? + @version = nil if :noversion == @version + + desc "Build all the packages" + task :package + + desc "Force a rebuild of the package files" + task repackage: [:clobber_package, :package] + + desc "Remove package products" + task :clobber_package do + rm_r package_dir rescue nil + end + + task clobber: [:clobber_package] + + [ + [need_tar, tgz_file, "z"], + [need_tar_gz, tar_gz_file, "z"], + [need_tar_bz2, tar_bz2_file, "j"], + [need_tar_xz, tar_xz_file, "J"] + ].each do |need, file, flag| + if need + task package: ["#{package_dir}/#{file}"] + file "#{package_dir}/#{file}" => + [package_dir_path] + package_files do + chdir(working_dir) { sh @tar_command, "#{flag}cvf", file, target_dir } + mv "#{package_dir_path}/#{target_dir}", package_dir if without_parent_dir + end + end + end + + if need_zip + task package: ["#{package_dir}/#{zip_file}"] + file "#{package_dir}/#{zip_file}" => + [package_dir_path] + package_files do + chdir(working_dir) { sh @zip_command, "-r", zip_file, target_dir } + mv "#{package_dir_path}/#{zip_file}", package_dir if without_parent_dir + end + end + + directory package_dir_path => @package_files do + @package_files.each do |fn| + f = File.join(package_dir_path, fn) + fdir = File.dirname(f) + mkdir_p(fdir) unless File.exist?(fdir) + if File.directory?(fn) + mkdir_p(f) + else + rm_f f + safe_ln(fn, f) + end + end + end + self + end + + # The name of this package + + def package_name + @version ? "#{@name}-#{@version}" : @name + end + + # The directory this package will be built in + + def package_dir_path + "#{package_dir}/#{package_name}" + end + + # The package name with .tgz added + + def tgz_file + "#{package_name}.tgz" + end + + # The package name with .tar.gz added + + def tar_gz_file + "#{package_name}.tar.gz" + end + + # The package name with .tar.bz2 added + + def tar_bz2_file + "#{package_name}.tar.bz2" + end + + # The package name with .tar.xz added + + def tar_xz_file + "#{package_name}.tar.xz" + end + + # The package name with .zip added + + def zip_file + "#{package_name}.zip" + end + + def working_dir + without_parent_dir ? package_dir_path : package_dir + end + + # target directory relative to working_dir + def target_dir + without_parent_dir ? "." : package_name + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/phony.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/phony.rb new file mode 100644 index 0000000000..8caa5de178 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/phony.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +# Defines a :phony task that you can use as a dependency. This allows +# file-based tasks to use non-file-based tasks as prerequisites +# without forcing them to rebuild. +# +# See FileTask#out_of_date? and Task#timestamp for more info. + +require "rake" + +task :phony + +Rake::Task[:phony].tap do |task| + def task.timestamp # :nodoc: + Time.at 0 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/private_reader.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/private_reader.rb new file mode 100644 index 0000000000..2815ce6436 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/private_reader.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module Rake + + # Include PrivateReader to use +private_reader+. + module PrivateReader # :nodoc: all + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + + # Declare a list of private accessors + def private_reader(*names) + attr_reader(*names) + private(*names) + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/promise.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/promise.rb new file mode 100644 index 0000000000..f45af4f3a3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/promise.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true +module Rake + + # A Promise object represents a promise to do work (a chore) in the + # future. The promise is created with a block and a list of + # arguments for the block. Calling value will return the value of + # the promised chore. + # + # Used by ThreadPool. + # + class Promise # :nodoc: all + NOT_SET = Object.new.freeze # :nodoc: + + attr_accessor :recorder + + # Create a promise to do the chore specified by the block. + def initialize(args, &block) + @mutex = Mutex.new + @result = NOT_SET + @error = NOT_SET + @args = args + @block = block + end + + # Return the value of this promise. + # + # If the promised chore is not yet complete, then do the work + # synchronously. We will wait. + def value + unless complete? + stat :sleeping_on, item_id: object_id + @mutex.synchronize do + stat :has_lock_on, item_id: object_id + chore + stat :releasing_lock_on, item_id: object_id + end + end + error? ? raise(@error) : @result + end + + # If no one else is working this promise, go ahead and do the chore. + def work + stat :attempting_lock_on, item_id: object_id + if @mutex.try_lock + stat :has_lock_on, item_id: object_id + chore + stat :releasing_lock_on, item_id: object_id + @mutex.unlock + else + stat :bailed_on, item_id: object_id + end + end + + private + + # Perform the chore promised + def chore + if complete? + stat :found_completed, item_id: object_id + return + end + stat :will_execute, item_id: object_id + begin + @result = @block.call(*@args) + rescue Exception => e + @error = e + end + stat :did_execute, item_id: object_id + discard + end + + # Do we have a result for the promise + def result? + !@result.equal?(NOT_SET) + end + + # Did the promise throw an error + def error? + !@error.equal?(NOT_SET) + end + + # Are we done with the promise + def complete? + result? || error? + end + + # free up these items for the GC + def discard + @args = nil + @block = nil + end + + # Record execution statistics if there is a recorder + def stat(*args) + @recorder.call(*args) if @recorder + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/pseudo_status.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/pseudo_status.rb new file mode 100644 index 0000000000..8b3c98949c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/pseudo_status.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +module Rake + + ## + # Exit status class for times the system just gives us a nil. + class PseudoStatus # :nodoc: all + attr_reader :exitstatus + + def initialize(code=0) + @exitstatus = code + end + + def to_i + @exitstatus << 8 + end + + def >>(n) + to_i >> n + end + + def stopped? + false + end + + def exited? + true + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_module.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_module.rb new file mode 100644 index 0000000000..03c2956245 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_module.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +require "rake/application" + +module Rake + + class << self + # Current Rake Application + def application + @application ||= Rake::Application.new + end + + # Set the current Rake application object. + def application=(app) + @application = app + end + + def suggested_thread_count # :nodoc: + @cpu_count ||= Rake::CpuCounter.count + @cpu_count + 4 + end + + # Return the original directory where the Rake application was started. + def original_dir + application.original_dir + end + + # Load a rakefile. + def load_rakefile(path) + load(path) + end + + # Add files to the rakelib list + def add_rakelib(*files) + application.options.rakelib ||= [] + application.options.rakelib.concat(files) + end + + # Make +block_application+ the default rake application inside a block so + # you can load rakefiles into a different application. + # + # This is useful when you want to run rake tasks inside a library without + # running rake in a sub-shell. + # + # Example: + # + # Dir.chdir 'other/directory' + # + # other_rake = Rake.with_application do |rake| + # rake.load_rakefile + # end + # + # puts other_rake.tasks + + def with_application(block_application = Rake::Application.new) + orig_application = Rake.application + + Rake.application = block_application + + yield block_application + + block_application + ensure + Rake.application = orig_application + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb new file mode 100644 index 0000000000..3ecee5d855 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "rake/file_list" + +# Load the test files from the command line. +argv = ARGV.select do |argument| + case argument + when /^-/ then + argument + when /\*/ then + Rake::FileList[argument].to_a.each do |file| + require File.expand_path file + end + + false + else + path = File.expand_path argument + + abort "\nFile does not exist: #{path}\n\n" unless File.exist?(path) + + require path + + false + end +end + +ARGV.replace argv diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rule_recursion_overflow_error.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rule_recursion_overflow_error.rb new file mode 100644 index 0000000000..a51e77489b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rule_recursion_overflow_error.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +module Rake + + # Error indicating a recursion overflow error in task selection. + class RuleRecursionOverflowError < StandardError + def initialize(*args) + super + @targets = [] + end + + def add_target(target) + @targets << target + end + + def message + super + ": [" + @targets.reverse.join(" => ") + "]" + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/scope.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/scope.rb new file mode 100644 index 0000000000..fc1eb6c3a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/scope.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +module Rake + class Scope < LinkedList # :nodoc: all + + # Path for the scope. + def path + map(&:to_s).reverse.join(":") + end + + # Path for the scope + the named path. + def path_with_task_name(task_name) + "#{path}:#{task_name}" + end + + # Trim +n+ innermost scope levels from the scope. In no case will + # this trim beyond the toplevel scope. + def trim(n) + result = self + while n > 0 && !result.empty? + result = result.tail + n -= 1 + end + result + end + + # Scope lists always end with an EmptyScope object. See Null + # Object Pattern) + class EmptyScope < EmptyLinkedList + @parent = Scope + + def path + "" + end + + def path_with_task_name(task_name) + task_name + end + end + + # Singleton null object for an empty scope. + EMPTY = EmptyScope.new + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb new file mode 100644 index 0000000000..ec2c756e03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb @@ -0,0 +1,434 @@ +# frozen_string_literal: true +require "rake/invocation_exception_mixin" + +module Rake + + ## + # A Task is the basic unit of work in a Rakefile. Tasks have associated + # actions (possibly more than one) and a list of prerequisites. When + # invoked, a task will first ensure that all of its prerequisites have an + # opportunity to run and then it will execute its own actions. + # + # Tasks are not usually created directly using the new method, but rather + # use the +file+ and +task+ convenience methods. + # + class Task + # List of prerequisites for a task. + attr_reader :prerequisites + alias prereqs prerequisites + + # List of order only prerequisites for a task. + attr_reader :order_only_prerequisites + + # List of actions attached to a task. + attr_reader :actions + + # Application owning this task. + attr_accessor :application + + # Array of nested namespaces names used for task lookup by this task. + attr_reader :scope + + # File/Line locations of each of the task definitions for this + # task (only valid if the task was defined with the detect + # location option set). + attr_reader :locations + + # Has this task already been invoked? Already invoked tasks + # will be skipped unless you reenable them. + attr_reader :already_invoked + + # Return task name + def to_s + name + end + + def inspect # :nodoc: + "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>" + end + + # List of sources for task. + attr_writer :sources + def sources + if defined?(@sources) + @sources + else + prerequisites + end + end + + # List of prerequisite tasks + def prerequisite_tasks + (prerequisites + order_only_prerequisites).map { |pre| lookup_prerequisite(pre) } + end + + def lookup_prerequisite(prerequisite_name) # :nodoc: + scoped_prerequisite_task = application[prerequisite_name, @scope] + if scoped_prerequisite_task == self + unscoped_prerequisite_task = application[prerequisite_name] + end + unscoped_prerequisite_task || scoped_prerequisite_task + end + private :lookup_prerequisite + + # List of all unique prerequisite tasks including prerequisite tasks' + # prerequisites. + # Includes self when cyclic dependencies are found. + def all_prerequisite_tasks + seen = {} + collect_prerequisites(seen) + seen.values + end + + def collect_prerequisites(seen) # :nodoc: + prerequisite_tasks.each do |pre| + next if seen[pre.name] + seen[pre.name] = pre + pre.collect_prerequisites(seen) + end + end + protected :collect_prerequisites + + # First source from a rule (nil if no sources) + def source + sources.first + end + + # Create a task named +task_name+ with no actions or prerequisites. Use + # +enhance+ to add actions and prerequisites. + def initialize(task_name, app) + @name = task_name.to_s + @prerequisites = [] + @actions = [] + @already_invoked = false + @comments = [] + @lock = Monitor.new + @application = app + @scope = app.current_scope + @arg_names = nil + @locations = [] + @invocation_exception = nil + @order_only_prerequisites = [] + end + + # Enhance a task with prerequisites or actions. Returns self. + def enhance(deps=nil, &block) + @prerequisites |= deps if deps + @actions << block if block_given? + self + end + + # Name of the task, including any namespace qualifiers. + def name + @name.to_s + end + + # Name of task with argument list description. + def name_with_args # :nodoc: + if arg_description + "#{name}#{arg_description}" + else + name + end + end + + # Argument description (nil if none). + def arg_description # :nodoc: + @arg_names ? "[#{arg_names.join(',')}]" : nil + end + + # Name of arguments for this task. + def arg_names + @arg_names || [] + end + + # Reenable the task, allowing its tasks to be executed if the task + # is invoked again. + def reenable + @already_invoked = false + @invocation_exception = nil + end + + # Clear the existing prerequisites, actions, comments, and arguments of a rake task. + def clear + clear_prerequisites + clear_actions + clear_comments + clear_args + self + end + + # Clear the existing prerequisites of a rake task. + def clear_prerequisites + prerequisites.clear + self + end + + # Clear the existing actions on a rake task. + def clear_actions + actions.clear + self + end + + # Clear the existing comments on a rake task. + def clear_comments + @comments = [] + self + end + + # Clear the existing arguments on a rake task. + def clear_args + @arg_names = nil + self + end + + # Invoke the task if it is needed. Prerequisites are invoked first. + def invoke(*args) + task_args = TaskArguments.new(arg_names, args) + invoke_with_call_chain(task_args, InvocationChain::EMPTY) + end + + # Same as invoke, but explicitly pass a call chain to detect + # circular dependencies. + # + # If multiple tasks depend on this + # one in parallel, they will all fail if the first execution of + # this task fails. + def invoke_with_call_chain(task_args, invocation_chain) + new_chain = Rake::InvocationChain.append(self, invocation_chain) + @lock.synchronize do + begin + if application.options.trace + application.trace "** Invoke #{name} #{format_trace_flags}" + end + + if @already_invoked + if @invocation_exception + if application.options.trace + application.trace "** Previous invocation of #{name} failed #{format_trace_flags}" + end + raise @invocation_exception + else + return + end + end + + @already_invoked = true + + invoke_prerequisites(task_args, new_chain) + execute(task_args) if needed? + rescue Exception => ex + add_chain_to(ex, new_chain) + @invocation_exception = ex + raise ex + end + end + end + protected :invoke_with_call_chain + + def add_chain_to(exception, new_chain) # :nodoc: + exception.extend(InvocationExceptionMixin) unless + exception.respond_to?(:chain) + exception.chain = new_chain if exception.chain.nil? + end + private :add_chain_to + + # Invoke all the prerequisites of a task. + def invoke_prerequisites(task_args, invocation_chain) # :nodoc: + if application.options.always_multitask + invoke_prerequisites_concurrently(task_args, invocation_chain) + else + prerequisite_tasks.each { |p| + prereq_args = task_args.new_scope(p.arg_names) + p.invoke_with_call_chain(prereq_args, invocation_chain) + } + end + end + + # Invoke all the prerequisites of a task in parallel. + def invoke_prerequisites_concurrently(task_args, invocation_chain)# :nodoc: + futures = prerequisite_tasks.map do |p| + prereq_args = task_args.new_scope(p.arg_names) + application.thread_pool.future(p) do |r| + r.invoke_with_call_chain(prereq_args, invocation_chain) + end + end + # Iterate in reverse to improve performance related to thread waiting and switching + futures.reverse_each(&:value) + end + + # Format the trace flags for display. + def format_trace_flags + flags = [] + flags << "first_time" unless @already_invoked + flags << "not_needed" unless needed? + flags.empty? ? "" : "(" + flags.join(", ") + ")" + end + private :format_trace_flags + + # Execute the actions associated with this task. + def execute(args=nil) + args ||= EMPTY_TASK_ARGS + if application.options.dryrun + application.trace "** Execute (dry run) #{name}" + return + end + application.trace "** Execute #{name}" if application.options.trace + application.enhance_with_matching_rule(name) if @actions.empty? + if opts = Hash.try_convert(args) and !opts.empty? + @actions.each { |act| act.call(self, args, **opts)} + else + @actions.each { |act| act.call(self, args)} + end + end + + # Is this task needed? + def needed? + true + end + + # Timestamp for this task. Basic tasks return the current time for their + # time stamp. Other tasks can be more sophisticated. + def timestamp + Time.now + end + + # Add a description to the task. The description can consist of an option + # argument list (enclosed brackets) and an optional comment. + def add_description(description) + return unless description + comment = description.strip + add_comment(comment) if comment && !comment.empty? + end + + def comment=(comment) # :nodoc: + add_comment(comment) + end + + def add_comment(comment) # :nodoc: + return if comment.nil? + @comments << comment unless @comments.include?(comment) + end + private :add_comment + + # Full collection of comments. Multiple comments are separated by + # newlines. + def full_comment + transform_comments("\n") + end + + # First line (or sentence) of all comments. Multiple comments are + # separated by a "/". + def comment + transform_comments(" / ") { |c| first_sentence(c) } + end + + # Transform the list of comments as specified by the block and + # join with the separator. + def transform_comments(separator, &block) + if @comments.empty? + nil + else + block ||= lambda { |c| c } + @comments.map(&block).join(separator) + end + end + private :transform_comments + + # Get the first sentence in a string. The sentence is terminated + # by the first period, exclamation mark, or the end of the line. + # Decimal points do not count as periods. + def first_sentence(string) + string.split(/(?<=\w)(\.|!)[ \t]|(\.$|!)|\n/).first + end + private :first_sentence + + # Set the names of the arguments for this task. +args+ should be + # an array of symbols, one for each argument name. + def set_arg_names(args) + @arg_names = args.map(&:to_sym) + end + + # Return a string describing the internal state of a task. Useful for + # debugging. + def investigation + result = "------------------------------\n".dup + result << "Investigating #{name}\n" + result << "class: #{self.class}\n" + result << "task needed: #{needed?}\n" + result << "timestamp: #{timestamp}\n" + result << "pre-requisites: \n" + prereqs = prerequisite_tasks + prereqs.sort! { |a, b| a.timestamp <=> b.timestamp } + prereqs.each do |p| + result << "--#{p.name} (#{p.timestamp})\n" + end + latest_prereq = prerequisite_tasks.map(&:timestamp).max + result << "latest-prerequisite time: #{latest_prereq}\n" + result << "................................\n\n" + return result + end + + # Format dependencies parameter to pass to task. + def self.format_deps(deps) + deps = [deps] unless deps.respond_to?(:to_ary) + deps.map { |d| Rake.from_pathname(d).to_s } + end + + # Add order only dependencies. + def |(deps) + @order_only_prerequisites |= Task.format_deps(deps) - @prerequisites + self + end + + # ---------------------------------------------------------------- + # Rake Module Methods + # + class << self + + # Clear the task list. This cause rake to immediately forget all the + # tasks that have been assigned. (Normally used in the unit tests.) + def clear + Rake.application.clear + end + + # List of all defined tasks. + def tasks + Rake.application.tasks + end + + # Return a task with the given name. If the task is not currently + # known, try to synthesize one from the defined rules. If no rules are + # found, but an existing file matches the task name, assume it is a file + # task with no dependencies or actions. + def [](task_name) + Rake.application[task_name] + end + + # TRUE if the task name is already defined. + def task_defined?(task_name) + Rake.application.lookup(task_name) != nil + end + + # Define a task given +args+ and an option block. If a rule with the + # given name already exists, the prerequisites and actions are added to + # the existing task. Returns the defined task. + def define_task(*args, &block) + Rake.application.define_task(self, *args, &block) + end + + # Define a rule for synthesizing tasks. + def create_rule(*args, &block) + Rake.application.create_rule(*args, &block) + end + + # Apply the scope to the task name according to the rules for + # this kind of task. Generic tasks will accept the scope as + # part of the name. + def scope_name(scope, task_name) + scope.path_with_task_name(task_name) + end + + end # class << Rake::Task + end # class Rake::Task +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_argument_error.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_argument_error.rb new file mode 100644 index 0000000000..ef20076c6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_argument_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Rake + + # Error indicating an ill-formed task declaration. + class TaskArgumentError < ArgumentError + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_arguments.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_arguments.rb new file mode 100644 index 0000000000..0d3001afd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_arguments.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true +module Rake + + ## + # TaskArguments manage the arguments passed to a task. + # + class TaskArguments + include Enumerable + + # Argument names + attr_reader :names + + # Create a TaskArgument object with a list of argument +names+ and a set + # of associated +values+. +parent+ is the parent argument object. + def initialize(names, values, parent=nil) + @names = names + @parent = parent + @hash = {} + @values = values + names.each_with_index { |name, i| + next if values[i].nil? || values[i] == "" + @hash[name.to_sym] = values[i] + } + end + + # Retrieve the complete array of sequential values + def to_a + @values.dup + end + + # Retrieve the list of values not associated with named arguments + def extras + @values[@names.length..-1] || [] + end + + # Create a new argument scope using the prerequisite argument + # names. + def new_scope(names) + values = names.map { |n| self[n] } + self.class.new(names, values + extras, self) + end + + # Find an argument value by name or index. + def [](index) + lookup(index.to_sym) + end + + # Specify a hash of default values for task arguments. Use the + # defaults only if there is no specific value for the given + # argument. + def with_defaults(defaults) + @hash = defaults.merge(@hash) + end + + # Enumerates the arguments and their values + def each(&block) + @hash.each(&block) + end + + # Extracts the argument values at +keys+ + def values_at(*keys) + keys.map { |k| lookup(k) } + end + + # Returns the value of the given argument via method_missing + def method_missing(sym, *args) + lookup(sym.to_sym) + end + + # Returns a Hash of arguments and their values + def to_hash + @hash.dup + end + + def to_s # :nodoc: + inspect + end + + def inspect # :nodoc: + inspection = @hash.map do |k,v| + "#{k.to_s}: #{v.to_s}" + end.join(", ") + + "#<#{self.class} #{inspection}>" + end + + # Returns true if +key+ is one of the arguments + def has_key?(key) + @hash.has_key?(key) + end + alias key? has_key? + + def fetch(*args, &block) + @hash.fetch(*args, &block) + end + + protected + + def lookup(name) # :nodoc: + if @hash.has_key?(name) + @hash[name] + elsif @parent + @parent.lookup(name) + end + end + end + + EMPTY_TASK_ARGS = TaskArguments.new([], []) # :nodoc: +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_manager.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_manager.rb new file mode 100644 index 0000000000..0db5c241e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task_manager.rb @@ -0,0 +1,331 @@ +# frozen_string_literal: true +module Rake + + # The TaskManager module is a mixin for managing tasks. + module TaskManager + # Track the last comment made in the Rakefile. + attr_accessor :last_description + + def initialize # :nodoc: + super + @tasks = Hash.new + @rules = Array.new + @scope = Scope.make + @last_description = nil + end + + def create_rule(*args, &block) # :nodoc: + pattern, args, deps, order_only = resolve_args(args) + pattern = Regexp.new(Regexp.quote(pattern) + "$") if String === pattern + @rules << [pattern, args, deps, order_only, block] + end + + def define_task(task_class, *args, &block) # :nodoc: + task_name, arg_names, deps, order_only = resolve_args(args) + + original_scope = @scope + if String === task_name and + not task_class.ancestors.include? Rake::FileTask + task_name, *definition_scope = *(task_name.split(":").reverse) + @scope = Scope.make(*(definition_scope + @scope.to_a)) + end + + task_name = task_class.scope_name(@scope, task_name) + task = intern(task_class, task_name) + task.set_arg_names(arg_names) unless arg_names.empty? + if Rake::TaskManager.record_task_metadata + add_location(task) + task.add_description(get_description(task)) + end + task.enhance(Task.format_deps(deps), &block) + task | order_only unless order_only.nil? + task + ensure + @scope = original_scope + end + + # Lookup a task. Return an existing task if found, otherwise + # create a task of the current type. + def intern(task_class, task_name) + @tasks[task_name.to_s] ||= task_class.new(task_name, self) + end + + # Find a matching task for +task_name+. + def [](task_name, scopes=nil) + task_name = task_name.to_s + self.lookup(task_name, scopes) or + enhance_with_matching_rule(task_name) or + synthesize_file_task(task_name) or + fail generate_message_for_undefined_task(task_name) + end + + def generate_message_for_undefined_task(task_name) + message = "Don't know how to build task '#{task_name}' "\ + "(See the list of available tasks with `#{Rake.application.name} --tasks`)" + message + generate_did_you_mean_suggestions(task_name) + end + + def generate_did_you_mean_suggestions(task_name) + return "" unless defined?(::DidYouMean::SpellChecker) + + suggestions = ::DidYouMean::SpellChecker.new(dictionary: @tasks.keys).correct(task_name.to_s) + if ::DidYouMean.respond_to?(:formatter)# did_you_mean v1.2.0 or later + ::DidYouMean.formatter.message_for(suggestions) + elsif defined?(::DidYouMean::Formatter) # before did_you_mean v1.2.0 + ::DidYouMean::Formatter.new(suggestions).to_s + else + "" + end + end + + def synthesize_file_task(task_name) # :nodoc: + return nil unless File.exist?(task_name) + define_task(Rake::FileTask, task_name) + end + + # Resolve the arguments for a task/rule. Returns a tuple of + # [task_name, arg_name_list, prerequisites, order_only_prerequisites]. + def resolve_args(args) + if args.last.is_a?(Hash) + deps = args.pop + resolve_args_with_dependencies(args, deps) + else + resolve_args_without_dependencies(args) + end + end + + # Resolve task arguments for a task or rule when there are no + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t + # task :t, [:a] + # + def resolve_args_without_dependencies(args) + task_name = args.shift + if args.size == 1 && args.first.respond_to?(:to_ary) + arg_names = args.first.to_ary + else + arg_names = args + end + [task_name, arg_names, [], nil] + end + private :resolve_args_without_dependencies + + # Resolve task arguments for a task or rule when there are + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t, order_only: [:e] + # task :t => [:d] + # task :t => [:d], order_only: [:e] + # task :t, [a] => [:d] + # task :t, [a] => [:d], order_only: [:e] + # + def resolve_args_with_dependencies(args, hash) # :nodoc: + fail "Task Argument Error" if + hash.size != 1 && + (hash.size != 2 || !hash.key?(:order_only)) + order_only = hash.delete(:order_only) + key, value = hash.map { |k, v| [k, v] }.first + if args.empty? + task_name = key + arg_names = [] + deps = value || [] + else + task_name = args.shift + arg_names = key || args.shift|| [] + deps = value || [] + end + deps = [deps] unless deps.respond_to?(:to_ary) + [task_name, arg_names, deps, order_only] + end + private :resolve_args_with_dependencies + + # If a rule can be found that matches the task name, enhance the + # task with the prerequisites and actions from the rule. Set the + # source attribute of the task appropriately for the rule. Return + # the enhanced task or nil of no rule was found. + def enhance_with_matching_rule(task_name, level=0) + fail Rake::RuleRecursionOverflowError, + "Rule Recursion Too Deep" if level >= 16 + @rules.each do |pattern, args, extensions, order_only, block| + if pattern && pattern.match(task_name) + task = attempt_rule(task_name, pattern, args, extensions, block, level) + task | order_only unless order_only.nil? + return task if task + end + end + nil + rescue Rake::RuleRecursionOverflowError => ex + ex.add_target(task_name) + fail ex + end + + # List of all defined tasks in this application. + def tasks + @tasks.values.sort_by { |t| t.name } + end + + # List of all the tasks defined in the given scope (and its + # sub-scopes). + def tasks_in_scope(scope) + prefix = scope.path + tasks.select { |t| + /^#{prefix}:/ =~ t.name + } + end + + # Clear all tasks in this application. + def clear + @tasks.clear + @rules.clear + end + + # Lookup a task, using scope and the scope hints in the task name. + # This method performs straight lookups without trying to + # synthesize file tasks or rules. Special scope names (e.g. '^') + # are recognized. If no scope argument is supplied, use the + # current scope. Return nil if the task cannot be found. + def lookup(task_name, initial_scope=nil) + initial_scope ||= @scope + task_name = task_name.to_s + if task_name =~ /^rake:/ + scopes = Scope.make + task_name = task_name.sub(/^rake:/, "") + elsif task_name =~ /^(\^+)/ + scopes = initial_scope.trim($1.size) + task_name = task_name.sub(/^(\^+)/, "") + else + scopes = initial_scope + end + lookup_in_scope(task_name, scopes) + end + + # Lookup the task name + def lookup_in_scope(name, scope) + loop do + tn = scope.path_with_task_name(name) + task = @tasks[tn] + return task if task + break if scope.empty? + scope = scope.tail + end + nil + end + private :lookup_in_scope + + # Return the list of scope names currently active in the task + # manager. + def current_scope + @scope + end + + # Evaluate the block in a nested namespace named +name+. Create + # an anonymous namespace if +name+ is nil. + def in_namespace(name) + name ||= generate_name + @scope = Scope.new(name, @scope) + ns = NameSpace.new(self, @scope) + yield(ns) + ns + ensure + @scope = @scope.tail + end + + private + + # Add a location to the locations field of the given task. + def add_location(task) + loc = find_location + task.locations << loc if loc + task + end + + # Find the location that called into the dsl layer. + def find_location + locations = caller + i = 0 + while locations[i] + return locations[i + 1] if locations[i] =~ /rake\/dsl_definition.rb/ + i += 1 + end + nil + end + + # Generate an anonymous namespace name. + def generate_name + @seed ||= 0 + @seed += 1 + "_anon_#{@seed}" + end + + def trace_rule(level, message) # :nodoc: + options.trace_output.puts "#{" " * level}#{message}" if + Rake.application.options.trace_rules + end + + # Attempt to create a rule given the list of prerequisites. + def attempt_rule(task_name, task_pattern, args, extensions, block, level) + sources = make_sources(task_name, task_pattern, extensions) + prereqs = sources.map { |source| + trace_rule level, "Attempting Rule #{task_name} => #{source}" + if File.exist?(source) || Rake::Task.task_defined?(source) + trace_rule level, "(#{task_name} => #{source} ... EXIST)" + source + elsif parent = enhance_with_matching_rule(source, level + 1) + trace_rule level, "(#{task_name} => #{source} ... ENHANCE)" + parent.name + else + trace_rule level, "(#{task_name} => #{source} ... FAIL)" + return nil + end + } + task = FileTask.define_task(task_name, { args => prereqs }, &block) + task.sources = prereqs + task + end + + # Make a list of sources from the list of file name extensions / + # translation procs. + def make_sources(task_name, task_pattern, extensions) + result = extensions.map { |ext| + case ext + when /%/ + task_name.pathmap(ext) + when %r{/} + ext + when /^\./ + source = task_name.sub(task_pattern, ext) + source == ext ? task_name.ext(ext) : source + when String, Symbol + ext.to_s + when Proc, Method + if ext.arity == 1 + ext.call(task_name) + else + ext.call + end + else + fail "Don't know how to handle rule dependent: #{ext.inspect}" + end + } + result.flatten + end + + # Return the current description, clearing it in the process. + def get_description(task) + desc = @last_description + @last_description = nil + desc + end + + class << self + attr_accessor :record_task_metadata # :nodoc: + TaskManager.record_task_metadata = false + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/tasklib.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/tasklib.rb new file mode 100644 index 0000000000..5354b4f941 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/tasklib.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +require "rake" + +module Rake + + # Base class for Task Libraries. + class TaskLib + include Cloneable + include Rake::DSL + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/testtask.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/testtask.rb new file mode 100644 index 0000000000..56521d23d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/testtask.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true +require "rake" +require "rake/tasklib" + +module Rake + + # Create a task that runs a set of tests. + # + # Example: + # require "rake/testtask" + # + # Rake::TestTask.new do |t| + # t.libs << "test" + # t.test_files = FileList['test/test*.rb'] + # t.verbose = true + # end + # + # If rake is invoked with a "TEST=filename" command line option, + # then the list of test files will be overridden to include only the + # filename specified on the command line. This provides an easy way + # to run just one test. + # + # If rake is invoked with a "TESTOPTS=options" command line option, + # then the given options are passed to the test process after a + # '--'. This allows Test::Unit options to be passed to the test + # suite. + # + # Examples: + # + # rake test # run tests normally + # rake test TEST=just_one_file.rb # run just one test file. + # rake test TESTOPTS="-v" # run in verbose mode + # rake test TESTOPTS="--runner=fox" # use the fox test runner + # + class TestTask < TaskLib + + # Name of test task. (default is :test) + attr_accessor :name + + # List of directories added to $LOAD_PATH before running the + # tests. (default is 'lib') + attr_accessor :libs + + # True if verbose test output desired. (default is false) + attr_accessor :verbose + + # Test options passed to the test suite. An explicit + # TESTOPTS=opts on the command line will override this. (default + # is NONE) + attr_accessor :options + + # Request that the tests be run with the warning flag set. + # E.g. warning=true implies "ruby -w" used to run the tests. + # (default is true) + attr_accessor :warning + + # Glob pattern to match test files. (default is 'test/test*.rb') + attr_accessor :pattern + + # Style of test loader to use. Options are: + # + # * :rake -- Rake provided test loading script (default). + # * :testrb -- Ruby provided test loading script. + # * :direct -- Load tests using command line loader. + # + attr_accessor :loader + + # Array of command line options to pass to ruby when running test loader. + attr_accessor :ruby_opts + + # Description of the test task. (default is 'Run tests') + attr_accessor :description + + # Task prerequisites. + attr_accessor :deps + + # Explicitly define the list of test files to be included in a + # test. +list+ is expected to be an array of file names (a + # FileList is acceptable). If both +pattern+ and +test_files+ are + # used, then the list of test files is the union of the two. + def test_files=(list) + @test_files = list + end + + # Create a testing task. + def initialize(name=:test) + @name = name + @libs = ["lib"] + @pattern = nil + @options = nil + @test_files = nil + @verbose = false + @warning = true + @loader = :rake + @ruby_opts = [] + @description = "Run tests" + (@name == :test ? "" : " for #{@name}") + @deps = [] + if @name.is_a?(Hash) + @deps = @name.values.first + @name = @name.keys.first + end + yield self if block_given? + @pattern = "test/test*.rb" if @pattern.nil? && @test_files.nil? + define + end + + # Create the tasks defined by this task lib. + def define + desc @description + task @name => Array(deps) do + FileUtilsExt.verbose(@verbose) do + puts "Use TESTOPTS=\"--verbose\" to pass --verbose" \ + ", etc. to runners." if ARGV.include? "--verbose" + args = + "#{ruby_opts_string} #{run_code} " + + "#{file_list_string} #{option_list}" + ruby args do |ok, status| + if !ok && status.respond_to?(:signaled?) && status.signaled? + raise SignalException.new(status.termsig) + elsif !ok + status = "Command failed with status (#{status.exitstatus})" + details = ": [ruby #{args}]" + message = + if Rake.application.options.trace or @verbose + status + details + else + status + end + + fail message + end + end + end + end + self + end + + def option_list # :nodoc: + (ENV["TESTOPTS"] || + ENV["TESTOPT"] || + ENV["TEST_OPTS"] || + ENV["TEST_OPT"] || + @options || + "") + end + + def ruby_opts_string # :nodoc: + opts = @ruby_opts.dup + opts.unshift("-I\"#{lib_path}\"") unless @libs.empty? + opts.unshift("-w") if @warning + opts.join(" ") + end + + def lib_path # :nodoc: + @libs.join(File::PATH_SEPARATOR) + end + + def file_list_string # :nodoc: + file_list.map { |fn| "\"#{fn}\"" }.join(" ") + end + + def file_list # :nodoc: + if ENV["TEST"] + FileList[ENV["TEST"]] + else + result = [] + result += @test_files.to_a if @test_files + result += FileList[@pattern].to_a if @pattern + result + end + end + + def ruby_version # :nodoc: + RUBY_VERSION + end + + def run_code # :nodoc: + case @loader + when :direct + "-e \"ARGV.each{|f| require f}\"" + when :testrb + "-S testrb" + when :rake + "#{__dir__}/rake_test_loader.rb" + end + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_history_display.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_history_display.rb new file mode 100644 index 0000000000..412ea37be5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_history_display.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require "rake/private_reader" + +module Rake + + class ThreadHistoryDisplay # :nodoc: all + include Rake::PrivateReader + + private_reader :stats, :items, :threads + + def initialize(stats) + @stats = stats + @items = { _seq_: 1 } + @threads = { _seq_: "A" } + end + + def show + puts "Job History:" + stats.each do |stat| + stat[:data] ||= {} + rename(stat, :thread, threads) + rename(stat[:data], :item_id, items) + rename(stat[:data], :new_thread, threads) + rename(stat[:data], :deleted_thread, threads) + printf("%8d %2s %-20s %s\n", + (stat[:time] * 1_000_000).round, + stat[:thread], + stat[:event], + stat[:data].map do |k, v| "#{k}:#{v}" end.join(" ")) + end + end + + private + + def rename(hash, key, renames) + if hash && hash[key] + original = hash[key] + value = renames[original] + unless value + value = renames[:_seq_] + renames[:_seq_] = renames[:_seq_].succ + renames[original] = value + end + hash[key] = value + end + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_pool.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_pool.rb new file mode 100644 index 0000000000..3329566705 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/thread_pool.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require "rake/promise" + +module Rake + + class ThreadPool # :nodoc: all + + # Creates a ThreadPool object. The +thread_count+ parameter is the size + # of the pool. + def initialize(thread_count) + require "set" + @max_active_threads = [thread_count, 0].max + @threads = Set.new + @threads_mon = Monitor.new + @queue = Queue.new + @join_cond = @threads_mon.new_cond + + @history_start_time = nil + @history = [] + @history_mon = Monitor.new + @total_threads_in_play = 0 + end + + # Creates a future executed by the +ThreadPool+. + # + # The args are passed to the block when executing (similarly to + # Thread#new) The return value is an object representing + # a future which has been created and added to the queue in the + # pool. Sending #value to the object will sleep the + # current thread until the future is finished and will return the + # result (or raise an exception thrown from the future) + def future(*args, &block) + promise = Promise.new(args, &block) + promise.recorder = lambda { |*stats| stat(*stats) } + + @queue.enq promise + stat :queued, item_id: promise.object_id + start_thread + promise + end + + # Waits until the queue of futures is empty and all threads have exited. + def join + @threads_mon.synchronize do + begin + stat :joining + @join_cond.wait unless @threads.empty? + stat :joined + rescue Exception => e + stat :joined + $stderr.puts e + $stderr.print "Queue contains #{@queue.size} items. " + + "Thread pool contains #{@threads.count} threads\n" + $stderr.print "Current Thread #{Thread.current} status = " + + "#{Thread.current.status}\n" + $stderr.puts e.backtrace.join("\n") + @threads.each do |t| + $stderr.print "Thread #{t} status = #{t.status}\n" + $stderr.puts t.backtrace.join("\n") + end + raise e + end + end + end + + # Enable the gathering of history events. + def gather_history #:nodoc: + @history_start_time = Time.now if @history_start_time.nil? + end + + # Return a array of history events for the thread pool. + # + # History gathering must be enabled to be able to see the events + # (see #gather_history). Best to call this when the job is + # complete (i.e. after ThreadPool#join is called). + def history # :nodoc: + @history_mon.synchronize { @history.dup }. + sort_by { |i| i[:time] }. + each { |i| i[:time] -= @history_start_time } + end + + # Return a hash of always collected statistics for the thread pool. + def statistics # :nodoc: + { + total_threads_in_play: @total_threads_in_play, + max_active_threads: @max_active_threads, + } + end + + private + + # processes one item on the queue. Returns true if there was an + # item to process, false if there was no item + def process_queue_item #:nodoc: + return false if @queue.empty? + + # Even though we just asked if the queue was empty, it + # still could have had an item which by this statement + # is now gone. For this reason we pass true to Queue#deq + # because we will sleep indefinitely if it is empty. + promise = @queue.deq(true) + stat :dequeued, item_id: promise.object_id + promise.work + return true + + rescue ThreadError # this means the queue is empty + false + end + + def safe_thread_count + @threads_mon.synchronize do + @threads.count + end + end + + def start_thread # :nodoc: + @threads_mon.synchronize do + next unless @threads.count < @max_active_threads + + t = Thread.new do + begin + while safe_thread_count <= @max_active_threads + break unless process_queue_item + end + ensure + @threads_mon.synchronize do + @threads.delete Thread.current + stat :ended, thread_count: @threads.count + @join_cond.broadcast if @threads.empty? + end + end + end + + @threads << t + stat( + :spawned, + new_thread: t.object_id, + thread_count: @threads.count) + @total_threads_in_play = @threads.count if + @threads.count > @total_threads_in_play + end + end + + def stat(event, data=nil) # :nodoc: + return if @history_start_time.nil? + info = { + event: event, + data: data, + time: Time.now, + thread: Thread.current.object_id, + } + @history_mon.synchronize { @history << info } + end + + # for testing only + + def __queue__ # :nodoc: + @queue + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/trace_output.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/trace_output.rb new file mode 100644 index 0000000000..d713a09262 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/trace_output.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +module Rake + module TraceOutput # :nodoc: all + + # Write trace output to output stream +out+. + # + # The write is done as a single IO call (to print) to lessen the + # chance that the trace output is interrupted by other tasks also + # producing output. + def trace_on(out, *strings) + sep = $\ || "\n" + if strings.empty? + output = sep + else + output = strings.map { |s| + next if s.nil? + s.end_with?(sep) ? s : s + sep + }.join + end + out.print(output) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/version.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/version.rb new file mode 100644 index 0000000000..a0bd095c13 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Rake + VERSION = "13.0.6" + + module Version # :nodoc: all + MAJOR, MINOR, BUILD, *OTHER = Rake::VERSION.split "." + + NUMBERS = [MAJOR, MINOR, BUILD, *OTHER] + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/win32.rb b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/win32.rb new file mode 100644 index 0000000000..6e62031810 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/lib/rake/win32.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +require "rbconfig" + +module Rake + # Win 32 interface methods for Rake. Windows specific functionality + # will be placed here to collect that knowledge in one spot. + module Win32 # :nodoc: all + + # Error indicating a problem in locating the home directory on a + # Win32 system. + class Win32HomeError < RuntimeError + end + + class << self + # True if running on a windows system. + def windows? + RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw|[Ww]indows)! + end + + # The standard directory containing system wide rake files on + # Win 32 systems. Try the following environment variables (in + # order): + # + # * HOME + # * HOMEDRIVE + HOMEPATH + # * APPDATA + # * USERPROFILE + # + # If the above are not defined, the return nil. + def win32_system_dir #:nodoc: + win32_shared_path = ENV["HOME"] + if win32_shared_path.nil? && ENV["HOMEDRIVE"] && ENV["HOMEPATH"] + win32_shared_path = ENV["HOMEDRIVE"] + ENV["HOMEPATH"] + end + + win32_shared_path ||= ENV["APPDATA"] + win32_shared_path ||= ENV["USERPROFILE"] + raise Win32HomeError, + "Unable to determine home path environment variable." if + win32_shared_path.nil? or win32_shared_path.empty? + normalize(File.join(win32_shared_path, "Rake")) + end + + # Normalize a win32 path so that the slashes are all forward slashes. + def normalize(path) + path.gsub(/\\/, "/") + end + + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/rake.gemspec b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/rake.gemspec new file mode 100644 index 0000000000..20a1db4231 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rake-13.0.6/rake.gemspec @@ -0,0 +1,100 @@ +# frozen_string_literal: true +require_relative 'lib/rake/version' + +Gem::Specification.new do |s| + s.name = "rake".freeze + s.version = Rake::VERSION + s.authors = ["Hiroshi SHIBATA".freeze, "Eric Hodel".freeze, "Jim Weirich".freeze] + s.email = ["hsbt@ruby-lang.org".freeze, "drbrain@segment7.net".freeze, "".freeze] + + s.summary = "Rake is a Make-like program implemented in Ruby".freeze + s.description = <<-DESCRIPTION +Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. +Rake has the following features: + * Rakefiles (rake's version of Makefiles) are completely defined in standard Ruby syntax. + No XML files to edit. No quirky Makefile syntax to worry about (is that a tab or a space?) + * Users can specify tasks with prerequisites. + * Rake supports rule patterns to synthesize implicit tasks. + * Flexible FileLists that act like arrays but know about manipulating file names and paths. + * Supports parallel execution of tasks. + DESCRIPTION + s.homepage = "https://github.com/ruby/rake".freeze + s.licenses = ["MIT".freeze] + + s.metadata = { + "bug_tracker_uri" => "https://github.com/ruby/rake/issues", + "changelog_uri" => "https://github.com/ruby/rake/blob/v#{s.version}/History.rdoc", + "documentation_uri" => "https://ruby.github.io/rake", + "source_code_uri" => "https://github.com/ruby/rake/tree/v#{s.version}", + } + + s.files = [ + "History.rdoc", + "MIT-LICENSE", + "README.rdoc", + "doc/command_line_usage.rdoc", + "doc/example/Rakefile1", + "doc/example/Rakefile2", + "doc/example/a.c", + "doc/example/b.c", + "doc/example/main.c", + "doc/glossary.rdoc", + "doc/jamis.rb", + "doc/proto_rake.rdoc", + "doc/rake.1", + "doc/rakefile.rdoc", + "doc/rational.rdoc", + "exe/rake", + "lib/rake.rb", + "lib/rake/application.rb", + "lib/rake/backtrace.rb", + "lib/rake/clean.rb", + "lib/rake/cloneable.rb", + "lib/rake/cpu_counter.rb", + "lib/rake/default_loader.rb", + "lib/rake/dsl_definition.rb", + "lib/rake/early_time.rb", + "lib/rake/ext/core.rb", + "lib/rake/ext/string.rb", + "lib/rake/file_creation_task.rb", + "lib/rake/file_list.rb", + "lib/rake/file_task.rb", + "lib/rake/file_utils.rb", + "lib/rake/file_utils_ext.rb", + "lib/rake/invocation_chain.rb", + "lib/rake/invocation_exception_mixin.rb", + "lib/rake/late_time.rb", + "lib/rake/linked_list.rb", + "lib/rake/loaders/makefile.rb", + "lib/rake/multi_task.rb", + "lib/rake/name_space.rb", + "lib/rake/packagetask.rb", + "lib/rake/phony.rb", + "lib/rake/private_reader.rb", + "lib/rake/promise.rb", + "lib/rake/pseudo_status.rb", + "lib/rake/rake_module.rb", + "lib/rake/rake_test_loader.rb", + "lib/rake/rule_recursion_overflow_error.rb", + "lib/rake/scope.rb", + "lib/rake/task.rb", + "lib/rake/task_argument_error.rb", + "lib/rake/task_arguments.rb", + "lib/rake/task_manager.rb", + "lib/rake/tasklib.rb", + "lib/rake/testtask.rb", + "lib/rake/thread_history_display.rb", + "lib/rake/thread_pool.rb", + "lib/rake/trace_output.rb", + "lib/rake/version.rb", + "lib/rake/win32.rb", + "rake.gemspec" + ] + s.bindir = "exe" + s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } + s.require_paths = ["lib".freeze] + + s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze) + s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/.gitignore b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/.gitignore new file mode 100644 index 0000000000..25a0e9a7e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/.gitignore @@ -0,0 +1,13 @@ +*.gem +.DS_Store +.Trashes +.bundle +.com.apple.timemachine.supported +.fseventsd +.idea +.rbx +/ext/build +Desktop DB +Desktop DF +Gemfile.lock +pkg/* diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Gemfile new file mode 100644 index 0000000000..b4e2a20bb6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Guardfile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Guardfile new file mode 100644 index 0000000000..63a666e507 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Guardfile @@ -0,0 +1,8 @@ +# A sample Guardfile +# More info at http://github.com/guard/guard#readme + +guard :rspec do + watch(%r(^spec/(.*)_spec.rb)) + watch(%r(^lib/(.*)\.rb)) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/LICENSE.txt new file mode 100644 index 0000000000..b083ecdd16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2010-2014 Thibaud Guillaume-Gentil & Travis Tilley + +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.0.0/gems/rb-fsevent-0.11.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/README.md new file mode 100644 index 0000000000..3d87bcc6f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/README.md @@ -0,0 +1,260 @@ +[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thibaudgg/rb-fsevent) +[![endorse](https://api.coderwall.com/ttilley/endorsecount.png)](https://coderwall.com/ttilley) + +# rb-fsevent + +Very simple & usable Mac OSX FSEvents API + +* Signals are working (really) +* Tested on MRI 2.4.1, RBX 3.72, JRuby 1.7.26 and 9.1.8.0 +* Tested on 10.8 + +## HFS+ filename corruption bug + +There is a _very_ long-standing (since 2011) OSX bug where sometimes the filename metadata for HFS+ filesystems will get corrupted, resulting in some APIs returning one case for a file, and other APIs returning another. The result is that sometimes, _for no visible reason to the user_, fsevents would simply not work. As of rb-fsevent 0.9.5 this issue is properly detected and an insanely hacky (but effective) workaround is used that replaces the system `realpath()` with a custom implementation that should almost always return the same value as the kernel reporting (thus fixing fsevents). The major flaw in the workaround is that it may return the wrong path for hard links. + +Please note that this doesn't repair the underlying issue on disk. Other apps and libraries using fsevents will continue to break with no warning. There may be other issues unrelated to fsevents. + +__This bug is resolved in MacOS 10.12 and all users are strongly encouraged to upgrade.__ + +## Install + + gem install rb-fsevent + +### re-compilation + +rb-fsevent comes with a pre-compiled fsevent\_watch binary supporting x86\_64 on 10.9 and above. The binary is codesigned with my (Travis Tilley) Developer ID as an extra precaution when distributing pre-compiled code and contains an embedded plist describing its build environment. This should be sufficient for most users, but if you need to use rb-fsevent on 10.8 or lower then recompilation is necessary. This can be done by entering the installed gem's ext directory and running: + + MACOSX_DEPLOYMENT_TARGET="10.7" rake replace_exe + +The following ENV vars are recognized: + +* CC +* CFLAGS +* ARCHFLAGS +* MACOSX\_DEPLOYMENT\_TARGET +* FWDEBUG (enables debug mode, printing an obscene number of informational + messages to STDERR) + +### embedded plist + +You can retrieve the values in the embedded plist via the CLI: + + fsevent_watch --show-plist + +The output is essentially formatted as `"#{key}:\n #{value}\n"` to make it easier to read than plist style xml. The result looks like this: + + DTSDKName: + macosx10.5 + FSEWBuildTriple: + i386-apple-darwin10.8.0 + FSEWCC: + /usr/bin/gcc-4.2 + DTSDKPath: + /Developer/SDKs/MacOSX10.5.sdk + FSEWCCVersion: + i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) + FSEWCFLAGS: + -fconstant-cfstrings -fno-strict-aliasing -Wall -mmacosx-version-min=10.5 -O3 + +If, for some perverse reason, you prefer to look at the xml... it can be retrieved via: + + otool -s __TEXT __info_plist ./bin/fsevent_watch | grep ^0 | xxd -r - + +### codesign + +You can verify code signing information for a specific fsevent\_watch via: + + codesign -d -vvv ./bin/fsevent_watch + +If you're using the pre-compiled binary, then the output should contain something to the effect of: + + Authority=Developer ID Application: Travis Tilley + Authority=Developer ID Certification Authority + Authority=Apple Root CA + Timestamp=Dec 31, 2012 12:49:13 PM + +## Usage + +### Singular path + +```ruby +require 'rb-fsevent' + +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] + +fsevent = FSEvent.new +fsevent.watch paths do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as a Hash + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = {:latency => 1.5, :no_defer => true } + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as an Array + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = ['--latency', 1.5, '--no-defer'] + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Using _full_ event information + +```ruby +require 'rb-fsevent' +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |paths, event_meta| + event_meta['events'].each do |event| + puts "event ID: #{event['id']}" + puts "path: #{event['path']}" + puts "c flags: #{event['cflags']}" + puts "named flags: #{event['flags'].join(', ')}" + # named flags will include strings such as `ItemInodeMetaMod` or `OwnEvent` + end +end +fsevent.run +``` + +## Options + +When defining options using a hash or hash-like object, it gets checked for validity and converted to the appropriate fsevent\_watch commandline arguments array when the FSEvent class is instantiated. This is obviously the safest and preferred method of passing in options. + +You may, however, choose to pass in an array of commandline arguments as your options value and it will be passed on, unmodified, to the fsevent\_watch binary when called. + +So far, the following options are supported: + +* :latency => 0.5 # in seconds +* :no\_defer => true +* :watch\_root => true +* :since\_when => 18446744073709551615 # an FSEventStreamEventId +* :file\_events => true + +### Latency + +The :latency parameter determines how long the service should wait after the first event before passing that information along to the client. If your latency is set to 4 seconds, and 300 changes occur in the first three, then the callback will be fired only once. If latency is set to 0.1 in the exact same scenario, you will see that callback fire somewhere closer to between 25 and 30 times. + +Setting a higher latency value allows for more effective temporal coalescing, resulting in fewer callbacks and greater overall efficiency... at the cost of apparent responsiveness. Setting this to a reasonably high value (and NOT setting :no\_defer) is particularly well suited for background, daemon, or batch processing applications. + +Implementation note: It appears that FSEvents will only coalesce events from a maximum of 32 distinct subpaths, making the above completely accurate only when events are to fewer than 32 subpaths. Creating 300 files in one directory, for example, or 30 files in 10 subdirectories, but not 300 files within 300 subdirectories. In the latter case, you may receive 31 callbacks in one go after the latency period. As this appears to be an implementation detail, the number could potentially differ across OS revisions. It is entirely possible that this number is somehow configurable, but I have not yet discovered an accepted method of doing so. + +### NoDefer + +The :no\_defer option changes the behavior of the latency parameter completely. Rather than waiting for $latency period of time before sending along events in an attempt to coalesce a potential deluge ahead of time, that first event is sent along to the client immediately and is followed by a $latency period of silence before sending along any additional events that occurred within that period. + +This behavior is particularly useful for interactive applications where that feeling of apparent responsiveness is most important, but you still don't want to get overwhelmed by a series of events that occur in rapid succession. + +### WatchRoot + +The :watch\_root option allows for catching the scenario where you start watching "~/src/demo\_project" and either it is later renamed to "~/src/awesome\_sauce\_3000" or the path changes in such a manner that the original directory is now at "~/clients/foo/iteration4/demo\_project". + +Unfortunately, while this behavior is somewhat supported in the fsevent\_watch binary built as part of this project, support for passing across detailed metadata is not (yet). As a result, you would not receive the appropriate RootChanged event and be able to react appropriately. Also, since the C code doesn't open watched directories and retain that file descriptor as part of path-specific callback metadata, we are unable to issue an F\_GETPATH fcntl() to determine the directory's new path. + +Please do not use this option until proper support is added (or, even better, add it and submit a pull request). + +### SinceWhen + +The FSEventStreamEventId passed in to :since\_when is used as a base for reacting to historic events. Unfortunately, not only is the metadata for transitioning from historic to live events not currently passed along, but it is incorrectly passed as a change event on the root path, and only per-host event streams are currently supported. When using per-host event streams, the event IDs are not guaranteed to be unique or contiguous when shared volumes (firewire/USB/net/etc) are used on multiple macs. + +Please do not use this option until proper support is added, unless it's acceptable for you to receive that one fake event that's handled incorrectly when events transition from historical to live. Even in that scenario, there's no metadata available for determining the FSEventStreamEventId of the last received event. + +WARNING: passing in 0 as the parameter to :since\_when will return events for every directory modified since "the beginning of time". + +### FileEvents ### + +Prepare yourself for an obscene number of callbacks. Realistically, an "Atomic Save" could easily fire maybe 6 events for the combination of creating the new file, changing metadata/permissions, writing content, swapping out the old file for the new may itself result in multiple events being fired, and so forth. By the time you get the event for the temporary file being created as part of the atomic save, it will already be gone and swapped with the original file. This and issues of a similar nature have prevented me from adding the option to the ruby code despite the fsevent\_watch binary supporting file level events for quite some time now. Mountain Lion seems to be better at coalescing needless events, but that might just be my imagination. + +## Debugging output + +If the gem is re-compiled with the environment variable FWDEBUG set, then fsevent\_watch will be built with its various DEBUG sections defined, and the output to STDERR is truly verbose (and hopefully helpful in debugging your application and not just fsevent\_watch itself). If enough people find this to be directly useful when developing code that makes use of rb-fsevent, then it wouldn't be hard to clean this up and make it a feature enabled by a commandline argument instead. Until somebody files an issue, however, I will assume otherwise. + + append_path called for: /tmp/moo/cow/ + resolved path to: /private/tmp/moo/cow + + config.sinceWhen 18446744073709551615 + config.latency 0.300000 + config.flags 00000000 + config.paths + /private/tmp/moo/cow + + FSEventStreamRef @ 0x100108540: + allocator = 0x7fff705a4ee0 + callback = 0x10000151e + context = {0, 0x0, 0x0, 0x0, 0x0} + numPathsToWatch = 1 + pathsToWatch = 0x7fff705a4ee0 + pathsToWatch[0] = '/private/tmp/moo/cow' + latestEventId = -1 + latency = 300000 (microseconds) + flags = 0x00000000 + runLoop = 0x0 + runLoopMode = 0x0 + + FSEventStreamCallback fired! + numEvents: 32 + event path: /private/tmp/moo/cow/1/a/ + event flags: 00000000 + event ID: 1023767 + event path: /private/tmp/moo/cow/1/b/ + event flags: 00000000 + event ID: 1023782 + event path: /private/tmp/moo/cow/1/c/ + event flags: 00000000 + event ID: 1023797 + event path: /private/tmp/moo/cow/1/d/ + event flags: 00000000 + event ID: 1023812 + [etc] + + +## Development + +* Source hosted at [GitHub](http://github.com/thibaudgg/rb-fsevent) +* Report issues/Questions/Feature requests on [GitHub Issues](http://github.com/thibaudgg/rb-fsevent/issues) + +Pull requests are quite welcome! Please ensure that your commits are in a topic branch for each individual changeset that can be reasonably isolated. It is also important to ensure that your changes are well tested... whether that means new tests, modified tests, or fixing a scenario where the existing tests currently fail. If you have rbenv and ruby-build, we have a helper task for running the testsuite in all of them: + + rake spec:portability + +The list of tested targets is currently: + + %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + +## Authors + +* [Travis Tilley](http://github.com/ttilley) +* [Thibaud Guillaume-Gentil](http://github.com/thibaudgg) +* [Andrey Tarantsov](https://github.com/andreyvit) diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Rakefile new file mode 100644 index 0000000000..53a08a14f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/Rakefile @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +require 'bundler' +Bundler::GemHelper.install_tasks + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) +task :default => :spec + +namespace(:spec) do + desc "Run all specs on multiple ruby versions" + task(:portability) do + versions = %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + versions.each do |version| + # system <<-BASH + # bash -c 'source ~/.rvm/scripts/rvm; + # rvm #{version}; + # echo "--------- version #{version} ----------\n"; + # bundle install; + # rake spec' + # BASH + system <<-BASH + bash -c 'export PATH="$HOME/.rbenv/bin:$PATH"; + [[ `which rbenv` ]] && eval "$(rbenv init -)"; + [[ ! -a $HOME/.rbenv/versions/#{version} ]] && rbenv install #{version}; + rbenv shell #{version}; + rbenv which bundle 2> /dev/null || gem install bundler; + rm Gemfile.lock; + bundle install; + rake spec;' + BASH + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/bin/fsevent_watch b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/bin/fsevent_watch new file mode 100755 index 0000000000..4fb821d4a3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/bin/fsevent_watch differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/LICENSE b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/LICENSE new file mode 100644 index 0000000000..a35e1957dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011-2013 Travis Tilley + +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.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.c new file mode 100644 index 0000000000..60e3d37bd6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.c @@ -0,0 +1,626 @@ +/* + * FSEventsFix + * + * Resolves a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X released (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * Copyright (c) 2015 Andrey Tarantsov + * Copyright (c) 2003 Constantin S. Svintsoff + * + * 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. + * + * Based on a realpath implementation from Apple libc 498.1.7, taken from + * http://www.opensource.apple.com/source/Libc/Libc-498.1.7/stdlib/FreeBSD/realpath.c + * and provided under the following license: + * + * Copyright (c) 2003 Constantin S. Svintsoff + * + * 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. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * 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. + */ + + +#include "FSEventsFix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const char *const FSEventsFixVersionString = "0.11.0"; + + +#pragma mark - Forward declarations + +static char *(*orig_realpath)(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *CFURL_realpath(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *FSEventsFix_realpath_wrapper(const char *restrict src, char *restrict dst); + +static void _FSEventsFixHookInstall(); +static void _FSEventsFixHookUninstall(); + + +#pragma mark - Internal state + +static dispatch_queue_t g_queue = NULL; + +static int64_t g_enable_refcount = 0; + +static bool g_in_self_test = false; +static bool g_hook_operational = false; + +static void(^g_logging_block)(FSEventsFixMessageType type, const char *message); +static FSEventsFixDebugOptions g_debug_opt = 0; + +typedef struct { + char *name; + void *replacement; + void *original; + uint hooked_symbols; +} rebinding_t; + +static rebinding_t g_rebindings[] = { + { "_realpath$DARWIN_EXTSN", (void *) &FSEventsFix_realpath_wrapper, (void *) &realpath, 0 } +}; +static const uint g_rebindings_nel = sizeof(g_rebindings) / sizeof(g_rebindings[0]); + + +#pragma mark - Logging + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) __attribute__((__format__ (__printf__, 2, 3))); + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) { + if (g_logging_block) { + char *message = NULL; + va_list va; + va_start(va, fmt); + vasprintf(&message, fmt, va); + va_end(va); + + if (message) { + if (!!(g_debug_opt & FSEventsFixDebugOptionLogToStderr)) { + fprintf(stderr, "FSEventsFix: %s\n", message); + } + if (g_logging_block) { + g_logging_block(type, message); + } + free(message); + } + } +} + + +#pragma mark - API + +void _FSEventsFixInitialize() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + g_queue = dispatch_queue_create("FSEventsFix", DISPATCH_QUEUE_SERIAL); + }); +} + +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType severity, const char *message)) { + _FSEventsFixInitialize(); + loggingBlock = Block_copy(loggingBlock); + dispatch_sync(g_queue, ^{ + g_debug_opt = debugOptions; + g_logging_block = loggingBlock; + }); +} + +// Must be called from the private serial queue. +void _FSEventsFixSelfTest() { + g_in_self_test = true; + g_hook_operational = false; + static char result[1024]; + realpath("/Etc/__!FSEventsFixSelfTest!__", result); + g_in_self_test = false; +} + +void FSEventsFixEnable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (++g_enable_refcount == 1) { + orig_realpath = dlsym(RTLD_DEFAULT, "realpath"); + _FSEventsFixHookInstall(); + _FSEventsFixSelfTest(); + if (g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Enabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to enable (hook not called)"); + } + } + }); +} + +void FSEventsFixDisable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (g_enable_refcount == 0) { + abort(); + } + if (--g_enable_refcount == 0) { + _FSEventsFixHookUninstall(); + _FSEventsFixSelfTest(); + if (!g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Disabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to disable (hook still called)"); + } + } + }); +} + +bool FSEventsFixIsOperational() { + _FSEventsFixInitialize(); + __block bool result = false; + dispatch_sync(g_queue, ^{ + result = g_hook_operational; + }); + return result; +} + +bool _FSEventsFixIsBroken_noresolve(const char *resolved) { + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateBroken)) { + if (strstr(resolved, FSEventsFixSimulatedBrokenFolderMarker)) { + return true; + } + } + + char *reresolved = realpath(resolved, NULL); + if (reresolved) { + bool broken = (0 != strcmp(resolved, reresolved)); + free(reresolved); + return broken; + } else { + return true; + } +} + +bool FSEventsFixIsBroken(const char *path) { + char *resolved = CFURL_realpath(path, NULL); + if (!resolved) { + return true; + } + bool broken = _FSEventsFixIsBroken_noresolve(resolved); + free(resolved); + return broken; +} + +char *FSEventsFixCopyRootBrokenFolderPath(const char *inpath) { + if (!FSEventsFixIsBroken(inpath)) { + return NULL; + } + + // get a mutable copy of an absolute path + char *path = CFURL_realpath(inpath, NULL); + if (!path) { + return NULL; + } + + for (;;) { + char *sep = strrchr(path, '/'); + if ((sep == NULL) || (sep == path)) { + break; + } + *sep = 0; + if (!_FSEventsFixIsBroken_noresolve(path)) { + *sep = '/'; + break; + } + } + + return path; +} + +static void _FSEventsFixAttemptRepair(const char *folder) { + int rv = rename(folder, folder); + + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateRepair)) { + const char *pos = strstr(folder, FSEventsFixSimulatedBrokenFolderMarker); + if (pos) { + char *fixed = strdup(folder); + fixed[pos - folder] = 0; + strcat(fixed, pos + strlen(FSEventsFixSimulatedBrokenFolderMarker)); + + rv = rename(folder, fixed); + free(fixed); + } + } + + if (rv != 0) { + if (errno == EPERM) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Permission error when trying to repair '%s'", folder); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeExpectedFailure, "Unknown error when trying to repair '%s': errno = %d", folder, errno); + } + } +} + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *inpath) { + char *root = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (root == NULL) { + return FSEventsFixRepairStatusNotBroken; + } + + for (;;) { + _FSEventsFixAttemptRepair(root); + char *newRoot = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (newRoot == NULL) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Repaired '%s' in '%s'", root, inpath); + free(root); + return FSEventsFixRepairStatusRepaired; + } + if (0 == strcmp(root, newRoot)) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Failed to repair '%s' in '%s'", root, inpath); + free(root); + free(newRoot); + return FSEventsFixRepairStatusFailed; + } + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Partial success, repaired '%s' in '%s'", root, inpath); + free(root); + root = newRoot; + } +} + + +#pragma mark - FSEventsFix realpath wrapper + +static char *FSEventsFix_realpath_wrapper(const char * __restrict src, char * __restrict dst) { + if (g_in_self_test) { + if (strstr(src, "__!FSEventsFixSelfTest!__")) { + g_hook_operational = true; + } + } + + // CFURL_realpath doesn't support putting where resolution failed into the + // dst buffer, so we call the original realpath here first and if it gets a + // result, replace that with the output of CFURL_realpath. that way all the + // features of the original realpath are available. + char *rv = NULL; + char *orv = orig_realpath(src, dst); + if (orv != NULL) { rv = CFURL_realpath(src, dst); } + + if (!!(g_debug_opt & FSEventsFixDebugOptionLogCalls)) { + char *result = rv ?: dst; + _FSEventsFixLog(FSEventsFixMessageTypeCall, "realpath(%s) => %s\n", src, result); + } + + if (!!(g_debug_opt & FSEventsFixDebugOptionUppercaseReturn)) { + char *result = rv ?: dst; + if (result) { + for (char *pch = result; *pch; ++pch) { + *pch = (char)toupper(*pch); + } + } + } + + return rv; +} + + +#pragma mark - realpath + +// naive implementation of realpath on top of CFURL +// NOTE: doesn't quite support the full range of errno results one would +// expect here, in part because some of these functions just return a boolean, +// and in part because i'm not dealing with messy CFErrorRef objects and +// attempting to translate those to sane errno values. +// NOTE: the OSX realpath will return _where_ resolution failed in resolved_name +// if passed in and return NULL. we can't properly support that extension here +// since the resolution happens entirely behind the scenes to us in CFURL. +static char* CFURL_realpath(const char *file_name, char resolved_name[PATH_MAX]) +{ + char* resolved; + CFURLRef url1; + CFURLRef url2; + CFStringRef path; + + if (file_name == NULL) { + errno = EINVAL; + return (NULL); + } + +#if __DARWIN_UNIX03 + if (*file_name == 0) { + errno = ENOENT; + return (NULL); + } +#endif + + // create a buffer to store our result if we weren't passed one + if (!resolved_name) { + if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL); + } else { + resolved = resolved_name; + } + + url1 = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)file_name, (CFIndex)strlen(file_name), false); + if (url1 == NULL) { goto error_return; } + + url2 = CFURLCopyAbsoluteURL(url1); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + url1 = CFURLCreateFileReferenceURL(NULL, url2, NULL); + CFRelease(url2); + if (url1 == NULL) { goto error_return; } + + // if there are multiple hard links to the original path, this may end up + // being _completely_ different from what was intended + url2 = CFURLCreateFilePathURL(NULL, url1, NULL); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + path = CFURLCopyFileSystemPath(url2, kCFURLPOSIXPathStyle); + CFRelease(url2); + if (path == NULL) { goto error_return; } + + bool success = CFStringGetCString(path, resolved, PATH_MAX, kCFStringEncodingUTF8); + CFRelease(path); + if (!success) { goto error_return; } + + return resolved; + +error_return: + if (!resolved_name) { + // we weren't passed in an output buffer and created our own. free it + int e = errno; + free(resolved); + errno = e; + } + return (NULL); +} + + +#pragma mark - fishhook + +// Copyright (c) 2013, Facebook, Inc. +// 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 Facebook 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 COPYRIGHT HOLDER 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. + +#import +#import +#import +#import +#import +#import + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +static volatile bool g_hook_installed = false; + +static void _FSEventsFixHookUpdateSection(section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) +{ + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + if (strcmp(symbol_name, cur->name) == 0) { + if (g_hook_installed) { + if (indirect_symbol_bindings[i] != cur->replacement) { + indirect_symbol_bindings[i] = cur->replacement; + ++cur->hooked_symbols; + } + } else if (cur->original != NULL) { + if (indirect_symbol_bindings[i] == cur->replacement) { + indirect_symbol_bindings[i] = cur->original; + if (cur->hooked_symbols > 0) { + --cur->hooked_symbols; + } + } + } + goto symbol_loop; + } + } + symbol_loop:; + } +} + +static void _FSEventsFixHookUpdateImage(const struct mach_header *header, intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _FSEventsFixHookSaveOriginals() { + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + void *original = cur->original = dlsym(RTLD_DEFAULT, cur->name+1); + if (!original) { + const char *error = dlerror(); + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Cannot find symbol %s, dlsym says: %s\n", cur->name, error); + } + } +} + +static void _FSEventsFixHookUpdate() { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _FSEventsFixHookUpdateImage(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } +} + +static void _FSEventsFixHookInstall() { + static bool first_rebinding_done = false; + + if (!g_hook_installed) { + g_hook_installed = true; + + if (!first_rebinding_done) { + first_rebinding_done = true; + _FSEventsFixHookSaveOriginals(); + _dyld_register_func_for_add_image(_FSEventsFixHookUpdateImage); + } else { + _FSEventsFixHookUpdate(); + } + } +} + +static void _FSEventsFixHookUninstall() { + if (g_hook_installed) { + g_hook_installed = false; + _FSEventsFixHookUpdate(); + } +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.h new file mode 100644 index 0000000000..b70b8800c7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/FSEventsFix.h @@ -0,0 +1,105 @@ +/* + * FSEventsFix + * + * Works around a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X releases (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * See .c file for license & copyrights, but basically this is available under a mix + * of MIT and BSD licenses. + */ + +#ifndef __FSEventsFix__ +#define __FSEventsFix__ + +#include + +/// A library version string (e.g. 1.2.3) for displaying and logging purposes +extern const char *const FSEventsFixVersionString; + +/// See FSEventsFixDebugOptionSimulateBroken +#define FSEventsFixSimulatedBrokenFolderMarker "__!FSEventsBroken!__" + +typedef CF_OPTIONS(unsigned, FSEventsFixDebugOptions) { + /// Always return an uppercase string from realpath + FSEventsFixDebugOptionUppercaseReturn = 0x01, + + /// Log all calls to realpath using the logger configured via FSEventsFixConfigure + FSEventsFixDebugOptionLogCalls = 0x02, + + /// In addition to the logging block (if any), log everything to stderr + FSEventsFixDebugOptionLogToStderr = 0x08, + + /// Report paths containing FSEventsFixSimulatedBrokenFolderMarker as broken + FSEventsFixDebugOptionSimulateBroken = 0x10, + + /// Repair paths containing FSEventsFixSimulatedBrokenFolderMarker by renaming them + FSEventsFixDebugOptionSimulateRepair = 0x20, +}; + +typedef CF_ENUM(int, FSEventsFixMessageType) { + /// Call logging requested via FSEventsFixDebugOptionLogCalls + FSEventsFixMessageTypeCall, + + /// Results of actions like repair, and other pretty verbose, but notable, stuff. + FSEventsFixMessageTypeResult, + + /// Enabled/disabled status change + FSEventsFixMessageTypeStatusChange, + + /// Expected failure (treat as a warning) + FSEventsFixMessageTypeExpectedFailure, + + /// Severe failure that most likely means that the library won't work + FSEventsFixMessageTypeFatalError +}; + +typedef CF_ENUM(int, FSEventsFixRepairStatus) { + FSEventsFixRepairStatusNotBroken, + FSEventsFixRepairStatusRepaired, + FSEventsFixRepairStatusFailed, +}; + +/// Note that the logging block can be called on any dispatch queue. +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType type, const char *message)); + +void FSEventsFixEnable(); +void FSEventsFixDisable(); + +bool FSEventsFixIsOperational(); + +bool FSEventsFixIsBroken(const char *path); + +/// If the path is broken, returns a string identifying the root broken folder, +/// otherwise, returns NULL. You need to free() the returned string. +char *FSEventsFixCopyRootBrokenFolderPath(const char *path); + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *path); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.c new file mode 100644 index 0000000000..6e033d0638 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.c @@ -0,0 +1,373 @@ +// +// TSICTString.c +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#include "TSICTString.h" + + +const char* const TNetstringTypes = ",#^!~}]Z"; +const char* const OTNetstringTypes = ",#^!~{[Z"; +const UInt8 TNetstringSeparator = ':'; + +TSITStringFormat TSITStringDefaultFormat = kTSITStringFormatTNetstring; + +static const CFRange BeginningRange = {0,0}; + +static CFTypeID kCFDataTypeID = -1UL; +static CFTypeID kCFStringTypeID = -1UL; +static CFTypeID kCFNumberTypeID = -1UL; +static CFTypeID kCFBooleanTypeID = -1UL; +static CFTypeID kCFNullTypeID = -1UL; +static CFTypeID kCFArrayTypeID = -1UL; +static CFTypeID kCFDictionaryTypeID = -1UL; + + +__attribute__((constructor)) void Init_TSICTString(void) +{ + kCFDataTypeID = CFDataGetTypeID(); + kCFStringTypeID = CFStringGetTypeID(); + kCFNumberTypeID = CFNumberGetTypeID(); + kCFBooleanTypeID = CFBooleanGetTypeID(); + kCFNullTypeID = CFNullGetTypeID(); + kCFArrayTypeID = CFArrayGetTypeID(); + kCFDictionaryTypeID = CFDictionaryGetTypeID(); +} + + +void TSICTStringSetDefaultFormat(TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + TSITStringDefaultFormat = kTSITStringFormatTNetstring; + } else { + TSITStringDefaultFormat = format; + } +} + +TSITStringFormat TSICTStringGetDefaultFormat(void) +{ + return TSITStringDefaultFormat; +} + + +void TSICTStringDestroy(TStringIRep* rep) +{ + CFRelease(rep->data); + free(rep->length); + free(rep); +} + + +static inline TStringIRep* TSICTStringCreateWithDataOfTypeAndFormat(CFDataRef data, TSITStringTag type, TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + format = TSICTStringGetDefaultFormat(); + } + + TStringIRep* rep = calloc(1, sizeof(TStringIRep)); + rep->data = CFDataCreateCopy(kCFAllocatorDefault, data); + rep->type = type; + rep->format = format; + rep->length = calloc(10, sizeof(char)); + + CFIndex len = CFDataGetLength(rep->data); + if (snprintf(rep->length, 10, "%lu", len)) { + return rep; + } else { + TSICTStringDestroy(rep); + return NULL; + } +} + +static inline CFDataRef TSICTStringCreateDataFromIntermediateRepresentation(TStringIRep* rep) +{ + CFIndex len = CFDataGetLength(rep->data); + CFMutableDataRef buffer = CFDataCreateMutableCopy(kCFAllocatorDefault, (len + 12), rep->data); + UInt8* bufferBytes = CFDataGetMutableBytePtr(buffer); + + size_t prefixLength = strlen(rep->length) + 1; + CFDataReplaceBytes(buffer, BeginningRange, (const UInt8*)rep->length, (CFIndex)prefixLength); + + if (rep->format == kTSITStringFormatTNetstring) { + const UInt8 ftag = (UInt8)TNetstringTypes[rep->type]; + CFDataAppendBytes(buffer, &ftag, 1); + bufferBytes[(prefixLength - 1)] = TNetstringSeparator; + } else if (rep->format == kTSITStringFormatOTNetstring) { + const UInt8 ftag = (UInt8)OTNetstringTypes[rep->type]; + bufferBytes[(prefixLength - 1)] = ftag; + } + + CFDataRef dataRep = CFDataCreateCopy(kCFAllocatorDefault, buffer); + CFRelease(buffer); + + return dataRep; +} + +static inline CFStringRef TSICTStringCreateStringFromIntermediateRepresentation(TStringIRep* rep) +{ + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + CFStringRef string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8); + CFRelease(data); + return string; +} + +static inline void TSICTStringAppendObjectToMutableDataWithFormat(CFTypeRef object, CFMutableDataRef buffer, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* objRep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef objData = TSICTStringCreateDataFromIntermediateRepresentation(objRep); + CFDataAppendBytes(buffer, (CFDataGetBytePtr(objData)), CFDataGetLength(objData)); + CFRelease(objData); + TSICTStringDestroy(objRep); + + CFRelease(object); +} + +static void ArrayBufferAppendCallback(const void* item, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(item, buffer, format); +} + +static void DictionaryBufferAppendCallback(const void* key, const void* value, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(key, buffer, format); + TSICTStringAppendObjectToMutableDataWithFormat(value, buffer, format); +} + + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep) +{ + return TSICTStringCreateDataFromIntermediateRepresentation(rep); +} + +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return data; +} + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep) +{ + return TSICTStringCreateStringFromIntermediateRepresentation(rep); +} + +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFStringRef string = TSICTStringCreateStringFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return string; +} + + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + return TSICTStringCreateNullWithFormat(format); + } + CFRetain(object); + + CFTypeID cfType = CFGetTypeID(object); + TStringIRep* rep = NULL; + + if (cfType == kCFDataTypeID) { + rep = TSICTStringCreateWithDataOfTypeAndFormat(object, kTSITStringTagString, format); + } else if (cfType == kCFStringTypeID) { + rep = TSICTStringCreateWithStringAndFormat(object, format); + } else if (cfType == kCFNumberTypeID) { + rep = TSICTStringCreateWithNumberAndFormat(object, format); + } else if (cfType == kCFBooleanTypeID) { + if (CFBooleanGetValue(object)) { + rep = TSICTStringCreateTrueWithFormat(format); + } else { + rep = TSICTStringCreateFalseWithFormat(format); + } + } else if (cfType == kCFNullTypeID) { + rep = TSICTStringCreateNullWithFormat(format); + } else if (cfType == kCFArrayTypeID) { + rep = TSICTStringCreateWithArrayAndFormat(object, format); + } else if (cfType == kCFDictionaryTypeID) { + rep = TSICTStringCreateWithDictionaryAndFormat(object, format); + } else { + rep = TSICTStringCreateInvalidWithFormat(format); + } + + CFRelease(object); + return rep; +} + +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format) +{ + CFRetain(string); + CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, string, kCFStringEncodingUTF8, '?'); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagString, format); + CFRelease(data); + CFRelease(string); + return rep; +} + +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format) +{ + CFRetain(number); + TSITStringTag tag = kTSITStringTagNumber; + CFDataRef data; + CFNumberType numType = CFNumberGetType(number); + + switch(numType) { + case kCFNumberCharType: + { + int value; + if (CFNumberGetValue(number, kCFNumberIntType, &value)) { + if (value == 0 || value == 1) { + tag = kTSITStringTagBool; + } else { + tag = kTSITStringTagString; + } + } + break; + } + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + { + tag = kTSITStringTagFloat; + break; + } + } + + if (tag == kTSITStringTagBool) { + bool value; + CFNumberGetValue(number, kCFNumberIntType, &value); + if (value) { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + } else { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + } + } else if (tag == kTSITStringTagFloat) { + char buf[32]; + char *p, *e; + double value; + + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%#.15g", value); + + e = buf + strlen(buf); + p = e; + while (p[-1]=='0' && ('0' <= p[-2] && p[-2] <= '9')) { + p--; + } + memmove(p, e, strlen(e)+1); + + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } else { + char buf[32]; + SInt64 value; + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%lli", value); + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, tag, format); + CFRelease(data); + CFRelease(number); + return rep; +} + +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagNull, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagInvalid, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format) +{ + CFRetain(array); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + CFRange all = CFRangeMake(0, CFArrayGetCount(array)); + TStringCollectionCallbackContext cx = {buffer, format}; + CFArrayApplyFunction(array, all, ArrayBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagList, format); + CFRelease(buffer); + CFRelease(array); + return rep; +} + +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format) +{ + CFRetain(dictionary); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + TStringCollectionCallbackContext cx = {buffer, format}; + CFDictionaryApplyFunction(dictionary, DictionaryBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagDict, format); + CFRelease(buffer); + CFRelease(dictionary); + return rep; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.h new file mode 100644 index 0000000000..daf085c32f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/TSICTString.h @@ -0,0 +1,74 @@ +// +// TSICTString.h +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#ifndef TSICTString_H +#define TSICTString_H + +#include + + +typedef enum { + kTSITStringTagString = 0, + kTSITStringTagNumber = 1, + kTSITStringTagFloat = 2, + kTSITStringTagBool = 3, + kTSITStringTagNull = 4, + kTSITStringTagDict = 5, + kTSITStringTagList = 6, + kTSITStringTagInvalid = 7, +} TSITStringTag; + +extern const char* const TNetstringTypes; +extern const char* const OTNetstringTypes; +extern const UInt8 TNetstringSeparator; + +typedef enum { + kTSITStringFormatDefault = 0, + kTSITStringFormatOTNetstring = 1, + kTSITStringFormatTNetstring = 2, +} TSITStringFormat; + +extern TSITStringFormat TSITStringDefaultFormat; + +typedef struct TSITStringIntermediate { + CFDataRef data; + char* length; + TSITStringTag type; + TSITStringFormat format; +} TStringIRep; + +typedef struct { + CFMutableDataRef buffer; + TSITStringFormat format; +} TStringCollectionCallbackContext; + + +void Init_TSICTString(void); + +void TSICTStringSetDefaultFormat(TSITStringFormat format); +TSITStringFormat TSICTStringGetDefaultFormat(void); + +void TSICTStringDestroy(TStringIRep* rep); + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep); +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep); +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format); +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format); + + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.c new file mode 100644 index 0000000000..6d36dd13dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.c @@ -0,0 +1,201 @@ +#include +#include "cli.h" + +const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; +const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; +const char* cli_info_help[] = { + " -h, --help you're looking at it", + " -V, --version print version number and exit", + " -p, --show-plist display the embedded Info.plist values", + " -s, --since-when=EventID fire historical events since ID", + " -l, --latency=seconds latency period (default='0.5')", + " -n, --no-defer enable no-defer latency modifier", + " -r, --watch-root watch for when the root path has changed", + // " -i, --ignore-self ignore current process", + " -F, --file-events provide file level event data", + " -f, --format=name output format (classic, niw, \n" + " tnetstring, otnetstring)", + 0 +}; + +static void default_args (struct cli_info* args_info) +{ + args_info->since_when_arg = kFSEventStreamEventIdSinceNow; + args_info->latency_arg = 0.5; + args_info->no_defer_flag = false; + args_info->watch_root_flag = false; + args_info->ignore_self_flag = false; + args_info->file_events_flag = false; + args_info->mark_self_flag = false; + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; +} + +static void cli_parser_release (struct cli_info* args_info) +{ + unsigned int i; + + for (i=0; i < args_info->inputs_num; ++i) { + free(args_info->inputs[i]); + } + + if (args_info->inputs_num) { + free(args_info->inputs); + } + + args_info->inputs_num = 0; +} + +void cli_parser_init (struct cli_info* args_info) +{ + default_args(args_info); + + args_info->inputs = 0; + args_info->inputs_num = 0; +} + +void cli_parser_free (struct cli_info* args_info) +{ + cli_parser_release(args_info); +} + +static void cli_print_info_dict (const void *key, + const void *value, + void *context) +{ + CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, + CFSTR("%@:\n %@"), key, value); + if (entry) { + CFShow(entry); + CFRelease(entry); + } +} + +void cli_show_plist (void) +{ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFRetain(mainBundle); + CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); + if (mainBundleDict) { + CFRetain(mainBundleDict); + printf("Embedded Info.plist metadata:\n\n"); + CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); + CFRelease(mainBundleDict); + } + CFRelease(mainBundle); + printf("\n"); +} + +void cli_print_version (void) +{ + printf("%s %s\n\n", CLI_NAME, CLI_VERSION); +#ifdef COMPILED_AT + printf("Compiled at: %s\n", COMPILED_AT); +#endif +#ifdef COMPILER + printf("Compiled with: %s\n", COMPILER); +#endif +#ifdef TARGET_CPU + printf("Compiled for: %s\n", TARGET_CPU); +#endif + printf("\n"); +} + +void cli_print_help (void) +{ + cli_print_version(); + + printf("\n%s\n", cli_info_purpose); + printf("\n%s\n", cli_info_usage); + printf("\n"); + + int i = 0; + while (cli_info_help[i]) { + printf("%s\n", cli_info_help[i++]); + } +} + +int cli_parser (int argc, const char** argv, struct cli_info* args_info) +{ + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "show-plist", no_argument, NULL, 'p' }, + { "since-when", required_argument, NULL, 's' }, + { "latency", required_argument, NULL, 'l' }, + { "no-defer", no_argument, NULL, 'n' }, + { "watch-root", no_argument, NULL, 'r' }, + { "ignore-self", no_argument, NULL, 'i' }, + { "file-events", no_argument, NULL, 'F' }, + { "mark-self", no_argument, NULL, 'm' }, + { "format", required_argument, NULL, 'f' }, + { 0, 0, 0, 0 } + }; + + const char* shortopts = "hVps:l:nriFf:"; + + int c = -1; + + while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 's': // since-when + args_info->since_when_arg = strtoull(optarg, NULL, 0); + break; + case 'l': // latency + args_info->latency_arg = strtod(optarg, NULL); + break; + case 'n': // no-defer + args_info->no_defer_flag = true; + break; + case 'r': // watch-root + args_info->watch_root_flag = true; + break; + case 'i': // ignore-self + args_info->ignore_self_flag = true; + break; + case 'F': // file-events + args_info->file_events_flag = true; + break; + case 'm': // mark-self + args_info->mark_self_flag = true; + break; + case 'f': // format + if (strcmp(optarg, "classic") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatClassic; + } else if (strcmp(optarg, "niw") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatNIW; + } else if (strcmp(optarg, "tnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatTNetstring; + } else if (strcmp(optarg, "otnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; + } else { + fprintf(stderr, "Unknown output format: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'V': // version + cli_print_version(); + exit(EXIT_SUCCESS); + case 'p': // show-plist + cli_show_plist(); + exit(EXIT_SUCCESS); + case 'h': // help + case '?': // invalid option + case ':': // missing argument + cli_print_help(); + exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); + } + } + + if (optind < argc) { + int i = 0; + args_info->inputs_num = (unsigned int)(argc - optind); + args_info->inputs = + (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); + while (optind < argc) + if (argv[optind++] != argv[0]) { + args_info->inputs[i++] = strdup(argv[optind-1]); + } + } + + return EXIT_SUCCESS; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.h new file mode 100644 index 0000000000..2164995a5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/cli.h @@ -0,0 +1,45 @@ +#ifndef CLI_H +#define CLI_H + +#include "common.h" + +#ifndef CLI_NAME +#define CLI_NAME "fsevent_watch" +#endif /* CLI_NAME */ + +#ifndef PROJECT_VERSION +#error "PROJECT_VERSION not set" +#endif /* PROJECT_VERSION */ + +#ifndef CLI_VERSION +#define CLI_VERSION _xstr(PROJECT_VERSION) +#endif /* CLI_VERSION */ + + +struct cli_info { + UInt64 since_when_arg; + double latency_arg; + bool no_defer_flag; + bool watch_root_flag; + bool ignore_self_flag; + bool file_events_flag; + bool mark_self_flag; + enum FSEventWatchOutputFormat format_arg; + + char** inputs; + unsigned inputs_num; +}; + +extern const char* cli_info_purpose; +extern const char* cli_info_usage; +extern const char* cli_info_help[]; + +void cli_print_help(void); +void cli_print_version(void); + +int cli_parser (int argc, const char** argv, struct cli_info* args_info); +void cli_parser_init (struct cli_info* args_info); +void cli_parser_free (struct cli_info* args_info); + + +#endif /* CLI_H */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/common.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/common.h new file mode 100644 index 0000000000..b2d3e4ebf4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/common.h @@ -0,0 +1,22 @@ +#ifndef fsevent_watch_common_h +#define fsevent_watch_common_h + +#include +#ifdef __OBJC__ +#import +#endif + +#include +#include +#include "compat.h" +#include "defines.h" +#include "TSICTString.h" + +enum FSEventWatchOutputFormat { + kFSEventWatchOutputFormatClassic, + kFSEventWatchOutputFormatNIW, + kFSEventWatchOutputFormatTNetstring, + kFSEventWatchOutputFormatOTNetstring +}; + +#endif /* fsevent_watch_common_h */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.c new file mode 100644 index 0000000000..5f51baff11 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.c @@ -0,0 +1,41 @@ +#include "compat.h" + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; +FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; +FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; +FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink = 0x00100000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsLastHardlink = 0x00200000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData = 0x00000040; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned = 0x00400000; +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.h new file mode 100644 index 0000000000..757b413565 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/compat.h @@ -0,0 +1,100 @@ +/** + * @headerfile compat.h + * FSEventStream flag compatibility shim + * + * In order to compile a binary against an older SDK yet still support the + * features present in later OS releases, we need to define any missing enum + * constants not present in the older SDK. This allows us to safely defer + * feature detection to runtime (and avoid recompilation). + */ + + +#ifndef listen_fsevents_compat_h +#define listen_fsevents_compat_h + +#ifndef __CORESERVICES__ +#include +#endif // __CORESERVICES__ + +#ifndef __AVAILABILITY__ +#include +#endif // __AVAILABILITY__ + +#ifndef __MAC_10_6 +#define __MAC_10_6 1060 +#endif +#ifndef __MAC_10_7 +#define __MAC_10_7 1070 +#endif +#ifndef __MAC_10_9 +#define __MAC_10_9 1090 +#endif +#ifndef __MAC_10_10 +#define __MAC_10_10 101000 +#endif +#ifndef __MAC_10_13 +#define __MAC_10_13 101300 +#endif +#ifndef __IPHONE_6_0 +#define __IPHONE_6_0 60000 +#endif +#ifndef __IPHONE_7_0 +#define __IPHONE_7_0 70000 +#endif +#ifndef __IPHONE_9_0 +#define __IPHONE_9_0 90000 +#endif +#ifndef __IPHONE_11_0 +#define __IPHONE_11_0 110000 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, + kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemInodeMetaMod, + kFSEventStreamEventFlagItemRenamed, + kFSEventStreamEventFlagItemModified, + kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemChangeOwner, + kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagItemIsFile, + kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsSymlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; +extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink, + kFSEventStreamEventFlagItemIsLastHardlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned; +#endif + + +#ifdef __cplusplus +} +#endif + +#endif // listen_fsevents_compat_h diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/defines.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/defines.h new file mode 100644 index 0000000000..34879c58d0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/defines.h @@ -0,0 +1,42 @@ +#ifndef fsevent_watch_defines_h +#define fsevent_watch_defines_h + +#define _str(s) #s +#define _xstr(s) _str(s) + +#define COMPILED_AT __DATE__ " " __TIME__ + +#if defined (__clang__) +#define COMPILER "clang " __clang_version__ +#elif defined (__GNUC__) +#define COMPILER "gcc " __VERSION__ +#else +#define COMPILER "unknown" +#endif + +#if defined(__ppc__) +#define TARGET_CPU "ppc" +#elif defined(__ppc64__) +#define TARGET_CPU "ppc64" +#elif defined(__i386__) +#define TARGET_CPU "i386" +#elif defined(__x86_64__) +#define TARGET_CPU "x86_64" +#elif defined(__arm64__) +#define TARGET_CPU "arm64" +#else +#define TARGET_CPU "unknown" +#endif + +#define FLAG_CHECK(flags, flag) ((flags) & (flag)) + +#define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ + do { \ + if (FLAG_CHECK(flags, flag)) { \ + fprintf(fd, "%s", msg "\n"); } } \ + while (0) + +#define FLAG_CHECK_STDERR(flags, flag, msg) \ + FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) + +#endif /* fsevent_watch_defines_h */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/main.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/main.c new file mode 100644 index 0000000000..b18596a603 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/main.c @@ -0,0 +1,548 @@ +#include "common.h" +#include "signal_handlers.h" +#include "cli.h" +#include "FSEventsFix.h" + +// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's +// no need to set those here. also, in order to scope metadata by path, +// each stream will need its own configuration... so this won't work as +// a global any more. In the end the goal is to make the output format +// able to declare not just that something happened and what flags were +// attached, but what path it was watching that caused those events (so +// that the path itself can be used for routing that information to the +// relevant callback). +// +// Structure for storing metadata parsed from the commandline +static struct { + FSEventStreamEventId sinceWhen; + CFTimeInterval latency; + FSEventStreamCreateFlags flags; + CFMutableArrayRef paths; + enum FSEventWatchOutputFormat format; +} config = { + (UInt64) kFSEventStreamEventIdSinceNow, + (double) 0.3, + (CFOptionFlags) kFSEventStreamCreateFlagNone, + NULL, + kFSEventWatchOutputFormatOTNetstring +}; + +// Prototypes +static void append_path(const char* path); +static inline void parse_cli_settings(int argc, const char* argv[]); +static void callback(FSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]); +static bool needs_fsevents_fix = false; + +// Resolve a path and append it to the CLI settings structure +// The FSEvents API will, internally, resolve paths using a similar scheme. +// Performing this ahead of time makes things less confusing, IMHO. +static void append_path(const char* path) +{ +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "append_path called for: %s\n", path); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + +#ifdef DEBUG + fprintf(stderr, "compiled against 10.6+, using CFURLCreateFileReferenceURL\n"); +#endif + + CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)path, (CFIndex)strlen(path), false); + CFURLRef placeholder = CFURLCopyAbsoluteURL(url); + CFRelease(url); + + CFMutableArrayRef imaginary = NULL; + + // if we don't have an existing url, spin until we get to a parent that + // does exist, saving any imaginary components for appending back later + while(!CFURLResourceIsReachable(placeholder, NULL)) { +#ifdef DEBUG + fprintf(stderr, "path does not exist\n"); +#endif + + CFStringRef child; + + if (imaginary == NULL) { + imaginary = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + + child = CFURLCopyLastPathComponent(placeholder); + CFArrayInsertValueAtIndex(imaginary, 0, child); + CFRelease(child); + + url = CFURLCreateCopyDeletingLastPathComponent(NULL, placeholder); + CFRelease(placeholder); + placeholder = url; + +#ifdef DEBUG + fprintf(stderr, "parent: "); + CFShow(placeholder); +#endif + } + +#ifdef DEBUG + fprintf(stderr, "path exists\n"); +#endif + + // realpath() doesn't always return the correct case for a path, so this + // is a funky workaround that converts a path into a (volId/inodeId) pair + // and asks what the path should be for that. since it looks at the actual + // inode instead of returning the same case passed in like realpath() + // appears to do for HFS+, it should always be correct. + url = CFURLCreateFileReferenceURL(NULL, placeholder, NULL); + CFRelease(placeholder); + placeholder = CFURLCreateFilePathURL(NULL, url, NULL); + CFRelease(url); + +#ifdef DEBUG + fprintf(stderr, "path resolved to: "); + CFShow(placeholder); +#endif + + // if we stripped off any imaginary path components, append them back on + if (imaginary != NULL) { + CFIndex count = CFArrayGetCount(imaginary); + for (CFIndex i = 0; i= 6)) { + config.flags |= kFSEventStreamCreateFlagIgnoreSelf; + } else { + fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.file_events_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 7)) { + config.flags |= kFSEventStreamCreateFlagFileEvents; + } else { + fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.mark_self_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 9)) { + config.flags |= kFSEventStreamCreateFlagMarkSelf; + } else { + fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.inputs_num == 0) { + append_path("."); + } else { + for (unsigned int i=0; i < args_info.inputs_num; ++i) { + append_path(args_info.inputs[i]); + } + } + + cli_parser_free(&args_info); + +#ifdef DEBUG + fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); + fprintf(stderr, "config.latency %f\n", config.latency); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, "config.flags %#.8x\n", config.flags); +#else + fprintf(stderr, "config.flags %#.8lx\n", config.flags); +#endif + + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, + " Using CF instead of C types"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, + " NoDefer latency modifier enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, + " WatchRoot notifications enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, + " IgnoreSelf enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, + " FileEvents enabled"); + + fprintf(stderr, "config.paths\n"); + + long numpaths = CFArrayGetCount(config.paths); + + for (long i = 0; i < numpaths; i++) { + char path[PATH_MAX]; + CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), + path, + PATH_MAX, + kCFStringEncodingUTF8); + fprintf(stderr, " %s\n", path); + } + + fprintf(stderr, "\n"); +#endif +} + +// original output format for rb-fsevent +static void classic_output_format(size_t numEvents, + char** paths) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%s:", paths[i]); + } + fprintf(stdout, "\n"); +} + +// output format used in the Yoshimasa Niwa branch of rb-fsevent +static void niw_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%lu:%llu:%s\n", + (unsigned long)eventFlags[i], + (unsigned long long)eventIds[i], + paths[i]); + } + fprintf(stdout, "\n"); +} + +static void tstring_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[], + TSITStringFormat format) +{ + CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < numEvents; i++) { + CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)paths[i], + (CFIndex)strlen(paths[i]), + kCFStringEncodingUTF8, + false); + CFDictionarySetValue(event, CFSTR("path"), path); + + CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]); + CFDictionarySetValue(event, CFSTR("id"), ident); + + CFNumberRef cflags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]); + CFDictionarySetValue(event, CFSTR("cflags"), cflags); + + CFMutableArrayRef flags = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + +#define FLAG_ADD_NAME(flagsnum, flagnum, flagname, flagarray) \ + do { \ + if (FLAG_CHECK(flagsnum, flagnum)) { \ + CFArrayAppendValue(flagarray, CFSTR(flagname)); } } \ + while(0) + + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, "MustScanSubDirs", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUserDropped, "UserDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagKernelDropped, "KernelDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, "EventIdsWrapped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagHistoryDone, "HistoryDone", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagRootChanged, "RootChanged", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMount, "Mount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUnmount, "Unmount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemCreated, "ItemCreated", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRemoved, "ItemRemoved", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, "ItemInodeMetaMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRenamed, "ItemRenamed", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemModified, "ItemModified", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, "ItemFinderInfoMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, "ItemChangeOwner", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, "ItemXattrMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsFile, "ItemIsFile", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsDir, "ItemIsDir", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, "ItemIsSymlink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagOwnEvent, "OwnEvent", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, "ItemIsHardLink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, "ItemIsLastHardLink", flags); + + CFDictionarySetValue(event, CFSTR("flags"), flags); + + + CFArrayAppendValue(events, event); + + CFRelease(event); + CFRelease(path); + CFRelease(ident); + CFRelease(cflags); + CFRelease(flags); + } + + CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionarySetValue(meta, CFSTR("events"), events); + + CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents); + CFDictionarySetValue(meta, CFSTR("numEvents"), num); + + CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format); + fprintf(stdout, "%s", CFDataGetBytePtr(data)); + + CFRelease(events); + CFRelease(num); + CFRelease(meta); + CFRelease(data); +} + +static void callback(__attribute__((unused)) FSEventStreamRef streamRef, + __attribute__((unused)) void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + char** paths = eventPaths; + + +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "FSEventStreamCallback fired!\n"); + fprintf(stderr, " numEvents: %lu\n", numEvents); + + for (size_t i = 0; i < numEvents; i++) { + fprintf(stderr, "\n"); + fprintf(stderr, " event ID: %llu\n", eventIds[i]); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]); +#else + fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]); +#endif + + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, + " Recursive scanning of directory required"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped, + " Buffering problem: events dropped user-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped, + " Buffering problem: events dropped kernel-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, + " Event IDs have wrapped"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone, + " All historical events have been processed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged, + " Root path has changed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount, + " A new volume was mounted at this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount, + " A volume was unmounted from this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated, + " Item created"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved, + " Item removed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, + " Item metadata modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed, + " Item renamed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified, + " Item modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, + " Item Finder Info modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, + " Item changed ownership"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, + " Item extended attributes modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile, + " Item is a file"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir, + " Item is a directory"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, + " Item is a symbolic link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, + " Item is a hard link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, + " Item is the last hard link"); + fprintf(stderr, " event path: %s\n", paths[i]); + fprintf(stderr, "\n"); + } + + fprintf(stderr, "\n"); +#endif + + if (config.format == kFSEventWatchOutputFormatClassic) { + classic_output_format(numEvents, paths); + } else if (config.format == kFSEventWatchOutputFormatNIW) { + niw_output_format(numEvents, paths, eventFlags, eventIds); + } else if (config.format == kFSEventWatchOutputFormatTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatTNetstring); + } else if (config.format == kFSEventWatchOutputFormatOTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatOTNetstring); + } + + fflush(stdout); +} + +int main(int argc, const char* argv[]) +{ + install_signal_handlers(); + parse_cli_settings(argc, argv); + + if (needs_fsevents_fix) { + FSEventsFixEnable(); + } + + FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; + FSEventStreamRef stream; + stream = FSEventStreamCreate(kCFAllocatorDefault, + (FSEventStreamCallback)&callback, + &context, + config.paths, + config.sinceWhen, + config.latency, + config.flags); + +#ifdef DEBUG + FSEventStreamShow(stream); + fprintf(stderr, "\n"); +#endif + + if (needs_fsevents_fix) { + FSEventsFixDisable(); + } + + FSEventStreamScheduleWithRunLoop(stream, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + FSEventStreamStart(stream); + CFRunLoopRun(); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); + + return 0; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.c new file mode 100644 index 0000000000..b20da3f381 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.c @@ -0,0 +1,66 @@ +#include "signal_handlers.h" +#include +#include +#include +#include +#include +#include + + +#define PPID_ALARM_INTERVAL 2 // send SIGALRM every this seconds + + +static pid_t orig_ppid; + + +static void signal_handler(int _) { + exit(EXIT_FAILURE); +} + +static void check_ppid(void) { + if (getppid() != orig_ppid) { + exit(EXIT_FAILURE); + } +} + +static void check_stdout_open(void) { + if (fcntl(STDOUT_FILENO, F_GETFD) < 0) { + exit(EXIT_FAILURE); + } +} + +static void alarm_handler(int _) { + check_ppid(); + check_stdout_open(); + alarm(PPID_ALARM_INTERVAL); + signal(SIGALRM, alarm_handler); +} + +static void die(const char *msg) { + fprintf(stderr, "\nFATAL: %s\n", msg); + abort(); +} + +static void install_signal_handler(int sig, void (*handler)(int)) { + if (signal(sig, handler) == SIG_ERR) { + die("Could not install signal handler"); + } +} + +void install_signal_handlers(void) { + // check pipe is still connected + check_stdout_open(); + + // watch getppid() every PPID_ALARM_INTERVAL seconds + orig_ppid = getppid(); + if (orig_ppid <= 1) { + die("prematurely zombied"); + } + install_signal_handler(SIGALRM, alarm_handler); + alarm(PPID_ALARM_INTERVAL); + + // be sure to exit on SIGHUP, SIGPIPE + install_signal_handler(SIGHUP, signal_handler); + install_signal_handler(SIGPIPE, signal_handler); +} + diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.h new file mode 100644 index 0000000000..c31685d9ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/fsevent_watch/signal_handlers.h @@ -0,0 +1,16 @@ +/** + * @headerfile signal_handlers.h + * Signal handlers to stop the zombie hordes + * + * Catch and handle signals better so that we die faster like a good meat puppet. + */ + + +#ifndef fsevent_watch_signal_handlers_h +#define fsevent_watch_signal_handlers_h + + +void install_signal_handlers(void); + + +#endif // fsevent_watch_signal_handlers_h diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/rakefile.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/rakefile.rb new file mode 100644 index 0000000000..e50e888742 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/ext/rakefile.rb @@ -0,0 +1,231 @@ +# -*- encoding: utf-8 -*- +require 'rubygems' unless defined?(Gem) +require 'pathname' +require 'date' +require 'time' +require 'rake/clean' + +raise "unable to find xcodebuild" unless system('which', 'xcodebuild') + + +FSEVENT_WATCH_EXE_VERSION = '0.1.5' + +$this_dir = Pathname.new(__FILE__).dirname.expand_path +$final_exe = $this_dir.parent.join('bin/fsevent_watch') + +$src_dir = $this_dir.join('fsevent_watch') +$obj_dir = $this_dir.join('build') + +SRC = Pathname.glob("#{$src_dir}/*.c") +OBJ = SRC.map {|s| $obj_dir.join("#{s.basename('.c')}.o")} + +$now = DateTime.now.xmlschema rescue Time.now.xmlschema + +$CC = ENV['CC'] || `which clang || which gcc`.strip +$CFLAGS = ENV['CFLAGS'] || '-fconstant-cfstrings -fasm-blocks -fstrict-aliasing -Wall' +$ARCHFLAGS = ENV['ARCHFLAGS'] || '-arch x86_64' +$DEFINES = "-DNS_BUILD_32_LIKE_64 -DNS_BLOCK_ASSERTIONS -DPROJECT_VERSION=#{FSEVENT_WATCH_EXE_VERSION}" + +$GCC_C_LANGUAGE_STANDARD = ENV['GCC_C_LANGUAGE_STANDARD'] || 'gnu11' + +# generic developer id name so it'll match correctly for anyone who has only +# one developer id in their keychain (not that I expect anyone else to bother) +$CODE_SIGN_IDENTITY = 'Developer ID Application' + +$arch = `uname -m`.strip +$os_release = `uname -r`.strip +$BUILD_TRIPLE = "#{$arch}-apple-darwin#{$os_release}" + +$CCVersion = `#{$CC} --version | head -n 1`.strip + + +CLEAN.include OBJ.map(&:to_s) +CLEAN.include $obj_dir.join('Info.plist').to_s +CLEAN.include $obj_dir.join('fsevent_watch').to_s +CLOBBER.include $final_exe.to_s + + +task :sw_vers do + $mac_product_version = `sw_vers -productVersion`.strip + $mac_build_version = `sw_vers -buildVersion`.strip + $MACOSX_DEPLOYMENT_TARGET = ENV['MACOSX_DEPLOYMENT_TARGET'] || $mac_product_version.sub(/\.\d*$/, '') + $CFLAGS = "#{$CFLAGS} -mmacosx-version-min=#{$MACOSX_DEPLOYMENT_TARGET}" +end + +task :get_sdk_info => :sw_vers do + $SDK_INFO = {} + version_info = `xcodebuild -version -sdk macosx#{$MACOSX_DEPLOYMENT_TARGET}` + raise "invalid SDK" unless !!$?.exitstatus + version_info.strip.each_line do |line| + next if line.strip.empty? + next unless line.include?(':') + match = line.match(/([^:]*): (.*)/) + next unless match + $SDK_INFO[match[1]] = match[2] + end +end + +task :debug => :sw_vers do + $DEFINES = "-DDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -O0 -fno-omit-frame-pointer -g" +end + +task :release => :sw_vers do + $DEFINES = "-DNDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -Ofast" +end + +desc 'configure build type depending on whether ENV var FWDEBUG is set' +task :set_build_type => :sw_vers do + if ENV['FWDEBUG'] + Rake::Task[:debug].invoke + else + Rake::Task[:release].invoke + end +end + +desc 'set build arch to ppc' +task :ppc do + $ARCHFLAGS = '-arch ppc' +end + +desc 'set build arch to x86_64' +task :x86_64 do + $ARCHFLAGS = '-arch x86_64' +end + +desc 'set build arch to i386' +task :x86 do + $ARCHFLAGS = '-arch i386' +end + +desc 'set build arch to arm64' +task :arm64 do + $ARCHFLAGS = '-arch arm64' +end + +task :setup_env => [:set_build_type, :sw_vers, :get_sdk_info] + +directory $obj_dir.to_s +file $obj_dir.to_s => :setup_env + +SRC.zip(OBJ).each do |source, object| + file object.to_s => [source.to_s, $obj_dir.to_s] do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-c', source, + '-o', object + ] + sh(cmd.map {|s| s.to_s}.join(' ')) + end +end + +file $obj_dir.join('Info.plist').to_s => [$obj_dir.to_s, :setup_env] do + File.open($obj_dir.join('Info.plist').to_s, 'w+') do |file| + indentation = '' + indent = lambda {|num| indentation = ' ' * num } + add = lambda {|str| file << "#{indentation}#{str}\n" } + key = lambda {|str| add["#{str}"] } + string = lambda {|str| add["#{str}"] } + + + add[''] + add[''] + add[''] + + indent[2] + add[''] + indent[4] + + key['CFBundleExecutable'] + string['fsevent_watch'] + key['CFBundleIdentifier'] + string['com.teaspoonofinsanity.fsevent_watch'] + key['CFBundleName'] + string['fsevent_watch'] + key['CFBundleDisplayName'] + string['FSEvent Watch CLI'] + key['NSHumanReadableCopyright'] + string['Copyright (C) 2011-2017 Travis Tilley'] + + key['CFBundleVersion'] + string["#{FSEVENT_WATCH_EXE_VERSION}"] + key['LSMinimumSystemVersion'] + string["#{$MACOSX_DEPLOYMENT_TARGET}"] + key['DTSDKBuild'] + string["#{$SDK_INFO['ProductBuildVersion']}"] + key['DTSDKName'] + string["macosx#{$SDK_INFO['SDKVersion']}"] + key['DTSDKPath'] + string["#{$SDK_INFO['Path']}"] + key['BuildMachineOSBuild'] + string["#{$mac_build_version}"] + key['BuildMachineOSVersion'] + string["#{$mac_product_version}"] + key['FSEWCompiledAt'] + string["#{$now}"] + key['FSEWVersionInfoBuilder'] + string["#{`whoami`.strip}"] + key['FSEWBuildTriple'] + string["#{$BUILD_TRIPLE}"] + key['FSEWCC'] + string["#{$CC}"] + key['FSEWCCVersion'] + string["#{$CCVersion}"] + key['FSEWCFLAGS'] + string["#{$CFLAGS}"] + + indent[2] + add[''] + indent[0] + + add[''] + end +end + +desc 'generate an Info.plist used for code signing as well as embedding build settings into the resulting binary' +task :plist => $obj_dir.join('Info.plist').to_s + + +file $obj_dir.join('fsevent_watch').to_s => [$obj_dir.to_s, $obj_dir.join('Info.plist').to_s] + OBJ.map(&:to_s) do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-framework CoreFoundation -framework CoreServices', + '-sectcreate __TEXT __info_plist', + $obj_dir.join('Info.plist') + ] + OBJ + [ + '-o', $obj_dir.join('fsevent_watch') + ] + sh(cmd.map {|s| s.to_s}.join(' ')) +end + +desc 'compile and link build/fsevent_watch' +task :build => $obj_dir.join('fsevent_watch').to_s + +desc 'codesign build/fsevent_watch binary' +task :codesign => :build do + sh "codesign -s '#{$CODE_SIGN_IDENTITY}' #{$obj_dir.join('fsevent_watch')}" +end + +directory $this_dir.parent.join('bin') + +desc 'replace bundled fsevent_watch binary with build/fsevent_watch' +task :replace_exe => [$this_dir.parent.join('bin'), :build] do + sh "mv #{$obj_dir.join('fsevent_watch')} #{$final_exe}" +end + +task :default => [:replace_exe, :clean] diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/otnetstring.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/otnetstring.rb new file mode 100644 index 0000000000..cd8de4c253 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/otnetstring.rb @@ -0,0 +1,85 @@ +# Copyright (c) 2011 Konstantin Haase +# +# 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 'stringio' + +module OTNetstring + class Error < StandardError; end + + class << self + def parse(io, encoding = 'internal', fallback_encoding = nil) + fallback_encoding = io.encoding if io.respond_to? :encoding + io = StringIO.new(io) if io.respond_to? :to_str + length, byte = "", nil + + while byte.nil? || byte =~ /\d/ + length << byte if byte + byte = io.read(1) + end + + if length.size > 9 + raise Error, "#{length} is longer than 9 digits" + elsif length !~ /\d+/ + raise Error, "Expected '#{byte}' to be a digit" + end + length = Integer(length) + + case byte + when '#' then Integer io.read(length) + when ',' then with_encoding io.read(length), encoding, fallback_encoding + when '~' then + raise Error, "nil has length of 0, #{length} given" unless length == 0 + when '!' then io.read(length) == 'true' + when '[', '{' + array = [] + start = io.pos + array << parse(io, encoding, fallback_encoding) while io.pos - start < length + raise Error, 'Nested element longer than container' if io.pos - start != length + byte == "{" ? Hash[*array] : array + else + raise Error, "Unknown type '#{byte}'" + end + end + + def encode(obj, string_sep = ',') + case obj + when String then with_encoding "#{obj.bytesize}#{string_sep}#{obj}", "binary" + when Integer then encode(obj.inspect, '#') + when NilClass then "0~" + when Array then encode(obj.map { |e| encode(e) }.join, '[') + when Hash then encode(obj.map { |a,b| encode(a)+encode(b) }.join, '{') + when FalseClass, TrueClass then encode(obj.inspect, '!') + else raise Error, 'cannot encode %p' % obj + end + end + + private + + def with_encoding(str, encoding, fallback = nil) + return str unless str.respond_to? :encode + encoding = Encoding.find encoding if encoding.respond_to? :to_str + encoding ||= fallback + encoding ? str.encode(encoding) : str + rescue EncodingError + str.force_encoding(encoding) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent.rb new file mode 100644 index 0000000000..1ff68a3095 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent.rb @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- +require 'rb-fsevent/fsevent' +require 'rb-fsevent/version' diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/fsevent.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/fsevent.rb new file mode 100644 index 0000000000..23c5aa9edd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/fsevent.rb @@ -0,0 +1,157 @@ +# -*- encoding: utf-8 -*- + +require 'otnetstring' + +class FSEvent + class << self + class_eval <<-END + def root_path + "#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}" + end + END + class_eval <<-END + def watcher_path + "#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}" + end + END + end + + attr_reader :paths, :callback + + def initialize args = nil, &block + watch(args, &block) unless args.nil? + end + + def watch(watch_paths, options=nil, &block) + @paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths] + @callback = block + + if options.kind_of?(Hash) + @options = parse_options(options) + elsif options.kind_of?(Array) + @options = options + else + @options = [] + end + end + + def run + @pipe = open_pipe + @running = true + + # please note the use of IO::select() here, as it is used specifically to + # preserve correct signal handling behavior in ruby 1.8. + while @running && IO::select([@pipe], nil, nil, nil) + # managing the IO ourselves allows us to be careful and never pass an + # incomplete message to OTNetstring.parse() + message = "" + length = "" + byte = nil + + reading_length = true + found_length = false + + while reading_length + byte = @pipe.read_nonblock(1) + if "#{byte}" =~ /\d/ + length << byte + found_length = true + elsif found_length == false + next + else + reading_length = false + end + end + length = Integer(length, 10) + type = byte + + message << "#{length}#{type}" + message << @pipe.read(length) + + decoded = OTNetstring.parse(message) + modified_paths = decoded["events"].map {|event| event["path"]} + # passing the full info as a second block param feels icky, but such is + # the trap of backward compatibility. + case callback.arity + when 1 + callback.call(modified_paths) + when 2 + callback.call(modified_paths, decoded) + end + end + rescue Interrupt, IOError, Errno::EBADF + ensure + stop + end + + def stop + unless @pipe.nil? + Process.kill('KILL', @pipe.pid) if process_running?(@pipe.pid) + @pipe.close + end + rescue IOError + ensure + @running = false + end + + def process_running?(pid) + begin + Process.kill(0, pid) + true + rescue Errno::ESRCH + false + end + end + + if RUBY_VERSION < '1.9' + def open_pipe + IO.popen("'#{self.class.watcher_path}' #{options_string} #{shellescaped_paths}") + end + + private + + def options_string + @options.join(' ') + end + + def shellescaped_paths + @paths.map {|path| shellescape(path)}.join(' ') + end + + # for Ruby 1.8.6 support + def shellescape(str) + # An empty argument will be skipped, so return empty quotes. + return "''" if str.empty? + + str = str.dup + + # Process as a single byte sequence because not all shell + # implementations are multibyte aware. + str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") + + # A LF cannot be escaped with a backslash because a backslash + LF + # combo is regarded as line continuation and simply ignored. + str.gsub!(/\n/, "'\n'") + + return str + end + else + def open_pipe + IO.popen([self.class.watcher_path] + @options + @paths) + end + end + + private + + def parse_options(options={}) + opts = ['--format=otnetstring'] + opts.concat(['--since-when', options[:since_when]]) if options[:since_when] + opts.concat(['--latency', options[:latency]]) if options[:latency] + opts.push('--no-defer') if options[:no_defer] + opts.push('--watch-root') if options[:watch_root] + opts.push('--file-events') if options[:file_events] + # ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings + opts.map {|opt| "#{opt}"} + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/version.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/version.rb new file mode 100644 index 0000000000..6b18bf3a48 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/lib/rb-fsevent/version.rb @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- + +class FSEvent + VERSION = '0.11.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/rb-fsevent.gemspec b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/rb-fsevent.gemspec new file mode 100644 index 0000000000..8f1e5aae45 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.0/rb-fsevent.gemspec @@ -0,0 +1,26 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rb-fsevent/version' + +Gem::Specification.new do |s| + s.name = 'rb-fsevent' + s.version = FSEvent::VERSION + s.authors = ['Thibaud Guillaume-Gentil', 'Travis Tilley'] + s.email = ['thibaud@thibaud.gg', 'ttilley@gmail.com'] + s.homepage = 'http://rubygems.org/gems/rb-fsevent' + s.summary = 'Very simple & usable FSEvents API' + s.description = 'FSEvents API with Signals catching (without RubyCocoa)' + s.license = 'MIT' + + s.metadata = { + 'source_code_uri' => 'https://github.com/thibaudgg/rb-fsevent' + } + + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) } + s.require_path = 'lib' + + s.add_development_dependency 'rspec', '~> 3.6' + s.add_development_dependency 'guard-rspec', '~> 4.2' + s.add_development_dependency 'rake', '~> 12.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/.gitignore b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/.gitignore new file mode 100644 index 0000000000..25a0e9a7e4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/.gitignore @@ -0,0 +1,13 @@ +*.gem +.DS_Store +.Trashes +.bundle +.com.apple.timemachine.supported +.fseventsd +.idea +.rbx +/ext/build +Desktop DB +Desktop DF +Gemfile.lock +pkg/* diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Gemfile new file mode 100644 index 0000000000..b4e2a20bb6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Guardfile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Guardfile new file mode 100644 index 0000000000..63a666e507 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Guardfile @@ -0,0 +1,8 @@ +# A sample Guardfile +# More info at http://github.com/guard/guard#readme + +guard :rspec do + watch(%r(^spec/(.*)_spec.rb)) + watch(%r(^lib/(.*)\.rb)) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/LICENSE.txt new file mode 100644 index 0000000000..b083ecdd16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2010-2014 Thibaud Guillaume-Gentil & Travis Tilley + +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.0.0/gems/rb-fsevent-0.11.2/README.md b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/README.md new file mode 100644 index 0000000000..3d87bcc6f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/README.md @@ -0,0 +1,260 @@ +[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thibaudgg/rb-fsevent) +[![endorse](https://api.coderwall.com/ttilley/endorsecount.png)](https://coderwall.com/ttilley) + +# rb-fsevent + +Very simple & usable Mac OSX FSEvents API + +* Signals are working (really) +* Tested on MRI 2.4.1, RBX 3.72, JRuby 1.7.26 and 9.1.8.0 +* Tested on 10.8 + +## HFS+ filename corruption bug + +There is a _very_ long-standing (since 2011) OSX bug where sometimes the filename metadata for HFS+ filesystems will get corrupted, resulting in some APIs returning one case for a file, and other APIs returning another. The result is that sometimes, _for no visible reason to the user_, fsevents would simply not work. As of rb-fsevent 0.9.5 this issue is properly detected and an insanely hacky (but effective) workaround is used that replaces the system `realpath()` with a custom implementation that should almost always return the same value as the kernel reporting (thus fixing fsevents). The major flaw in the workaround is that it may return the wrong path for hard links. + +Please note that this doesn't repair the underlying issue on disk. Other apps and libraries using fsevents will continue to break with no warning. There may be other issues unrelated to fsevents. + +__This bug is resolved in MacOS 10.12 and all users are strongly encouraged to upgrade.__ + +## Install + + gem install rb-fsevent + +### re-compilation + +rb-fsevent comes with a pre-compiled fsevent\_watch binary supporting x86\_64 on 10.9 and above. The binary is codesigned with my (Travis Tilley) Developer ID as an extra precaution when distributing pre-compiled code and contains an embedded plist describing its build environment. This should be sufficient for most users, but if you need to use rb-fsevent on 10.8 or lower then recompilation is necessary. This can be done by entering the installed gem's ext directory and running: + + MACOSX_DEPLOYMENT_TARGET="10.7" rake replace_exe + +The following ENV vars are recognized: + +* CC +* CFLAGS +* ARCHFLAGS +* MACOSX\_DEPLOYMENT\_TARGET +* FWDEBUG (enables debug mode, printing an obscene number of informational + messages to STDERR) + +### embedded plist + +You can retrieve the values in the embedded plist via the CLI: + + fsevent_watch --show-plist + +The output is essentially formatted as `"#{key}:\n #{value}\n"` to make it easier to read than plist style xml. The result looks like this: + + DTSDKName: + macosx10.5 + FSEWBuildTriple: + i386-apple-darwin10.8.0 + FSEWCC: + /usr/bin/gcc-4.2 + DTSDKPath: + /Developer/SDKs/MacOSX10.5.sdk + FSEWCCVersion: + i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) + FSEWCFLAGS: + -fconstant-cfstrings -fno-strict-aliasing -Wall -mmacosx-version-min=10.5 -O3 + +If, for some perverse reason, you prefer to look at the xml... it can be retrieved via: + + otool -s __TEXT __info_plist ./bin/fsevent_watch | grep ^0 | xxd -r - + +### codesign + +You can verify code signing information for a specific fsevent\_watch via: + + codesign -d -vvv ./bin/fsevent_watch + +If you're using the pre-compiled binary, then the output should contain something to the effect of: + + Authority=Developer ID Application: Travis Tilley + Authority=Developer ID Certification Authority + Authority=Apple Root CA + Timestamp=Dec 31, 2012 12:49:13 PM + +## Usage + +### Singular path + +```ruby +require 'rb-fsevent' + +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] + +fsevent = FSEvent.new +fsevent.watch paths do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as a Hash + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = {:latency => 1.5, :no_defer => true } + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as an Array + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = ['--latency', 1.5, '--no-defer'] + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Using _full_ event information + +```ruby +require 'rb-fsevent' +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |paths, event_meta| + event_meta['events'].each do |event| + puts "event ID: #{event['id']}" + puts "path: #{event['path']}" + puts "c flags: #{event['cflags']}" + puts "named flags: #{event['flags'].join(', ')}" + # named flags will include strings such as `ItemInodeMetaMod` or `OwnEvent` + end +end +fsevent.run +``` + +## Options + +When defining options using a hash or hash-like object, it gets checked for validity and converted to the appropriate fsevent\_watch commandline arguments array when the FSEvent class is instantiated. This is obviously the safest and preferred method of passing in options. + +You may, however, choose to pass in an array of commandline arguments as your options value and it will be passed on, unmodified, to the fsevent\_watch binary when called. + +So far, the following options are supported: + +* :latency => 0.5 # in seconds +* :no\_defer => true +* :watch\_root => true +* :since\_when => 18446744073709551615 # an FSEventStreamEventId +* :file\_events => true + +### Latency + +The :latency parameter determines how long the service should wait after the first event before passing that information along to the client. If your latency is set to 4 seconds, and 300 changes occur in the first three, then the callback will be fired only once. If latency is set to 0.1 in the exact same scenario, you will see that callback fire somewhere closer to between 25 and 30 times. + +Setting a higher latency value allows for more effective temporal coalescing, resulting in fewer callbacks and greater overall efficiency... at the cost of apparent responsiveness. Setting this to a reasonably high value (and NOT setting :no\_defer) is particularly well suited for background, daemon, or batch processing applications. + +Implementation note: It appears that FSEvents will only coalesce events from a maximum of 32 distinct subpaths, making the above completely accurate only when events are to fewer than 32 subpaths. Creating 300 files in one directory, for example, or 30 files in 10 subdirectories, but not 300 files within 300 subdirectories. In the latter case, you may receive 31 callbacks in one go after the latency period. As this appears to be an implementation detail, the number could potentially differ across OS revisions. It is entirely possible that this number is somehow configurable, but I have not yet discovered an accepted method of doing so. + +### NoDefer + +The :no\_defer option changes the behavior of the latency parameter completely. Rather than waiting for $latency period of time before sending along events in an attempt to coalesce a potential deluge ahead of time, that first event is sent along to the client immediately and is followed by a $latency period of silence before sending along any additional events that occurred within that period. + +This behavior is particularly useful for interactive applications where that feeling of apparent responsiveness is most important, but you still don't want to get overwhelmed by a series of events that occur in rapid succession. + +### WatchRoot + +The :watch\_root option allows for catching the scenario where you start watching "~/src/demo\_project" and either it is later renamed to "~/src/awesome\_sauce\_3000" or the path changes in such a manner that the original directory is now at "~/clients/foo/iteration4/demo\_project". + +Unfortunately, while this behavior is somewhat supported in the fsevent\_watch binary built as part of this project, support for passing across detailed metadata is not (yet). As a result, you would not receive the appropriate RootChanged event and be able to react appropriately. Also, since the C code doesn't open watched directories and retain that file descriptor as part of path-specific callback metadata, we are unable to issue an F\_GETPATH fcntl() to determine the directory's new path. + +Please do not use this option until proper support is added (or, even better, add it and submit a pull request). + +### SinceWhen + +The FSEventStreamEventId passed in to :since\_when is used as a base for reacting to historic events. Unfortunately, not only is the metadata for transitioning from historic to live events not currently passed along, but it is incorrectly passed as a change event on the root path, and only per-host event streams are currently supported. When using per-host event streams, the event IDs are not guaranteed to be unique or contiguous when shared volumes (firewire/USB/net/etc) are used on multiple macs. + +Please do not use this option until proper support is added, unless it's acceptable for you to receive that one fake event that's handled incorrectly when events transition from historical to live. Even in that scenario, there's no metadata available for determining the FSEventStreamEventId of the last received event. + +WARNING: passing in 0 as the parameter to :since\_when will return events for every directory modified since "the beginning of time". + +### FileEvents ### + +Prepare yourself for an obscene number of callbacks. Realistically, an "Atomic Save" could easily fire maybe 6 events for the combination of creating the new file, changing metadata/permissions, writing content, swapping out the old file for the new may itself result in multiple events being fired, and so forth. By the time you get the event for the temporary file being created as part of the atomic save, it will already be gone and swapped with the original file. This and issues of a similar nature have prevented me from adding the option to the ruby code despite the fsevent\_watch binary supporting file level events for quite some time now. Mountain Lion seems to be better at coalescing needless events, but that might just be my imagination. + +## Debugging output + +If the gem is re-compiled with the environment variable FWDEBUG set, then fsevent\_watch will be built with its various DEBUG sections defined, and the output to STDERR is truly verbose (and hopefully helpful in debugging your application and not just fsevent\_watch itself). If enough people find this to be directly useful when developing code that makes use of rb-fsevent, then it wouldn't be hard to clean this up and make it a feature enabled by a commandline argument instead. Until somebody files an issue, however, I will assume otherwise. + + append_path called for: /tmp/moo/cow/ + resolved path to: /private/tmp/moo/cow + + config.sinceWhen 18446744073709551615 + config.latency 0.300000 + config.flags 00000000 + config.paths + /private/tmp/moo/cow + + FSEventStreamRef @ 0x100108540: + allocator = 0x7fff705a4ee0 + callback = 0x10000151e + context = {0, 0x0, 0x0, 0x0, 0x0} + numPathsToWatch = 1 + pathsToWatch = 0x7fff705a4ee0 + pathsToWatch[0] = '/private/tmp/moo/cow' + latestEventId = -1 + latency = 300000 (microseconds) + flags = 0x00000000 + runLoop = 0x0 + runLoopMode = 0x0 + + FSEventStreamCallback fired! + numEvents: 32 + event path: /private/tmp/moo/cow/1/a/ + event flags: 00000000 + event ID: 1023767 + event path: /private/tmp/moo/cow/1/b/ + event flags: 00000000 + event ID: 1023782 + event path: /private/tmp/moo/cow/1/c/ + event flags: 00000000 + event ID: 1023797 + event path: /private/tmp/moo/cow/1/d/ + event flags: 00000000 + event ID: 1023812 + [etc] + + +## Development + +* Source hosted at [GitHub](http://github.com/thibaudgg/rb-fsevent) +* Report issues/Questions/Feature requests on [GitHub Issues](http://github.com/thibaudgg/rb-fsevent/issues) + +Pull requests are quite welcome! Please ensure that your commits are in a topic branch for each individual changeset that can be reasonably isolated. It is also important to ensure that your changes are well tested... whether that means new tests, modified tests, or fixing a scenario where the existing tests currently fail. If you have rbenv and ruby-build, we have a helper task for running the testsuite in all of them: + + rake spec:portability + +The list of tested targets is currently: + + %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + +## Authors + +* [Travis Tilley](http://github.com/ttilley) +* [Thibaud Guillaume-Gentil](http://github.com/thibaudgg) +* [Andrey Tarantsov](https://github.com/andreyvit) diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Rakefile new file mode 100644 index 0000000000..53a08a14f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/Rakefile @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +require 'bundler' +Bundler::GemHelper.install_tasks + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) +task :default => :spec + +namespace(:spec) do + desc "Run all specs on multiple ruby versions" + task(:portability) do + versions = %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + versions.each do |version| + # system <<-BASH + # bash -c 'source ~/.rvm/scripts/rvm; + # rvm #{version}; + # echo "--------- version #{version} ----------\n"; + # bundle install; + # rake spec' + # BASH + system <<-BASH + bash -c 'export PATH="$HOME/.rbenv/bin:$PATH"; + [[ `which rbenv` ]] && eval "$(rbenv init -)"; + [[ ! -a $HOME/.rbenv/versions/#{version} ]] && rbenv install #{version}; + rbenv shell #{version}; + rbenv which bundle 2> /dev/null || gem install bundler; + rm Gemfile.lock; + bundle install; + rake spec;' + BASH + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/bin/fsevent_watch b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/bin/fsevent_watch new file mode 100755 index 0000000000..4fb821d4a3 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/bin/fsevent_watch differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/LICENSE b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/LICENSE new file mode 100644 index 0000000000..a35e1957dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011-2013 Travis Tilley + +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.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.c new file mode 100644 index 0000000000..60e3d37bd6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.c @@ -0,0 +1,626 @@ +/* + * FSEventsFix + * + * Resolves a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X released (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * Copyright (c) 2015 Andrey Tarantsov + * Copyright (c) 2003 Constantin S. Svintsoff + * + * 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. + * + * Based on a realpath implementation from Apple libc 498.1.7, taken from + * http://www.opensource.apple.com/source/Libc/Libc-498.1.7/stdlib/FreeBSD/realpath.c + * and provided under the following license: + * + * Copyright (c) 2003 Constantin S. Svintsoff + * + * 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. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * 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. + */ + + +#include "FSEventsFix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const char *const FSEventsFixVersionString = "0.11.0"; + + +#pragma mark - Forward declarations + +static char *(*orig_realpath)(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *CFURL_realpath(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *FSEventsFix_realpath_wrapper(const char *restrict src, char *restrict dst); + +static void _FSEventsFixHookInstall(); +static void _FSEventsFixHookUninstall(); + + +#pragma mark - Internal state + +static dispatch_queue_t g_queue = NULL; + +static int64_t g_enable_refcount = 0; + +static bool g_in_self_test = false; +static bool g_hook_operational = false; + +static void(^g_logging_block)(FSEventsFixMessageType type, const char *message); +static FSEventsFixDebugOptions g_debug_opt = 0; + +typedef struct { + char *name; + void *replacement; + void *original; + uint hooked_symbols; +} rebinding_t; + +static rebinding_t g_rebindings[] = { + { "_realpath$DARWIN_EXTSN", (void *) &FSEventsFix_realpath_wrapper, (void *) &realpath, 0 } +}; +static const uint g_rebindings_nel = sizeof(g_rebindings) / sizeof(g_rebindings[0]); + + +#pragma mark - Logging + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) __attribute__((__format__ (__printf__, 2, 3))); + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) { + if (g_logging_block) { + char *message = NULL; + va_list va; + va_start(va, fmt); + vasprintf(&message, fmt, va); + va_end(va); + + if (message) { + if (!!(g_debug_opt & FSEventsFixDebugOptionLogToStderr)) { + fprintf(stderr, "FSEventsFix: %s\n", message); + } + if (g_logging_block) { + g_logging_block(type, message); + } + free(message); + } + } +} + + +#pragma mark - API + +void _FSEventsFixInitialize() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + g_queue = dispatch_queue_create("FSEventsFix", DISPATCH_QUEUE_SERIAL); + }); +} + +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType severity, const char *message)) { + _FSEventsFixInitialize(); + loggingBlock = Block_copy(loggingBlock); + dispatch_sync(g_queue, ^{ + g_debug_opt = debugOptions; + g_logging_block = loggingBlock; + }); +} + +// Must be called from the private serial queue. +void _FSEventsFixSelfTest() { + g_in_self_test = true; + g_hook_operational = false; + static char result[1024]; + realpath("/Etc/__!FSEventsFixSelfTest!__", result); + g_in_self_test = false; +} + +void FSEventsFixEnable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (++g_enable_refcount == 1) { + orig_realpath = dlsym(RTLD_DEFAULT, "realpath"); + _FSEventsFixHookInstall(); + _FSEventsFixSelfTest(); + if (g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Enabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to enable (hook not called)"); + } + } + }); +} + +void FSEventsFixDisable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (g_enable_refcount == 0) { + abort(); + } + if (--g_enable_refcount == 0) { + _FSEventsFixHookUninstall(); + _FSEventsFixSelfTest(); + if (!g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Disabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to disable (hook still called)"); + } + } + }); +} + +bool FSEventsFixIsOperational() { + _FSEventsFixInitialize(); + __block bool result = false; + dispatch_sync(g_queue, ^{ + result = g_hook_operational; + }); + return result; +} + +bool _FSEventsFixIsBroken_noresolve(const char *resolved) { + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateBroken)) { + if (strstr(resolved, FSEventsFixSimulatedBrokenFolderMarker)) { + return true; + } + } + + char *reresolved = realpath(resolved, NULL); + if (reresolved) { + bool broken = (0 != strcmp(resolved, reresolved)); + free(reresolved); + return broken; + } else { + return true; + } +} + +bool FSEventsFixIsBroken(const char *path) { + char *resolved = CFURL_realpath(path, NULL); + if (!resolved) { + return true; + } + bool broken = _FSEventsFixIsBroken_noresolve(resolved); + free(resolved); + return broken; +} + +char *FSEventsFixCopyRootBrokenFolderPath(const char *inpath) { + if (!FSEventsFixIsBroken(inpath)) { + return NULL; + } + + // get a mutable copy of an absolute path + char *path = CFURL_realpath(inpath, NULL); + if (!path) { + return NULL; + } + + for (;;) { + char *sep = strrchr(path, '/'); + if ((sep == NULL) || (sep == path)) { + break; + } + *sep = 0; + if (!_FSEventsFixIsBroken_noresolve(path)) { + *sep = '/'; + break; + } + } + + return path; +} + +static void _FSEventsFixAttemptRepair(const char *folder) { + int rv = rename(folder, folder); + + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateRepair)) { + const char *pos = strstr(folder, FSEventsFixSimulatedBrokenFolderMarker); + if (pos) { + char *fixed = strdup(folder); + fixed[pos - folder] = 0; + strcat(fixed, pos + strlen(FSEventsFixSimulatedBrokenFolderMarker)); + + rv = rename(folder, fixed); + free(fixed); + } + } + + if (rv != 0) { + if (errno == EPERM) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Permission error when trying to repair '%s'", folder); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeExpectedFailure, "Unknown error when trying to repair '%s': errno = %d", folder, errno); + } + } +} + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *inpath) { + char *root = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (root == NULL) { + return FSEventsFixRepairStatusNotBroken; + } + + for (;;) { + _FSEventsFixAttemptRepair(root); + char *newRoot = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (newRoot == NULL) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Repaired '%s' in '%s'", root, inpath); + free(root); + return FSEventsFixRepairStatusRepaired; + } + if (0 == strcmp(root, newRoot)) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Failed to repair '%s' in '%s'", root, inpath); + free(root); + free(newRoot); + return FSEventsFixRepairStatusFailed; + } + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Partial success, repaired '%s' in '%s'", root, inpath); + free(root); + root = newRoot; + } +} + + +#pragma mark - FSEventsFix realpath wrapper + +static char *FSEventsFix_realpath_wrapper(const char * __restrict src, char * __restrict dst) { + if (g_in_self_test) { + if (strstr(src, "__!FSEventsFixSelfTest!__")) { + g_hook_operational = true; + } + } + + // CFURL_realpath doesn't support putting where resolution failed into the + // dst buffer, so we call the original realpath here first and if it gets a + // result, replace that with the output of CFURL_realpath. that way all the + // features of the original realpath are available. + char *rv = NULL; + char *orv = orig_realpath(src, dst); + if (orv != NULL) { rv = CFURL_realpath(src, dst); } + + if (!!(g_debug_opt & FSEventsFixDebugOptionLogCalls)) { + char *result = rv ?: dst; + _FSEventsFixLog(FSEventsFixMessageTypeCall, "realpath(%s) => %s\n", src, result); + } + + if (!!(g_debug_opt & FSEventsFixDebugOptionUppercaseReturn)) { + char *result = rv ?: dst; + if (result) { + for (char *pch = result; *pch; ++pch) { + *pch = (char)toupper(*pch); + } + } + } + + return rv; +} + + +#pragma mark - realpath + +// naive implementation of realpath on top of CFURL +// NOTE: doesn't quite support the full range of errno results one would +// expect here, in part because some of these functions just return a boolean, +// and in part because i'm not dealing with messy CFErrorRef objects and +// attempting to translate those to sane errno values. +// NOTE: the OSX realpath will return _where_ resolution failed in resolved_name +// if passed in and return NULL. we can't properly support that extension here +// since the resolution happens entirely behind the scenes to us in CFURL. +static char* CFURL_realpath(const char *file_name, char resolved_name[PATH_MAX]) +{ + char* resolved; + CFURLRef url1; + CFURLRef url2; + CFStringRef path; + + if (file_name == NULL) { + errno = EINVAL; + return (NULL); + } + +#if __DARWIN_UNIX03 + if (*file_name == 0) { + errno = ENOENT; + return (NULL); + } +#endif + + // create a buffer to store our result if we weren't passed one + if (!resolved_name) { + if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL); + } else { + resolved = resolved_name; + } + + url1 = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)file_name, (CFIndex)strlen(file_name), false); + if (url1 == NULL) { goto error_return; } + + url2 = CFURLCopyAbsoluteURL(url1); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + url1 = CFURLCreateFileReferenceURL(NULL, url2, NULL); + CFRelease(url2); + if (url1 == NULL) { goto error_return; } + + // if there are multiple hard links to the original path, this may end up + // being _completely_ different from what was intended + url2 = CFURLCreateFilePathURL(NULL, url1, NULL); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + path = CFURLCopyFileSystemPath(url2, kCFURLPOSIXPathStyle); + CFRelease(url2); + if (path == NULL) { goto error_return; } + + bool success = CFStringGetCString(path, resolved, PATH_MAX, kCFStringEncodingUTF8); + CFRelease(path); + if (!success) { goto error_return; } + + return resolved; + +error_return: + if (!resolved_name) { + // we weren't passed in an output buffer and created our own. free it + int e = errno; + free(resolved); + errno = e; + } + return (NULL); +} + + +#pragma mark - fishhook + +// Copyright (c) 2013, Facebook, Inc. +// 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 Facebook 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 COPYRIGHT HOLDER 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. + +#import +#import +#import +#import +#import +#import + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +static volatile bool g_hook_installed = false; + +static void _FSEventsFixHookUpdateSection(section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) +{ + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + if (strcmp(symbol_name, cur->name) == 0) { + if (g_hook_installed) { + if (indirect_symbol_bindings[i] != cur->replacement) { + indirect_symbol_bindings[i] = cur->replacement; + ++cur->hooked_symbols; + } + } else if (cur->original != NULL) { + if (indirect_symbol_bindings[i] == cur->replacement) { + indirect_symbol_bindings[i] = cur->original; + if (cur->hooked_symbols > 0) { + --cur->hooked_symbols; + } + } + } + goto symbol_loop; + } + } + symbol_loop:; + } +} + +static void _FSEventsFixHookUpdateImage(const struct mach_header *header, intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _FSEventsFixHookSaveOriginals() { + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + void *original = cur->original = dlsym(RTLD_DEFAULT, cur->name+1); + if (!original) { + const char *error = dlerror(); + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Cannot find symbol %s, dlsym says: %s\n", cur->name, error); + } + } +} + +static void _FSEventsFixHookUpdate() { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _FSEventsFixHookUpdateImage(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } +} + +static void _FSEventsFixHookInstall() { + static bool first_rebinding_done = false; + + if (!g_hook_installed) { + g_hook_installed = true; + + if (!first_rebinding_done) { + first_rebinding_done = true; + _FSEventsFixHookSaveOriginals(); + _dyld_register_func_for_add_image(_FSEventsFixHookUpdateImage); + } else { + _FSEventsFixHookUpdate(); + } + } +} + +static void _FSEventsFixHookUninstall() { + if (g_hook_installed) { + g_hook_installed = false; + _FSEventsFixHookUpdate(); + } +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.h new file mode 100644 index 0000000000..b70b8800c7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/FSEventsFix.h @@ -0,0 +1,105 @@ +/* + * FSEventsFix + * + * Works around a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X releases (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * See .c file for license & copyrights, but basically this is available under a mix + * of MIT and BSD licenses. + */ + +#ifndef __FSEventsFix__ +#define __FSEventsFix__ + +#include + +/// A library version string (e.g. 1.2.3) for displaying and logging purposes +extern const char *const FSEventsFixVersionString; + +/// See FSEventsFixDebugOptionSimulateBroken +#define FSEventsFixSimulatedBrokenFolderMarker "__!FSEventsBroken!__" + +typedef CF_OPTIONS(unsigned, FSEventsFixDebugOptions) { + /// Always return an uppercase string from realpath + FSEventsFixDebugOptionUppercaseReturn = 0x01, + + /// Log all calls to realpath using the logger configured via FSEventsFixConfigure + FSEventsFixDebugOptionLogCalls = 0x02, + + /// In addition to the logging block (if any), log everything to stderr + FSEventsFixDebugOptionLogToStderr = 0x08, + + /// Report paths containing FSEventsFixSimulatedBrokenFolderMarker as broken + FSEventsFixDebugOptionSimulateBroken = 0x10, + + /// Repair paths containing FSEventsFixSimulatedBrokenFolderMarker by renaming them + FSEventsFixDebugOptionSimulateRepair = 0x20, +}; + +typedef CF_ENUM(int, FSEventsFixMessageType) { + /// Call logging requested via FSEventsFixDebugOptionLogCalls + FSEventsFixMessageTypeCall, + + /// Results of actions like repair, and other pretty verbose, but notable, stuff. + FSEventsFixMessageTypeResult, + + /// Enabled/disabled status change + FSEventsFixMessageTypeStatusChange, + + /// Expected failure (treat as a warning) + FSEventsFixMessageTypeExpectedFailure, + + /// Severe failure that most likely means that the library won't work + FSEventsFixMessageTypeFatalError +}; + +typedef CF_ENUM(int, FSEventsFixRepairStatus) { + FSEventsFixRepairStatusNotBroken, + FSEventsFixRepairStatusRepaired, + FSEventsFixRepairStatusFailed, +}; + +/// Note that the logging block can be called on any dispatch queue. +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType type, const char *message)); + +void FSEventsFixEnable(); +void FSEventsFixDisable(); + +bool FSEventsFixIsOperational(); + +bool FSEventsFixIsBroken(const char *path); + +/// If the path is broken, returns a string identifying the root broken folder, +/// otherwise, returns NULL. You need to free() the returned string. +char *FSEventsFixCopyRootBrokenFolderPath(const char *path); + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *path); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.c new file mode 100644 index 0000000000..6e033d0638 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.c @@ -0,0 +1,373 @@ +// +// TSICTString.c +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#include "TSICTString.h" + + +const char* const TNetstringTypes = ",#^!~}]Z"; +const char* const OTNetstringTypes = ",#^!~{[Z"; +const UInt8 TNetstringSeparator = ':'; + +TSITStringFormat TSITStringDefaultFormat = kTSITStringFormatTNetstring; + +static const CFRange BeginningRange = {0,0}; + +static CFTypeID kCFDataTypeID = -1UL; +static CFTypeID kCFStringTypeID = -1UL; +static CFTypeID kCFNumberTypeID = -1UL; +static CFTypeID kCFBooleanTypeID = -1UL; +static CFTypeID kCFNullTypeID = -1UL; +static CFTypeID kCFArrayTypeID = -1UL; +static CFTypeID kCFDictionaryTypeID = -1UL; + + +__attribute__((constructor)) void Init_TSICTString(void) +{ + kCFDataTypeID = CFDataGetTypeID(); + kCFStringTypeID = CFStringGetTypeID(); + kCFNumberTypeID = CFNumberGetTypeID(); + kCFBooleanTypeID = CFBooleanGetTypeID(); + kCFNullTypeID = CFNullGetTypeID(); + kCFArrayTypeID = CFArrayGetTypeID(); + kCFDictionaryTypeID = CFDictionaryGetTypeID(); +} + + +void TSICTStringSetDefaultFormat(TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + TSITStringDefaultFormat = kTSITStringFormatTNetstring; + } else { + TSITStringDefaultFormat = format; + } +} + +TSITStringFormat TSICTStringGetDefaultFormat(void) +{ + return TSITStringDefaultFormat; +} + + +void TSICTStringDestroy(TStringIRep* rep) +{ + CFRelease(rep->data); + free(rep->length); + free(rep); +} + + +static inline TStringIRep* TSICTStringCreateWithDataOfTypeAndFormat(CFDataRef data, TSITStringTag type, TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + format = TSICTStringGetDefaultFormat(); + } + + TStringIRep* rep = calloc(1, sizeof(TStringIRep)); + rep->data = CFDataCreateCopy(kCFAllocatorDefault, data); + rep->type = type; + rep->format = format; + rep->length = calloc(10, sizeof(char)); + + CFIndex len = CFDataGetLength(rep->data); + if (snprintf(rep->length, 10, "%lu", len)) { + return rep; + } else { + TSICTStringDestroy(rep); + return NULL; + } +} + +static inline CFDataRef TSICTStringCreateDataFromIntermediateRepresentation(TStringIRep* rep) +{ + CFIndex len = CFDataGetLength(rep->data); + CFMutableDataRef buffer = CFDataCreateMutableCopy(kCFAllocatorDefault, (len + 12), rep->data); + UInt8* bufferBytes = CFDataGetMutableBytePtr(buffer); + + size_t prefixLength = strlen(rep->length) + 1; + CFDataReplaceBytes(buffer, BeginningRange, (const UInt8*)rep->length, (CFIndex)prefixLength); + + if (rep->format == kTSITStringFormatTNetstring) { + const UInt8 ftag = (UInt8)TNetstringTypes[rep->type]; + CFDataAppendBytes(buffer, &ftag, 1); + bufferBytes[(prefixLength - 1)] = TNetstringSeparator; + } else if (rep->format == kTSITStringFormatOTNetstring) { + const UInt8 ftag = (UInt8)OTNetstringTypes[rep->type]; + bufferBytes[(prefixLength - 1)] = ftag; + } + + CFDataRef dataRep = CFDataCreateCopy(kCFAllocatorDefault, buffer); + CFRelease(buffer); + + return dataRep; +} + +static inline CFStringRef TSICTStringCreateStringFromIntermediateRepresentation(TStringIRep* rep) +{ + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + CFStringRef string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8); + CFRelease(data); + return string; +} + +static inline void TSICTStringAppendObjectToMutableDataWithFormat(CFTypeRef object, CFMutableDataRef buffer, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* objRep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef objData = TSICTStringCreateDataFromIntermediateRepresentation(objRep); + CFDataAppendBytes(buffer, (CFDataGetBytePtr(objData)), CFDataGetLength(objData)); + CFRelease(objData); + TSICTStringDestroy(objRep); + + CFRelease(object); +} + +static void ArrayBufferAppendCallback(const void* item, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(item, buffer, format); +} + +static void DictionaryBufferAppendCallback(const void* key, const void* value, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(key, buffer, format); + TSICTStringAppendObjectToMutableDataWithFormat(value, buffer, format); +} + + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep) +{ + return TSICTStringCreateDataFromIntermediateRepresentation(rep); +} + +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return data; +} + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep) +{ + return TSICTStringCreateStringFromIntermediateRepresentation(rep); +} + +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFStringRef string = TSICTStringCreateStringFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return string; +} + + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + return TSICTStringCreateNullWithFormat(format); + } + CFRetain(object); + + CFTypeID cfType = CFGetTypeID(object); + TStringIRep* rep = NULL; + + if (cfType == kCFDataTypeID) { + rep = TSICTStringCreateWithDataOfTypeAndFormat(object, kTSITStringTagString, format); + } else if (cfType == kCFStringTypeID) { + rep = TSICTStringCreateWithStringAndFormat(object, format); + } else if (cfType == kCFNumberTypeID) { + rep = TSICTStringCreateWithNumberAndFormat(object, format); + } else if (cfType == kCFBooleanTypeID) { + if (CFBooleanGetValue(object)) { + rep = TSICTStringCreateTrueWithFormat(format); + } else { + rep = TSICTStringCreateFalseWithFormat(format); + } + } else if (cfType == kCFNullTypeID) { + rep = TSICTStringCreateNullWithFormat(format); + } else if (cfType == kCFArrayTypeID) { + rep = TSICTStringCreateWithArrayAndFormat(object, format); + } else if (cfType == kCFDictionaryTypeID) { + rep = TSICTStringCreateWithDictionaryAndFormat(object, format); + } else { + rep = TSICTStringCreateInvalidWithFormat(format); + } + + CFRelease(object); + return rep; +} + +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format) +{ + CFRetain(string); + CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, string, kCFStringEncodingUTF8, '?'); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagString, format); + CFRelease(data); + CFRelease(string); + return rep; +} + +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format) +{ + CFRetain(number); + TSITStringTag tag = kTSITStringTagNumber; + CFDataRef data; + CFNumberType numType = CFNumberGetType(number); + + switch(numType) { + case kCFNumberCharType: + { + int value; + if (CFNumberGetValue(number, kCFNumberIntType, &value)) { + if (value == 0 || value == 1) { + tag = kTSITStringTagBool; + } else { + tag = kTSITStringTagString; + } + } + break; + } + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + { + tag = kTSITStringTagFloat; + break; + } + } + + if (tag == kTSITStringTagBool) { + bool value; + CFNumberGetValue(number, kCFNumberIntType, &value); + if (value) { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + } else { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + } + } else if (tag == kTSITStringTagFloat) { + char buf[32]; + char *p, *e; + double value; + + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%#.15g", value); + + e = buf + strlen(buf); + p = e; + while (p[-1]=='0' && ('0' <= p[-2] && p[-2] <= '9')) { + p--; + } + memmove(p, e, strlen(e)+1); + + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } else { + char buf[32]; + SInt64 value; + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%lli", value); + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, tag, format); + CFRelease(data); + CFRelease(number); + return rep; +} + +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagNull, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagInvalid, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format) +{ + CFRetain(array); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + CFRange all = CFRangeMake(0, CFArrayGetCount(array)); + TStringCollectionCallbackContext cx = {buffer, format}; + CFArrayApplyFunction(array, all, ArrayBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagList, format); + CFRelease(buffer); + CFRelease(array); + return rep; +} + +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format) +{ + CFRetain(dictionary); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + TStringCollectionCallbackContext cx = {buffer, format}; + CFDictionaryApplyFunction(dictionary, DictionaryBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagDict, format); + CFRelease(buffer); + CFRelease(dictionary); + return rep; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.h new file mode 100644 index 0000000000..daf085c32f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/TSICTString.h @@ -0,0 +1,74 @@ +// +// TSICTString.h +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#ifndef TSICTString_H +#define TSICTString_H + +#include + + +typedef enum { + kTSITStringTagString = 0, + kTSITStringTagNumber = 1, + kTSITStringTagFloat = 2, + kTSITStringTagBool = 3, + kTSITStringTagNull = 4, + kTSITStringTagDict = 5, + kTSITStringTagList = 6, + kTSITStringTagInvalid = 7, +} TSITStringTag; + +extern const char* const TNetstringTypes; +extern const char* const OTNetstringTypes; +extern const UInt8 TNetstringSeparator; + +typedef enum { + kTSITStringFormatDefault = 0, + kTSITStringFormatOTNetstring = 1, + kTSITStringFormatTNetstring = 2, +} TSITStringFormat; + +extern TSITStringFormat TSITStringDefaultFormat; + +typedef struct TSITStringIntermediate { + CFDataRef data; + char* length; + TSITStringTag type; + TSITStringFormat format; +} TStringIRep; + +typedef struct { + CFMutableDataRef buffer; + TSITStringFormat format; +} TStringCollectionCallbackContext; + + +void Init_TSICTString(void); + +void TSICTStringSetDefaultFormat(TSITStringFormat format); +TSITStringFormat TSICTStringGetDefaultFormat(void); + +void TSICTStringDestroy(TStringIRep* rep); + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep); +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep); +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format); +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format); + + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.c new file mode 100644 index 0000000000..6d36dd13dd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.c @@ -0,0 +1,201 @@ +#include +#include "cli.h" + +const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; +const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; +const char* cli_info_help[] = { + " -h, --help you're looking at it", + " -V, --version print version number and exit", + " -p, --show-plist display the embedded Info.plist values", + " -s, --since-when=EventID fire historical events since ID", + " -l, --latency=seconds latency period (default='0.5')", + " -n, --no-defer enable no-defer latency modifier", + " -r, --watch-root watch for when the root path has changed", + // " -i, --ignore-self ignore current process", + " -F, --file-events provide file level event data", + " -f, --format=name output format (classic, niw, \n" + " tnetstring, otnetstring)", + 0 +}; + +static void default_args (struct cli_info* args_info) +{ + args_info->since_when_arg = kFSEventStreamEventIdSinceNow; + args_info->latency_arg = 0.5; + args_info->no_defer_flag = false; + args_info->watch_root_flag = false; + args_info->ignore_self_flag = false; + args_info->file_events_flag = false; + args_info->mark_self_flag = false; + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; +} + +static void cli_parser_release (struct cli_info* args_info) +{ + unsigned int i; + + for (i=0; i < args_info->inputs_num; ++i) { + free(args_info->inputs[i]); + } + + if (args_info->inputs_num) { + free(args_info->inputs); + } + + args_info->inputs_num = 0; +} + +void cli_parser_init (struct cli_info* args_info) +{ + default_args(args_info); + + args_info->inputs = 0; + args_info->inputs_num = 0; +} + +void cli_parser_free (struct cli_info* args_info) +{ + cli_parser_release(args_info); +} + +static void cli_print_info_dict (const void *key, + const void *value, + void *context) +{ + CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, + CFSTR("%@:\n %@"), key, value); + if (entry) { + CFShow(entry); + CFRelease(entry); + } +} + +void cli_show_plist (void) +{ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFRetain(mainBundle); + CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); + if (mainBundleDict) { + CFRetain(mainBundleDict); + printf("Embedded Info.plist metadata:\n\n"); + CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); + CFRelease(mainBundleDict); + } + CFRelease(mainBundle); + printf("\n"); +} + +void cli_print_version (void) +{ + printf("%s %s\n\n", CLI_NAME, CLI_VERSION); +#ifdef COMPILED_AT + printf("Compiled at: %s\n", COMPILED_AT); +#endif +#ifdef COMPILER + printf("Compiled with: %s\n", COMPILER); +#endif +#ifdef TARGET_CPU + printf("Compiled for: %s\n", TARGET_CPU); +#endif + printf("\n"); +} + +void cli_print_help (void) +{ + cli_print_version(); + + printf("\n%s\n", cli_info_purpose); + printf("\n%s\n", cli_info_usage); + printf("\n"); + + int i = 0; + while (cli_info_help[i]) { + printf("%s\n", cli_info_help[i++]); + } +} + +int cli_parser (int argc, const char** argv, struct cli_info* args_info) +{ + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "show-plist", no_argument, NULL, 'p' }, + { "since-when", required_argument, NULL, 's' }, + { "latency", required_argument, NULL, 'l' }, + { "no-defer", no_argument, NULL, 'n' }, + { "watch-root", no_argument, NULL, 'r' }, + { "ignore-self", no_argument, NULL, 'i' }, + { "file-events", no_argument, NULL, 'F' }, + { "mark-self", no_argument, NULL, 'm' }, + { "format", required_argument, NULL, 'f' }, + { 0, 0, 0, 0 } + }; + + const char* shortopts = "hVps:l:nriFf:"; + + int c = -1; + + while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 's': // since-when + args_info->since_when_arg = strtoull(optarg, NULL, 0); + break; + case 'l': // latency + args_info->latency_arg = strtod(optarg, NULL); + break; + case 'n': // no-defer + args_info->no_defer_flag = true; + break; + case 'r': // watch-root + args_info->watch_root_flag = true; + break; + case 'i': // ignore-self + args_info->ignore_self_flag = true; + break; + case 'F': // file-events + args_info->file_events_flag = true; + break; + case 'm': // mark-self + args_info->mark_self_flag = true; + break; + case 'f': // format + if (strcmp(optarg, "classic") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatClassic; + } else if (strcmp(optarg, "niw") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatNIW; + } else if (strcmp(optarg, "tnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatTNetstring; + } else if (strcmp(optarg, "otnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; + } else { + fprintf(stderr, "Unknown output format: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'V': // version + cli_print_version(); + exit(EXIT_SUCCESS); + case 'p': // show-plist + cli_show_plist(); + exit(EXIT_SUCCESS); + case 'h': // help + case '?': // invalid option + case ':': // missing argument + cli_print_help(); + exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); + } + } + + if (optind < argc) { + int i = 0; + args_info->inputs_num = (unsigned int)(argc - optind); + args_info->inputs = + (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); + while (optind < argc) + if (argv[optind++] != argv[0]) { + args_info->inputs[i++] = strdup(argv[optind-1]); + } + } + + return EXIT_SUCCESS; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.h new file mode 100644 index 0000000000..2164995a5d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/cli.h @@ -0,0 +1,45 @@ +#ifndef CLI_H +#define CLI_H + +#include "common.h" + +#ifndef CLI_NAME +#define CLI_NAME "fsevent_watch" +#endif /* CLI_NAME */ + +#ifndef PROJECT_VERSION +#error "PROJECT_VERSION not set" +#endif /* PROJECT_VERSION */ + +#ifndef CLI_VERSION +#define CLI_VERSION _xstr(PROJECT_VERSION) +#endif /* CLI_VERSION */ + + +struct cli_info { + UInt64 since_when_arg; + double latency_arg; + bool no_defer_flag; + bool watch_root_flag; + bool ignore_self_flag; + bool file_events_flag; + bool mark_self_flag; + enum FSEventWatchOutputFormat format_arg; + + char** inputs; + unsigned inputs_num; +}; + +extern const char* cli_info_purpose; +extern const char* cli_info_usage; +extern const char* cli_info_help[]; + +void cli_print_help(void); +void cli_print_version(void); + +int cli_parser (int argc, const char** argv, struct cli_info* args_info); +void cli_parser_init (struct cli_info* args_info); +void cli_parser_free (struct cli_info* args_info); + + +#endif /* CLI_H */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/common.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/common.h new file mode 100644 index 0000000000..b2d3e4ebf4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/common.h @@ -0,0 +1,22 @@ +#ifndef fsevent_watch_common_h +#define fsevent_watch_common_h + +#include +#ifdef __OBJC__ +#import +#endif + +#include +#include +#include "compat.h" +#include "defines.h" +#include "TSICTString.h" + +enum FSEventWatchOutputFormat { + kFSEventWatchOutputFormatClassic, + kFSEventWatchOutputFormatNIW, + kFSEventWatchOutputFormatTNetstring, + kFSEventWatchOutputFormatOTNetstring +}; + +#endif /* fsevent_watch_common_h */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.c new file mode 100644 index 0000000000..5f51baff11 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.c @@ -0,0 +1,41 @@ +#include "compat.h" + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; +FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; +FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; +FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink = 0x00100000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsLastHardlink = 0x00200000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData = 0x00000040; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned = 0x00400000; +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.h new file mode 100644 index 0000000000..757b413565 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/compat.h @@ -0,0 +1,100 @@ +/** + * @headerfile compat.h + * FSEventStream flag compatibility shim + * + * In order to compile a binary against an older SDK yet still support the + * features present in later OS releases, we need to define any missing enum + * constants not present in the older SDK. This allows us to safely defer + * feature detection to runtime (and avoid recompilation). + */ + + +#ifndef listen_fsevents_compat_h +#define listen_fsevents_compat_h + +#ifndef __CORESERVICES__ +#include +#endif // __CORESERVICES__ + +#ifndef __AVAILABILITY__ +#include +#endif // __AVAILABILITY__ + +#ifndef __MAC_10_6 +#define __MAC_10_6 1060 +#endif +#ifndef __MAC_10_7 +#define __MAC_10_7 1070 +#endif +#ifndef __MAC_10_9 +#define __MAC_10_9 1090 +#endif +#ifndef __MAC_10_10 +#define __MAC_10_10 101000 +#endif +#ifndef __MAC_10_13 +#define __MAC_10_13 101300 +#endif +#ifndef __IPHONE_6_0 +#define __IPHONE_6_0 60000 +#endif +#ifndef __IPHONE_7_0 +#define __IPHONE_7_0 70000 +#endif +#ifndef __IPHONE_9_0 +#define __IPHONE_9_0 90000 +#endif +#ifndef __IPHONE_11_0 +#define __IPHONE_11_0 110000 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, + kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemInodeMetaMod, + kFSEventStreamEventFlagItemRenamed, + kFSEventStreamEventFlagItemModified, + kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemChangeOwner, + kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagItemIsFile, + kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsSymlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; +extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink, + kFSEventStreamEventFlagItemIsLastHardlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned; +#endif + + +#ifdef __cplusplus +} +#endif + +#endif // listen_fsevents_compat_h diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/defines.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/defines.h new file mode 100644 index 0000000000..34879c58d0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/defines.h @@ -0,0 +1,42 @@ +#ifndef fsevent_watch_defines_h +#define fsevent_watch_defines_h + +#define _str(s) #s +#define _xstr(s) _str(s) + +#define COMPILED_AT __DATE__ " " __TIME__ + +#if defined (__clang__) +#define COMPILER "clang " __clang_version__ +#elif defined (__GNUC__) +#define COMPILER "gcc " __VERSION__ +#else +#define COMPILER "unknown" +#endif + +#if defined(__ppc__) +#define TARGET_CPU "ppc" +#elif defined(__ppc64__) +#define TARGET_CPU "ppc64" +#elif defined(__i386__) +#define TARGET_CPU "i386" +#elif defined(__x86_64__) +#define TARGET_CPU "x86_64" +#elif defined(__arm64__) +#define TARGET_CPU "arm64" +#else +#define TARGET_CPU "unknown" +#endif + +#define FLAG_CHECK(flags, flag) ((flags) & (flag)) + +#define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ + do { \ + if (FLAG_CHECK(flags, flag)) { \ + fprintf(fd, "%s", msg "\n"); } } \ + while (0) + +#define FLAG_CHECK_STDERR(flags, flag, msg) \ + FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) + +#endif /* fsevent_watch_defines_h */ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/main.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/main.c new file mode 100644 index 0000000000..b18596a603 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/main.c @@ -0,0 +1,548 @@ +#include "common.h" +#include "signal_handlers.h" +#include "cli.h" +#include "FSEventsFix.h" + +// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's +// no need to set those here. also, in order to scope metadata by path, +// each stream will need its own configuration... so this won't work as +// a global any more. In the end the goal is to make the output format +// able to declare not just that something happened and what flags were +// attached, but what path it was watching that caused those events (so +// that the path itself can be used for routing that information to the +// relevant callback). +// +// Structure for storing metadata parsed from the commandline +static struct { + FSEventStreamEventId sinceWhen; + CFTimeInterval latency; + FSEventStreamCreateFlags flags; + CFMutableArrayRef paths; + enum FSEventWatchOutputFormat format; +} config = { + (UInt64) kFSEventStreamEventIdSinceNow, + (double) 0.3, + (CFOptionFlags) kFSEventStreamCreateFlagNone, + NULL, + kFSEventWatchOutputFormatOTNetstring +}; + +// Prototypes +static void append_path(const char* path); +static inline void parse_cli_settings(int argc, const char* argv[]); +static void callback(FSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]); +static bool needs_fsevents_fix = false; + +// Resolve a path and append it to the CLI settings structure +// The FSEvents API will, internally, resolve paths using a similar scheme. +// Performing this ahead of time makes things less confusing, IMHO. +static void append_path(const char* path) +{ +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "append_path called for: %s\n", path); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + +#ifdef DEBUG + fprintf(stderr, "compiled against 10.6+, using CFURLCreateFileReferenceURL\n"); +#endif + + CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)path, (CFIndex)strlen(path), false); + CFURLRef placeholder = CFURLCopyAbsoluteURL(url); + CFRelease(url); + + CFMutableArrayRef imaginary = NULL; + + // if we don't have an existing url, spin until we get to a parent that + // does exist, saving any imaginary components for appending back later + while(!CFURLResourceIsReachable(placeholder, NULL)) { +#ifdef DEBUG + fprintf(stderr, "path does not exist\n"); +#endif + + CFStringRef child; + + if (imaginary == NULL) { + imaginary = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + + child = CFURLCopyLastPathComponent(placeholder); + CFArrayInsertValueAtIndex(imaginary, 0, child); + CFRelease(child); + + url = CFURLCreateCopyDeletingLastPathComponent(NULL, placeholder); + CFRelease(placeholder); + placeholder = url; + +#ifdef DEBUG + fprintf(stderr, "parent: "); + CFShow(placeholder); +#endif + } + +#ifdef DEBUG + fprintf(stderr, "path exists\n"); +#endif + + // realpath() doesn't always return the correct case for a path, so this + // is a funky workaround that converts a path into a (volId/inodeId) pair + // and asks what the path should be for that. since it looks at the actual + // inode instead of returning the same case passed in like realpath() + // appears to do for HFS+, it should always be correct. + url = CFURLCreateFileReferenceURL(NULL, placeholder, NULL); + CFRelease(placeholder); + placeholder = CFURLCreateFilePathURL(NULL, url, NULL); + CFRelease(url); + +#ifdef DEBUG + fprintf(stderr, "path resolved to: "); + CFShow(placeholder); +#endif + + // if we stripped off any imaginary path components, append them back on + if (imaginary != NULL) { + CFIndex count = CFArrayGetCount(imaginary); + for (CFIndex i = 0; i= 6)) { + config.flags |= kFSEventStreamCreateFlagIgnoreSelf; + } else { + fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.file_events_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 7)) { + config.flags |= kFSEventStreamCreateFlagFileEvents; + } else { + fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.mark_self_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 9)) { + config.flags |= kFSEventStreamCreateFlagMarkSelf; + } else { + fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.inputs_num == 0) { + append_path("."); + } else { + for (unsigned int i=0; i < args_info.inputs_num; ++i) { + append_path(args_info.inputs[i]); + } + } + + cli_parser_free(&args_info); + +#ifdef DEBUG + fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); + fprintf(stderr, "config.latency %f\n", config.latency); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, "config.flags %#.8x\n", config.flags); +#else + fprintf(stderr, "config.flags %#.8lx\n", config.flags); +#endif + + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, + " Using CF instead of C types"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, + " NoDefer latency modifier enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, + " WatchRoot notifications enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, + " IgnoreSelf enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, + " FileEvents enabled"); + + fprintf(stderr, "config.paths\n"); + + long numpaths = CFArrayGetCount(config.paths); + + for (long i = 0; i < numpaths; i++) { + char path[PATH_MAX]; + CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), + path, + PATH_MAX, + kCFStringEncodingUTF8); + fprintf(stderr, " %s\n", path); + } + + fprintf(stderr, "\n"); +#endif +} + +// original output format for rb-fsevent +static void classic_output_format(size_t numEvents, + char** paths) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%s:", paths[i]); + } + fprintf(stdout, "\n"); +} + +// output format used in the Yoshimasa Niwa branch of rb-fsevent +static void niw_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%lu:%llu:%s\n", + (unsigned long)eventFlags[i], + (unsigned long long)eventIds[i], + paths[i]); + } + fprintf(stdout, "\n"); +} + +static void tstring_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[], + TSITStringFormat format) +{ + CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < numEvents; i++) { + CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)paths[i], + (CFIndex)strlen(paths[i]), + kCFStringEncodingUTF8, + false); + CFDictionarySetValue(event, CFSTR("path"), path); + + CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]); + CFDictionarySetValue(event, CFSTR("id"), ident); + + CFNumberRef cflags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]); + CFDictionarySetValue(event, CFSTR("cflags"), cflags); + + CFMutableArrayRef flags = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + +#define FLAG_ADD_NAME(flagsnum, flagnum, flagname, flagarray) \ + do { \ + if (FLAG_CHECK(flagsnum, flagnum)) { \ + CFArrayAppendValue(flagarray, CFSTR(flagname)); } } \ + while(0) + + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, "MustScanSubDirs", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUserDropped, "UserDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagKernelDropped, "KernelDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, "EventIdsWrapped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagHistoryDone, "HistoryDone", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagRootChanged, "RootChanged", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMount, "Mount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUnmount, "Unmount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemCreated, "ItemCreated", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRemoved, "ItemRemoved", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, "ItemInodeMetaMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRenamed, "ItemRenamed", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemModified, "ItemModified", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, "ItemFinderInfoMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, "ItemChangeOwner", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, "ItemXattrMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsFile, "ItemIsFile", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsDir, "ItemIsDir", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, "ItemIsSymlink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagOwnEvent, "OwnEvent", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, "ItemIsHardLink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, "ItemIsLastHardLink", flags); + + CFDictionarySetValue(event, CFSTR("flags"), flags); + + + CFArrayAppendValue(events, event); + + CFRelease(event); + CFRelease(path); + CFRelease(ident); + CFRelease(cflags); + CFRelease(flags); + } + + CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionarySetValue(meta, CFSTR("events"), events); + + CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents); + CFDictionarySetValue(meta, CFSTR("numEvents"), num); + + CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format); + fprintf(stdout, "%s", CFDataGetBytePtr(data)); + + CFRelease(events); + CFRelease(num); + CFRelease(meta); + CFRelease(data); +} + +static void callback(__attribute__((unused)) FSEventStreamRef streamRef, + __attribute__((unused)) void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + char** paths = eventPaths; + + +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "FSEventStreamCallback fired!\n"); + fprintf(stderr, " numEvents: %lu\n", numEvents); + + for (size_t i = 0; i < numEvents; i++) { + fprintf(stderr, "\n"); + fprintf(stderr, " event ID: %llu\n", eventIds[i]); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]); +#else + fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]); +#endif + + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, + " Recursive scanning of directory required"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped, + " Buffering problem: events dropped user-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped, + " Buffering problem: events dropped kernel-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, + " Event IDs have wrapped"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone, + " All historical events have been processed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged, + " Root path has changed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount, + " A new volume was mounted at this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount, + " A volume was unmounted from this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated, + " Item created"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved, + " Item removed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, + " Item metadata modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed, + " Item renamed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified, + " Item modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, + " Item Finder Info modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, + " Item changed ownership"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, + " Item extended attributes modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile, + " Item is a file"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir, + " Item is a directory"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, + " Item is a symbolic link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, + " Item is a hard link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, + " Item is the last hard link"); + fprintf(stderr, " event path: %s\n", paths[i]); + fprintf(stderr, "\n"); + } + + fprintf(stderr, "\n"); +#endif + + if (config.format == kFSEventWatchOutputFormatClassic) { + classic_output_format(numEvents, paths); + } else if (config.format == kFSEventWatchOutputFormatNIW) { + niw_output_format(numEvents, paths, eventFlags, eventIds); + } else if (config.format == kFSEventWatchOutputFormatTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatTNetstring); + } else if (config.format == kFSEventWatchOutputFormatOTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatOTNetstring); + } + + fflush(stdout); +} + +int main(int argc, const char* argv[]) +{ + install_signal_handlers(); + parse_cli_settings(argc, argv); + + if (needs_fsevents_fix) { + FSEventsFixEnable(); + } + + FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; + FSEventStreamRef stream; + stream = FSEventStreamCreate(kCFAllocatorDefault, + (FSEventStreamCallback)&callback, + &context, + config.paths, + config.sinceWhen, + config.latency, + config.flags); + +#ifdef DEBUG + FSEventStreamShow(stream); + fprintf(stderr, "\n"); +#endif + + if (needs_fsevents_fix) { + FSEventsFixDisable(); + } + + FSEventStreamScheduleWithRunLoop(stream, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + FSEventStreamStart(stream); + CFRunLoopRun(); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); + + return 0; +} diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.c b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.c new file mode 100644 index 0000000000..b20da3f381 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.c @@ -0,0 +1,66 @@ +#include "signal_handlers.h" +#include +#include +#include +#include +#include +#include + + +#define PPID_ALARM_INTERVAL 2 // send SIGALRM every this seconds + + +static pid_t orig_ppid; + + +static void signal_handler(int _) { + exit(EXIT_FAILURE); +} + +static void check_ppid(void) { + if (getppid() != orig_ppid) { + exit(EXIT_FAILURE); + } +} + +static void check_stdout_open(void) { + if (fcntl(STDOUT_FILENO, F_GETFD) < 0) { + exit(EXIT_FAILURE); + } +} + +static void alarm_handler(int _) { + check_ppid(); + check_stdout_open(); + alarm(PPID_ALARM_INTERVAL); + signal(SIGALRM, alarm_handler); +} + +static void die(const char *msg) { + fprintf(stderr, "\nFATAL: %s\n", msg); + abort(); +} + +static void install_signal_handler(int sig, void (*handler)(int)) { + if (signal(sig, handler) == SIG_ERR) { + die("Could not install signal handler"); + } +} + +void install_signal_handlers(void) { + // check pipe is still connected + check_stdout_open(); + + // watch getppid() every PPID_ALARM_INTERVAL seconds + orig_ppid = getppid(); + if (orig_ppid <= 1) { + die("prematurely zombied"); + } + install_signal_handler(SIGALRM, alarm_handler); + alarm(PPID_ALARM_INTERVAL); + + // be sure to exit on SIGHUP, SIGPIPE + install_signal_handler(SIGHUP, signal_handler); + install_signal_handler(SIGPIPE, signal_handler); +} + diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.h b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.h new file mode 100644 index 0000000000..c31685d9ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/fsevent_watch/signal_handlers.h @@ -0,0 +1,16 @@ +/** + * @headerfile signal_handlers.h + * Signal handlers to stop the zombie hordes + * + * Catch and handle signals better so that we die faster like a good meat puppet. + */ + + +#ifndef fsevent_watch_signal_handlers_h +#define fsevent_watch_signal_handlers_h + + +void install_signal_handlers(void); + + +#endif // fsevent_watch_signal_handlers_h diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/rakefile.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/rakefile.rb new file mode 100644 index 0000000000..e50e888742 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/ext/rakefile.rb @@ -0,0 +1,231 @@ +# -*- encoding: utf-8 -*- +require 'rubygems' unless defined?(Gem) +require 'pathname' +require 'date' +require 'time' +require 'rake/clean' + +raise "unable to find xcodebuild" unless system('which', 'xcodebuild') + + +FSEVENT_WATCH_EXE_VERSION = '0.1.5' + +$this_dir = Pathname.new(__FILE__).dirname.expand_path +$final_exe = $this_dir.parent.join('bin/fsevent_watch') + +$src_dir = $this_dir.join('fsevent_watch') +$obj_dir = $this_dir.join('build') + +SRC = Pathname.glob("#{$src_dir}/*.c") +OBJ = SRC.map {|s| $obj_dir.join("#{s.basename('.c')}.o")} + +$now = DateTime.now.xmlschema rescue Time.now.xmlschema + +$CC = ENV['CC'] || `which clang || which gcc`.strip +$CFLAGS = ENV['CFLAGS'] || '-fconstant-cfstrings -fasm-blocks -fstrict-aliasing -Wall' +$ARCHFLAGS = ENV['ARCHFLAGS'] || '-arch x86_64' +$DEFINES = "-DNS_BUILD_32_LIKE_64 -DNS_BLOCK_ASSERTIONS -DPROJECT_VERSION=#{FSEVENT_WATCH_EXE_VERSION}" + +$GCC_C_LANGUAGE_STANDARD = ENV['GCC_C_LANGUAGE_STANDARD'] || 'gnu11' + +# generic developer id name so it'll match correctly for anyone who has only +# one developer id in their keychain (not that I expect anyone else to bother) +$CODE_SIGN_IDENTITY = 'Developer ID Application' + +$arch = `uname -m`.strip +$os_release = `uname -r`.strip +$BUILD_TRIPLE = "#{$arch}-apple-darwin#{$os_release}" + +$CCVersion = `#{$CC} --version | head -n 1`.strip + + +CLEAN.include OBJ.map(&:to_s) +CLEAN.include $obj_dir.join('Info.plist').to_s +CLEAN.include $obj_dir.join('fsevent_watch').to_s +CLOBBER.include $final_exe.to_s + + +task :sw_vers do + $mac_product_version = `sw_vers -productVersion`.strip + $mac_build_version = `sw_vers -buildVersion`.strip + $MACOSX_DEPLOYMENT_TARGET = ENV['MACOSX_DEPLOYMENT_TARGET'] || $mac_product_version.sub(/\.\d*$/, '') + $CFLAGS = "#{$CFLAGS} -mmacosx-version-min=#{$MACOSX_DEPLOYMENT_TARGET}" +end + +task :get_sdk_info => :sw_vers do + $SDK_INFO = {} + version_info = `xcodebuild -version -sdk macosx#{$MACOSX_DEPLOYMENT_TARGET}` + raise "invalid SDK" unless !!$?.exitstatus + version_info.strip.each_line do |line| + next if line.strip.empty? + next unless line.include?(':') + match = line.match(/([^:]*): (.*)/) + next unless match + $SDK_INFO[match[1]] = match[2] + end +end + +task :debug => :sw_vers do + $DEFINES = "-DDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -O0 -fno-omit-frame-pointer -g" +end + +task :release => :sw_vers do + $DEFINES = "-DNDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -Ofast" +end + +desc 'configure build type depending on whether ENV var FWDEBUG is set' +task :set_build_type => :sw_vers do + if ENV['FWDEBUG'] + Rake::Task[:debug].invoke + else + Rake::Task[:release].invoke + end +end + +desc 'set build arch to ppc' +task :ppc do + $ARCHFLAGS = '-arch ppc' +end + +desc 'set build arch to x86_64' +task :x86_64 do + $ARCHFLAGS = '-arch x86_64' +end + +desc 'set build arch to i386' +task :x86 do + $ARCHFLAGS = '-arch i386' +end + +desc 'set build arch to arm64' +task :arm64 do + $ARCHFLAGS = '-arch arm64' +end + +task :setup_env => [:set_build_type, :sw_vers, :get_sdk_info] + +directory $obj_dir.to_s +file $obj_dir.to_s => :setup_env + +SRC.zip(OBJ).each do |source, object| + file object.to_s => [source.to_s, $obj_dir.to_s] do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-c', source, + '-o', object + ] + sh(cmd.map {|s| s.to_s}.join(' ')) + end +end + +file $obj_dir.join('Info.plist').to_s => [$obj_dir.to_s, :setup_env] do + File.open($obj_dir.join('Info.plist').to_s, 'w+') do |file| + indentation = '' + indent = lambda {|num| indentation = ' ' * num } + add = lambda {|str| file << "#{indentation}#{str}\n" } + key = lambda {|str| add["#{str}"] } + string = lambda {|str| add["#{str}"] } + + + add[''] + add[''] + add[''] + + indent[2] + add[''] + indent[4] + + key['CFBundleExecutable'] + string['fsevent_watch'] + key['CFBundleIdentifier'] + string['com.teaspoonofinsanity.fsevent_watch'] + key['CFBundleName'] + string['fsevent_watch'] + key['CFBundleDisplayName'] + string['FSEvent Watch CLI'] + key['NSHumanReadableCopyright'] + string['Copyright (C) 2011-2017 Travis Tilley'] + + key['CFBundleVersion'] + string["#{FSEVENT_WATCH_EXE_VERSION}"] + key['LSMinimumSystemVersion'] + string["#{$MACOSX_DEPLOYMENT_TARGET}"] + key['DTSDKBuild'] + string["#{$SDK_INFO['ProductBuildVersion']}"] + key['DTSDKName'] + string["macosx#{$SDK_INFO['SDKVersion']}"] + key['DTSDKPath'] + string["#{$SDK_INFO['Path']}"] + key['BuildMachineOSBuild'] + string["#{$mac_build_version}"] + key['BuildMachineOSVersion'] + string["#{$mac_product_version}"] + key['FSEWCompiledAt'] + string["#{$now}"] + key['FSEWVersionInfoBuilder'] + string["#{`whoami`.strip}"] + key['FSEWBuildTriple'] + string["#{$BUILD_TRIPLE}"] + key['FSEWCC'] + string["#{$CC}"] + key['FSEWCCVersion'] + string["#{$CCVersion}"] + key['FSEWCFLAGS'] + string["#{$CFLAGS}"] + + indent[2] + add[''] + indent[0] + + add[''] + end +end + +desc 'generate an Info.plist used for code signing as well as embedding build settings into the resulting binary' +task :plist => $obj_dir.join('Info.plist').to_s + + +file $obj_dir.join('fsevent_watch').to_s => [$obj_dir.to_s, $obj_dir.join('Info.plist').to_s] + OBJ.map(&:to_s) do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-framework CoreFoundation -framework CoreServices', + '-sectcreate __TEXT __info_plist', + $obj_dir.join('Info.plist') + ] + OBJ + [ + '-o', $obj_dir.join('fsevent_watch') + ] + sh(cmd.map {|s| s.to_s}.join(' ')) +end + +desc 'compile and link build/fsevent_watch' +task :build => $obj_dir.join('fsevent_watch').to_s + +desc 'codesign build/fsevent_watch binary' +task :codesign => :build do + sh "codesign -s '#{$CODE_SIGN_IDENTITY}' #{$obj_dir.join('fsevent_watch')}" +end + +directory $this_dir.parent.join('bin') + +desc 'replace bundled fsevent_watch binary with build/fsevent_watch' +task :replace_exe => [$this_dir.parent.join('bin'), :build] do + sh "mv #{$obj_dir.join('fsevent_watch')} #{$final_exe}" +end + +task :default => [:replace_exe, :clean] diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/otnetstring.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/otnetstring.rb new file mode 100644 index 0000000000..7623d677ed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/otnetstring.rb @@ -0,0 +1,85 @@ +# Copyright (c) 2011 Konstantin Haase +# +# 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 'stringio' + +module OTNetstring + class Error < StandardError; end + + class << self + def parse(io, encoding = 'internal', fallback_encoding = nil) + fallback_encoding = io.encoding if io.respond_to? :encoding + io = StringIO.new(io) if io.respond_to? :to_str + length, byte = String.new, nil + + while byte.nil? || byte =~ /\d/ + length << byte if byte + byte = io.read(1) + end + + if length.size > 9 + raise Error, "#{length} is longer than 9 digits" + elsif length !~ /\d+/ + raise Error, "Expected '#{byte}' to be a digit" + end + length = Integer(length) + + case byte + when '#' then Integer io.read(length) + when ',' then with_encoding io.read(length), encoding, fallback_encoding + when '~' then + raise Error, "nil has length of 0, #{length} given" unless length == 0 + when '!' then io.read(length) == 'true' + when '[', '{' + array = [] + start = io.pos + array << parse(io, encoding, fallback_encoding) while io.pos - start < length + raise Error, 'Nested element longer than container' if io.pos - start != length + byte == "{" ? Hash[*array] : array + else + raise Error, "Unknown type '#{byte}'" + end + end + + def encode(obj, string_sep = ',') + case obj + when String then with_encoding "#{obj.bytesize}#{string_sep}#{obj}", "binary" + when Integer then encode(obj.inspect, '#') + when NilClass then "0~" + when Array then encode(obj.map { |e| encode(e) }.join, '[') + when Hash then encode(obj.map { |a,b| encode(a)+encode(b) }.join, '{') + when FalseClass, TrueClass then encode(obj.inspect, '!') + else raise Error, 'cannot encode %p' % obj + end + end + + private + + def with_encoding(str, encoding, fallback = nil) + return str unless str.respond_to? :encode + encoding = Encoding.find encoding if encoding.respond_to? :to_str + encoding ||= fallback + encoding ? str.encode(encoding) : str + rescue EncodingError + str.force_encoding(encoding) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent.rb new file mode 100644 index 0000000000..1ff68a3095 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent.rb @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- +require 'rb-fsevent/fsevent' +require 'rb-fsevent/version' diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/fsevent.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/fsevent.rb new file mode 100644 index 0000000000..fac2364819 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/fsevent.rb @@ -0,0 +1,157 @@ +# -*- encoding: utf-8 -*- + +require 'otnetstring' + +class FSEvent + class << self + class_eval <<-END + def root_path + "#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}" + end + END + class_eval <<-END + def watcher_path + "#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}" + end + END + end + + attr_reader :paths, :callback + + def initialize args = nil, &block + watch(args, &block) unless args.nil? + end + + def watch(watch_paths, options=nil, &block) + @paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths] + @callback = block + + if options.kind_of?(Hash) + @options = parse_options(options) + elsif options.kind_of?(Array) + @options = options + else + @options = [] + end + end + + def run + @pipe = open_pipe + @running = true + + # please note the use of IO::select() here, as it is used specifically to + # preserve correct signal handling behavior in ruby 1.8. + while @running && IO::select([@pipe], nil, nil, nil) + # managing the IO ourselves allows us to be careful and never pass an + # incomplete message to OTNetstring.parse() + message = String.new + length = String.new + byte = nil + + reading_length = true + found_length = false + + while reading_length + byte = @pipe.read_nonblock(1) + if "#{byte}" =~ /\d/ + length << byte + found_length = true + elsif found_length == false + next + else + reading_length = false + end + end + length = Integer(length, 10) + type = byte + + message << "#{length}#{type}" + message << @pipe.read(length) + + decoded = OTNetstring.parse(message) + modified_paths = decoded["events"].map {|event| event["path"]} + # passing the full info as a second block param feels icky, but such is + # the trap of backward compatibility. + case callback.arity + when 1 + callback.call(modified_paths) + when 2 + callback.call(modified_paths, decoded) + end + end + rescue Interrupt, IOError, Errno::EBADF + ensure + stop + end + + def stop + unless @pipe.nil? + Process.kill('KILL', @pipe.pid) if process_running?(@pipe.pid) + @pipe.close + end + rescue IOError, Errno::EBADF + ensure + @running = false + end + + def process_running?(pid) + begin + Process.kill(0, pid) + true + rescue Errno::ESRCH + false + end + end + + if RUBY_VERSION < '1.9' + def open_pipe + IO.popen("'#{self.class.watcher_path}' #{options_string} #{shellescaped_paths}") + end + + private + + def options_string + @options.join(' ') + end + + def shellescaped_paths + @paths.map {|path| shellescape(path)}.join(' ') + end + + # for Ruby 1.8.6 support + def shellescape(str) + # An empty argument will be skipped, so return empty quotes. + return "''" if str.empty? + + str = str.dup + + # Process as a single byte sequence because not all shell + # implementations are multibyte aware. + str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") + + # A LF cannot be escaped with a backslash because a backslash + LF + # combo is regarded as line continuation and simply ignored. + str.gsub!(/\n/, "'\n'") + + return str + end + else + def open_pipe + IO.popen([self.class.watcher_path] + @options + @paths) + end + end + + private + + def parse_options(options={}) + opts = ['--format=otnetstring'] + opts.concat(['--since-when', options[:since_when]]) if options[:since_when] + opts.concat(['--latency', options[:latency]]) if options[:latency] + opts.push('--no-defer') if options[:no_defer] + opts.push('--watch-root') if options[:watch_root] + opts.push('--file-events') if options[:file_events] + # ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings + opts.map {|opt| "#{opt}"} + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/version.rb b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/version.rb new file mode 100644 index 0000000000..332be3bc84 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/lib/rb-fsevent/version.rb @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- + +class FSEvent + VERSION = '0.11.2' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/rb-fsevent.gemspec b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/rb-fsevent.gemspec new file mode 100644 index 0000000000..8f1e5aae45 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-fsevent-0.11.2/rb-fsevent.gemspec @@ -0,0 +1,26 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rb-fsevent/version' + +Gem::Specification.new do |s| + s.name = 'rb-fsevent' + s.version = FSEvent::VERSION + s.authors = ['Thibaud Guillaume-Gentil', 'Travis Tilley'] + s.email = ['thibaud@thibaud.gg', 'ttilley@gmail.com'] + s.homepage = 'http://rubygems.org/gems/rb-fsevent' + s.summary = 'Very simple & usable FSEvents API' + s.description = 'FSEvents API with Signals catching (without RubyCocoa)' + s.license = 'MIT' + + s.metadata = { + 'source_code_uri' => 'https://github.com/thibaudgg/rb-fsevent' + } + + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) } + s.require_path = 'lib' + + s.add_development_dependency 'rspec', '~> 3.6' + s.add_development_dependency 'guard-rspec', '~> 4.2' + s.add_development_dependency 'rake', '~> 12.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.gitignore b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.gitignore new file mode 100644 index 0000000000..79dfcae942 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.gitignore @@ -0,0 +1,21 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +.tags* +.rspec_status +/guard/ +/listen/ diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.travis.yml b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.travis.yml new file mode 100644 index 0000000000..5508dd3958 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.travis.yml @@ -0,0 +1,19 @@ +language: ruby +cache: bundler + +matrix: + include: + - rvm: 2.3 + - rvm: 2.4 + - rvm: 2.5 + - rvm: 2.6 + - rvm: jruby + - rvm: truffleruby + - rvm: jruby-head + - rvm: ruby-head + allow_failures: + - rvm: truffleruby + - rvm: jruby + - rvm: ruby-head + - rvm: jruby-head + fast_finish: true diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.yardopts new file mode 100644 index 0000000000..cd347c5076 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/.yardopts @@ -0,0 +1,4 @@ +--readme README.md +--markup markdown +--markup-provider maruku +--no-private diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/Gemfile new file mode 100644 index 0000000000..9b2ce272ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/Gemfile @@ -0,0 +1,16 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in utopia.gemspec +gemspec + +group :development do + gem 'pry' + gem 'pry-coolline' + + gem 'tty-prompt' +end + +group :test do + gem 'simplecov' + gem 'coveralls', require: false +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/LICENSE.md new file mode 100644 index 0000000000..bba4996e4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/LICENSE.md @@ -0,0 +1,10 @@ +# The MIT License + +Copyright, 2009, by [Natalie Weizenbaum](https://github.com/nex3). +Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.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.0.0/gems/rb-inotify-0.10.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/README.md new file mode 100644 index 0000000000..90f5aa37a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/README.md @@ -0,0 +1,113 @@ +# rb-inotify + +This is a simple wrapper over the [inotify](http://en.wikipedia.org/wiki/Inotify) Linux kernel subsystem +for monitoring changes to files and directories. +It uses the [FFI](http://wiki.github.com/ffi/ffi) gem to avoid having to compile a C extension. + +[API documentation is available on rdoc.info](http://rdoc.info/projects/nex3/rb-inotify). + +[![Build Status](https://secure.travis-ci.org/guard/rb-inotify.svg)](http://travis-ci.org/guard/rb-inotify) +[![Code Climate](https://codeclimate.com/github/guard/rb-inotify.svg)](https://codeclimate.com/github/guard/rb-inotify) +[![Coverage Status](https://coveralls.io/repos/guard/rb-inotify/badge.svg)](https://coveralls.io/r/guard/rb-inotify) + +## Usage + +The API is similar to the inotify C API, but with a more Rubyish feel. +First, create a notifier: + + notifier = INotify::Notifier.new + +Then, tell it to watch the paths you're interested in +for the events you care about: + + notifier.watch("path/to/foo.txt", :modify) {puts "foo.txt was modified!"} + notifier.watch("path/to/bar", :moved_to, :create) do |event| + puts "#{event.name} is now in path/to/bar!" + end + +Inotify can watch directories or individual files. +It can pay attention to all sorts of events; +for a full list, see [the inotify man page](http://www.tin.org/bin/man.cgi?section=7&topic=inotify). + +Finally, you get at the events themselves: + + notifier.run + +This will loop infinitely, calling the appropriate callbacks when the files are changed. +If you don't want infinite looping, +you can also block until there are available events, +process them all at once, +and then continue on your merry way: + + notifier.process + +## Advanced Usage + +Sometimes it's necessary to have finer control over the underlying IO operations +than is provided by the simple callback API. +The trick to this is that the \{INotify::Notifier#to_io Notifier#to_io} method +returns a fully-functional IO object, +with a file descriptor and everything. +This means, for example, that it can be passed to `IO#select`: + + # Wait 10 seconds for an event then give up + if IO.select([notifier.to_io], [], [], 10) + notifier.process + end + +It can even be used with EventMachine: + + require 'eventmachine' + + EM.run do + EM.watch notifier.to_io do + notifier.process + end + end + +Unfortunately, this currently doesn't work under JRuby. +JRuby currently doesn't use native file descriptors for the IO object, +so we can't use the notifier's file descriptor as a stand-in. + +### Resource Limits + +If you get an error like `inotify event queue has overflowed` you might be running into system limits. You can add the following to your `/etc/sysctl.conf` to increase the number of files that can be monitored: + +``` +fs.inotify.max_user_watches = 100000 +fs.inotify.max_queued_events = 100000 +fs.inotify.max_user_instances = 100000 +``` + +## 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 + +## License + +Released under the MIT license. + +Copyright, 2009, by [Natalie Weizenbaum](https://github.com/nex3). +Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). + +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.0.0/gems/rb-inotify-0.10.1/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/Rakefile new file mode 100644 index 0000000000..bc3a3b6b8d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/Rakefile @@ -0,0 +1,14 @@ +require "bundler/gem_tasks" +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +desc "Run tests" +task :default => :spec + +task :console do + require 'rb-inotify' + require 'pry' + + binding.pry +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify.rb new file mode 100644 index 0000000000..8897aefabe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify.rb @@ -0,0 +1,15 @@ +require 'rb-inotify/version' +require 'rb-inotify/native' +require 'rb-inotify/native/flags' +require 'rb-inotify/notifier' +require 'rb-inotify/watcher' +require 'rb-inotify/event' +require 'rb-inotify/errors' + +# The root module of the library, which is laid out as so: +# +# * {Notifier} -- The main class, where the notifications are set up +# * {Watcher} -- A watcher for a single file or directory +# * {Event} -- An filesystem event notification +module INotify +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/errors.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/errors.rb new file mode 100644 index 0000000000..afee709935 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/errors.rb @@ -0,0 +1,3 @@ +module INotify + class QueueOverflowError < RuntimeError; end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/event.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/event.rb new file mode 100644 index 0000000000..11701acfa1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/event.rb @@ -0,0 +1,146 @@ +module INotify + # An event caused by a change on the filesystem. + # Each {Watcher} can fire many events, + # which are passed to that watcher's callback. + class Event + # A list of other events that are related to this one. + # Currently, this is only used for files that are moved within the same directory: + # the `:moved_from` and the `:moved_to` events will be related. + # + # @return [Array] + attr_reader :related + + # The name of the file that the event occurred on. + # This is only set for events that occur on files in directories; + # otherwise, it's `""`. + # Similarly, if the event is being fired for the directory itself + # the name will be `""` + # + # This pathname is relative to the enclosing directory. + # For the absolute pathname, use \{#absolute\_name}. + # Note that when the `:recursive` flag is passed to {Notifier#watch}, + # events in nested subdirectories will still have a `#name` field + # relative to their immediately enclosing directory. + # For example, an event on the file `"foo/bar/baz"` + # will have name `"baz"`. + # + # @return [String] + attr_reader :name + + # The {Notifier} that fired this event. + # + # @return [Notifier] + attr_reader :notifier + + # An integer specifying that this event is related to some other event, + # which will have the same cookie. + # + # Currently, this is only used for files that are moved within the same directory. + # Both the `:moved_from` and the `:moved_to` events will have the same cookie. + # + # @private + # @return [Fixnum] + attr_reader :cookie + + # The {Watcher#id id} of the {Watcher} that fired this event. + # + # @private + # @return [Fixnum] + attr_reader :watcher_id + + # Returns the {Watcher} that fired this event. + # + # @return [Watcher] + def watcher + @watcher ||= @notifier.watchers[@watcher_id] + end + + # The absolute path of the file that the event occurred on. + # + # This is actually only as absolute as the path passed to the {Watcher} + # that created this event. + # However, it is relative to the working directory, + # assuming that hasn't changed since the watcher started. + # + # @return [String] + def absolute_name + return watcher.path if name.empty? + return File.join(watcher.path, name) + end + + # Returns the flags that describe this event. + # This is generally similar to the input to {Notifier#watch}, + # except that it won't contain options flags nor `:all_events`, + # and it may contain one or more of the following flags: + # + # `:unmount` + # : The filesystem containing the watched file or directory was unmounted. + # + # `:ignored` + # : The \{#watcher watcher} was closed, or the watched file or directory was deleted. + # + # `:isdir` + # : The subject of this event is a directory. + # + # @return [Array] + def flags + @flags ||= Native::Flags.from_mask(@native[:mask]) + end + + # Constructs an {Event} object from a string of binary data, + # and destructively modifies the string to get rid of the initial segment + # used to construct the Event. + # + # @private + # @param data [String] The string to be modified + # @param notifier [Notifier] The {Notifier} that fired the event + # @return [Event, nil] The event, or `nil` if the string is empty + def self.consume(data, notifier) + return nil if data.empty? + ev = new(data, notifier) + data.replace data[ev.size..-1] + ev + end + + # Creates an event from a string of binary data. + # Differs from {Event.consume} in that it doesn't modify the string. + # + # @private + # @param data [String] The data string + # @param notifier [Notifier] The {Notifier} that fired the event + def initialize(data, notifier) + ptr = FFI::MemoryPointer.from_string(data) + @native = Native::Event.new(ptr) + @related = [] + @cookie = @native[:cookie] + @name = fix_encoding(data[@native.size, @native[:len]].gsub(/\0+$/, '')) + @notifier = notifier + @watcher_id = @native[:wd] + + raise QueueOverflowError.new("inotify event queue has overflowed.") if @native[:mask] & Native::Flags::IN_Q_OVERFLOW != 0 + end + + # Calls the callback of the watcher that fired this event, + # passing in the event itself. + # + # @private + def callback! + watcher && watcher.callback!(self) + end + + # Returns the size of this event object in bytes, + # including the \{#name} string. + # + # @return [Fixnum] + def size + @native.size + @native[:len] + end + + private + + def fix_encoding(name) + name.force_encoding('filesystem') if name.respond_to?(:force_encoding) + name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native.rb new file mode 100644 index 0000000000..6da36eb1cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native.rb @@ -0,0 +1,33 @@ +require 'ffi' + +module INotify + # This module contains the low-level foreign-function interface code + # for dealing with the inotify C APIs. + # It's an implementation detail, and not meant for users to deal with. + # + # @private + module Native + extend FFI::Library + ffi_lib FFI::Library::LIBC + begin + ffi_lib 'inotify' + rescue LoadError + end + + # The C struct describing an inotify event. + # + # @private + class Event < FFI::Struct + layout( + :wd, :int, + :mask, :uint32, + :cookie, :uint32, + :len, :uint32) + end + + attach_function :inotify_init, [], :int + attach_function :inotify_add_watch, [:int, :string, :uint32], :int + attach_function :inotify_rm_watch, [:int, :uint32], :int + attach_function :fpathconf, [:int, :int], :long + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native/flags.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native/flags.rb new file mode 100644 index 0000000000..5640130630 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/native/flags.rb @@ -0,0 +1,94 @@ +module INotify + module Native + # A module containing all the inotify flags + # to be passed to {Notifier#watch}. + # + # @private + module Flags + # File was accessed. + IN_ACCESS = 0x00000001 + # Metadata changed. + IN_ATTRIB = 0x00000004 + # Writtable file was closed. + IN_CLOSE_WRITE = 0x00000008 + # File was modified. + IN_MODIFY = 0x00000002 + # Unwrittable file closed. + IN_CLOSE_NOWRITE = 0x00000010 + # File was opened. + IN_OPEN = 0x00000020 + # File was moved from X. + IN_MOVED_FROM = 0x00000040 + # File was moved to Y. + IN_MOVED_TO = 0x00000080 + # Subfile was created. + IN_CREATE = 0x00000100 + # Subfile was deleted. + IN_DELETE = 0x00000200 + # Self was deleted. + IN_DELETE_SELF = 0x00000400 + # Self was moved. + IN_MOVE_SELF = 0x00000800 + + ## Helper events. + + # Close. + IN_CLOSE = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) + # Moves. + IN_MOVE = (IN_MOVED_FROM | IN_MOVED_TO) + # All events which a program can wait on. + IN_ALL_EVENTS = (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | + IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF) + + + ## Special flags. + + # Only watch the path if it is a directory. + IN_ONLYDIR = 0x01000000 + # Do not follow a sym link. + IN_DONT_FOLLOW = 0x02000000 + # Add to the mask of an already existing watch. + IN_MASK_ADD = 0x20000000 + # Only send event once. + IN_ONESHOT = 0x80000000 + + + ## Events sent by the kernel. + + # Backing fs was unmounted. + IN_UNMOUNT = 0x00002000 + # Event queued overflowed. + IN_Q_OVERFLOW = 0x00004000 + # File was ignored. + IN_IGNORED = 0x00008000 + # Event occurred against dir. + IN_ISDIR = 0x40000000 + + ## fpathconf Macros + + # returns the maximum length of a filename in the directory path or fd that the process is allowed to create. The corresponding macro is _POSIX_NAME_MAX. + PC_NAME_MAX = 3 + + # Converts a list of flags to the bitmask that the C API expects. + # + # @param flags [Array] + # @return [Fixnum] + def self.to_mask(flags) + flags.map {|flag| const_get("IN_#{flag.to_s.upcase}")}. + inject(0) {|mask, flag| mask | flag} + end + + # Converts a bitmask from the C API into a list of flags. + # + # @param mask [Fixnum] + # @return [Array] + def self.from_mask(mask) + constants.map {|c| c.to_s}.select do |c| + next false unless c =~ /^IN_/ + const_get(c) & mask != 0 + end.map {|c| c.sub("IN_", "").downcase.to_sym} - [:all_events] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/notifier.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/notifier.rb new file mode 100644 index 0000000000..89be6f859d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/notifier.rb @@ -0,0 +1,326 @@ +require 'thread' + +module INotify + # Notifier wraps a single instance of inotify. + # It's possible to have more than one instance, + # but usually unnecessary. + # + # @example + # # Create the notifier + # notifier = INotify::Notifier.new + # + # # Run this callback whenever the file path/to/foo.txt is read + # notifier.watch("path/to/foo.txt", :access) do + # puts "Foo.txt was accessed!" + # end + # + # # Watch for any file in the directory being deleted + # # or moved out of the directory. + # notifier.watch("path/to/directory", :delete, :moved_from) do |event| + # # The #name field of the event object contains the name of the affected file + # puts "#{event.name} is no longer in the directory!" + # end + # + # # Nothing happens until you run the notifier! + # notifier.run + class Notifier + # A list of directories that should never be recursively watched. + # + # * Files in `/dev/fd` sometimes register as directories, but are not enumerable. + RECURSIVE_BLACKLIST = %w[/dev/fd] + + # A hash from {Watcher} ids to the instances themselves. + # + # @private + # @return [{Fixnum => Watcher}] + attr_reader :watchers + + # The underlying file descriptor for this notifier. + # This is a valid OS file descriptor, and can be used as such + # (except under JRuby -- see \{#to\_io}). + # + # @return [Fixnum] + def fd + @handle.fileno + end + + # Creates a new {Notifier}. + # + # @return [Notifier] + # @raise [SystemCallError] if inotify failed to initialize for some reason + def initialize + @running = Mutex.new + @pipe = IO.pipe + # JRuby shutdown sometimes runs IO finalizers before all threads finish. + if RUBY_ENGINE == 'jruby' + @pipe[0].autoclose = false + @pipe[1].autoclose = false + end + + @watchers = {} + + fd = Native.inotify_init + unless fd < 0 + @handle = IO.new(fd) + @handle.autoclose = false if RUBY_ENGINE == 'jruby' + return + end + + raise SystemCallError.new( + "Failed to initialize inotify" + + case FFI.errno + when Errno::EMFILE::Errno; ": the user limit on the total number of inotify instances has been reached." + when Errno::ENFILE::Errno; ": the system limit on the total number of file descriptors has been reached." + when Errno::ENOMEM::Errno; ": insufficient kernel memory is available." + else; "" + end, + FFI.errno) + end + + # Returns a Ruby IO object wrapping the underlying file descriptor. + # Since this file descriptor is fully functional (except under JRuby), + # this IO object can be used in any way a Ruby-created IO object can. + # This includes passing it to functions like `#select`. + # + # Note that this always returns the same IO object. + # Creating lots of IO objects for the same file descriptor + # can cause some odd problems. + # + # **This is not supported under JRuby**. + # JRuby currently doesn't use native file descriptors for the IO object, + # so we can't use this file descriptor as a stand-in. + # + # @return [IO] An IO object wrapping the file descriptor + # @raise [NotImplementedError] if this is being called in JRuby + def to_io + @handle + end + + # Watches a file or directory for changes, + # calling the callback when there are. + # This is only activated once \{#process} or \{#run} is called. + # + # **Note that by default, this does not recursively watch subdirectories + # of the watched directory**. + # To do so, use the `:recursive` flag. + # + # ## Flags + # + # `:access` + # : A file is accessed (that is, read). + # + # `:attrib` + # : A file's metadata is changed (e.g. permissions, timestamps, etc). + # + # `:close_write` + # : A file that was opened for writing is closed. + # + # `:close_nowrite` + # : A file that was not opened for writing is closed. + # + # `:modify` + # : A file is modified. + # + # `:open` + # : A file is opened. + # + # ### Directory-Specific Flags + # + # These flags only apply when a directory is being watched. + # + # `:moved_from` + # : A file is moved out of the watched directory. + # + # `:moved_to` + # : A file is moved into the watched directory. + # + # `:create` + # : A file is created in the watched directory. + # + # `:delete` + # : A file is deleted in the watched directory. + # + # `:delete_self` + # : The watched file or directory itself is deleted. + # + # `:move_self` + # : The watched file or directory itself is moved. + # + # ### Helper Flags + # + # These flags are just combinations of the flags above. + # + # `:close` + # : Either `:close_write` or `:close_nowrite` is activated. + # + # `:move` + # : Either `:moved_from` or `:moved_to` is activated. + # + # `:all_events` + # : Any event above is activated. + # + # ### Options Flags + # + # These flags don't actually specify events. + # Instead, they specify options for the watcher. + # + # `:onlydir` + # : Only watch the path if it's a directory. + # + # `:dont_follow` + # : Don't follow symlinks. + # + # `:mask_add` + # : Add these flags to the pre-existing flags for this path. + # + # `:oneshot` + # : Only send the event once, then shut down the watcher. + # + # `:recursive` + # : Recursively watch any subdirectories that are created. + # Note that this is a feature of rb-inotify, + # rather than of inotify itself, which can only watch one level of a directory. + # This means that the {Event#name} field + # will contain only the basename of the modified file. + # When using `:recursive`, {Event#absolute_name} should always be used. + # + # @param path [String] The path to the file or directory + # @param flags [Array] Which events to watch for + # @yield [event] A block that will be called + # whenever one of the specified events occur + # @yieldparam event [Event] The Event object containing information + # about the event that occured + # @return [Watcher] A Watcher set up to watch this path for these events + # @raise [SystemCallError] if the file or directory can't be watched, + # e.g. if the file isn't found, read access is denied, + # or the flags don't contain any events + def watch(path, *flags, &callback) + return Watcher.new(self, path, *flags, &callback) unless flags.include?(:recursive) + + dir = Dir.new(path) + + dir.each do |base| + d = File.join(path, base) + binary_d = d.respond_to?(:force_encoding) ? d.dup.force_encoding('BINARY') : d + next if binary_d =~ /\/\.\.?$/ # Current or parent directory + next if RECURSIVE_BLACKLIST.include?(d) + next if flags.include?(:dont_follow) && File.symlink?(d) + next if !File.directory?(d) + + watch(d, *flags, &callback) + end + + dir.close + + rec_flags = [:create, :moved_to] + return watch(path, *((flags - [:recursive]) | rec_flags)) do |event| + callback.call(event) if flags.include?(:all_events) || !(flags & event.flags).empty? + next if (rec_flags & event.flags).empty? || !event.flags.include?(:isdir) + begin + watch(event.absolute_name, *flags, &callback) + rescue Errno::ENOENT + # If the file has been deleted since the glob was run, we don't want to error out. + end + end + end + + # Starts the notifier watching for filesystem events. + # Blocks until \{#stop} is called. + # + # @see #process + def run + @running.synchronize do + Thread.current[:INOTIFY_RUN_THREAD] = true + @stop = false + + process until @stop + end + ensure + Thread.current[:INOTIFY_RUN_THREAD] = false + end + + # Stop watching for filesystem events. + # That is, if we're in a \{#run} loop, + # exit out as soon as we finish handling the events. + def stop + @stop = true + @pipe.last.write "." + + unless Thread.current[:INOTIFY_RUN_THREAD] + @running.synchronize do + # no-op: we just needed to wait until the lock was available + end + end + end + + # Blocks until there are one or more filesystem events + # that this notifier has watchers registered for. + # Once there are events, the appropriate callbacks are called + # and this function returns. + # + # @see #run + def process + read_events.each do |event| + event.callback! + event.flags.include?(:ignored) && event.notifier.watchers.delete(event.watcher_id) + end + end + + # Close the notifier. + # + # @raise [SystemCallError] if closing the underlying file descriptor fails. + def close + stop + @handle.close + @watchers.clear + end + + # Blocks until there are one or more filesystem events that this notifier + # has watchers registered for. Once there are events, returns their {Event} + # objects. + # + # This can return an empty list if the watcher was closed elsewhere. + # + # {#run} or {#process} are ususally preferable to calling this directly. + def read_events + size = Native::Event.size + Native.fpathconf(fd, Native::Flags::PC_NAME_MAX) + 1 + tries = 1 + + begin + data = readpartial(size) + rescue SystemCallError => er + # EINVAL means that there's more data to be read + # than will fit in the buffer size + raise er unless er.errno == Errno::EINVAL::Errno && tries < 5 + size *= 2 + tries += 1 + retry + end + return [] if data.nil? + + events = [] + cookies = {} + while event = Event.consume(data, self) + events << event + next if event.cookie == 0 + cookies[event.cookie] ||= [] + cookies[event.cookie] << event + end + cookies.each {|c, evs| evs.each {|ev| ev.related.replace(evs - [ev]).freeze}} + events + end + + private + + # Same as IO#readpartial, or as close as we need. + def readpartial(size) + readable, = select([@handle, @pipe.first]) + return nil if readable.include?(@pipe.first) + @handle.readpartial(size) + rescue Errno::EBADF + # If the IO has already been closed, reading from it will cause + # Errno::EBADF. + nil + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/version.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/version.rb new file mode 100644 index 0000000000..f38a6783d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/version.rb @@ -0,0 +1,24 @@ +# Copyright, 2012, by Natalie Weizenbaum. +# Copyright, 2017, by Samuel G. D. Williams. +# +# 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 INotify + VERSION = '0.10.1' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/watcher.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/watcher.rb new file mode 100644 index 0000000000..1205e2dea5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/lib/rb-inotify/watcher.rb @@ -0,0 +1,88 @@ +module INotify + # Watchers monitor a single path for changes, + # specified by {INotify::Notifier#watch event flags}. + # A watcher is usually created via \{Notifier#watch}. + # + # One {Notifier} may have many {Watcher}s. + # The Notifier actually takes care of the checking for events, + # via \{Notifier#run #run} or \{Notifier#process #process}. + # The main purpose of having Watcher objects + # is to be able to disable them using \{#close}. + class Watcher + # The {Notifier} that this Watcher belongs to. + # + # @return [Notifier] + attr_reader :notifier + + # The path that this Watcher is watching. + # + # @return [String] + attr_reader :path + + # The {INotify::Notifier#watch flags} + # specifying the events that this Watcher is watching for, + # and potentially some options as well. + # + # @return [Array] + attr_reader :flags + + # The id for this Watcher. + # Used to retrieve this Watcher from {Notifier#watchers}. + # + # @private + # @return [Fixnum] + attr_reader :id + + # Calls this Watcher's callback with the given {Event}. + # + # @private + # @param event [Event] + def callback!(event) + @callback[event] + end + + # Disables this Watcher, so that it doesn't fire any more events. + # + # @raise [SystemCallError] if the watch fails to be disabled for some reason + def close + if Native.inotify_rm_watch(@notifier.fd, @id) == 0 + @notifier.watchers.delete(@id) + return + end + + raise SystemCallError.new("Failed to stop watching #{path.inspect}", + FFI.errno) + end + + # Creates a new {Watcher}. + # + # @private + # @see Notifier#watch + def initialize(notifier, path, *flags, &callback) + @notifier = notifier + @callback = callback || proc {} + @path = path + @flags = flags.freeze + @id = Native.inotify_add_watch(@notifier.fd, path.dup, + Native::Flags.to_mask(flags)) + + unless @id < 0 + @notifier.watchers[@id] = self + return + end + + raise SystemCallError.new( + "Failed to watch #{path.inspect}" + + case FFI.errno + when Errno::EACCES::Errno; ": read access to the given file is not permitted." + when Errno::EBADF::Errno; ": the given file descriptor is not valid." + when Errno::EFAULT::Errno; ": path points outside of the process's accessible address space." + when Errno::EINVAL::Errno; ": the given event mask contains no legal events; or fd is not an inotify file descriptor." + when Errno::ENOMEM::Errno; ": insufficient kernel memory was available." + when Errno::ENOSPC::Errno; ": The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource." + else; "" + end, + FFI.errno) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/rb-inotify.gemspec b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/rb-inotify.gemspec new file mode 100644 index 0000000000..e83eafeb9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/rb-inotify.gemspec @@ -0,0 +1,28 @@ +# -*- encoding: utf-8 -*- +require_relative 'lib/rb-inotify/version' + +Gem::Specification.new do |spec| + spec.name = 'rb-inotify' + spec.version = INotify::VERSION + spec.platform = Gem::Platform::RUBY + + spec.summary = 'A Ruby wrapper for Linux inotify, using FFI' + spec.authors = ['Natalie Weizenbaum', 'Samuel Williams'] + spec.email = ['nex342@gmail.com', 'samuel.williams@oriontransfer.co.nz'] + spec.homepage = 'https://github.com/guard/rb-inotify' + spec.licenses = ['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{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + spec.required_ruby_version = '>= 2.2' + + spec.add_dependency "ffi", "~> 1.0" + + spec.add_development_dependency "rspec", "~> 3.6" + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "concurrent-ruby" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/inotify_spec.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/inotify_spec.rb new file mode 100644 index 0000000000..b73ea3033f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/inotify_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe INotify do + describe "version" do + it "exists" do + expect(INotify::VERSION).to be_truthy + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/notifier_spec.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/notifier_spec.rb new file mode 100644 index 0000000000..af94cad75c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/notifier_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' +require 'tmpdir' +require 'concurrent' + +describe INotify::Notifier do + describe "instance" do + around do |block| + Dir.mktmpdir do |dir| + @root = Pathname.new(dir) + @notifier = INotify::Notifier.new + + begin + block.call + ensure + @notifier.close + end + end + end + + let(:dir) do + @root.join("foo").tap(&:mkdir) + end + + let(:another_dir) do + @root.join("bar").tap(&:mkdir) + end + + it "stops" do + @notifier.stop + end + + describe :process do + it "gets events" do + events = recording(dir, :create) + dir.join("test.txt").write("hello world") + + @notifier.process + + expect(events.size).to eq(1) + expect(events.first.name).to eq("test.txt") + expect(events.first.absolute_name).to eq(dir.join("test.txt").to_s) + end + + it "gets simultaneous events" do + events = recording(dir, :create) + + dir.join("one.txt").write("hello world") + dir.join("two.txt").write("hello world") + + @notifier.process + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + end + + it "separates events between watches" do + bar_events = nil + + foo_events = recording(dir, :create) + bar_events = recording(another_dir, :create) + + dir.join("test.txt").write("hello world") + another_dir.join("test_two.txt").write("hello world") + + @notifier.process + + expect(foo_events.size).to eq(1) + expect(foo_events.first.name).to eq("test.txt") + expect(foo_events.first.absolute_name).to eq(dir.join("test.txt").to_s) + + expect(bar_events.size).to eq(1) + expect(bar_events.first.name).to eq("test_two.txt") + expect(bar_events.first.absolute_name).to eq(another_dir.join("test_two.txt").to_s) + end + end + + describe :run do + it "processes repeatedly until stopped" do + barriers = Array.new(3) { Concurrent::Event.new } + barrier_queue = barriers.dup + events = recording(dir, :create) { barrier_queue.shift.set } + + run_thread = Thread.new { @notifier.run } + + dir.join("one.txt").write("hello world") + barriers.shift.wait(1) or raise "timeout" + + expect(events.map(&:name)).to match_array(%w(one.txt)) + + dir.join("two.txt").write("hello world") + barriers.shift.wait(1) or raise "timeout" + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + + @notifier.stop + + dir.join("three.txt").write("hello world") + barriers.shift.wait(1) + + dir.join("four.txt").write("hello world") + run_thread.join + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + end + + it "can be stopped from within a callback" do + barriers = Array.new(3) { Concurrent::Event.new } + barrier_queue = barriers.dup + events = recording(dir, :create) { @notifier.stop } + + run_thread = Thread.new { @notifier.run } + dir.join("one.txt").write("hello world") + run_thread.join + end + end + + describe :fd do + it "returns an integer" do + expect(@notifier.fd).to be_an(Integer) + end + end + + describe :to_io do + it "returns a ruby IO" do + expect(@notifier.to_io).to be_an(::IO) + end + + it "matches the fd" do + expect(@notifier.to_io.fileno).to eq(@notifier.fd) + end + + it "caches its result" do + expect(@notifier.to_io).to be(@notifier.to_io) + end + + it "is selectable" do + events = recording(dir, :create) + expect(select([@notifier.to_io], nil, nil, 0.2)).to be_nil + + dir.join("test.txt").write("hello world") + expect(select([@notifier.to_io], nil, nil, 0.2)).to eq([[@notifier.to_io], [], []]) + + @notifier.process + expect(select([@notifier.to_io], nil, nil, 0.2)).to be_nil + end + end + + private + + def recording(dir, *flags, callback: nil) + events = [] + @notifier.watch(dir.to_s, *flags) do |event| + events << event + yield if block_given? + end + + events + end + end + + describe "mixed instances" do + it "doesn't tangle fds" do + notifiers = Array.new(30) { INotify::Notifier.new } + notifiers.each(&:to_io) + + one = Array.new(10) { IO.pipe.last } + notifiers.each(&:close) + + two = Array.new(10) { IO.pipe.last } + + notifiers = nil + GC.start + + _, writable, _ = select(nil, one, nil, 1) + expect(writable).to match_array(one) + + _, writable, _ = select(nil, two, nil, 1) + expect(writable).to match_array(two) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/spec_helper.rb b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/spec_helper.rb new file mode 100644 index 0000000000..b62f9cf5ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rb-inotify-0.10.1/spec/spec_helper.rb @@ -0,0 +1,29 @@ + +if ENV['COVERAGE'] || ENV['TRAVIS'] + begin + require 'simplecov' + + SimpleCov.start do + add_filter "/spec/" + end + + if ENV['TRAVIS'] + require 'coveralls' + Coveralls.wear! + end + rescue LoadError + warn "Could not load simplecov: #{$!}" + end +end + +require "bundler/setup" +require "rb-inotify" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.gitignore b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.gitignore new file mode 100644 index 0000000000..0303c25afd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.gitignore @@ -0,0 +1,6 @@ +pkg +coverage/ +.ruby-version +.ruby-gemset +.rspec +Gemfile.lock diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.travis.yml b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.travis.yml new file mode 100644 index 0000000000..2cfb466391 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/.travis.yml @@ -0,0 +1,16 @@ +rvm: + - 2.5.0 + - 2.4.3 + - 2.3.6 + - jruby-19mode + - rbx-3 + - ruby-head +notifications: + recipients: + - jarmo.p@gmail.com +matrix: + allow_failures: + - rvm: ruby-head + - rvm: jruby-19mode + - rvm: rbx-3 + diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/CHANGES.md b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/CHANGES.md new file mode 100644 index 0000000000..aba4ee3a57 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/CHANGES.md @@ -0,0 +1,62 @@ +### 3.0.0: + +* Merged PR #29 (https://github.com/jarmo/require_all/pull/29) + Respect inflections defined by ActiveSupport when autoloading. + + Thanks to James Le Cuirot (@chewi) + +### 2.0.0: + +* Merged PR #24 (https://github.com/jarmo/require_all/pull/24) + Prior to version 2, RequireAll attempted to automatically resolve dependencies between files, thus + allowing them to be required in any order. Whilst convenient, the approach used (of rescuing + `NameError`s and later retrying files that failed to load) was fundamentally unsafe and can result + in incorrect behaviour (for example issue #8, plus more detail and discussion in #21). + + Thanks to Joe Horsnell (@joehorsnell) + +### 1.5.0: + +* Merged PR #13 (https://github.com/jarmo/require_all/pull/13). +* Merged PR #18 (https://github.com/jarmo/require_all/pull/18). + +### 1.4.0: + +* License is now correctly as MIT. Thanks to Eric Kessler for pull request #16. + +### 1.3.3: + +* Support empty directories without crashing. Issue #11. Thanks to Eric Kessler. + +### 1.3.2: + +* Add license to gemspec. + +### 1.3.1: + +* README improvements. + +### 1.3.0: + +* Make everything work with Ruby 1.9 and 2.0. Awesome! Thanks to Aaron Klaassen. + +### 1.2.0: + +* Add load_all, and load_rel which behave similarly to require_all/require_rel except that Kernel#load is used +* Add autoload_all and autoload_rel (see README and/or specs for examples of usage) +* Minor bug fixes +* Improved specs + +### 1.1.0: + +* Add require_rel (require_all relative to the current file) +* Fix bug in auto-appending .rb ala require + +### 1.0.1: + +* Allow require_all to take a directory name as an argument + +### 1.0.0: + +* Initial release (was originally load_glob, converted to require_all which is + a lot cooler, seriously trust me) diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Gemfile b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Gemfile new file mode 100644 index 0000000000..4c598fa205 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gemspec +gem 'coveralls', require: false diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/LICENSE b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/LICENSE new file mode 100644 index 0000000000..b94d0dd66b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) Jarmo Pertman + +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.0.0/gems/require_all-3.0.0/README.md b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/README.md new file mode 100644 index 0000000000..a6675ec390 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/README.md @@ -0,0 +1,123 @@ +# require_all +[![Gem Version](https://badge.fury.io/rb/require_all.png)](http://badge.fury.io/rb/require_all) +[![Build Status](https://secure.travis-ci.org/jarmo/require_all.png)](http://travis-ci.org/jarmo/require_all) +[![Coverage](https://coveralls.io/repos/jarmo/require_all/badge.png?branch=master)](https://coveralls.io/r/jarmo/require_all) + +A wonderfully simple way to load your code. + +Tired of futzing around with `require` statements everywhere, littering your code +with `require File.dirname(__FILE__)` crap? What if you could just +point something at a big directory full of code and have everything just +automagically load? + +Wouldn't that be nice? Well, now you can! + +## Installation + +Add this line to your application's Gemfile: + + gem 'require_all' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install require_all + +## Usage + +```ruby +require 'require_all' + +# load all ruby files in the directory "lib" and its subdirectories +require_all 'lib' + +# or load all files by using glob +require_all 'lib/**/*.rb' + +# or load files in an Array +require_all Dir.glob("blah/**/*.rb").reject { |f| stupid_file? f } + +# or load manually specified files +require_all 'lib/a.rb', 'lib/b.rb', 'lib/c.rb', 'lib/d.rb' +``` + +You can also load files relative to the current file by using `require_rel`: + +```ruby +# Instead of +require File.dirname(__FILE__) + '/foobar' + +# you can do simply like this +require_rel 'foobar' +``` + +You can give all the same argument types to the `require_rel` as for `require_all`. + +It is recommended to use `require_rel` instead of `require_all` since it will require files relatively +to the current file (`__FILE__`) as opposed to loading files relative from the working directory. + +`load_all` and `load_rel` methods also exist to use `Kernel#load` instead of `Kernel#require`! + +Files are required in alphabetical order and if there are files in nested directories, they are +required depth-first. If a `NameError` caused by a reference to an uninitialised constant is +encountered during the requiring process, then a `RequireAll::LoadError` will be thrown, +indicating the file that needs the dependency adding to. + +## autoload_all + +This library also includes methods for performing `autoload` - what a bargain! + +Similar syntax is used as for `require_(all|rel)` and `load_(all|rel)` methods with some caveats: + +* Directory and file names have to reflect namespaces and/or constant names: + +```ruby +# lib/dir1/dir2/my_file.rb +module Dir1 + module Dir2 + class MyFile + end + end +end + +# lib/loader.rb +autoload_all File.dirname(__FILE__) + "/dir1" +``` + +* A `base_dir` option has to be specified if loading directories or files from some other location + than top-level directory: + +```ruby +# lib/dir1/other_file.rb +autoload_all File.dirname(__FILE__) + "/dir2/my_file.rb", + base_dir: File.dirname(__FILE__) + "/../dir1" +``` + +* All namespaces will be created dynamically by `autoload_all` - this means that `defined?(Dir1)` will + return `"constant"` even if `my_file.rb` is not yet loaded! + +Of course there's also an `autoload_rel` method: +```ruby +autoload_rel "dir2/my_file.rb", base_dir: File.dirname(__FILE__) + "/../dir1" +``` + +If having some problems with `autoload_all` or `autoload_rel` then set `$DEBUG=true` to see how files +are mapped to their respective modules and classes. + +## Version compatibility and upgrading + +As of version 2, RequireAll will raise a `RequireAll::LoadError` if it encounters a `NameError` +caused by a reference to an uninitialised constant during the requiring process. As such, it is not +backwards compatible with version 1.x, but simple to upgrade by adding any requires to load +dependencies in files that need them. See [CHANGES](CHANGES.md) for more details. + +## Questions? Comments? Concerns? + +You can reach the author on github or by email [jarmo.p@gmail.com](mailto:jarmo.p@gmail.com) + +## License + +MIT (see the [LICENSE](LICENSE) file for details) diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Rakefile new file mode 100644 index 0000000000..670a0e106c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" + +require "rspec/core/rake_task" +RSpec::Core::RakeTask.new(:spec) +task default: :spec +task build: :spec diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/lib/require_all.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/lib/require_all.rb new file mode 100644 index 0000000000..d54c162f3d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/lib/require_all.rb @@ -0,0 +1,252 @@ +#-- +# Copyright (C)2009 Tony Arcieri +# You can redistribute this under the terms of the MIT license +# See file LICENSE for details +#++ + +module RequireAll + LoadError = Class.new(::LoadError) + + # A wonderfully simple way to load your code. + # + # The easiest way to use require_all is to just point it at a directory + # containing a bunch of .rb files. These files can be nested under + # subdirectories as well: + # + # require_all 'lib' + # + # This will find all the .rb files under the lib directory and load them. + # + # If a file required by require_all references a constant that is not yet + # loaded, a RequireAll::LoadError will be thrown. + # + # You can also give it a glob, which will enumerate all the matching files: + # + # require_all 'lib/**/*.rb' + # + # It will also accept an array of files: + # + # require_all Dir.glob("blah/**/*.rb").reject { |f| stupid_file(f) } + # + # Or if you want, just list the files directly as arguments: + # + # require_all 'lib/a.rb', 'lib/b.rb', 'lib/c.rb', 'lib/d.rb' + # + def require_all(*args) + # Handle passing an array as an argument + args.flatten! + + options = {method: :require} + options.merge!(args.pop) if args.last.is_a?(Hash) + + if args.empty? + puts "no files were loaded due to an empty Array" if $DEBUG + return false + end + + if args.size > 1 + # Expand files below directories + files = args.map do |path| + if File.directory? path + Dir[File.join(path, '**', '*.rb')] + else + path + end + end.flatten + else + arg = args.first + begin + # Try assuming we're doing plain ol' require compat + stat = File.stat(arg) + + if stat.file? + files = [arg] + elsif stat.directory? + files = Dir.glob File.join(arg, '**', '*.rb') + else + raise ArgumentError, "#{arg} isn't a file or directory" + end + rescue SystemCallError + # If the stat failed, maybe we have a glob! + files = Dir.glob arg + + # Maybe it's an .rb file and the .rb was omitted + if File.file?(arg + '.rb') + file = arg + '.rb' + options[:method] != :autoload ? __require(options[:method], file) : __autoload(file, file, options) + return true + end + + # If we ain't got no files, the glob failed + raise LoadError, "no such file to load -- #{arg}" if files.empty? + end + end + + return if files.empty? + + if options[:method] == :autoload + files.map! { |file_| [file_, File.expand_path(file_)] } + files.each do |file_, full_path| + __autoload(file_, full_path, options) + end + + return true + end + + files.map { |file_| File.expand_path file_ }.sort.each do |file_| + begin + __require(options[:method], file_) + rescue NameError => e + # Only wrap NameError exceptions for uninitialized constants + raise e unless e.instance_of?(NameError) && e.message.include?('uninitialized constant') + raise LoadError, "Could not require #{file_} (#{e}). Please require the necessary files" + end + end + + true + end + + # Works like require_all, but paths are relative to the caller rather than + # the current working directory + def require_rel(*paths) + # Handle passing an array as an argument + paths.flatten! + return false if paths.empty? + + source_directory = File.dirname caller.first.sub(/:\d+$/, '') + paths.each do |path| + require_all File.join(source_directory, path) + end + end + + # Loads all files like require_all instead of requiring + def load_all(*paths) + require_all paths, method: :load + end + + # Loads all files by using relative paths of the caller rather than + # the current working directory + def load_rel(*paths) + paths.flatten! + return false if paths.empty? + + source_directory = File.dirname caller.first.sub(/:\d+$/, '') + paths.each do |path| + require_all File.join(source_directory, path), method: :load + end + end + + # Performs Kernel#autoload on all of the files rather than requiring immediately. + # + # Note that all Ruby files inside of the specified directories should have same module name as + # the directory itself and file names should reflect the class/module names. + # For example if there is a my_file.rb in directories dir1/dir2/ then + # there should be a declaration like this in my_file.rb: + # module Dir1 + # module Dir2 + # class MyFile + # ... + # end + # end + # end + # + # If the filename and namespaces won't match then my_file.rb will be loaded into wrong module! + # Better to fix these files. + # + # Set $DEBUG=true to see how files will be autoloaded if experiencing any problems. + # + # If trying to perform autoload on some individual file or some inner module, then you'd have + # to always specify *:base_dir* option to specify where top-level namespace resides. + # Otherwise it's impossible to know the namespace of the loaded files. + # + # For example loading only my_file.rb from dir1/dir2 with autoload_all: + # + # autoload_all File.dirname(__FILE__) + '/dir1/dir2/my_file', + # base_dir: File.dirname(__FILE__) + '/dir1' + # + # WARNING: All modules will be created even if files themselves aren't loaded yet, meaning + # that all the code which depends of the modules being loaded or not will not work, like usages + # of define? and it's friends. + # + # Also, normal caveats of using Kernel#autoload apply - you have to remember that before + # applying any monkey-patches to code using autoload, you'll have to reference the full constant + # to load the code before applying your patch! + + def autoload_all(*paths) + paths.flatten! + return false if paths.empty? + require "pathname" + + options = {method: :autoload} + options.merge!(paths.pop) if paths.last.is_a?(Hash) + + paths.each do |path| + require_all path, {base_dir: path}.merge(options) + end + end + + # Performs autoloading relatively from the caller instead of using current working directory + def autoload_rel(*paths) + paths.flatten! + return false if paths.empty? + require "pathname" + + options = {method: :autoload} + options.merge!(paths.pop) if paths.last.is_a?(Hash) + + source_directory = File.dirname caller.first.sub(/:\d+$/, '') + paths.each do |path| + file_path = Pathname.new(source_directory).join(path).to_s + require_all file_path, {method: :autoload, + base_dir: source_directory}.merge(options) + end + end + + private + + def __require(method, file) + Kernel.send(method, file) + end + + def __autoload(file, full_path, options) + last_module = "Object" # default constant where namespaces are created into + begin + base_dir = Pathname.new(options[:base_dir]).realpath + rescue Errno::ENOENT + raise LoadError, ":base_dir doesn't exist at #{options[:base_dir]}" + end + Pathname.new(file).realpath.descend do |entry| + # skip until *entry* is same as desired directory + # or anything inside of it avoiding to create modules + # from the top-level directories + next if (entry <=> base_dir) < 0 + + # get the module into which a new module is created or + # autoload performed + mod = Object.class_eval(last_module) + + without_ext = entry.basename(entry.extname).to_s + + const = + if defined? ActiveSupport::Inflector + ActiveSupport::Inflector.camelize(without_ext) + else + without_ext.split("_").map {|word| word.capitalize}.join + end + + if entry.file? || (entry.directory? && entry.sub_ext('.rb').file?) + mod.class_eval do + puts "autoloading #{mod}::#{const} from #{full_path}" if $DEBUG + autoload const, full_path + end + else + mod.class_eval "module #{const} end" if entry.directory? + end + + last_module += "::#{const}" if entry.directory? + end + end + +end + +include RequireAll diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/require_all.gemspec b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/require_all.gemspec new file mode 100644 index 0000000000..9b110cac64 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/require_all.gemspec @@ -0,0 +1,22 @@ +Gem::Specification.new do |s| + s.name = "require_all" + s.version = "3.0.0" + s.authors = ["Jarmo Pertman", "Tony Arcieri"] + s.email = "jarmo.p@gmail.com" + s.summary = "A wonderfully simple way to load your code" + + s.files = `git ls-files`.split($\) + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + s.require_paths = ["lib"] + + s.homepage = "http://github.com/jarmo/require_all" + s.license = "MIT" + + s.has_rdoc = true + s.rdoc_options = %w(--title require_all --main README.md --line-numbers) + s.extra_rdoc_files = ["LICENSE", "README.md", "CHANGES.md"] + + s.add_development_dependency "rake", "~> 10.4" + s.add_development_dependency "rspec", "~> 3.2" + s.add_development_dependency "simplecov", "~> 0.7" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_shared.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_shared.rb new file mode 100644 index 0000000000..3d72dbfa0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_shared.rb @@ -0,0 +1,84 @@ +shared_examples_for "#autoload_all syntactic sugar" do + before :each do + @file_list = [ + "#{@base_dir}/module1/a.rb", + "#{@base_dir}/module2/longer_name.rb", + "#{@base_dir}/module2/module3/b.rb" + ] + end + + it "accepts files with and without extensions" do + is_expected.not_to be_loaded("Autoloaded::Module2::LongerName") + expect(send(@method, @base_dir + '/module2/longer_name', base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module2::LongerName") + + is_expected.not_to be_loaded("Autoloaded::Module1::A") + expect(send(@method, @base_dir + '/module1/a.rb', base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A") + end + + it "accepts lists of files" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, @file_list, base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "is totally cool with a splatted list of arguments" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, *(@file_list << {base_dir: @autoload_base_dir}))).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "will load all .rb files under a directory without a trailing slash" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, @base_dir, base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "will load all .rb files under a directory with a trailing slash" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, "#{@base_dir}/", base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "will load all files specified by a glob" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, "#{@base_dir}/**/*.rb", base_dir: @autoload_base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "returns false if an empty input was given" do + send(@method, []) + expect(send(@method, [])).to be_falsey + expect(send(@method)).to be_falsey + end + + it "raises LoadError if no file or directory found" do + expect {send(@method, "not_found")}.to raise_error(LoadError) + end + + it "can handle empty directories" do + # Have to make these on the fly because they can't be saved as a test fixture because Git won't track directories with nothing in them. + FileUtils.mkpath("#{@base_dir}/empty_dir") + FileUtils.mkpath("#{@base_dir}/nested/empty_dir") + FileUtils.mkpath("#{@base_dir}/nested/more_nested/empty_dir") + + expect {send(@method, "#{@base_dir}/empty_dir")}.to_not raise_error + expect {send(@method, "#{@base_dir}/nested")}.to_not raise_error + end + + it "raises LoadError if :base_dir doesn't exist" do + expect {send(@method, @base_dir, base_dir: @base_dir + "/non_existing_dir")}. + to raise_exception(LoadError) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_spec.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_spec.rb new file mode 100644 index 0000000000..8656ae800f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/autoload_spec.rb @@ -0,0 +1,71 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.dirname(__FILE__) + '/autoload_shared.rb' + +describe "autoload_all" do + + subject { self } + + it "provides require_all functionality by using 'autoload' instead of 'require'" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + autoload_all fixture_path('autoloaded') + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + end + + it "doesn't autoload files with wrong module names" do + autoload_all fixture_path('autoloaded') + is_expected.not_to be_loaded("Autoloaded::WrongModule::WithWrongModule", "WrongModule::WithWrongModule") + end + + it "autoloads class nested into another class" do + is_expected.not_to be_loaded("Autoloaded::Class1", "Autoloaded::Class1::C") + autoload_all fixture_path('autoloaded') + is_expected.to be_loaded("Autoloaded::Class1") + expect(Autoloaded::Class1).to be_a Class + is_expected.to be_loaded("Autoloaded::Class1::C") + end + + it "needs to specify base_dir for autoloading if loading something from under top-level module directory" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + autoload_all fixture_path('autoloaded/module1') + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + + autoload_all fixture_path('autoloaded/module1'), base_dir: fixture_path('autoloaded') + is_expected.to be_loaded("Autoloaded::Module1::A") + is_expected.not_to be_loaded("Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + end + + before(:all) do + @base_dir = fixture_path('autoloaded') + @method = :autoload_all + @autoload_base_dir = @base_dir + end + it_should_behave_like "#autoload_all syntactic sugar" +end + +describe "autoload_rel" do + + subject { self } + + it "provides autoload_all functionality relative to the current file" do + is_expected.not_to be_loaded("Modules::Module1::First", "Modules::Module2::Second", "Modules::Zero") + require fixture_path('autoloaded_rel/modules/zero') + is_expected.to be_loaded("Modules::Module1::First", "Modules::Module2::Second", "Modules::Zero") + end + + it "needs to specify base_dir for autoloading if loading something from under top-level module directory" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + autoload_rel relative_fixture_path('autoloaded/module1') + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + + autoload_rel relative_fixture_path('autoloaded/module1'), base_dir: fixture_path('autoloaded') + is_expected.to be_loaded("Autoloaded::Module1::A") + is_expected.not_to be_loaded("Autoloaded::Module2::LongerName", "Autoloaded::Module2::Module3::B") + end + + before(:all) do + @base_dir = relative_fixture_path('autoloaded') + @method = :autoload_rel + @autoload_base_dir = fixture_path('autoloaded') + end + it_should_behave_like "#autoload_all syntactic sugar" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1.rb new file mode 100644 index 0000000000..81d90a00a1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1.rb @@ -0,0 +1,4 @@ +module Autoloaded + class Class1 + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1/c.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1/c.rb new file mode 100644 index 0000000000..ede30a1bdb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/class1/c.rb @@ -0,0 +1,6 @@ +module Autoloaded + class Class1 + class C + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module1/a.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module1/a.rb new file mode 100644 index 0000000000..b92ed48b86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module1/a.rb @@ -0,0 +1,6 @@ +module Autoloaded + module Module1 + class A + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/longer_name.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/longer_name.rb new file mode 100644 index 0000000000..d7b0df1feb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/longer_name.rb @@ -0,0 +1,6 @@ +module Autoloaded + module Module2 + class LongerName + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/module3/b.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/module3/b.rb new file mode 100644 index 0000000000..d64a890551 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/module2/module3/b.rb @@ -0,0 +1,8 @@ +module Autoloaded + module Module2 + module Module3 + class B + end + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/with_wrong_module.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/with_wrong_module.rb new file mode 100644 index 0000000000..b8d5237cd6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded/with_wrong_module.rb @@ -0,0 +1,4 @@ +module WrongModule + class WithWrongModule + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module1/first.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module1/first.rb new file mode 100644 index 0000000000..9703159535 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module1/first.rb @@ -0,0 +1,6 @@ +module Modules + module Module1 + class First + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module2/second.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module2/second.rb new file mode 100644 index 0000000000..53a456b200 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/module2/second.rb @@ -0,0 +1,6 @@ +module Modules + module Module2 + class Second + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/zero.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/zero.rb new file mode 100644 index 0000000000..4394e76a31 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/autoloaded_rel/modules/zero.rb @@ -0,0 +1,6 @@ +autoload_rel "../modules" + +module Modules + class Zero + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/error/a.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/error/a.rb new file mode 100644 index 0000000000..9a51a8fc4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/error/a.rb @@ -0,0 +1,3 @@ +class A + non_existent_method +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/a.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/a.rb new file mode 100644 index 0000000000..66c7f4e6c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/a.rb @@ -0,0 +1,2 @@ +class RelativeA +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/b/b.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/b/b.rb new file mode 100644 index 0000000000..303dfaad7f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/b/b.rb @@ -0,0 +1,5 @@ +require_rel '../a' +require_rel '../c' + +class RelativeB +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/c/c.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/c/c.rb new file mode 100644 index 0000000000..f075f7c02e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/c/c.rb @@ -0,0 +1,2 @@ +class RelativeC +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/d/d.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/d/d.rb new file mode 100644 index 0000000000..e9ba5764b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/relative/d/d.rb @@ -0,0 +1,8 @@ +load_rel '../a' +load_rel '../c' + +class RelativeD + def ok? + true + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/a.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/a.rb new file mode 100644 index 0000000000..62f5d21e99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/a.rb @@ -0,0 +1,3 @@ +require_relative 'c' + +class A < C; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/b.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/b.rb new file mode 100644 index 0000000000..679e4d64fd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/b.rb @@ -0,0 +1 @@ +class B < A; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/c.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/c.rb new file mode 100644 index 0000000000..68b417f925 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/c.rb @@ -0,0 +1,5 @@ +class C + def cool? + true + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/d.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/d.rb new file mode 100644 index 0000000000..31a2a9bf3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/resolvable/d.rb @@ -0,0 +1 @@ +class D < C; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/a.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/a.rb new file mode 100644 index 0000000000..0794bc055d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/a.rb @@ -0,0 +1 @@ +class A < C; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/b.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/b.rb new file mode 100644 index 0000000000..679e4d64fd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/b.rb @@ -0,0 +1 @@ +class B < A; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/c.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/c.rb new file mode 100644 index 0000000000..8197347474 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/c.rb @@ -0,0 +1 @@ +class C < Nonexistent; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/d.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/d.rb new file mode 100644 index 0000000000..31a2a9bf3e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/fixtures/unresolvable/d.rb @@ -0,0 +1 @@ +class D < C; end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/load_spec.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/load_spec.rb new file mode 100644 index 0000000000..54699e680e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/load_spec.rb @@ -0,0 +1,60 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.dirname(__FILE__) + '/require_shared.rb' + +describe "load_all" do + + subject { self } + + it "provides require_all functionality but using 'load' instead of 'require'" do + require_all fixture_path('resolvable') + expect(C.new).to be_cool + + class C + remove_method :cool? + def cool? + false + end + end + expect(C.new).not_to be_cool + C.send :remove_method, :cool? + + load_all fixture_path('resolvable') + expect(C.new).to be_cool + end + + before(:all) do + @base_dir = fixture_path('autoloaded') + @method = :load_all + end + it_should_behave_like "#require_all syntactic sugar" +end + +describe "load_rel" do + + subject { self } + + it "provides load_all functionality relative to the current file" do + require fixture_path('relative/d/d') + + is_expected.to be_loaded("RelativeA", "RelativeC", "RelativeD") + expect(RelativeD.new).to be_ok + + class RelativeD + remove_method :ok? + def ok? + false + end + end + expect(RelativeD.new).not_to be_ok + RelativeD.send :remove_method, :ok? + + load fixture_path('relative/d/d.rb') + expect(RelativeD.new).to be_ok + end + + before(:all) do + @base_dir = './fixtures/autoloaded' + @method = :load_rel + end + it_should_behave_like "#require_all syntactic sugar" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_shared.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_shared.rb new file mode 100644 index 0000000000..575e016991 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_shared.rb @@ -0,0 +1,78 @@ +shared_examples_for "#require_all syntactic sugar" do + before :each do + @file_list = [ + "#{@base_dir}/module1/a.rb", + "#{@base_dir}/module2/longer_name.rb", + "#{@base_dir}/module2/module3/b.rb" + ] + end + + it "accepts files with and without extensions" do + is_expected.not_to be_loaded("Autoloaded::Module2::LongerName") + expect(send(@method, @base_dir + '/module2/longer_name')).to be_truthy + is_expected.to be_loaded("Autoloaded::Module2::LongerName") + + is_expected.not_to be_loaded("Autoloaded::Module1::A") + expect(send(@method, @base_dir + '/module1/a.rb')).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A") + end + + it "accepts lists of files" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, @file_list)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "is totally cool with a splatted list of arguments" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + expect(send(@method, *@file_list)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B") + end + + it "will load all .rb files under a directory without a trailing slash" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + expect(send(@method, @base_dir)).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + end + + it "will load all .rb files under a directory with a trailing slash" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + expect(send(@method, "#{@base_dir}/")).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + end + + it "will load all files specified by a glob" do + is_expected.not_to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + expect(send(@method, "#{@base_dir}/**/*.rb")).to be_truthy + is_expected.to be_loaded("Autoloaded::Module1::A", "Autoloaded::Module2::LongerName", + "Autoloaded::Module2::Module3::B", "WrongModule::WithWrongModule") + end + + it "returns false if an empty input was given" do + expect(send(@method, [])).to be_falsey + expect(send(@method)).to be_falsey + end + + it "throws LoadError if no file or directory found" do + expect {send(@method, "not_found")}.to raise_error(LoadError) + end + + it "can handle empty directories" do + # Have to make these on the fly because they can't be saved as a test fixture because Git won't track directories with nothing in them. + FileUtils.mkpath("#{@base_dir}/empty_dir") + FileUtils.mkpath("#{@base_dir}/nested/empty_dir") + FileUtils.mkpath("#{@base_dir}/nested/more_nested/empty_dir") + + expect {send(@method, "#{@base_dir}/empty_dir")}.to_not raise_error + expect {send(@method, "#{@base_dir}/nested")}.to_not raise_error + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_spec.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_spec.rb new file mode 100644 index 0000000000..4f97b10bf6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/require_spec.rb @@ -0,0 +1,58 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.dirname(__FILE__) + '/require_shared.rb' + +describe "require_all" do + + subject { self } + + context "when files correctly declare their dependencies" do + it "requires them successfully" do + require_all fixture_path('resolvable/*.rb') + + is_expected.to be_loaded("A", "B", "C", "D") + end + end + + context "errors" do + it "raises RequireAll:LoadError if files do not declare their dependencies" do + expect do + require_all fixture_path('unresolvable/*.rb') + end.to raise_error(RequireAll::LoadError) do |error| + expect(error.cause).to be_a NameError + expect(error.message).to match /Please require the necessary files/ + end + end + + it "raises other NameErrors if encountered" do + expect do + require_all fixture_path('error') + end.to raise_error(NameError) do |error| + expect(error.message).to match /undefined local variable or method `non_existent_method'/ + end + end + end + + before(:all) do + @base_dir = fixture_path('autoloaded') + @method = :require_all + end + it_should_behave_like "#require_all syntactic sugar" +end + +describe "require_rel" do + + subject { self } + + it "provides require_all functionality relative to the current file" do + require fixture_path('relative/b/b') + + is_expected.to be_loaded("RelativeA", "RelativeB", "RelativeC") + is_expected.not_to be_loaded("RelativeD") + end + + before(:all) do + @base_dir = relative_fixture_path('autoloaded') + @method = :require_rel + end + it_should_behave_like "#require_all syntactic sugar" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/spec_helper.rb b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/spec_helper.rb new file mode 100644 index 0000000000..612c89e91d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/require_all-3.0.0/spec/spec_helper.rb @@ -0,0 +1,38 @@ +require "simplecov" +require "coveralls" + +SimpleCov.formatter = Coveralls::SimpleCov::Formatter +SimpleCov.start + +require File.dirname(__FILE__) + '/../lib/require_all.rb' + +module SpecHelper + def fixture_path(fixture_name, relative_dir = File.dirname(__FILE__)) + File.join(relative_dir, 'fixtures', fixture_name) + end + + def relative_fixture_path(fixture_name) + fixture_path(fixture_name, '.') + end + + def unload_all + %w{A B C D WrongModule Autoloaded Modules + RelativeA RelativeB RelativeC RelativeD}.each do |const| + Object.send(:remove_const, const) rescue nil + end + $LOADED_FEATURES.delete_if {|f| f =~ /autoloaded|error|relative|resolvable/} + end + + def loaded?(*klazzes) + klazzes.all? {|klazz| Object.class_eval(klazz) rescue nil} + end +end + +RSpec.configure do |config| + config.include SpecHelper + config.color = true + + config.before do + unload_all + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/LICENSE b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/LICENSE new file mode 100644 index 0000000000..c12d0dcf56 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/LICENSE @@ -0,0 +1,34 @@ +Rerun +Copyright (c) 2009 Alex Chaffee + +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 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. + +--- + +rerun partially based on code from Rspactor +Copyright (c) 2009 Mislav Marohnić +License as above (MIT open source). + +rerun partially based on code from FileSystemWatcher +http://paulhorman.com/filesystemwatcher/ +No license provided; assumed public domain. + +rerun partially based on code from Shotgun +Copyright (c) 2009 Ryan Tomayko +License as above (MIT open source). + diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/README.md new file mode 100644 index 0000000000..3ccb23f621 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/README.md @@ -0,0 +1,459 @@ +# Rerun + + + +Rerun launches your program, then watches the filesystem. If a relevant file +changes, then it restarts your program. + +Rerun works for both long-running processes (e.g. apps) and short-running ones +(e.g. tests). It's basically a no-frills command-line alternative to Guard, +Shotgun, Autotest, etc. that doesn't require config files and works on any +command, not just Ruby programs. + +Rerun's advantage is its simple design. Since it uses `exec` and the standard +Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really +acting just like it was when you ran it from the command line the first time. + +By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. +Use the `--pattern` option if you want to change this. + +As of version 0.7.0, we use the Listen gem, which tries to use your OS's +built-in facilities for monitoring the filesystem, so CPU use is very light. + +**UPDATE**: Now Rerun *does* work on Windows! Caveats: + * not well-tested + * you need to press Enter after keypress input + * you may need to install the `wdm` gem manually: `gem install wdm` + * You may see this persistent `INFO` error message; to remove it, use`--no-notify`: + * `INFO: Could not find files for the given pattern(s)` + +# Installation: + + gem install rerun + +("sudo" may be required on older systems, but try it without sudo first.) + +If you are using RVM you might want to put this in your global gemset so it's +available to all your apps. (There really should be a better way to distinguish +gems-as-libraries from gems-as-tools.) + + rvm @global do gem install rerun + +The Listen gem looks for certain platform-dependent gems, and will complain if +they're not available. Unfortunately, Rubygems doesn't understand optional +dependencies very well, so you may have to install extra gems (and/or put them +in your Gemfile) to make Rerun work more smoothly on your system. +(Learn more at .) + +On Mac OS X, use + + gem install rb-fsevent + +On Windows, use + + gem install wdm + +On *BSD, use + + gem install rb-kqueue + +## Installation via Gemfile / Bundler + +If you are using rerun inside an existing Ruby application (like a Rails or Sinatra app), you can add it to your Gemfile: + +``` ruby +group :development, :test do + gem "rerun" +end +``` + +Using a Gemfile is also an easy way to use the pre-release branch, which may have bugfixes or features you want: + +``` ruby +group :development, :test do + gem "rerun", git: "https://github.com/alexch/rerun.git" +end +``` + +When using a Gemfile, install with `bundle install` or `bundle update`, and run using `bundle exec rerun`, to guarantee you are using the rerun version specified in the Gemfile, and not a different version in a system-wide gemset. + +# Usage: + + rerun [options] [--] cmd + +For example, if you're running a Sinatra app whose main file is `app.rb`: + + rerun ruby app.rb + +If the first part of the command is a `.rb` filename, then `ruby` is +optional, so the above can also be accomplished like this: + + rerun app.rb + +Rails doesn't automatically notice all config file changes, so you can force it +to restart when you change a config file like this: + + rerun --dir config rails s + +Or if you're using Thin to run a Rack app that's configured in config.ru +but you want it on port 4000 and in debug mode, and only want to watch +the `app` and `web` subdirectories: + + rerun --dir app,web -- thin start --debug --port=4000 -R config.ru + +The `--` is to separate rerun options from cmd options. You can also +use a quoted string for the command, e.g. + + rerun --dir app "thin start --debug --port=4000 -R config.ru" + +Rackup can also be used to launch a Rack server, so let's try that: + + rerun -- rackup --port 4000 config.ru + +Want to mimic [autotest](https://github.com/grosser/autotest)? Try + + rerun -x rake + +or + + rerun -cx rspec + +And if you're using [Spork](https://github.com/sporkrb/spork) with Rails, you +need to [restart your spork server](https://github.com/sporkrb/spork/issues/201) +whenever certain Rails environment files change, so why not put this in your +Rakefile... + + desc "run spork (via rerun)" + task :spork do + sh "rerun --pattern '{Gemfile,Gemfile.lock,spec/spec_helper.rb,.rspec,spec/factories/**,config/environment.rb,config/environments/test.rb,config/initializers/*.rb,lib/**/*.rb}' -- spork" + end + +and start using `rake spork` to launch your spork server? + +(If you're using Guard instead of Rerun, check out +[guard-spork](https://github.com/guard/guard-spork) +for a similar solution.) + +How about regenerating your HTML files after every change to your +[Erector](http://erector.rubyforge.org) widgets? + + rerun -x erector --to-html my_site.rb + +Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your +Procfile processes locally and restart them all when necessary. + + rerun foreman start + +# Options: + +These options can be specified on the command line and/or inside a `.rerun` config file (see below). + +`--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options. + +`--pattern` glob to match inside directory. This uses the Ruby Dir glob style -- see for details. +By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. +On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`). +Run `rerun --help` to see the actual list. + +`--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append `'/*'` e.g. + `--ignore 'coverage/*'`. + +`--[no-]ignore-dotfiles` By default, on top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot. Setting `--no-ignore-dotfiles` allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs. + +`--signal` (or `-s`) use specified signal(s) (instead of the default `TERM,INT,KILL`) to terminate the previous process. You can use a comma-delimited list if you want to try a signal, wait up to 5 seconds for the process to die, then try again with a different signal, and so on. +This may be useful for forcing the respective process to terminate as quickly as possible. +(`--signal KILL` is the equivalent of `kill -9`) + +`--wait sec` (or `-w`) after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: 2 sec + +`--restart` (or `-r`) expect process to restart itself, using signal HUP by default +(e.g. `-r -s INT` will send a INT and then resume watching for changes) + +`--exit` (or -x) expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the launched process is still running. + +`--clear` (or -c) clear the screen before each run + +`--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded + +`--notify NOTIFIER` use `growl` or `osx` or `notify-send` for notifications (see below) + +`--no-notify` disable notifications + +`--name` set the app name (for display) + +`--force-polling` use polling instead of a native filesystem scan (useful for Vagrant) + +`--quiet` silences most messages + +`--verbose` enables even more messages (unless you also specified `--quiet`, which overrides `--verbose`) + +Also `--version` and `--help`, naturally. + +## Config file + +If the current directory contains a file named `.rerun`, it will be parsed with the same rules as command-line arguments. Newlines are the same as any other whitespace, so you can stack options vertically, like this: + +``` +--quiet +--pattern **/*.{rb,js,scss,sass,html,md} +``` + +Options specified on the command line will override those in the config file. You can negate boolean options with `--no-`, so for example, with the above config file, to re-enable logging, you could say: + +```sh +rerun --no-quiet rackup +``` + +If you're not sure what options are being overwritten, use `--verbose` and rerun will show you the final result of the parsing. + +# Notifications + +If you have `growlnotify` available on the `PATH`, it sends notifications to +growl in addition to the console. + +If you have `terminal-notifier`, it sends notifications to +the OS X notification center in addition to the console. + +If you have `notify-send`, it sends notifications to Freedesktop-compatible +desktops in addition to the console. + +If you have more than one available notification program, Rerun will pick one, or you can choose between them using `--notify growl`, `--notify osx`, `--notify notify-send`, etc. + +If you have a notifier installed but don't want rerun to use it, +set the `--no-notify` option. + +Download [growlnotify here](http://growl.info/downloads.php#generaldownloads) +now that Growl has moved to the App Store. + +Install [terminal-notifier](https://github.com/julienXX/terminal-notifier) using `gem install terminal-notifier`. (You may have to put it in your system gemset and/or use `sudo` too.) Using Homebrew to install terminal-notifier is not recommended. + +On Debian/Ubuntu, `notify-send` is availble in the `libnotify-bin` package. On other GNU/Linux systems, it might be in a package with a different name. + +# On-The-Fly Commands + +While the app is (re)running, you can make things happen by pressing keys: + +* **r** -- restart (as if a file had changed) +* **f** -- force restart (stop and start) +* **c** -- clear the screen +* **x** or **q** -- exit (just like control-C) +* **p** -- pause/unpause filesystem watching + +If you're backgrounding or using Pry or a debugger, you might not want these +keys to be trapped, so use the `--background` option. + +# Signals + +The current algorithm for killing the process is: + +* send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option) +* if that doesn't work after 2 seconds, send SIGINT (aka control-C) +* if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9) + +This seems like the most gentle and unixy way of doing things, but it does +mean that if your program ignores SIGTERM, it takes an extra 2 to 4 seconds to +restart. + +If you want to use your own series of signals, use the `--signal` option. If you want to change the delay before attempting the next signal, use the `--wait` option. + +# Vagrant and VirtualBox + +If running inside a shared directory using Vagrant and VirtualBox, you must pass the `--force-polling` option. You may also have to pass some extra `--ignore` options too; otherwise each scan can take 10 or more seconds on directories with a large number of files or subdirectories underneath it. + +# Troubleshooting + +## zsh ## + +If you are using `zsh` as your shell, and you are specifying your `--pattern` as `**/*.rb`, you may face this error +``` +Errno::EACCES: Permission denied - +``` +This is because `**/*.rb` gets expanded into the command by `zsh` instead of passing it through to rerun. The solution is to simply quote ('' or "") the pattern. +i.e +``` +rerun -p **/*.rb rake test +``` +becomes +``` +rerun -p "**/*.rb" rake test +``` + +# To Do: + +## Must have for v1.0 +* Make sure to pass through quoted options correctly to target process [bug] +* Optionally do "bundle install" before and "bundle exec" during launch + +## Nice to have +* ".rerun" file in $HOME +* If the last element of the command is a `.ru` file and there's no other command then use `rackup` +* Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru` +* Specify (or deduce) port to listen for to determine success of a web server launch +* see also [todo.md](todo.md) + +## Wacky Ideas + +* On OS X: + * use a C library using growl's developer API + * Use growl's AppleScript or SDK instead of relying on growlnotify + * "Failed" icon for notifications + +# Other projects that do similar things + +* Guard: +* Restartomatic: +* Shotgun: +* Rack::Reloader middleware: +* The Sinatra FAQ has a discussion at +* Kicker: +* Watchr: +* Autotest: + +# Why would I use this instead of Shotgun? + +Shotgun does a "fork" after the web framework has loaded but before +your application is loaded. It then loads your app, processes a +single request in the child process, then exits the child process. + +Rerun launches the whole app, then when it's time to restart, uses +"kill" to shut it down and starts the whole thing up again from +scratch. + +So rerun takes somewhat longer than Shotgun to restart the app, but +does it much less frequently. And once it's running it behaves more +normally and consistently with your production app. + +Also, Shotgun reloads the app on every request, even if it doesn't +need to. This is fine if you're loading a single file, but if your web +pages all load other files (CSS, JS, media) then that adds up quickly. +(I can only assume that the developers of shotgun are using caching or a +front web server so this isn't a pain point for them.) + +And hey, does Shotgun reload your Worker processes if you're using Foreman and +a Procfile? I'm pretty sure it doesn't. + +YMMV! + +# Why would I use this instead of Rack::Reloader? + +Rack::Reloader is certifiably beautiful code, and is a very elegant use +of Rack's middleware architecture. But because it relies on the +LOADED_FEATURES variable, it only reloads .rb files that were 'require'd, +not 'load'ed. That leaves out (non-Erector) template files, and also, +at least the way I was doing it, sub-actions (see +[this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a# +)). + +Rack::Reloader also doesn't reload configuration changes or redo other +things that happen during app startup. Rerun takes the attitude that if +you want to restart an app, you should just restart the whole app. You know? + +# Why would I use this instead of Guard? + +Guard is very powerful but requires some up-front configuration. +Rerun is meant as a no-frills command-line alternative requiring no knowledge +of Ruby nor config file syntax. + +# Why did you write this? + +I've been using [Sinatra](http://sinatrarb.com) and loving it. In order +to simplify their system, the Rat Pack removed auto-reloading from +Sinatra proper. I approve of this: a web application framework should be +focused on serving requests, not on munging Ruby ObjectSpace for +dev-time convenience. But I still wanted automatic reloading during +development. Shotgun wasn't working for me (see above) so I spliced +Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun +-- with a heavy amount of refactoring and rewriting. In late 2012 I +migrated the backend to the Listen gem, which was extracted from Guard, +so it should be more reliable and performant on multiple platforms. + +# Credits + +Rerun: [Alex Chaffee](http://alexchaffee.com), , + +Based upon and/or inspired by: + +* Shotgun: +* Rspactor: + * (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ ) +* FileSystemWatcher: + +## Patches by: + +* David Billskog +* Jens B +* Andrés Botero +* Dreamcat4 +* +* Barry Sia +* Paul Rangel +* James Edward Gray II +* Raul E Rangel and Antonio Terceiro +* Mike Pastore +* Andy Duncan +* Brent Van Minnen +* Matthew O'Riordan +* Antonio Terceiro +* +* + +# Version History + +* + * --no-ignore-dotfiles option + +* v0.13.0 26 January 2018 + * bugfix: pause/unpause works again (thanks Barry!) + * `.rerun` config file + +* v0.12.0 23 January 2018 + * smarter `--signal` option, allowing you to specify a series of signals to try in order; also `--wait` to change how long between tries + * `--force-polling` option (thanks ajduncan) + * `f` key to force stop and start (thanks mwpastore) + * add `.c` and `.h` files to default ignore list + * support for Windows + * use `Kernel.spawn` instead of `fork` + * use `wdm` gem for Windows Directory Monitor + * support for notifications on GNU/Linux using [notify-send](http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send) (thanks terceiro) + * fix `Gem::LoadError - terminal-notifier is not part of the bundle` [bug](https://github.com/alexch/rerun/issues/108) (thanks mattheworiordan) + +* 0.11.0 7 October 2015 + * better 'changed' message + * `--notify osx` option + * `--restart` option (with bugfix by Mike Pastore) + * use Listen 3 gem + * add `.feature` files to default watchlist (thanks @jmuheim) + +* v0.10.0 4 May 2014 + * add '.coffee,.slim,.md' to default pattern (thanks @xylinq) + * --ignore option + +* v0.9.0 6 March 2014 + * --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!) + * --name option + * press 'p' to pause/unpause filesystem watching (Barry is the man!) + * works with Listen 2 (note: needs 2.3 or higher) + * cooldown works, thanks to patches to underlying Listen gem + * ignore all dotfiles, and add actual list of ignored dirs and files + +* v0.8.2 + * bugfix, forcing Rerun to use Listen v1.0.3 while we work out the troubles we're having with Listen 1.3 and 2.1 + +* v0.8.1 + * bugfix release (#30 and #34) + +* v0.8.0 + * --background option (thanks FND!) to disable the keyboard listener + * --signal option (thanks FND!) + * --no-growl option + * --dir supports multiple directories (thanks Barry!) + +* v0.7.1 + * bugfix: make rails icon work again + +* v0.7.0 + * uses Listen gem (which uses rb-fsevent for lightweight filesystem snooping) + +# License + +Open Source MIT License. See "LICENSE" file. diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/Rakefile new file mode 100644 index 0000000000..6ca278d733 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/Rakefile @@ -0,0 +1,82 @@ +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rspec/core/rake_task' + +task :default => [:spec] +task :test => :spec + +desc "Run all specs" +RSpec::Core::RakeTask.new('spec') do |t| + ENV['ENV'] = "test" + t.pattern = 'spec/**/*_spec.rb' + t.rspec_opts = ['--color'] +end + +$rubyforge_project = 'pivotalrb' + +$spec = + begin + require 'rubygems/specification' + data = File.read('rerun.gemspec') + spec = nil + #Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join + spec = eval data + spec + end + +def package(ext='') + "pkg/#{$spec.name}-#{$spec.version}" + ext +end + +desc 'Exit if git is dirty' +task :check_git do + state = `git status 2> /dev/null | tail -n1` + clean = (state =~ /working (directory|tree) clean/) + unless clean + warn "can't do that on an unclean git dir" + exit 1 + end +end + +desc 'Build packages' +task :package => %w[.gem .tar.gz].map { |e| package(e) } + +desc 'Build and install as local gem' +task :install => package('.gem') do + sh "gem install #{package('.gem')}" +end + +directory 'pkg/' +CLOBBER.include('pkg') + +file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f| + sh "gem build #{$spec.name}.gemspec" + mv File.basename(f.name), f.name +end + +file package('.tar.gz') => %w[pkg/] + $spec.files do |f| + cmd = <<-SH + git archive \ + --prefix=#{$spec.name}-#{$spec.version}/ \ + --format=tar \ + HEAD | gzip > #{f.name} + SH + sh cmd.gsub(/ +/, ' ') +end + +desc 'Publish gem and tarball to rubyforge' +task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t| + puts "Releasing #{$spec.version}" + sh "gem push #{package('.gem')}" + puts "Tagging and pushing" + sh "git tag v#{$spec.version}" + sh "git push && git push --tags" +end + +desc 'download github issues and pull requests' +task 'github' do + %w(issues pulls).each do |type| + sh "curl -o #{type}.json https://api.github.com/repos/alexch/rerun/#{type}" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/bin/rerun b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/bin/rerun new file mode 100755 index 0000000000..bd5905963f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/bin/rerun @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require 'rubygems' +libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib" +$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) + +require 'rerun' +require 'optparse' + +options = Rerun::Options.parse config_file: ".rerun" + +if options and options[:verbose] + puts "\nrerun options:\n\t#{options}" +end + +exit if options.nil? or options[:cmd].nil? or options[:cmd].empty? + +Rerun::Runner.keep_running(options[:cmd], options) diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_grn_sml.png b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_grn_sml.png new file mode 100644 index 0000000000..b335baeabf Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_grn_sml.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_red_sml.png b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_red_sml.png new file mode 100644 index 0000000000..b8441f182e Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/icons/rails_red_sml.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/goo.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/goo.rb new file mode 100644 index 0000000000..b7c4e0a55d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/goo.rb @@ -0,0 +1,3 @@ +goooo +goooo +goooo diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun.rb new file mode 100644 index 0000000000..31eecfef47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun.rb @@ -0,0 +1,15 @@ +here = File.expand_path(File.dirname(__FILE__)) +$: << here unless $:.include?(here) + +require "listen" # pull in the Listen gem +require "rerun/options" +require "rerun/system" +require "rerun/notification" +require "rerun/runner" +require "rerun/watcher" +require "rerun/glob" + +module Rerun + +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/glob.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/glob.rb new file mode 100644 index 0000000000..c15f6b57ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/glob.rb @@ -0,0 +1,85 @@ +# based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string- + +# todo: release as separate gem +# +module Rerun + class Glob + NO_LEADING_DOT = '(?=[^\.])' # todo + START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash + END_OF_STRING = '\z' + + def initialize glob_string + @glob_string = glob_string + end + + def to_regexp_string + chars = @glob_string.split('') + + chars = smoosh(chars) + + curlies = 0 + escaping = false + string = chars.map do |char| + if escaping + escaping = false + char + else + case char + when '**' + "([^/]+/)*" + when '*' + ".*" + when "?" + "." + when "." + "\\." + + when "{" + curlies += 1 + "(" + when "}" + if curlies > 0 + curlies -= 1 + ")" + else + char + end + when "," + if curlies > 0 + "|" + else + char + end + when "\\" + escaping = true + "\\" + + else + char + + end + end + end.join + START_OF_FILENAME + string + END_OF_STRING + end + + def to_regexp + Regexp.new(to_regexp_string) + end + + def smoosh chars + out = [] + until chars.empty? + char = chars.shift + if char == "*" and chars.first == "*" + chars.shift + chars.shift if chars.first == "/" + out.push("**") + else + out.push(char) + end + end + out + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/notification.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/notification.rb new file mode 100644 index 0000000000..b5b3c5b943 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/notification.rb @@ -0,0 +1,82 @@ +# todo: unit tests + +module Rerun + class Notification + include System + + attr_reader :title, :body, :options + + def initialize(title, body, options = Options::DEFAULTS.dup) + @title = title + @body = body + @options = options + end + + def command + # todo: strategy or subclass + + s = nil + + if options[:notify] == true or options[:notify] == "growl" + if (cmd = command_named("growlnotify")) + # todo: check version of growlnotify and warn if it's too old + icon_str = ("--image \"#{icon}\"" if icon) + s = "#{cmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" + end + end + + if s.nil? and options[:notify] == true or options[:notify] == "osx" + if (cmd = command_named("terminal-notifier")) + icon_str = ("-appIcon \"#{icon}\"" if icon) + s = "#{cmd} -title \"#{app_name}\" -message \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" + end + end + + if s.nil? and options[:notify] == true or options[:notify] == "notify-send" + if (cmd = command_named('notify-send')) + icon_str = "--icon #{icon}" if icon + s = "#{cmd} -t 500 --hint=int:transient:1 #{icon_str} \"#{app_name}: #{title}\" \"#{body}\"" + end + end + + s + end + + def command_named(name) + which_command = windows? ? 'where.exe %{cmd}' : 'which %{cmd} 2> /dev/null' + # TODO: remove 'INFO' error message + path = `#{which_command % {cmd: name}}`.chomp + path.empty? ? nil : path + end + + def send(background = true) + return unless command + with_clean_env do + `#{command}#{" &" if background}` + end + end + + def app_name + options[:name] + end + + def icon + "#{icon_dir}/rails_red_sml.png" if rails? + end + + def icon_dir + here = File.expand_path(File.dirname(__FILE__)) + File.expand_path("#{here}/../../icons") + end + + def with_clean_env + if defined?(Bundler) + Bundler.with_clean_env do + yield + end + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/options.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/options.rb new file mode 100644 index 0000000000..b1a26c1f4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/options.rb @@ -0,0 +1,146 @@ +require 'optparse' +require 'pathname' +require 'rerun/watcher' +require 'rerun/system' + +libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}" + +$spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec")) + +module Rerun + class Options + + extend Rerun::System + + # If you change the default pattern, please update the README.md file -- the list appears twice therein, which at the time of this comment are lines 17 and 119 + DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h}" + DEFAULT_DIRS = ["."] + + DEFAULTS = { + :background => false, + :dir => DEFAULT_DIRS, + :force_polling => false, + :ignore => [], + :ignore_dotfiles => true, + :name => Pathname.getwd.basename.to_s.capitalize, + :notify => true, + :pattern => DEFAULT_PATTERN, + :quiet => false, + :signal => (windows? ? "TERM,KILL" : "TERM,INT,KILL"), + :verbose => false, + :wait => 2, + } + + def self.parse args: ARGV, config_file: nil + + default_options = DEFAULTS.dup + options = { + ignore: [] + } + + if config_file && File.exist?(config_file) + require 'shellwords' + config_args = File.read(config_file).shellsplit + args = config_args + args + end + + option_parser = OptionParser.new("", 24, ' ') do |o| + o.banner = "Usage: rerun [options] [--] cmd" + + o.separator "" + o.separator "Launches an app, and restarts it when the filesystem changes." + o.separator "See http://github.com/alexch/rerun for more info." + o.separator "Version: #{$spec.version}" + o.separator "" + o.separator "Options:" + + o.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir| + elements = dir.split(",") + options[:dir] = (options[:dir] || []) + elements + end + + # todo: rename to "--watch" + o.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern| + options[:pattern] = pattern + end + + o.on("-i pattern", "--ignore pattern", "file glob(s) to ignore. Can be set many times. To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*' . Globs do not match dotfiles by default.") do |pattern| + options[:ignore] += [pattern] + end + + o.on("--[no-]ignore-dotfiles", "by default, file globs do not match files that begin with a dot. Setting --no-ignore-dotfiles allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.") do |value| + options[:ignore_dotfiles] = value + end + + o.on("-s signal", "--signal signal", "terminate process using this signal. To try several signals in series, use a comma-delimited list. Default: \"#{DEFAULTS[:signal]}\"") do |signal| + options[:signal] = signal + end + + o.on("-w sec", "--wait sec", "after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: #{DEFAULTS[:wait]} sec") + + o.on("-r", "--restart", "expect process to restart itself, so just send a signal and continue watching. Sends the HUP signal unless overridden using --signal") do |signal| + options[:restart] = true + default_options[:signal] = "HUP" + end + + o.on("-x", "--exit", "expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the process is running.") do |value| + options[:exit] = value + end + + o.on("-c", "--clear", "clear screen before each run") do |value| + options[:clear] = value + end + + o.on("-b", "--background", "disable on-the-fly keypress commands, allowing the process to be backgrounded") do |value| + options[:background] = value + end + + o.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name| + options[:name] = name + end + + o.on("--[no-]force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do |value| + options[:force_polling] = value + end + + o.on("--no-growl", "don't use growl [OBSOLETE]") do + options[:growl] = false + $stderr.puts "--no-growl is obsolete; use --no-notify instead" + return + end + + o.on("--[no-]notify [notifier]", "send messages through a desktop notification application. Supports growl (requires growlnotify), osx (requires terminal-notifier gem), and notify-send on GNU/Linux (notify-send must be installed)") do |notifier| + notifier = true if notifier.nil? + options[:notify] = notifier + end + + o.on("-q", "--[no-]quiet", "don't output any logs") do |value| + options[:quiet] = value + end + + o.on("--[no-]verbose", "log extra stuff like PIDs (unless you also specified `--quiet`") do |value| + options[:verbose] = value + end + + o.on_tail("-h", "--help", "--usage", "show this message and immediately exit") do + puts o + return + end + + o.on_tail("--version", "show version and immediately exit") do + puts $spec.version + return + end + + end + + puts option_parser if args.empty? + option_parser.parse! args + options = default_options.merge(options) + options[:cmd] = args.join(" ").strip # todo: better arg word handling + + options + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/runner.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/runner.rb new file mode 100644 index 0000000000..e879e8bbb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/runner.rb @@ -0,0 +1,374 @@ +require 'timeout' +require 'io/wait' + +module Rerun + class Runner + + # The watcher instance that wait for changes + attr_reader :watcher + + def self.keep_running(cmd, options) + runner = new(cmd, options) + runner.start + runner.join + # apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-( + sleep 10000 while true # :-( + end + + include System + include ::Timeout + + def initialize(run_command, options = {}) + @run_command, @options = run_command, options + @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/ + @options[:directory] ||= options.delete(:dir) || '.' + @options[:ignore] ||= [] + end + + def start_keypress_thread + return if @options[:background] + + @keypress_thread = Thread.new do + while true + if c = key_pressed + case c.downcase + when 'c' + say "Clearing screen" + clear_screen + when 'r' + say "Restarting" + restart + when 'f' + say "Stopping and starting" + restart(false) + when 'p' + toggle_pause + when 'x', 'q' + die + break # the break will stop this thread, in case the 'die' doesn't + else + puts "\n#{c.inspect} pressed inside rerun" + puts [["c", "clear screen"], + ["r", "restart"], + ["f", "forced restart (stop and start)"], + ["p", "toggle pause"], + ["x or q", "stop and exit"] + ].map {|key, description| " #{key} -- #{description}"}.join("\n") + puts + end + end + sleep 1 # todo: use select instead of polling somehow? + end + end + @keypress_thread.run + end + + def stop_keypress_thread + @keypress_thread.kill if @keypress_thread + @keypress_thread = nil + end + + def restart(with_signal = true) + @restarting = true + if @options[:restart] && with_signal + restart_with_signal(@options[:signal]) + else + stop + start + end + @restarting = false + end + + def toggle_pause + unless @pausing + say "Pausing. Press 'p' again to resume." + @watcher.pause + @pausing = true + else + say "Resuming." + @watcher.unpause + @pausing = false + end + end + + def unpause + @watcher.unpause + end + + def dir + @options[:directory] + end + + def pattern + @options[:pattern] + end + + def clear? + @options[:clear] + end + + def quiet? + @options[:quiet] + end + + def verbose? + @options[:verbose] + end + + def exit? + @options[:exit] + end + + def app_name + @options[:name] + end + + def restart_with_signal(restart_signal) + if @pid && (@pid != 0) + notify "restarting", "We will be with you shortly." + send_signal(restart_signal) + end + end + + def force_polling + @options[:force_polling] + end + + def start + if @already_running + taglines = [ + "Here we go again!", + "Keep on trucking.", + "Once more unto the breach, dear friends, once more!", + "The road goes ever on and on, down from the door where it began.", + ] + notify "restarted", taglines[rand(taglines.size)] + else + taglines = [ + "To infinity... and beyond!", + "Charge!", + ] + notify "launched", taglines[rand(taglines.size)] + @already_running = true + end + + clear_screen if clear? + start_keypress_thread unless @keypress_thread + + begin + @pid = run @run_command + say "Rerun (#{$PID}) running #{app_name} (#{@pid})" + rescue => e + puts "#{e.class}: #{e.message}" + exit + end + + status_thread = Process.detach(@pid) # so if the child exits, it dies + + Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process + die + end + + Signal.trap("TERM") do # TERM is the polite way of terminating a process + die + end + + begin + sleep 2 + rescue Interrupt => e + # in case someone hits control-C immediately ("oops!") + die + end + + if exit? + status = status_thread.value + if status.success? + notify "succeeded", "" + else + notify "failed", "Exit status #{status.exitstatus}" + end + else + if !running? + notify "Launch Failed", "See console for error output" + @already_running = false + end + end + + unless @watcher + watcher = Watcher.new(@options) do |changes| + message = change_message(changes) + say "Change detected: #{message}" + restart unless @restarting + end + watcher.start + @watcher = watcher + ignore = @options[:ignore] + say "Watching #{dir.join(', ')} for #{pattern}" + + (ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") + + (watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter") + end + end + + def run command + Kernel.spawn command + end + + def change_message(changes) + message = [:modified, :added, :removed].map do |change| + count = changes[change] ? changes[change].size : 0 + if count > 0 + "#{count} #{change}" + end + end.compact.join(", ") + + changed_files = changes.values.flatten + if changed_files.count > 0 + message += ": " + message += changes.values.flatten[0..3].map {|path| path.split('/').last}.join(', ') + if changed_files.count > 3 + message += ", ..." + end + end + message + end + + def die + #stop_keypress_thread # don't do this since we're probably *in* the keypress thread + stop # stop the child process if it exists + exit 0 # todo: status code param + end + + def join + @watcher.join + end + + def running? + send_signal(0) + end + + # Send the signal to process @pid and wait for it to die. + # @returns true if the process dies + # @returns false if either sending the signal fails or the process fails to die + def signal_and_wait(signal) + + signal_sent = if windows? + force_kill = (signal == 'KILL') + system("taskkill /T #{'/F' if force_kill} /PID #{@pid}") + else + send_signal(signal) + end + + if signal_sent + # the signal was successfully sent, so wait for the process to die + begin + timeout(@options[:wait]) do + Process.wait(@pid) + end + process_status = $? + say "Process ended: #{process_status}" if verbose? + true + rescue Timeout::Error + false + end + else + false + end + end + + # Send the signal to process @pid. + # @returns true if the signal is sent + # @returns false if sending the signal fails + # If sending the signal fails, the exception will be swallowed + # (and logged if verbose is true) and this method will return false. + # + def send_signal(signal) + say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose? + Process.kill(signal, @pid) + true + rescue => e + say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose? + false + end + + # todo: test escalation + def stop + if @pid && (@pid != 0) + notify "stopping", "All good things must come to an end." unless @restarting + @options[:signal].split(',').each do |signal| + success = signal_and_wait(signal) + return true if success + end + end + rescue + false + end + + def git_head_changed? + old_git_head = @git_head + read_git_head + @git_head and old_git_head and @git_head != old_git_head + end + + def read_git_head + git_head_file = File.join(dir, '.git', 'HEAD') + @git_head = File.exists?(git_head_file) && File.read(git_head_file) + end + + def notify(title, body, background = true) + Notification.new(title, body, @options).send(background) if @options[:notify] + puts + say "#{app_name} #{title}" + end + + def say msg + puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet? + end + + def stty(args) + system "stty #{args}" + end + + # non-blocking stdin reader. + # returns a 1-char string if a key was pressed; otherwise nil + # + def key_pressed + return one_char if windows? + begin + # this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin + + # 'raw' means turn raw input on + + # restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h + # looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */ + # which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */ + # so this sets it back on again since all we care about is raw input, not raw output + stty "raw opost" + one_char + ensure + stty "-raw" # turn raw input off + end + + # note: according to 'man tty' the proper way restore the settings is + # tty_state=`stty -g` + # ensure + # system 'stty "#{tty_state}' + # end + # but this way seems fine and less confusing + end + + def clear_screen + # see http://ascii-table.com/ansi-escape-sequences-vt-100.php + $stdout.print "\033[H\033[2J" + end + + private + def one_char + c = nil + if $stdin.ready? + c = $stdin.getc + end + c.chr if c + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/system.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/system.rb new file mode 100644 index 0000000000..31e9ace1f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/system.rb @@ -0,0 +1,22 @@ +module Rerun + module System + + def mac? + RUBY_PLATFORM =~ /darwin/i + end + + def windows? + RUBY_PLATFORM =~ /(mswin|mingw32)/i + end + + def linux? + RUBY_PLATFORM =~ /linux/i + end + + def rails? + rails_sig_file = File.expand_path(".")+"/config/boot.rb" + File.exists? rails_sig_file + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/watcher.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/watcher.rb new file mode 100644 index 0000000000..da165c6bca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/lib/rerun/watcher.rb @@ -0,0 +1,134 @@ +require 'listen' + +Thread.abort_on_exception = true + +# This class will watch a directory and alert you of +# new files, modified files, deleted files. +# +# Now uses the Listen gem, but spawns its own thread on top. +# We should probably be accessing the Listen thread directly. +# +# Author: Alex Chaffee +# +module Rerun + class Watcher + InvalidDirectoryError = Class.new(RuntimeError) + + #def self.default_ignore + # Listen::Silencer.new(Listen::Listener.new).send :_default_ignore_patterns + #end + + attr_reader :directory, :pattern, :priority, :ignore_dotfiles + + # Create a file system watcher. Start it by calling #start. + # + # @param options[:directory] the directory to watch (default ".") + # @param options[:pattern] the glob pattern to search under the watched directory (default "**/*") + # @param options[:priority] the priority of the watcher thread (default 0) + # + def initialize(options = {}, &client_callback) + @client_callback = client_callback + + options = { + :directory => ".", + :pattern => "**/*", + :priority => 0, + :ignore_dotfiles => true, + }.merge(options) + + @pattern = options[:pattern] + @directories = options[:directory] + @directories = sanitize_dirs(@directories) + @priority = options[:priority] + @force_polling = options[:force_polling] + @ignore = [options[:ignore]].flatten.compact + @ignore_dotfiles = options[:ignore_dotfiles] + @thread = nil + end + + def sanitize_dirs(dirs) + dirs = [*dirs] + dirs.map do |d| + d.chomp!("/") + unless FileTest.exists?(d) && FileTest.readable?(d) && FileTest.directory?(d) + raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable" + end + File.expand_path(d) + end + end + + def start + if @thread then + raise RuntimeError, "already started" + end + + @thread = Thread.new do + @listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1, force_polling: @force_polling) do |modified, added, removed| + count = modified.size + added.size + removed.size + if count > 0 + @client_callback.call(:modified => modified, :added => added, :removed => removed) + end + end + @listener.start + end + + @thread.priority = @priority + + sleep 0.1 until @listener + + at_exit { stop } # try really hard to clean up after ourselves + end + + def watching + Rerun::Glob.new(@pattern).to_regexp + end + + def ignoring + patterns = [] + if ignore_dotfiles + patterns << /^\.[^.]/ # at beginning of string, a real dot followed by any other character + end + patterns + @ignore.map { |x| Rerun::Glob.new(x).to_regexp } + end + + # kill the file watcher thread + def stop + @thread.wakeup rescue ThreadError + begin + @listener.stop + rescue Exception => e + puts "#{e.class}: #{e.message} stopping listener" + end + @thread.kill rescue ThreadError + end + + # wait for the file watcher to finish + def join + @thread.join if @thread + rescue Interrupt + # don't care + end + + def pause + @listener.pause if @listener + end + + def unpause + @listener.start if @listener + end + + def running? + @listener && @listener.processing? + end + + def adapter + @listener && + (backend = @listener.instance_variable_get(:@backend)) && + backend.instance_variable_get(:@adapter) + end + + def adapter_name + adapter && adapter.class.name.split('::').last + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/rerun.gemspec b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/rerun.gemspec new file mode 100644 index 0000000000..f027ffec98 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.13.1/rerun.gemspec @@ -0,0 +1,34 @@ +$spec = Gem::Specification.new do |s| + s.specification_version = 2 if s.respond_to? :specification_version= + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + + s.name = 'rerun' + s.version = '0.13.1' + + s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." + s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." + + s.authors = ["Alex Chaffee"] + s.email = "alex@stinky.com" + + s.files = %w[ + README.md + LICENSE + Rakefile + rerun.gemspec + bin/rerun + icons/rails_grn_sml.png + icons/rails_red_sml.png] + + Dir['lib/**/*.rb'] + s.executables = ['rerun'] + s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/} + + s.extra_rdoc_files = %w[README.md] + + s.add_runtime_dependency 'listen', '~> 3.0' + + s.homepage = "http://github.com/alexch/rerun/" + s.require_paths = %w[lib] + + s.license = 'MIT' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/LICENSE b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/LICENSE new file mode 100644 index 0000000000..c12d0dcf56 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/LICENSE @@ -0,0 +1,34 @@ +Rerun +Copyright (c) 2009 Alex Chaffee + +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 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. + +--- + +rerun partially based on code from Rspactor +Copyright (c) 2009 Mislav Marohnić +License as above (MIT open source). + +rerun partially based on code from FileSystemWatcher +http://paulhorman.com/filesystemwatcher/ +No license provided; assumed public domain. + +rerun partially based on code from Shotgun +Copyright (c) 2009 Ryan Tomayko +License as above (MIT open source). + diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/README.md new file mode 100644 index 0000000000..6a8193e52b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/README.md @@ -0,0 +1,462 @@ +# Rerun + + + +Rerun launches your program, then watches the filesystem. If a relevant file +changes, then it restarts your program. + +Rerun works for both long-running processes (e.g. apps) and short-running ones +(e.g. tests). It's basically a no-frills command-line alternative to Guard, +Shotgun, Autotest, etc. that doesn't require config files and works on any +command, not just Ruby programs. + +Rerun's advantage is its simple design. Since it uses `exec` and the standard +Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really +acting just like it was when you ran it from the command line the first time. + +By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. +Use the `--pattern` option if you want to change this. + +As of version 0.7.0, we use the Listen gem, which tries to use your OS's +built-in facilities for monitoring the filesystem, so CPU use is very light. + +**UPDATE**: Now Rerun *does* work on Windows! Caveats: + * not well-tested + * you need to press Enter after keypress input + * you may need to install the `wdm` gem manually: `gem install wdm` + * You may see this persistent `INFO` error message; to remove it, use`--no-notify`: + * `INFO: Could not find files for the given pattern(s)` + +# Installation: + + gem install rerun + +("sudo" may be required on older systems, but try it without sudo first.) + +If you are using RVM you might want to put this in your global gemset so it's +available to all your apps. (There really should be a better way to distinguish +gems-as-libraries from gems-as-tools.) + + rvm @global do gem install rerun + +The Listen gem looks for certain platform-dependent gems, and will complain if +they're not available. Unfortunately, Rubygems doesn't understand optional +dependencies very well, so you may have to install extra gems (and/or put them +in your Gemfile) to make Rerun work more smoothly on your system. +(Learn more at .) + +On Mac OS X, use + + gem install rb-fsevent + +On Windows, use + + gem install wdm + +On *BSD, use + + gem install rb-kqueue + +## Installation via Gemfile / Bundler + +If you are using rerun inside an existing Ruby application (like a Rails or Sinatra app), you can add it to your Gemfile: + +``` ruby +group :development, :test do + gem "rerun" +end +``` + +Using a Gemfile is also an easy way to use the pre-release branch, which may have bugfixes or features you want: + +``` ruby +group :development, :test do + gem "rerun", git: "https://github.com/alexch/rerun.git" +end +``` + +When using a Gemfile, install with `bundle install` or `bundle update`, and run using `bundle exec rerun`, to guarantee you are using the rerun version specified in the Gemfile, and not a different version in a system-wide gemset. + +# Usage: + + rerun [options] [--] cmd + +For example, if you're running a Sinatra app whose main file is `app.rb`: + + rerun ruby app.rb + +If the first part of the command is a `.rb` filename, then `ruby` is +optional, so the above can also be accomplished like this: + + rerun app.rb + +Rails doesn't automatically notice all config file changes, so you can force it +to restart when you change a config file like this: + + rerun --dir config rails s + +Or if you're using Thin to run a Rack app that's configured in config.ru +but you want it on port 4000 and in debug mode, and only want to watch +the `app` and `web` subdirectories: + + rerun --dir app,web -- thin start --debug --port=4000 -R config.ru + +The `--` is to separate rerun options from cmd options. You can also +use a quoted string for the command, e.g. + + rerun --dir app "thin start --debug --port=4000 -R config.ru" + +Rackup can also be used to launch a Rack server, so let's try that: + + rerun -- rackup --port 4000 config.ru + +Want to mimic [autotest](https://github.com/grosser/autotest)? Try + + rerun -x rake + +or + + rerun -cx rspec + +And if you're using [Spork](https://github.com/sporkrb/spork) with Rails, you +need to [restart your spork server](https://github.com/sporkrb/spork/issues/201) +whenever certain Rails environment files change, so why not put this in your +Rakefile... + + desc "run spork (via rerun)" + task :spork do + sh "rerun --pattern '{Gemfile,Gemfile.lock,spec/spec_helper.rb,.rspec,spec/factories/**,config/environment.rb,config/environments/test.rb,config/initializers/*.rb,lib/**/*.rb}' -- spork" + end + +and start using `rake spork` to launch your spork server? + +(If you're using Guard instead of Rerun, check out +[guard-spork](https://github.com/guard/guard-spork) +for a similar solution.) + +How about regenerating your HTML files after every change to your +[Erector](http://erector.rubyforge.org) widgets? + + rerun -x erector --to-html my_site.rb + +Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your +Procfile processes locally and restart them all when necessary. + + rerun foreman start + +# Options: + +These options can be specified on the command line and/or inside a `.rerun` config file (see below). + +`--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options. + +`--pattern` glob to match inside directory. This uses the Ruby Dir glob style -- see for details. +By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`. +On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`). +Run `rerun --help` to see the actual list. + +`--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append `'/*'` e.g. + `--ignore 'coverage/*'`. + +`--[no-]ignore-dotfiles` By default, on top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot. Setting `--no-ignore-dotfiles` allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs. + +`--signal` (or `-s`) use specified signal(s) (instead of the default `TERM,INT,KILL`) to terminate the previous process. You can use a comma-delimited list if you want to try a signal, wait up to 5 seconds for the process to die, then try again with a different signal, and so on. +This may be useful for forcing the respective process to terminate as quickly as possible. +(`--signal KILL` is the equivalent of `kill -9`) + +`--wait sec` (or `-w`) after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: 2 sec + +`--restart` (or `-r`) expect process to restart itself, using signal HUP by default +(e.g. `-r -s INT` will send a INT and then resume watching for changes) + +`--exit` (or -x) expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the launched process is still running. + +`--clear` (or -c) clear the screen before each run + +`--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded + +`--notify NOTIFIER` use `growl` or `osx` or `notify-send` for notifications (see below) + +`--no-notify` disable notifications + +`--name` set the app name (for display) + +`--force-polling` use polling instead of a native filesystem scan (useful for Vagrant) + +`--quiet` silences most messages + +`--verbose` enables even more messages (unless you also specified `--quiet`, which overrides `--verbose`) + +Also `--version` and `--help`, naturally. + +## Config file + +If the current directory contains a file named `.rerun`, it will be parsed with the same rules as command-line arguments. Newlines are the same as any other whitespace, so you can stack options vertically, like this: + +``` +--quiet +--pattern **/*.{rb,js,scss,sass,html,md} +``` + +Options specified on the command line will override those in the config file. You can negate boolean options with `--no-`, so for example, with the above config file, to re-enable logging, you could say: + +```sh +rerun --no-quiet rackup +``` + +If you're not sure what options are being overwritten, use `--verbose` and rerun will show you the final result of the parsing. + +# Notifications + +If you have `growlnotify` available on the `PATH`, it sends notifications to +growl in addition to the console. + +If you have `terminal-notifier`, it sends notifications to +the OS X notification center in addition to the console. + +If you have `notify-send`, it sends notifications to Freedesktop-compatible +desktops in addition to the console. + +If you have more than one available notification program, Rerun will pick one, or you can choose between them using `--notify growl`, `--notify osx`, `--notify notify-send`, etc. + +If you have a notifier installed but don't want rerun to use it, +set the `--no-notify` option. + +Download [growlnotify here](http://growl.info/downloads.php#generaldownloads) +now that Growl has moved to the App Store. + +Install [terminal-notifier](https://github.com/julienXX/terminal-notifier) using `gem install terminal-notifier`. (You may have to put it in your system gemset and/or use `sudo` too.) Using Homebrew to install terminal-notifier is not recommended. + +On Debian/Ubuntu, `notify-send` is availble in the `libnotify-bin` package. On other GNU/Linux systems, it might be in a package with a different name. + +# On-The-Fly Commands + +While the app is (re)running, you can make things happen by pressing keys: + +* **r** -- restart (as if a file had changed) +* **f** -- force restart (stop and start) +* **c** -- clear the screen +* **x** or **q** -- exit (just like control-C) +* **p** -- pause/unpause filesystem watching + +If you're backgrounding or using Pry or a debugger, you might not want these +keys to be trapped, so use the `--background` option. + +# Signals + +The current algorithm for killing the process is: + +* send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option) +* if that doesn't work after 2 seconds, send SIGINT (aka control-C) +* if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9) + +This seems like the most gentle and unixy way of doing things, but it does +mean that if your program ignores SIGTERM, it takes an extra 2 to 4 seconds to +restart. + +If you want to use your own series of signals, use the `--signal` option. If you want to change the delay before attempting the next signal, use the `--wait` option. + +# Vagrant and VirtualBox + +If running inside a shared directory using Vagrant and VirtualBox, you must pass the `--force-polling` option. You may also have to pass some extra `--ignore` options too; otherwise each scan can take 10 or more seconds on directories with a large number of files or subdirectories underneath it. + +# Troubleshooting + +## zsh ## + +If you are using `zsh` as your shell, and you are specifying your `--pattern` as `**/*.rb`, you may face this error +``` +Errno::EACCES: Permission denied - +``` +This is because `**/*.rb` gets expanded into the command by `zsh` instead of passing it through to rerun. The solution is to simply quote ('' or "") the pattern. +i.e +``` +rerun -p **/*.rb rake test +``` +becomes +``` +rerun -p "**/*.rb" rake test +``` + +# To Do: + +## Must have for v1.0 +* Make sure to pass through quoted options correctly to target process [bug] +* Optionally do "bundle install" before and "bundle exec" during launch + +## Nice to have +* ".rerun" file in $HOME +* If the last element of the command is a `.ru` file and there's no other command then use `rackup` +* Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru` +* Specify (or deduce) port to listen for to determine success of a web server launch +* see also [todo.md](todo.md) + +## Wacky Ideas + +* On OS X: + * use a C library using growl's developer API + * Use growl's AppleScript or SDK instead of relying on growlnotify + * "Failed" icon for notifications + +# Other projects that do similar things + +* Guard: +* Restartomatic: +* Shotgun: +* Rack::Reloader middleware: +* The Sinatra FAQ has a discussion at +* Kicker: +* Watchr: +* Autotest: + +# Why would I use this instead of Shotgun? + +Shotgun does a "fork" after the web framework has loaded but before +your application is loaded. It then loads your app, processes a +single request in the child process, then exits the child process. + +Rerun launches the whole app, then when it's time to restart, uses +"kill" to shut it down and starts the whole thing up again from +scratch. + +So rerun takes somewhat longer than Shotgun to restart the app, but +does it much less frequently. And once it's running it behaves more +normally and consistently with your production app. + +Also, Shotgun reloads the app on every request, even if it doesn't +need to. This is fine if you're loading a single file, but if your web +pages all load other files (CSS, JS, media) then that adds up quickly. +(I can only assume that the developers of shotgun are using caching or a +front web server so this isn't a pain point for them.) + +And hey, does Shotgun reload your Worker processes if you're using Foreman and +a Procfile? I'm pretty sure it doesn't. + +YMMV! + +# Why would I use this instead of Rack::Reloader? + +Rack::Reloader is certifiably beautiful code, and is a very elegant use +of Rack's middleware architecture. But because it relies on the +LOADED_FEATURES variable, it only reloads .rb files that were 'require'd, +not 'load'ed. That leaves out (non-Erector) template files, and also, +at least the way I was doing it, sub-actions (see +[this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a# +)). + +Rack::Reloader also doesn't reload configuration changes or redo other +things that happen during app startup. Rerun takes the attitude that if +you want to restart an app, you should just restart the whole app. You know? + +# Why would I use this instead of Guard? + +Guard is very powerful but requires some up-front configuration. +Rerun is meant as a no-frills command-line alternative requiring no knowledge +of Ruby nor config file syntax. + +# Why did you write this? + +I've been using [Sinatra](http://sinatrarb.com) and loving it. In order +to simplify their system, the Rat Pack removed auto-reloading from +Sinatra proper. I approve of this: a web application framework should be +focused on serving requests, not on munging Ruby ObjectSpace for +dev-time convenience. But I still wanted automatic reloading during +development. Shotgun wasn't working for me (see above) so I spliced +Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun +-- with a heavy amount of refactoring and rewriting. In late 2012 I +migrated the backend to the Listen gem, which was extracted from Guard, +so it should be more reliable and performant on multiple platforms. + +# Credits + +Rerun: [Alex Chaffee](http://alexchaffee.com), , + +Based upon and/or inspired by: + +* Shotgun: +* Rspactor: + * (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ ) +* FileSystemWatcher: + +## Patches by: + +* David Billskog +* Jens B +* Andrés Botero +* Dreamcat4 +* +* Barry Sia +* Paul Rangel +* James Edward Gray II +* Raul E Rangel and Antonio Terceiro +* Mike Pastore +* Andy Duncan +* Brent Van Minnen +* Matthew O'Riordan +* Antonio Terceiro +* +* + +# Version History + +* v0.14.0 4 January 2023 + * Ruby 3.2 compatibility fix: .exists? no longer exists + +* v0.13.1 9 December 2020 + * --no-ignore-dotfiles option + +* v0.13.0 26 January 2018 + * bugfix: pause/unpause works again (thanks Barry!) + * `.rerun` config file + +* v0.12.0 23 January 2018 + * smarter `--signal` option, allowing you to specify a series of signals to try in order; also `--wait` to change how long between tries + * `--force-polling` option (thanks ajduncan) + * `f` key to force stop and start (thanks mwpastore) + * add `.c` and `.h` files to default ignore list + * support for Windows + * use `Kernel.spawn` instead of `fork` + * use `wdm` gem for Windows Directory Monitor + * support for notifications on GNU/Linux using [notify-send](http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send) (thanks terceiro) + * fix `Gem::LoadError - terminal-notifier is not part of the bundle` [bug](https://github.com/alexch/rerun/issues/108) (thanks mattheworiordan) + +* 0.11.0 7 October 2015 + * better 'changed' message + * `--notify osx` option + * `--restart` option (with bugfix by Mike Pastore) + * use Listen 3 gem + * add `.feature` files to default watchlist (thanks @jmuheim) + +* v0.10.0 4 May 2014 + * add '.coffee,.slim,.md' to default pattern (thanks @xylinq) + * --ignore option + +* v0.9.0 6 March 2014 + * --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!) + * --name option + * press 'p' to pause/unpause filesystem watching (Barry is the man!) + * works with Listen 2 (note: needs 2.3 or higher) + * cooldown works, thanks to patches to underlying Listen gem + * ignore all dotfiles, and add actual list of ignored dirs and files + +* v0.8.2 + * bugfix, forcing Rerun to use Listen v1.0.3 while we work out the troubles we're having with Listen 1.3 and 2.1 + +* v0.8.1 + * bugfix release (#30 and #34) + +* v0.8.0 + * --background option (thanks FND!) to disable the keyboard listener + * --signal option (thanks FND!) + * --no-growl option + * --dir supports multiple directories (thanks Barry!) + +* v0.7.1 + * bugfix: make rails icon work again + +* v0.7.0 + * uses Listen gem (which uses rb-fsevent for lightweight filesystem snooping) + +# License + +Open Source MIT License. See "LICENSE" file. diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/Rakefile new file mode 100644 index 0000000000..6ca278d733 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/Rakefile @@ -0,0 +1,82 @@ +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rspec/core/rake_task' + +task :default => [:spec] +task :test => :spec + +desc "Run all specs" +RSpec::Core::RakeTask.new('spec') do |t| + ENV['ENV'] = "test" + t.pattern = 'spec/**/*_spec.rb' + t.rspec_opts = ['--color'] +end + +$rubyforge_project = 'pivotalrb' + +$spec = + begin + require 'rubygems/specification' + data = File.read('rerun.gemspec') + spec = nil + #Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join + spec = eval data + spec + end + +def package(ext='') + "pkg/#{$spec.name}-#{$spec.version}" + ext +end + +desc 'Exit if git is dirty' +task :check_git do + state = `git status 2> /dev/null | tail -n1` + clean = (state =~ /working (directory|tree) clean/) + unless clean + warn "can't do that on an unclean git dir" + exit 1 + end +end + +desc 'Build packages' +task :package => %w[.gem .tar.gz].map { |e| package(e) } + +desc 'Build and install as local gem' +task :install => package('.gem') do + sh "gem install #{package('.gem')}" +end + +directory 'pkg/' +CLOBBER.include('pkg') + +file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f| + sh "gem build #{$spec.name}.gemspec" + mv File.basename(f.name), f.name +end + +file package('.tar.gz') => %w[pkg/] + $spec.files do |f| + cmd = <<-SH + git archive \ + --prefix=#{$spec.name}-#{$spec.version}/ \ + --format=tar \ + HEAD | gzip > #{f.name} + SH + sh cmd.gsub(/ +/, ' ') +end + +desc 'Publish gem and tarball to rubyforge' +task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t| + puts "Releasing #{$spec.version}" + sh "gem push #{package('.gem')}" + puts "Tagging and pushing" + sh "git tag v#{$spec.version}" + sh "git push && git push --tags" +end + +desc 'download github issues and pull requests' +task 'github' do + %w(issues pulls).each do |type| + sh "curl -o #{type}.json https://api.github.com/repos/alexch/rerun/#{type}" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/bin/rerun b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/bin/rerun new file mode 100755 index 0000000000..bd5905963f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/bin/rerun @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require 'rubygems' +libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib" +$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) + +require 'rerun' +require 'optparse' + +options = Rerun::Options.parse config_file: ".rerun" + +if options and options[:verbose] + puts "\nrerun options:\n\t#{options}" +end + +exit if options.nil? or options[:cmd].nil? or options[:cmd].empty? + +Rerun::Runner.keep_running(options[:cmd], options) diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_grn_sml.png b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_grn_sml.png new file mode 100644 index 0000000000..b335baeabf Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_grn_sml.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_red_sml.png b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_red_sml.png new file mode 100644 index 0000000000..b8441f182e Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/icons/rails_red_sml.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/goo.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/goo.rb new file mode 100644 index 0000000000..b7c4e0a55d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/goo.rb @@ -0,0 +1,3 @@ +goooo +goooo +goooo diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun.rb new file mode 100644 index 0000000000..31eecfef47 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun.rb @@ -0,0 +1,15 @@ +here = File.expand_path(File.dirname(__FILE__)) +$: << here unless $:.include?(here) + +require "listen" # pull in the Listen gem +require "rerun/options" +require "rerun/system" +require "rerun/notification" +require "rerun/runner" +require "rerun/watcher" +require "rerun/glob" + +module Rerun + +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/glob.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/glob.rb new file mode 100644 index 0000000000..c15f6b57ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/glob.rb @@ -0,0 +1,85 @@ +# based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string- + +# todo: release as separate gem +# +module Rerun + class Glob + NO_LEADING_DOT = '(?=[^\.])' # todo + START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash + END_OF_STRING = '\z' + + def initialize glob_string + @glob_string = glob_string + end + + def to_regexp_string + chars = @glob_string.split('') + + chars = smoosh(chars) + + curlies = 0 + escaping = false + string = chars.map do |char| + if escaping + escaping = false + char + else + case char + when '**' + "([^/]+/)*" + when '*' + ".*" + when "?" + "." + when "." + "\\." + + when "{" + curlies += 1 + "(" + when "}" + if curlies > 0 + curlies -= 1 + ")" + else + char + end + when "," + if curlies > 0 + "|" + else + char + end + when "\\" + escaping = true + "\\" + + else + char + + end + end + end.join + START_OF_FILENAME + string + END_OF_STRING + end + + def to_regexp + Regexp.new(to_regexp_string) + end + + def smoosh chars + out = [] + until chars.empty? + char = chars.shift + if char == "*" and chars.first == "*" + chars.shift + chars.shift if chars.first == "/" + out.push("**") + else + out.push(char) + end + end + out + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/notification.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/notification.rb new file mode 100644 index 0000000000..b5b3c5b943 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/notification.rb @@ -0,0 +1,82 @@ +# todo: unit tests + +module Rerun + class Notification + include System + + attr_reader :title, :body, :options + + def initialize(title, body, options = Options::DEFAULTS.dup) + @title = title + @body = body + @options = options + end + + def command + # todo: strategy or subclass + + s = nil + + if options[:notify] == true or options[:notify] == "growl" + if (cmd = command_named("growlnotify")) + # todo: check version of growlnotify and warn if it's too old + icon_str = ("--image \"#{icon}\"" if icon) + s = "#{cmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" + end + end + + if s.nil? and options[:notify] == true or options[:notify] == "osx" + if (cmd = command_named("terminal-notifier")) + icon_str = ("-appIcon \"#{icon}\"" if icon) + s = "#{cmd} -title \"#{app_name}\" -message \"#{body}\" \"#{app_name} #{title}\" #{icon_str}" + end + end + + if s.nil? and options[:notify] == true or options[:notify] == "notify-send" + if (cmd = command_named('notify-send')) + icon_str = "--icon #{icon}" if icon + s = "#{cmd} -t 500 --hint=int:transient:1 #{icon_str} \"#{app_name}: #{title}\" \"#{body}\"" + end + end + + s + end + + def command_named(name) + which_command = windows? ? 'where.exe %{cmd}' : 'which %{cmd} 2> /dev/null' + # TODO: remove 'INFO' error message + path = `#{which_command % {cmd: name}}`.chomp + path.empty? ? nil : path + end + + def send(background = true) + return unless command + with_clean_env do + `#{command}#{" &" if background}` + end + end + + def app_name + options[:name] + end + + def icon + "#{icon_dir}/rails_red_sml.png" if rails? + end + + def icon_dir + here = File.expand_path(File.dirname(__FILE__)) + File.expand_path("#{here}/../../icons") + end + + def with_clean_env + if defined?(Bundler) + Bundler.with_clean_env do + yield + end + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/options.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/options.rb new file mode 100644 index 0000000000..b1a26c1f4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/options.rb @@ -0,0 +1,146 @@ +require 'optparse' +require 'pathname' +require 'rerun/watcher' +require 'rerun/system' + +libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}" + +$spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec")) + +module Rerun + class Options + + extend Rerun::System + + # If you change the default pattern, please update the README.md file -- the list appears twice therein, which at the time of this comment are lines 17 and 119 + DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h}" + DEFAULT_DIRS = ["."] + + DEFAULTS = { + :background => false, + :dir => DEFAULT_DIRS, + :force_polling => false, + :ignore => [], + :ignore_dotfiles => true, + :name => Pathname.getwd.basename.to_s.capitalize, + :notify => true, + :pattern => DEFAULT_PATTERN, + :quiet => false, + :signal => (windows? ? "TERM,KILL" : "TERM,INT,KILL"), + :verbose => false, + :wait => 2, + } + + def self.parse args: ARGV, config_file: nil + + default_options = DEFAULTS.dup + options = { + ignore: [] + } + + if config_file && File.exist?(config_file) + require 'shellwords' + config_args = File.read(config_file).shellsplit + args = config_args + args + end + + option_parser = OptionParser.new("", 24, ' ') do |o| + o.banner = "Usage: rerun [options] [--] cmd" + + o.separator "" + o.separator "Launches an app, and restarts it when the filesystem changes." + o.separator "See http://github.com/alexch/rerun for more info." + o.separator "Version: #{$spec.version}" + o.separator "" + o.separator "Options:" + + o.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir| + elements = dir.split(",") + options[:dir] = (options[:dir] || []) + elements + end + + # todo: rename to "--watch" + o.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern| + options[:pattern] = pattern + end + + o.on("-i pattern", "--ignore pattern", "file glob(s) to ignore. Can be set many times. To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*' . Globs do not match dotfiles by default.") do |pattern| + options[:ignore] += [pattern] + end + + o.on("--[no-]ignore-dotfiles", "by default, file globs do not match files that begin with a dot. Setting --no-ignore-dotfiles allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.") do |value| + options[:ignore_dotfiles] = value + end + + o.on("-s signal", "--signal signal", "terminate process using this signal. To try several signals in series, use a comma-delimited list. Default: \"#{DEFAULTS[:signal]}\"") do |signal| + options[:signal] = signal + end + + o.on("-w sec", "--wait sec", "after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: #{DEFAULTS[:wait]} sec") + + o.on("-r", "--restart", "expect process to restart itself, so just send a signal and continue watching. Sends the HUP signal unless overridden using --signal") do |signal| + options[:restart] = true + default_options[:signal] = "HUP" + end + + o.on("-x", "--exit", "expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the process is running.") do |value| + options[:exit] = value + end + + o.on("-c", "--clear", "clear screen before each run") do |value| + options[:clear] = value + end + + o.on("-b", "--background", "disable on-the-fly keypress commands, allowing the process to be backgrounded") do |value| + options[:background] = value + end + + o.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name| + options[:name] = name + end + + o.on("--[no-]force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do |value| + options[:force_polling] = value + end + + o.on("--no-growl", "don't use growl [OBSOLETE]") do + options[:growl] = false + $stderr.puts "--no-growl is obsolete; use --no-notify instead" + return + end + + o.on("--[no-]notify [notifier]", "send messages through a desktop notification application. Supports growl (requires growlnotify), osx (requires terminal-notifier gem), and notify-send on GNU/Linux (notify-send must be installed)") do |notifier| + notifier = true if notifier.nil? + options[:notify] = notifier + end + + o.on("-q", "--[no-]quiet", "don't output any logs") do |value| + options[:quiet] = value + end + + o.on("--[no-]verbose", "log extra stuff like PIDs (unless you also specified `--quiet`") do |value| + options[:verbose] = value + end + + o.on_tail("-h", "--help", "--usage", "show this message and immediately exit") do + puts o + return + end + + o.on_tail("--version", "show version and immediately exit") do + puts $spec.version + return + end + + end + + puts option_parser if args.empty? + option_parser.parse! args + options = default_options.merge(options) + options[:cmd] = args.join(" ").strip # todo: better arg word handling + + options + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/runner.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/runner.rb new file mode 100644 index 0000000000..581d00c520 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/runner.rb @@ -0,0 +1,374 @@ +require 'timeout' +require 'io/wait' + +module Rerun + class Runner + + # The watcher instance that wait for changes + attr_reader :watcher + + def self.keep_running(cmd, options) + runner = new(cmd, options) + runner.start + runner.join + # apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-( + sleep 10000 while true # :-( + end + + include System + include ::Timeout + + def initialize(run_command, options = {}) + @run_command, @options = run_command, options + @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/ + @options[:directory] ||= options.delete(:dir) || '.' + @options[:ignore] ||= [] + end + + def start_keypress_thread + return if @options[:background] + + @keypress_thread = Thread.new do + while true + if c = key_pressed + case c.downcase + when 'c' + say "Clearing screen" + clear_screen + when 'r' + say "Restarting" + restart + when 'f' + say "Stopping and starting" + restart(false) + when 'p' + toggle_pause + when 'x', 'q' + die + break # the break will stop this thread, in case the 'die' doesn't + else + puts "\n#{c.inspect} pressed inside rerun" + puts [["c", "clear screen"], + ["r", "restart"], + ["f", "forced restart (stop and start)"], + ["p", "toggle pause"], + ["x or q", "stop and exit"] + ].map {|key, description| " #{key} -- #{description}"}.join("\n") + puts + end + end + sleep 1 # todo: use select instead of polling somehow? + end + end + @keypress_thread.run + end + + def stop_keypress_thread + @keypress_thread.kill if @keypress_thread + @keypress_thread = nil + end + + def restart(with_signal = true) + @restarting = true + if @options[:restart] && with_signal + restart_with_signal(@options[:signal]) + else + stop + start + end + @restarting = false + end + + def toggle_pause + unless @pausing + say "Pausing. Press 'p' again to resume." + @watcher.pause + @pausing = true + else + say "Resuming." + @watcher.unpause + @pausing = false + end + end + + def unpause + @watcher.unpause + end + + def dir + @options[:directory] + end + + def pattern + @options[:pattern] + end + + def clear? + @options[:clear] + end + + def quiet? + @options[:quiet] + end + + def verbose? + @options[:verbose] + end + + def exit? + @options[:exit] + end + + def app_name + @options[:name] + end + + def restart_with_signal(restart_signal) + if @pid && (@pid != 0) + notify "restarting", "We will be with you shortly." + send_signal(restart_signal) + end + end + + def force_polling + @options[:force_polling] + end + + def start + if @already_running + taglines = [ + "Here we go again!", + "Keep on trucking.", + "Once more unto the breach, dear friends, once more!", + "The road goes ever on and on, down from the door where it began.", + ] + notify "restarted", taglines[rand(taglines.size)] + else + taglines = [ + "To infinity... and beyond!", + "Charge!", + ] + notify "launched", taglines[rand(taglines.size)] + @already_running = true + end + + clear_screen if clear? + start_keypress_thread unless @keypress_thread + + begin + @pid = run @run_command + say "Rerun (#{$PID}) running #{app_name} (#{@pid})" + rescue => e + puts "#{e.class}: #{e.message}" + exit + end + + status_thread = Process.detach(@pid) # so if the child exits, it dies + + Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process + die + end + + Signal.trap("TERM") do # TERM is the polite way of terminating a process + die + end + + begin + sleep 2 + rescue Interrupt => e + # in case someone hits control-C immediately ("oops!") + die + end + + if exit? + status = status_thread.value + if status.success? + notify "succeeded", "" + else + notify "failed", "Exit status #{status.exitstatus}" + end + else + if !running? + notify "Launch Failed", "See console for error output" + @already_running = false + end + end + + unless @watcher + watcher = Watcher.new(@options) do |changes| + message = change_message(changes) + say "Change detected: #{message}" + restart unless @restarting + end + watcher.start + @watcher = watcher + ignore = @options[:ignore] + say "Watching #{dir.join(', ')} for #{pattern}" + + (ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") + + (watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter") + end + end + + def run command + Kernel.spawn command + end + + def change_message(changes) + message = [:modified, :added, :removed].map do |change| + count = changes[change] ? changes[change].size : 0 + if count > 0 + "#{count} #{change}" + end + end.compact.join(", ") + + changed_files = changes.values.flatten + if changed_files.count > 0 + message += ": " + message += changes.values.flatten[0..3].map {|path| path.split('/').last}.join(', ') + if changed_files.count > 3 + message += ", ..." + end + end + message + end + + def die + #stop_keypress_thread # don't do this since we're probably *in* the keypress thread + stop # stop the child process if it exists + exit 0 # todo: status code param + end + + def join + @watcher.join + end + + def running? + send_signal(0) + end + + # Send the signal to process @pid and wait for it to die. + # @returns true if the process dies + # @returns false if either sending the signal fails or the process fails to die + def signal_and_wait(signal) + + signal_sent = if windows? + force_kill = (signal == 'KILL') + system("taskkill /T #{'/F' if force_kill} /PID #{@pid}") + else + send_signal(signal) + end + + if signal_sent + # the signal was successfully sent, so wait for the process to die + begin + timeout(@options[:wait]) do + Process.wait(@pid) + end + process_status = $? + say "Process ended: #{process_status}" if verbose? + true + rescue Timeout::Error + false + end + else + false + end + end + + # Send the signal to process @pid. + # @returns true if the signal is sent + # @returns false if sending the signal fails + # If sending the signal fails, the exception will be swallowed + # (and logged if verbose is true) and this method will return false. + # + def send_signal(signal) + say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose? + Process.kill(signal, @pid) + true + rescue => e + say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose? + false + end + + # todo: test escalation + def stop + if @pid && (@pid != 0) + notify "stopping", "All good things must come to an end." unless @restarting + @options[:signal].split(',').each do |signal| + success = signal_and_wait(signal) + return true if success + end + end + rescue + false + end + + def git_head_changed? + old_git_head = @git_head + read_git_head + @git_head and old_git_head and @git_head != old_git_head + end + + def read_git_head + git_head_file = File.join(dir, '.git', 'HEAD') + @git_head = File.exist?(git_head_file) && File.read(git_head_file) + end + + def notify(title, body, background = true) + Notification.new(title, body, @options).send(background) if @options[:notify] + puts + say "#{app_name} #{title}" + end + + def say msg + puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet? + end + + def stty(args) + system "stty #{args}" + end + + # non-blocking stdin reader. + # returns a 1-char string if a key was pressed; otherwise nil + # + def key_pressed + return one_char if windows? + begin + # this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin + + # 'raw' means turn raw input on + + # restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h + # looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */ + # which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */ + # so this sets it back on again since all we care about is raw input, not raw output + stty "raw opost" + one_char + ensure + stty "-raw" # turn raw input off + end + + # note: according to 'man tty' the proper way restore the settings is + # tty_state=`stty -g` + # ensure + # system 'stty "#{tty_state}' + # end + # but this way seems fine and less confusing + end + + def clear_screen + # see http://ascii-table.com/ansi-escape-sequences-vt-100.php + $stdout.print "\033[H\033[2J" + end + + private + def one_char + c = nil + if $stdin.ready? + c = $stdin.getc + end + c.chr if c + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/system.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/system.rb new file mode 100644 index 0000000000..2bebedb1ac --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/system.rb @@ -0,0 +1,22 @@ +module Rerun + module System + + def mac? + RUBY_PLATFORM =~ /darwin/i + end + + def windows? + RUBY_PLATFORM =~ /(mswin|mingw32)/i + end + + def linux? + RUBY_PLATFORM =~ /linux/i + end + + def rails? + rails_sig_file = File.expand_path(".")+"/config/boot.rb" + File.exist? rails_sig_file + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/watcher.rb b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/watcher.rb new file mode 100644 index 0000000000..f6eb83fe58 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/lib/rerun/watcher.rb @@ -0,0 +1,134 @@ +require 'listen' + +Thread.abort_on_exception = true + +# This class will watch a directory and alert you of +# new files, modified files, deleted files. +# +# Now uses the Listen gem, but spawns its own thread on top. +# We should probably be accessing the Listen thread directly. +# +# Author: Alex Chaffee +# +module Rerun + class Watcher + InvalidDirectoryError = Class.new(RuntimeError) + + #def self.default_ignore + # Listen::Silencer.new(Listen::Listener.new).send :_default_ignore_patterns + #end + + attr_reader :directory, :pattern, :priority, :ignore_dotfiles + + # Create a file system watcher. Start it by calling #start. + # + # @param options[:directory] the directory to watch (default ".") + # @param options[:pattern] the glob pattern to search under the watched directory (default "**/*") + # @param options[:priority] the priority of the watcher thread (default 0) + # + def initialize(options = {}, &client_callback) + @client_callback = client_callback + + options = { + :directory => ".", + :pattern => "**/*", + :priority => 0, + :ignore_dotfiles => true, + }.merge(options) + + @pattern = options[:pattern] + @directories = options[:directory] + @directories = sanitize_dirs(@directories) + @priority = options[:priority] + @force_polling = options[:force_polling] + @ignore = [options[:ignore]].flatten.compact + @ignore_dotfiles = options[:ignore_dotfiles] + @thread = nil + end + + def sanitize_dirs(dirs) + dirs = [*dirs] + dirs.map do |d| + d.chomp!("/") + unless FileTest.exist?(d) && FileTest.readable?(d) && FileTest.directory?(d) + raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable" + end + File.expand_path(d) + end + end + + def start + if @thread then + raise RuntimeError, "already started" + end + + @thread = Thread.new do + @listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1, force_polling: @force_polling) do |modified, added, removed| + count = modified.size + added.size + removed.size + if count > 0 + @client_callback.call(:modified => modified, :added => added, :removed => removed) + end + end + @listener.start + end + + @thread.priority = @priority + + sleep 0.1 until @listener + + at_exit { stop } # try really hard to clean up after ourselves + end + + def watching + Rerun::Glob.new(@pattern).to_regexp + end + + def ignoring + patterns = [] + if ignore_dotfiles + patterns << /^\.[^.]/ # at beginning of string, a real dot followed by any other character + end + patterns + @ignore.map { |x| Rerun::Glob.new(x).to_regexp } + end + + # kill the file watcher thread + def stop + @thread.wakeup rescue ThreadError + begin + @listener.stop + rescue Exception => e + puts "#{e.class}: #{e.message} stopping listener" + end + @thread.kill rescue ThreadError + end + + # wait for the file watcher to finish + def join + @thread.join if @thread + rescue Interrupt + # don't care + end + + def pause + @listener.pause if @listener + end + + def unpause + @listener.start if @listener + end + + def running? + @listener && @listener.processing? + end + + def adapter + @listener && + (backend = @listener.instance_variable_get(:@backend)) && + backend.instance_variable_get(:@adapter) + end + + def adapter_name + adapter && adapter.class.name.split('::').last + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/rerun.gemspec b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/rerun.gemspec new file mode 100644 index 0000000000..6362585a90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rerun-0.14.0/rerun.gemspec @@ -0,0 +1,34 @@ +$spec = Gem::Specification.new do |s| + s.specification_version = 2 if s.respond_to? :specification_version= + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + + s.name = 'rerun' + s.version = '0.14.0' + + s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." + s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc." + + s.authors = ["Alex Chaffee"] + s.email = "alexch@gmail.com" + + s.files = %w[ + README.md + LICENSE + Rakefile + rerun.gemspec + bin/rerun + icons/rails_grn_sml.png + icons/rails_red_sml.png] + + Dir['lib/**/*.rb'] + s.executables = ['rerun'] + s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/} + + s.extra_rdoc_files = %w[README.md] + + s.add_runtime_dependency 'listen', '~> 3.0' + + s.homepage = "http://github.com/alexch/rerun/" + s.require_paths = %w[lib] + + s.license = 'MIT' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/LICENSE.md new file mode 100644 index 0000000000..722b21e9c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/LICENSE.md @@ -0,0 +1,27 @@ +The MIT License (MIT) +===================== + +Copyright © 2009 Chad Humphries, David Chelimsky +Copyright © 2006 David Chelimsky, The RSpec Development Team +Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-3.10.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/README.md new file mode 100644 index 0000000000..98a711446b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/README.md @@ -0,0 +1,43 @@ +# RSpec + +Behaviour Driven Development for Ruby + +# Description + +rspec is a meta-gem, which depends on the +[rspec-core](https://github.com/rspec/rspec-core), +[rspec-expectations](https://github.com/rspec/rspec-expectations) +and [rspec-mocks](https://github.com/rspec/rspec-mocks) gems. Each of these +can be installed separately and loaded in isolation using `require`. Among +other benefits, this allows you to use rspec-expectations, for example, in +Test::Unit::TestCase if you happen to prefer that style. + +Conversely, if you like RSpec's approach to declaring example groups and +examples (`describe` and `it`) but prefer Test::Unit assertions and +[mocha](https://github.com/freerange/mocha), [rr](https://github.com/rr/rr) +or [flexmock](https://github.com/jimweirich/flexmock) for mocking, you'll be +able to do that without having to install or load the components of RSpec that +you're not using. + +## Documentation + +See http://rspec.info/documentation/ for links to documentation for all gems. + +## Install + + gem install rspec + +## Setup + + rspec --init + +## Contribute + +* [http://github.com/rspec/rspec-dev](http://github.com/rspec/rspec-dev) + +## Also see + +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec.rb new file mode 100644 index 0000000000..36149e09e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec.rb @@ -0,0 +1,3 @@ +require 'rspec/core' +require 'rspec/version' + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec/version.rb new file mode 100644 index 0000000000..0dffcb5a53 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.10.0/lib/rspec/version.rb @@ -0,0 +1,5 @@ +module RSpec # :nodoc: + module Version # :nodoc: + STRING = '3.10.0' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/LICENSE.md new file mode 100644 index 0000000000..722b21e9c4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/LICENSE.md @@ -0,0 +1,27 @@ +The MIT License (MIT) +===================== + +Copyright © 2009 Chad Humphries, David Chelimsky +Copyright © 2006 David Chelimsky, The RSpec Development Team +Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-3.12.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/README.md new file mode 100644 index 0000000000..4af390da3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/README.md @@ -0,0 +1,47 @@ +# RSpec + +Behaviour Driven Development for Ruby + +**The rspec metagem repository has been renamed to rspec-metagem, please update +any rspec/rspec Github references to rspec/rspec-metagem, this is in preparation +for a new mono-repo approach to RSpec dev to unify issue tracking and PR management** + +## Description + +rspec is a meta-gem, which depends on the +[rspec-core](https://github.com/rspec/rspec-core), +[rspec-expectations](https://github.com/rspec/rspec-expectations) +and [rspec-mocks](https://github.com/rspec/rspec-mocks) gems. Each of these +can be installed separately and loaded in isolation using `require`. Among +other benefits, this allows you to use rspec-expectations, for example, in +Test::Unit::TestCase if you happen to prefer that style. + +Conversely, if you like RSpec's approach to declaring example groups and +examples (`describe` and `it`) but prefer Test::Unit assertions and +[mocha](https://github.com/freerange/mocha), [rr](https://github.com/rr/rr) +or [flexmock](https://github.com/jimweirich/flexmock) for mocking, you'll be +able to do that without having to install or load the components of RSpec that +you're not using. + +## Documentation + +See http://rspec.info/documentation/ for links to documentation for all gems. + +## Install + + gem install rspec + +## Setup + + rspec --init + +## Contribute + +* [http://github.com/rspec/rspec-dev](http://github.com/rspec/rspec-dev) + +## Also see + +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec.rb new file mode 100644 index 0000000000..36149e09e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec.rb @@ -0,0 +1,3 @@ +require 'rspec/core' +require 'rspec/version' + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec/version.rb new file mode 100644 index 0000000000..bf85e6c8b8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-3.12.0/lib/rspec/version.rb @@ -0,0 +1,5 @@ +module RSpec # :nodoc: + module Version # :nodoc: + STRING = '3.12.0' + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.yardopts new file mode 100644 index 0000000000..0e4432664e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/.yardopts @@ -0,0 +1,8 @@ +--exclude features +--no-private +--markup markdown +--default-return void +- +Filtering.md +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/Changelog.md new file mode 100644 index 0000000000..e648586bc2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/Changelog.md @@ -0,0 +1,2327 @@ +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.10.0...v3.10.1) + + +Bug fixes: + +* RSpec warning output was missing deprecations from Ruby, these are now included. + (Jon Rowe, #2811) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.3...v3.10.0) + +Enhancements: + +* Memoize `RSpec::Core::Formatters::ExceptionPresenter#exception_lines` to improve performance + with slow exception messages. (Maxime Lapointe, #2743) +* Add configuration for an error exit code (to disambiguate errored builds from failed builds + by exit status). (Dana Sherson, #2749) + +# 3.9.3 / 2020-09-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.2...v3.9.3) + +Bug Fixes: + +* Declare `ruby2_keywords` on `method_missing` for other gems. (Jon Rowe, #2731) +* Ensure custom error codes are returned from bisect runs. (Jon Rowe, #2732) +* Ensure `RSpec::Core::Configuration` predicate config methods return booleans. + (Marc-André Lafortune, #2736) +* Prevent `rspec --bisect` from generating zombie processes while executing + bisect runs. (Benoit Tigeot, Jon Rowe, #2739) +* Predicates for pending examples, (in `RSpec::Core::Example`, `#pending?`, `#skipped?` and + `#pending_fixed?`) now return boolean values rather than truthy values. + (Marc-André Lafortune, #2756, #2758) +* Exceptions which have a message which cannot be cast to a string will no longer + cause a crash. (Jon Rowe, #2761) + +### 3.9.2 / 2020-05-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Emit a warning when `around` hook is used with `:context` scope + (Phil Pirozhkov, #2687) +* Prevent invalid implementations of `Exception#cause` from being treated as a + valid cause (and causing strange errors) in `RSpec::Core::Formatters::ExceptionPresenter`. + (Jon Rowe, #2703) +* Correctly detect patterns when `rspec_opts` is an array in `RSpec::Core::RakeTask`. + (Marc-André Lafortune, #2704) +* Make `RSpec.clear_examples` reset example counts for example groups. This fixes + an issue with re-running specs not matching ids. (Agis Anastasopoulos, #2723) + +### 3.9.1 / 2019-12-28 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Prevent bisect command from blocking when number of specs exceeds file + descriptor limit on OSX or Linux. (Benoit Tigeot, #2669) +* Prevent warnings being issued on Ruby 2.7.0. (Jon Rowe, #2680) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.2...v3.9.0) + +Enhancements: + +* Improve the handling of errors during loading support files, if a file + errors before loading specs, RSpec will now skip loading the specs. + (David Rodríguez, #2568) +* Add support for --example-matches to run examples by regular expression. + (Sam Joseph, Matt Rider, @okothkongo1, #2586) +* Add `did_you_mean` suggestions for file names encountering a `LoadError` + outside of examples. (@obromios, #2601) +* Add a minimalist quick fix style formatter, only outputs failures as + `file:line:message`. (Romain Tartière, #2614) +* Convert string number values to integer when used for `RSpec::Configuration#fail_fast` + (Viktor Fonic, #2634) +* Issue warning when invalid values are used for `RSpec::Configuration#fail_fast` + (Viktor Fonic, #2634) +* Add support for running the Rake task in a clean environment. + (Jon Rowe, #2632) +* Indent messages by there example group / example in the documentation formatter. + (Samuel Williams, #2649) + +### 3.8.2 / 2019-06-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Fix `config.define_derived_metadata` so that cascades are not triggered + until metadata has been assigned to the example or example group + (Myron Marston, #2635). + +### 3.8.1 / 2019-06-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Handle RSpec description(s) with japanese chars in CP932 encoded files. + (Benoit Tigeot, #2575) +* When defining `let` methods that overwrite an existing method, prevent + a warning being issued by removing the old definition. (Jon Rowe, #2593) +* Prevent warning on Ruby 2.6.0-rc1 (Keiji Yoshimi, #2582) +* Fix `config.define_derived_metadata` so that it supports cascades. + (Myron Marston, #2630). + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.7.1...v3.8.0) + +Enhancements: + +* Improve shell escaping used by `RSpec::Core::RakeTask` and `--bisect` so + that it works on `Pathname` objects. (Andrew Vit, #2479) +* Nicely format errors encountered while loading files specified + by `--require` option. (Myron Marston, #2504) +* Significantly improve the performance of `--bisect` on platforms that + support forking by replacing the shell-based runner with one that uses + forking so that RSpec and the application environment can be booted only + once, instead of once per spec run. (Myron Marston, #2511) +* Provide a configuration API to pick which bisect runner is used for + `--bisect`. Pick a runner via `config.bisect_runner = :shell` or + `config.bisect_runner = :fork` in a file loaded by a `--require` + option passed at the command line or set in `.rspec`. (Myron Marston, #2511) +* Support the [XDG Base Directory + Specification](https://specifications.freedesktop.org/basedir-spec/latest/) + for the global options file. `~/.rspec` is still supported when no + options file is found in `$XDG_CONFIG_HOME/rspec/options` (Magnus Bergmark, #2538) +* Extract `RSpec.world.prepare_example_filtering` that sets up the + example filtering for custom RSpec runners. (Oleg Pudeyev, #2552) + +Bug Fixes: + +* Prevent an `ArgumentError` when truncating backtraces with two identical + backtraces. (Systho, #2515, Benoit Tigeot, #2539) + +### 3.7.1 / 2018-01-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.7.0...v3.7.1) + +Bug Fixes: + +* Work around duplicate config hook regression introduced + by Ruby 2.5's lazy proc allocation. (Myron Marston, #2497) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Add `-n` alias for `--next-failure`. (Ian Ker-Seymer, #2434) +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #2425, #2427, #2437) +* Do not run `:context` hooks for example groups that have been skipped. + (Devon Estes, #2442) +* Add `errors_outside_of_examples_count` to the JSON formatter. + (Takeshi Arabiki, #2448) + +Bug Fixes: + +* Improve compatibility with frozen string literal flag. (#2425, Pat Allan) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0.beta2...v3.6.0) + +Enhancements: + +* Add seed information to JSON formatter output. (#2388, Mitsutaka Mimura) +* Include example id in the JSON formatter output. (#2369, Xavier Shay) +* Respect changes to `config.output_stream` after formatters have been + setup. (#2401, #2419, Ilya Lavrov) + +Bug Fixes: + +* Delay formatter loading until the last minute to allow accessing the reporter + without triggering formatter setup. (Jon Rowe, #2243) +* Ensure context hook failures running before an example can access the + reporter. (Jon Jensen, #2387) +* Multiple fixes to allow using the runner multiple times within the same + process: `RSpec.clear_examples` resets the formatter and no longer clears + shared examples, and streams can be used across multiple runs rather than + being closed after the first. (#2368, Xavier Shay) +* Prevent unexpected `example_group_finished` notifications causing an error. + (#2396, VTJamie) +* Fix bugs where `config.when_first_matching_example_defined` hooks would fire + multiple times in some cases. (Yuji Nakayama, #2400) +* Default `last_run_status` to "unknown" when the `status` field in the + persistence file contains an unrecognized value. (#2360, matrinox) +* Prevent `let` from defining an `initialize` method. (#2414, Jon Rowe) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0.beta1...v3.6.0.beta2) + +Enhancements: + +* Include count of errors occurring outside examples in default summaries. + (#2351, Jon Rowe) +* Warn when including shared example groups recursively. (#2356, Jon Rowe) +* Improve failure snippet syntax highlighting with CodeRay to highlight + RSpec "keywords" like `expect`. (#2358, Myron Marston) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.4...v3.6.0.beta1) + +Enhancements: + +* Warn when duplicate shared examples definitions are loaded due to being + defined in files matching the spec pattern (e.g. `_spec.rb`) (#2278, Devon Estes) +* Improve metadata filtering so that it can match against any object + that implements `===` instead of treating regular expressions as + special. (Myron Marston, #2294) +* Improve `rspec -v` so that it prints out the versions of each part of + RSpec to prevent confusion. (Myron Marston, #2304) +* Add `config.fail_if_no_examples` option which causes RSpec to fail if + no examples are found. (Ewa Czechowska, #2302) +* Nicely format errors encountered while loading spec files. + (Myron Marston, #2323) +* Improve the API for enabling and disabling color output (Josh + Justice, #2321): + * Automatically enable color if the output is a TTY, since color is + nearly always desirable if the output can handle it. + * Introduce new CLI flag to force color on (`--force-color`), even + if the output is not a TTY. `--no-color` continues to work as well. + * Introduce `config.color_mode` for configuring the color from Ruby. + `:automatic` is the default and will produce color if the output is + a TTY. `:on` forces it on and `:off` forces it off. + +### 3.5.4 / 2016-09-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.3...v3.5.4) + +Bug Fixes: + +* Remove accumulated `ExampleGroup` constants when reseting RSpec, + preventing a memory leak. (TravisSpangle, #2328) + +### 3.5.3 / 2016-09-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.2...v3.5.3) + +Bug Fixes: + +* When applying shared group metadata to a host group, overwrite + conflicting keys if the value in the host group was inherited from + a parent group instead of being specified at that level. + (Myron Marston, #2307) +* Handle errors in `:suite` hooks and provide the same nicely formatted + output as errors that happen in examples. (Myron Marston, #2316) +* Set the exit status to non-zero when an error occurs in an + `after(:context)` hook. (Myron Marston, #2320) + +### 3.5.2 / 2016-07-28 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.1...v3.5.2) + +Bug Fixes: + +* Wait to report `example_finished` until the example's `execution_result` + has been completely filled in. (Myron Marston, #2291) +* Make sure example block is still available when using `duplicate_with` + to clone examples. (bootstraponline, #2298) +* Don't include the default `--pattern` in the Rake task when + `rspec_opts` specifies its own. (Jon Rowe, #2305) + +### 3.5.1 / 2016-07-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0...v3.5.1) + +Bug Fixes: + +* Ensure that config hooks that are added to existing example groups are + added only once. (Eugene Kenny, #2280) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Include any `SPEC_OPTS` in reproduction command printed at the end of + a bisect run. (Simon Coffey, #2274) + +Bug Fixes: + +* Handle `--bisect` in `SPEC_OPTS` environment variable correctly so as + to avoid infinite recursion. (Simon Coffey, #2271) + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: + +* Filter out bundler stackframes from backtraces by default, since + Bundler 1.12 now includes its own frames in stack traces produced + by using `bundle exec`. (Myron Marston, #2240) +* HTML Formatter uses exception presenter to get failure message + for consistency with other formatters. (@mrageh, #2222) +* Load spec files in the order of the directories or files passed + at the command line, making it easy to make some specs run before + others in a one-off manner. For example, `rspec spec/unit + spec/acceptance --order defined` will run unit specs before acceptance + specs. (Myron Marston, #2253) +* Add new `config.include_context` API for configuring global or + filtered inclusion of shared contexts in example groups. + (Myron Marston, #2256) +* Add new `config.shared_context_metadata_behavior = :apply_to_host_groups` + option, which causes shared context metadata to be inherited by the + metadata hash of all host groups and examples instead of configuring + implicit auto-inclusion based on the passed metadata. (Myron Marston, #2256) + +Bug Fixes: + +* Fix `--bisect` so it works on large spec suites that were previously triggering + "Argument list too long errors" due to all the spec locations being passed as + CLI args. (Matt Jones, #2223). +* Fix deprecated `:example_group`-based filtering so that it properly + applies to matching example groups. (Myron Marston, #2234) +* Fix `NoMethodError` caused by Java backtraces on JRuby. (Michele Piccirillo, #2244) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Add new `config.filter_run_when_matching` API, intended to replace + the combination of `config.filter_run` and + `config.run_all_when_everything_filtered` (Myron Marston, #2206) + +Bug Fixes: + +* Use the encoded string logic for source extraction. (Jon Rowe, #2183) +* Fix rounding issue in duration formatting helper. (Fabersky, Jon Rowe, #2208) +* Fix failure snippet extraction so that `def-end` snippets + ending with `end`-only line can be extracted properly. + (Yuji Nakayama, #2215) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Remove unneeded `:execution_result` example group metadata, saving a + bit of memory. (Myron Marston, #2172) +* Apply hooks registered with `config` to previously defined groups. + (Myron Marston, #2189) +* `RSpec::Core::Configuration#reporter` is now public API under SemVer. + (Jon Rowe, #2193) +* Add new `config.when_first_matching_example_defined` hook. (Myron + Marston, #2175) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.4...v3.5.0.beta1) + +Enhancements: + +* Add `RSpec::Core::ExampleGroup.currently_executing_a_context_hook?`, + primarily for use by rspec-rails. (Sam Phippen, #2131) + +Bug Fixes: + +* Ensure `MultipleExceptionError` does not contain a recursive reference + to itself. (Sam Phippen, #2133) + +### 3.4.4 / 2016-03-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.3...v3.4.4) + +Bug Fixes: + +* Fix `RSpec::Core::RakeTask` so that it works with Rake 11. + (Travis Grathwell, #2197) + +### 3.4.3 / 2016-02-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.2...v3.4.3) + +Bug Fixes: + +* Prevent a `TypeError` from occurring when running via the rake task when + Ruby crashes. (Patrik Wenger, #2161) +* Only consider example and group declaration lines from a specific file + when applying line number filtering, instead of considering all + declaration lines from all spec files. (Myron Marston, #2170) +* Fix failure snippet extraction so that snippets that contain `do-end` style + block and end with `end`-only line can be extracted properly. + (Yuji Nakayama, #2173) +* Prevent infinite recursion when an exception is caused by itself. + (Jon Rowe, #2128) + +### 3.4.2 / 2016-01-26 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.1...v3.4.2) + +Bug Fixes: + +* Fix `rspec --profile` when an example calls `abort` or `exit`. + (Bradley Schaefer, #2144) +* Fix `--drb` so that when no DRb server is running, it prevents + the DRb connection error from being listed as the cause of all + expectation failures. (Myron Marston, #2156) +* Fix syntax highlighter so that it works when the `coderay` gem is + installed as a rubygem but not already available on your load path + (as happens when you use bundler). (Myron Marston, #2159) + +### 3.4.1 / 2015-11-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix backtrace formatter to handle backtraces that are `nil`. + (Myron Marston, #2118) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.2...v3.4.0) + +Enhancements: + +* Combine multiple `--pattern` arguments making them equivalent to + `--pattern=1,2,...,n`. (Jon Rowe, #2002) +* Improve `inspect` and `to_s` output for `RSpec::Core::Example` + objects, replacing Ruby's excessively verbose output. (Gavin Miller, #1922) +* Add `silence_filter_announcements` configuration option. + (David Raffensperger, #2007) +* Add optional `example_finished` notification to the reporter protocol for + when you don't care about the example outcome. (Jon Rowe, #2013) +* Switch `--bisect` to a recursion-based bisection algorithm rather than + a permutation-based one. This better handles cases where an example + depends upon multiple other examples instead of just one and minimizes + the number of runs necessary to determine that an example set cannot be + minimized further. (Simon Coffey, #1997) +* Allow simple filters (e.g. `:symbol` key only) to be triggered by truthey + values. (Tim Mertens, #2035) +* Remove unneeded warning about need for `ansicon` on Windows when using + RSpec's `--color` option. (Ashley Engelund, #2038) +* Add option to configure RSpec to raise errors when issuing warnings. + (Jon Rowe, #2052) +* Append the root `cause` of a failure or error to the printed failure + output when a `cause` is available. (Adam Magan) +* Stop rescuing `NoMemoryError`, `SignalExcepetion`, `Interrupt` and + `SystemExit`. It is dangerous to interfere with these. (Myron Marston, #2063) +* Add `config.project_source_dirs` setting which RSpec uses to determine + if a backtrace line comes from your project source or from some + external library. It defaults to `spec`, `lib` and `app` but can be + configured differently. (Myron Marston, #2088) +* Improve failure line detection so that it looks for the failure line + in any project source directory instead of just in the spec file. + In addition, if no backtrace lines can be found from a project source + file, we fall back to displaying the source of the first backtrace + line. This should virtually eliminate the "Unable to find matching + line from backtrace" messages. (Myron Marston, #2088) +* Add support for `:extra_failure_lines` example metadata that will + be appended to the failure output. (bootstraponline, #2092). +* Add `RSpec::Core::Example#duplicate_with` to produce new examples + with cloned metadata. (bootstraponline, #2098) +* Add `RSpec::Core::Configuration#on_example_group_definition` to register + hooks to be invoked when example groups are created. (bootstraponline, #2094) +* Add `add_example` and `remove_example` to `RSpec::Core::ExampleGroup` to + allow manipulating an example groups examples. (bootstraponline, #2095) +* Display multiline failure source lines in failure output when Ripper is + available (MRI >= 1.9.2, and JRuby >= 1.7.5 && < 9.0.0.0.rc1). + (Yuji Nakayama, #2083) +* Add `max_displayed_failure_line_count` configuration option + (defaults to 10). (Yuji Nakayama, #2083) +* Enhance `fail_fast` option so it can take a number (e.g. `--fail-fast=3`) + to force the run to abort after the specified number of failures. + (Jack Scotti, #2065) +* Syntax highlight the failure snippets in text formatters when `color` + is enabled and the `coderay` gem is installed on a POSIX system. + (Myron Marston, #2109) + +Bug Fixes: + +* Lock `example_status_persistence_file` when reading from and writing + to it to prevent race conditions when multiple processes try to use + it. (Ben Woosley, #2029) +* Fix regression in 3.3 that caused spec file names with square brackets in + them (such as `1[]_spec.rb`) to not be loaded properly. (Myron Marston, #2041) +* Fix output encoding issue caused by ASCII literal on 1.9.3 (Jon Rowe, #2072) +* Fix requires in `rspec/core/rake_task.rb` to avoid double requires + seen by some users. (Myron Marston, #2101) + +### 3.3.2 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.1...v3.3.2) + +Bug Fixes: + +* Fix formatters to handle exceptions for which `backtrace` returns `nil`. + (Myron Marston, #2023) +* Fix duplicate formatter detection so that it allows subclasses of formatters + to be added. (Sebastián Tello, #2019) + +### 3.3.1 / 2015-06-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Correctly run `before(:suite)` (and friends) in the context of an example + group instance, thus making the expected RSpec environment available. + (Jon Rowe, #1986) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.3...v3.3.0) + +Enhancements: + +* Expose the reporter used to run examples via `RSpec::Core::Example#reporter`. + (Jon Rowe, #1866) +* Make `RSpec::Core::Reporter#message` a public supported API. (Jon Rowe, #1866) +* Allow custom formatter events to be published via + `RSpec::Core::Reporter#publish(event_name, hash_of_attributes)`. (Jon Rowe, #1869) +* Remove dependency on the standard library `Set` and replace with `RSpec::Core::Set`. + (Jon Rowe, #1870) +* Assign a unique id to each example and group so that they can be + uniquely identified, even for shared examples (and similar situations) + where the location isn't unique. (Myron Marston, #1884) +* Use the example id in the rerun command printed for failed examples + when the location is not unique. (Myron Marston, #1884) +* Add `config.example_status_persistence_file_path` option, which is + used to persist the last run status of each example. (Myron Marston, #1888) +* Add `:last_run_status` metadata to each example, which indicates what + happened the last time an example ran. (Myron Marston, #1888) +* Add `--only-failures` CLI option which filters to only the examples + that failed the last time they ran. (Myron Marston, #1888) +* Add `--next-failure` CLI option which allows you to repeatedly focus + on just one of the currently failing examples, then move on to the + next failure, etc. (Myron Marston, #1888) +* Make `--order random` ordering stable, so that when you rerun a + subset with a given seed, the examples will be order consistently + relative to each other. (Myron Marston, #1908) +* Set example group constant earlier so errors when evaluating the context + include the example group name (Myron Marson, #1911) +* Make `let` and `subject` threadsafe. (Josh Cheek, #1858) +* Add version information into the JSON formatter. (Mark Swinson, #1883) +* Add `--bisect` CLI option, which will repeatedly run your suite in + order to isolate the failures to the smallest reproducible case. + (Myron Marston, #1917) +* For `config.include`, `config.extend` and `config.prepend`, apply the + module to previously defined matching example groups. (Eugene Kenny, #1935) +* When invalid options are parsed, notify users where they came from + (e.g. `.rspec` or `~/.rspec` or `ENV['SPEC_OPTS']`) so they can + easily find the source of the problem. (Myron Marston, #1940) +* Add pending message contents to the json formatter output. (Jon Rowe, #1949) +* Add shared group backtrace to the output displayed by the built-in + formatters for pending examples that have been fixed. (Myron Marston, #1946) +* Add support for `:aggregate_failures` metadata. Tag an example or + group with this metadata and it'll use rspec-expectations' + `aggregate_failures` feature to allow multiple failures in an example + and list them all, rather than aborting on the first failure. (Myron + Marston, #1946) +* When no formatter implements #message add a fallback to prevent those + messages being lost. (Jon Rowe, #1980) +* Profiling examples now takes into account time spent in `before(:context)` + hooks. (Denis Laliberté, Jon Rowe, #1971) +* Improve failure output when an example has multiple exceptions, such + as one from an `it` block and one from an `after` block. (Myron Marston, #1985) + +Bug Fixes: + +* Handle invalid UTF-8 strings within exception methods. (Benjamin Fleischer, #1760) +* Fix Rake Task quoting of file names with quotes to work properly on + Windows. (Myron Marston, #1887) +* Fix `RSpec::Core::RakeTask#failure_message` so that it gets printed + when the task failed. (Myron Marston, #1905) +* Make `let` work properly when defined in a shared context that is applied + to an individual example via metadata. (Myron Marston, #1912) +* Ensure `rspec/autorun` respects configuration defaults. (Jon Rowe, #1933) +* Prevent modules overriding example group defined methods when included, + prepended or extended by config defined after an example group. (Eugene Kenny, #1935) +* Fix regression which caused shared examples to be mistakenly run when specs + where filtered to a particular location. (Ben Axnick, #1963) +* Fix time formatting logic so that it displays 70 seconds as "1 minute, + 10 seconds" rather than "1 minute, 1 second". (Paul Brennan, #1984) +* Fix regression where the formatter loader would allow duplicate formatters. + (Jon Rowe, #1990) + +### 3.2.3 / 2015-04-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.2...v3.2.3) + +Bug Fixes: + +* Fix how the DSL methods are defined so that RSpec is compatible with + gems that define methods of the same name on `Kernel` (such as + the `its-it` gem). (Alex Kwiatkowski, Ryan Ong, #1907) +* Fix `before(:context) { skip }` so that it does not wrongly cause the + spec suite to exit with a non-zero status when no examples failed. + (Myron Marston, #1926) + +### 3.2.2 / 2015-03-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.1...v3.2.2) + +Bug Fixes: + +* Fix regression in 3.2.0 that allowed tag-filtered examples to + run even if there was a location filter applied to the spec + file that was intended to limit the file to other examples. + (#1894, Myron Marston) + +### 3.2.1 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Notify start-of-run seed _before_ `start` notification rather than + _after_ so that formatters like Fuubar work properly. (Samuel Esposito, #1882) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.7...v3.2.0) + +Enhancements: + +* Improve the `inspect` output of example groups. (Mike Dalton, #1687) +* When rake task fails, only output the command if `verbose` flag is + set. (Ben Snape, #1704) +* Add `RSpec.clear_examples` as a clear way to reset examples in between + spec runs, whilst retaining user configuration. (Alexey Fedorov, #1706) +* Reduce string allocations when defining and running examples by 70% + and 50% respectively. (Myron Marston, #1738) +* Removed dependency on pathname from stdlib. (Sam Phippen, #1703) +* Improve the message presented when a user hits Ctrl-C. + (Alex Chaffee #1717, #1742) +* Improve shared example group inclusion backtrace displayed + in failed example output so that it works for all methods + of including shared example groups and shows all inclusion + locations. (Myron Marston, #1763) +* Issue seed notification at start (as well as the end) of the reporter + run. (Arlandis Word, #1761) +* Improve the documentation of around hooks. (Jim Kingdon, #1772) +* Support prepending of modules into example groups from config and allow + filtering based on metadata. (Arlandis Word, #1806) +* Emit warnings when `:suite` hooks are registered on an example group + (where it has always been ignored) or are registered with metadata + (which has always been ignored). (Myron Marston, #1805) +* Provide a friendly error message when users call RSpec example group + APIs (e.g. `context`, `describe`, `it`, `let`, `before`, etc) from + within an example where those APIs are unavailable. (Myron Marston, #1819) +* Provide a friendly error message when users call RSpec example + APIs (e.g. `expect`, `double`, `stub_const`, etc) from + within an example group where those APIs are unavailable. + (Myron Marston, #1819) +* Add new `RSpec::Core::Sandbox.sandboxed { }` API that facilitates + testing RSpec with RSpec, allowing you to define example groups + and example from within an example without affecting the global + `RSpec.world` state. (Tyler Ball, 1808) +* Apply line-number filters only to the files they are scoped to, + allowing you to mix filtered and unfiltered files. (Myron Marston, #1839) +* When dumping pending examples, include the failure details so that you + don't have to un-pend the example to see it. (Myron Marston, #1844) +* Make `-I` option support multiple values when separated by + `File::PATH_SEPARATOR`, such as `rspec -I foo:bar`. This matches + the behavior of Ruby's `-I` option. (Fumiaki Matsushima, #1855). +* Treat each example as having a singleton example group for the + purposes of applying metadata-based features that normally apply + to example groups to individually tagged examples. For example, + `RSpec.shared_context "Uses redis", :uses_redis` will now apply + to individual examples tagged with `:uses_redis`, as will + `config.include RedisHelpers, :uses_redis`, and + `config.before(:context, :uses_redis) { }`, etc. (Myron Marston, #1749) + +Bug Fixes: + +* When assigning generated example descriptions, surface errors + raised by `matcher.description` in the example description. + (Myron Marston, #1771) +* Don't consider expectations from `after` hooks when generating + example descriptions. (Myron Marston, #1771) +* Don't apply metadata-filtered config hooks to examples in groups + with matching metadata when those examples override the parent + metadata value to not match. (Myron Marston, #1796) +* Fix `config.expect_with :minitest` so that `skip` uses RSpec's + implementation rather than Minitest's. (Jonathan Rochkind, #1822) +* Fix `NameError` caused when duplicate example group aliases are defined and + the DSL is not globally exposed. (Aaron Kromer, #1825) +* When a shared example defined in an external file fails, use the host + example group (from a loaded spec file) for the re-run command to + ensure the command will actually work. (Myron Marston, #1835) +* Fix location filtering to work properly for examples defined in + a nested example group within a shared example group defined in + an external file. (Bradley Schaefer, Xavier Shay, Myron Marston, #1837) +* When a pending example fails (as expected) due to a mock expectation, + set `RSpec::Core::Example::ExecutionResult#pending_exception` -- + previously it was not being set but should have been. (Myron Marston, #1844) +* Fix rake task to work when `rspec-core` is installed in a directory + containing a space. (Guido Günther, #1845) +* Fix regression in 3.1 that caused `describe Regexp` to raise errors. + (Durran Jordan, #1853) +* Fix regression in 3.x that caused the profile information to be printed + after the summary. (Max Lincoln, #1857) +* Apply `--seed` before loading `--require` files so that required files + can access the provided seed. (Myron Marston, #1745) +* Handle `RSpec::Core::Formatters::DeprecationFormatter::FileStream` being + reopened with an IO stream, which sometimes happens with spring. + (Kevin Mook, #1757) + +### 3.1.7 / 2014-10-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.6...v3.1.7) + +Bug Fixes: + +* Fix `Metadata.relative_path` so that for a current directory of + `/foo/bar`, `/foo/bar_1` is not wrongly converted to `._1`. + (Akos Vandra, #1730) +* Prevent constant lookup mistakenly finding `RSpec::ExampleGroups` generated + constants on 1.9.2 by appending a trailing `_` to the generated names. + (Jon Rowe, #1737) +* Fix bug in `:pending` metadata. If it got set in any way besides passing + it as part of the metadata literal passed to `it` (such as by using + `define_derived_metadata`), it did not have the desired effect, + instead marking the example as `:passed`. (Myron Marston, #1739) + +### 3.1.6 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.5...v3.1.6) + +Bug Fixes: + +* Fix regression in rake task pattern handling, that prevented patterns + that were relative from the current directory rather than from `spec` + from working properly. (Myron Marston, #1734) +* Prevent rake task from generating duplicate load path entries. + (Myron Marston, #1735) + +### 3.1.5 / 2014-09-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.4...v3.1.5) + +Bug Fixes: + +* Fix issue with the rake task incorrectly escaping strings on Windows. + (Jon Rowe #1718) +* Support absolute path patterns. While this wasn't officially supported + previously, setting `rake_task.pattern` to an absolute path pattern in + RSpec 3.0 and before worked since it delegated to `FileList` internally + (but now just forwards the pattern on to the `rspec` command). + (Myron Marston, #1726) + +### 3.1.4 / 2014-09-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.3...v3.1.4) + +Bug Fixes: + +* Fix implicit `subject` when using `describe false` or `describe nil` + so that it returns the provided primitive rather than the string + representation. (Myron Marston, #1710) +* Fix backtrace filtering to allow code in subdirectories of your + current working directory (such as vendor/bundle/...) to be filtered + from backtraces. (Myron Marston, #1708) + +### 3.1.3 / 2014-09-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.2...v3.1.3) + +Bug Fixes: + +* Fix yet another regression in rake task pattern handling, to allow + `task.pattern = FileList["..."]` to work. That was never intended + to be supported but accidentally worked in 3.0 and earlier. + (Myron Marston, #1701) +* Fix pattern handling so that files are normalized to absolute paths + before subtracting the `--exclude-pattern` matched files from the + `--pattern` matched files so that it still works even if the patterns + are in slightly different forms (e.g. one starting with `./`). + (Christian Nelson, #1698) + +### 3.1.2 / 2014-09-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix another regression in rake task pattern handling, so that patterns + that start with `./` still work. (Christian Nelson, #1696) + +### 3.1.1 / 2014-09-05 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix a regression in rake task pattern handling, so that `rake_task.pattern = array` + works again. While we never intended to support array values (or even knew that worked!), + the implementation from 3.0 and earlier used `FileList` internally, which allows arrays. + The fix restores the old behavior. (Myron Marston, #1694) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Update files generated by `rspec --init` so that warnings are enabled + in commented out section of `spec_helper` rather than `.rspec` so users + have to consciously opt-in to the setting. (Andrew Hooker, #1572) +* Update `spec_helper` generated by `rspec --init` so that it sets the new + rspec-expectations `include_chain_clauses_in_custom_matcher_descriptions` + config option (which will be on by default in RSpec 4) and also sets the + rspec-mocks `verify_partial_doubles` option (which will also default + to on in RSpec 4). (Myron Marston, #1647) +* Provide an `inspect` output for example procsy objects (used in around + hooks) that doesn't make them look like procs. (Jon Rowe, #1620) +* Remove a few unneeded `require` statements from + `rspec/core/rake_task.rb`, making it even more lighterweight. + (Myron Marston, #1640) +* Allow rspec-core to be used when neither rspec-mocks or + rspec-expectations are installed, without requiring any + user configuration. (Sam Phippen, Myron Marston, #1615) +* Don't filter out gems from backtraces by default. (The RSpec + gems will still be filtered). User feedback has indicated + that including gems in default backtraces will be useful. + (Myron Marston, #1641) +* Add new `config.filter_gems_from_backtrace "rack", "rake"` API + to easily filter the named gems from backtraces. (Myron Marston, #1682) +* Fix default backtrace filters so that the RSpec binary is + excluded when installing RSpec as a bundler `:git` dependency. + (Myron Marston, #1648) +* Simplify command generated by the rake task so that it no longer + includes unnecessary `-S`. (Myron Marston, #1559) +* Add `--exclude-pattern` CLI option, `config.exclude_pattern =` config + option and `task.exclude_pattern =` rake task config option. Matching + files will be excluded. (John Gesimondo, Myron Marston, #1651, #1671) +* When an around hook fails to execute the example, mark it as + pending (rather than passing) so the user is made aware of the + fact that the example did not actually run. (Myron Marston, #1660) +* Remove dependency on `FileUtils` from the standard library so that users do + not get false positives where their code relies on it but they are not + requiring it. (Sam Phippen, #1565) + +Bug Fixes: + +* Fix rake task `t.pattern =` option so that it does not run all specs + when it matches no files, by passing along a `--pattern` option to + the `rspec` command, rather than resolving the file list and passing + along the files individually. (Evgeny Zislis, #1653) +* Fix rake task default pattern so that it follows symlinks properly. + (Myron Marston, #1672) +* Fix default pattern used with `rspec` command so that it follows + symlinks properly. (Myron Marston, #1672) +* Change how we assign constant names to example group classes so that + it avoids a problem with `describe "Core"`. (Daniela Wellisz, #1679) +* Handle rendering exceptions that have a different encoding than that + of their original source file. (Jon Rowe, #1681) +* Allow access to message_lines without colour for failed examples even + when they're part of a shared example group. (tomykaira, #1689) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix processing order of CLI options so that if `config.files_to_run` + is accessed from a file loaded by `--require`, `--pattern` is still + applied. (Myron Marston, #1652) +* Fix `config.pattern=` so that it still takes affect even if + `config.files_to_run` has already been accessed. (Myron Marston, #1652) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Properly convert both parts of a description into strings before + concatenation. (@nicklink483, #1636) +* Exclude the working directory when figuring out folders to ignore. + (Jon Rowe, Myron Marston, #1616) +* Allow `::RSpec::Core::Notifications::FailedExampleNotification#message_lines` + to be accessed without a colouriser. (@tomykaira, #1637) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix regression in CLI option handling that prevented `--tag slow` + passed at the command line from overriding `--tag ~slow` in `.rspec`. + (Colin Jones, #1602) +* Fix metadata `:example_group` deprecation warning so that it gets + issued at the call site of the configuration that specified it as + a filter rather than later when an example group is defined. + (Myron Marston, #1562) +* Make the line that is printed when a shared example group fails indicating + where the concrete example group is white, separating it from the stack trace + that is produced for the failure. (Sam Phippen, Jon Rowe, #1606) + +### 3.0.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Fix a couple ruby warnings caused by rspec-core when loaded. + (Prem Sichanugrist, #1584) +* Example groups named `Config` will no longer cause a Ruby warning to be + issued. (Jimmy Cuadra, #1580) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.rc1...v3.0.0) + +Bug Fixes: + +* Fix `BaseTextFormatter` so that it does not re-close a closed output + stream. (Myron Marston) +* Fix regression in metadata that caused the metadata hash of a top-level + example group to have a `:parent_example_group` key even though it has + no parent example group. (Myron Marston) + +Enhancements: + +* Alter the default `spec_helper.rb` to no longer recommend + `config.full_backtrace = true` see #1536 for discussion. (Jon Rowe) + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Change `described_class` so that in a nested group like `describe + MyClass`, it returns `MyClass` rather than the outer group's described + class. (Myron Marston) +* Refactor filter manager so that it no longer subclasses Hash and has a + tighter, more domain-specific interface. (Sergey Pchelincev) +* Remove legacy colours definitions from `BaseTextFormatter`. (Jon Rowe) +* Remove console color definitions from `BaseTextFormatter`. (Jon Rowe) +* Restructure example group metadata so that the computed keys are + exposed directly off of the metadata hash rather than being on + a nested `:example_group` subhash. In addition, the parent example + group metadata is now available as `[:parent_example_group]` rather + than `[:example_group][:example_group]`. Deprecated access via the + old key structure is still provided. (Myron Marston) +* Remove `:describes` metadata key. It duplicates `:described_class` + for no good reason. Deprecated access via `:describes` is still + provided. (Myron Marston) +* Rename `:example_group_block` metadata key to `:block`. + (Myron Marston) +* Remove deprecated `RSpec::Core::Example#options`. (Myron Marston) +* Move `BaseTextFormatter#colorize_summary` to `SummaryNotification#colorize_with` + (Jon Rowe). +* `describe some_hash` treated `some_hash` as metadata in RSpec 2.x but + will treat it as the described object in RSpec 3.0. Metadata must + always come after the description args. (Myron Marston) +* Remove deprecated `display_name` alias of `ExampleGroup.description`. + (Myron Marston) +* Remove deprecated `describes` alias of `ExampleGroup.described_class`. + (Myron Marston) +* Remove deprecated `RSpec::Core::ExampleGroup.alias_it_behaves_like_to`. + Use `RSpec::Core::Configuration#alias_it_behaves_like_to` instead. + (Myron Marston) +* Remove deprecated `RSpec::Core::ExampleGroup.alias_example_to`. + Use `RSpec::Core::Configuration#alias_example_to` instead. + (Myron Marston) +* Removed `focused` example alias and change example/group aliases + `fit`, `focus`, `fcontext` and `fdescribe` to no longer include + `:focused => true` metadata. They only contain `:focus => true` + metadata now. This means that you will need to filter them with + `filter_run :focus`, not `filter_run :focused`. (Myron Marston) +* Remove `--line-number` filtering. It's semantically dubious since it's + a global filter (potentially applied to multiple files) but there's no + meaningful connection between the same line number in multiple files. + Instead use the `rspec path/to/spec.rb:23:46` form, which is terser + and makes more sense as it is scoped to a file. (Myron Marston) +* Remove `--default_path` as an alias for `--default-path`. (Jon Rowe) +* Remove deprecated `share_examples_for`. There's still + `shared_examples` and `shared_examples_for`. (Myron Marston) +* Rename `RSpec::Core::Configuration#warnings` to + `RSpec::Core::Configuration#warnings?` since it's a boolean flag. + (Myron Marston) +* RSpec's global state is no longer reset after a spec run. This gives + more flexibility to alternate runners to decide when and if they + want the state reset. Alternate runners are now responsible for + calling this (or doing a similar reset) if they are going to run + the spec suite multiple times in the same process. (Sam Phippen) +* Merge `RSpec::Core::CommandLine` (never formally declared public) + into `RSpec::Core::Runner`. (Myron Marston) +* Remove `color_enabled` as an alias of `color`. (Jon Rowe) +* Remove `backtrace_cleaner` as an alias of `backtrace_formatter`. (Jon Rowe) +* Remove `filename_pattern` as an alias of `pattern`. (Jon Rowe) +* Extract support for legacy formatters to `rspec-legacy_formatters`. (Jon Rowe) +* `RSpec::Configuration#formatters` now returns a dup to prevent mutation. (Jon Rowe) +* Replace `stdlib` as an available expectation framework with `test_unit` and + `minitest`. (Aaron Kromer) +* Remove backtrace formatting helpers from `BaseTextFormatter`. (Jon Rowe) +* Extract profiler support to `ProfileFormatter` and `ProfileNotification`. + Formatters should implement `dump_profile` if they wish to respond to `--profile`. + (Jon Rowe) +* Extract remaining formatter state to reporter and notifications. Introduce + `ExamplesNotification` to share information about examples that was previously + held in `BaseFormatter`. (Jon Rowe) + +Enhancements: + +* Add `config.default_formatter` attribute, which can be used to set a + formatter which will only be used if no other formatter is set + (e.g. via `--formatter`). (Myron Marston) +* Support legacy colour definitions in `LegacyFormatterAdaptor`. (Jon Rowe) +* Migrate `execution_result` (exposed by metadata) from a hash to a + first-class object with appropriate attributes. `status` is now + stored and returned as a symbol rather than a string. It retains + deprecated hash behavior for backwards compatibility. (Myron Marston) +* Provide console code helper for formatters. (Jon Rowe) +* Use raw ruby hashes for the metadata hashes rather than a subclass of + a hash. Computed metadata entries are now computed in advance rather + than being done lazily on first access. (Myron Marston) +* Add `:block` metadata entry to the example metadata, bringing + parity with `:block` in the example group metadata. (Myron Marston) +* Add `fspecify` and `fexample` as aliases of `specify` and `example` + with `:focus => true` metadata for parity with `fit`. (Myron Marston) +* Add legacy support for `colorize_summary`. (Jon Rowe) +* Restructure runner so it can be more easily customized in a subclass + for an alternate runner. (Ben Hoskings) +* Document `RSpec::Core::ConfigurationOptions` as an officially + supported public API. (Myron Marston) +* Add `--deprecation-out` CLI option which directs deprecation warnings + to the named file. (Myron Marston) +* Minitest 5 compatability for `expect_with :stdlib` (now available as + `expect_with :minitest`). (Xavier Shay) +* Reporter now notifies formatters of the load time of RSpec and your + specs via `StartNotification` and `SummaryNotification`. (Jon Rowe) +* Add `disable_monkey_patching!` config option that disables all monkey + patching from whatever pieces of RSpec you use. (Alexey Fedorov) +* Add `Pathname` support for setting all output streams. (Aaron Kromer) +* Add `config.define_derived_metadata`, which can be used to apply + additional metadata to all groups or examples that match a given + filter. (Myron Marston) +* Provide formatted and colorized backtraces via `FailedExampleNotification` + and send `PendingExampleFixedNotifications` when the error is due to a + passing spec you expect to fail. (Jon Rowe) +* Add `dump_profile` to formatter API to allow formatters to implement + support for `--profile`. (Jon Rowe) +* Allow colourising text via `ConsoleCodes` with RSpec 'states' + (e.g. `:success`, `:failure`) rather than direct colour codes. (Jon Rowe) +* Expose `fully_formatted` methods off the formatter notification objects + that make it easy for a custom formatter to produce formatted output + like rspec-core's. (Myron Marston) + +Bug Fixes: + +* Fix `spec_helper.rb` file generated by `rspec --init` so that the + recommended settings correctly use the documentation formatter + when running one file. (Myron Marston) +* Fix ordering problem where descriptions were generated after + tearing down mocks, which resulted in unexpected exceptions. + (Bradley Schaefer, Aaron Kromer, Andrey Savchenko) +* Allow a symbol to be used as an implicit subject (e.g. `describe + :foo`). (Myron Marston) +* Prevent creating an isolated context (i.e. using `RSpec.describe`) when + already inside a context. There is no reason to do this, and it could + potentially cause unexpected bugs. (Xavier Shay) +* Fix shared example group scoping so that when two shared example + groups share the same name at different levels of nested contexts, + the one in the nearest context is used. (Myron Marston) +* Fix `--warnings` option so that it enables warnings immediately so + that it applies to files loaded by `--require`. (Myron Marston) +* Issue a warning when you set `config.deprecation_stream` too late for + it to take effect because the reporter has already been setup. (Myron Marston) +* Add the full `RSpec::Core::Example` interface to the argument yielded + to `around` hooks. (Myron Marston) +* Line number always takes precendence when running specs with filters. + (Xavier Shay) +* Ensure :if and :unless metadata filters are treated as a special case + and are always in-effect. (Bradley Schaefer) +* Ensure the currently running installation of RSpec is used when + the rake task shells out to `rspec`, even if a newer version is also + installed. (Postmodern) +* Using a legacy formatter as default no longer causes an infinite loop. + (Xavier Shay) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Make `mock_with` option more strict. Strings are no longer supported + (e.g. `mock_with "mocha"`) -- use a symbol instead. Also, unrecognized + values will now result in an error rather than falling back to the + null mocking adapter. If you want to use the null mocking adapter, + use `mock_with :nothing` (as has been documented for a long time). + (Myron Marston) +* Remove support for overriding RSpec's built-in `:if` and `:unless` + filters. (Ashish Dixit) +* Custom formatters are now required to call + `RSpec::Core::Formatters.register(formatter_class, *notifications)` + where `notifications` is the list of events the formatter wishes to + be notified about. Notifications are handled by methods matching the + names on formatters. This allows us to add or remove notifications + without breaking existing formatters. (Jon Rowe) +* Change arguments passed to formatters. Rather than passing multiple + arguments (which limits are ability to add additional arguments as + doing so would break existing formatters), we now pass a notification + value object that exposes the same data via attributes. This will + allow us to add new bits of data to a notification event without + breaking existing formatters. (Jon Rowe) +* Remove support for deprecated `:alias` option for + `RSpec.configuration.add_setting`. (Myron Marston) +* Remove support for deprecated `RSpec.configuration.requires = [...]`. + (Myron Marston) +* Remove support for deprecated `--formatter` CLI option. (Myron Marston) +* Remove support for deprecated `--configure` CLI option. (Myron Marston) +* Remove support for deprecated `RSpec::Core::RakeTask#spec_opts=`. + (Myron Marston) +* An example group level `pending` block or `:pending` metadata now executes + the example and cause a failure if it passes, otherwise it will be pending if + it fails. The old "never run" behaviour is still used for `xexample`, `xit`, + and `xspecify`, or via a new `skip` method or `:skip` metadata option. + (Xavier Shay) +* After calling `pending` inside an example, the remainder of the example will + now be run. If it passes a failure is raised, otherwise the example is marked + pending. The old "never run" behaviour is provided a by a new `skip` method. + (Xavier Shay) +* Pending blocks inside an example have been removed as a feature with no + direct replacement. Use `skip` or `pending` without a block. (Xavier Shay) +* Pending statement is no longer allowed in `before(:all)` hooks. Use `skip` + instead. (Xavier Shay) +* Remove `show_failures_in_pending_blocks` configuration option. (Xavier Shay) +* Remove support for specifying the documentation formatter using + 's', 'n', 'spec' or 'nested'. (Jon Rowe) + +Enhancements: + +* Add example run time to JSON formatter output. (Karthik Kastury) +* Add more suggested settings to the files generated by + `rspec --init`. (Myron Marston) +* Add `config.alias_example_group_to`, which can be used to define a + new method that defines an example group with the provided metadata. + (Michi Huber) +* Add `xdescribe` and `xcontext` as shortcuts to skip an example group. + (Myron Marston) +* Add `fdescribe` and `fcontext` as shortcuts to focus an example group. + (Myron Marston) +* Don't autorun specs via `#at_exit` by default. `require 'rspec/autorun'` + is only needed when running specs via `ruby`, as it always has been. + Running specs via `rake` or `rspec` are both unaffected. (Ben Hoskings) +* Add `expose_dsl_globally` config option, defaulting to true. When disabled + it will remove the monkey patches rspec-core adds to `main` and `Module` + (e.g. `describe`, `shared_examples_for`, etc). (Jon Rowe) +* Expose RSpec DSL entry point methods (`describe`, + `shared_examples_for`, etc) on the `RSpec` constant. Intended for use + when `expose_dsl_globally` is set to `false`. (Jon Rowe) +* For consistency, expose all example group aliases (including + `context`) on the `RSpec` constant. If `expose_dsl_globally` is set to + `true`, also expose them on `main` and `Module`. Historically, only `describe` + was exposed. (Jon Rowe, Michi Huber) +* Add hook scope `:example` as an alias for `:each`, and `:context` as an alias + for `:all`. (John Feminella) + +Bug Fixes: + +* Fix failure (undefined method `path`) in end-of-run summary + when `raise_errors_for_deprecations!` is configured. (Myron Marston) +* Issue error when attempting to use `-i` or `--I` on command line, + too close to `-I` to be considered short hand for `--init`. (Jon Rowe) +* Prevent adding formatters to an output target if the same + formatter has already been added to that output. (Alex Peattie) +* Allow a matcher-generated example description to be used when + the example is pending. (Myron Marston) +* Ensure the configured `failure_exit_code` is used by the rake + task when there is a failure. (Jon Rowe) +* Restore behaviour whereby system exclusion filters take priority over working + directory (was broken in beta1). (Jon Rowe) +* Prevent RSpec mangling file names that have substrings containing `line_number` + or `default_path`. (Matijs van Zuijlen) +* Fix failure line detection so that it handles relative file paths + (which can happen when running specs through `ruby` using `rspec/autorun`). + (Myron Marston, #1829) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.1...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Remove explicit support for 1.8.6. (Jon Rowe) +* Remove `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. If you need + access to the example (e.g. to get its metadata), use a block arg + instead. (David Chelimsky) +* Remove `TextMateFormatter`, it has been moved to `rspec-tmbundle`. + (Aaron Kromer) +* Remove RCov integration. (Jon Rowe) +* Remove deprecated support for RSpec 1 constructs (Myron Marston): + * The `Spec` and `Rspec` constants (rather than `RSpec`). + * `Spec::Runner.configure` rather than `RSpec.configure`. + * `Rake::SpecTask` rather than `RSpec::Core::RakeTask`. +* Remove deprecated support for `share_as`. (Myron Marston) +* Remove `--debug` option (and corresponding option on + `RSpec::Core::Configuration`). Instead, use `-r` to + load whichever debugger gem you wish to use (e.g. `ruby-debug`, + `debugger`, or `pry`). (Myron Marston) +* Extract Autotest support to a seperate gem. (Jon Rowe) +* Raise an error when a `let` or `subject` declaration is + accessed in a `before(:all)` or `after(:all)` hook. (Myron Marston) +* Extract `its` support to a separate gem. (Peter Alfvin) +* Disallow use of a shared example group from sibling contexts, making them + fully isolated. 2.14 and 2.99 allowed this but printed a deprecation warning. + (Jon Rowe) +* Remove `RSpec::Core::Configuration#output` and + `RSpec::Core::Configuration#out` aliases of + `RSpec::Core::Configuration#output_stream`. (Myron Marston) +* Remove legacy ordering APIs deprecated in 2.99.0.beta1. (Myron + Marston) + +Enhancements: + +* Replace unmaintained syntax gem with coderay gem. (Xavier Shay) +* Times in profile output are now bold instead of `failure_color`. + (Matthew Boedicker) +* Add `--no-fail-fast` command line option. (Gonzalo Rodríguez-Baltanás Díaz) +* Runner now considers the local system ip address when running under Drb. + (Adrian CB) +* JsonFormatter now includes `--profile` information. (Alex / @MasterLambaster) +* Always treat symbols passed as metadata args as hash + keys with true values. RSpec 2 supported this with the + `treat_symbols_as_metadata_keys_with_true_values` but + now this behavior is always enabled. (Myron Marston) +* Add `--dry-run` option, which prints the formatter output + of your suite without running any examples or hooks. + (Thomas Stratmann, Myron Marston) +* Document the configuration options and default values in the `spec_helper.rb` + file that is generated by RSpec. (Parker Selbert) +* Give generated example group classes a friendly name derived + from the docstring, rather than something like "Nested_2". + (Myron Marston) +* Avoid affecting randomization of user code when shuffling + examples so that users can count on their own seeds + working. (Travis Herrick) +* Ordering is no longer a single global property of the test suite. + Each group can pick an ordering using `:order` metadata. (Andy + Lindeman, Sam Phippen, Myron Marston) +* Allow named custom ordering strategies to be registered, which can + then be used on individual example groups. (Andy Lindeman, Sam + Phippen, Myron Marston) + +Deprecations: + +* `treat_symbols_as_metadata_keys_with_true_values` is deprecated and no + longer has an affect now that the behavior it enabled is always + enabled. (Myron Marston) + +### 2.99.2 / 2014-08-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.1...v2.99.2) + +Enhancements: + +* Improve deprecation warning for RSpec 3 change in `describe ` + behavior. (Jon Rowe, #1667) + +### 2.99.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Add missing deprecation warning for when `RSpec::Core::Runner` is used + multiple times in the same process. In 2.x RSpec's global state was + automatically cleared between runs but in 3.0 you need to call `RSpec.reset` + manually in these situations. (Sam Phippen, #1587) +* Prevent deprecation being accidentally issues when doubles used with `be_` + matchers due to automatically generated descriptions. (Jon Rowe, #1573) +* Load `rspec/core` when loading `rspec/core/rake_task` to ensure we can + issue deprecations correctly. (Jon Rowe, #1612) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.rc1...v2.99.0) + +Bug Fixes: + +* Fix `BaseTextFormatter` so that it does not re-close a closed output + stream. (Myron Marston) +* Use `RSpec::Configuration#backtrace_exclusion_patterns` rather than the + deprecated `RSpec::Configuration#backtrace_clean_patterns` when mocking + with rr. (David Dollar) + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.beta2...v2.99.0.rc1) + +Enhancements: + +* Add `--deprecation-out` CLI option which directs deprecation warnings + to the named file. (Myron Marston) +* Backport support for `skip` in metadata to skip execution of an example. + (Xavier Shay, #1472) +* Add `Pathname` support for setting all output streams. (Aaron Kromer) +* Add `test_unit` and `minitest` expectation frameworks. (Aaron Kromer) + +Deprecations: + +* Deprecate `RSpec::Core::Pending::PendingDeclaredInExample`, use + `SkipDeclaredInExample` instead. (Xavier Shay) +* Issue a deprecation when `described_class` is accessed from within + a nested `describe ` example group, since `described_class` + will return the innermost described class in RSpec 3 rather than the + outermost described class, as it behaved in RSpec 2. (Myron Marston) +* Deprecate `RSpec::Core::FilterManager::DEFAULT_EXCLUSIONS`, + `RSpec::Core::FilterManager::STANDALONE_FILTERS` and use of + `#empty_without_conditional_filters?` on those filters. (Sergey Pchelincev) +* Deprecate `RSpec::Core::Example#options` in favor of + `RSpec::Core::Example#metadata`. (Myron Marston) +* Issue warning when passing a symbol or hash to `describe` or `context` + as the first argument. In RSpec 2.x this would be treated as metadata + but in RSpec 3 it'll be treated as the described object. To continue + having it treated as metadata, pass a description before the symbol or + hash. (Myron Marston) +* Deprecate `RSpec::Core::BaseTextFormatter::VT100_COLORS` and + `RSpec::Core::BaseTextFormatter::VT100_COLOR_CODES` in favour + of `RSpec::Core::BaseTextFormatter::ConsoleCodes::VT100_CODES` and + `RSpec::Core::BaseTextFormatter::ConsoleCodes::VT100_CODE_VALUES`. + (Jon Rowe) +* Deprecate `RSpec::Core::ExampleGroup.display_name` in favor of + `RSpec::Core::ExampleGroup.description`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.describes` in favor of + `RSpec::Core::ExampleGroup.described_class`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.alias_example_to` in favor of + `RSpec::Core::Configuration#alias_example_to`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.alias_it_behaves_like_to` in favor + of `RSpec::Core::Configuration#alias_it_behaves_like_to`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.focused` in favor of + `RSpec::Core::ExampleGroup.focus`. (Myron Marston) +* Add deprecation warning for `config.filter_run :focused` since + example aliases `fit` and `focus` will no longer include + `:focused` metadata but will continue to include `:focus`. (Myron Marston) +* Deprecate filtering by `:line_number` (e.g. `--line-number` from the + CLI). Use location filtering instead. (Myron Marston) +* Deprecate `--default_path` as an alternative to `--default-path`. (Jon Rowe) +* Deprecate `RSpec::Core::Configuration#warnings` in favor of + `RSpec::Core::Configuration#warnings?`. (Myron Marston) +* Deprecate `share_examples_for` in favor of `shared_examples_for` or + just `shared_examples`. (Myron Marston) +* Deprecate `RSpec::Core::CommandLine` in favor of + `RSpec::Core::Runner`. (Myron Marston) +* Deprecate `#color_enabled`, `#color_enabled=` and `#color?` in favour of + `#color`, `#color=` and `#color_enabled? output`. (Jon Rowe) +* Deprecate `#filename_pattern` in favour of `#pattern`. (Jon Rowe) +* Deprecate `#backtrace_cleaner` in favour of `#backtrace_formatter`. (Jon Rowe) +* Deprecate mutating `RSpec::Configuration#formatters`. (Jon Rowe) +* Deprecate `stdlib` as an available expectation framework in favour of + `test_unit` and `minitest`. (Aaron Kromer) + +Bug Fixes: + +* Issue a warning when you set `config.deprecation_stream` too late for + it to take effect because the reporter has already been setup. (Myron Marston) +* `skip` with a block should not execute the block. (Xavier Shay) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.beta1...v2.99.0.beta2) + +Enhancements: + +* Add `is_expected` for one-liners that read well with the + `expect`-based syntax. `is_expected` is simply defined as + `expect(subject)` and can be used in an expression like: + `it { is_expected.to read_well }`. (Myron Marston) +* Backport `skip` from RSpec 3, which acts like `pending` did in RSpec 2 + when not given a block, since the behavior of `pending` is changing in + RSpec 3. (Xavier Shay) + +Deprecations: + +* Deprecate inexact `mock_with` config options. RSpec 3 will only support + the exact symbols `:rspec`, `:mocha`, `:flexmock`, `:rr` or `:nothing` + (or any module that implements the adapter interface). RSpec 2 did + fuzzy matching but this will not be supported going forward. + (Myron Marston) +* Deprecate `show_failures_in_pending_blocks` config option. To achieve + the same behavior as the option enabled, you can use a custom + formatter instead. (Xavier Shay) +* Add a deprecation warning for the fact that the behavior of `pending` + is changing in RSpec 3 -- rather than skipping the example (as it did + in 2.x when no block was provided), it will run the example and mark + it as failed if no exception is raised. Use `skip` instead to preserve + the old behavior. (Xavier Shay) +* Deprecate 's', 'n', 'spec' and 'nested' as aliases for documentation + formatter. (Jon Rowe) +* Deprecate `RSpec::Core::Reporter#abort` in favor of + `RSpec::Core::Reporter#finish`. (Jon Rowe) + +Bug Fixes: + +* Fix failure (undefined method `path`) in end-of-run summary + when `raise_errors_for_deprecations!` is configured. (Myron Marston) +* Fix issue were overridding spec ordering from the command line wasn't + fully recognised interally. (Jon Rowe) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.7...v2.99.0.beta1) + +Enhancements + +* Block-based DSL methods that run in the context of an example + (`it`, `before(:each)`, `after(:each)`, `let` and `subject`) + now yield the example as a block argument. (David Chelimsky) +* Warn when the name of more than one example group is submitted to + `include_examples` and it's aliases. (David Chelimsky) +* Add `expose_current_running_example_as` config option for + use during the upgrade process when external gems use the + deprecated `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. (Myron Marston) +* Limit spamminess of deprecation messages. (Bradley Schaefer, Loren Segal) +* Add `config.raise_errors_for_deprecations!` option, which turns + deprecations warnings into errors to surface the full backtrace + of the call site. (Myron Marston) + +Deprecations + +* Deprecate `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. If you need + access to the example (e.g. to get its metadata), use a block argument + instead. (David Chelimsky) +* Deprecate use of `autotest/rspec2` in favour of `rspec-autotest`. (Jon Rowe) +* Deprecate RSpec's built-in debugger support. Use a CLI option like + `-rruby-debug` (for the ruby-debug gem) or `-rdebugger` (for the + debugger gem) instead. (Myron Marston) +* Deprecate `RSpec.configuration.treat_symbols_as_metadata_keys_with_true_values = false`. + RSpec 3 will not support having this option set to `false`. (Myron Marston) +* Deprecate accessing a `let` or `subject` declaration in + a `after(:all)` hook. (Myron Marston, Jon Rowe) +* Deprecate built-in `its` usage in favor of `rspec-its` gem due to planned + removal in RSpec 3. (Peter Alfvin) +* Deprecate `RSpec::Core::PendingExampleFixedError` in favor of + `RSpec::Core::Pending::PendingExampleFixedError`. (Myron Marston) +* Deprecate `RSpec::Core::Configuration#out` and + `RSpec::Core::Configuration#output` in favor of + `RSpec::Core::Configuration#output_stream`. (Myron Marston) +* Deprecate legacy ordering APIs. + * You should use `register_ordering(:global)` instead of these: + * `RSpec::Core::Configuration#order_examples` + * `RSpec::Core::Configuration#order_groups` + * `RSpec::Core::Configuration#order_groups_and_examples` + * These are deprecated with no replacement because in RSpec 3 + ordering is a property of individual example groups rather than + just a global property of the entire test suite: + * `RSpec::Core::Configuration#order` + * `RSpec::Core::Configuration#randomize?` + * `--order default` is deprecated in favor of `--order defined` + (Myron Marston) + +### 2.14.8 / 2014-02-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.7...v2.14.8) + +Bug fixes: + +* Fix regression with the `textmateformatter` that prevented backtrace links + from being clickable. (Stefan Daschek) + +### 2.14.7 / 2013-10-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.6...v2.14.7) + +Bug fixes: + +* Fix regression in 2.14.6 that broke the Fivemat formatter. + It depended upon either + `example.execution_result[:exception].pending_fixed?` (which + was removed in 2.14.6 to fix an issue with frozen error objects) + or `RSpec::Core::PendingExampleFixedError` (which was renamed + to `RSpec::Core::Pending::PendingExampleFixedError` in 2.8. + This fix makes a constant alias for the old error name. + (Myron Marston) + +### 2.14.6 / 2013-10-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.5...v2.14.6) + +Bug fixes: + +* Format stringified numbers correctly when mathn library is loaded. + (Jay Hayes) +* Fix an issue that prevented the use of frozen error objects. (Lars + Gierth) + +### 2.14.5 / 2013-08-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.4...v2.14.5) + +Bug fixes: + +* Fix a `NoMethodError` that was being raised when there were no shared + examples or contexts declared and `RSpec.world.reset` is invoked. + (thepoho, Jon Rowe, Myron Marston) +* Fix a deprecation warning that was being incorrectly displayed when + `shared_examples` are declared at top level in a `module` scope. + (Jon Rowe) +* Fix after(:all) hooks so consecutive (same context) scopes will run even if + one raises an error. (Jon Rowe, Trejkaz) +* JsonFormatter no longer dies if `dump_profile` isn't defined (Alex / @MasterLambaster, Jon Rowe) + +### 2.14.4 / 2013-07-21 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.3...v2.14.4) + +Bug fixes + +* Fix regression in 2.14: ensure configured requires (via `-r` option) + are loaded before spec files are loaded. This allows the spec files + to programatically change the file pattern (Jon Rowe). +* Autoload `RSpec::Mocks` and `RSpec::Expectations` when referenced if + they are not already loaded (`RSpec::Matches` has been autoloaded + for a while). In the `rspec` gem, we changed it recently to stop + loading `rspec/mocks` and `rspec/expectations` by default, as some + users reported problems where they were intending to use mocha, + not rspec-mocks, but rspec-mocks was loaded and causing a conflict. + rspec-core loads mocks and expectations at the appropriate time, so + it seemed like a safe change -- but caused a problem for some authors + of libraries that integrate with RSpec. This fixes that problem. + (Myron Marston) +* Gracefully handle a command like `rspec --profile path/to/spec.rb`: + the `path/to/spec.rb` arg was being wrongly treated as the `profile` + integer arg, which got cast `0` using `to_i`, causing no profiled + examples to be printed. (Jon Rowe) + +### 2.14.3 / 2013-07-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.2...v2.14.3) + +Bug fixes + +* Fix deprecation notices issued from `RSpec::Core::RakeTask` so + that they work properly when all of rspec-core is not loaded. + (This was a regression in 2.14) (Jon Rowe) + +### 2.14.2 / 2013-07-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.1...v2.14.2) + +Bug fixes + +* Fix regression caused by 2.14.1 release: formatters that + report that they `respond_to?` a notification, but had + no corresponding method would raise an error when registered. + The new fix is to just implement `start` on the deprecation + formatter to fix the original JRuby/ruby-debug issue. + (Jon Rowe) + +### 2.14.1 / 2013-07-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.0...v2.14.1) + +Bug fixes + +* Address deprecation formatter failure when using `ruby-debug` on + JRuby: fix `RSpec::Core::Reporter` to not send a notification + when the formatter's implementation of the notification method + comes from `Kernel` (Alex Portnov, Jon Rowe). + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.0.rc1...v2.14.0) + +Enhancements + +* Apply focus to examples defined with `fit` (equivalent of + `it "description", focus: true`) (Michael de Silva) + +Bug fix + +* Ensure methods defined by `let` take precedence over others + when there is a name collision (e.g. from an included module). + (Jon Rowe, Andy Lindeman and Myron Marston) + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.13.1...v2.14.0.rc1) + +Enhancements + +* Improved Windows detection inside Git Bash, for better `--color` handling. +* Add profiling of the slowest example groups to `--profile` option. + The output is sorted by the slowest average example groups. +* Don't show slow examples if there's a failure and both `--fail-fast` + and `--profile` options are used (Paweł Gościcki). +* Rather than always adding `spec` to the load path, add the configured + `--default-path` to the load path (which defaults to `spec`). This + better supports folks who choose to put their specs in a different + directory (John Feminella). +* Add some logic to test time duration precision. Make it a + function of time, dropping precision as the time increases. (Aaron Kromer) +* Add new `backtrace_inclusion_patterns` config option. Backtrace lines + that match one of these patterns will _always_ be included in the + backtrace, even if they match an exclusion pattern, too (Sam Phippen). +* Support ERB trim mode using the `-` when parsing `.rspec` as ERB + (Gabor Garami). +* Give a better error message when let and subject are called without a block. + (Sam Phippen). +* List the precedence of `.rspec-local` in the configuration documentation + (Sam Phippen) +* Support `{a,b}` shell expansion syntax in `--pattern` option + (Konstantin Haase). +* Add cucumber documentation for --require command line option + (Bradley Schaefer) +* Expose configuration options via config: + * `config.libs` returns the libs configured to be added onto the load path + * `full_backtrace?` returns the state of the backtrace cleaner + * `debug?` returns true when the debugger is loaded + * `line_numbers` returns the line numbers we are filtering by (if any) + * `full_description` returns the RegExp used to filter descriptions + (Jon Rowe) +* Add setters for RSpec.world and RSpec.configuration (Alex Soulim) +* Configure ruby's warning behaviour with `--warnings` (Jon Rowe) +* Fix an obscure issue on old versions of `1.8.7` where `Time.dup` wouldn't + allow access to `Time.now` (Jon Rowe) +* Make `shared_examples_for` context aware, so that keys may be safely reused + in multiple contexts without colliding. (Jon Rowe) +* Add a configurable `deprecation_stream` (Jon Rowe) +* Publish deprecations through a formatter (David Chelimsky) + +Bug fixes + +* Make JSON formatter behave the same when it comes to `--profile` as + the text formatter (Paweł Gościcki). +* Fix named subjects so that if an inner group defines a method that + overrides the named method, `subject` still retains the originally + declared value (Myron Marston). +* Fix random ordering so that it does not cause `rand` in examples in + nested sibling contexts to return the same value (Max Shytikov). +* Use the new `backtrace_inclusion_patterns` config option to ensure + that folks who develop code in a directory matching one of the default + exclusion patterns (e.g. `gems`) still get the normal backtrace + filtering (Sam Phippen). +* Fix ordering of `before` hooks so that `before` hooks declared in + `RSpec.configure` run before `before` hooks declared in a shared + context (Michi Huber and Tejas Dinkar). +* Fix `Example#full_description` so that it gets filled in by the last + matcher description (as `Example#description` already did) when no + doc string has been provided (David Chelimsky). +* Fix the memoized methods (`let` and `subject`) leaking `define_method` + as a `public` method. (Thomas Holmes and Jon Rowe) (#873) +* Fix warnings coming from the test suite. (Pete Higgins) + +Deprecations + +* Deprecate `Configuration#backtrace_clean_patterns` in favor of + `Configuration#backtrace_exclusion_patterns` for greater consistency + and symmetry with new `backtrace_inclusion_patterns` config option + (Sam Phippen). +* Deprecate `Configuration#requires=` in favor of using ruby's + `require`. Requires specified by the command line can still be + accessed by the `Configuration#require` reader. (Bradley Schaefer) +* Deprecate calling `SharedExampleGroups` defined across sibling contexts + (Jon Rowe) + +### 2.13.1 / 2013-03-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.13.0...v2.13.1) + +Bug fixes + +* Use hook classes as proxies rather than extending hook blocks to support + lambdas for before/after/around hooks. (David Chelimsky) +* Fix regression in 2.13.0 that caused confusing behavior when overriding + a named subject with an unnamed subject in an inner group and then + referencing the outer group subject's name. The fix for this required + us to disallow using `super` in a named subject (which is confusing, + anyway -- named subjects create 2 methods, so which method on the + parent example group are you `super`ing to?) but `super` in an unnamed + subject continues to work (Myron Marston). +* Do not allow a referenced `let` or `subject` in `before(:all)` to cause + other `let` declarations to leak across examples (Myron Marston). +* Work around odd ruby 1.9 bug with `String#match` that was triggered + by passing it a regex from a `let` declaration. For more info, see + http://bugs.ruby-lang.org/issues/8059 (Aaron Kromer). +* Add missing `require 'set'` to `base_text_formatter.rb` (Tom + Anderson). + +Deprecations + +* Deprecate accessing `let` or `subject` declarations in `before(:all)`. + These were not intended to be called in a `before(:all)` hook, as + they exist to define state that is reset between each example, while + `before(:all)` exists to define state that is shared across examples + in an example group (Myron Marston). + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.2...v2.13.0) + +Enhancements + +* Allow `--profile` option to take a count argument that + determines the number of slow examples to dump + (Greggory Rothmeier). +* Add `subject!` that is the analog to `let!`. It defines an + explicit subject and sets a `before` hook that will invoke + the subject (Zubin Henner). +* Fix `let` and `subject` declaration so that `super` + and `return` can be used in them, just like in a normal + method. (Myron Marston) +* Allow output colors to be configured individually. + (Charlie Maffitt) +* Always dump slow examples when `--profile` option is given, + even when an example failed (Myron Marston). + +Bug fixes + +* Don't blow up when dumping error output for instances + of anonymous error classes (Myron Marston). +* Fix default backtrace filters so lines from projects + containing "gems" in the name are not filtered, but + lines from installed gems still are (Myron Marston). +* Fix autotest command so that is uses double quotes + rather than single quotes for windows compatibility + (Jonas Tingeborn). +* Fix `its` so that uses of `subject` in a `before` or `let` + declaration in the parent group continue to reference the + parent group's subject. (Olek Janiszewski) + +### 2.12.2 / 2012-12-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.1...v2.12.2) + +Bug fixes + +* Fix `RSpec::Core::RakeTask` so that it is compatible with rake 0.8.7 + on ruby 1.8.7. We had accidentally broke it in the 2.12 release + (Myron Marston). +* Fix `RSpec::Core::RakeTask` so it is tolerant of the `Rspec` constant + for backwards compatibility (Patrick Van Stee) + +### 2.12.1 / 2012-12-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Specs are run even if another at\_exit hook calls `exit`. This allows + Test::Unit and RSpec to run together. (Suraj N. Kurapati) +* Fix full doc string concatenation so that it handles the case of a + method string (e.g. "#foo") being nested under a context string + (e.g. "when it is tuesday"), so that we get "when it is tuesday #foo" + rather than "when it is tuesday#foo". (Myron Marston) +* Restore public API I unintentionally broke in 2.12.0: + `RSpec::Core::Formatters::BaseFormatter#format_backtrce(backtrace, example)` + (Myron Marston). + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.11.1...v2.12.0) + +Enhancements + +* Add support for custom ordering strategies for groups and examples. + (Myron Marston) +* JSON Formatter (Alex Chaffee) +* Refactor rake task internals (Sam Phippen) +* Refactor HtmlFormatter (Pete Hodgson) +* Autotest supports a path to Ruby that contains spaces (dsisnero) +* Provide a helpful warning when a shared example group is redefined. + (Mark Burns). +* `--default_path` can be specified as `--default-line`. `--line_number` can be + specified as `--line-number`. Hyphens are more idiomatic command line argument + separators (Sam Phippen). +* A more useful error message is shown when an invalid command line option is + used (Jordi Polo). +* Add `format_docstrings { |str| }` config option. It can be used to + apply formatting rules to example group and example docstrings. + (Alex Tan) +* Add support for an `.rspec-local` options file. This is intended to + allow individual developers to set options in a git-ignored file that + override the common project options in `.rspec`. (Sam Phippen) +* Support for mocha 0.13.0. (Andy Lindeman) + +Bug fixes + +* Remove override of `ExampleGroup#ancestors`. This is a core ruby method that + RSpec shouldn't override. Instead, define `ExampleGroup#parent_groups`. (Myron + Marston) +* Limit monkey patching of shared example/context declaration methods + (`shared_examples_for`, etc.) to just the objects that need it rather than + every object in the system (Myron Marston). +* Fix Metadata#fetch to support computed values (Sam Goldman). +* Named subject can now be referred to from within subject block in a nested + group (tomykaira). +* Fix `fail_fast` so that it properly exits when an error occurs in a + `before(:all) hook` (Bradley Schaefer). +* Make the order spec files are loaded consistent, regardless of the + order of the files returned by the OS or the order passed at + the command line (Jo Liss and Sam Phippen). +* Ensure instance variables from `before(:all)` are always exposed + from `after(:all)`, even if an error occurs in `before(:all)` + (Sam Phippen). +* `rspec --init` no longer generates an incorrect warning about `--configure` + being deprecated (Sam Phippen). +* Fix pluralization of `1 seconds` (Odin Dutton) +* Fix ANSICON url (Jarmo Pertman) +* Use dup of Time so reporting isn't clobbered by examples that modify Time + without properly restoring it. (David Chelimsky) + +Deprecations + +* `share_as` is no longer needed. `shared_context` and/or + `RSpec::SharedContext` provide better mechanisms (Sam Phippen). +* Deprecate `RSpec.configuration` with a block (use `RSpec.configure`). + + +### 2.11.1 / 2012-07-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Fix the way we autoload RSpec::Matchers so that custom matchers can be + defined before rspec-core has been configured to definitely use + rspec-expectations. (Myron Marston) +* Fix typo in --help message printed for -e option. (Jo Liss) +* Fix ruby warnings. (Myron Marston) +* Ignore mock expectation failures when the example has already failed. + Mock expectation failures have always been ignored in this situation, + but due to my changes in 27059bf1 it was printing a confusing message. + (Myron Marston). + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.10.1...v2.11.0) + +Enhancements + +* Support multiple `--example` options. (Daniel Doubrovkine @dblock) +* Named subject e.g. `subject(:article) { Article.new }` + * see [http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/](http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/) + for background. + * thanks to Bradley Schaefer for suggesting it and Avdi Grimm for almost + suggesting it. +* `config.mock_with` and `config.expect_with` yield custom config object to a + block if given + * aids decoupling from rspec-core's configuation +* `include_context` and `include_examples` support a block, which gets eval'd + in the current context (vs the nested context generated by `it_behaves_like`). +* Add `config.order = 'random'` to the `spec_helper.rb` generated by `rspec + --init`. +* Delay the loading of DRb (Myron Marston). +* Limit monkey patching of `describe` onto just the objects that need it rather + than every object in the system (Myron Marston). + +Bug fixes + +* Support alternative path separators. For example, on Windows, you can now do + this: `rspec spec\subdir`. (Jarmo Pertman @jarmo) +* When an example raises an error and an after or around hook does as + well, print out the hook error. Previously, the error was silenced and + the user got no feedback about what happened. (Myron Marston) +* `--require` and `-I` are merged among different configuration sources (Andy + Lindeman) +* Delegate to mocha methods instead of aliasing them in mocha adapter. + +### 2.10.1 / 2012-05-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.10.0...v2.10.1) + +Bug fixes + +* `RSpec.reset` properly reinits configuration and world +* Call `to_s` before `split` on exception messages that might not always be + Strings (slyphon) + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.9.0...v2.10.0) + +Enhancements + +* Add `prepend_before` and `append_after` hooks (preethiramdev) + * intended for extension libs + * restores rspec-1 behavior +* Reporting of profiled examples (moro) + * Report the total amount of time taken for the top slowest examples. + * Report what percentage the slowest examples took from the total runtime. + +Bug fixes + +* Properly parse `SPEC_OPTS` options. +* `example.description` returns the location of the example if there is no + explicit description or matcher-generated description. +* RDoc fixes (Grzegorz Świrski) +* Do not modify example ancestry when dumping errors (Michael Grosser) + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0...v2.9.0) + +Enhancements + +* Support for "X minutes X seconds" spec run duration in formatter. (uzzz) +* Strip whitespace from group and example names in doc formatter. +* Removed spork-0.9 shim. If you're using spork-0.8.x, you'll need to upgrade + to 0.9.0. + +Bug fixes + +* Restore `--full_backtrace` option +* Ensure that values passed to `config.filter_run` are respected when running + over DRb (using spork). +* Ensure shared example groups are reset after a run (as example groups are). +* Remove `rescue false` from calls to filters represented as Procs +* Ensure `described_class` gets the closest constant (pyromaniac) +* In "autorun", don't run the specs in the `at_exit` hook if there was an + exception (most likely due to a SyntaxError). (sunaku) +* Don't extend groups with modules already used to extend ancestor groups. +* `its` correctly memoizes nil or false values (Yamada Masaki) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0.rc2...v2.8.0) + +Bug fixes + +* For metadata filtering, restore passing the entire array to the proc, rather + than each item in the array (weidenfreak) +* Ensure each spec file is loaded only once + * Fixes a bug that caused all the examples in a file to be run when + referenced twice with line numbers in a command, e.g. + * `rspec path/to/file:37 path/to/file:42` + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0.rc1...v2.8.0.rc2) + +Enhancments + +* new `--init` command (Peter Schröder) + * generates `spec/spec_helper.rb` + * deletes obsolete files (on confirmation) + * merged with and deprecates `--configure` command, which generated + `.rspec` +* use `require_relative` when available (Ian Leitch) +* `include_context` and `include_examples` accept params (Calvin Bascom) +* print the time for every example in the html formatter (Richie Vos) +* several tasty refactoring niblets (Sasha) +* `it "does something", :x => [:foo,'bar',/baz/] (Ivan Neverov) + * supports matching n command line tag values with an example or group + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.7.1...v2.8.0.rc1) + +Enhancements + +* `--order` (Justin Ko) + * run examples in random order: `--order rand` + * specify the seed: `--order rand:123` +* `--seed SEED` + * equivalent of `--order rand:SEED` +* SharedContext supports `let` (David Chelimsky) +* Filter improvements (David Chelimsky) + * override opposing tags from the command line + * override RSpec.configure tags from the command line + * `--line_number 37` overrides all other filters + * `path/to/file.rb:37` overrides all other filters + * refactor: consolidate filter management in a FilterManger object +* Eliminate Ruby warnings (Matijs van Zuijlen) +* Make reporter.report an API (David Chelimsky) + * supports extension tools like interative_rspec + +Changes + +* change `config.color_enabled` (getter/setter/predicate) to `color` to align + with `--[no]-color` CLI option. + * `color_enabled` is still supported for now, but will likley be deprecated + in a 2.x release so we can remove it in 3.0. + +Bug fixes + +* Make sure the `bar` in `--tag foo:bar` makes it to DRb (Aaron Gibralter) +* Fix bug where full descriptions of groups nested 3 deep were repeated. +* Restore report of time to run to start after files are loaded. + * fixes bug where run times were cumalitive in spork + * fixes compatibility with time-series metrics +* Don't error out when `config.mock_with` or `expect_with` is re-specifying the + current config (Myron Marston) + +* Deprecations + * :alias option on `configuration.add_setting`. Use `:alias_with` on the + original setting declaration instead. + +### 2.7.1 / 2011-10-20 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.7.0...v2.7.1) + +Bug fixes + +* tell autotest the correct place to find the rspec executable + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.4...v2.7.0) + +NOTE: RSpec's release policy dictates that there should not be any backward +incompatible changes in minor releases, but we're making an exception to +release a change to how RSpec interacts with other command line tools. + +As of 2.7.0, you must explicity `require "rspec/autorun"` unless you use the +`rspec` command (which already does this for you). + +Enhancements + +* Add `example.exception` (David Chelimsky) +* `--default_path` command line option (Justin Ko) +* support multiple `--line_number` options (David J. Hamilton) + * also supports `path/to/file.rb:5:9` (runs examples on lines 5 and 9) +* Allow classes/modules to be used as shared example group identifiers (Arthur + Gunn) +* Friendly error message when shared context cannot be found (Sławosz + Sławiński) +* Clear formatters when resetting config (John Bintz) +* Add `xspecify` and xexample as temp-pending methods (David Chelimsky) +* Add `--no-drb` option (Iain Hecker) +* Provide more accurate run time by registering start time before code is + loaded (David Chelimsky) + * reverted in 2.8.0 +* Rake task default pattern finds specs in symlinked dirs (Kelly Felkins) +* Rake task no longer does anything to invoke bundler since Bundler already + handles it for us. Thanks to Andre Arko for the tip. +* Add `--failure-exit-code` option (Chris Griego) + +Bug fixes + +* Include `Rake::DSL` to remove deprecation warnings in Rake > 0.8.7 (Pivotal + Casebook) +* Only eval `let` block once even if it returns `nil` (Adam Meehan) +* Fix `--pattern` option (wasn't being recognized) (David Chelimsky) +* Only implicitly `require "rspec/autorun"` with the `rspec` command (David + Chelimsky) +* Ensure that rspec's `at_exit` defines the exit code (Daniel Doubrovkine) +* Show the correct snippet in the HTML and TextMate formatters (Brian Faherty) + +### 2.6.4 / 2011-06-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.3...v2.6.4) + +NOTE: RSpec's release policy dictates that there should not be new +functionality in patch releases, but this minor enhancement slipped in by +accident. As it doesn't add a new API, we decided to leave it in rather than +roll back this release. + +Enhancements + +* Add summary of commands to run individual failed examples. + +Bug fixes + +* Support exclusion filters in DRb. (Yann Lugrin) +* Fix --example escaping when run over DRb. (Elliot Winkler) +* Use standard ANSI codes for color formatting so colors work in a wider set of + color schemes. + +### 2.6.3 / 2011-05-24 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.2...v2.6.3) + +Bug fixes + +* Explicitly convert exit code to integer, avoiding TypeError when return + value of run is IO object proxied by `DRb::DRbObject` (Julian Scheid) +* Clarify behavior of `--example` command line option +* Build using a rubygems-1.6.2 to avoid downstream yaml parsing error + +### 2.6.2 / 2011-05-21 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.1...v2.6.2) + +Bug fixes + +* Warn rather than raise when HOME env var is not defined +* Properly merge command-line exclusions with default :if and :unless (joshcooper) + +### 2.6.1 / 2011-05-19 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.0...v2.6.1) + +Bug fixes + +* Don't extend nil when filters are nil +* `require 'rspec/autorun'` when running rcov. + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.5.1...v2.6.0) + +Enhancements + +* `shared_context` (Damian Nurzynski) + * extend groups matching specific metadata with: + * method definitions + * subject declarations + * let/let! declarations + * etc (anything you can do in a group) +* `its([:key])` works for any subject with #[]. (Peter Jaros) +* `treat_symbols_as_metadata_keys_with_true_values` (Myron Marston) +* Print a deprecation warning when you configure RSpec after defining an + example. All configuration should happen before any examples are defined. + (Myron Marston) +* Pass the exit status of a DRb run to the invoking process. This causes specs + run via DRb to not just return true or false. (Ilkka Laukkanen) +* Refactoring of `ConfigurationOptions#parse_options` (Rodrigo Rosenfeld Rosas) +* Report excluded filters in runner output (tip from andyl) +* Clean up messages for filters/tags. +* Restore --pattern/-P command line option from rspec-1 +* Support false as well as true in config.full_backtrace= (Andreas Tolf + Tolfsen) + +Bug fixes + +* Don't stumble over an exception without a message (Hans Hasselberg) +* Remove non-ascii characters from comments that were choking rcov (Geoffrey + Byers) +* Fixed backtrace so it doesn't include lines from before the autorun at_exit + hook (Myron Marston) +* Include RSpec::Matchers when first example group is defined, rather than just + before running the examples. This works around an obscure bug in ruby 1.9 + that can cause infinite recursion. (Myron Marston) +* Don't send `example_group_[started|finished]` to formatters for empty groups. +* Get specs passing on jruby (Sidu Ponnappa) +* Fix bug where mixing nested groups and outer-level examples gave + unpredictable :line_number behavior (Artur Małecki) +* Regexp.escape the argument to --example (tip from Elliot Winkler) +* Correctly pass/fail pending block with message expectations +* CommandLine returns exit status (0/1) instead of true/false +* Create path to formatter output file if it doesn't exist (marekj). + + +### 2.5.1 / 2011-02-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.5.0...v2.5.1) + +NOTE: this release breaks compatibility with rspec/autotest/bundler +integration, but does so in order to greatly simplify it. + +With this release, if you want the generated autotest command to include +'bundle exec', require Autotest's bundler plugin in a .autotest file in the +project's root directory or in your home directory: + + require "autotest/bundler" + +Now you can just type 'autotest' on the command line and it will work as you expect. + +If you don't want 'bundle exec', there is nothing you have to do. + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.4.0...v2.5.0) + +Enhancements + +* Autotest::Rspec2 parses command line args passed to autotest after '--' +* --skip-bundler option for autotest command +* Autotest regexp fixes (Jon Rowe) +* Add filters to html and textmate formatters (Daniel Quimper) +* Explicit passing of block (need for JRuby 1.6) (John Firebaugh) + +Bug fixes + +* fix dom IDs in HTML formatter (Brian Faherty) +* fix bug with --drb + formatters when not running in drb +* include --tag options in drb args (monocle) +* fix regression so now SPEC_OPTS take precedence over CLI options again (Roman + Chernyatchik) +* only call its(:attribute) once (failing example from Brian Dunn) +* fix bizarre bug where rspec would hang after String.alias :to_int :to_i + (Damian Nurzynski) + +Deprecations + +* implicit inclusion of 'bundle exec' when Gemfile present (use autotest's + bundler plugin instead) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.3.1...v2.4.0) + +Enhancements + +* start the debugger on -d so the stack trace is visible when it stops + (Clifford Heath) +* apply hook filtering to examples as well as groups (Myron Marston) +* support multiple formatters, each with their own output +* show exception classes in failure messages unless they come from RSpec + matchers or message expectations +* before(:all) { pending } sets all examples to pending + +Bug fixes + +* fix bug due to change in behavior of reject in Ruby 1.9.3-dev (Shota + Fukumori) +* fix bug when running in jruby: be explicit about passing block to super (John + Firebaugh) +* rake task doesn't choke on paths with quotes (Janmejay Singh) +* restore --options option from rspec-1 +* require 'ostruct' to fix bug with its([key]) (Kim Burgestrand) +* --configure option generates .rspec file instead of autotest/discover.rb + +### 2.3.1 / 2010-12-16 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.3.0...v2.3.1) + +Bug fixes + +* send debugger warning message to $stdout if RSpec.configuration.error_stream + has not been defined yet. +* HTML Formatter _finally_ properly displays nested groups (Jarmo Pertman) +* eliminate some warnings when running RSpec's own suite (Jarmo Pertman) + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.2.1...v2.3.0) + +Enhancements + +* tell autotest to use "rspec2" if it sees a .rspec file in the project's root + directory + * replaces the need for ./autotest/discover.rb, which will not work with + all versions of ZenTest and/or autotest +* config.expect_with + * :rspec # => rspec/expectations + * :stdlib # => test/unit/assertions + * :rspec, :stdlib # => both + +Bug fixes + +* fix dev Gemfile to work on non-mac-os machines (Lake Denman) +* ensure explicit subject is only eval'd once (Laszlo Bacsi) + +### 2.2.1 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.2.0...v2.2.1) + +Bug fixes + +* alias_method instead of override Kernel#method_missing (John Wilger) +* changed --autotest to --tty in generated command (MIKAMI Yoshiyuki) +* revert change to debugger (had introduced conflict with Rails) + * also restored --debugger/-debug option + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.1.0...v2.2.0) + +Deprecations/changes + +* --debug/-d on command line is deprecated and now has no effect +* win32console is now ignored; Windows users must use ANSICON for color support + (Bosko Ivanisevic) + +Enhancements + +* When developing locally rspec-core now works with the rspec-dev setup or your + local gems +* Raise exception with helpful message when rspec-1 is loaded alongside rspec-2 + (Justin Ko) +* debugger statements _just work_ as long as ruby-debug is installed + * otherwise you get warned, but not fired +* Expose example.metadata in around hooks +* Performance improvments (much faster now) + +Bug fixes + +* Make sure --fail-fast makes it across drb +* Pass -Ilib:spec to rcov + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.1...v2.1.0) + +Enhancments + +* Add skip_bundler option to rake task to tell rake task to ignore the presence + of a Gemfile (jfelchner) +* Add gemfile option to rake task to tell rake task what Gemfile to look for + (defaults to 'Gemfile') +* Allow passing caller trace into Metadata to support extensions (Glenn + Vanderburg) +* Add deprecation warning for Spec::Runner.configure to aid upgrade from + RSpec-1 +* Add deprecated Spec::Rake::SpecTask to aid upgrade from RSpec-1 +* Add 'autospec' command with helpful message to aid upgrade from RSpec-1 +* Add support for filtering with tags on CLI (Lailson Bandeira) +* Add a helpful message about RUBYOPT when require fails in bin/rspec (slyphon) +* Add "-Ilib" to the default rcov options (Tianyi Cui) +* Make the expectation framework configurable (default rspec, of course) + (Justin Ko) +* Add 'pending' to be conditional (Myron Marston) +* Add explicit support for :if and :unless as metadata keys for conditional run + of examples (Myron Marston) +* Add --fail-fast command line option (Jeff Kreeftmeijer) + +Bug fixes + +* Eliminate stack overflow with "subject { self }" +* Require 'rspec/core' in the Raketask (ensures it required when running rcov) + +### 2.0.1 / 2010-10-18 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0...v2.0.1) + +Bug fixes + +* Restore color when using spork + autotest +* Pending examples without docstrings render the correct message (Josep M. + Bach) +* Fixed bug where a failure in a spec file ending in anything but _spec.rb + would fail in a confusing way. +* Support backtrace lines from erb templates in html formatter (Alex Crichton) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.rc...v2.0.0) + +RSpec-1 compatibility + +* Rake task uses ENV["SPEC"] as file list if present + +Bug fixes + +* Bug Fix: optparse --out foo.txt (Leonardo Bessa) +* Suppress color codes for non-tty output (except autotest) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* implicitly require unknown formatters so you don't have to require the file + explicitly on the command line (Michael Grosser) +* add --out/-o option to assign output target +* added fail_fast configuration option to abort on first failure +* support a Hash subject (its([:key]) { should == value }) (Josep M. Bach) + +Bug fixes + +* Explicitly require rspec version to fix broken rdoc task (Hans de Graaff) +* Ignore backtrace lines that come from other languages, like Java or + Javascript (Charles Lowell) +* Rake task now does what is expected when setting (or not setting) + fail_on_error and verbose +* Fix bug in which before/after(:all) hooks were running on excluded nested + groups (Myron Marston) +* Fix before(:all) error handling so that it fails examples in nested groups, + too (Myron Marston) + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Enhancements + +* removed at_exit hook +* CTRL-C stops the run (almost) immediately + * first it cleans things up by running the appropriate after(:all) and + after(:suite) hooks + * then it reports on any examples that have already run +* cleaned up rake task + * generate correct task under variety of conditions + * options are more consistent + * deprecated redundant options +* run 'bundle exec autotest' when Gemfile is present +* support ERB in .rspec options files (Justin Ko) +* depend on bundler for development tasks (Myron Marston) +* add example_group_finished to formatters and reporter (Roman Chernyatchik) + +Bug fixes + +* support paths with spaces when using autotest (Andreas Neuhaus) +* fix module_exec with ruby 1.8.6 (Myron Marston) +* remove context method from top-level + * was conflicting with irb, for example +* errors in before(:all) are now reported correctly (Chad Humphries) + +Removals + +* removed -o --options-file command line option + * use ./.rspec and ~/.rspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/LICENSE.md new file mode 100644 index 0000000000..76dc17d739 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/LICENSE.md @@ -0,0 +1,26 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 Chad Humphries, David Chelimsky, Myron Marston +* Copyright © 2009 Chad Humphries, David Chelimsky +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-core-3.10.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/README.md new file mode 100644 index 0000000000..e9c3649eee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/README.md @@ -0,0 +1,384 @@ +# rspec-core [![Build Status](https://secure.travis-ci.org/rspec/rspec-core.svg?branch=main)](http://travis-ci.org/rspec/rspec-core) [![Code Climate](https://codeclimate.com/github/rspec/rspec-core.svg)](https://codeclimate.com/github/rspec/rspec-core) + +rspec-core provides the structure for writing executable examples of how your +code should behave, and an `rspec` command with tools to constrain which +examples get run and tailor the output. + +## Install + + gem install rspec # for rspec-core, rspec-expectations, rspec-mocks + gem install rspec-core # for rspec-core only + rspec --help + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +## Basic Structure + +RSpec uses the words "describe" and "it" so we can express concepts like a conversation: + + "Describe an order." + "It sums the prices of its line items." + +```ruby +RSpec.describe Order do + it "sums the prices of its line items" do + order = Order.new + + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(1.11, :USD) + ))) + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(2.22, :USD), + :quantity => 2 + ))) + + expect(order.total).to eq(Money.new(5.55, :USD)) + end +end +``` + +The `describe` method creates an [ExampleGroup](http://rubydoc.info/gems/rspec-core/RSpec/Core/ExampleGroup). Within the +block passed to `describe` you can declare examples using the `it` method. + +Under the hood, an example group is a class in which the block passed to +`describe` is evaluated. The blocks passed to `it` are evaluated in the +context of an _instance_ of that class. + +## Nested Groups + +You can also declare nested groups using the `describe` or `context` +methods: + +```ruby +RSpec.describe Order do + context "with no items" do + it "behaves one way" do + # ... + end + end + + context "with one item" do + it "behaves another way" do + # ... + end + end +end +``` + +Nested groups are subclasses of the outer example group class, providing +the inheritance semantics you'd want for free. + +## Aliases + +You can declare example groups using either `describe` or `context`. +For a top level example group, `describe` and `context` are available +off of `RSpec`. For backwards compatibility, they are also available +off of the `main` object and `Module` unless you disable monkey +patching. + +You can declare examples within a group using any of `it`, `specify`, or +`example`. + +## Shared Examples and Contexts + +Declare a shared example group using `shared_examples`, and then include it +in any group using `include_examples`. + +```ruby +RSpec.shared_examples "collections" do |collection_class| + it "is empty when first created" do + expect(collection_class.new).to be_empty + end +end + +RSpec.describe Array do + include_examples "collections", Array +end + +RSpec.describe Hash do + include_examples "collections", Hash +end +``` + +Nearly anything that can be declared within an example group can be declared +within a shared example group. This includes `before`, `after`, and `around` +hooks, `let` declarations, and nested groups/contexts. + +You can also use the names `shared_context` and `include_context`. These are +pretty much the same as `shared_examples` and `include_examples`, providing +more accurate naming when you share hooks, `let` declarations, helper methods, +etc, but no examples. + +## Metadata + +rspec-core stores a metadata hash with every example and group, which +contains their descriptions, the locations at which they were +declared, etc, etc. This hash powers many of rspec-core's features, +including output formatters (which access descriptions and locations), +and filtering before and after hooks. + +Although you probably won't ever need this unless you are writing an +extension, you can access it from an example like this: + +```ruby +it "does something" do |example| + expect(example.metadata[:description]).to eq("does something") +end +``` + +### `described_class` + +When a class is passed to `describe`, you can access it from an example +using the `described_class` method, which is a wrapper for +`example.metadata[:described_class]`. + +```ruby +RSpec.describe Widget do + example do + expect(described_class).to equal(Widget) + end +end +``` + +This is useful in extensions or shared example groups in which the specific +class is unknown. Taking the collections shared example group from above, we can +clean it up a bit using `described_class`: + +```ruby +RSpec.shared_examples "collections" do + it "is empty when first created" do + expect(described_class.new).to be_empty + end +end + +RSpec.describe Array do + include_examples "collections" +end + +RSpec.describe Hash do + include_examples "collections" +end +``` + +## A Word on Scope + +RSpec has two scopes: + +* **Example Group**: Example groups are defined by a `describe` or + `context` block, which is eagerly evaluated when the spec file is + loaded. The block is evaluated in the context of a subclass of + `RSpec::Core::ExampleGroup`, or a subclass of the parent example group + when you're nesting them. +* **Example**: Examples -- typically defined by an `it` block -- and any other + blocks with per-example semantics -- such as a `before(:example)` hook -- are + evaluated in the context of + an _instance_ of the example group class to which the example belongs. + Examples are _not_ executed when the spec file is loaded; instead, + RSpec waits to run any examples until all spec files have been loaded, + at which point it can apply filtering, randomization, etc. + +To make this more concrete, consider this code snippet: + +``` ruby +RSpec.describe "Using an array as a stack" do + def build_stack + [] + end + + before(:example) do + @stack = build_stack + end + + it 'is initially empty' do + expect(@stack).to be_empty + end + + context "after an item has been pushed" do + before(:example) do + @stack.push :item + end + + it 'allows the pushed item to be popped' do + expect(@stack.pop).to eq(:item) + end + end +end +``` + +Under the covers, this is (roughly) equivalent to: + +``` ruby +class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup + def build_stack + [] + end + + def before_example_1 + @stack = build_stack + end + + def it_is_initially_empty + expect(@stack).to be_empty + end + + class AfterAnItemHasBeenPushed < self + def before_example_2 + @stack.push :item + end + + def it_allows_the_pushed_item_to_be_popped + expect(@stack.pop).to eq(:item) + end + end +end +``` + +To run these examples, RSpec would (roughly) do the following: + +``` ruby +example_1 = UsingAnArrayAsAStack.new +example_1.before_example_1 +example_1.it_is_initially_empty + +example_2 = UsingAnArrayAsAStack::AfterAnItemHasBeenPushed.new +example_2.before_example_1 +example_2.before_example_2 +example_2.it_allows_the_pushed_item_to_be_popped +``` + +## The `rspec` Command + +When you install the rspec-core gem, it installs the `rspec` executable, +which you'll use to run rspec. The `rspec` command comes with many useful +options. +Run `rspec --help` to see the complete list. + +## Store Command Line Options `.rspec` + +You can store command line options in a `.rspec` file in the project's root +directory, and the `rspec` command will read them as though you typed them on +the command line. + +## Get Started + +Start with a simple example of behavior you expect from your system. Do +this before you write any implementation code: + +```ruby +# in spec/calculator_spec.rb +RSpec.describe Calculator do + describe '#add' do + it 'returns the sum of its arguments' do + expect(Calculator.new.add(1, 2)).to eq(3) + end + end +end +``` + +Run this with the rspec command, and watch it fail: + +``` +$ rspec spec/calculator_spec.rb +./spec/calculator_spec.rb:1: uninitialized constant Calculator +``` + +Address the failure by defining a skeleton of the `Calculator` class: + +```ruby +# in lib/calculator.rb +class Calculator + def add(a, b) + end +end +``` + +Be sure to require the implementation file in the spec: + +```ruby +# in spec/calculator_spec.rb +# - RSpec adds ./lib to the $LOAD_PATH +require "calculator" +``` + +Now run the spec again, and watch the expectation fail: + +``` +$ rspec spec/calculator_spec.rb +F + +Failures: + + 1) Calculator#add returns the sum of its arguments + Failure/Error: expect(Calculator.new.add(1, 2)).to eq(3) + + expected: 3 + got: nil + + (compared using ==) + # ./spec/calcalator_spec.rb:6:in `block (3 levels) in ' + +Finished in 0.00131 seconds (files took 0.10968 seconds to load) +1 example, 1 failure + +Failed examples: + +rspec ./spec/calcalator_spec.rb:5 # Calculator#add returns the sum of its arguments +``` + +Implement the simplest solution, by changing the definition of `Calculator#add` to: + +```ruby +def add(a, b) + a + b +end +``` + +Now run the spec again, and watch it pass: + +``` +$ rspec spec/calculator_spec.rb +. + +Finished in 0.000315 seconds +1 example, 0 failures +``` + +Use the `documentation` formatter to see the resulting spec: + +``` +$ rspec spec/calculator_spec.rb --format doc +Calculator + #add + returns the sum of its arguments + +Finished in 0.000379 seconds +1 example, 0 failures +``` + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +* [Build details](BUILD_DETAIL.md) +* [Code of Conduct](CODE_OF_CONDUCT.md) +* [Detailed contributing guide](CONTRIBUTING.md) +* [Development setup guide](DEVELOPMENT.md) + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/exe/rspec b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/exe/rspec new file mode 100755 index 0000000000..7ee5fd89f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/exe/rspec @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require 'rspec/core' +RSpec::Core::Runner.invoke diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/autorun.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/autorun.rb new file mode 100644 index 0000000000..3080cfdd4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/autorun.rb @@ -0,0 +1,3 @@ +require 'rspec/core' +# Ensure the default config is loaded +RSpec::Core::Runner.autorun diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core.rb new file mode 100644 index 0000000000..2f10014b2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core.rb @@ -0,0 +1,186 @@ +# rubocop:disable Style/GlobalVars +$_rspec_core_load_started_at = Time.now +# rubocop:enable Style/GlobalVars + +require "rspec/support" +RSpec::Support.require_rspec_support "caller_filter" + +RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative f } + +%w[ + version + warnings + + set + flat_map + filter_manager + dsl + notifications + reporter + + hooks + memoized_helpers + metadata + metadata_filter + pending + formatters + ordering + + world + configuration + option_parser + configuration_options + runner + invocations + example + shared_example_group + example_group +].each { |name| RSpec::Support.require_rspec_core name } + +# Namespace for all core RSpec code. +module RSpec + autoload :SharedContext, 'rspec/core/shared_context' + + extend RSpec::Core::Warnings + + class << self + # Setters for shared global objects + # @api private + attr_writer :configuration, :world + end + + # Used to ensure examples get reloaded and user configuration gets reset to + # defaults between multiple runs in the same process. + # + # Users must invoke this if they want to have the configuration reset when + # they use the runner multiple times within the same process. Users must deal + # themselves with re-configuration of RSpec before run. + def self.reset + RSpec::ExampleGroups.remove_all_constants + @world = nil + @configuration = nil + end + + # Used to ensure examples get reloaded between multiple runs in the same + # process and ensures user configuration is persisted. + # + # Users must invoke this if they want to clear all examples but preserve + # current configuration when they use the runner multiple times within the + # same process. + def self.clear_examples + world.reset + configuration.reset_reporter + configuration.start_time = ::RSpec::Core::Time.now + configuration.reset_filters + end + + # Returns the global [Configuration](RSpec/Core/Configuration) object. While + # you _can_ use this method to access the configuration, the more common + # convention is to use [RSpec.configure](RSpec#configure-class_method). + # + # @example + # RSpec.configuration.drb_port = 1234 + # @see RSpec.configure + # @see Core::Configuration + def self.configuration + @configuration ||= RSpec::Core::Configuration.new + end + + # Yields the global configuration to a block. + # @yield [Configuration] global configuration + # + # @example + # RSpec.configure do |config| + # config.add_formatter 'documentation' + # end + # @see Core::Configuration + def self.configure + yield configuration if block_given? + end + + # The example being executed. + # + # The primary audience for this method is library authors who need access + # to the example currently being executed and also want to support all + # versions of RSpec 2 and 3. + # + # @example + # + # RSpec.configure do |c| + # # context.example is deprecated, but RSpec.current_example is not + # # available until RSpec 3.0. + # fetch_current_example = RSpec.respond_to?(:current_example) ? + # proc { RSpec.current_example } : proc { |context| context.example } + # + # c.before(:example) do + # example = fetch_current_example.call(self) + # + # # ... + # end + # end + # + def self.current_example + RSpec::Support.thread_local_data[:current_example] + end + + # Set the current example being executed. + # @api private + def self.current_example=(example) + RSpec::Support.thread_local_data[:current_example] = example + end + + # @private + # Internal container for global non-configuration data. + def self.world + @world ||= RSpec::Core::World.new + end + + # Namespace for the rspec-core code. + module Core + autoload :ExampleStatusPersister, "rspec/core/example_status_persister" + autoload :Profiler, "rspec/core/profiler" + autoload :DidYouMean, "rspec/core/did_you_mean" + + # @private + # This avoids issues with reporting time caused by examples that + # change the value/meaning of Time.now without properly restoring + # it. + class Time + class << self + define_method(:now, &::Time.method(:now)) + end + end + + # @private path to executable file. + def self.path_to_executable + @path_to_executable ||= File.expand_path('../../../exe/rspec', __FILE__) + end + end + + # @private + MODULES_TO_AUTOLOAD = { + :Matchers => "rspec/expectations", + :Expectations => "rspec/expectations", + :Mocks => "rspec/mocks" + } + + # @private + def self.const_missing(name) + # Load rspec-expectations when RSpec::Matchers is referenced. This allows + # people to define custom matchers (using `RSpec::Matchers.define`) before + # rspec-core has loaded rspec-expectations (since it delays the loading of + # it to allow users to configure a different assertion/expectation + # framework). `autoload` can't be used since it works with ruby's built-in + # require (e.g. for files that are available relative to a load path dir), + # but not with rubygems' extended require. + # + # As of rspec 2.14.1, we no longer require `rspec/mocks` and + # `rspec/expectations` when `rspec` is required, so we want + # to make them available as an autoload. + require MODULES_TO_AUTOLOAD.fetch(name) { return super } + ::RSpec.const_get(name) + end + + Core::DSL.expose_globally! + Core::SharedExampleGroup::TopLevelDSL.expose_globally! +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/backtrace_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/backtrace_formatter.rb new file mode 100644 index 0000000000..e0bee52a99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/backtrace_formatter.rb @@ -0,0 +1,65 @@ +module RSpec + module Core + # @private + class BacktraceFormatter + # @private + attr_accessor :exclusion_patterns, :inclusion_patterns + + def initialize + @full_backtrace = false + + patterns = %w[ /lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle: ] + patterns << "org/jruby/" if RUBY_PLATFORM == 'java' + patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) } + + @exclusion_patterns = [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *patterns)] + @inclusion_patterns = [] + + return unless matches?(@exclusion_patterns, File.join(Dir.getwd, "lib", "foo.rb:13")) + inclusion_patterns << Regexp.new(Dir.getwd) + end + + attr_writer :full_backtrace + + def full_backtrace? + @full_backtrace || exclusion_patterns.empty? + end + + def filter_gem(gem_name) + sep = File::SEPARATOR + exclusion_patterns << /#{sep}#{gem_name}(-[^#{sep}]+)?#{sep}/ + end + + def format_backtrace(backtrace, options={}) + return [] unless backtrace + return backtrace if options[:full_backtrace] || backtrace.empty? + + backtrace.map { |l| backtrace_line(l) }.compact. + tap do |filtered| + if filtered.empty? + filtered.concat backtrace + filtered << "" + filtered << " Showing full backtrace because every line was filtered out." + filtered << " See docs for RSpec::Configuration#backtrace_exclusion_patterns and" + filtered << " RSpec::Configuration#backtrace_inclusion_patterns for more information." + end + end + end + + def backtrace_line(line) + Metadata.relative_path(line) unless exclude?(line) + end + + def exclude?(line) + return false if @full_backtrace + matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line) + end + + private + + def matches?(patterns, line) + patterns.any? { |p| line =~ p } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/coordinator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/coordinator.rb new file mode 100644 index 0000000000..c4d304bce4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/coordinator.rb @@ -0,0 +1,62 @@ +RSpec::Support.require_rspec_core "bisect/shell_command" +RSpec::Support.require_rspec_core "bisect/example_minimizer" +RSpec::Support.require_rspec_core "bisect/utilities" +RSpec::Support.require_rspec_core "formatters/bisect_progress_formatter" + +module RSpec + module Core + module Bisect + # The main entry point into the bisect logic. Coordinates among: + # - Bisect::ShellCommand: Generates shell commands to run spec subsets + # - Bisect::ExampleMinimizer: Contains the core bisect logic. + # - A bisect runner: runs a set of examples and returns the results. + # - A bisect formatter: provides progress updates to the user. + # @private + class Coordinator + def self.bisect_with(spec_runner, original_cli_args, formatter) + new(spec_runner, original_cli_args, formatter).bisect + end + + def initialize(spec_runner, original_cli_args, formatter) + @spec_runner = spec_runner + @shell_command = ShellCommand.new(original_cli_args) + @notifier = Bisect::Notifier.new(formatter) + end + + def bisect + repro = start_bisect_runner do |runner| + minimizer = ExampleMinimizer.new(@shell_command, runner, @notifier) + + gracefully_abort_on_sigint(minimizer) + minimizer.find_minimal_repro + minimizer.repro_command_for_currently_needed_ids + end + + @notifier.publish(:bisect_repro_command, :repro => repro) + + true + rescue BisectFailedError => e + @notifier.publish(:bisect_failed, :failure_explanation => e.message) + false + ensure + @notifier.publish(:close) + end + + private + + def start_bisect_runner(&block) + klass = @spec_runner.configuration.bisect_runner_class + klass.start(@shell_command, @spec_runner, &block) + end + + def gracefully_abort_on_sigint(minimizer) + trap('INT') do + repro = minimizer.repro_command_for_currently_needed_ids + @notifier.publish(:bisect_aborted, :repro => repro) + exit(1) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/example_minimizer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/example_minimizer.rb new file mode 100644 index 0000000000..7ee5a4f8bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/example_minimizer.rb @@ -0,0 +1,173 @@ +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Bisect + # @private + # Contains the core bisect logic. Searches for examples we can ignore by + # repeatedly running different subsets of the suite. + class ExampleMinimizer + attr_reader :shell_command, :runner, :all_example_ids, :failed_example_ids + attr_accessor :remaining_ids + + def initialize(shell_command, runner, notifier) + @shell_command = shell_command + @runner = runner + @notifier = notifier + end + + def find_minimal_repro + prep + + _, duration = track_duration do + bisect(non_failing_example_ids) + end + + notify(:bisect_complete, :duration => duration, + :original_non_failing_count => non_failing_example_ids.size, + :remaining_count => remaining_ids.size) + + remaining_ids + failed_example_ids + end + + def bisect(candidate_ids) + notify(:bisect_dependency_check_started) + if get_expected_failures_for?([]) + notify(:bisect_dependency_check_failed) + self.remaining_ids = [] + return + end + notify(:bisect_dependency_check_passed) + + bisect_over(candidate_ids) + end + + def bisect_over(candidate_ids) + return if candidate_ids.one? + + notify( + :bisect_round_started, + :candidate_range => example_range(candidate_ids), + :candidates_count => candidate_ids.size + ) + + slice_size = (candidate_ids.length / 2.0).ceil + lhs, rhs = candidate_ids.each_slice(slice_size).to_a + + ids_to_ignore, duration = track_duration do + [lhs, rhs].find do |ids| + get_expected_failures_for?(remaining_ids - ids) + end + end + + if ids_to_ignore + self.remaining_ids -= ids_to_ignore + notify( + :bisect_round_ignoring_ids, + :ids_to_ignore => ids_to_ignore, + :ignore_range => example_range(ids_to_ignore), + :remaining_ids => remaining_ids, + :duration => duration + ) + bisect_over(candidate_ids - ids_to_ignore) + else + notify( + :bisect_round_detected_multiple_culprits, + :duration => duration + ) + bisect_over(lhs) + bisect_over(rhs) + end + end + + def currently_needed_ids + remaining_ids + failed_example_ids + end + + def repro_command_for_currently_needed_ids + return shell_command.repro_command_from(currently_needed_ids) if remaining_ids + "(Not yet enough information to provide any repro command)" + end + + # @private + # Convenience class for describing a subset of the candidate examples + ExampleRange = Struct.new(:start, :finish) do + def description + if start == finish + "example #{start}" + else + "examples #{start}-#{finish}" + end + end + end + + private + + def example_range(ids) + ExampleRange.new( + non_failing_example_ids.find_index(ids.first) + 1, + non_failing_example_ids.find_index(ids.last) + 1 + ) + end + + def prep + notify(:bisect_starting, :original_cli_args => shell_command.original_cli_args, + :bisect_runner => runner.class.name) + + _, duration = track_duration do + original_results = runner.original_results + @all_example_ids = original_results.all_example_ids + @failed_example_ids = original_results.failed_example_ids + @remaining_ids = non_failing_example_ids + end + + if @failed_example_ids.empty? + raise BisectFailedError, "\n\nNo failures found. Bisect only works " \ + "in the presence of one or more failing examples." + else + notify(:bisect_original_run_complete, :failed_example_ids => failed_example_ids, + :non_failing_example_ids => non_failing_example_ids, + :duration => duration) + end + end + + def non_failing_example_ids + @non_failing_example_ids ||= all_example_ids - failed_example_ids + end + + def get_expected_failures_for?(ids) + ids_to_run = ids + failed_example_ids + notify( + :bisect_individual_run_start, + :command => shell_command.repro_command_from(ids_to_run), + :ids_to_run => ids_to_run + ) + + results, duration = track_duration { runner.run(ids_to_run) } + notify(:bisect_individual_run_complete, :duration => duration, :results => results) + + abort_if_ordering_inconsistent(results) + (failed_example_ids & results.failed_example_ids) == failed_example_ids + end + + def track_duration + start = ::RSpec::Core::Time.now + [yield, ::RSpec::Core::Time.now - start] + end + + def abort_if_ordering_inconsistent(results) + expected_order = all_example_ids & results.all_example_ids + return if expected_order == results.all_example_ids + + raise BisectFailedError, "\n\nThe example ordering is inconsistent. " \ + "`--bisect` relies upon consistent ordering (e.g. by passing " \ + "`--seed` if you're using random ordering) to work properly." + end + + def notify(*args) + @notifier.publish(*args) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/fork_runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/fork_runner.rb new file mode 100644 index 0000000000..4641a14429 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/fork_runner.rb @@ -0,0 +1,138 @@ +require 'stringio' +RSpec::Support.require_rspec_core "formatters/base_bisect_formatter" +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Bisect + # A Bisect runner that runs requested subsets of the suite by forking + # sub-processes. The main process bootstraps RSpec and the application + # environment (including preloading files specified via `--require`) so + # that the individual spec runs do not have to re-pay that cost. Each + # spec run happens in a forked process, ensuring that the spec files are + # not loaded in the main process. + # + # For most projects, bisections that use `ForkRunner` instead of + # `ShellRunner` will finish significantly faster, because the `ShellRunner` + # pays the cost of booting RSpec and the app environment on _every_ run of + # a subset. In contrast, `ForkRunner` pays that cost only once. + # + # However, not all projects can use `ForkRunner`. Obviously, on platforms + # that do not support forking (e.g. Windows), it cannot be used. In addition, + # it can cause problems for some projects that put side-effectful spec + # bootstrapping logic that should run on every spec run directly at the top + # level in a file loaded by `--require`, rather than in a `before(:suite)` + # hook. For example, consider a project that relies on some top-level logic + # in `spec_helper` to boot a Redis server for the test suite, intending the + # Redis bootstrapping to happen on every spec run. With `ShellRunner`, the + # bootstrapping logic will happen for each run of any subset of the suite, + # but for `ForkRunner`, such logic will only get run once, when the + # `RunDispatcher` boots the application environment. This might cause + # problems. The solution is for users to move the bootstrapping logic into + # a `before(:suite)` hook, or use the slower `ShellRunner`. + # + # @private + class ForkRunner + def self.start(shell_command, spec_runner) + instance = new(shell_command, spec_runner) + yield instance + ensure + instance.shutdown + end + + def self.name + :fork + end + + def initialize(shell_command, spec_runner) + @shell_command = shell_command + @channel = Channel.new + @run_dispatcher = RunDispatcher.new(spec_runner, @channel) + end + + def run(locations) + run_descriptor = ExampleSetDescriptor.new(locations, original_results.failed_example_ids) + dispatch_run(run_descriptor) + end + + def original_results + @original_results ||= dispatch_run(ExampleSetDescriptor.new( + @shell_command.original_locations, [])) + end + + def shutdown + @channel.close + end + + private + + def dispatch_run(run_descriptor) + @run_dispatcher.dispatch_specs(run_descriptor) + @channel.receive.tap do |result| + if result.is_a?(String) + raise BisectFailedError.for_failed_spec_run(result) + end + end + end + + # @private + class RunDispatcher + def initialize(runner, channel) + @runner = runner + @channel = channel + + @spec_output = StringIO.new + + runner.configuration.tap do |c| + c.reset_reporter + c.output_stream = @spec_output + c.error_stream = @spec_output + end + end + + def dispatch_specs(run_descriptor) + pid = fork { run_specs(run_descriptor) } + # We don't use Process.waitpid here as it was causing bisects to + # block due to the file descriptor limit on OSX / Linux. We need + # to detach the process to avoid having zombie processes + # consuming slots in the kernel process table during bisect runs. + Process.detach(pid) + end + + private + + def run_specs(run_descriptor) + $stdout = $stderr = @spec_output + formatter = CaptureFormatter.new(run_descriptor.failed_example_ids) + + @runner.configuration.tap do |c| + c.files_or_directories_to_run = run_descriptor.all_example_ids + c.formatter = formatter + c.load_spec_files + end + + # `announce_filters` has the side effect of implementing the logic + # that honors `config.run_all_when_everything_filtered` so we need + # to call it here. When we remove `run_all_when_everything_filtered` + # (slated for RSpec 4), we can remove this call to `announce_filters`. + @runner.world.announce_filters + + @runner.run_specs(@runner.world.ordered_example_groups) + latest_run_results = formatter.results + + if latest_run_results.nil? || latest_run_results.all_example_ids.empty? + @channel.send(@spec_output.string) + else + @channel.send(latest_run_results) + end + end + end + + class CaptureFormatter < Formatters::BaseBisectFormatter + attr_accessor :results + alias_method :notify_results, :results= + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/server.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/server.rb new file mode 100644 index 0000000000..73f02998cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/server.rb @@ -0,0 +1,61 @@ +require 'drb/drb' +require 'drb/acl' +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + # @private + module Bisect + # @private + # A DRb server that receives run results from a separate RSpec process + # started by the bisect process. + class Server + def self.run + server = new + server.start + yield server + ensure + server.stop + end + + def capture_run_results(files_or_directories_to_run=[], expected_failures=[]) + self.expected_failures = expected_failures + self.files_or_directories_to_run = files_or_directories_to_run + self.latest_run_results = nil + run_output = yield + + if latest_run_results.nil? || latest_run_results.all_example_ids.empty? + raise BisectFailedError.for_failed_spec_run(run_output) + end + + latest_run_results + end + + def start + # Only allow remote DRb requests from this machine. + DRb.install_acl ACL.new(%w[ deny all allow localhost allow 127.0.0.1 allow ::1 ]) + + # We pass `nil` as the first arg to allow it to pick a DRb port. + @drb = DRb.start_service(nil, self) + end + + def stop + @drb.stop_service + end + + def drb_port + @drb_port ||= Integer(@drb.uri[/\d+$/]) + end + + # Fetched via DRb by the BisectDRbFormatter to determine when to abort. + attr_accessor :expected_failures + + # Set via DRb by the BisectDRbFormatter with the results of the run. + attr_accessor :latest_run_results + + # Fetched via DRb to tell clients which files to run + attr_accessor :files_or_directories_to_run + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_command.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_command.rb new file mode 100644 index 0000000000..b68f8b16eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_command.rb @@ -0,0 +1,126 @@ +RSpec::Support.require_rspec_core "shell_escape" +require 'shellwords' + +module RSpec + module Core + module Bisect + # Provides an API to generate shell commands to run the suite for a + # set of locations, using the given bisect server to capture the results. + # @private + class ShellCommand + attr_reader :original_cli_args + + def initialize(original_cli_args) + @original_cli_args = original_cli_args.reject { |arg| arg.start_with?("--bisect") } + end + + def command_for(locations, server) + parts = [] + + parts << RUBY << load_path + parts << open3_safe_escape(RSpec::Core.path_to_executable) + + parts << "--format" << "bisect-drb" + parts << "--drb-port" << server.drb_port + + parts.concat(reusable_cli_options) + parts.concat(locations.map { |l| open3_safe_escape(l) }) + + parts.join(" ") + end + + def repro_command_from(locations) + parts = [] + + parts.concat environment_repro_parts + parts << "rspec" + parts.concat Formatters::Helpers.organize_ids(locations) + parts.concat original_cli_args_without_locations + + parts.join(" ") + end + + def original_locations + parsed_original_cli_options.fetch(:files_or_directories_to_run) + end + + def bisect_environment_hash + if ENV.key?('SPEC_OPTS') + { 'SPEC_OPTS' => spec_opts_without_bisect } + else + {} + end + end + + def spec_opts_without_bisect + Shellwords.join( + Shellwords.split(ENV.fetch('SPEC_OPTS', '')).reject do |arg| + arg =~ /^--bisect/ + end + ) + end + + private + + include RSpec::Core::ShellEscape + # On JRuby, Open3.popen3 does not handle shellescaped args properly: + # https://github.com/jruby/jruby/issues/2767 + if RSpec::Support::Ruby.jruby? + # :nocov: + alias open3_safe_escape quote + # :nocov: + else + alias open3_safe_escape escape + end + + def environment_repro_parts + bisect_environment_hash.map do |k, v| + %Q(#{k}="#{v}") + end + end + + def reusable_cli_options + @reusable_cli_options ||= begin + opts = original_cli_args_without_locations + + if (port = parsed_original_cli_options[:drb_port]) + opts -= %W[ --drb-port #{port} ] + end + + parsed_original_cli_options.fetch(:formatters) { [] }.each do |(name, out)| + opts -= %W[ --format #{name} -f -f#{name} ] + opts -= %W[ --out #{out} -o -o#{out} ] + end + + opts + end + end + + def original_cli_args_without_locations + @original_cli_args_without_locations ||= begin + files_or_dirs = parsed_original_cli_options.fetch(:files_or_directories_to_run) + @original_cli_args - files_or_dirs + end + end + + def parsed_original_cli_options + @parsed_original_cli_options ||= Parser.parse(@original_cli_args) + end + + def load_path + @load_path ||= "-I#{$LOAD_PATH.map { |p| open3_safe_escape(p) }.join(':')}" + end + + # Path to the currently running Ruby executable, borrowed from Rake: + # https://github.com/ruby/rake/blob/v10.4.2/lib/rake/file_utils.rb#L8-L12 + # Note that we skip `ENV['RUBY']` because we don't have to deal with running + # RSpec from within a MRI source repository: + # https://github.com/ruby/rake/commit/968682759b3b65e42748cd2befb2ff3e982272d9 + RUBY = File.join( + RbConfig::CONFIG['bindir'], + RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']). + sub(/.*\s.*/m, '"\&"') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_runner.rb new file mode 100644 index 0000000000..34afb1926d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/shell_runner.rb @@ -0,0 +1,73 @@ +require 'open3' +RSpec::Support.require_rspec_core "bisect/server" + +module RSpec + module Core + module Bisect + # Provides an API to run the suite for a set of locations, using + # the given bisect server to capture the results. + # + # Sets of specs are run by shelling out. + # @private + class ShellRunner + def self.start(shell_command, _spec_runner) + Server.run do |server| + yield new(server, shell_command) + end + end + + def self.name + :shell + end + + def initialize(server, shell_command) + @server = server + @shell_command = shell_command + end + + def run(locations) + run_locations(locations, original_results.failed_example_ids) + end + + def original_results + @original_results ||= run_locations(@shell_command.original_locations) + end + + private + + def run_locations(*capture_args) + @server.capture_run_results(*capture_args) do + run_command @shell_command.command_for([], @server) + end + end + + # `Open3.capture2e` does not work on JRuby: + # https://github.com/jruby/jruby/issues/2766 + if Open3.respond_to?(:capture2e) && !RSpec::Support::Ruby.jruby? + def run_command(cmd) + Open3.capture2e(@shell_command.bisect_environment_hash, cmd).first + end + else # for 1.8.7 + # :nocov: + def run_command(cmd) + out = err = nil + + original_spec_opts = ENV['SPEC_OPTS'] + ENV['SPEC_OPTS'] = @shell_command.spec_opts_without_bisect + + Open3.popen3(cmd) do |_, stdout, stderr| + # Reading the streams blocks until the process is complete + out = stdout.read + err = stderr.read + end + + "Stdout:\n#{out}\n\nStderr:\n#{err}" + ensure + ENV['SPEC_OPTS'] = original_spec_opts + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/utilities.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/utilities.rb new file mode 100644 index 0000000000..ff031e6405 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/bisect/utilities.rb @@ -0,0 +1,58 @@ +module RSpec + module Core + module Bisect + # @private + ExampleSetDescriptor = Struct.new(:all_example_ids, :failed_example_ids) + + # @private + class BisectFailedError < StandardError + def self.for_failed_spec_run(spec_output) + new("Failed to get results from the spec run. Spec run output:\n\n" + + spec_output) + end + end + + # Wraps a `formatter` providing a simple means to notify it in place + # of an `RSpec::Core::Reporter`, without involving configuration in + # any way. + # @private + class Notifier + def initialize(formatter) + @formatter = formatter + end + + def publish(event, *args) + return unless @formatter.respond_to?(event) + notification = Notifications::CustomNotification.for(*args) + @formatter.__send__(event, notification) + end + end + + # Wraps a pipe to support sending objects between a child and + # parent process. + # @private + class Channel + def initialize + @read_io, @write_io = IO.pipe + end + + def send(message) + packet = Marshal.dump(message) + @write_io.write("#{packet.bytesize}\n#{packet}") + end + + # rubocop:disable Security/MarshalLoad + def receive + packet_size = Integer(@read_io.gets) + Marshal.load(@read_io.read(packet_size)) + end + # rubocop:enable Security/MarshalLoad + + def close + @read_io.close + @write_io.close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration.rb new file mode 100644 index 0000000000..bb38a8de4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration.rb @@ -0,0 +1,2372 @@ +RSpec::Support.require_rspec_core "backtrace_formatter" +RSpec::Support.require_rspec_core "ruby_project" +RSpec::Support.require_rspec_core "formatters/deprecation_formatter" +RSpec::Support.require_rspec_core "output_wrapper" + +module RSpec + module Core + # rubocop:disable Metrics/ClassLength + + # Stores runtime configuration information. + # + # Configuration options are loaded from multiple files and joined together + # with command-line switches and the `SPEC_OPTS` environment variable. + # + # Precedence order (where later entries overwrite earlier entries on + # conflicts): + # + # * Global (`$XDG_CONFIG_HOME/rspec/options`, or `~/.rspec` if it does + # not exist) + # * Project-specific (`./.rspec`) + # * Local (`./.rspec-local`) + # * Command-line options + # * `SPEC_OPTS` + # + # For example, an option set in the local file will override an option set + # in your global file. + # + # The global, project-specific and local files can all be overridden with a + # separate custom file using the --options command-line parameter. + # + # @example Standard settings + # RSpec.configure do |c| + # c.drb = true + # c.drb_port = 1234 + # c.default_path = 'behavior' + # end + # + # @example Hooks + # RSpec.configure do |c| + # c.before(:suite) { establish_connection } + # c.before(:example) { log_in_as :authorized } + # c.around(:example) { |ex| Database.transaction(&ex) } + # end + # + # @see RSpec.configure + # @see Hooks + class Configuration + include RSpec::Core::Hooks + + # Module that holds `attr_reader` declarations. It's in a separate + # module to allow us to override those methods and use `super`. + # @private + Readers = Module.new + include Readers + + # @private + class MustBeConfiguredBeforeExampleGroupsError < StandardError; end + + # @private + def self.define_reader(name) + Readers.class_eval do + remove_method name if method_defined?(name) + attr_reader name + end + + define_method(name) { value_for(name) { super() } } + end + + # @private + def self.define_alias(name, alias_name) + alias_method alias_name, name + alias_method "#{alias_name}=", "#{name}=" + define_predicate alias_name + end + + # @private + def self.define_predicate(name) + define_method "#{name}?" do + !!send(name) + end + end + + # @private + # + # Invoked by the `add_setting` instance method. Use that method on a + # `Configuration` instance rather than this class method. + def self.add_setting(name, opts={}) + raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default) + attr_writer name + add_read_only_setting name + + Array(opts[:alias_with]).each do |alias_name| + define_alias(name, alias_name) + end + end + + # @private + # + # As `add_setting` but only add the reader. + def self.add_read_only_setting(name, opts={}) + raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default) + define_reader name + define_predicate name + end + + # @macro [attach] add_setting + # @!attribute [rw] $1 + # + # @macro [attach] define_reader + # @!attribute [r] $1 + + # @macro add_setting + # Path to use if no path is provided to the `rspec` command (default: + # `"spec"`). Allows you to just type `rspec` instead of `rspec spec` to + # run all the examples in the `spec` directory. + # + # @note Other scripts invoking `rspec` indirectly will ignore this + # setting. + # @return [String] + add_read_only_setting :default_path + def default_path=(path) + project_source_dirs << path + @default_path = path + end + + # @macro add_setting + # Run examples over DRb (default: `false`). RSpec doesn't supply the DRb + # server, but you can use tools like spork. + # @return [Boolean] + add_setting :drb + + # @macro add_setting + # The drb_port (default: nil). + add_setting :drb_port + + # @macro add_setting + # Default: `$stderr`. + add_setting :error_stream + + # Indicates if the DSL has been exposed off of modules and `main`. + # Default: true + # @return [Boolean] + def expose_dsl_globally? + Core::DSL.exposed_globally? + end + + # Use this to expose the core RSpec DSL via `Module` and the `main` + # object. It will be set automatically but you can override it to + # remove the DSL. + # Default: true + def expose_dsl_globally=(value) + if value + Core::DSL.expose_globally! + Core::SharedExampleGroup::TopLevelDSL.expose_globally! + else + Core::DSL.remove_globally! + Core::SharedExampleGroup::TopLevelDSL.remove_globally! + end + end + + # Determines where deprecation warnings are printed. + # Defaults to `$stderr`. + # @return [IO, String] IO or filename to write to + define_reader :deprecation_stream + + # Determines where deprecation warnings are printed. + # @param value [IO, String] IO to write to or filename to write to + def deprecation_stream=(value) + if @reporter && !value.equal?(@deprecation_stream) + warn "RSpec's reporter has already been initialized with " \ + "#{deprecation_stream.inspect} as the deprecation stream, so your change to "\ + "`deprecation_stream` will be ignored. You should configure it earlier for " \ + "it to take effect, or use the `--deprecation-out` CLI option. " \ + "(Called from #{CallerFilter.first_non_rspec_line})" + else + @deprecation_stream = value + end + end + + # @macro define_reader + # The file path to use for persisting example statuses. Necessary for the + # `--only-failures` and `--next-failure` CLI options. + # + # @overload example_status_persistence_file_path + # @return [String] the file path + # @overload example_status_persistence_file_path=(value) + # @param value [String] the file path + define_reader :example_status_persistence_file_path + + # Sets the file path to use for persisting example statuses. Necessary for the + # `--only-failures` and `--next-failure` CLI options. + def example_status_persistence_file_path=(value) + @example_status_persistence_file_path = value + clear_values_derived_from_example_status_persistence_file_path + end + + # @macro define_reader + # Indicates if the `--only-failures` (or `--next-failure`) flag is being used. + define_reader :only_failures + alias_method :only_failures?, :only_failures + + # @private + def only_failures_but_not_configured? + only_failures? && !example_status_persistence_file_path + end + + # @macro define_reader + # If specified, indicates the number of failures required before cleaning + # up and exit (default: `nil`). Can also be `true` to fail and exit on first + # failure + define_reader :fail_fast + + # @see fail_fast + def fail_fast=(value) + case value + when true, 'true' + @fail_fast = true + when false, 'false', 0 + @fail_fast = false + when nil + @fail_fast = nil + else + @fail_fast = value.to_i + + if value.to_i == 0 + # TODO: in RSpec 4, consider raising an error here. + RSpec.warning "Cannot set `RSpec.configuration.fail_fast`" \ + " to `#{value.inspect}`. Only `true`, `false`, `nil` and integers" \ + " are valid values." + @fail_fast = true + end + end + end + + # @macro add_setting + # Prints the formatter output of your suite without running any + # examples or hooks. + add_setting :dry_run + + # @macro add_setting + # The exit code to return if there are any failures (default: 1). + # @return [Integer] + add_setting :failure_exit_code + + # @macro add_setting + # The exit code to return if there are any errors outside examples (default: failure_exit_code) + # @return [Integer] + add_setting :error_exit_code + + # @macro add_setting + # Whether or not to fail when there are no RSpec examples (default: false). + # @return [Boolean] + add_setting :fail_if_no_examples + + # @macro define_reader + # Indicates files configured to be required. + # @return [Array] + define_reader :requires + + # @macro define_reader + # Returns dirs that have been prepended to the load path by the `-I` + # command line option. + # @return [Array] + define_reader :libs + + # @macro add_setting + # Determines where RSpec will send its output. + # Default: `$stdout`. + # @return [IO, String] + define_reader :output_stream + + # Set the output stream for reporter. + # @attr value [IO, String] IO to write to or filename to write to, defaults to $stdout + def output_stream=(value) + if @reporter && !value.equal?(@output_stream) + warn "RSpec's reporter has already been initialized with " \ + "#{output_stream.inspect} as the output stream, so your change to "\ + "`output_stream` will be ignored. You should configure it earlier for " \ + "it to take effect. (Called from #{CallerFilter.first_non_rspec_line})" + else + @output_stream = value + output_wrapper.output = @output_stream + end + end + + # @macro define_reader + # Load files matching this pattern (default: `'**{,/*/**}/*_spec.rb'`). + # @return [String] + define_reader :pattern + + # Set pattern to match files to load. + # @attr value [String] the filename pattern to filter spec files by + def pattern=(value) + update_pattern_attr :pattern, value + end + + # @macro define_reader + # Exclude files matching this pattern. + # @return [String] + define_reader :exclude_pattern + + # Set pattern to match files to exclude. + # @attr value [String] the filename pattern to exclude spec files by + def exclude_pattern=(value) + update_pattern_attr :exclude_pattern, value + end + + # @macro add_setting + # Specifies which directories contain the source code for your project. + # When a failure occurs, RSpec looks through the backtrace to find a + # a line of source to print. It first looks for a line coming from + # one of the project source directories so that, for example, it prints + # the expectation or assertion call rather than the source code from + # the expectation or assertion framework. + # @return [Array] + add_setting :project_source_dirs + + # @macro add_setting + # Report the times for the slowest examples (default: `false`). + # Use this to specify the number of examples to include in the profile. + # @return [Boolean] + attr_writer :profile_examples + define_predicate :profile_examples + + # @macro add_setting + # Run all examples if none match the configured filters + # (default: `false`). + # @deprecated Use {#filter_run_when_matching} instead for the specific + # filters that you want to be ignored if none match. + add_setting :run_all_when_everything_filtered + + # @macro add_setting + # Color to use to indicate success. Defaults to `:green` but can be set + # to one of the following: `[:black, :white, :red, :green, :yellow, + # :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :success_color + + # @macro add_setting + # Color to use to print pending examples. Defaults to `:yellow` but can + # be set to one of the following: `[:black, :white, :red, :green, + # :yellow, :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :pending_color + + # @macro add_setting + # Color to use to indicate failure. Defaults to `:red` but can be set to + # one of the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :failure_color + + # @macro add_setting + # The default output color. Defaults to `:white` but can be set to one of + # the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :default_color + + # @macro add_setting + # Color used when a pending example is fixed. Defaults to `:blue` but can + # be set to one of the following: `[:black, :white, :red, :green, + # :yellow, :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :fixed_color + + # @macro add_setting + # Color used to print details. Defaults to `:cyan` but can be set to one + # of the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :detail_color + + # @macro add_setting + # Don't print filter info i.e. "Run options: include {:focus=>true}" + # (default `false`). + # return [Boolean] + add_setting :silence_filter_announcements + + # @deprecated This config option was added in RSpec 2 to pave the way + # for this being the default behavior in RSpec 3. Now this option is + # a no-op. + def treat_symbols_as_metadata_keys_with_true_values=(_value) + RSpec.deprecate( + "RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values=", + :message => "RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values= " \ + "is deprecated, it is now set to true as default and " \ + "setting it to false has no effect." + ) + end + + # @macro define_reader + # Configures how RSpec treats metadata passed as part of a shared example + # group definition. For example, given this shared example group definition: + # + # RSpec.shared_context "uses DB", :db => true do + # around(:example) do |ex| + # MyORM.transaction(:rollback => true, &ex) + # end + # end + # + # ...there are two ways RSpec can treat the `:db => true` metadata, each + # of which has a corresponding config option: + # + # 1. `:trigger_inclusion`: this shared context will be implicitly included + # in any groups (or examples) that have `:db => true` metadata. + # 2. `:apply_to_host_groups`: the metadata will be inherited by the metadata + # hash of all host groups and examples. + # + # `:trigger_inclusion` is the legacy behavior from before RSpec 3.5 but should + # be considered deprecated. Instead, you can explicitly include a group with + # `include_context`: + # + # RSpec.describe "My model" do + # include_context "uses DB" + # end + # + # ...or you can configure RSpec to include the context based on matching metadata + # using an API that mirrors configured module inclusion: + # + # RSpec.configure do |rspec| + # rspec.include_context "uses DB", :db => true + # end + # + # `:apply_to_host_groups` is a new feature of RSpec 3.5 and will be the only + # supported behavior in RSpec 4. + # + # @overload shared_context_metadata_behavior + # @return [:trigger_inclusion, :apply_to_host_groups] the configured behavior + # @overload shared_context_metadata_behavior=(value) + # @param value [:trigger_inclusion, :apply_to_host_groups] sets the configured behavior + define_reader :shared_context_metadata_behavior + # @see shared_context_metadata_behavior + def shared_context_metadata_behavior=(value) + case value + when :trigger_inclusion, :apply_to_host_groups + @shared_context_metadata_behavior = value + else + raise ArgumentError, "Cannot set `RSpec.configuration." \ + "shared_context_metadata_behavior` to `#{value.inspect}`. Only " \ + "`:trigger_inclusion` and `:apply_to_host_groups` are valid values." + end + end + + # Record the start time of the spec suite to measure load time. + # return [Time] + add_setting :start_time + + # @macro add_setting + # Use threadsafe options where available. + # Currently this will place a mutex around memoized values such as let blocks. + # return [Boolean] + add_setting :threadsafe + + # @macro add_setting + # Maximum count of failed source lines to display in the failure reports. + # (default `10`). + # return [Integer] + add_setting :max_displayed_failure_line_count + + # Determines which bisect runner implementation gets used to run subsets + # of the suite during a bisection. Your choices are: + # + # - `:shell`: Performs a spec run by shelling out, booting RSpec and your + # application environment each time. This runner is the most widely + # compatible runner, but is not as fast. On platforms that do not + # support forking, this is the default. + # - `:fork`: Pre-boots RSpec and your application environment in a parent + # process, and then forks a child process for each spec run. This runner + # tends to be significantly faster than the `:shell` runner but cannot + # be used in some situations. On platforms that support forking, this + # is the default. If you use this runner, you should ensure that all + # of your one-time setup logic goes in a `before(:suite)` hook instead + # of getting run at the top-level of a file loaded by `--require`. + # + # @note This option will only be used by `--bisect` if you set it in a file + # loaded via `--require`. + # + # @return [Symbol] + attr_reader :bisect_runner + def bisect_runner=(value) + if @bisect_runner_class && value != @bisect_runner + raise "`config.bisect_runner = #{value.inspect}` can no longer take " \ + "effect as the #{@bisect_runner.inspect} bisect runnner is already " \ + "in use. This config setting must be set in a file loaded by a " \ + "`--require` option (passed at the CLI or in a `.rspec` file) for " \ + "it to have any effect." + end + + @bisect_runner = value + end + + # @private + # @deprecated Use {#color_mode} = :on, instead of {#color} with {#tty} + add_setting :tty + # @private + attr_writer :files_to_run + # @private + attr_accessor :filter_manager, :world + # @private + attr_accessor :static_config_filter_manager + # @private + attr_reader :backtrace_formatter, :ordering_manager, :loaded_spec_files + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + + # Build an object to store runtime configuration options and set defaults + def initialize + # rubocop:disable Style/GlobalVars + @start_time = $_rspec_core_load_started_at || ::RSpec::Core::Time.now + # rubocop:enable Style/GlobalVars + @expectation_frameworks = [] + @include_modules = FilterableItemRepository::QueryOptimized.new(:any?) + @extend_modules = FilterableItemRepository::QueryOptimized.new(:any?) + @prepend_modules = FilterableItemRepository::QueryOptimized.new(:any?) + + @bisect_runner = RSpec::Support::RubyFeatures.fork_supported? ? :fork : :shell + @bisect_runner_class = nil + + @before_suite_hooks = [] + @after_suite_hooks = [] + + @mock_framework = nil + @files_or_directories_to_run = [] + @loaded_spec_files = Set.new + @color = false + @color_mode = :automatic + @pattern = '**{,/*/**}/*_spec.rb' + @exclude_pattern = '' + @failure_exit_code = 1 + @error_exit_code = nil # so it can be overridden by failure exit code + @fail_if_no_examples = false + @spec_files_loaded = false + + @backtrace_formatter = BacktraceFormatter.new + + @default_path = 'spec' + @project_source_dirs = %w[ spec lib app ] + @deprecation_stream = $stderr + @output_stream = $stdout + @reporter = nil + @reporter_buffer = nil + @filter_manager = FilterManager.new + @static_config_filter_manager = FilterManager.new + @ordering_manager = Ordering::ConfigurationManager.new + @preferred_options = {} + @failure_color = :red + @success_color = :green + @pending_color = :yellow + @default_color = :white + @fixed_color = :blue + @detail_color = :cyan + @profile_examples = false + @requires = [] + @libs = [] + @derived_metadata_blocks = FilterableItemRepository::QueryOptimized.new(:any?) + @threadsafe = true + @max_displayed_failure_line_count = 10 + @world = World::Null + @shared_context_metadata_behavior = :trigger_inclusion + + define_built_in_hooks + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + # @private + # + # Used to set higher priority option values from the command line. + def force(hash) + ordering_manager.force(hash) + @preferred_options.merge!(hash) + + return unless hash.key?(:example_status_persistence_file_path) + clear_values_derived_from_example_status_persistence_file_path + end + + # @private + def reset + @spec_files_loaded = false + reset_reporter + end + + # @private + def reset_reporter + @reporter = nil + @formatter_loader = nil + @output_wrapper = nil + end + + # @private + def reset_filters + self.filter_manager = FilterManager.new + filter_manager.include_only( + Metadata.deep_hash_dup(static_config_filter_manager.inclusions.rules) + ) + filter_manager.exclude_only( + Metadata.deep_hash_dup(static_config_filter_manager.exclusions.rules) + ) + end + + # @overload add_setting(name) + # @overload add_setting(name, opts) + # @option opts [Symbol] :default + # + # Set a default value for the generated getter and predicate methods: + # + # add_setting(:foo, :default => "default value") + # + # @option opts [Symbol] :alias_with + # + # Use `:alias_with` to alias the setter, getter, and predicate to + # another name, or names: + # + # add_setting(:foo, :alias_with => :bar) + # add_setting(:foo, :alias_with => [:bar, :baz]) + # + # Adds a custom setting to the RSpec.configuration object. + # + # RSpec.configuration.add_setting :foo + # + # Used internally and by extension frameworks like rspec-rails, so they + # can add config settings that are domain specific. For example: + # + # RSpec.configure do |c| + # c.add_setting :use_transactional_fixtures, + # :default => true, + # :alias_with => :use_transactional_examples + # end + # + # `add_setting` creates three methods on the configuration object, a + # setter, a getter, and a predicate: + # + # RSpec.configuration.foo=(value) + # RSpec.configuration.foo + # RSpec.configuration.foo? # Returns true if foo returns anything but nil or false. + def add_setting(name, opts={}) + default = opts.delete(:default) + (class << self; self; end).class_exec do + add_setting(name, opts) + end + __send__("#{name}=", default) if default + end + + # Returns the configured mock framework adapter module. + # @return [Symbol] + def mock_framework + if @mock_framework.nil? + begin + mock_with :rspec + rescue LoadError + mock_with :nothing + end + end + @mock_framework + end + + # Delegates to mock_framework=(framework). + def mock_framework=(framework) + mock_with framework + end + + # Regexps used to exclude lines from backtraces. + # + # Excludes lines from ruby (and jruby) source, installed gems, anything + # in any "bin" directory, and any of the RSpec libs (outside gem + # installs) by default. + # + # You can modify the list via the getter, or replace it with the setter. + # + # To override this behaviour and display a full backtrace, use + # `--backtrace` on the command line, in a `.rspec` file, or in the + # `rspec_options` attribute of RSpec's rake task. + # @return [Array] + def backtrace_exclusion_patterns + @backtrace_formatter.exclusion_patterns + end + + # Set regular expressions used to exclude lines in backtrace. + # @param patterns [Array] set backtrace_formatter exlusion_patterns + def backtrace_exclusion_patterns=(patterns) + @backtrace_formatter.exclusion_patterns = patterns + end + + # Regexps used to include lines in backtraces. + # + # Defaults to [Regexp.new Dir.getwd]. + # + # Lines that match an exclusion _and_ an inclusion pattern + # will be included. + # + # You can modify the list via the getter, or replace it with the setter. + # @return [Array] + def backtrace_inclusion_patterns + @backtrace_formatter.inclusion_patterns + end + + # Set regular expressions used to include lines in backtrace. + # @attr patterns [Array] set backtrace_formatter inclusion_patterns + def backtrace_inclusion_patterns=(patterns) + @backtrace_formatter.inclusion_patterns = patterns + end + + # Adds {#backtrace_exclusion_patterns} that will filter lines from + # the named gems from backtraces. + # + # @param gem_names [Array] Names of the gems to filter + # + # @example + # RSpec.configure do |config| + # config.filter_gems_from_backtrace "rack", "rake" + # end + # + # @note The patterns this adds will match the named gems in their common + # locations (e.g. system gems, vendored with bundler, installed as a + # :git dependency with bundler, etc) but is not guaranteed to work for + # all possible gem locations. For example, if you have the gem source + # in a directory with a completely unrelated name, and use bundler's + # :path option, this will not filter it. + def filter_gems_from_backtrace(*gem_names) + gem_names.each do |name| + @backtrace_formatter.filter_gem(name) + end + end + + # @private + MOCKING_ADAPTERS = { + :rspec => :RSpec, + :flexmock => :Flexmock, + :rr => :RR, + :mocha => :Mocha, + :nothing => :Null + } + + # Sets the mock framework adapter module. + # + # `framework` can be a Symbol or a Module. + # + # Given any of `:rspec`, `:mocha`, `:flexmock`, or `:rr`, configures the + # named framework. + # + # Given `:nothing`, configures no framework. Use this if you don't use + # any mocking framework to save a little bit of overhead. + # + # Given a Module, includes that module in every example group. The module + # should adhere to RSpec's mock framework adapter API: + # + # setup_mocks_for_rspec + # - called before each example + # + # verify_mocks_for_rspec + # - called after each example if the example hasn't yet failed. + # Framework should raise an exception when expectations fail + # + # teardown_mocks_for_rspec + # - called after verify_mocks_for_rspec (even if there are errors) + # + # If the module responds to `configuration` and `mock_with` receives a + # block, it will yield the configuration object to the block e.g. + # + # config.mock_with OtherMockFrameworkAdapter do |mod_config| + # mod_config.custom_setting = true + # end + def mock_with(framework) + framework_module = + if framework.is_a?(Module) + framework + else + const_name = MOCKING_ADAPTERS.fetch(framework) do + raise ArgumentError, + "Unknown mocking framework: #{framework.inspect}. " \ + "Pass a module or one of #{MOCKING_ADAPTERS.keys.inspect}" + end + + RSpec::Support.require_rspec_core "mocking_adapters/#{const_name.to_s.downcase}" + RSpec::Core::MockingAdapters.const_get(const_name) + end + + new_name, old_name = [framework_module, @mock_framework].map do |mod| + mod.respond_to?(:framework_name) ? mod.framework_name : :unnamed + end + + unless new_name == old_name + assert_no_example_groups_defined(:mock_framework) + end + + if block_given? + raise "#{framework_module} must respond to `configuration` so that " \ + "mock_with can yield it." unless framework_module.respond_to?(:configuration) + yield framework_module.configuration + end + + @mock_framework = framework_module + end + + # Returns the configured expectation framework adapter module(s) + def expectation_frameworks + if @expectation_frameworks.empty? + begin + expect_with :rspec + rescue LoadError + expect_with Module.new + end + end + @expectation_frameworks + end + + # Delegates to expect_with(framework). + def expectation_framework=(framework) + expect_with(framework) + end + + # Sets the expectation framework module(s) to be included in each example + # group. + # + # `frameworks` can be `:rspec`, `:test_unit`, `:minitest`, a custom + # module, or any combination thereof: + # + # config.expect_with :rspec + # config.expect_with :test_unit + # config.expect_with :minitest + # config.expect_with :rspec, :minitest + # config.expect_with OtherExpectationFramework + # + # RSpec will translate `:rspec`, `:minitest`, and `:test_unit` into the + # appropriate modules. + # + # ## Configuration + # + # If the module responds to `configuration`, `expect_with` will + # yield the `configuration` object if given a block: + # + # config.expect_with OtherExpectationFramework do |custom_config| + # custom_config.custom_setting = true + # end + def expect_with(*frameworks) + modules = frameworks.map do |framework| + case framework + when Module + framework + when :rspec + require 'rspec/expectations' + + # Tag this exception class so our exception formatting logic knows + # that it satisfies the `MultipleExceptionError` interface. + ::RSpec::Expectations::MultipleExpectationsNotMetError.__send__( + :include, MultipleExceptionError::InterfaceTag + ) + + ::RSpec::Matchers + when :test_unit + require 'rspec/core/test_unit_assertions_adapter' + ::RSpec::Core::TestUnitAssertionsAdapter + when :minitest + require 'rspec/core/minitest_assertions_adapter' + ::RSpec::Core::MinitestAssertionsAdapter + else + raise ArgumentError, "#{framework.inspect} is not supported" + end + end + + if (modules - @expectation_frameworks).any? + assert_no_example_groups_defined(:expect_with) + end + + if block_given? + raise "expect_with only accepts a block with a single argument. " \ + "Call expect_with #{modules.length} times, " \ + "once with each argument, instead." if modules.length > 1 + raise "#{modules.first} must respond to `configuration` so that " \ + "expect_with can yield it." unless modules.first.respond_to?(:configuration) + yield modules.first.configuration + end + + @expectation_frameworks.push(*modules) + end + + # Check if full backtrace is enabled. + # @return [Boolean] is full backtrace enabled + def full_backtrace? + @backtrace_formatter.full_backtrace? + end + + # Toggle full backtrace. + # @attr true_or_false [Boolean] toggle full backtrace display + def full_backtrace=(true_or_false) + @backtrace_formatter.full_backtrace = true_or_false + end + + # Enables color output if the output is a TTY. As of RSpec 3.6, this is + # the default behavior and this option is retained only for backwards + # compatibility. + # + # @deprecated No longer recommended because of complex behavior. Instead, + # rely on the fact that TTYs will display color by default, or set + # {#color_mode} to :on to display color on a non-TTY output. + # @see color_mode + # @see color_enabled? + # @return [Boolean] + def color + value_for(:color) { @color } + end + + # The mode for determining whether to display output in color. One of: + # + # - :automatic - the output will be in color if the output is a TTY (the + # default) + # - :on - the output will be in color, whether or not the output is a TTY + # - :off - the output will not be in color + # + # @see color_enabled? + # @return [Boolean] + def color_mode + value_for(:color_mode) { @color_mode } + end + + # Check if color is enabled for a particular output. + # @param output [IO] an output stream to use, defaults to the current + # `output_stream` + # @return [Boolean] + def color_enabled?(output=output_stream) + case color_mode + when :on then true + when :off then false + else # automatic + output_to_tty?(output) || (color && tty?) + end + end + + # Set the color mode. + attr_writer :color_mode + + # Toggle output color. + # + # @deprecated No longer recommended because of complex behavior. Instead, + # rely on the fact that TTYs will display color by default, or set + # {:color_mode} to :on to display color on a non-TTY output. + attr_writer :color + + # @private + def libs=(libs) + libs.map do |lib| + @libs.unshift lib + $LOAD_PATH.unshift lib + end + end + + # Run examples matching on `description` in all files to run. + # @param description [String, Regexp] the pattern to filter on + def full_description=(description) + filter_run :full_description => Regexp.union(*Array(description).map { |d| Regexp.new(d) }) + end + + # @return [Array] full description filter + def full_description + filter.fetch :full_description, nil + end + + # @overload add_formatter(formatter) + # @overload add_formatter(formatter, output) + # + # @param formatter [Class, String, Object] formatter to use. Can be any of the + # string values supported from the CLI (`p`/`progress`, + # `d`/`doc`/`documentation`, `h`/`html`, or `j`/`json`), any + # class that implements the formatter protocol and has registered + # itself with RSpec as a formatter, or a formatter instance. + # @param output [String, IO] where the formatter will write its output. + # Can be an IO object or a string path to a file. If not provided, + # the configured `output_stream` (`$stdout`, by default) will be used. + # + # Adds a formatter to the set RSpec will use for this run. + # + # @see RSpec::Core::Formatters::Protocol + def add_formatter(formatter, output=output_wrapper) + formatter_loader.add(formatter, output) + end + alias_method :formatter=, :add_formatter + + # The formatter that will be used if no formatter has been set. + # Defaults to 'progress'. + def default_formatter + formatter_loader.default_formatter + end + + # Sets a fallback formatter to use if none other has been set. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.default_formatter = 'doc' + # end + def default_formatter=(value) + formatter_loader.default_formatter = value + end + + # Returns a duplicate of the formatters currently loaded in + # the `FormatterLoader` for introspection. + # + # Note as this is a duplicate, any mutations will be disregarded. + # + # @return [Array] the formatters currently loaded + def formatters + formatter_loader.formatters.dup + end + + # @private + def formatter_loader + @formatter_loader ||= Formatters::Loader.new(Reporter.new(self)) + end + + # @private + # + # This buffer is used to capture all messages sent to the reporter during + # reporter initialization. It can then replay those messages after the + # formatter is correctly initialized. Otherwise, deprecation warnings + # during formatter initialization can cause an infinite loop. + class DeprecationReporterBuffer + def initialize + @calls = [] + end + + def deprecation(*args) + @calls << args + end + + def play_onto(reporter) + @calls.each do |args| + reporter.deprecation(*args) + end + end + end + + # @return [RSpec::Core::Reporter] the currently configured reporter + def reporter + # @reporter_buffer should only ever be set in this method to cover + # initialization of @reporter. + @reporter_buffer || @reporter ||= + begin + @reporter_buffer = DeprecationReporterBuffer.new + formatter_loader.prepare_default output_wrapper, deprecation_stream + @reporter_buffer.play_onto(formatter_loader.reporter) + @reporter_buffer = nil + formatter_loader.reporter + end + end + + # @api private + # + # Defaults `profile_examples` to 10 examples when `@profile_examples` is + # `true`. + def profile_examples + profile = value_for(:profile_examples) { @profile_examples } + if profile && !profile.is_a?(Integer) + 10 + else + profile + end + end + + # @private + def files_or_directories_to_run=(*files) + files = files.flatten + + if (command == 'rspec' || Runner.running_in_drb?) && default_path && files.empty? + files << default_path + end + + @files_or_directories_to_run = files + @files_to_run = nil + end + + # The spec files RSpec will run. + # @return [Array] specified files about to run + def files_to_run + @files_to_run ||= get_files_to_run(@files_or_directories_to_run) + end + + # @private + def last_run_statuses + @last_run_statuses ||= Hash.new(UNKNOWN_STATUS).tap do |statuses| + if (path = example_status_persistence_file_path) + begin + ExampleStatusPersister.load_from(path).inject(statuses) do |hash, example| + status = example[:status] + status = UNKNOWN_STATUS unless VALID_STATUSES.include?(status) + hash[example.fetch(:example_id)] = status + hash + end + rescue SystemCallError => e + RSpec.warning "Could not read from #{path.inspect} (configured as " \ + "`config.example_status_persistence_file_path`) due " \ + "to a system error: #{e.inspect}. Please check that " \ + "the config option is set to an accessible, valid " \ + "file path", :call_site => nil + end + end + end + end + + # @private + UNKNOWN_STATUS = "unknown".freeze + + # @private + FAILED_STATUS = "failed".freeze + + # @private + PASSED_STATUS = "passed".freeze + + # @private + PENDING_STATUS = "pending".freeze + + # @private + VALID_STATUSES = [UNKNOWN_STATUS, FAILED_STATUS, PASSED_STATUS, PENDING_STATUS] + + # @private + def spec_files_with_failures + @spec_files_with_failures ||= last_run_statuses.inject(Set.new) do |files, (id, status)| + files << Example.parse_id(id).first if status == FAILED_STATUS + files + end.to_a + end + + # Creates a method that delegates to `example` including the submitted + # `args`. Used internally to add variants of `example` like `pending`: + # @param name [String] example name alias + # @param args [Array, Hash] metadata for the generated example + # + # @note The specific example alias below (`pending`) is already + # defined for you. + # @note Use with caution. This extends the language used in your + # specs, but does not add any additional documentation. We use this + # in RSpec to define methods like `focus` and `xit`, but we also add + # docs for those methods. + # + # @example + # RSpec.configure do |config| + # config.alias_example_to :pending, :pending => true + # end + # + # # This lets you do this: + # + # RSpec.describe Thing do + # pending "does something" do + # thing = Thing.new + # end + # end + # + # # ... which is the equivalent of + # + # RSpec.describe Thing do + # it "does something", :pending => true do + # thing = Thing.new + # end + # end + def alias_example_to(name, *args) + extra_options = Metadata.build_hash_from(args) + RSpec::Core::ExampleGroup.define_example_method(name, extra_options) + end + + # Creates a method that defines an example group with the provided + # metadata. Can be used to define example group/metadata shortcuts. + # + # @example + # RSpec.configure do |config| + # config.alias_example_group_to :describe_model, :type => :model + # end + # + # shared_context_for "model tests", :type => :model do + # # define common model test helper methods, `let` declarations, etc + # end + # + # # This lets you do this: + # + # RSpec.describe_model User do + # end + # + # # ... which is the equivalent of + # + # RSpec.describe User, :type => :model do + # end + # + # @note The defined aliased will also be added to the top level + # (e.g. `main` and from within modules) if + # `expose_dsl_globally` is set to true. + # @see #alias_example_to + # @see #expose_dsl_globally= + def alias_example_group_to(new_name, *args) + extra_options = Metadata.build_hash_from(args) + RSpec::Core::ExampleGroup.define_example_group_method(new_name, extra_options) + end + + # Define an alias for it_should_behave_like that allows different + # language (like "it_has_behavior" or "it_behaves_like") to be + # employed when including shared examples. + # + # @example + # RSpec.configure do |config| + # config.alias_it_behaves_like_to(:it_has_behavior, 'has behavior:') + # end + # + # # allows the user to include a shared example group like: + # + # RSpec.describe Entity do + # it_has_behavior 'sortability' do + # let(:sortable) { Entity.new } + # end + # end + # + # # which is reported in the output as: + # # Entity + # # has behavior: sortability + # # ...sortability examples here + # + # @note Use with caution. This extends the language used in your + # specs, but does not add any additional documentation. We use this + # in RSpec to define `it_should_behave_like` (for backward + # compatibility), but we also add docs for that method. + def alias_it_behaves_like_to(new_name, report_label='') + RSpec::Core::ExampleGroup.define_nested_shared_group_method(new_name, report_label) + end + alias_method :alias_it_should_behave_like_to, :alias_it_behaves_like_to + + # Adds key/value pairs to the `inclusion_filter`. If `args` + # includes any symbols that are not part of the hash, each symbol + # is treated as a key in the hash with the value `true`. + # + # ### Note + # + # Filters set using this method can be overridden from the command line + # or config files (e.g. `.rspec`). + # + # @example + # # Given this declaration. + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # Any of the following will include that group. + # config.filter_run_including :foo => 'bar' + # config.filter_run_including :foo => /^ba/ + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # Given a proc with an arity of 1, the lambda is passed the value + # # related to the key, e.g. + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # + # # Given a proc with an arity of 2, the lambda is passed the value + # # related to the key, and the metadata itself e.g. + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # filter_run_including :foo # same as filter_run_including :foo => true + def filter_run_including(*args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + filter_manager.include_with_low_priority meta + static_config_filter_manager.include_with_low_priority Metadata.deep_hash_dup(meta) + end + alias_method :filter_run, :filter_run_including + + # Applies the provided filter only if any of examples match, in constrast + # to {#filter_run}, which always applies even if no examples match, in + # which case no examples will be run. This allows you to leave configured + # filters in place that are intended only for temporary use. The most common + # example is focus filtering: `config.filter_run_when_matching :focus`. + # With that configured, you can temporarily focus an example or group + # by tagging it with `:focus` metadata, or prefixing it with an `f` + # (as in `fdescribe`, `fcontext` and `fit`) since those are aliases for + # `describe`/`context`/`it` with `:focus` metadata. + def filter_run_when_matching(*args) + when_first_matching_example_defined(*args) do + filter_run(*args) + end + end + + # Clears and reassigns the `inclusion_filter`. Set to `nil` if you don't + # want any inclusion filter at all. + # + # ### Warning + # + # This overrides any inclusion filters/tags set on the command line or in + # configuration files. + def inclusion_filter=(filter) + meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + filter_manager.include_only meta + end + + alias_method :filter=, :inclusion_filter= + + # Returns the `inclusion_filter`. If none has been set, returns an empty + # hash. + def inclusion_filter + filter_manager.inclusions + end + + alias_method :filter, :inclusion_filter + + # Adds key/value pairs to the `exclusion_filter`. If `args` + # includes any symbols that are not part of the hash, each symbol + # is treated as a key in the hash with the value `true`. + # + # ### Note + # + # Filters set using this method can be overridden from the command line + # or config files (e.g. `.rspec`). + # + # @example + # # Given this declaration. + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # Any of the following will exclude that group. + # config.filter_run_excluding :foo => 'bar' + # config.filter_run_excluding :foo => /^ba/ + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # Given a proc with an arity of 1, the lambda is passed the value + # # related to the key, e.g. + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # + # # Given a proc with an arity of 2, the lambda is passed the value + # # related to the key, and the metadata itself e.g. + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # filter_run_excluding :foo # same as filter_run_excluding :foo => true + def filter_run_excluding(*args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + filter_manager.exclude_with_low_priority meta + static_config_filter_manager.exclude_with_low_priority Metadata.deep_hash_dup(meta) + end + + # Clears and reassigns the `exclusion_filter`. Set to `nil` if you don't + # want any exclusion filter at all. + # + # ### Warning + # + # This overrides any exclusion filters/tags set on the command line or in + # configuration files. + def exclusion_filter=(filter) + meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + filter_manager.exclude_only meta + end + + # Returns the `exclusion_filter`. If none has been set, returns an empty + # hash. + def exclusion_filter + filter_manager.exclusions + end + + # Tells RSpec to include `mod` in example groups. Methods defined in + # `mod` are exposed to examples (not example groups). Use `filters` to + # constrain the groups or examples in which to include the module. + # + # @example + # + # module AuthenticationHelpers + # def login_as(user) + # # ... + # end + # end + # + # module PreferencesHelpers + # def preferences(user, preferences = {}) + # # ... + # end + # end + # + # module UserHelpers + # def users(username) + # # ... + # end + # end + # + # RSpec.configure do |config| + # config.include(UserHelpers) # included in all groups + # + # # included in examples with `:preferences` metadata + # config.include(PreferenceHelpers, :preferences) + # + # # included in examples with `:type => :request` metadata + # config.include(AuthenticationHelpers, :type => :request) + # end + # + # describe "edit profile", :preferences, :type => :request do + # it "can be viewed by owning user" do + # login_as preferences(users(:jdoe), :lang => 'es') + # get "/profiles/jdoe" + # assert_select ".username", :text => 'jdoe' + # end + # end + # + # @note Filtered module inclusions can also be applied to + # individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # @see #include_context + # @see #extend + # @see #prepend + def include(mod, *filters) + define_mixed_in_module(mod, filters, @include_modules, :include) do |group| + safe_include(mod, group) + end + end + + # Tells RSpec to include the named shared example group in example groups. + # Use `filters` to constrain the groups or examples in which to include + # the example group. + # + # @example + # + # RSpec.shared_context "example admin user" do + # let(:admin_user) { create_user(:admin) } + # end + # + # RSpec.shared_context "example guest user" do + # let(:guest_user) { create_user(:guest) } + # end + # + # RSpec.configure do |config| + # config.include_context "example guest user", :type => :request + # config.include_context "example admin user", :admin, :type => :request + # end + # + # RSpec.describe "The admin page", :type => :request do + # it "can be viewed by admins", :admin do + # login_with admin_user + # get "/admin" + # expect(response).to be_ok + # end + # + # it "cannot be viewed by guests" do + # login_with guest_user + # get "/admin" + # expect(response).to be_forbidden + # end + # end + # + # @note Filtered context inclusions can also be applied to + # individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # @see #include + def include_context(shared_group_name, *filters) + shared_module = world.shared_example_group_registry.find([:main], shared_group_name) + include shared_module, *filters + end + + # Tells RSpec to extend example groups with `mod`. Methods defined in + # `mod` are exposed to example groups (not examples). Use `filters` to + # constrain the groups to extend. + # + # Similar to `include`, but behavior is added to example groups, which + # are classes, rather than the examples, which are instances of those + # classes. + # + # @example + # + # module UiHelpers + # def run_in_browser + # # ... + # end + # end + # + # module PermissionHelpers + # def define_permissions + # # ... + # end + # end + # + # RSpec.configure do |config| + # config.extend(UiHelpers, :type => :request) + # config.extend(PermissionHelpers, :with_permissions, :type => :request) + # end + # + # describe "edit profile", :with_permissions, :type => :request do + # run_in_browser + # define_permissions + # + # it "does stuff in the client" do + # # ... + # end + # end + # + # @see #include + # @see #prepend + def extend(mod, *filters) + define_mixed_in_module(mod, filters, @extend_modules, :extend) do |group| + safe_extend(mod, group) + end + end + + if RSpec::Support::RubyFeatures.module_prepends_supported? + # Tells RSpec to prepend example groups with `mod`. Methods defined in + # `mod` are exposed to examples (not example groups). Use `filters` to + # constrain the groups in which to prepend the module. + # + # Similar to `include`, but module is included before the example group's class + # in the ancestor chain. + # + # @example + # + # module OverrideMod + # def override_me + # "overridden" + # end + # end + # + # RSpec.configure do |config| + # config.prepend(OverrideMod, :method => :prepend) + # end + # + # describe "overriding example's class", :method => :prepend do + # it "finds the user" do + # self.class.class_eval do + # def override_me + # end + # end + # override_me # => "overridden" + # # ... + # end + # end + # + # @see #include + # @see #extend + def prepend(mod, *filters) + define_mixed_in_module(mod, filters, @prepend_modules, :prepend) do |group| + safe_prepend(mod, group) + end + end + end + + # @private + # + # Used internally to extend a group with modules using `include`, `prepend` and/or + # `extend`. + def configure_group(group) + group.hooks.register_globals(group, hooks) + + configure_group_with group, @include_modules, :safe_include + configure_group_with group, @extend_modules, :safe_extend + configure_group_with group, @prepend_modules, :safe_prepend + end + + # @private + # + # Used internally to extend the singleton class of a single example's + # example group instance with modules using `include` and/or `extend`. + def configure_example(example, example_hooks) + example_hooks.register_global_singleton_context_hooks(example, hooks) + singleton_group = example.example_group_instance.singleton_class + + # We replace the metadata so that SharedExampleGroupModule#included + # has access to the example's metadata[:location]. + singleton_group.with_replaced_metadata(example.metadata) do + modules = @include_modules.items_for(example.metadata) + modules.each do |mod| + safe_include(mod, example.example_group_instance.singleton_class) + end + + MemoizedHelpers.define_helpers_on(singleton_group) unless modules.empty? + end + end + + # @private + def requires=(paths) + directories = ['lib', default_path].select { |p| File.directory? p } + RSpec::Core::RubyProject.add_to_load_path(*directories) + paths.each { |path| load_file_handling_errors(:require, path) } + @requires += paths + end + + # @private + def in_project_source_dir_regex + regexes = project_source_dirs.map do |dir| + /\A#{Regexp.escape(File.expand_path(dir))}\// + end + + Regexp.union(regexes) + end + + # @private + def configure_mock_framework + RSpec::Core::ExampleGroup.__send__(:include, mock_framework) + conditionally_disable_mocks_monkey_patching + end + + # @private + def configure_expectation_framework + expectation_frameworks.each do |framework| + RSpec::Core::ExampleGroup.__send__(:include, framework) + end + conditionally_disable_expectations_monkey_patching + end + + # @private + def load_spec_files + # Note which spec files world is already aware of. + # This is generally only needed for when the user runs + # `ruby path/to/spec.rb` (and loads `rspec/autorun`) -- + # in that case, the spec file was loaded by `ruby` and + # isn't loaded by us here so we only know about it because + # of an example group being registered in it. + world.registered_example_group_files.each do |f| + loaded_spec_files << f # the registered files are already expended absolute paths + end + + files_to_run.uniq.each do |f| + file = File.expand_path(f) + load_file_handling_errors(:load, file) + loaded_spec_files << file + end + + @spec_files_loaded = true + end + + # @private + DEFAULT_FORMATTER = lambda { |string| string } + + # Formats the docstring output using the block provided. + # + # @example + # # This will strip the descriptions of both examples and example + # # groups. + # RSpec.configure do |config| + # config.format_docstrings { |s| s.strip } + # end + def format_docstrings(&block) + @format_docstrings_block = block_given? ? block : DEFAULT_FORMATTER + end + + # @private + def format_docstrings_block + @format_docstrings_block ||= DEFAULT_FORMATTER + end + + # @private + def self.delegate_to_ordering_manager(*methods) + methods.each do |method| + define_method method do |*args, &block| + ordering_manager.__send__(method, *args, &block) + end + end + end + + # @!method seed=(value) + # + # Sets the seed value and sets the default global ordering to random. + delegate_to_ordering_manager :seed= + + # @!method seed + # Seed for random ordering (default: generated randomly each run). + # + # When you run specs with `--order random`, RSpec generates a random seed + # for the randomization and prints it to the `output_stream` (assuming + # you're using RSpec's built-in formatters). If you discover an ordering + # dependency (i.e. examples fail intermittently depending on order), set + # this (on Configuration or on the command line with `--seed`) to run + # using the same seed while you debug the issue. + # + # We recommend, actually, that you use the command line approach so you + # don't accidentally leave the seed encoded. + delegate_to_ordering_manager :seed + + # @!method order=(value) + # + # Sets the default global ordering strategy. By default this can be one + # of `:defined`, `:random`, but is customizable through the + # `register_ordering` API. If order is set to `'rand:'`, + # the seed will also be set. + # + # @see #register_ordering + delegate_to_ordering_manager :order= + + # @!method register_ordering(name) + # + # Registers a named ordering strategy that can later be + # used to order an example group's subgroups by adding + # `:order => ` metadata to the example group. + # + # @param name [Symbol] The name of the ordering. + # @yield Block that will order the given examples or example groups + # @yieldparam list [Array, + # Array] The examples or groups to order + # @yieldreturn [Array, + # Array] The re-ordered examples or groups + # + # @example + # RSpec.configure do |rspec| + # rspec.register_ordering :reverse do |list| + # list.reverse + # end + # end + # + # RSpec.describe 'MyClass', :order => :reverse do + # # ... + # end + # + # @note Pass the symbol `:global` to set the ordering strategy that + # will be used to order the top-level example groups and any example + # groups that do not have declared `:order` metadata. + # + # @example + # RSpec.configure do |rspec| + # rspec.register_ordering :global do |examples| + # acceptance, other = examples.partition do |example| + # example.metadata[:type] == :acceptance + # end + # other + acceptance + # end + # end + # + # RSpec.describe 'MyClass', :type => :acceptance do + # # will run last + # end + # + # RSpec.describe 'MyClass' do + # # will run first + # end + # + delegate_to_ordering_manager :register_ordering + + # @private + delegate_to_ordering_manager :seed_used?, :ordering_registry + + # Set Ruby warnings on or off. + def warnings=(value) + $VERBOSE = !!value + end + + # @return [Boolean] Whether or not ruby warnings are enabled. + def warnings? + $VERBOSE + end + + # @private + RAISE_ERROR_WARNING_NOTIFIER = lambda { |message| raise message } + + # Turns warnings into errors. This can be useful when + # you want RSpec to run in a 'strict' no warning situation. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.raise_on_warning = true + # end + def raise_on_warning=(value) + if value + RSpec::Support.warning_notifier = RAISE_ERROR_WARNING_NOTIFIER + else + RSpec::Support.warning_notifier = RSpec::Support::DEFAULT_WARNING_NOTIFIER + end + end + + # Exposes the current running example via the named + # helper method. RSpec 2.x exposed this via `example`, + # but in RSpec 3.0, the example is instead exposed via + # an arg yielded to `it`, `before`, `let`, etc. However, + # some extension gems (such as Capybara) depend on the + # RSpec 2.x's `example` method, so this config option + # can be used to maintain compatibility. + # + # @param method_name [Symbol] the name of the helper method + # + # @example + # + # RSpec.configure do |rspec| + # rspec.expose_current_running_example_as :example + # end + # + # RSpec.describe MyClass do + # before do + # # `example` can be used here because of the above config. + # do_something if example.metadata[:type] == "foo" + # end + # end + def expose_current_running_example_as(method_name) + ExposeCurrentExample.module_exec do + extend RSpec::SharedContext + let(method_name) { |ex| ex } + end + + include ExposeCurrentExample + end + + # @private + module ExposeCurrentExample; end + + # Turns deprecation warnings into errors, in order to surface + # the full backtrace of the call site. This can be useful when + # you need more context to address a deprecation than the + # single-line call site normally provided. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.raise_errors_for_deprecations! + # end + def raise_errors_for_deprecations! + self.deprecation_stream = Formatters::DeprecationFormatter::RaiseErrorStream.new + end + + # Enables zero monkey patching mode for RSpec. It removes monkey + # patching of the top-level DSL methods (`describe`, + # `shared_examples_for`, etc) onto `main` and `Module`, instead + # requiring you to prefix these methods with `RSpec.`. It enables + # expect-only syntax for rspec-mocks and rspec-expectations. It + # simply disables monkey patching on whatever pieces of RSpec + # the user is using. + # + # @note It configures rspec-mocks and rspec-expectations only + # if the user is using those (either explicitly or implicitly + # by not setting `mock_with` or `expect_with` to anything else). + # + # @note If the user uses this options with `mock_with :mocha` + # (or similiar) they will still have monkey patching active + # in their test environment from mocha. + # + # @example + # + # # It disables all monkey patching. + # RSpec.configure do |config| + # config.disable_monkey_patching! + # end + # + # # Is an equivalent to + # RSpec.configure do |config| + # config.expose_dsl_globally = false + # + # config.mock_with :rspec do |mocks| + # mocks.syntax = :expect + # mocks.patch_marshal_to_support_partial_doubles = false + # end + # + # config.expect_with :rspec do |expectations| + # expectations.syntax = :expect + # end + # end + def disable_monkey_patching! + self.expose_dsl_globally = false + self.disable_monkey_patching = true + conditionally_disable_mocks_monkey_patching + conditionally_disable_expectations_monkey_patching + end + + # @private + attr_accessor :disable_monkey_patching + + # Defines a callback that can assign derived metadata values. + # + # @param filters [Array, Hash] metadata filters that determine + # which example or group metadata hashes the callback will be triggered + # for. If none are given, the callback will be run against the metadata + # hashes of all groups and examples. + # @yieldparam metadata [Hash] original metadata hash from an example or + # group. Mutate this in your block as needed. + # + # @example + # RSpec.configure do |config| + # # Tag all groups and examples in the spec/unit directory with + # # :type => :unit + # config.define_derived_metadata(:file_path => %r{/spec/unit/}) do |metadata| + # metadata[:type] = :unit + # end + # end + def define_derived_metadata(*filters, &block) + meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + @derived_metadata_blocks.append(block, meta) + end + + # Defines a callback that runs after the first example with matching + # metadata is defined. If no examples are defined with matching metadata, + # it will not get called at all. + # + # This can be used to ensure some setup is performed (such as bootstrapping + # a DB or loading a specific file that adds significantly to the boot time) + # if needed (as indicated by the presence of an example with matching metadata) + # but avoided otherwise. + # + # @example + # RSpec.configure do |config| + # config.when_first_matching_example_defined(:db) do + # # Load a support file that does some heavyweight setup, + # # including bootstrapping the DB, but only if we have loaded + # # any examples tagged with `:db`. + # require 'support/db' + # end + # end + def when_first_matching_example_defined(*filters) + specified_meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + + callback = lambda do |example_or_group_meta| + # Example groups do not have `:example_group` metadata + # (instead they have `:parent_example_group` metadata). + return unless example_or_group_meta.key?(:example_group) + + # Ensure the callback only fires once. + @derived_metadata_blocks.delete(callback, specified_meta) + + yield + end + + @derived_metadata_blocks.append(callback, specified_meta) + end + + # @private + def apply_derived_metadata_to(metadata) + already_run_blocks = Set.new + + # We loop and attempt to re-apply metadata blocks to support cascades + # (e.g. where a derived bit of metadata triggers the application of + # another piece of derived metadata, etc) + # + # We limit our looping to 200 times as a way to detect infinitely recursing derived metadata blocks. + # It's hard to imagine a valid use case for a derived metadata cascade greater than 200 iterations. + 200.times do + return if @derived_metadata_blocks.items_for(metadata).all? do |block| + already_run_blocks.include?(block).tap do |skip_block| + block.call(metadata) unless skip_block + already_run_blocks << block + end + end + end + + # If we got here, then `@derived_metadata_blocks.items_for(metadata).all?` never returned + # `true` above and we treat this as an attempt to recurse infinitely. It's better to fail + # with a clear # error than hang indefinitely, which is what would happen if we didn't limit + # the looping above. + raise SystemStackError, "Attempted to recursively derive metadata indefinitely." + end + + # Defines a `before` hook. See {Hooks#before} for full docs. + # + # This method differs from {Hooks#before} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once before + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #prepend_before + # @see #after + # @see #append_after + def before(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @before_suite_hooks << Hooks::BeforeHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.before(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + alias_method :append_before, :before + + # Adds `block` to the start of the list of `before` blocks in the same + # scope (`:example`, `:context`, or `:suite`), in contrast to {#before}, + # which adds the hook to the end of the list. + # + # See {Hooks#before} for full `before` hook docs. + # + # This method differs from {Hooks#prepend_before} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once before + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #before + # @see #after + # @see #append_after + def prepend_before(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @before_suite_hooks.unshift Hooks::BeforeHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.prepend_before(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + + # Defines a `after` hook. See {Hooks#after} for full docs. + # + # This method differs from {Hooks#after} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once after + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #append_after + # @see #before + # @see #prepend_before + def after(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @after_suite_hooks.unshift Hooks::AfterHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.after(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + alias_method :prepend_after, :after + + # Adds `block` to the end of the list of `after` blocks in the same + # scope (`:example`, `:context`, or `:suite`), in contrast to {#after}, + # which adds the hook to the start of the list. + # + # See {Hooks#after} for full `after` hook docs. + # + # This method differs from {Hooks#append_after} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once after + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #append_after + # @see #before + # @see #prepend_before + def append_after(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @after_suite_hooks << Hooks::AfterHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.append_after(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + + # Registers `block` as an `around` hook. + # + # See {Hooks#around} for full `around` hook docs. + def around(scope=nil, *meta, &block) + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.around(scope, *meta, &block) } + super(scope, *meta, &block) + end + + # @private + def with_suite_hooks + return yield if dry_run? + + begin + run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) + yield + ensure + run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) + end + end + + # @private + # Holds the various registered hooks. Here we use a FilterableItemRepository + # implementation that is specifically optimized for the read/write patterns + # of the config object. + def hooks + @hooks ||= HookCollections.new(self, FilterableItemRepository::QueryOptimized) + end + + # Invokes block before defining an example group + def on_example_group_definition(&block) + on_example_group_definition_callbacks << block + end + + # @api private + # Returns an array of blocks to call before defining an example group + def on_example_group_definition_callbacks + @on_example_group_definition_callbacks ||= [] + end + + # @private + def bisect_runner_class + @bisect_runner_class ||= begin + case bisect_runner + when :fork + RSpec::Support.require_rspec_core 'bisect/fork_runner' + Bisect::ForkRunner + when :shell + RSpec::Support.require_rspec_core 'bisect/shell_runner' + Bisect::ShellRunner + else + raise "Unsupported value for `bisect_runner` (#{bisect_runner.inspect}). " \ + "Only `:fork` and `:shell` are supported." + end + end + end + + private + + def load_file_handling_errors(method, file) + __send__(method, file) + rescue LoadError => ex + relative_file = Metadata.relative_path(file) + suggestions = DidYouMean.new(relative_file).call + reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.#{suggestions}") + RSpec.world.wants_to_quit = true + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + relative_file = Metadata.relative_path(file) + reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.") + RSpec.world.wants_to_quit = true + end + + def handle_suite_hook(scope, meta) + return nil unless scope == :suite + + unless meta.empty? + # TODO: in RSpec 4, consider raising an error here. + # We warn only for backwards compatibility. + RSpec.warn_with "WARNING: `:suite` hooks do not support metadata since " \ + "they apply to the suite as a whole rather than " \ + "any individual example or example group that has metadata. " \ + "The metadata you have provided (#{meta.inspect}) will be ignored." + end + + yield + end + + def run_suite_hooks(hook_description, hooks) + context = SuiteHookContext.new(hook_description, reporter) + + hooks.each do |hook| + begin + hook.run(context) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + context.set_exception(ex) + + # Do not run subsequent `before` hooks if one fails. + # But for `after` hooks, we run them all so that all + # cleanup bits get a chance to complete, minimizing the + # chance that resources get left behind. + break if hooks.equal?(@before_suite_hooks) + end + end + end + + def get_files_to_run(paths) + files = FlatMap.flat_map(paths_to_check(paths)) do |path| + path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + File.directory?(path) ? gather_directories(path) : extract_location(path) + end.uniq + + return files unless only_failures? + relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) } + intersection = (relative_files & spec_files_with_failures.to_a) + intersection.empty? ? files : intersection + end + + def paths_to_check(paths) + return paths if pattern_might_load_specs_from_vendored_dirs? + paths + [Dir.getwd] + end + + def pattern_might_load_specs_from_vendored_dirs? + pattern.split(File::SEPARATOR).first.include?('**') + end + + def gather_directories(path) + include_files = get_matching_files(path, pattern) + exclude_files = get_matching_files(path, exclude_pattern) + (include_files - exclude_files).uniq + end + + def get_matching_files(path, pattern) + raw_files = Dir[file_glob_from(path, pattern)] + raw_files.map { |file| File.expand_path(file) }.sort + end + + def file_glob_from(path, pattern) + stripped = "{#{pattern.gsub(/\s*,\s*/, ',')}}" + return stripped if pattern =~ /^(\.\/)?#{Regexp.escape path}/ || absolute_pattern?(pattern) + File.join(path, stripped) + end + + if RSpec::Support::OS.windows? + # :nocov: + def absolute_pattern?(pattern) + pattern =~ /\A[A-Z]:\\/ || windows_absolute_network_path?(pattern) + end + + def windows_absolute_network_path?(pattern) + return false unless ::File::ALT_SEPARATOR + pattern.start_with?(::File::ALT_SEPARATOR + ::File::ALT_SEPARATOR) + end + # :nocov: + else + def absolute_pattern?(pattern) + pattern.start_with?(File::Separator) + end + end + + def extract_location(path) + match = /^(.*?)((?:\:\d+)+)$/.match(path) + + if match + captures = match.captures + path = captures[0] + lines = captures[1][1..-1].split(":").map(&:to_i) + filter_manager.add_location path, lines + else + path, scoped_ids = Example.parse_id(path) + filter_manager.add_ids(path, scoped_ids.split(/\s*,\s*/)) if scoped_ids + end + + return [] if path == default_path + File.expand_path(path) + end + + def command + $0.split(File::SEPARATOR).last + end + + def value_for(key) + @preferred_options.fetch(key) { yield } + end + + def define_built_in_hooks + around(:example, :aggregate_failures => true) do |procsy| + begin + aggregate_failures(nil, :hide_backtrace => true, &procsy) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception + procsy.example.set_aggregate_failures_exception(exception) + end + end + end + + def assert_no_example_groups_defined(config_option) + return unless world.example_groups.any? + + raise MustBeConfiguredBeforeExampleGroupsError.new( + "RSpec's #{config_option} configuration option must be configured before " \ + "any example groups are defined, but you have already defined a group." + ) + end + + def output_wrapper + @output_wrapper ||= OutputWrapper.new(output_stream) + end + + def output_to_tty?(output=output_stream) + output.respond_to?(:tty?) && output.tty? + end + + def conditionally_disable_mocks_monkey_patching + return unless disable_monkey_patching && rspec_mocks_loaded? + + RSpec::Mocks.configuration.tap do |config| + config.syntax = :expect + config.patch_marshal_to_support_partial_doubles = false + end + end + + def conditionally_disable_expectations_monkey_patching + return unless disable_monkey_patching && rspec_expectations_loaded? + + RSpec::Expectations.configuration.syntax = :expect + end + + def rspec_mocks_loaded? + defined?(RSpec::Mocks.configuration) + end + + def rspec_expectations_loaded? + defined?(RSpec::Expectations.configuration) + end + + def update_pattern_attr(name, value) + if @spec_files_loaded + RSpec.warning "Configuring `#{name}` to #{value} has no effect since " \ + "RSpec has already loaded the spec files." + end + + instance_variable_set(:"@#{name}", value) + @files_to_run = nil + end + + def clear_values_derived_from_example_status_persistence_file_path + @last_run_statuses = nil + @spec_files_with_failures = nil + end + + def configure_group_with(group, module_list, application_method) + module_list.items_for(group.metadata).each do |mod| + __send__(application_method, mod, group) + end + end + + def add_hook_to_existing_matching_groups(meta, scope, &block) + # For example hooks, we have to apply it to each of the top level + # groups, even if the groups do not match. When we apply it, we + # apply it with the metadata, so it will only apply to examples + # in the group that match the metadata. + # #2280 for background and discussion. + if scope == :example || scope == :each || scope.nil? + world.example_groups.each(&block) + else + meta = Metadata.build_hash_from(meta.dup) + on_existing_matching_groups(meta, &block) + end + end + + def on_existing_matching_groups(meta) + world.traverse_example_group_trees_until do |group| + metadata_applies_to_group?(meta, group).tap do |applies| + yield group if applies + end + end + end + + def metadata_applies_to_group?(meta, group) + meta.empty? || MetadataFilter.apply?(:any?, meta, group.metadata) + end + + if RSpec::Support::RubyFeatures.module_prepends_supported? + def safe_prepend(mod, host) + host.__send__(:prepend, mod) unless host < mod + end + end + + if RUBY_VERSION.to_f >= 1.9 + def safe_include(mod, host) + host.__send__(:include, mod) unless host < mod + end + + def safe_extend(mod, host) + host.extend(mod) unless host.singleton_class < mod + end + else # for 1.8.7 + # :nocov: + def safe_include(mod, host) + host.__send__(:include, mod) unless host.included_modules.include?(mod) + end + + def safe_extend(mod, host) + host.extend(mod) unless (class << host; self; end).included_modules.include?(mod) + end + # :nocov: + end + + def define_mixed_in_module(mod, filters, mod_list, config_method, &block) + unless Module === mod + raise TypeError, "`RSpec.configuration.#{config_method}` expects a module but got: #{mod.inspect}" + end + + meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + mod_list.append(mod, meta) + on_existing_matching_groups(meta, &block) + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration_options.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration_options.rb new file mode 100644 index 0000000000..f669cda5f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration_options.rb @@ -0,0 +1,233 @@ +require 'erb' +require 'shellwords' + +module RSpec + module Core + # Responsible for utilizing externally provided configuration options, + # whether via the command line, `.rspec`, `~/.rspec`, + # `$XDG_CONFIG_HOME/rspec/options`, `.rspec-local` or a custom options + # file. + class ConfigurationOptions + # @param args [Array] command line arguments + def initialize(args) + @args = args.dup + organize_options + end + + # Updates the provided {Configuration} instance based on the provided + # external configuration options. + # + # @param config [Configuration] the configuration instance to update + def configure(config) + process_options_into config + configure_filter_manager config.filter_manager + load_formatters_into config + end + + # @api private + # Updates the provided {FilterManager} based on the filter options. + # @param filter_manager [FilterManager] instance to update + def configure_filter_manager(filter_manager) + @filter_manager_options.each do |command, value| + filter_manager.__send__ command, value + end + end + + # @return [Hash] the final merged options, drawn from all external sources + attr_reader :options + + # @return [Array] the original command-line arguments + attr_reader :args + + private + + def organize_options + @filter_manager_options = [] + + @options = (file_options << command_line_options << env_options).each do |opts| + @filter_manager_options << [:include, opts.delete(:inclusion_filter)] if opts.key?(:inclusion_filter) + @filter_manager_options << [:exclude, opts.delete(:exclusion_filter)] if opts.key?(:exclusion_filter) + end + + @options = @options.inject(:libs => [], :requires => []) do |hash, opts| + hash.merge(opts) do |key, oldval, newval| + [:libs, :requires].include?(key) ? oldval + newval : newval + end + end + end + + UNFORCED_OPTIONS = Set.new([ + :requires, :profile, :drb, :libs, :files_or_directories_to_run, + :full_description, :full_backtrace, :tty + ]) + + UNPROCESSABLE_OPTIONS = Set.new([:formatters]) + + def force?(key) + !UNFORCED_OPTIONS.include?(key) + end + + def order(keys) + OPTIONS_ORDER.reverse_each do |key| + keys.unshift(key) if keys.delete(key) + end + keys + end + + OPTIONS_ORDER = [ + # It's important to set this before anything that might issue a + # deprecation (or otherwise access the reporter). + :deprecation_stream, + + # load paths depend on nothing, but must be set before `requires` + # to support load-path-relative requires. + :libs, + + # `files_or_directories_to_run` uses `default_path` so it must be + # set before it. + :default_path, :only_failures, + + # These must be set before `requires` to support checking + # `config.files_to_run` from within `spec_helper.rb` when a + # `-rspec_helper` option is used. + :files_or_directories_to_run, :pattern, :exclude_pattern, + + # Necessary so that the `--seed` option is applied before requires, + # in case required files do something with the provided seed. + # (such as seed global randomization with it). + :order, + + # In general, we want to require the specified files as early as + # possible. The `--require` option is specifically intended to allow + # early requires. For later requires, they can just put the require in + # their spec files, but `--require` provides a unique opportunity for + # users to instruct RSpec to load an extension file early for maximum + # flexibility. + :requires + ] + + def process_options_into(config) + opts = options.reject { |k, _| UNPROCESSABLE_OPTIONS.include? k } + + order(opts.keys).each do |key| + force?(key) ? config.force(key => opts[key]) : config.__send__("#{key}=", opts[key]) + end + end + + def load_formatters_into(config) + options[:formatters].each { |pair| config.add_formatter(*pair) } if options[:formatters] + end + + def file_options + if custom_options_file + [custom_options] + else + [global_options, project_options, local_options] + end + end + + def env_options + return {} unless ENV['SPEC_OPTS'] + + parse_args_ignoring_files_or_dirs_to_run( + Shellwords.split(ENV["SPEC_OPTS"]), + "ENV['SPEC_OPTS']" + ) + end + + def command_line_options + @command_line_options ||= Parser.parse(@args) + end + + def custom_options + options_from(custom_options_file) + end + + def local_options + @local_options ||= options_from(local_options_file) + end + + def project_options + @project_options ||= options_from(project_options_file) + end + + def global_options + @global_options ||= options_from(global_options_file) + end + + def options_from(path) + args = args_from_options_file(path) + parse_args_ignoring_files_or_dirs_to_run(args, path) + end + + def parse_args_ignoring_files_or_dirs_to_run(args, source) + options = Parser.parse(args, source) + options.delete(:files_or_directories_to_run) + options + end + + def args_from_options_file(path) + return [] unless path && File.exist?(path) + config_string = options_file_as_erb_string(path) + FlatMap.flat_map(config_string.split(/\n+/), &:shellsplit) + end + + def options_file_as_erb_string(path) + if RUBY_VERSION >= '2.6' + ERB.new(File.read(path), :trim_mode => '-').result(binding) + else + ERB.new(File.read(path), nil, '-').result(binding) + end + end + + def custom_options_file + command_line_options[:custom_options_file] + end + + def project_options_file + "./.rspec" + end + + def local_options_file + "./.rspec-local" + end + + def global_options_file + xdg_options_file_if_exists || home_options_file_path + end + + def xdg_options_file_if_exists + path = xdg_options_file_path + if path && File.exist?(path) + path + end + end + + def home_options_file_path + File.join(File.expand_path("~"), ".rspec") + rescue ArgumentError + # :nocov: + RSpec.warning "Unable to find ~/.rspec because the HOME environment variable is not set" + nil + # :nocov: + end + + def xdg_options_file_path + xdg_config_home = resolve_xdg_config_home + if xdg_config_home + File.join(xdg_config_home, "rspec", "options") + end + end + + def resolve_xdg_config_home + File.expand_path(ENV.fetch("XDG_CONFIG_HOME", "~/.config")) + rescue ArgumentError + # :nocov: + # On Ruby 2.4, `File.expand("~")` works even if `ENV['HOME']` is not set. + # But on earlier versions, it fails. + nil + # :nocov: + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/did_you_mean.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/did_you_mean.rb new file mode 100644 index 0000000000..07daea966d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/did_you_mean.rb @@ -0,0 +1,46 @@ +module RSpec + module Core + # @private + # Wrapper around Ruby's `DidYouMean::SpellChecker` when available to provide file name suggestions. + class DidYouMean + attr_reader :relative_file_name + + def initialize(relative_file_name) + @relative_file_name = relative_file_name + end + + if defined?(::DidYouMean::SpellChecker) + # provide probable suggestions + def call + checker = ::DidYouMean::SpellChecker.new(:dictionary => Dir["spec/**/*.rb"]) + probables = checker.correct(relative_file_name.sub('./', ''))[0..2] + return '' unless probables.any? + + formats probables + end + else + # return a hint if API for ::DidYouMean::SpellChecker not supported + def call + "\nHint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files." + end + end + + private + + def formats(probables) + rspec_format = probables.map { |s, _| "rspec ./#{s}" } + red_font(top_and_tail rspec_format) + end + + def top_and_tail(rspec_format) + spaces = ' ' * 20 + rspec_format.insert(0, ' - Did you mean?').join("\n#{spaces}") + "\n" + end + + def red_font(mytext) + colorizer = ::RSpec::Core::Formatters::ConsoleCodes + colorizer.wrap mytext, :failure + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/drb.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/drb.rb new file mode 100644 index 0000000000..e44db97c3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/drb.rb @@ -0,0 +1,120 @@ +require 'drb/drb' + +module RSpec + module Core + # @private + class DRbRunner + def initialize(options, configuration=RSpec.configuration) + @options = options + @configuration = configuration + end + + def drb_port + @options.options[:drb_port] || ENV['RSPEC_DRB'] || 8989 + end + + def run(err, out) + begin + DRb.start_service("druby://localhost:0") + rescue SocketError, Errno::EADDRNOTAVAIL + DRb.start_service("druby://:0") + end + spec_server = DRbObject.new_with_uri("druby://127.0.0.1:#{drb_port}") + spec_server.run(drb_argv, err, out) + end + + def drb_argv + @drb_argv ||= begin + @options.configure_filter_manager(@configuration.filter_manager) + DRbOptions.new(@options.options, @configuration.filter_manager).options + end + end + end + + # @private + class DRbOptions + def initialize(submitted_options, filter_manager) + @submitted_options = submitted_options + @filter_manager = filter_manager + end + + def options + argv = [] + argv << "--color" if @submitted_options[:color] + argv << "--force-color" if @submitted_options[:color_mode] == :on + argv << "--no-color" if @submitted_options[:color_mode] == :off + argv << "--profile" if @submitted_options[:profile_examples] + argv << "--backtrace" if @submitted_options[:full_backtrace] + argv << "--tty" if @submitted_options[:tty] + argv << "--fail-fast" if @submitted_options[:fail_fast] + argv << "--options" << @submitted_options[:custom_options_file] if @submitted_options[:custom_options_file] + argv << "--order" << @submitted_options[:order] if @submitted_options[:order] + + add_failure_exit_code(argv) + add_error_exit_code(argv) + add_full_description(argv) + add_filter(argv, :inclusion, @filter_manager.inclusions) + add_filter(argv, :exclusion, @filter_manager.exclusions) + add_formatters(argv) + add_libs(argv) + add_requires(argv) + + argv + @submitted_options[:files_or_directories_to_run] + end + + def add_failure_exit_code(argv) + return unless @submitted_options[:failure_exit_code] + + argv << "--failure-exit-code" << @submitted_options[:failure_exit_code].to_s + end + + def add_error_exit_code(argv) + return unless @submitted_options[:error_exit_code] + + argv << "--error-exit-code" << @submitted_options[:error_exit_code].to_s + end + + def add_full_description(argv) + return unless @submitted_options[:full_description] + + # The argument to --example is regexp-escaped before being stuffed + # into a regexp when received for the first time (see OptionParser). + # Hence, merely grabbing the source of this regexp will retain the + # backslashes, so we must remove them. + @submitted_options[:full_description].each do |description| + argv << "--example" << description.source.delete('\\') + end + end + + CONDITIONAL_FILTERS = [:if, :unless] + + def add_filter(argv, name, hash) + hash.each_pair do |k, v| + next if CONDITIONAL_FILTERS.include?(k) + tag = name == :inclusion ? k.to_s : "~#{k}".dup + tag << ":#{v}" if v.is_a?(String) + argv << "--tag" << tag + end unless hash.empty? + end + + def add_formatters(argv) + @submitted_options[:formatters].each do |pair| + argv << "--format" << pair[0] + argv << "--out" << pair[1] if pair[1] + end if @submitted_options[:formatters] + end + + def add_libs(argv) + @submitted_options[:libs].each do |path| + argv << "-I" << path + end if @submitted_options[:libs] + end + + def add_requires(argv) + @submitted_options[:requires].each do |path| + argv << "--require" << path + end if @submitted_options[:requires] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/dsl.rb new file mode 100644 index 0000000000..220403e6e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/dsl.rb @@ -0,0 +1,98 @@ +module RSpec + module Core + # DSL defines methods to group examples, most notably `describe`, + # and exposes them as class methods of {RSpec}. They can also be + # exposed globally (on `main` and instances of `Module`) through + # the {Configuration} option `expose_dsl_globally`. + # + # By default the methods `describe`, `context` and `example_group` + # are exposed. These methods define a named context for one or + # more examples. The given block is evaluated in the context of + # a generated subclass of {RSpec::Core::ExampleGroup}. + # + # ## Examples: + # + # RSpec.describe "something" do + # context "when something is a certain way" do + # it "does something" do + # # example code goes here + # end + # end + # end + # + # @see ExampleGroup + # @see ExampleGroup.example_group + module DSL + # @private + def self.example_group_aliases + @example_group_aliases ||= [] + end + + # @private + def self.exposed_globally? + @exposed_globally ||= false + end + + # @private + def self.expose_example_group_alias(name) + return if example_group_aliases.include?(name) + + example_group_aliases << name + + (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block| + group = RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block) + RSpec.world.record(group) + group + end + + expose_example_group_alias_globally(name) if exposed_globally? + end + + class << self + # @private + attr_accessor :top_level + end + + # Adds the describe method to Module and the top level binding. + # @api private + def self.expose_globally! + return if exposed_globally? + + example_group_aliases.each do |method_name| + expose_example_group_alias_globally(method_name) + end + + @exposed_globally = true + end + + # Removes the describe method from Module and the top level binding. + # @api private + def self.remove_globally! + return unless exposed_globally? + + example_group_aliases.each do |method_name| + change_global_dsl { undef_method method_name } + end + + @exposed_globally = false + end + + # @private + def self.expose_example_group_alias_globally(method_name) + change_global_dsl do + remove_method(method_name) if method_defined?(method_name) + define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) } + end + end + + # @private + def self.change_global_dsl(&changes) + (class << top_level; self; end).class_exec(&changes) + Module.class_exec(&changes) + end + end + end +end + +# Capture main without an eval. +::RSpec::Core::DSL.top_level = self diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb new file mode 100644 index 0000000000..d3b891fa92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb @@ -0,0 +1,663 @@ +module RSpec + module Core + # Wrapper for an instance of a subclass of {ExampleGroup}. An instance of + # `RSpec::Core::Example` is returned by example definition methods + # such as {ExampleGroup.it it} and is yielded to the {ExampleGroup.it it}, + # {Hooks#before before}, {Hooks#after after}, {Hooks#around around}, + # {MemoizedHelpers::ClassMethods#let let} and + # {MemoizedHelpers::ClassMethods#subject subject} blocks. + # + # This allows us to provide rich metadata about each individual + # example without adding tons of methods directly to the ExampleGroup + # that users may inadvertently redefine. + # + # Useful for configuring logging and/or taking some action based + # on the state of an example's metadata. + # + # @example + # + # RSpec.configure do |config| + # config.before do |example| + # log example.description + # end + # + # config.after do |example| + # log example.description + # end + # + # config.around do |example| + # log example.description + # example.run + # end + # end + # + # shared_examples "auditable" do + # it "does something" do + # log "#{example.full_description}: #{auditable.inspect}" + # auditable.should do_something + # end + # end + # + # @see ExampleGroup + # @note Example blocks are evaluated in the context of an instance + # of an `ExampleGroup`, not in the context of an instance of `Example`. + class Example + # @private + # + # Used to define methods that delegate to this example's metadata. + def self.delegate_to_metadata(key) + define_method(key) { @metadata[key] } + end + + # @return [ExecutionResult] represents the result of running this example. + delegate_to_metadata :execution_result + # @return [String] the relative path to the file where this example was + # defined. + delegate_to_metadata :file_path + # @return [String] the full description (including the docstrings of + # all parent example groups). + delegate_to_metadata :full_description + # @return [String] the exact source location of this example in a form + # like `./path/to/spec.rb:17` + delegate_to_metadata :location + # @return [Boolean] flag that indicates that the example is not expected + # to pass. It will be run and will either have a pending result (if a + # failure occurs) or a failed result (if no failure occurs). + delegate_to_metadata :pending + # @return [Boolean] flag that will cause the example to not run. + # The {ExecutionResult} status will be `:pending`. + delegate_to_metadata :skip + + # Returns the string submitted to `example` or its aliases (e.g. + # `specify`, `it`, etc). If no string is submitted (e.g. + # `it { is_expected.to do_something }`) it returns the message generated + # by the matcher if there is one, otherwise returns a message including + # the location of the example. + def description + description = if metadata[:description].to_s.empty? + location_description + else + metadata[:description] + end + + RSpec.configuration.format_docstrings_block.call(description) + end + + # Returns a description of the example that always includes the location. + def inspect_output + inspect_output = "\"#{description}\"" + unless metadata[:description].to_s.empty? + inspect_output += " (#{location})" + end + inspect_output + end + + # Returns the location-based argument that can be passed to the `rspec` command to rerun this example. + def location_rerun_argument + @location_rerun_argument ||= begin + loaded_spec_files = RSpec.configuration.loaded_spec_files + + Metadata.ascending(metadata) do |meta| + return meta[:location] if loaded_spec_files.include?(meta[:absolute_file_path]) + end + end + end + + # Returns the location-based argument that can be passed to the `rspec` command to rerun this example. + # + # @deprecated Use {#location_rerun_argument} instead. + # @note If there are multiple examples identified by this location, they will use {#id} + # to rerun instead, but this method will still return the location (that's why it is deprecated!). + def rerun_argument + location_rerun_argument + end + + # @return [String] the unique id of this example. Pass + # this at the command line to re-run this exact example. + def id + @id ||= Metadata.id_from(metadata) + end + + # @private + def self.parse_id(id) + # http://rubular.com/r/OMZSAPcAfn + id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures + end + + # Duplicates the example and overrides metadata with the provided + # hash. + # + # @param metadata_overrides [Hash] the hash to override the example metadata + # @return [Example] a duplicate of the example with modified metadata + def duplicate_with(metadata_overrides={}) + new_metadata = metadata.clone.merge(metadata_overrides) + + RSpec::Core::Metadata::RESERVED_KEYS.each do |reserved_key| + new_metadata.delete reserved_key + end + + # don't clone the example group because the new example + # must belong to the same example group (not a clone). + # + # block is nil in new_metadata so we have to get it from metadata. + Example.new(example_group, description.clone, + new_metadata, metadata[:block]) + end + + # @private + def update_inherited_metadata(updates) + metadata.update(updates) do |_key, existing_example_value, _new_inherited_value| + existing_example_value + end + end + + # @attr_reader + # + # Returns the first exception raised in the context of running this + # example (nil if no exception is raised). + attr_reader :exception + + # @attr_reader + # + # Returns the metadata object associated with this example. + attr_reader :metadata + + # @attr_reader + # @private + # + # Returns the example_group_instance that provides the context for + # running this example. + attr_reader :example_group_instance + + # @attr + # @private + attr_accessor :clock + + # Creates a new instance of Example. + # @param example_group_class [Class] the subclass of ExampleGroup in which + # this Example is declared + # @param description [String] the String passed to the `it` method (or + # alias) + # @param user_metadata [Hash] additional args passed to `it` to be used as + # metadata + # @param example_block [Proc] the block of code that represents the + # example + # @api private + def initialize(example_group_class, description, user_metadata, example_block=nil) + @example_group_class = example_group_class + @example_block = example_block + + # Register the example with the group before creating the metadata hash. + # This is necessary since creating the metadata hash triggers + # `when_first_matching_example_defined` callbacks, in which users can + # load RSpec support code which defines hooks. For that to work, the + # examples and example groups must be registered at the time the + # support code is called or be defined afterwards. + # Begin defined beforehand but registered afterwards causes hooks to + # not be applied where they should. + example_group_class.examples << self + + @metadata = Metadata::ExampleHash.create( + @example_group_class.metadata, user_metadata, + example_group_class.method(:next_runnable_index_for), + description, example_block + ) + + config = RSpec.configuration + config.apply_derived_metadata_to(@metadata) + + # This should perhaps be done in `Metadata::ExampleHash.create`, + # but the logic there has no knowledge of `RSpec.world` and we + # want to keep it that way. It's easier to just assign it here. + @metadata[:last_run_status] = config.last_run_statuses[id] + + @example_group_instance = @exception = nil + @clock = RSpec::Core::Time + @reporter = RSpec::Core::NullReporter + end + + # Provide a human-readable representation of this class + def inspect + "#<#{self.class.name} #{description.inspect}>" + end + alias to_s inspect + + # @return [RSpec::Core::Reporter] the current reporter for the example + attr_reader :reporter + + # Returns the example group class that provides the context for running + # this example. + def example_group + @example_group_class + end + + def pending? + !!pending + end + + def skipped? + !!skip + end + + # @api private + # instance_execs the block passed to the constructor in the context of + # the instance of {ExampleGroup}. + # @param example_group_instance the instance of an ExampleGroup subclass + def run(example_group_instance, reporter) + @example_group_instance = example_group_instance + @reporter = reporter + RSpec.configuration.configure_example(self, hooks) + RSpec.current_example = self + + start(reporter) + Pending.mark_pending!(self, pending) if pending? + + begin + if skipped? + Pending.mark_pending! self, skip + elsif !RSpec.configuration.dry_run? + with_around_and_singleton_context_hooks do + begin + run_before_example + @example_group_instance.instance_exec(self, &@example_block) + + if pending? + Pending.mark_fixed! self + + raise Pending::PendingExampleFixedError, + 'Expected example to fail since it is pending, but it passed.', + [location] + end + rescue Pending::SkipDeclaredInExample => _ + # The "=> _" is normally useless but on JRuby it is a workaround + # for a bug that prevents us from getting backtraces: + # https://github.com/jruby/jruby/issues/4467 + # + # no-op, required metadata has already been set by the `skip` + # method. + rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e + set_exception(e) + ensure + run_after_example + end + end + end + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + ensure + @example_group_instance = nil # if you love something... let it go + end + + finish(reporter) + ensure + execution_result.ensure_timing_set(clock) + RSpec.current_example = nil + end + + if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9 + # :nocov: + # For some reason, rescuing `Support::AllExceptionsExceptOnesWeMustNotRescue` + # in place of `Exception` above can cause the exit status to be the wrong + # thing. I have no idea why. See: + # https://github.com/rspec/rspec-core/pull/2063#discussion_r38284978 + # @private + AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Exception + # :nocov: + else + # @private + AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Support::AllExceptionsExceptOnesWeMustNotRescue + end + + # Wraps both a `Proc` and an {Example} for use in {Hooks#around + # around} hooks. In around hooks we need to yield this special + # kind of object (rather than the raw {Example}) because when + # there are multiple `around` hooks we have to wrap them recursively. + # + # @example + # + # RSpec.configure do |c| + # c.around do |ex| # Procsy which wraps the example + # if ex.metadata[:key] == :some_value && some_global_condition + # raise "some message" + # end + # ex.run # run delegates to ex.call. + # end + # end + # + # @note This class also exposes the instance methods of {Example}, + # proxying them through to the wrapped {Example} instance. + class Procsy + # The {Example} instance. + attr_reader :example + + Example.public_instance_methods(false).each do |name| + name_sym = name.to_sym + next if name_sym == :run || name_sym == :inspect || name_sym == :to_s + + define_method(name) { |*a, &b| @example.__send__(name, *a, &b) } + end + + Proc.public_instance_methods(false).each do |name| + name_sym = name.to_sym + next if name_sym == :call || name_sym == :inspect || name_sym == :to_s || name_sym == :to_proc + + define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) } + end + + # Calls the proc and notes that the example has been executed. + def call(*args, &block) + @executed = true + @proc.call(*args, &block) + end + alias run call + + # Provides a wrapped proc that will update our `executed?` state when + # executed. + def to_proc + method(:call).to_proc + end + + def initialize(example, &block) + @example = example + @proc = block + @executed = false + end + + # @private + def wrap(&block) + self.class.new(example, &block) + end + + # Indicates whether or not the around hook has executed the example. + def executed? + @executed + end + + # @private + def inspect + @example.inspect.gsub('Example', 'ExampleProcsy') + end + end + + # @private + # + # The exception that will be displayed to the user -- either the failure of + # the example or the `pending_exception` if the example is pending. + def display_exception + @exception || execution_result.pending_exception + end + + # @private + # + # Assigns the exception that will be displayed to the user -- either the failure of + # the example or the `pending_exception` if the example is pending. + def display_exception=(ex) + if pending? && !(Pending::PendingExampleFixedError === ex) + @exception = nil + execution_result.pending_fixed = false + execution_result.pending_exception = ex + else + @exception = ex + end + end + + # rubocop:disable Naming/AccessorMethodName + + # @private + # + # Used internally to set an exception in an after hook, which + # captures the exception but doesn't raise it. + def set_exception(exception) + return self.display_exception = exception unless display_exception + + unless RSpec::Core::MultipleExceptionError === display_exception + self.display_exception = RSpec::Core::MultipleExceptionError.new(display_exception) + end + + display_exception.add exception + end + + # @private + # + # Used to set the exception when `aggregate_failures` fails. + def set_aggregate_failures_exception(exception) + return set_exception(exception) unless display_exception + + exception = RSpec::Core::MultipleExceptionError::InterfaceTag.for(exception) + exception.add display_exception + self.display_exception = exception + end + + # rubocop:enable Naming/AccessorMethodName + + # @private + # + # Used internally to set an exception and fail without actually executing + # the example when an exception is raised in before(:context). + def fail_with_exception(reporter, exception) + start(reporter) + set_exception(exception) + finish(reporter) + end + + # @private + # + # Used internally to skip without actually executing the example when + # skip is used in before(:context). + def skip_with_exception(reporter, exception) + start(reporter) + Pending.mark_skipped! self, exception.argument + finish(reporter) + end + + # @private + def instance_exec(*args, &block) + @example_group_instance.instance_exec(*args, &block) + end + + private + + def hooks + example_group_instance.singleton_class.hooks + end + + def with_around_example_hooks + hooks.run(:around, :example, self) { yield } + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + end + + def start(reporter) + reporter.example_started(self) + execution_result.started_at = clock.now + end + + def finish(reporter) + pending_message = execution_result.pending_message + + if @exception + execution_result.exception = @exception + record_finished :failed, reporter + reporter.example_failed self + false + elsif pending_message + execution_result.pending_message = pending_message + record_finished :pending, reporter + reporter.example_pending self + true + else + record_finished :passed, reporter + reporter.example_passed self + true + end + end + + def record_finished(status, reporter) + execution_result.record_finished(status, clock.now) + reporter.example_finished(self) + end + + def run_before_example + @example_group_instance.setup_mocks_for_rspec + hooks.run(:before, :example, self) + end + + def with_around_and_singleton_context_hooks + singleton_context_hooks_host = example_group_instance.singleton_class + singleton_context_hooks_host.run_before_context_hooks(example_group_instance) + with_around_example_hooks { yield } + ensure + singleton_context_hooks_host.run_after_context_hooks(example_group_instance) + end + + def run_after_example + assign_generated_description if defined?(::RSpec::Matchers) + hooks.run(:after, :example, self) + verify_mocks + ensure + @example_group_instance.teardown_mocks_for_rspec + end + + def verify_mocks + @example_group_instance.verify_mocks_for_rspec if mocks_need_verification? + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + end + + def mocks_need_verification? + exception.nil? || execution_result.pending_fixed? + end + + def assign_generated_description + if metadata[:description].empty? && (description = generate_description) + metadata[:description] = description + metadata[:full_description] += description + end + ensure + RSpec::Matchers.clear_generated_description + end + + def generate_description + RSpec::Matchers.generated_description + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + location_description + " (Got an error when generating description " \ + "from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})" + end + + def location_description + "example at #{location}" + end + + # Represents the result of executing an example. + # Behaves like a hash for backwards compatibility. + class ExecutionResult + include HashImitatable + + # @return [Symbol] `:passed`, `:failed` or `:pending`. + attr_accessor :status + + # @return [Exception, nil] The failure, if there was one. + attr_accessor :exception + + # @return [Time] When the example started. + attr_accessor :started_at + + # @return [Time] When the example finished. + attr_accessor :finished_at + + # @return [Float] How long the example took in seconds. + attr_accessor :run_time + + # @return [String, nil] The reason the example was pending, + # or nil if the example was not pending. + attr_accessor :pending_message + + # @return [Exception, nil] The exception triggered while + # executing the pending example. If no exception was triggered + # it would no longer get a status of `:pending` unless it was + # tagged with `:skip`. + attr_accessor :pending_exception + + # @return [Boolean] For examples tagged with `:pending`, + # this indicates whether or not it now passes. + attr_accessor :pending_fixed + + def pending_fixed? + !!pending_fixed + end + + # @return [Boolean] Indicates if the example was completely skipped + # (typically done via `:skip` metadata or the `skip` method). Skipped examples + # will have a `:pending` result. A `:pending` result can also come from examples + # that were marked as `:pending`, which causes them to be run, and produces a + # `:failed` result if the example passes. + def example_skipped? + status == :pending && !pending_exception + end + + # @api private + # Records the finished status of the example. + def record_finished(status, finished_at) + self.status = status + calculate_run_time(finished_at) + end + + # @api private + # Populates finished_at and run_time if it has not yet been set + def ensure_timing_set(clock) + calculate_run_time(clock.now) unless finished_at + end + + private + + def calculate_run_time(finished_at) + self.finished_at = finished_at + self.run_time = (finished_at - started_at).to_f + end + + # For backwards compatibility we present `status` as a string + # when presenting the legacy hash interface. + def hash_for_delegation + super.tap do |hash| + hash[:status] &&= status.to_s + end + end + + def set_value(name, value) + value &&= value.to_sym if name == :status + super(name, value) + end + + def get_value(name) + if name == :status + status.to_s if status + else + super + end + end + + def issue_deprecation(_method_name, *_args) + RSpec.deprecate("Treating `metadata[:execution_result]` as a hash", + :replacement => "the attributes methods to access the data") + end + end + end + + # @private + # Provides an execution context for before/after :suite hooks. + class SuiteHookContext < Example + def initialize(hook_description, reporter) + super(AnonymousExampleGroup, hook_description, {}) + @example_group_instance = AnonymousExampleGroup.new + @reporter = reporter + end + + # rubocop:disable Naming/AccessorMethodName + def set_exception(exception) + reporter.notify_non_example_exception(exception, "An error occurred in #{description}.") + RSpec.world.wants_to_quit = true + end + # rubocop:enable Naming/AccessorMethodName + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb new file mode 100644 index 0000000000..5efee9f386 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb @@ -0,0 +1,901 @@ +RSpec::Support.require_rspec_support 'recursive_const_methods' + +module RSpec + module Core + # rubocop:disable Metrics/ClassLength + + # ExampleGroup and {Example} are the main structural elements of + # rspec-core. Consider this example: + # + # RSpec.describe Thing do + # it "does something" do + # end + # end + # + # The object returned by `describe Thing` is a subclass of ExampleGroup. + # The object returned by `it "does something"` is an instance of Example, + # which serves as a wrapper for an instance of the ExampleGroup in which it + # is declared. + # + # Example group bodies (e.g. `describe` or `context` blocks) are evaluated + # in the context of a new subclass of ExampleGroup. Individual examples are + # evaluated in the context of an instance of the specific ExampleGroup + # subclass to which they belong. + # + # Besides the class methods defined here, there are other interesting macros + # defined in {Hooks}, {MemoizedHelpers::ClassMethods} and + # {SharedExampleGroup}. There are additional instance methods available to + # your examples defined in {MemoizedHelpers} and {Pending}. + class ExampleGroup + extend Hooks + + include MemoizedHelpers + extend MemoizedHelpers::ClassMethods + include Pending + extend SharedExampleGroup + + # Define a singleton method for the singleton class (remove the method if + # it's already been defined). + # @private + def self.idempotently_define_singleton_method(name, &definition) + (class << self; self; end).module_exec do + remove_method(name) if method_defined?(name) && instance_method(name).owner == self + define_method(name, &definition) + end + end + + # @!group Metadata + + # The [Metadata](Metadata) object associated with this group. + # @see Metadata + def self.metadata + @metadata ||= nil + end + + # Temporarily replace the provided metadata. + # Intended primarily to allow an example group's singleton class + # to return the metadata of the example that it exists for. This + # is necessary for shared example group inclusion to work properly + # with singleton example groups. + # @private + def self.with_replaced_metadata(meta) + orig_metadata = metadata + @metadata = meta + yield + ensure + @metadata = orig_metadata + end + + # @private + # @return [Metadata] belonging to the parent of a nested {ExampleGroup} + def self.superclass_metadata + @superclass_metadata ||= superclass.respond_to?(:metadata) ? superclass.metadata : nil + end + + # @private + def self.delegate_to_metadata(*names) + names.each do |name| + idempotently_define_singleton_method(name) { metadata.fetch(name) } + end + end + + delegate_to_metadata :described_class, :file_path, :location + + # @return [String] the current example group description + def self.description + description = metadata[:description] + RSpec.configuration.format_docstrings_block.call(description) + end + + # Returns the class or module passed to the `describe` method (or alias). + # Returns nil if the subject is not a class or module. + # @example + # RSpec.describe Thing do + # it "does something" do + # described_class == Thing + # end + # end + # + def described_class + self.class.described_class + end + + # @!endgroup + + # @!group Defining Examples + + # @private + # @macro [attach] define_example_method + # @!scope class + # @method $1 + # @overload $1 + # @overload $1(&example_implementation) + # @param example_implementation [Block] The implementation of the example. + # @overload $1(doc_string, *metadata) + # @param doc_string [String] The example's doc string. + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. + # @overload $1(doc_string, *metadata, &example_implementation) + # @param doc_string [String] The example's doc string. + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. + # @param example_implementation [Block] The implementation of the example. + # @yield [Example] the example object + # @example + # $1 do + # end + # + # $1 "does something" do + # end + # + # $1 "does something", :slow, :uses_js do + # end + # + # $1 "does something", :with => 'additional metadata' do + # end + # + # $1 "does something" do |ex| + # # ex is the Example object that contains metadata about the example + # end + # + # @example + # $1 "does something", :slow, :load_factor => 100 do + # end + # + def self.define_example_method(name, extra_options={}) + idempotently_define_singleton_method(name) do |*all_args, &block| + desc, *args = *all_args + + options = Metadata.build_hash_from(args) + options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block + options.update(extra_options) + + RSpec::Core::Example.new(self, desc, options, block) + end + end + + # Defines an example within a group. + define_example_method :example + # Defines an example within a group. + # This is the primary API to define a code example. + define_example_method :it + # Defines an example within a group. + # Useful for when your docstring does not read well off of `it`. + # @example + # RSpec.describe MyClass do + # specify "#do_something is deprecated" do + # # ... + # end + # end + define_example_method :specify + + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :focus, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fexample, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fit, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fspecify, :focus => true + # Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'`. + # @see example + define_example_method :xexample, :skip => 'Temporarily skipped with xexample' + # Shortcut to define an example with `:skip => 'Temporarily skipped with xit'`. + # @see example + define_example_method :xit, :skip => 'Temporarily skipped with xit' + # Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'`. + # @see example + define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify' + # Shortcut to define an example with `:skip => true` + # @see example + define_example_method :skip, :skip => true + # Shortcut to define an example with `:pending => true` + # @see example + define_example_method :pending, :pending => true + + # @!endgroup + + # @!group Defining Example Groups + + # @private + # @macro [attach] define_example_group_method + # @!scope class + # @overload $1 + # @overload $1(&example_group_definition) + # @param example_group_definition [Block] The definition of the example group. + # @overload $1(doc_string, *metadata, &example_implementation) + # @param doc_string [String] The group's doc string. + # @param metadata [Array, Hash] Metadata for the group. + # Symbols will be transformed into hash entries with `true` values. + # @param example_group_definition [Block] The definition of the example group. + # + # Generates a subclass of this example group which inherits + # everything except the examples themselves. + # + # @example + # + # RSpec.describe "something" do # << This describe method is defined in + # # << RSpec::Core::DSL, included in the + # # << global namespace (optional) + # before do + # do_something_before + # end + # + # before(:example, :clean_env) do + # env.clear! + # end + # + # let(:thing) { Thing.new } + # + # $1 "attribute (of something)" do + # # examples in the group get the before hook + # # declared above, and can access `thing` + # end + # + # $1 "needs additional setup", :clean_env, :implementation => JSON do + # # specifies that hooks with matching metadata + # # should be be run additionally + # end + # end + # + # @see DSL#describe + def self.define_example_group_method(name, metadata={}) + idempotently_define_singleton_method(name) do |*args, &example_group_block| + thread_data = RSpec::Support.thread_local_data + top_level = self == ExampleGroup + + registration_collection = + if top_level + if thread_data[:in_example_group] + raise "Creating an isolated context from within a context is " \ + "not allowed. Change `RSpec.#{name}` to `#{name}` or " \ + "move this to a top-level scope." + end + + thread_data[:in_example_group] = true + RSpec.world.example_groups + else + children + end + + begin + description = args.shift + combined_metadata = metadata.dup + combined_metadata.merge!(args.pop) if args.last.is_a? Hash + args << combined_metadata + + subclass(self, description, args, registration_collection, &example_group_block) + ensure + thread_data.delete(:in_example_group) if top_level + end + end + + RSpec::Core::DSL.expose_example_group_alias(name) + end + + define_example_group_method :example_group + + # An alias of `example_group`. Generally used when grouping examples by a + # thing you are describing (e.g. an object, class or method). + # @see example_group + define_example_group_method :describe + + # An alias of `example_group`. Generally used when grouping examples + # contextually (e.g. "with xyz", "when xyz" or "if xyz"). + # @see example_group + define_example_group_method :context + + # Shortcut to temporarily make an example group skipped. + # @see example_group + define_example_group_method :xdescribe, :skip => "Temporarily skipped with xdescribe" + + # Shortcut to temporarily make an example group skipped. + # @see example_group + define_example_group_method :xcontext, :skip => "Temporarily skipped with xcontext" + + # Shortcut to define an example group with `:focus => true`. + # @see example_group + define_example_group_method :fdescribe, :focus => true + + # Shortcut to define an example group with `:focus => true`. + # @see example_group + define_example_group_method :fcontext, :focus => true + + # @!endgroup + + # @!group Including Shared Example Groups + + # @private + # @macro [attach] define_nested_shared_group_method + # @!scope class + # + # @see SharedExampleGroup + def self.define_nested_shared_group_method(new_name, report_label="it should behave like") + idempotently_define_singleton_method(new_name) do |name, *args, &customization_block| + # Pass :caller so the :location metadata is set properly. + # Otherwise, it'll be set to the next line because that's + # the block's source_location. + group = example_group("#{report_label} #{name}", :caller => (the_caller = caller)) do + find_and_eval_shared("examples", name, the_caller.first, *args, &customization_block) + end + group.metadata[:shared_group_name] = name + group + end + end + + # Generates a nested example group and includes the shared content + # mapped to `name` in the nested group. + define_nested_shared_group_method :it_behaves_like, "behaves like" + # Generates a nested example group and includes the shared content + # mapped to `name` in the nested group. + define_nested_shared_group_method :it_should_behave_like + + # Includes shared content mapped to `name` directly in the group in which + # it is declared, as opposed to `it_behaves_like`, which creates a nested + # group. If given a block, that block is also eval'd in the current + # context. + # + # @see SharedExampleGroup + def self.include_context(name, *args, &block) + find_and_eval_shared("context", name, caller.first, *args, &block) + end + + # Includes shared content mapped to `name` directly in the group in which + # it is declared, as opposed to `it_behaves_like`, which creates a nested + # group. If given a block, that block is also eval'd in the current + # context. + # + # @see SharedExampleGroup + def self.include_examples(name, *args, &block) + find_and_eval_shared("examples", name, caller.first, *args, &block) + end + + # Clear memoized values when adding/removing examples + # @private + def self.reset_memoized + @descendant_filtered_examples = nil + @_descendants = nil + @parent_groups = nil + @declaration_locations = nil + end + + # Adds an example to the example group + def self.add_example(example) + reset_memoized + examples << example + end + + # Removes an example from the example group + def self.remove_example(example) + reset_memoized + examples.delete example + end + + # @private + def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block) + shared_module = RSpec.world.shared_example_group_registry.find(parent_groups, name) + + unless shared_module + raise ArgumentError, "Could not find shared #{label} #{name.inspect}" + end + + shared_module.include_in( + self, Metadata.relative_path(inclusion_location), + args, customization_block + ) + end + + # @!endgroup + + # @private + def self.subclass(parent, description, args, registration_collection, &example_group_block) + subclass = Class.new(parent) + subclass.set_it_up(description, args, registration_collection, &example_group_block) + subclass.module_exec(&example_group_block) if example_group_block + + # The LetDefinitions module must be included _after_ other modules + # to ensure that it takes precedence when there are name collisions. + # Thus, we delay including it until after the example group block + # has been eval'd. + MemoizedHelpers.define_helpers_on(subclass) + + subclass + end + + # @private + def self.set_it_up(description, args, registration_collection, &example_group_block) + # Ruby 1.9 has a bug that can lead to infinite recursion and a + # SystemStackError if you include a module in a superclass after + # including it in a subclass: https://gist.github.com/845896 + # To prevent this, we must include any modules in + # RSpec::Core::ExampleGroup before users create example groups and have + # a chance to include the same module in a subclass of + # RSpec::Core::ExampleGroup. So we need to configure example groups + # here. + ensure_example_groups_are_configured + + # Register the example with the group before creating the metadata hash. + # This is necessary since creating the metadata hash triggers + # `when_first_matching_example_defined` callbacks, in which users can + # load RSpec support code which defines hooks. For that to work, the + # examples and example groups must be registered at the time the + # support code is called or be defined afterwards. + # Begin defined beforehand but registered afterwards causes hooks to + # not be applied where they should. + registration_collection << self + + @user_metadata = Metadata.build_hash_from(args) + + @metadata = Metadata::ExampleGroupHash.create( + superclass_metadata, @user_metadata, + superclass.method(:next_runnable_index_for), + description, *args, &example_group_block + ) + + config = RSpec.configuration + config.apply_derived_metadata_to(@metadata) + + ExampleGroups.assign_const(self) + + @currently_executing_a_context_hook = false + + config.configure_group(self) + end + + # @private + def self.examples + @examples ||= [] + end + + # @private + def self.filtered_examples + RSpec.world.filtered_examples[self] + end + + # @private + def self.descendant_filtered_examples + @descendant_filtered_examples ||= filtered_examples + + FlatMap.flat_map(children, &:descendant_filtered_examples) + end + + # @private + def self.children + @children ||= [] + end + + # @private + # Traverses the tree of groups, starting with `self`, then the children, recursively. + # Halts the traversal of a branch of the tree as soon as the passed block returns true. + # Note that siblings groups and their sub-trees will continue to be explored. + # This is intended to make it easy to find the top-most group that satisfies some + # condition. + def self.traverse_tree_until(&block) + return if yield self + + children.each do |child| + child.traverse_tree_until(&block) + end + end + + # @private + def self.next_runnable_index_for(file) + if self == ExampleGroup + # We add 1 so the ids start at 1 instead of 0. This is + # necessary for this branch (but not for the other one) + # because we register examples and groups with the + # `children` and `examples` collection BEFORE this + # method is called as part of metadata hash creation, + # but the example group is recorded with + # `RSpec.world.example_group_counts_by_spec_file` AFTER + # the metadata hash is created and the group is returned + # to the caller. + RSpec.world.num_example_groups_defined_in(file) + 1 + else + children.count + examples.count + end + end + + # @private + def self.descendants + @_descendants ||= [self] + FlatMap.flat_map(children, &:descendants) + end + + ## @private + def self.parent_groups + @parent_groups ||= ancestors.select { |a| a < RSpec::Core::ExampleGroup } + end + + # @private + def self.top_level? + superclass == ExampleGroup + end + + # @private + def self.ensure_example_groups_are_configured + unless defined?(@@example_groups_configured) + RSpec.configuration.configure_mock_framework + RSpec.configuration.configure_expectation_framework + # rubocop:disable Style/ClassVars + @@example_groups_configured = true + # rubocop:enable Style/ClassVars + end + end + + # @private + def self.before_context_ivars + @before_context_ivars ||= {} + end + + # @private + def self.store_before_context_ivars(example_group_instance) + each_instance_variable_for_example(example_group_instance) do |ivar| + before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar) + end + end + + # Returns true if a `before(:context)` or `after(:context)` + # hook is currently executing. + def self.currently_executing_a_context_hook? + @currently_executing_a_context_hook + end + + # @private + def self.run_before_context_hooks(example_group_instance) + set_ivars(example_group_instance, superclass_before_context_ivars) + + @currently_executing_a_context_hook = true + + ContextHookMemoized::Before.isolate_for_context_hook(example_group_instance) do + hooks.run(:before, :context, example_group_instance) + end + ensure + store_before_context_ivars(example_group_instance) + @currently_executing_a_context_hook = false + end + + if RUBY_VERSION.to_f >= 1.9 + # @private + def self.superclass_before_context_ivars + superclass.before_context_ivars + end + else # 1.8.7 + # :nocov: + # @private + def self.superclass_before_context_ivars + if superclass.respond_to?(:before_context_ivars) + superclass.before_context_ivars + else + # `self` must be the singleton class of an ExampleGroup instance. + # On 1.8.7, the superclass of a singleton class of an instance of A + # is A's singleton class. On 1.9+, it's A. On 1.8.7, the first ancestor + # is A, so we can mirror 1.8.7's behavior here. Note that we have to + # search for the first that responds to `before_context_ivars` + # in case a module has been included in the singleton class. + ancestors.find { |a| a.respond_to?(:before_context_ivars) }.before_context_ivars + end + end + # :nocov: + end + + # @private + def self.run_after_context_hooks(example_group_instance) + set_ivars(example_group_instance, before_context_ivars) + + @currently_executing_a_context_hook = true + + ContextHookMemoized::After.isolate_for_context_hook(example_group_instance) do + hooks.run(:after, :context, example_group_instance) + end + ensure + before_context_ivars.clear + @currently_executing_a_context_hook = false + end + + # Runs all the examples in this group. + def self.run(reporter=RSpec::Core::NullReporter) + return if RSpec.world.wants_to_quit + reporter.example_group_started(self) + + should_run_context_hooks = descendant_filtered_examples.any? + begin + run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks + result_for_this_group = run_examples(reporter) + results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all? + result_for_this_group && results_for_descendants + rescue Pending::SkipDeclaredInExample => ex + for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) } + true + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) } + RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met? + false + ensure + run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks + reporter.example_group_finished(self) + end + end + + # @private + def self.ordering_strategy + order = metadata.fetch(:order, :global) + registry = RSpec.configuration.ordering_registry + + registry.fetch(order) do + warn <<-WARNING.gsub(/^ +\|/, '') + |WARNING: Ignoring unknown ordering specified using `:order => #{order.inspect}` metadata. + | Falling back to configured global ordering. + | Unrecognized ordering specified at: #{location} + WARNING + + registry.fetch(:global) + end + end + + # @private + def self.run_examples(reporter) + ordering_strategy.order(filtered_examples).map do |example| + next if RSpec.world.wants_to_quit + instance = new(example.inspect_output) + set_ivars(instance, before_context_ivars) + succeeded = example.run(instance, reporter) + if !succeeded && reporter.fail_fast_limit_met? + RSpec.world.wants_to_quit = true + end + succeeded + end.all? + end + + # @private + def self.for_filtered_examples(reporter, &block) + filtered_examples.each(&block) + + children.each do |child| + reporter.example_group_started(child) + child.for_filtered_examples(reporter, &block) + reporter.example_group_finished(child) + end + false + end + + # @private + def self.declaration_locations + @declaration_locations ||= [Metadata.location_tuple_from(metadata)] + + examples.map { |e| Metadata.location_tuple_from(e.metadata) } + + FlatMap.flat_map(children, &:declaration_locations) + end + + # @return [String] the unique id of this example group. Pass + # this at the command line to re-run this exact example group. + def self.id + Metadata.id_from(metadata) + end + + # @private + def self.top_level_description + parent_groups.last.description + end + + # @private + def self.set_ivars(instance, ivars) + ivars.each { |name, value| instance.instance_variable_set(name, value) } + end + + if RUBY_VERSION.to_f < 1.9 + # :nocov: + # @private + INSTANCE_VARIABLE_TO_IGNORE = '@__inspect_output'.freeze + # :nocov: + else + # @private + INSTANCE_VARIABLE_TO_IGNORE = :@__inspect_output + end + + # @private + def self.each_instance_variable_for_example(group) + group.instance_variables.each do |ivar| + yield ivar unless ivar == INSTANCE_VARIABLE_TO_IGNORE + end + end + + def initialize(inspect_output=nil) + @__inspect_output = inspect_output || '(no description provided)' + super() # no args get passed + end + + # @private + def inspect + "#<#{self.class} #{@__inspect_output}>" + end + + unless method_defined?(:singleton_class) # for 1.8.7 + # :nocov: + # @private + def singleton_class + class << self; self; end + end + # :nocov: + end + + # @private + def self.update_inherited_metadata(updates) + metadata.update(updates) do |key, existing_group_value, new_inherited_value| + @user_metadata.key?(key) ? existing_group_value : new_inherited_value + end + + RSpec.configuration.configure_group(self) + examples.each { |ex| ex.update_inherited_metadata(updates) } + children.each { |group| group.update_inherited_metadata(updates) } + end + + # Raised when an RSpec API is called in the wrong scope, such as `before` + # being called from within an example rather than from within an example + # group block. + WrongScopeError = Class.new(NoMethodError) + + def self.method_missing(name, *args) + if method_defined?(name) + raise WrongScopeError, + "`#{name}` is not available on an example group (e.g. a " \ + "`describe` or `context` block). It is only available from " \ + "within individual examples (e.g. `it` blocks) or from " \ + "constructs that run in the scope of an example (e.g. " \ + "`before`, `let`, etc)." + end + + super + end + private_class_method :method_missing + + private + + def method_missing(name, *args) + if self.class.respond_to?(name) + raise WrongScopeError, + "`#{name}` is not available from within an example (e.g. an " \ + "`it` block) or from constructs that run in the scope of an " \ + "example (e.g. `before`, `let`, etc). It is only available " \ + "on an example group (e.g. a `describe` or `context` block)." + end + + super(name, *args) + end + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + end + # rubocop:enable Metrics/ClassLength + + # @private + # Unnamed example group used by `SuiteHookContext`. + class AnonymousExampleGroup < ExampleGroup + def self.metadata + {} + end + end + + # Contains information about the inclusion site of a shared example group. + class SharedExampleGroupInclusionStackFrame + # @return [String] the name of the shared example group + attr_reader :shared_group_name + # @return [String] the location where the shared example was included + attr_reader :inclusion_location + + def initialize(shared_group_name, inclusion_location) + @shared_group_name = shared_group_name + @inclusion_location = inclusion_location + end + + # @return [String] The {#inclusion_location}, formatted for display by a formatter. + def formatted_inclusion_location + @formatted_inclusion_location ||= begin + RSpec.configuration.backtrace_formatter.backtrace_line( + inclusion_location.sub(/(:\d+):in .+$/, '\1') + ) + end + end + + # @return [String] Description of this stack frame, in the form used by + # RSpec's built-in formatters. + def description + @description ||= "Shared Example Group: #{shared_group_name.inspect} " \ + "called from #{formatted_inclusion_location}" + end + + # @private + def self.current_backtrace + shared_example_group_inclusions.reverse + end + + # @private + def self.with_frame(name, location) + current_stack = shared_example_group_inclusions + if current_stack.any? { |frame| frame.shared_group_name == name } + raise ArgumentError, "can't include shared examples recursively" + else + current_stack << new(name, location) + yield + end + ensure + current_stack.pop + end + + # @private + def self.shared_example_group_inclusions + RSpec::Support.thread_local_data[:shared_example_group_inclusions] ||= [] + end + end + end + + # @private + # + # Namespace for the example group subclasses generated by top-level + # `describe`. + module ExampleGroups + extend Support::RecursiveConstMethods + + def self.assign_const(group) + base_name = base_name_for(group) + const_scope = constant_scope_for(group) + name = disambiguate(base_name, const_scope) + + const_scope.const_set(name, group) + end + + def self.constant_scope_for(group) + const_scope = group.superclass + const_scope = self if const_scope == ::RSpec::Core::ExampleGroup + const_scope + end + + def self.remove_all_constants + constants.each do |constant| + __send__(:remove_const, constant) + end + end + + def self.base_name_for(group) + return "Anonymous".dup if group.description.empty? + + # Convert to CamelCase. + name = ' ' + group.description + name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do + match = ::Regexp.last_match[1] + match.upcase! + match + end + + name.lstrip! # Remove leading whitespace + name.gsub!(/\W/, ''.freeze) # JRuby, RBX and others don't like non-ascii in const names + + # Ruby requires first const letter to be A-Z. Use `Nested` + # as necessary to enforce that. + name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1'.freeze) + + name + end + + if RUBY_VERSION == '1.9.2' + # :nocov: + class << self + alias _base_name_for base_name_for + def base_name_for(group) + _base_name_for(group) + '_' + end + end + private_class_method :_base_name_for + # :nocov: + end + + def self.disambiguate(name, const_scope) + return name unless const_defined_on?(const_scope, name) + + # Add a trailing number if needed to disambiguate from an existing + # constant. + name << "_2" + name.next! while const_defined_on?(const_scope, name) + name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_status_persister.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_status_persister.rb new file mode 100644 index 0000000000..7da584e68b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/example_status_persister.rb @@ -0,0 +1,235 @@ +RSpec::Support.require_rspec_support "directory_maker" + +module RSpec + module Core + # Persists example ids and their statuses so that we can filter + # to just the ones that failed the last time they ran. + # @private + class ExampleStatusPersister + def self.load_from(file_name) + return [] unless File.exist?(file_name) + ExampleStatusParser.parse(File.read(file_name)) + end + + def self.persist(examples, file_name) + new(examples, file_name).persist + end + + def initialize(examples, file_name) + @examples = examples + @file_name = file_name + end + + def persist + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name)) + File.open(@file_name, File::RDWR | File::CREAT) do |f| + # lock the file while reading / persisting to avoid a race + # condition where parallel or unrelated spec runs race to + # update the same file + f.flock(File::LOCK_EX) + unparsed_previous_runs = f.read + f.rewind + f.write(dump_statuses(unparsed_previous_runs)) + f.flush + f.truncate(f.pos) + end + end + + private + + def dump_statuses(unparsed_previous_runs) + statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs) + merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs) + ExampleStatusDumper.dump(merged_statuses) + end + + def statuses_from_this_run + @examples.map do |ex| + result = ex.execution_result + + { + :example_id => ex.id, + :status => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS, + :run_time => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : "" + } + end + end + end + + # Merges together a list of example statuses from this run + # and a list from previous runs (presumably loaded from disk). + # Each example status object is expected to be a hash with + # at least an `:example_id` and a `:status` key. Examples that + # were loaded but not executed (due to filtering, `--fail-fast` + # or whatever) should have a `:status` of `UNKNOWN_STATUS`. + # + # This willl produce a new list that: + # - Will be missing examples from previous runs that we know for sure + # no longer exist. + # - Will have the latest known status for any examples that either + # definitively do exist or may still exist. + # - Is sorted by file name and example definition order, so that + # the saved file is easily scannable if users want to inspect it. + # @private + class ExampleStatusMerger + def self.merge(this_run, from_previous_runs) + new(this_run, from_previous_runs).merge + end + + def initialize(this_run, from_previous_runs) + @this_run = hash_from(this_run) + @from_previous_runs = hash_from(from_previous_runs) + @file_exists_cache = Hash.new { |hash, file| hash[file] = File.exist?(file) } + end + + def merge + delete_previous_examples_that_no_longer_exist + + @this_run.merge(@from_previous_runs) do |_ex_id, new, old| + new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new + end.values.sort_by(&method(:sort_value_from)) + end + + private + + def hash_from(example_list) + example_list.inject({}) do |hash, example| + hash[example.fetch(:example_id)] = example + hash + end + end + + def delete_previous_examples_that_no_longer_exist + @from_previous_runs.delete_if do |ex_id, _| + example_must_no_longer_exist?(ex_id) + end + end + + def example_must_no_longer_exist?(ex_id) + # Obviously, it exists if it was loaded for this spec run... + return false if @this_run.key?(ex_id) + + spec_file = spec_file_from(ex_id) + + # `this_run` includes examples that were loaded but not executed. + # Given that, if the spec file for this example was loaded, + # but the id does not still exist, it's safe to assume that + # the example must no longer exist. + return true if loaded_spec_files.include?(spec_file) + + # The example may still exist as long as the file exists... + !@file_exists_cache[spec_file] + end + + def loaded_spec_files + @loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from))) + end + + def spec_file_from(ex_id) + ex_id.split("[").first + end + + def sort_value_from(example) + file, scoped_id = Example.parse_id(example.fetch(:example_id)) + [file, *scoped_id.split(":").map(&method(:Integer))] + end + end + + # Dumps a list of hashes in a pretty, human readable format + # for later parsing. The hashes are expected to have symbol + # keys and string values, and each hash should have the same + # set of keys. + # @private + class ExampleStatusDumper + def self.dump(examples) + new(examples).dump + end + + def initialize(examples) + @examples = examples + end + + def dump + return nil if @examples.empty? + (formatted_header_rows + formatted_value_rows).join("\n") << "\n" + end + + private + + def formatted_header_rows + @formatted_header_rows ||= begin + dividers = column_widths.map { |w| "-" * w } + [formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)] + end + end + + def formatted_value_rows + @foramtted_value_rows ||= rows.map do |row| + formatted_row_from(row) + end + end + + def rows + @rows ||= @examples.map { |ex| ex.values_at(*headers) } + end + + def formatted_row_from(row_values) + padded_values = row_values.each_with_index.map do |value, index| + value.ljust(column_widths[index]) + end + + padded_values.join(" | ") << " |" + end + + def headers + @headers ||= @examples.first.keys + end + + def column_widths + @column_widths ||= begin + value_sets = rows.transpose + + headers.each_with_index.map do |header, index| + values = value_sets[index] << header.to_s + values.map(&:length).max + end + end + end + end + + # Parses a string that has been previously dumped by ExampleStatusDumper. + # Note that this parser is a bit naive in that it does a simple split on + # "\n" and " | ", with no concern for handling escaping. For now, that's + # OK because the values we plan to persist (example id, status, and perhaps + # example duration) are highly unlikely to contain "\n" or " | " -- after + # all, who puts those in file names? + # @private + class ExampleStatusParser + def self.parse(string) + new(string).parse + end + + def initialize(string) + @header_line, _, *@row_lines = string.lines.to_a + end + + def parse + @row_lines.map { |line| parse_row(line) } + end + + private + + def parse_row(line) + Hash[headers.zip(split_line(line))] + end + + def headers + @headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym) + end + + def split_line(line) + line.split(/\s+\|\s+?/, -1) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/filter_manager.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/filter_manager.rb new file mode 100644 index 0000000000..7fe6cb18d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/filter_manager.rb @@ -0,0 +1,231 @@ +module RSpec + module Core + # @private + class FilterManager + attr_reader :exclusions, :inclusions + + def initialize + @exclusions, @inclusions = FilterRules.build + end + + # @api private + # + # @param file_path [String] + # @param line_numbers [Array] + def add_location(file_path, line_numbers) + # locations is a hash of expanded paths to arrays of line + # numbers to match against. e.g. + # { "path/to/file.rb" => [37, 42] } + add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers) + end + + def add_ids(rerun_path, scoped_ids) + # ids is a hash of relative paths to arrays of ids + # to match against. e.g. + # { "./path/to/file.rb" => ["1:1", "2:4"] } + rerun_path = Metadata.relative_path(File.expand_path rerun_path) + add_path_to_arrays_filter(:ids, rerun_path, scoped_ids) + end + + def empty? + inclusions.empty? && exclusions.empty? + end + + def prune(examples) + # Semantically, this is unnecessary (the filtering below will return the empty + # array unmodified), but for perf reasons it's worth exiting early here. Users + # commonly have top-level examples groups that do not have any direct examples + # and instead have nested groups with examples. In that kind of situation, + # `examples` will be empty. + return examples if examples.empty? + + examples = prune_conditionally_filtered_examples(examples) + + if inclusions.standalone? + examples.select { |e| inclusions.include_example?(e) } + else + locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules + + examples.select do |ex| + file_scoped_include?(ex.metadata, ids, locations) do + !exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex) + end + end + end + end + + def exclude(*args) + exclusions.add(args.last) + end + + def exclude_only(*args) + exclusions.use_only(args.last) + end + + def exclude_with_low_priority(*args) + exclusions.add_with_low_priority(args.last) + end + + def include(*args) + inclusions.add(args.last) + end + + def include_only(*args) + inclusions.use_only(args.last) + end + + def include_with_low_priority(*args) + inclusions.add_with_low_priority(args.last) + end + + private + + def add_path_to_arrays_filter(filter_key, path, values) + filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] } + filter[path].concat(values) + inclusions.add(filter_key => filter) + end + + def prune_conditionally_filtered_examples(examples) + examples.reject do |ex| + meta = ex.metadata + !meta.fetch(:if, true) || meta[:unless] + end + end + + # When a user specifies a particular spec location, that takes priority + # over any exclusion filters (such as if the spec is tagged with `:slow` + # and there is a `:slow => true` exclusion filter), but only for specs + # defined in the same file as the location filters. Excluded specs in + # other files should still be excluded. + def file_scoped_include?(ex_metadata, ids, locations) + no_id_filters = ids[ex_metadata[:rerun_file_path]].empty? + no_location_filters = locations[ + File.expand_path(ex_metadata[:rerun_file_path]) + ].empty? + + return yield if no_location_filters && no_id_filters + + MetadataFilter.filter_applies?(:ids, ids, ex_metadata) || + MetadataFilter.filter_applies?(:locations, locations, ex_metadata) + end + end + + # @private + class FilterRules + PROC_HEX_NUMBER = /0x[0-9a-f]+@?/ + PROJECT_DIR = File.expand_path('.') + + attr_accessor :opposite + attr_reader :rules + + def self.build + exclusions = ExclusionRules.new + inclusions = InclusionRules.new + exclusions.opposite = inclusions + inclusions.opposite = exclusions + [exclusions, inclusions] + end + + def initialize(rules={}) + @rules = rules + end + + def add(updated) + @rules.merge!(updated).each_key { |k| opposite.delete(k) } + end + + def add_with_low_priority(updated) + updated = updated.merge(@rules) + opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v } + @rules.replace(updated) + end + + def use_only(updated) + updated.each_key { |k| opposite.delete(k) } + @rules.replace(updated) + end + + def clear + @rules.clear + end + + def delete(key) + @rules.delete(key) + end + + def fetch(*args, &block) + @rules.fetch(*args, &block) + end + + def [](key) + @rules[key] + end + + def empty? + rules.empty? + end + + def each_pair(&block) + @rules.each_pair(&block) + end + + def description + rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '') + end + + def include_example?(example) + MetadataFilter.apply?(:any?, @rules, example.metadata) + end + end + + # @private + ExclusionRules = FilterRules + + # @private + class InclusionRules < FilterRules + def add(*args) + apply_standalone_filter(*args) || super + end + + def add_with_low_priority(*args) + apply_standalone_filter(*args) || super + end + + def include_example?(example) + @rules.empty? || super + end + + def standalone? + is_standalone_filter?(@rules) + end + + def split_file_scoped_rules + rules_dup = @rules.dup + locations = rules_dup.delete(:locations) { Hash.new([]) } + ids = rules_dup.delete(:ids) { Hash.new([]) } + + return locations, ids, self.class.new(rules_dup) + end + + private + + def apply_standalone_filter(updated) + return true if standalone? + return nil unless is_standalone_filter?(updated) + + replace_filters(updated) + true + end + + def replace_filters(new_rules) + @rules.replace(new_rules) + opposite.clear + end + + def is_standalone_filter?(rules) + rules.key?(:full_description) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/flat_map.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/flat_map.rb new file mode 100644 index 0000000000..0e30cceb51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/flat_map.rb @@ -0,0 +1,20 @@ +module RSpec + module Core + # @private + module FlatMap + if [].respond_to?(:flat_map) + def flat_map(array, &block) + array.flat_map(&block) + end + else # for 1.8.7 + # :nocov: + def flat_map(array, &block) + array.map(&block).flatten(1) + end + # :nocov: + end + + module_function :flat_map + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters.rb new file mode 100644 index 0000000000..a693a80c5c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters.rb @@ -0,0 +1,273 @@ +RSpec::Support.require_rspec_support "directory_maker" + +# ## Built-in Formatters +# +# * progress (default) - Prints dots for passing examples, `F` for failures, `*` +# for pending. +# * documentation - Prints the docstrings passed to `describe` and `it` methods +# (and their aliases). +# * html +# * json - Useful for archiving data for subsequent analysis. +# +# The progress formatter is the default, but you can choose any one or more of +# the other formatters by passing with the `--format` (or `-f` for short) +# command-line option, e.g. +# +# rspec --format documentation +# +# You can also send the output of multiple formatters to different streams, e.g. +# +# rspec --format documentation --format html --out results.html +# +# This example sends the output of the documentation formatter to `$stdout`, and +# the output of the html formatter to results.html. +# +# ## Custom Formatters +# +# You can tell RSpec to use a custom formatter by passing its path and name to +# the `rspec` command. For example, if you define MyCustomFormatter in +# path/to/my_custom_formatter.rb, you would type this command: +# +# rspec --require path/to/my_custom_formatter.rb --format MyCustomFormatter +# +# The reporter calls every formatter with this protocol: +# +# * To start +# * `start(StartNotification)` +# * Once per example group +# * `example_group_started(GroupNotification)` +# * Once per example +# * `example_started(ExampleNotification)` +# * One of these per example, depending on outcome +# * `example_passed(ExampleNotification)` +# * `example_failed(FailedExampleNotification)` +# * `example_pending(ExampleNotification)` +# * Optionally at any time +# * `message(MessageNotification)` +# * At the end of the suite +# * `stop(ExamplesNotification)` +# * `start_dump(NullNotification)` +# * `dump_pending(ExamplesNotification)` +# * `dump_failures(ExamplesNotification)` +# * `dump_summary(SummaryNotification)` +# * `seed(SeedNotification)` +# * `close(NullNotification)` +# +# Only the notifications to which you subscribe your formatter will be called +# on your formatter. To subscribe your formatter use: +# `RSpec::Core::Formatters#register` e.g. +# +# `RSpec::Core::Formatters.register FormatterClassName, :example_passed, :example_failed` +# +# We recommend you implement the methods yourself; for simplicity we provide the +# default formatter output via our notification objects but if you prefer you +# can subclass `RSpec::Core::Formatters::BaseTextFormatter` and override the +# methods you wish to enhance. +# +# @see RSpec::Core::Formatters::BaseTextFormatter +# @see RSpec::Core::Reporter +module RSpec::Core::Formatters + autoload :DocumentationFormatter, 'rspec/core/formatters/documentation_formatter' + autoload :HtmlFormatter, 'rspec/core/formatters/html_formatter' + autoload :FallbackMessageFormatter, 'rspec/core/formatters/fallback_message_formatter' + autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter' + autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter' + autoload :JsonFormatter, 'rspec/core/formatters/json_formatter' + autoload :BisectDRbFormatter, 'rspec/core/formatters/bisect_drb_formatter' + autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter' + autoload :FailureListFormatter, 'rspec/core/formatters/failure_list_formatter' + + # Register the formatter class + # @param formatter_class [Class] formatter class to register + # @param notifications [Symbol, ...] one or more notifications to be + # registered to the specified formatter + # + # @see RSpec::Core::Formatters::BaseFormatter + def self.register(formatter_class, *notifications) + Loader.formatters[formatter_class] = notifications + end + + # @api private + # + # `RSpec::Core::Formatters::Loader` is an internal class for + # managing formatters used by a particular configuration. It is + # not expected to be used directly, but only through the configuration + # interface. + class Loader + # @api private + # + # Internal formatters are stored here when loaded. + def self.formatters + @formatters ||= {} + end + + # @api private + def initialize(reporter) + @formatters = [] + @reporter = reporter + self.default_formatter = 'progress' + end + + # @return [Array] the loaded formatters + attr_reader :formatters + + # @return [Reporter] the reporter + attr_reader :reporter + + # @return [String] the default formatter to setup, defaults to `progress` + attr_accessor :default_formatter + + # @private + def prepare_default(output_stream, deprecation_stream) + reporter.prepare_default(self, output_stream, deprecation_stream) + end + + # @private + def setup_default(output_stream, deprecation_stream) + add default_formatter, output_stream if @formatters.empty? + + unless @formatters.any? { |formatter| DeprecationFormatter === formatter } + add DeprecationFormatter, deprecation_stream, output_stream + end + + unless existing_formatter_implements?(:message) + add FallbackMessageFormatter, output_stream + end + + return unless RSpec.configuration.profile_examples? + return if existing_formatter_implements?(:dump_profile) + + add RSpec::Core::Formatters::ProfileFormatter, output_stream + end + + # @private + def add(formatter_to_use, *paths) + # If a formatter instance was passed, we can register it directly, + # with no need for any of the further processing that happens below. + if Loader.formatters.key?(formatter_to_use.class) + register formatter_to_use, notifications_for(formatter_to_use.class) + return + end + + formatter_class = find_formatter(formatter_to_use) + + args = paths.map { |p| p.respond_to?(:puts) ? p : open_stream(p) } + + if !Loader.formatters[formatter_class].nil? + formatter = formatter_class.new(*args) + register formatter, notifications_for(formatter_class) + elsif defined?(RSpec::LegacyFormatters) + formatter = RSpec::LegacyFormatters.load_formatter formatter_class, *args + register formatter, formatter.notifications + else + call_site = "Formatter added at: #{::RSpec::CallerFilter.first_non_rspec_line}" + + RSpec.warn_deprecation <<-WARNING.gsub(/\s*\|/, ' ') + |The #{formatter_class} formatter uses the deprecated formatter + |interface not supported directly by RSpec 3. + | + |To continue to use this formatter you must install the + |`rspec-legacy_formatters` gem, which provides support + |for legacy formatters or upgrade the formatter to a + |compatible version. + | + |#{call_site} + WARNING + end + end + + private + + def find_formatter(formatter_to_use) + built_in_formatter(formatter_to_use) || + custom_formatter(formatter_to_use) || + (raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - " \ + "maybe you meant 'documentation' or 'progress'?.") + end + + def register(formatter, notifications) + return if duplicate_formatter_exists?(formatter) + @reporter.register_listener formatter, *notifications + @formatters << formatter + formatter + end + + def duplicate_formatter_exists?(new_formatter) + @formatters.any? do |formatter| + formatter.class == new_formatter.class && formatter.output == new_formatter.output + end + end + + def existing_formatter_implements?(notification) + @reporter.registered_listeners(notification).any? + end + + def built_in_formatter(key) + case key.to_s + when 'd', 'doc', 'documentation' + DocumentationFormatter + when 'h', 'html' + HtmlFormatter + when 'p', 'progress' + ProgressFormatter + when 'j', 'json' + JsonFormatter + when 'bisect-drb' + BisectDRbFormatter + when 'f', 'failures' + FailureListFormatter + end + end + + def notifications_for(formatter_class) + formatter_class.ancestors.inject(::RSpec::Core::Set.new) do |notifications, klass| + notifications.merge Loader.formatters.fetch(klass) { ::RSpec::Core::Set.new } + end + end + + def custom_formatter(formatter_ref) + if Class === formatter_ref + formatter_ref + elsif string_const?(formatter_ref) + begin + formatter_ref.gsub(/^::/, '').split('::').inject(Object) { |a, e| a.const_get e } + rescue NameError + require(path_for(formatter_ref)) ? retry : raise + end + end + end + + def string_const?(str) + str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str + end + + def path_for(const_ref) + underscore_with_fix_for_non_standard_rspec_naming(const_ref) + end + + def underscore_with_fix_for_non_standard_rspec_naming(string) + underscore(string).sub(%r{(^|/)r_spec($|/)}, '\\1rspec\\2') + end + + # activesupport/lib/active_support/inflector/methods.rb, line 48 + def underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + 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 + + def open_stream(path_or_wrapper) + if RSpec::Core::OutputWrapper === path_or_wrapper + path_or_wrapper.output = open_stream(path_or_wrapper.output) + path_or_wrapper + else + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path_or_wrapper)) + File.new(path_or_wrapper, 'w') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_bisect_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_bisect_formatter.rb new file mode 100644 index 0000000000..4159cba891 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_bisect_formatter.rb @@ -0,0 +1,45 @@ +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Formatters + # Contains common logic for formatters used by `--bisect` to communicate results + # back to the bisect runner. + # + # Subclasses must define a `notify_results(all_example_ids, failed_example_ids)` + # method. + # @private + class BaseBisectFormatter + def self.inherited(formatter) + Formatters.register formatter, :start_dump, :example_failed, :example_finished + end + + def initialize(expected_failures) + @all_example_ids = [] + @failed_example_ids = [] + @remaining_failures = expected_failures + end + + def example_failed(notification) + @failed_example_ids << notification.example.id + end + + def example_finished(notification) + @all_example_ids << notification.example.id + return unless @remaining_failures.include?(notification.example.id) + @remaining_failures.delete(notification.example.id) + + status = notification.example.execution_result.status + return if status == :failed && !@remaining_failures.empty? + RSpec.world.wants_to_quit = true + end + + def start_dump(_notification) + # `notify_results` is defined in the subclass + notify_results(Bisect::ExampleSetDescriptor.new( + @all_example_ids, @failed_example_ids)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_formatter.rb new file mode 100644 index 0000000000..dca4b6d609 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_formatter.rb @@ -0,0 +1,70 @@ +RSpec::Support.require_rspec_core "formatters/helpers" +require 'stringio' + +module RSpec + module Core + module Formatters + # RSpec's built-in formatters are all subclasses of + # RSpec::Core::Formatters::BaseFormatter. + # + # @see RSpec::Core::Formatters::BaseTextFormatter + # @see RSpec::Core::Reporter + # @see RSpec::Core::Formatters::Protocol + class BaseFormatter + # All formatters inheriting from this formatter will receive these + # notifications. + Formatters.register self, :start, :example_group_started, :close + attr_accessor :example_group + attr_reader :output + + # @api public + # @param output [IO] the formatter output + # @see RSpec::Core::Formatters::Protocol#initialize + def initialize(output) + @output = output || StringIO.new + @example_group = nil + end + + # @api public + # + # @param notification [StartNotification] + # @see RSpec::Core::Formatters::Protocol#start + def start(notification) + start_sync_output + @example_count = notification.count + end + + # @api public + # + # @param notification [GroupNotification] containing example_group + # subclass of `RSpec::Core::ExampleGroup` + # @see RSpec::Core::Formatters::Protocol#example_group_started + def example_group_started(notification) + @example_group = notification.group + end + + # @api public + # + # @param _notification [NullNotification] (Ignored) + # @see RSpec::Core::Formatters::Protocol#close + def close(_notification) + restore_sync_output + end + + private + + def start_sync_output + @old_sync, output.sync = output.sync, true if output_supports_sync + end + + def restore_sync_output + output.sync = @old_sync if output_supports_sync && !output.closed? + end + + def output_supports_sync + output.respond_to?(:sync=) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_text_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_text_formatter.rb new file mode 100644 index 0000000000..5d9daba4dc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/base_text_formatter.rb @@ -0,0 +1,75 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" + +module RSpec + module Core + module Formatters + # Base for all of RSpec's built-in formatters. See + # RSpec::Core::Formatters::BaseFormatter to learn more about all of the + # methods called by the reporter. + # + # @see RSpec::Core::Formatters::BaseFormatter + # @see RSpec::Core::Reporter + class BaseTextFormatter < BaseFormatter + Formatters.register self, + :message, :dump_summary, :dump_failures, :dump_pending, :seed + + # @api public + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [MessageNotification] containing message + def message(notification) + output.puts notification.message + end + + # @api public + # + # Dumps detailed information about each example failure. + # + # @param notification [NullNotification] + def dump_failures(notification) + return if notification.failure_notifications.empty? + output.puts notification.fully_formatted_failed_examples + end + + # @api public + # + # This method is invoked after the dumping of examples and failures. + # Each parameter is assigned to a corresponding attribute. + # + # @param summary [SummaryNotification] containing duration, + # example_count, failure_count and pending_count + def dump_summary(summary) + output.puts summary.fully_formatted + end + + # @private + def dump_pending(notification) + return if notification.pending_examples.empty? + output.puts notification.fully_formatted_pending_examples + end + + # @private + def seed(notification) + return unless notification.seed_used? + output.puts notification.fully_formatted + end + + # @api public + # + # Invoked at the end of a suite run. Allows the formatter to do any + # tidying up, but be aware that formatter output streams may be used + # elsewhere so don't actually close them. + # + # @param _notification [NullNotification] (Ignored) + def close(_notification) + return if output.closed? + + output.puts + + output.flush + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_drb_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_drb_formatter.rb new file mode 100644 index 0000000000..9fb1299d81 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_drb_formatter.rb @@ -0,0 +1,29 @@ +require 'drb/drb' +RSpec::Support.require_rspec_core "formatters/base_bisect_formatter" + +module RSpec + module Core + module Formatters + # Used by `--bisect`. When it shells out and runs a portion of the suite, it uses + # this formatter as a means to have the status reported back to it, via DRb. + # + # Note that since DRb calls carry considerable overhead compared to normal + # method calls, we try to minimize the number of DRb calls for perf reasons, + # opting to communicate only at the start and the end of the run, rather than + # after each example. + # @private + class BisectDRbFormatter < BaseBisectFormatter + def initialize(_output) + drb_uri = "druby://localhost:#{RSpec.configuration.drb_port}" + @bisect_server = DRbObject.new_with_uri(drb_uri) + RSpec.configuration.files_or_directories_to_run = @bisect_server.files_or_directories_to_run + super(Set.new(@bisect_server.expected_failures)) + end + + def notify_results(results) + @bisect_server.latest_run_results = results + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_progress_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_progress_formatter.rb new file mode 100644 index 0000000000..35f01a663f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/bisect_progress_formatter.rb @@ -0,0 +1,157 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" + +module RSpec + module Core + module Formatters + # @private + # Produces progress output while bisecting. + class BisectProgressFormatter < BaseTextFormatter + def initialize(output, bisect_runner) + super(output) + @bisect_runner = bisect_runner + end + + def bisect_starting(notification) + @round_count = 0 + output.puts bisect_started_message(notification) + output.print "Running suite to find failures..." + end + + def bisect_original_run_complete(notification) + failures = Helpers.pluralize(notification.failed_example_ids.size, "failing example") + non_failures = Helpers.pluralize(notification.non_failing_example_ids.size, "non-failing example") + + output.puts " (#{Helpers.format_duration(notification.duration)})" + output.puts "Starting bisect with #{failures} and #{non_failures}." + end + + def bisect_dependency_check_started(_notification) + output.print "Checking that failure(s) are order-dependent.." + end + + def bisect_dependency_check_passed(_notification) + output.puts " failure appears to be order-dependent" + end + + def bisect_dependency_check_failed(_notification) + output.puts " failure(s) do not require any non-failures to run first" + + if @bisect_runner == :fork + output.puts + output.puts "=" * 80 + output.puts "NOTE: this bisect run used `config.bisect_runner = :fork`, which generally" + output.puts "provides significantly faster bisection runs than the old shell-based runner," + output.puts "but may inaccurately report that no non-failures are required. If this result" + output.puts "is unexpected, consider setting `config.bisect_runner = :shell` and trying again." + output.puts "=" * 80 + end + end + + def bisect_round_started(notification, include_trailing_space=true) + @round_count += 1 + range_desc = notification.candidate_range.description + + output.print "\nRound #{@round_count}: bisecting over non-failing #{range_desc}" + output.print " " if include_trailing_space + end + + def bisect_round_ignoring_ids(notification) + range_desc = notification.ignore_range.description + + output.print " ignoring #{range_desc}" + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_round_detected_multiple_culprits(notification) + output.print " multiple culprits detected - splitting candidates" + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_individual_run_complete(_) + output.print '.' + end + + def bisect_complete(notification) + output.puts "\nBisect complete! Reduced necessary non-failing examples " \ + "from #{notification.original_non_failing_count} to " \ + "#{notification.remaining_count} in " \ + "#{Helpers.format_duration(notification.duration)}." + end + + def bisect_repro_command(notification) + output.puts "\nThe minimal reproduction command is:\n #{notification.repro}" + end + + def bisect_failed(notification) + output.puts "\nBisect failed! #{notification.failure_explanation}" + end + + def bisect_aborted(notification) + output.puts "\n\nBisect aborted!" + output.puts "\nThe most minimal reproduction command discovered so far is:\n #{notification.repro}" + end + + private + + def bisect_started_message(notification) + options = notification.original_cli_args.join(' ') + "Bisect started using options: #{options.inspect}" + end + end + + # @private + # Produces detailed debug output while bisecting. Used when bisect is + # performed with `--bisect=verbose`. Designed to provide details for + # us when we need to troubleshoot bisect bugs. + class BisectDebugFormatter < BisectProgressFormatter + def bisect_original_run_complete(notification) + output.puts " (#{Helpers.format_duration(notification.duration)})" + + output.puts " - #{describe_ids 'Failing examples', notification.failed_example_ids}" + output.puts " - #{describe_ids 'Non-failing examples', notification.non_failing_example_ids}" + end + + def bisect_individual_run_start(notification) + output.print "\n - Running: #{notification.command}" + end + + def bisect_individual_run_complete(notification) + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_dependency_check_passed(_notification) + output.print "\n - Failure appears to be order-dependent" + end + + def bisect_dependency_check_failed(_notification) + output.print "\n - Failure is not order-dependent" + end + + def bisect_round_started(notification) + super(notification, false) + end + + def bisect_round_ignoring_ids(notification) + output.print "\n - #{describe_ids 'Examples we can safely ignore', notification.ids_to_ignore}" + output.print "\n - #{describe_ids 'Remaining non-failing examples', notification.remaining_ids}" + end + + def bisect_round_detected_multiple_culprits(_notification) + output.print "\n - Multiple culprits detected - splitting candidates" + end + + private + + def describe_ids(description, ids) + organized_ids = Formatters::Helpers.organize_ids(ids) + formatted_ids = organized_ids.map { |id| " - #{id}" }.join("\n") + "#{description} (#{ids.size}):\n#{formatted_ids}" + end + + def bisect_started_message(notification) + "#{super} and bisect runner: #{notification.bisect_runner.inspect}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/console_codes.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/console_codes.rb new file mode 100644 index 0000000000..be2ee549b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/console_codes.rb @@ -0,0 +1,68 @@ +module RSpec + module Core + module Formatters + # ConsoleCodes provides helpers for formatting console output + # with ANSI codes, e.g. color's and bold. + module ConsoleCodes + # @private + VT100_CODES = + { + :black => 30, + :red => 31, + :green => 32, + :yellow => 33, + :blue => 34, + :magenta => 35, + :cyan => 36, + :white => 37, + :bold => 1, + } + # @private + VT100_CODE_VALUES = VT100_CODES.invert + + module_function + + # @private + def config_colors_to_methods + @config_colors_to_methods ||= + Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method| + hash[method.to_s.sub(/_color\z/, '').to_sym] = method + hash + end + end + + # Fetches the correct code for the supplied symbol, or checks + # that a code is valid. Defaults to white (37). + # + # @param code_or_symbol [Symbol, Fixnum] Symbol or code to check + # @return [Fixnum] a console code + def console_code_for(code_or_symbol) + if (config_method = config_colors_to_methods[code_or_symbol]) + console_code_for RSpec.configuration.__send__(config_method) + elsif VT100_CODE_VALUES.key?(code_or_symbol) + code_or_symbol + else + VT100_CODES.fetch(code_or_symbol) do + console_code_for(:white) + end + end + end + + # Wraps a piece of text in ANSI codes with the supplied code. Will + # only apply the control code if `RSpec.configuration.color_enabled?` + # returns true. + # + # @param text [String] the text to wrap + # @param code_or_symbol [Symbol, Fixnum] the desired control code + # @return [String] the wrapped text + def wrap(text, code_or_symbol) + if RSpec.configuration.color_enabled? + "\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m" + else + text + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/deprecation_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/deprecation_formatter.rb new file mode 100644 index 0000000000..110a71f5e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/deprecation_formatter.rb @@ -0,0 +1,223 @@ +RSpec::Support.require_rspec_core "formatters/helpers" + +module RSpec + module Core + module Formatters + # @private + class DeprecationFormatter + Formatters.register self, :deprecation, :deprecation_summary + + attr_reader :count, :deprecation_stream, :summary_stream + + def initialize(deprecation_stream, summary_stream) + @deprecation_stream = deprecation_stream + @summary_stream = summary_stream + @seen_deprecations = Set.new + @count = 0 + end + alias :output :deprecation_stream + + def printer + @printer ||= case deprecation_stream + when File + ImmediatePrinter.new(FileStream.new(deprecation_stream), + summary_stream, self) + when RaiseErrorStream + ImmediatePrinter.new(deprecation_stream, summary_stream, self) + else + DelayedPrinter.new(deprecation_stream, summary_stream, self) + end + end + + def deprecation(notification) + return if @seen_deprecations.include? notification + + @count += 1 + printer.print_deprecation_message notification + @seen_deprecations << notification + end + + def deprecation_summary(_notification) + printer.deprecation_summary + end + + def deprecation_message_for(data) + if data.message + SpecifiedDeprecationMessage.new(data) + else + GeneratedDeprecationMessage.new(data) + end + end + + RAISE_ERROR_CONFIG_NOTICE = <<-EOS.gsub(/^\s+\|/, '') + | + |If you need more of the backtrace for any of these deprecations to + |identify where to make the necessary changes, you can configure + |`config.raise_errors_for_deprecations!`, and it will turn the + |deprecation warnings into errors, giving you the full backtrace. + EOS + + DEPRECATION_STREAM_NOTICE = "Pass `--deprecation-out` or set " \ + "`config.deprecation_stream` to a file for full output." + TOO_MANY_WARNINGS_NOTICE = "Too many similar deprecation messages " \ + "reported, disregarding further reports. #{DEPRECATION_STREAM_NOTICE}" + + # @private + SpecifiedDeprecationMessage = Struct.new(:type) do + def initialize(data) + @message = data.message + super deprecation_type_for(data) + end + + def to_s + output_formatted @message + end + + def too_many_warnings_message + TOO_MANY_WARNINGS_NOTICE + end + + private + + def output_formatted(str) + return str unless str.lines.count > 1 + separator = '-' * 80 + "#{separator}\n#{str.chomp}\n#{separator}" + end + + def deprecation_type_for(data) + data.message.gsub(/(\w+\/)+\w+\.rb:\d+/, '') + end + end + + # @private + GeneratedDeprecationMessage = Struct.new(:type) do + def initialize(data) + @data = data + super data.deprecated + end + + def to_s + msg = String.new("#{@data.deprecated} is deprecated.") + msg << " Use #{@data.replacement} instead." if @data.replacement + msg << " Called from #{@data.call_site}." if @data.call_site + msg + end + + def too_many_warnings_message + "Too many uses of deprecated '#{type}'. #{DEPRECATION_STREAM_NOTICE}" + end + end + + # @private + class ImmediatePrinter + attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter + + def initialize(deprecation_stream, summary_stream, deprecation_formatter) + @deprecation_stream = deprecation_stream + + @summary_stream = summary_stream + @deprecation_formatter = deprecation_formatter + end + + def print_deprecation_message(data) + deprecation_message = deprecation_formatter.deprecation_message_for(data) + deprecation_stream.puts deprecation_message.to_s + end + + def deprecation_summary + return if deprecation_formatter.count.zero? + deprecation_stream.summarize(summary_stream, deprecation_formatter.count) + end + end + + # @private + class DelayedPrinter + TOO_MANY_USES_LIMIT = 4 + + attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter + + def initialize(deprecation_stream, summary_stream, deprecation_formatter) + @deprecation_stream = deprecation_stream + @summary_stream = summary_stream + @deprecation_formatter = deprecation_formatter + @seen_deprecations = Hash.new { 0 } + @deprecation_messages = Hash.new { |h, k| h[k] = [] } + end + + def print_deprecation_message(data) + deprecation_message = deprecation_formatter.deprecation_message_for(data) + @seen_deprecations[deprecation_message] += 1 + + stash_deprecation_message(deprecation_message) + end + + def stash_deprecation_message(deprecation_message) + if @seen_deprecations[deprecation_message] < TOO_MANY_USES_LIMIT + @deprecation_messages[deprecation_message] << deprecation_message.to_s + elsif @seen_deprecations[deprecation_message] == TOO_MANY_USES_LIMIT + @deprecation_messages[deprecation_message] << deprecation_message.too_many_warnings_message + end + end + + def deprecation_summary + return unless @deprecation_messages.any? + + print_deferred_deprecation_warnings + deprecation_stream.puts RAISE_ERROR_CONFIG_NOTICE + + summary_stream.puts "\n#{Helpers.pluralize(deprecation_formatter.count, 'deprecation warning')} total" + end + + def print_deferred_deprecation_warnings + deprecation_stream.puts "\nDeprecation Warnings:\n\n" + @deprecation_messages.keys.sort_by(&:type).each do |deprecation| + messages = @deprecation_messages[deprecation] + messages.each { |msg| deprecation_stream.puts msg } + deprecation_stream.puts + end + end + end + + # @private + # Not really a stream, but is usable in place of one. + class RaiseErrorStream + def puts(message) + raise DeprecationError, message + end + + def summarize(summary_stream, deprecation_count) + summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} found." + end + end + + # @private + # Wraps a File object and provides file-specific operations. + class FileStream + def initialize(file) + @file = file + + # In one of my test suites, I got lots of duplicate output in the + # deprecation file (e.g. 200 of the same deprecation, even though + # the `puts` below was only called 6 times). Setting `sync = true` + # fixes this (but we really have no idea why!). + @file.sync = true + end + + def puts(*args) + @file.puts(*args) + end + + def summarize(summary_stream, deprecation_count) + path = @file.respond_to?(:path) ? @file.path : @file.inspect + summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} logged to #{path}" + puts RAISE_ERROR_CONFIG_NOTICE + end + end + end + end + + # Deprecation Error. + DeprecationError = Class.new(StandardError) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/documentation_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/documentation_formatter.rb new file mode 100644 index 0000000000..f64919c29c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/documentation_formatter.rb @@ -0,0 +1,102 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @private + class DocumentationFormatter < BaseTextFormatter + Formatters.register self, :example_started, :example_group_started, :example_group_finished, + :example_passed, :example_pending, :example_failed + + def initialize(output) + super + @group_level = 0 + + @example_running = false + @messages = [] + end + + def example_started(_notification) + @example_running = true + end + + def example_group_started(notification) + output.puts if @group_level == 0 + output.puts "#{current_indentation}#{notification.group.description.strip}" + + @group_level += 1 + end + + def example_group_finished(_notification) + @group_level -= 1 if @group_level > 0 + end + + def example_passed(passed) + output.puts passed_output(passed.example) + + flush_messages + @example_running = false + end + + def example_pending(pending) + output.puts pending_output(pending.example, + pending.example.execution_result.pending_message) + + flush_messages + @example_running = false + end + + def example_failed(failure) + output.puts failure_output(failure.example) + + flush_messages + @example_running = false + end + + def message(notification) + if @example_running + @messages << notification.message + else + output.puts "#{current_indentation}#{notification.message}" + end + end + + private + + def flush_messages + @messages.each do |message| + output.puts "#{current_indentation(1)}#{message}" + end + + @messages.clear + end + + def passed_output(example) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip}", :success) + end + + def pending_output(example, message) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \ + "(PENDING: #{message})", + :pending) + end + + def failure_output(example) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \ + "(FAILED - #{next_failure_index})", + :failure) + end + + def next_failure_index + @next_failure_index ||= 0 + @next_failure_index += 1 + end + + def current_indentation(offset=0) + ' ' * (@group_level + offset) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/exception_presenter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/exception_presenter.rb new file mode 100644 index 0000000000..9320ff16d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/exception_presenter.rb @@ -0,0 +1,521 @@ +# encoding: utf-8 +RSpec::Support.require_rspec_core "formatters/console_codes" +RSpec::Support.require_rspec_core "formatters/snippet_extractor" +RSpec::Support.require_rspec_core 'formatters/syntax_highlighter' +RSpec::Support.require_rspec_support "encoded_string" + +module RSpec + module Core + module Formatters + # @private + class ExceptionPresenter + attr_reader :exception, :example, :description, :message_color, + :detail_formatter, :extra_detail_formatter, :backtrace_formatter + private :message_color, :detail_formatter, :extra_detail_formatter, :backtrace_formatter + + def initialize(exception, example, options={}) + @exception = exception + @example = example + @message_color = options.fetch(:message_color) { RSpec.configuration.failure_color } + @description = options.fetch(:description) { example.full_description } + @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} } + @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} } + @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter } + @indentation = options.fetch(:indentation, 2) + @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false) + @failure_lines = options[:failure_lines] + end + + def message_lines + add_shared_group_lines(failure_lines, Notifications::NullColorizer) + end + + def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + add_shared_group_lines(failure_lines, colorizer).map do |line| + colorizer.wrap line, message_color + end + end + + def formatted_backtrace(exception=@exception) + backtrace_formatter.format_backtrace(exception.backtrace, example.metadata) + + formatted_cause(exception) + end + + if RSpec::Support::RubyFeatures.supports_exception_cause? + def formatted_cause(exception) + last_cause = final_exception(exception, [exception]) + cause = [] + + if exception.cause + cause << '------------------' + cause << '--- Caused by: ---' + cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/ + + encoded_string(exception_message_string(last_cause)).split("\n").each do |line| + cause << " #{line}" + end + + unless last_cause.backtrace.empty? + cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}") + end + end + + cause + end + else + # :nocov: + def formatted_cause(_) + [] + end + # :nocov: + end + + def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted_backtrace.map do |backtrace_info| + colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color + end + end + + def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + lines = fully_formatted_lines(failure_number, colorizer) + lines.join("\n") << "\n" + end + + def fully_formatted_lines(failure_number, colorizer) + lines = [ + encoded_description(description), + detail_formatter.call(example, colorizer), + formatted_message_and_backtrace(colorizer), + extra_detail_formatter.call(failure_number, colorizer), + ].compact.flatten + + lines = indent_lines(lines, failure_number) + lines.unshift("") + lines + end + + private + + def final_exception(exception, previous=[]) + cause = exception.cause + + if cause && Exception === cause && !previous.include?(cause) + previous << cause + final_exception(cause, previous) + else + exception + end + end + + if String.method_defined?(:encoding) + def encoding_of(string) + string.encoding + end + + def encoded_string(string) + RSpec::Support::EncodedString.new(string, Encoding.default_external) + end + else # for 1.8.7 + # :nocov: + def encoding_of(_string) + end + + def encoded_string(string) + RSpec::Support::EncodedString.new(string) + end + # :nocov: + end + + def indent_lines(lines, failure_number) + alignment_basis = ' ' * @indentation + alignment_basis << "#{failure_number}) " if failure_number + indentation = ' ' * alignment_basis.length + + lines.each_with_index.map do |line, index| + if index == 0 + "#{alignment_basis}#{line}" + elsif line.empty? + line + else + "#{indentation}#{line}" + end + end + end + + def exception_class_name(exception=@exception) + name = exception.class.name.to_s + name = "(anonymous error class)" if name == '' + name + end + + def failure_lines + @failure_lines ||= [].tap do |lines| + lines.concat(failure_slash_error_lines) + + sections = [failure_slash_error_lines, exception_lines] + if sections.any? { |section| section.size > 1 } && !exception_lines.first.empty? + lines << '' + end + + lines.concat(exception_lines) + lines.concat(extra_failure_lines) + end + end + + def failure_slash_error_lines + lines = read_failed_lines + if lines.count == 1 + lines[0] = "Failure/Error: #{lines[0].strip}" + else + least_indentation = SnippetExtractor.least_indentation_from(lines) + lines = lines.map { |line| line.sub(/^#{least_indentation}/, ' ') } + lines.unshift('Failure/Error:') + end + lines + end + + # rubocop:disable Lint/RescueException + def exception_message_string(exception) + exception.message.to_s + rescue Exception => other + "A #{exception.class} for which `exception.message.to_s` raises #{other.class}." + end + # rubocop:enable Lint/RescueException + + def exception_lines + @exception_lines ||= begin + lines = [] + lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/ + encoded_string(exception_message_string(exception)).split("\n").each do |line| + lines << (line.empty? ? line : " #{line}") + end + lines + end + end + + def extra_failure_lines + @extra_failure_lines ||= begin + lines = Array(example.metadata[:extra_failure_lines]) + unless lines.empty? + lines.unshift('') + lines.push('') + end + lines + end + end + + def add_shared_group_lines(lines, colorizer) + return lines if @skip_shared_group_trace + + example.metadata[:shared_group_inclusion_backtrace].each do |frame| + lines << colorizer.wrap(frame.description, RSpec.configuration.default_color) + end + + lines + end + + def read_failed_lines + matching_line = find_failed_line + unless matching_line + return ["Unable to find matching line from backtrace"] + end + + file_and_line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/) + + unless file_and_line_number + return ["Unable to infer file and line number from backtrace"] + end + + file_path, line_number = file_and_line_number[1..2] + max_line_count = RSpec.configuration.max_displayed_failure_line_count + lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count) + RSpec.world.syntax_highlighter.highlight(lines) + rescue SnippetExtractor::NoSuchFileError + ["Unable to find #{file_path} to read failed line"] + rescue SnippetExtractor::NoSuchLineError + ["Unable to find matching line in #{file_path}"] + rescue SecurityError + ["Unable to read failed line"] + end + + def find_failed_line + line_regex = RSpec.configuration.in_project_source_dir_regex + loaded_spec_files = RSpec.configuration.loaded_spec_files + + exception_backtrace.find do |line| + next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1]) + path = File.expand_path(line_path) + loaded_spec_files.include?(path) || path =~ line_regex + end || exception_backtrace.first + end + + def formatted_message_and_backtrace(colorizer) + lines = colorized_message_lines(colorizer) + colorized_formatted_backtrace(colorizer) + encoding = encoding_of("") + lines.map do |line| + RSpec::Support::EncodedString.new(line, encoding) + end + end + + if String.method_defined?(:encoding) + def encoded_description(description) + return if description.nil? + encoded_string(description) + end + else # for 1.8.7 + def encoded_description(description) + description + end + end + + def exception_backtrace + exception.backtrace || [] + end + + # @private + # Configuring the `ExceptionPresenter` with the right set of options to handle + # pending vs failed vs skipped and aggregated (or not) failures is not simple. + # This class takes care of building an appropriate `ExceptionPresenter` for the + # provided example. + class Factory + def build + ExceptionPresenter.new(@exception, @example, options) + end + + private + + def initialize(example) + @example = example + @execution_result = example.execution_result + @exception = if @execution_result.status == :pending + @execution_result.pending_exception + else + @execution_result.exception + end + end + + def options + with_multiple_error_options_as_needed(@exception, pending_options || {}) + end + + def pending_options + if @execution_result.pending_fixed? + { + :description => "#{@example.full_description} FIXED", + :message_color => RSpec.configuration.fixed_color, + :failure_lines => [ + "Expected pending '#{@execution_result.pending_message}' to fail. No error was raised." + ] + } + elsif @execution_result.status == :pending + { + :message_color => RSpec.configuration.pending_color, + :detail_formatter => PENDING_DETAIL_FORMATTER + } + end + end + + def with_multiple_error_options_as_needed(exception, options) + return options unless multiple_exceptions_error?(exception) + + options = options.merge( + :failure_lines => [], + :extra_detail_formatter => sub_failure_list_formatter(exception, options[:message_color]), + :detail_formatter => multiple_exception_summarizer(exception, + options[:detail_formatter], + options[:message_color]) + ) + + return options unless exception.aggregation_metadata[:hide_backtrace] + options[:backtrace_formatter] = EmptyBacktraceFormatter + options + end + + def multiple_exceptions_error?(exception) + MultipleExceptionError::InterfaceTag === exception + end + + def multiple_exception_summarizer(exception, prior_detail_formatter, color) + lambda do |example, colorizer| + summary = if exception.aggregation_metadata[:hide_backtrace] + # Since the backtrace is hidden, the subfailures will come + # immediately after this, and using `:` will read well. + "Got #{exception.exception_count_description}:" + else + # The backtrace comes after this, so using a `:` doesn't make sense + # since the failures may be many lines below. + "#{exception.summary}." + end + + summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color) + return summary unless prior_detail_formatter + [ + prior_detail_formatter.call(example, colorizer), + summary + ] + end + end + + def sub_failure_list_formatter(exception, message_color) + common_backtrace_truncater = CommonBacktraceTruncater.new(exception) + + lambda do |failure_number, colorizer| + FlatMap.flat_map(exception.all_exceptions.each_with_index) do |failure, index| + options = with_multiple_error_options_as_needed( + failure, + :description => nil, + :indentation => 0, + :message_color => message_color || RSpec.configuration.failure_color, + :skip_shared_group_trace => true + ) + + failure = common_backtrace_truncater.with_truncated_backtrace(failure) + presenter = ExceptionPresenter.new(failure, @example, options) + presenter.fully_formatted_lines( + "#{failure_number ? "#{failure_number}." : ''}#{index + 1}", + colorizer + ) + end + end + end + + # @private + # Used to prevent a confusing backtrace from showing up from the `aggregate_failures` + # block declared for `:aggregate_failures` metadata. + module EmptyBacktraceFormatter + def self.format_backtrace(*) + [] + end + end + + # @private + class CommonBacktraceTruncater + def initialize(parent) + @parent = parent + end + + def with_truncated_backtrace(child) + child_bt = child.backtrace + parent_bt = @parent.backtrace + return child if child_bt.nil? || child_bt.empty? || parent_bt.nil? + + index_before_first_common_frame = -1.downto(-child_bt.size).find do |index| + parent_bt[index] != child_bt[index] + end + + return child if index_before_first_common_frame.nil? + return child if index_before_first_common_frame == -1 + + child = child.dup + child.set_backtrace(child_bt[0..index_before_first_common_frame]) + child + end + end + end + + # @private + PENDING_DETAIL_FORMATTER = Proc.new do |example, colorizer| + colorizer.wrap("# #{example.execution_result.pending_message}", :detail) + end + end + end + + # Provides a single exception instance that provides access to + # multiple sub-exceptions. This is used in situations where a single + # individual spec has multiple exceptions, such as one in the `it` block + # and one in an `after` block. + class MultipleExceptionError < StandardError + # @private + # Used so there is a common module in the ancestor chain of this class + # and `RSpec::Expectations::MultipleExpectationsNotMetError`, which allows + # code to detect exceptions that are instances of either, without first + # checking to see if rspec-expectations is loaded. + module InterfaceTag + # Appends the provided exception to the list. + # @param exception [Exception] Exception to append to the list. + # @private + def add(exception) + # `PendingExampleFixedError` can be assigned to an example that initially has no + # failures, but when the `aggregate_failures` around hook completes, it notifies of + # a failure. If we do not ignore `PendingExampleFixedError` it would be surfaced to + # the user as part of a multiple exception error, which is undesirable. While it's + # pretty weird we handle this here, it's the best solution I've been able to come + # up with, and `PendingExampleFixedError` always represents the _lack_ of any exception + # so clearly when we are transitioning to a `MultipleExceptionError`, it makes sense to + # ignore it. + return if Pending::PendingExampleFixedError === exception + + return if exception == self + + all_exceptions << exception + + if exception.class.name =~ /RSpec/ + failures << exception + else + other_errors << exception + end + end + + # Provides a way to force `ex` to be something that satisfies the multiple + # exception error interface. If it already satisfies it, it will be returned; + # otherwise it will wrap it in a `MultipleExceptionError`. + # @private + def self.for(ex) + return ex if self === ex + MultipleExceptionError.new(ex) + end + end + + include InterfaceTag + + # @return [Array] The list of failures. + attr_reader :failures + + # @return [Array] The list of other errors. + attr_reader :other_errors + + # @return [Array] The list of failures and other exceptions, combined. + attr_reader :all_exceptions + + # @return [Hash] Metadata used by RSpec for formatting purposes. + attr_reader :aggregation_metadata + + # @return [nil] Provided only for interface compatibility with + # `RSpec::Expectations::MultipleExpectationsNotMetError`. + attr_reader :aggregation_block_label + + # @param exceptions [Array] The initial list of exceptions. + def initialize(*exceptions) + super() + + @failures = [] + @other_errors = [] + @all_exceptions = [] + @aggregation_metadata = { :hide_backtrace => true } + @aggregation_block_label = nil + + exceptions.each { |e| add e } + end + + # @return [String] Combines all the exception messages into a single string. + # @note RSpec does not actually use this -- instead it formats each exception + # individually. + def message + all_exceptions.map(&:message).join("\n\n") + end + + # @return [String] A summary of the failure, including the block label and a count of failures. + def summary + "Got #{exception_count_description}" + end + + # return [String] A description of the failure/error counts. + def exception_count_description + failure_count = Formatters::Helpers.pluralize(failures.size, "failure") + return failure_count if other_errors.empty? + error_count = Formatters::Helpers.pluralize(other_errors.size, "other error") + "#{failure_count} and #{error_count}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/failure_list_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/failure_list_formatter.rb new file mode 100644 index 0000000000..a071dfe82f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/failure_list_formatter.rb @@ -0,0 +1,23 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" + +module RSpec + module Core + module Formatters + # @private + class FailureListFormatter < BaseFormatter + Formatters.register self, :example_failed, :dump_profile, :message + + def example_failed(failure) + output.puts "#{failure.example.location}:#{failure.example.description}" + end + + # Discard profile and messages + # + # These outputs are not really relevant in the context of this failure + # list formatter. + def dump_profile(_profile); end + def message(_message); end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/fallback_message_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/fallback_message_formatter.rb new file mode 100644 index 0000000000..db4423ff18 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/fallback_message_formatter.rb @@ -0,0 +1,28 @@ +module RSpec + module Core + module Formatters + # @api private + # Formatter for providing message output as a fallback when no other + # profiler implements #message + class FallbackMessageFormatter + Formatters.register self, :message + + def initialize(output) + @output = output + end + + # @private + attr_reader :output + + # @api public + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [MessageNotification] containing message + def message(notification) + output.puts notification.message + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/helpers.rb new file mode 100644 index 0000000000..112a006b7c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/helpers.rb @@ -0,0 +1,110 @@ +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec + module Core + module Formatters + # Formatters helpers. + module Helpers + # @private + SUB_SECOND_PRECISION = 5 + + # @private + DEFAULT_PRECISION = 2 + + # @api private + # + # Formats seconds into a human-readable string. + # + # @param duration [Float, Fixnum] in seconds + # @return [String] human-readable time + # + # @example + # format_duration(1) #=> "1 minute 1 second" + # format_duration(135.14) #=> "2 minutes 15.14 seconds" + def self.format_duration(duration) + precision = case + when duration < 1 then SUB_SECOND_PRECISION + when duration < 120 then DEFAULT_PRECISION + when duration < 300 then 1 + else 0 + end + + if duration > 60 + minutes = (duration.round / 60).to_i + seconds = (duration - minutes * 60) + + "#{pluralize(minutes, 'minute')} #{pluralize(format_seconds(seconds, precision), 'second')}" + else + pluralize(format_seconds(duration, precision), 'second') + end + end + + # @api private + # + # Formats seconds to have 5 digits of precision with trailing zeros + # removed if the number is less than 1 or with 2 digits of precision if + # the number is greater than zero. + # + # @param float [Float] + # @return [String] formatted float + # + # @example + # format_seconds(0.000006) #=> "0.00001" + # format_seconds(0.020000) #=> "0.02" + # format_seconds(1.00000000001) #=> "1" + # + # The precision used is set in {Helpers::SUB_SECOND_PRECISION} and + # {Helpers::DEFAULT_PRECISION}. + # + # @see #strip_trailing_zeroes + def self.format_seconds(float, precision=nil) + return '0' if float < 0 + precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION + formatted = "%.#{precision}f" % float + strip_trailing_zeroes(formatted) + end + + # @api private + # + # Remove trailing zeros from a string. + # + # Only remove trailing zeros after a decimal place. + # see: http://rubular.com/r/ojtTydOgpn + # + # @param string [String] string with trailing zeros + # @return [String] string with trailing zeros removed + def self.strip_trailing_zeroes(string) + string.sub(/(?:(\..*[^0])0+|\.0+)$/, '\1') + end + private_class_method :strip_trailing_zeroes + + # @api private + # + # Pluralize a word based on a count. + # + # @param count [Fixnum] number of objects + # @param string [String] word to be pluralized + # @return [String] pluralized word + def self.pluralize(count, string) + "#{count} #{string}#{'s' unless count.to_f == 1}" + end + + # @api private + # Given a list of example ids, organizes them into a compact, ordered list. + def self.organize_ids(ids) + grouped = ids.inject(Hash.new { |h, k| h[k] = [] }) do |hash, id| + file, id = Example.parse_id(id) + hash[file] << id + hash + end + + grouped.sort_by(&:first).map do |file, grouped_ids| + grouped_ids = grouped_ids.sort_by { |id| id.split(':').map(&:to_i) } + id = Metadata.id_from(:rerun_file_path => file, :scoped_id => grouped_ids.join(',')) + ShellEscape.conditionally_quote(id) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_formatter.rb new file mode 100644 index 0000000000..e7eff6619d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_formatter.rb @@ -0,0 +1,153 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/html_printer" + +module RSpec + module Core + module Formatters + # @private + class HtmlFormatter < BaseFormatter + Formatters.register self, :start, :example_group_started, :start_dump, + :example_started, :example_passed, :example_failed, + :example_pending, :dump_summary + + def initialize(output) + super(output) + @failed_examples = [] + @example_group_number = 0 + @example_number = 0 + @header_red = nil + @printer = HtmlPrinter.new(output) + end + + def start(notification) + super + @printer.print_html_start + @printer.flush + end + + def example_group_started(notification) + super + @example_group_red = false + @example_group_number += 1 + + @printer.print_example_group_end unless example_group_number == 1 + @printer.print_example_group_start(example_group_number, + notification.group.description, + notification.group.parent_groups.size) + @printer.flush + end + + def start_dump(_notification) + @printer.print_example_group_end + @printer.flush + end + + def example_started(_notification) + @example_number += 1 + end + + def example_passed(passed) + @printer.move_progress(percent_done) + @printer.print_example_passed(passed.example.description, passed.example.execution_result.run_time) + @printer.flush + end + + def example_failed(failure) + @failed_examples << failure.example + unless @header_red + @header_red = true + @printer.make_header_red + end + + unless @example_group_red + @example_group_red = true + @printer.make_example_group_header_red(example_group_number) + end + + @printer.move_progress(percent_done) + + example = failure.example + + exception = failure.exception + message_lines = failure.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer) + exception_details = if exception + { + # drop 2 removes the description (regardless of newlines) and leading blank line + :message => message_lines.drop(2).join("\n"), + :backtrace => failure.formatted_backtrace.join("\n"), + } + end + extra = extra_failure_content(failure) + + @printer.print_example_failed( + example.execution_result.pending_fixed, + example.description, + example.execution_result.run_time, + @failed_examples.size, + exception_details, + (extra == "") ? false : extra + ) + @printer.flush + end + + def example_pending(pending) + example = pending.example + + @printer.make_header_yellow unless @header_red + @printer.make_example_group_header_yellow(example_group_number) unless @example_group_red + @printer.move_progress(percent_done) + @printer.print_example_pending(example.description, example.execution_result.pending_message) + @printer.flush + end + + def dump_summary(summary) + @printer.print_summary( + summary.duration, + summary.example_count, + summary.failure_count, + summary.pending_count + ) + @printer.flush + end + + private + + # If these methods are declared with attr_reader Ruby will issue a + # warning because they are private. + # rubocop:disable Style/TrivialAccessors + + # The number of the currently running example_group. + def example_group_number + @example_group_number + end + + # The number of the currently running example (a global counter). + def example_number + @example_number + end + # rubocop:enable Style/TrivialAccessors + + def percent_done + result = 100.0 + if @example_count > 0 + result = (((example_number).to_f / @example_count.to_f * 1000).to_i / 10.0).to_f + end + result + end + + # Override this method if you wish to output extra HTML for a failed + # spec. For example, you could output links to images or other files + # produced during the specs. + def extra_failure_content(failure) + RSpec::Support.require_rspec_core "formatters/html_snippet_extractor" + backtrace = (failure.exception.backtrace || []).map do |line| + RSpec.configuration.backtrace_formatter.backtrace_line(line) + end + backtrace.compact! + @snippet_extractor ||= HtmlSnippetExtractor.new + "
    #{@snippet_extractor.snippet(backtrace)}
    " + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_printer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_printer.rb new file mode 100644 index 0000000000..16f96df659 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_printer.rb @@ -0,0 +1,414 @@ +require 'erb' + +module RSpec + module Core + module Formatters + # @private + class HtmlPrinter + include ERB::Util # For the #h method. + def initialize(output) + @output = output + end + + def print_html_start + @output.puts HTML_HEADER + @output.puts REPORT_HEADER + end + + def print_example_group_end + @output.puts " " + @output.puts "" + end + + def print_example_group_start(group_id, description, number_of_parents) + @output.puts "
    " + @output.puts "
    " + @output.puts "
    #{h(description)}
    " + end + + def print_example_passed(description, run_time) + formatted_run_time = "%.5f" % run_time + @output.puts "
    " \ + "#{h(description)}" \ + "#{formatted_run_time}s
    " + end + + # rubocop:disable Metrics/ParameterLists + def print_example_failed(pending_fixed, description, run_time, failure_id, + exception, extra_content) + # rubocop:enable Metrics/ParameterLists + formatted_run_time = "%.5f" % run_time + + @output.puts "
    " + @output.puts " #{h(description)}" + @output.puts " #{formatted_run_time}s" + @output.puts "
    " + if exception + @output.puts "
    #{h(exception[:message])}
    " + @output.puts "
    #{h exception[:backtrace]}
    " + end + @output.puts extra_content if extra_content + @output.puts "
    " + @output.puts "
    " + end + + def print_example_pending(description, pending_message) + @output.puts "
    " \ + "#{h(description)} " \ + "(PENDING: #{h(pending_message)})
    " + end + + def print_summary(duration, example_count, failure_count, pending_count) + totals = String.new( + "#{example_count} example#{'s' unless example_count == 1}, " + ) + totals << "#{failure_count} failure#{'s' unless failure_count == 1}" + totals << ", #{pending_count} pending" if pending_count > 0 + + formatted_duration = "%.5f" % duration + + @output.puts "" + @output.puts "" + @output.puts "
    " + @output.puts "" + @output.puts "" + @output.puts "" + end + + def flush + @output.flush + end + + def move_progress(percent_done) + @output.puts " " + @output.flush + end + + def make_header_red + @output.puts " " + end + + def make_header_yellow + @output.puts " " + end + + def make_example_group_header_red(group_id) + @output.puts " " + @output.puts " " + end + + def make_example_group_header_yellow(group_id) + @output.puts " " + @output.puts " " + end + + private + + def indentation_style(number_of_parents) + "style=\"margin-left: #{(number_of_parents - 1) * 15}px;\"" + end + + REPORT_HEADER = <<-EOF +
    + +
    +
    +

    RSpec Code Examples

    +
    + +
    + + + +
    + +
    +

     

    +

     

    +
    +
    + + +
    +EOF + + GLOBAL_SCRIPTS = <<-EOF + +function addClass(element_id, classname) { + document.getElementById(element_id).className += (" " + classname); +} + +function removeClass(element_id, classname) { + var elem = document.getElementById(element_id); + var classlist = elem.className.replace(classname,''); + elem.className = classlist; +} + +function moveProgressBar(percentDone) { + document.getElementById("rspec-header").style.width = percentDone +"%"; +} + +function makeRed(element_id) { + removeClass(element_id, 'passed'); + removeClass(element_id, 'not_implemented'); + addClass(element_id,'failed'); +} + +function makeYellow(element_id) { + var elem = document.getElementById(element_id); + if (elem.className.indexOf("failed") == -1) { // class doesn't includes failed + if (elem.className.indexOf("not_implemented") == -1) { // class doesn't include not_implemented + removeClass(element_id, 'passed'); + addClass(element_id,'not_implemented'); + } + } +} + +function apply_filters() { + var passed_filter = document.getElementById('passed_checkbox').checked; + var failed_filter = document.getElementById('failed_checkbox').checked; + var pending_filter = document.getElementById('pending_checkbox').checked; + + assign_display_style("example passed", passed_filter); + assign_display_style("example failed", failed_filter); + assign_display_style("example not_implemented", pending_filter); + + assign_display_style_for_group("example_group passed", passed_filter); + assign_display_style_for_group("example_group not_implemented", pending_filter, pending_filter || passed_filter); + assign_display_style_for_group("example_group failed", failed_filter, failed_filter || pending_filter || passed_filter); +} + +function get_display_style(display_flag) { + var style_mode = 'none'; + if (display_flag == true) { + style_mode = 'block'; + } + return style_mode; +} + +function assign_display_style(classname, display_flag) { + var style_mode = get_display_style(display_flag); + var elems = document.getElementsByClassName(classname) + for (var i=0; i + + + RSpec results + + + + + + + + +EOF + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_snippet_extractor.rb new file mode 100644 index 0000000000..d89cb1cc6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/html_snippet_extractor.rb @@ -0,0 +1,120 @@ +module RSpec + module Core + module Formatters + # @api private + # + # Extracts code snippets by looking at the backtrace of the passed error + # and applies synax highlighting and line numbers using html. + class HtmlSnippetExtractor + # @private + module NullConverter + def self.convert(code) + %Q(#{code}\n# Install the coderay gem to get syntax highlighting) + end + end + + # @private + module CoderayConverter + def self.convert(code) + CodeRay.scan(code, :ruby).html(:line_numbers => false) + end + end + + # rubocop:disable Style/ClassVars + # @private + @@converter = NullConverter + + begin + require 'coderay' + RSpec::Support.require_rspec_core 'formatters/syntax_highlighter' + RSpec::Core::Formatters::SyntaxHighlighter.attempt_to_add_rspec_terms_to_coderay_keywords + @@converter = CoderayConverter + # rubocop:disable Lint/HandleExceptions + rescue LoadError + # it'll fall back to the NullConverter assigned above + # rubocop:enable Lint/HandleExceptions + end + + # rubocop:enable Style/ClassVars + + # @api private + # + # Extract lines of code corresponding to a backtrace. + # + # @param backtrace [String] the backtrace from a test failure + # @return [String] highlighted code snippet indicating where the test + # failure occured + # + # @see #post_process + def snippet(backtrace) + raw_code, line = snippet_for(backtrace[0]) + highlighted = @@converter.convert(raw_code) + post_process(highlighted, line) + end + # rubocop:enable Style/ClassVars + + # @api private + # + # Create a snippet from a line of code. + # + # @param error_line [String] file name with line number (i.e. + # 'foo_spec.rb:12') + # @return [String] lines around the target line within the file + # + # @see #lines_around + def snippet_for(error_line) + if error_line =~ /(.*):(\d+)/ + file = Regexp.last_match[1] + line = Regexp.last_match[2].to_i + [lines_around(file, line), line] + else + ["# Couldn't get snippet for #{error_line}", 1] + end + end + + # @api private + # + # Extract lines of code centered around a particular line within a + # source file. + # + # @param file [String] filename + # @param line [Fixnum] line number + # @return [String] lines around the target line within the file (2 above + # and 1 below). + def lines_around(file, line) + if File.file?(file) + lines = File.read(file).split("\n") + min = [0, line - 3].max + max = [line + 1, lines.length - 1].min + selected_lines = [] + selected_lines.join("\n") + lines[min..max].join("\n") + else + "# Couldn't get snippet for #{file}" + end + rescue SecurityError + "# Couldn't get snippet for #{file}" + end + + # @api private + # + # Adds line numbers to all lines and highlights the line where the + # failure occurred using html `span` tags. + # + # @param highlighted [String] syntax-highlighted snippet surrounding the + # offending line of code + # @param offending_line [Fixnum] line where failure occured + # @return [String] completed snippet + def post_process(highlighted, offending_line) + new_lines = [] + highlighted.split("\n").each_with_index do |line, i| + new_line = "#{offending_line + i - 2}#{line}" + new_line = "#{new_line}" if i == 2 + new_lines << new_line + end + new_lines.join("\n") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/json_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/json_formatter.rb new file mode 100644 index 0000000000..ff15d90b26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/json_formatter.rb @@ -0,0 +1,102 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" +require 'json' + +module RSpec + module Core + module Formatters + # @private + class JsonFormatter < BaseFormatter + Formatters.register self, :message, :dump_summary, :dump_profile, :stop, :seed, :close + + attr_reader :output_hash + + def initialize(output) + super + @output_hash = { + :version => RSpec::Core::Version::STRING + } + end + + def message(notification) + (@output_hash[:messages] ||= []) << notification.message + end + + def dump_summary(summary) + @output_hash[:summary] = { + :duration => summary.duration, + :example_count => summary.example_count, + :failure_count => summary.failure_count, + :pending_count => summary.pending_count, + :errors_outside_of_examples_count => summary.errors_outside_of_examples_count + } + @output_hash[:summary_line] = summary.totals_line + end + + def stop(notification) + @output_hash[:examples] = notification.examples.map do |example| + format_example(example).tap do |hash| + e = example.exception + if e + hash[:exception] = { + :class => e.class.name, + :message => e.message, + :backtrace => e.backtrace, + } + end + end + end + end + + def seed(notification) + return unless notification.seed_used? + @output_hash[:seed] = notification.seed + end + + def close(_notification) + output.write @output_hash.to_json + end + + def dump_profile(profile) + @output_hash[:profile] = {} + dump_profile_slowest_examples(profile) + dump_profile_slowest_example_groups(profile) + end + + # @api private + def dump_profile_slowest_examples(profile) + @output_hash[:profile] = {} + @output_hash[:profile][:examples] = profile.slowest_examples.map do |example| + format_example(example).tap do |hash| + hash[:run_time] = example.execution_result.run_time + end + end + @output_hash[:profile][:slowest] = profile.slow_duration + @output_hash[:profile][:total] = profile.duration + end + + # @api private + def dump_profile_slowest_example_groups(profile) + @output_hash[:profile] ||= {} + @output_hash[:profile][:groups] = profile.slowest_groups.map do |loc, hash| + hash.update(:location => loc) + end + end + + private + + def format_example(example) + { + :id => example.id, + :description => example.description, + :full_description => example.full_description, + :status => example.execution_result.status.to_s, + :file_path => example.metadata[:file_path], + :line_number => example.metadata[:line_number], + :run_time => example.execution_result.run_time, + :pending_message => example.execution_result.pending_message, + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/profile_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/profile_formatter.rb new file mode 100644 index 0000000000..4b95d9386f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/profile_formatter.rb @@ -0,0 +1,68 @@ +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @api private + # Formatter for providing profile output. + class ProfileFormatter + Formatters.register self, :dump_profile + + def initialize(output) + @output = output + end + + # @private + attr_reader :output + + # @api public + # + # This method is invoked after the dumping the summary if profiling is + # enabled. + # + # @param profile [ProfileNotification] containing duration, + # slowest_examples and slowest_example_groups + def dump_profile(profile) + dump_profile_slowest_examples(profile) + dump_profile_slowest_example_groups(profile) + end + + private + + def dump_profile_slowest_examples(profile) + @output.puts "\nTop #{profile.slowest_examples.size} slowest " \ + "examples (#{Helpers.format_seconds(profile.slow_duration)} " \ + "seconds, #{profile.percentage}% of total time):\n" + + profile.slowest_examples.each do |example| + @output.puts " #{example.full_description}" + @output.puts " #{bold(Helpers.format_seconds(example.execution_result.run_time))} " \ + "#{bold("seconds")} #{format_caller(example.location)}" + end + end + + def dump_profile_slowest_example_groups(profile) + return if profile.slowest_groups.empty? + + @output.puts "\nTop #{profile.slowest_groups.size} slowest example groups:" + profile.slowest_groups.each do |loc, hash| + average = "#{bold(Helpers.format_seconds(hash[:average]))} #{bold("seconds")} average" + total = "#{Helpers.format_seconds(hash[:total_time])} seconds" + count = Helpers.pluralize(hash[:count], "example") + @output.puts " #{hash[:description]}" + @output.puts " #{average} (#{total} / #{count}) #{loc}" + end + end + + def format_caller(caller_info) + RSpec.configuration.backtrace_formatter.backtrace_line( + caller_info.to_s.split(':in `block').first) + end + + def bold(text) + ConsoleCodes.wrap(text, :bold) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/progress_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/progress_formatter.rb new file mode 100644 index 0000000000..81e0beb407 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/progress_formatter.rb @@ -0,0 +1,29 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @private + class ProgressFormatter < BaseTextFormatter + Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump + + def example_passed(_notification) + output.print ConsoleCodes.wrap('.', :success) + end + + def example_pending(_notification) + output.print ConsoleCodes.wrap('*', :pending) + end + + def example_failed(_notification) + output.print ConsoleCodes.wrap('F', :failure) + end + + def start_dump(_notification) + output.puts + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/protocol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/protocol.rb new file mode 100644 index 0000000000..12fc71f8d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/protocol.rb @@ -0,0 +1,182 @@ +module RSpec + module Core + module Formatters + # This class isn't loaded at runtime but serves to document all of the + # notifications implemented as part of the standard interface. The + # reporter will issue these during a normal test suite run, but a + # formatter will only receive those notifications it has registered + # itself to receive. To register a formatter call: + # + # `::RSpec::Core::Formatters.register class, :list, :of, :notifications` + # + # e.g. + # + # `::RSpec::Core::Formatters.register self, :start, :example_started` + # + # @see RSpec::Core::Formatters::BaseFormatter + # @see RSpec::Core::Formatters::BaseTextFormatter + # @see RSpec::Core::Reporter + class Protocol + # @method initialize(output) + # @api public + # + # @param output [IO] the formatter output + + # @method start(notification) + # @api public + # @group Suite Notifications + # + # This method is invoked before any examples are run, right after + # they have all been collected. This can be useful for special + # formatters that need to provide progress on feedback (graphical ones). + # + # This will only be invoked once, and the next one to be invoked + # is {#example_group_started}. + # + # @param notification [Notifications::StartNotification] + + # @method example_group_started(notification) + # @api public + # @group Group Notifications + # + # This method is invoked at the beginning of the execution of each + # example group. + # + # The next method to be invoked after this is {#example_passed}, + # {#example_pending}, or {#example_group_finished}. + # + # @param notification [Notifications::GroupNotification] containing example_group + # subclass of {ExampleGroup} + + # @method example_group_finished(notification) + # @api public + # @group Group Notifications + # + # Invoked at the end of the execution of each example group. + # + # @param notification [Notifications::GroupNotification] containing example_group + # subclass of {ExampleGroup} + + # @method example_started(notification) + # @api public + # @group Example Notifications + # + # Invoked at the beginning of the execution of each example. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_finished(notification) + # @api public + # @group Example Notifications + # + # Invoked at the end of the execution of each example. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_passed(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example passes. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_pending(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example is pending. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_failed(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example fails. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method message(notification) + # @api public + # @group Suite Notifications + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [Notifications::MessageNotification] containing message + + # @method stop(notification) + # @api public + # @group Suite Notifications + # + # Invoked after all examples have executed, before dumping post-run + # reports. + # + # @param notification [Notifications::NullNotification] + + # @method start_dump(notification) + # @api public + # @group Suite Notifications + # + # This method is invoked after all of the examples have executed. The + # next method to be invoked after this one is {#dump_failures} + # (BaseTextFormatter then calls {#dump_failures} once for each failed + # example). + # + # @param notification [Notifications::NullNotification] + + # @method dump_failures(notification) + # @api public + # @group Suite Notifications + # + # Dumps detailed information about each example failure. + # + # @param notification [Notifications::NullNotification] + + # @method dump_summary(summary) + # @api public + # @group Suite Notifications + # + # This method is invoked after the dumping of examples and failures. + # Each parameter is assigned to a corresponding attribute. + # + # @param summary [Notifications::SummaryNotification] containing duration, + # example_count, failure_count and pending_count + + # @method dump_profile(profile) + # @api public + # @group Suite Notifications + # + # This method is invoked after the dumping the summary if profiling is + # enabled. + # + # @param profile [Notifications::ProfileNotification] containing duration, + # slowest_examples and slowest_example_groups + + # @method dump_pending(notification) + # @api public + # @group Suite Notifications + # + # Outputs a report of pending examples. This gets invoked + # after the summary if option is set to do so. + # + # @param notification [Notifications::NullNotification] + + # @method close(notification) + # @api public + # @group Suite Notifications + # + # Invoked at the end of a suite run. Allows the formatter to do any + # tidying up, but be aware that formatter output streams may be used + # elsewhere so don't actually close them. + # + # @param notification [Notifications::NullNotification] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/snippet_extractor.rb new file mode 100644 index 0000000000..c585db41b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/snippet_extractor.rb @@ -0,0 +1,134 @@ +module RSpec + module Core + module Formatters + # @private + class SnippetExtractor + NoSuchFileError = Class.new(StandardError) + NoSuchLineError = Class.new(StandardError) + + def self.extract_line_at(file_path, line_number) + source = source_from_file(file_path) + line = source.lines[line_number - 1] + raise NoSuchLineError unless line + line + end + + def self.source_from_file(path) + raise NoSuchFileError unless File.exist?(path) + RSpec.world.source_from_file(path) + end + + if RSpec::Support::RubyFeatures.ripper_supported? + NoExpressionAtLineError = Class.new(StandardError) + + attr_reader :source, :beginning_line_number, :max_line_count + + def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil) + if max_line_count == 1 + [extract_line_at(file_path, beginning_line_number)] + else + source = source_from_file(file_path) + new(source, beginning_line_number, max_line_count).expression_lines + end + end + + def initialize(source, beginning_line_number, max_line_count=nil) + @source = source + @beginning_line_number = beginning_line_number + @max_line_count = max_line_count + end + + def expression_lines + line_range = line_range_of_expression + + if max_line_count && line_range.count > max_line_count + line_range = (line_range.begin)..(line_range.begin + max_line_count - 1) + end + + source.lines[(line_range.begin - 1)..(line_range.end - 1)] + rescue SyntaxError, NoExpressionAtLineError + [self.class.extract_line_at(source.path, beginning_line_number)] + end + + private + + def line_range_of_expression + @line_range_of_expression ||= begin + line_range = line_range_of_location_nodes_in_expression + initial_unclosed_tokens = unclosed_tokens_in_line_range(line_range) + unclosed_tokens = initial_unclosed_tokens + + until (initial_unclosed_tokens & unclosed_tokens).empty? + line_range = (line_range.begin)..(line_range.end + 1) + unclosed_tokens = unclosed_tokens_in_line_range(line_range) + end + + line_range + end + end + + def unclosed_tokens_in_line_range(line_range) + tokens = FlatMap.flat_map(line_range) do |line_number| + source.tokens_by_line_number[line_number] + end + + tokens.each_with_object([]) do |token, unclosed_tokens| + if token.opening? + unclosed_tokens << token + else + index = unclosed_tokens.rindex do |unclosed_token| + unclosed_token.closed_by?(token) + end + unclosed_tokens.delete_at(index) if index + end + end + end + + def line_range_of_location_nodes_in_expression + line_numbers = expression_node.each_with_object(Set.new) do |node, set| + set << node.location.line if node.location + end + + line_numbers.min..line_numbers.max + end + + def expression_node + raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty? + + @expression_node ||= begin + common_ancestor_nodes = location_nodes_at_beginning_line.map do |node| + node.each_ancestor.to_a + end.reduce(:&) + + common_ancestor_nodes.find { |node| expression_outmost_node?(node) } + end + end + + def expression_outmost_node?(node) + return true unless node.parent + return false if node.type.to_s.start_with?('@') + ![node, node.parent].all? do |n| + # See `Ripper::PARSER_EVENTS` for the complete list of sexp types. + type = n.type.to_s + type.end_with?('call') || type.start_with?('method_add_') + end + end + + def location_nodes_at_beginning_line + source.nodes_by_line_number[beginning_line_number] + end + else + # :nocov: + def self.extract_expression_lines_at(file_path, beginning_line_number, *) + [extract_line_at(file_path, beginning_line_number)] + end + # :nocov: + end + + def self.least_indentation_from(lines) + lines.map { |line| line[/^[ \t]*/] }.min + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/syntax_highlighter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/syntax_highlighter.rb new file mode 100644 index 0000000000..e7766f6a62 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/formatters/syntax_highlighter.rb @@ -0,0 +1,91 @@ +module RSpec + module Core + module Formatters + # @private + # Provides terminal syntax highlighting of code snippets + # when coderay is available. + class SyntaxHighlighter + def initialize(configuration) + @configuration = configuration + end + + def highlight(lines) + implementation.highlight_syntax(lines) + end + + # rubocop:disable Lint/RescueException + # rubocop:disable Lint/HandleExceptions + def self.attempt_to_add_rspec_terms_to_coderay_keywords + CodeRay::Scanners::Ruby::Patterns::IDENT_KIND.add(%w[ + describe context + it specify + before after around + let subject + expect allow + ], :keyword) + rescue Exception + # Mutating CodeRay's contants like this is not a public API + # and might not always work. If we cannot add our keywords + # to CodeRay it is not a big deal and not worth raising an + # error over, so we ignore it. + end + # rubocop:enable Lint/HandleExceptions + # rubocop:enable Lint/RescueException + + private + + if RSpec::Support::OS.windows? + # :nocov: + def implementation + WindowsImplementation + end + # :nocov: + else + def implementation + return color_enabled_implementation if @configuration.color_enabled? + NoSyntaxHighlightingImplementation + end + end + + def color_enabled_implementation + @color_enabled_implementation ||= begin + require 'coderay' + self.class.attempt_to_add_rspec_terms_to_coderay_keywords + CodeRayImplementation + rescue LoadError + NoSyntaxHighlightingImplementation + end + end + + # @private + module CodeRayImplementation + RESET_CODE = "\e[0m" + + def self.highlight_syntax(lines) + highlighted = begin + CodeRay.encode(lines.join("\n"), :ruby, :terminal) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + return lines + end + + highlighted.split("\n").map do |line| + line.sub(/\S/) { |char| char.insert(0, RESET_CODE) } + end + end + end + + # @private + module NoSyntaxHighlightingImplementation + def self.highlight_syntax(lines) + lines + end + end + + # @private + # Not sure why, but our code above (and/or coderay itself) does not work + # on Windows, so we disable the feature on Windows. + WindowsImplementation = NoSyntaxHighlightingImplementation + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb new file mode 100644 index 0000000000..97bd0fc501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb @@ -0,0 +1,646 @@ +module RSpec + module Core + # Provides `before`, `after` and `around` hooks as a means of + # supporting common setup and teardown. This module is extended + # onto {ExampleGroup}, making the methods available from any `describe` + # or `context` block and included in {Configuration}, making them + # available off of the configuration object to define global setup + # or teardown logic. + module Hooks + # @api public + # + # @overload before(&block) + # @overload before(scope, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` + # (defaults to `:example`) + # @overload before(scope, *conditions, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` + # (defaults to `:example`) + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `before(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload before(conditions, &block) + # @param conditions [Hash] + # constrains this hook to examples matching these conditions e.g. + # `before(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. + # + # @see #after + # @see #around + # @see ExampleGroup + # @see SharedContext + # @see SharedExampleGroup + # @see Configuration + # + # Declare a block of code to be run before each example (using `:example`) + # or once before any example (using `:context`). These are usually + # declared directly in the {ExampleGroup} to which they apply, but they + # can also be shared across multiple groups. + # + # You can also use `before(:suite)` to run a block of code before any + # example groups are run. This should be declared in {RSpec.configure}. + # + # Instance variables declared in `before(:example)` or `before(:context)` + # are accessible within each example. + # + # ### Order + # + # `before` hooks are stored in three scopes, which are run in order: + # `:suite`, `:context`, and `:example`. They can also be declared in + # several different places: `RSpec.configure`, a parent group, the current + # group. They are run in the following order: + # + # before(:suite) # Declared in RSpec.configure. + # before(:context) # Declared in RSpec.configure. + # before(:context) # Declared in a parent group. + # before(:context) # Declared in the current group. + # before(:example) # Declared in RSpec.configure. + # before(:example) # Declared in a parent group. + # before(:example) # Declared in the current group. + # + # If more than one `before` is declared within any one example group, they + # are run in the order in which they are declared. Any `around` hooks will + # execute after `before` context hooks but before any `before` example + # hook regardless of where they are declared. + # + # ### Conditions + # + # When you add a conditions hash to `before(:example)` or + # `before(:context)`, RSpec will only apply that hook to groups or + # examples that match the conditions. e.g. + # + # RSpec.configure do |config| + # config.before(:example, :authorized => true) do + # log_in_as :authorized_user + # end + # end + # + # RSpec.describe Something, :authorized => true do + # # The before hook will run in before each example in this group. + # end + # + # RSpec.describe SomethingElse do + # it "does something", :authorized => true do + # # The before hook will run before this example. + # end + # + # it "does something else" do + # # The hook will not run before this example. + # end + # end + # + # Note that filtered config `:context` hooks can still be applied + # to individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # ### Warning: `before(:suite, :with => :conditions)` + # + # The conditions hash is used to match against specific examples. Since + # `before(:suite)` is not run in relation to any specific example or + # group, conditions passed along with `:suite` are effectively ignored. + # + # ### Exceptions + # + # When an exception is raised in a `before` block, RSpec skips any + # subsequent `before` blocks and the example, but runs all of the + # `after(:example)` and `after(:context)` hooks. + # + # ### Warning: implicit before blocks + # + # `before` hooks can also be declared in shared contexts which get + # included implicitly either by you or by extension libraries. Since + # RSpec runs these in the order in which they are declared within each + # scope, load order matters, and can lead to confusing results when one + # before block depends on state that is prepared in another before block + # that gets run later. + # + # ### Warning: `before(:context)` + # + # It is very tempting to use `before(:context)` to speed things up, but we + # recommend that you avoid this as there are a number of gotchas, as well + # as things that simply don't work. + # + # #### Context + # + # `before(:context)` is run in an example that is generated to provide + # group context for the block. + # + # #### Instance variables + # + # Instance variables declared in `before(:context)` are shared across all + # the examples in the group. This means that each example can change the + # state of a shared object, resulting in an ordering dependency that can + # make it difficult to reason about failures. + # + # #### Unsupported RSpec constructs + # + # RSpec has several constructs that reset state between each example + # automatically. These are not intended for use from within + # `before(:context)`: + # + # * `let` declarations + # * `subject` declarations + # * Any mocking, stubbing or test double declaration + # + # ### other frameworks + # + # Mock object frameworks and database transaction managers (like + # ActiveRecord) are typically designed around the idea of setting up + # before an example, running that one example, and then tearing down. This + # means that mocks and stubs can (sometimes) be declared in + # `before(:context)`, but get torn down before the first real example is + # ever run. + # + # You _can_ create database-backed model objects in a `before(:context)` + # in rspec-rails, but it will not be wrapped in a transaction for you, so + # you are on your own to clean up in an `after(:context)` block. + # + # @example before(:example) declared in an {ExampleGroup} + # + # RSpec.describe Thing do + # before(:example) do + # @thing = Thing.new + # end + # + # it "does something" do + # # Here you can access @thing. + # end + # end + # + # @example before(:context) declared in an {ExampleGroup} + # + # RSpec.describe Parser do + # before(:context) do + # File.open(file_to_parse, 'w') do |f| + # f.write <<-CONTENT + # stuff in the file + # CONTENT + # end + # end + # + # it "parses the file" do + # Parser.parse(file_to_parse) + # end + # + # after(:context) do + # File.delete(file_to_parse) + # end + # end + # + # @note The `:example` and `:context` scopes are also available as + # `:each` and `:all`, respectively. Use whichever you prefer. + # @note The `:suite` scope is only supported for hooks registered on + # `RSpec.configuration` since they exist independently of any + # example or example group. + def before(*args, &block) + hooks.register :append, :before, *args, &block + end + + alias_method :append_before, :before + + # Adds `block` to the front of the list of `before` blocks in the same + # scope (`:example`, `:context`, or `:suite`). + # + # See {#before} for scoping semantics. + def prepend_before(*args, &block) + hooks.register :prepend, :before, *args, &block + end + + # @api public + # @overload after(&block) + # @overload after(scope, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to + # `:example`) + # @overload after(scope, *conditions, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to + # `:example`) + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `after(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload after(conditions, &block) + # @param conditions [Hash] + # constrains this hook to examples matching these conditions e.g. + # `after(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. + # + # @see #before + # @see #around + # @see ExampleGroup + # @see SharedContext + # @see SharedExampleGroup + # @see Configuration + # + # Declare a block of code to be run after each example (using `:example`) + # or once after all examples n the context (using `:context`). See + # {#before} for more information about ordering. + # + # ### Exceptions + # + # `after` hooks are guaranteed to run even when there are exceptions in + # `before` hooks or examples. When an exception is raised in an after + # block, the exception is captured for later reporting, and subsequent + # `after` blocks are run. + # + # ### Order + # + # `after` hooks are stored in three scopes, which are run in order: + # `:example`, `:context`, and `:suite`. They can also be declared in + # several different places: `RSpec.configure`, a parent group, the current + # group. They are run in the following order: + # + # after(:example) # Declared in the current group. + # after(:example) # Declared in a parent group. + # after(:example) # Declared in RSpec.configure. + # after(:context) # Declared in the current group. + # after(:context) # Declared in a parent group. + # after(:context) # Declared in RSpec.configure. + # after(:suite) # Declared in RSpec.configure. + # + # This is the reverse of the order in which `before` hooks are run. + # Similarly, if more than one `after` is declared within any example + # group, they are run in reverse order of that in which they are declared. + # Also `around` hooks will run after any `after` example hooks are + # invoked but before any `after` context hooks. + # + # @note The `:example` and `:context` scopes are also available as + # `:each` and `:all`, respectively. Use whichever you prefer. + # @note The `:suite` scope is only supported for hooks registered on + # `RSpec.configuration` since they exist independently of any + # example or example group. + def after(*args, &block) + hooks.register :prepend, :after, *args, &block + end + + alias_method :prepend_after, :after + + # Adds `block` to the back of the list of `after` blocks in the same + # scope (`:example`, `:context`, or `:suite`). + # + # See {#after} for scoping semantics. + def append_after(*args, &block) + hooks.register :append, :after, *args, &block + end + + # @api public + # @overload around(&block) + # @overload around(scope, &block) + # @param scope [Symbol] `:example` (defaults to `:example`) + # present for syntax parity with `before` and `after`, but + # `:example`/`:each` is the only supported value. + # @overload around(scope, *conditions, &block) + # @param scope [Symbol] `:example` (defaults to `:example`) + # present for syntax parity with `before` and `after`, but + # `:example`/`:each` is the only supported value. + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `around(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload around(conditions, &block) + # @param conditions [Hash] constrains this hook to examples matching + # these conditions e.g. `around(:example, :ui => true) { ... }` will + # only run with examples or groups declared with `:ui => true`. + # + # @yield [Example] the example to run + # + # @note the syntax of `around` is similar to that of `before` and `after` + # but the semantics are quite different. `before` and `after` hooks are + # run in the context of the examples with which they are associated, + # whereas `around` hooks are actually responsible for running the + # examples. Consequently, `around` hooks do not have direct access to + # resources that are made available within the examples and their + # associated `before` and `after` hooks. + # + # @note `:example`/`:each` is the only supported scope. + # + # Declare a block of code, parts of which will be run before and parts + # after the example. It is your responsibility to run the example: + # + # around(:example) do |ex| + # # Do some stuff before. + # ex.run + # # Do some stuff after. + # end + # + # The yielded example aliases `run` with `call`, which lets you treat it + # like a `Proc`. This is especially handy when working with libraries + # that manage their own setup and teardown using a block or proc syntax, + # e.g. + # + # around(:example) {|ex| Database.transaction(&ex)} + # around(:example) {|ex| FakeFS(&ex)} + # + # ### Order + # + # The `around` hooks execute surrounding an example and its hooks. + # + # This means after any `before` context hooks, but before any `before` + # example hooks, and similarly after any `after` example hooks but before + # any `after` context hooks. + # + # They are not a synonym for `before`/`after`. + def around(*args, &block) + hooks.register :prepend, :around, *args, &block + end + + # @private + # Holds the various registered hooks. + def hooks + @hooks ||= HookCollections.new(self, FilterableItemRepository::UpdateOptimized) + end + + # @private + Hook = Struct.new(:block, :options) + + # @private + class BeforeHook < Hook + def run(example) + example.instance_exec(example, &block) + end + end + + # @private + class AfterHook < Hook + def run(example) + example.instance_exec(example, &block) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + example.set_exception(ex) + end + end + + # @private + class AfterContextHook < Hook + def run(example) + example.instance_exec(example, &block) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + RSpec.configuration.reporter.notify_non_example_exception(e, "An error occurred in an `after(:context)` hook.") + end + end + + # @private + class AroundHook < Hook + def execute_with(example, procsy) + example.instance_exec(procsy, &block) + return if procsy.executed? + Pending.mark_skipped!(example, + "#{hook_description} did not execute the example") + end + + if Proc.method_defined?(:source_location) + def hook_description + "around hook at #{Metadata.relative_path(block.source_location.join(':'))}" + end + else # for 1.8.7 + # :nocov: + def hook_description + "around hook" + end + # :nocov: + end + end + + # @private + # + # This provides the primary API used by other parts of rspec-core. By hiding all + # implementation details behind this facade, it's allowed us to heavily optimize + # this, so that, for example, hook collection objects are only instantiated when + # a hook is added. This allows us to avoid many object allocations for the common + # case of a group having no hooks. + # + # This is only possible because this interface provides a "tell, don't ask"-style + # API, so that callers _tell_ this class what to do with the hooks, rather than + # asking this class for a list of hooks, and then doing something with them. + class HookCollections + def initialize(owner, filterable_item_repo_class) + @owner = owner + @filterable_item_repo_class = filterable_item_repo_class + @before_example_hooks = nil + @after_example_hooks = nil + @before_context_hooks = nil + @after_context_hooks = nil + @around_example_hooks = nil + end + + def register_globals(host, globals) + parent_groups = host.parent_groups + + process(host, parent_groups, globals, :before, :example, &:options) + process(host, parent_groups, globals, :after, :example, &:options) + process(host, parent_groups, globals, :around, :example, &:options) + + process(host, parent_groups, globals, :before, :context, &:options) + process(host, parent_groups, globals, :after, :context, &:options) + end + + def register_global_singleton_context_hooks(example, globals) + parent_groups = example.example_group.parent_groups + + process(example, parent_groups, globals, :before, :context) { {} } + process(example, parent_groups, globals, :after, :context) { {} } + end + + def register(prepend_or_append, position, *args, &block) + scope, options = scope_and_options_from(*args) + + if scope == :suite + # TODO: consider making this an error in RSpec 4. For SemVer reasons, + # we are only warning in RSpec 3. + RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \ + "the RSpec configuration object. This " \ + "`#{position}(:suite)` hook, registered on an example " \ + "group, will be ignored." + return + elsif scope == :context && position == :around + # TODO: consider making this an error in RSpec 4. For SemVer reasons, + # we are only warning in RSpec 3. + RSpec.warn_with "WARNING: `around(:context)` hooks are not supported and " \ + "behave like `around(:example)." + end + + hook = HOOK_TYPES[position][scope].new(block, options) + ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options) + end + + # @private + # + # Runs all of the blocks stored with the hook in the context of the + # example. If no example is provided, just calls the hook directly. + def run(position, scope, example_or_group) + return if RSpec.configuration.dry_run? + + if scope == :context + unless example_or_group.class.metadata[:skip] + run_owned_hooks_for(position, :context, example_or_group) + end + else + case position + when :before then run_example_hooks_for(example_or_group, :before, :reverse_each) + when :after then run_example_hooks_for(example_or_group, :after, :each) + when :around then run_around_example_hooks_for(example_or_group) { yield } + end + end + end + + SCOPES = [:example, :context] + + SCOPE_ALIASES = { :each => :example, :all => :context } + + HOOK_TYPES = { + :before => Hash.new { BeforeHook }, + :after => Hash.new { AfterHook }, + :around => Hash.new { AroundHook } + } + + HOOK_TYPES[:after][:context] = AfterContextHook + + protected + + EMPTY_HOOK_ARRAY = [].freeze + + def matching_hooks_for(position, scope, example_or_group) + repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY } + + # It would be nice to not have to switch on type here, but + # we don't want to define `ExampleGroup#metadata` because then + # `metadata` from within an individual example would return the + # group's metadata but the user would probably expect it to be + # the example's metadata. + metadata = case example_or_group + when ExampleGroup then example_or_group.class.metadata + else example_or_group.metadata + end + + repository.items_for(metadata) + end + + def all_hooks_for(position, scope) + hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first) + end + + def run_owned_hooks_for(position, scope, example_or_group) + matching_hooks_for(position, scope, example_or_group).each do |hook| + hook.run(example_or_group) + end + end + + def processable_hooks_for(position, scope, host) + if scope == :example + all_hooks_for(position, scope) + else + matching_hooks_for(position, scope, host) + end + end + + private + + def hooks_for(position, scope) + if position == :before + scope == :example ? @before_example_hooks : @before_context_hooks + elsif position == :after + scope == :example ? @after_example_hooks : @after_context_hooks + else # around + @around_example_hooks + end || yield + end + + def ensure_hooks_initialized_for(position, scope) + if position == :before + if scope == :example + @before_example_hooks ||= @filterable_item_repo_class.new(:all?) + else + @before_context_hooks ||= @filterable_item_repo_class.new(:all?) + end + elsif position == :after + if scope == :example + @after_example_hooks ||= @filterable_item_repo_class.new(:all?) + else + @after_context_hooks ||= @filterable_item_repo_class.new(:all?) + end + else # around + @around_example_hooks ||= @filterable_item_repo_class.new(:all?) + end + end + + def process(host, parent_groups, globals, position, scope) + hooks_to_process = globals.processable_hooks_for(position, scope, host) + return if hooks_to_process.empty? + + hooks_to_process -= FlatMap.flat_map(parent_groups) do |group| + group.hooks.all_hooks_for(position, scope) + end + return if hooks_to_process.empty? + + repository = ensure_hooks_initialized_for(position, scope) + hooks_to_process.each { |hook| repository.append hook, (yield hook) } + end + + def scope_and_options_from(*args) + return :suite if args.first == :suite + scope = extract_scope_from(args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + return scope, meta + end + + def extract_scope_from(args) + if known_scope?(args.first) + normalized_scope_for(args.shift) + elsif args.any? { |a| a.is_a?(Symbol) } + error_message = "You must explicitly give a scope " \ + "(#{SCOPES.join(", ")}) or scope alias " \ + "(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \ + "metadata for a hook." + raise ArgumentError.new error_message + else + :example + end + end + + def known_scope?(scope) + SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope) + end + + def normalized_scope_for(scope) + SCOPE_ALIASES[scope] || scope + end + + def run_example_hooks_for(example, position, each_method) + owner_parent_groups.__send__(each_method) do |group| + group.hooks.run_owned_hooks_for(position, :example, example) + end + end + + def run_around_example_hooks_for(example) + hooks = FlatMap.flat_map(owner_parent_groups) do |group| + group.hooks.matching_hooks_for(:around, :example, example) + end + + return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy` + + initial_procsy = Example::Procsy.new(example) { yield } + hooks.inject(initial_procsy) do |procsy, around_hook| + procsy.wrap { around_hook.execute_with(example, procsy) } + end.call + end + + if respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class) + def owner_parent_groups + @owner.parent_groups + end + else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035) + # :nocov: + def owner_parent_groups + @owner_parent_groups ||= [@owner] + @owner.parent_groups + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/invocations.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/invocations.rb new file mode 100644 index 0000000000..4719085b36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/invocations.rb @@ -0,0 +1,87 @@ +module RSpec + module Core + # @private + module Invocations + # @private + class InitializeProject + def call(*_args) + RSpec::Support.require_rspec_core "project_initializer" + ProjectInitializer.new.run + 0 + end + end + + # @private + class DRbWithFallback + def call(options, err, out) + require 'rspec/core/drb' + begin + return DRbRunner.new(options).run(err, out) + rescue DRb::DRbConnError + err.puts "No DRb server is running. Running in local process instead ..." + end + RSpec::Core::Runner.new(options).run(err, out) + end + end + + # @private + class Bisect + def call(options, err, out) + RSpec::Support.require_rspec_core "bisect/coordinator" + runner = Runner.new(options).tap { |r| r.configure(err, out) } + formatter = bisect_formatter_klass_for(options.options[:bisect]).new( + out, runner.configuration.bisect_runner + ) + + success = RSpec::Core::Bisect::Coordinator.bisect_with( + runner, options.args, formatter + ) + + runner.exit_code(success) + end + + private + + def bisect_formatter_klass_for(argument) + return Formatters::BisectDebugFormatter if argument == "verbose" + Formatters::BisectProgressFormatter + end + end + + # @private + class PrintVersion + def call(_options, _err, out) + overall_version = RSpec::Core::Version::STRING + unless overall_version =~ /[a-zA-Z]+/ + overall_version = overall_version.split('.').first(2).join('.') + end + + out.puts "RSpec #{overall_version}" + + [:Core, :Expectations, :Mocks, :Rails, :Support].each do |const_name| + lib_name = const_name.to_s.downcase + begin + require "rspec/#{lib_name}/version" + rescue LoadError + # Not worth mentioning libs that are not installed + nil + else + out.puts " - rspec-#{lib_name} #{RSpec.const_get(const_name)::Version::STRING}" + end + end + + 0 + end + end + + # @private + PrintHelp = Struct.new(:parser, :hidden_options) do + def call(_options, _err, out) + # Removing the hidden options from the output. + out.puts parser.to_s.gsub(/^\s+(#{hidden_options.join('|')})\b.*$\n/, '') + 0 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb new file mode 100644 index 0000000000..771c12d716 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/memoized_helpers.rb @@ -0,0 +1,554 @@ +RSpec::Support.require_rspec_support 'reentrant_mutex' + +module RSpec + module Core + # This module is included in {ExampleGroup}, making the methods + # available to be called from within example blocks. + # + # @see ClassMethods + module MemoizedHelpers + # @note `subject` was contributed by Joe Ferris to support the one-liner + # syntax embraced by shoulda matchers: + # + # RSpec.describe Widget do + # it { is_expected.to validate_presence_of(:name) } + # # or + # it { should validate_presence_of(:name) } + # end + # + # While the examples below demonstrate how to use `subject` + # explicitly in examples, we recommend that you define a method with + # an intention revealing name instead. + # + # @example + # + # # Explicit declaration of subject. + # RSpec.describe Person do + # subject { Person.new(:birthdate => 19.years.ago) } + # it "should be eligible to vote" do + # subject.should be_eligible_to_vote + # # ^ ^ explicit reference to subject not recommended + # end + # end + # + # # Implicit subject => { Person.new }. + # RSpec.describe Person do + # it "should be eligible to vote" do + # subject.should be_eligible_to_vote + # # ^ ^ explicit reference to subject not recommended + # end + # end + # + # # One-liner syntax - expectation is set on the subject. + # RSpec.describe Person do + # it { is_expected.to be_eligible_to_vote } + # # or + # it { should be_eligible_to_vote } + # end + # + # @note Because `subject` is designed to create state that is reset + # between each example, and `before(:context)` is designed to setup + # state that is shared across _all_ examples in an example group, + # `subject` is _not_ intended to be used in a `before(:context)` hook. + # + # @see #should + # @see #should_not + # @see #is_expected + def subject + __memoized.fetch_or_store(:subject) do + described = described_class || self.class.metadata.fetch(:description_args).first + Class === described ? described.new : described + end + end + + # When `should` is called with no explicit receiver, the call is + # delegated to the object returned by `subject`. Combined with an + # implicit subject this supports very concise expressions. + # + # @example + # + # RSpec.describe Person do + # it { should be_eligible_to_vote } + # end + # + # @see #subject + # @see #is_expected + # + # @note This only works if you are using rspec-expectations. + # @note If you are using RSpec's newer expect-based syntax you may + # want to use `is_expected.to` instead of `should`. + def should(matcher=nil, message=nil) + RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message) + end + + # Just like `should`, `should_not` delegates to the subject (implicit or + # explicit) of the example group. + # + # @example + # + # RSpec.describe Person do + # it { should_not be_eligible_to_vote } + # end + # + # @see #subject + # @see #is_expected + # + # @note This only works if you are using rspec-expectations. + # @note If you are using RSpec's newer expect-based syntax you may + # want to use `is_expected.to_not` instead of `should_not`. + def should_not(matcher=nil, message=nil) + RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message) + end + + # Wraps the `subject` in `expect` to make it the target of an expectation. + # Designed to read nicely for one-liners. + # + # @example + # + # describe [1, 2, 3] do + # it { is_expected.to be_an Array } + # it { is_expected.not_to include 4 } + # end + # + # @see #subject + # @see #should + # @see #should_not + # + # @note This only works if you are using rspec-expectations. + def is_expected + expect(subject) + end + + # @private + # should just be placed in private section, + # but Ruby issues warnings on private attributes. + # and expanding it to the equivalent method upsets Rubocop, + # b/c it should obviously be a reader + attr_reader :__memoized + private :__memoized + + private + + # @private + def initialize(*) + __init_memoized + super + end + + # @private + def __init_memoized + @__memoized = if RSpec.configuration.threadsafe? + ThreadsafeMemoized.new + else + NonThreadSafeMemoized.new + end + end + + # @private + class ThreadsafeMemoized + def initialize + @memoized = {} + @mutex = Support::ReentrantMutex.new + end + + def fetch_or_store(key) + @memoized.fetch(key) do # only first access pays for synchronization + @mutex.synchronize do + @memoized.fetch(key) { @memoized[key] = yield } + end + end + end + end + + # @private + class NonThreadSafeMemoized + def initialize + @memoized = {} + end + + def fetch_or_store(key) + @memoized.fetch(key) { @memoized[key] = yield } + end + end + + # Used internally to customize the behavior of the + # memoized hash when used in a `before(:context)` hook. + # + # @private + class ContextHookMemoized + def self.isolate_for_context_hook(example_group_instance) + exploding_memoized = self + + example_group_instance.instance_exec do + @__memoized = exploding_memoized + + begin + yield + ensure + # This is doing a reset instead of just isolating for context hook. + # Really, this should set the old @__memoized back into place. + # + # Caller is the before and after context hooks + # which are both called from self.run + # I didn't look at why it made tests fail, maybe an object was getting reused in RSpec tests, + # if so, then that probably already works, and its the tests that are wrong. + __init_memoized + end + end + end + + def self.fetch_or_store(key, &_block) + description = if key == :subject + "subject" + else + "let declaration `#{key}`" + end + + raise <<-EOS +#{description} accessed in #{article} #{hook_expression} hook at: + #{CallerFilter.first_non_rspec_line} + +`let` and `subject` declarations are not intended to be called +in #{article} #{hook_expression} hook, as they exist to define state that +is reset between each example, while #{hook_expression} exists to +#{hook_intention}. +EOS + end + + # @private + class Before < self + def self.hook_expression + "`before(:context)`" + end + + def self.article + "a" + end + + def self.hook_intention + "define state that is shared across examples in an example group" + end + end + + # @private + class After < self + def self.hook_expression + "`after(:context)`" + end + + def self.article + "an" + end + + def self.hook_intention + "cleanup state that is shared across examples in an example group" + end + end + end + + # This module is extended onto {ExampleGroup}, making the methods + # available to be called from within example group blocks. + # You can think of them as being analagous to class macros. + module ClassMethods + # Generates a method whose return value is memoized after the first + # call. Useful for reducing duplication between examples that assign + # values to the same local variable. + # + # @note `let` _can_ enhance readability when used sparingly (1,2, or + # maybe 3 declarations) in any given example group, but that can + # quickly degrade with overuse. YMMV. + # + # @note `let` can be configured to be threadsafe or not. + # If it is threadsafe, it will take longer to access the value. + # If it is not threadsafe, it may behave in surprising ways in examples + # that spawn separate threads. Specify this on `RSpec.configure` + # + # @note Because `let` is designed to create state that is reset between + # each example, and `before(:context)` is designed to setup state that + # is shared across _all_ examples in an example group, `let` is _not_ + # intended to be used in a `before(:context)` hook. + # + # @example + # + # RSpec.describe Thing do + # let(:thing) { Thing.new } + # + # it "does something" do + # # First invocation, executes block, memoizes and returns result. + # thing.do_something + # + # # Second invocation, returns the memoized value. + # thing.should be_something + # end + # end + def let(name, &block) + # We have to pass the block directly to `define_method` to + # allow it to use method constructs like `super` and `return`. + raise "#let or #subject called without a block" if block.nil? + raise( + "#let or #subject called with a reserved name #initialize" + ) if :initialize == name + our_module = MemoizedHelpers.module_for(self) + + # If we have a module clash in our helper module + # then we need to remove it to prevent a warning. + # + # Note we do not check ancestor modules (see: `instance_methods(false)`) + # as we can override them. + if our_module.instance_methods(false).include?(name) + our_module.__send__(:remove_method, name) + end + our_module.__send__(:define_method, name, &block) + + # If we have a module clash in the example module + # then we need to remove it to prevent a warning. + # + # Note we do not check ancestor modules (see: `instance_methods(false)`) + # as we can override them. + if instance_methods(false).include?(name) + remove_method(name) + end + + # Apply the memoization. The method has been defined in an ancestor + # module so we can use `super` here to get the value. + if block.arity == 1 + define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } } + else + define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } } + end + end + + # Just like `let`, except the block is invoked by an implicit `before` + # hook. This serves a dual purpose of setting up state and providing a + # memoized reference to that state. + # + # @example + # + # class Thing + # def self.count + # @count ||= 0 + # end + # + # def self.count=(val) + # @count += val + # end + # + # def self.reset_count + # @count = 0 + # end + # + # def initialize + # self.class.count += 1 + # end + # end + # + # RSpec.describe Thing do + # after(:example) { Thing.reset_count } + # + # context "using let" do + # let(:thing) { Thing.new } + # + # it "is not invoked implicitly" do + # Thing.count.should eq(0) + # end + # + # it "can be invoked explicitly" do + # thing + # Thing.count.should eq(1) + # end + # end + # + # context "using let!" do + # let!(:thing) { Thing.new } + # + # it "is invoked implicitly" do + # Thing.count.should eq(1) + # end + # + # it "returns memoized version on first invocation" do + # thing + # Thing.count.should eq(1) + # end + # end + # end + def let!(name, &block) + let(name, &block) + before { __send__(name) } + end + + # Declares a `subject` for an example group which can then be wrapped + # with `expect` using `is_expected` to make it the target of an + # expectation in a concise, one-line example. + # + # Given a `name`, defines a method with that name which returns the + # `subject`. This lets you declare the subject once and access it + # implicitly in one-liners and explicitly using an intention revealing + # name. + # + # When given a `name`, calling `super` in the block is not supported. + # + # @note `subject` can be configured to be threadsafe or not. + # If it is threadsafe, it will take longer to access the value. + # If it is not threadsafe, it may behave in surprising ways in examples + # that spawn separate threads. Specify this on `RSpec.configure` + # + # @param name [String,Symbol] used to define an accessor with an + # intention revealing name + # @param block defines the value to be returned by `subject` in examples + # + # @example + # + # RSpec.describe CheckingAccount, "with $50" do + # subject { CheckingAccount.new(Money.new(50, :USD)) } + # it { is_expected.to have_a_balance_of(Money.new(50, :USD)) } + # it { is_expected.not_to be_overdrawn } + # end + # + # RSpec.describe CheckingAccount, "with a non-zero starting balance" do + # subject(:account) { CheckingAccount.new(Money.new(50, :USD)) } + # it { is_expected.not_to be_overdrawn } + # it "has a balance equal to the starting balance" do + # account.balance.should eq(Money.new(50, :USD)) + # end + # end + # + # @see MemoizedHelpers#should + # @see MemoizedHelpers#should_not + # @see MemoizedHelpers#is_expected + def subject(name=nil, &block) + if name + let(name, &block) + alias_method :subject, name + + self::NamedSubjectPreventSuper.__send__(:define_method, name) do + raise NotImplementedError, "`super` in named subjects is not supported" + end + else + let(:subject, &block) + end + end + + # Just like `subject`, except the block is invoked by an implicit + # `before` hook. This serves a dual purpose of setting up state and + # providing a memoized reference to that state. + # + # @example + # + # class Thing + # def self.count + # @count ||= 0 + # end + # + # def self.count=(val) + # @count += val + # end + # + # def self.reset_count + # @count = 0 + # end + # + # def initialize + # self.class.count += 1 + # end + # end + # + # RSpec.describe Thing do + # after(:example) { Thing.reset_count } + # + # context "using subject" do + # subject { Thing.new } + # + # it "is not invoked implicitly" do + # Thing.count.should eq(0) + # end + # + # it "can be invoked explicitly" do + # subject + # Thing.count.should eq(1) + # end + # end + # + # context "using subject!" do + # subject!(:thing) { Thing.new } + # + # it "is invoked implicitly" do + # Thing.count.should eq(1) + # end + # + # it "returns memoized version on first invocation" do + # subject + # Thing.count.should eq(1) + # end + # end + # end + def subject!(name=nil, &block) + subject(name, &block) + before { subject } + end + end + + # @private + # + # Gets the LetDefinitions module. The module is mixed into + # the example group and is used to hold all let definitions. + # This is done so that the block passed to `let` can be + # forwarded directly on to `define_method`, so that all method + # constructs (including `super` and `return`) can be used in + # a `let` block. + # + # The memoization is provided by a method definition on the + # example group that supers to the LetDefinitions definition + # in order to get the value to memoize. + def self.module_for(example_group) + get_constant_or_yield(example_group, :LetDefinitions) do + mod = Module.new do + include(Module.new { + example_group.const_set(:NamedSubjectPreventSuper, self) + }) + end + + example_group.const_set(:LetDefinitions, mod) + mod + end + end + + # @private + def self.define_helpers_on(example_group) + example_group.__send__(:include, module_for(example_group)) + end + + if Module.method(:const_defined?).arity == 1 # for 1.8 + # @private + # + # Gets the named constant or yields. + # On 1.8, const_defined? / const_get do not take into + # account the inheritance hierarchy. + # :nocov: + def self.get_constant_or_yield(example_group, name) + if example_group.const_defined?(name) + example_group.const_get(name) + else + yield + end + end + # :nocov: + else + # @private + # + # Gets the named constant or yields. + # On 1.9, const_defined? / const_get take into account the + # the inheritance by default, and accept an argument to + # disable this behavior. It's important that we don't + # consider inheritance here; each example group level that + # uses a `let` should get its own `LetDefinitions` module. + def self.get_constant_or_yield(example_group, name) + if example_group.const_defined?(name, (check_ancestors = false)) + example_group.const_get(name, check_ancestors) + else + yield + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata.rb new file mode 100644 index 0000000000..5e0d7e2bdb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata.rb @@ -0,0 +1,498 @@ +module RSpec + module Core + # Each ExampleGroup class and Example instance owns an instance of + # Metadata, which is Hash extended to support lazy evaluation of values + # associated with keys that may or may not be used by any example or group. + # + # In addition to metadata that is used internally, this also stores + # user-supplied metadata, e.g. + # + # RSpec.describe Something, :type => :ui do + # it "does something", :slow => true do + # # ... + # end + # end + # + # `:type => :ui` is stored in the Metadata owned by the example group, and + # `:slow => true` is stored in the Metadata owned by the example. These can + # then be used to select which examples are run using the `--tag` option on + # the command line, or several methods on `Configuration` used to filter a + # run (e.g. `filter_run_including`, `filter_run_excluding`, etc). + # + # @see Example#metadata + # @see ExampleGroup.metadata + # @see FilterManager + # @see Configuration#filter_run_including + # @see Configuration#filter_run_excluding + module Metadata + # Matches strings either at the beginning of the input or prefixed with a + # whitespace, containing the current path, either postfixed with the + # separator, or at the end of the string. Match groups are the character + # before and the character after the string if any. + # + # http://rubular.com/r/fT0gmX6VJX + # http://rubular.com/r/duOrD4i3wb + # http://rubular.com/r/sbAMHFrOx1 + def self.relative_path_regex + @relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/ + end + + # @api private + # + # @param line [String] current code line + # @return [String] relative path to line + def self.relative_path(line) + line = line.sub(relative_path_regex, "\\1.\\2".freeze) + line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze) + return nil if line == '-e:1'.freeze + line + rescue SecurityError + # :nocov: + nil + # :nocov: + end + + # @private + # Iteratively walks up from the given metadata through all + # example group ancestors, yielding each metadata hash along the way. + def self.ascending(metadata) + yield metadata + return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] }) + + loop do + yield group_metadata + break unless (group_metadata = group_metadata[:parent_example_group]) + end + end + + # @private + # Returns an enumerator that iteratively walks up the given metadata through all + # example group ancestors, yielding each metadata hash along the way. + def self.ascend(metadata) + enum_for(:ascending, metadata) + end + + # @private + # Used internally to build a hash from an args array. + # Symbols are converted into hash keys with a value of `true`. + # This is done to support simple tagging using a symbol, rather + # than needing to do `:symbol => true`. + def self.build_hash_from(args, warn_about_example_group_filtering=false) + hash = args.last.is_a?(Hash) ? args.pop : {} + + hash[args.pop] = true while args.last.is_a?(Symbol) + + if warn_about_example_group_filtering && hash.key?(:example_group) + RSpec.deprecate("Filtering by an `:example_group` subhash", + :replacement => "the subhash to filter directly") + end + + hash + end + + # @private + def self.deep_hash_dup(object) + return object.dup if Array === object + return object unless Hash === object + + object.inject(object.dup) do |duplicate, (key, value)| + duplicate[key] = deep_hash_dup(value) + duplicate + end + end + + # @private + def self.id_from(metadata) + "#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]" + end + + # @private + def self.location_tuple_from(metadata) + [metadata[:absolute_file_path], metadata[:line_number]] + end + + # @private + # Used internally to populate metadata hashes with computed keys + # managed by RSpec. + class HashPopulator + attr_reader :metadata, :user_metadata, :description_args, :block + + def initialize(metadata, user_metadata, index_provider, description_args, block) + @metadata = metadata + @user_metadata = user_metadata + @index_provider = index_provider + @description_args = description_args + @block = block + end + + def populate + ensure_valid_user_keys + + metadata[:block] = block + metadata[:description_args] = description_args + metadata[:description] = build_description_from(*metadata[:description_args]) + metadata[:full_description] = full_description + metadata[:described_class] = described_class + + populate_location_attributes + metadata.update(user_metadata) + end + + private + + def populate_location_attributes + backtrace = user_metadata.delete(:caller) + + file_path, line_number = if backtrace + file_path_and_line_number_from(backtrace) + elsif block.respond_to?(:source_location) + block.source_location + else + file_path_and_line_number_from(caller) + end + + relative_file_path = Metadata.relative_path(file_path) + absolute_file_path = File.expand_path(relative_file_path) + metadata[:file_path] = relative_file_path + metadata[:line_number] = line_number.to_i + metadata[:location] = "#{relative_file_path}:#{line_number}" + metadata[:absolute_file_path] = absolute_file_path + metadata[:rerun_file_path] ||= relative_file_path + metadata[:scoped_id] = build_scoped_id_for(absolute_file_path) + end + + def file_path_and_line_number_from(backtrace) + first_caller_from_outside_rspec = backtrace.find { |l| l !~ CallerFilter::LIB_REGEX } + first_caller_from_outside_rspec ||= backtrace.first + /(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures + end + + def description_separator(parent_part, child_part) + if parent_part.is_a?(Module) && /^(?:#|::|\.)/.match(child_part.to_s) + ''.freeze + else + ' '.freeze + end + end + + def build_description_from(parent_description=nil, my_description=nil) + return parent_description.to_s unless my_description + return my_description.to_s if parent_description.to_s == '' + separator = description_separator(parent_description, my_description) + (parent_description.to_s + separator) << my_description.to_s + end + + def build_scoped_id_for(file_path) + index = @index_provider.call(file_path).to_s + parent_scoped_id = metadata.fetch(:scoped_id) { return index } + "#{parent_scoped_id}:#{index}" + end + + def ensure_valid_user_keys + RESERVED_KEYS.each do |key| + next unless user_metadata.key?(key) + raise <<-EOM.gsub(/^\s+\|/, '') + |#{"*" * 50} + |:#{key} is not allowed + | + |RSpec reserves some hash keys for its own internal use, + |including :#{key}, which is used on: + | + | #{CallerFilter.first_non_rspec_line}. + | + |Here are all of RSpec's reserved hash keys: + | + | #{RESERVED_KEYS.join("\n ")} + |#{"*" * 50} + EOM + end + end + end + + # @private + class ExampleHash < HashPopulator + def self.create(group_metadata, user_metadata, index_provider, description, block) + example_metadata = group_metadata.dup + group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash| + hash[:parent_example_group] + end) + group_metadata.update(example_metadata) + + example_metadata[:execution_result] = Example::ExecutionResult.new + example_metadata[:example_group] = group_metadata + example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace + example_metadata.delete(:parent_example_group) + + description_args = description.nil? ? [] : [description] + hash = new(example_metadata, user_metadata, index_provider, description_args, block) + hash.populate + hash.metadata + end + + private + + def described_class + metadata[:example_group][:described_class] + end + + def full_description + build_description_from( + metadata[:example_group][:full_description], + metadata[:description] + ) + end + end + + # @private + class ExampleGroupHash < HashPopulator + def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block) + group_metadata = hash_with_backwards_compatibility_default_proc + + if parent_group_metadata + group_metadata.update(parent_group_metadata) + group_metadata[:parent_example_group] = parent_group_metadata + end + + hash = new(group_metadata, user_metadata, example_group_index, args, block) + hash.populate + hash.metadata + end + + def self.hash_with_backwards_compatibility_default_proc + Hash.new(&backwards_compatibility_default_proc { |hash| hash }) + end + + def self.backwards_compatibility_default_proc(&example_group_selector) + Proc.new do |hash, key| + case key + when :example_group + # We commonly get here when rspec-core is applying a previously + # configured filter rule, such as when a gem configures: + # + # RSpec.configure do |c| + # c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ } + # end + # + # It's confusing for a user to get a deprecation at this point in + # the code, so instead we issue a deprecation from the config APIs + # that take a metadata hash, and MetadataFilter sets this thread + # local to silence the warning here since it would be so + # confusing. + unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] + RSpec.deprecate("The `:example_group` key in an example group's metadata hash", + :replacement => "the example group's hash directly for the " \ + "computed keys and `:parent_example_group` to access the parent " \ + "example group metadata") + end + + group_hash = example_group_selector.call(hash) + LegacyExampleGroupHash.new(group_hash) if group_hash + when :example_group_block + RSpec.deprecate("`metadata[:example_group_block]`", + :replacement => "`metadata[:block]`") + hash[:block] + when :describes + RSpec.deprecate("`metadata[:describes]`", + :replacement => "`metadata[:described_class]`") + hash[:described_class] + end + end + end + + private + + def described_class + candidate = metadata[:description_args].first + return candidate unless NilClass === candidate || String === candidate + parent_group = metadata[:parent_example_group] + parent_group && parent_group[:described_class] + end + + def full_description + description = metadata[:description] + parent_example_group = metadata[:parent_example_group] + return description unless parent_example_group + + parent_description = parent_example_group[:full_description] + separator = description_separator(parent_example_group[:description_args].last, + metadata[:description_args].first) + + parent_description + separator + description + end + end + + # @private + RESERVED_KEYS = [ + :description, + :description_args, + :described_class, + :example_group, + :parent_example_group, + :execution_result, + :last_run_status, + :file_path, + :absolute_file_path, + :rerun_file_path, + :full_description, + :line_number, + :location, + :scoped_id, + :block, + :shared_group_inclusion_backtrace + ] + end + + # Mixin that makes the including class imitate a hash for backwards + # compatibility. The including class should use `attr_accessor` to + # declare attributes. + # @private + module HashImitatable + def self.included(klass) + klass.extend ClassMethods + end + + def to_h + hash = extra_hash_attributes.dup + + self.class.hash_attribute_names.each do |name| + hash[name] = __send__(name) + end + + hash + end + + (Hash.public_instance_methods - Object.public_instance_methods).each do |method_name| + next if [:[], :[]=, :to_h].include?(method_name.to_sym) + + define_method(method_name) do |*args, &block| + issue_deprecation(method_name, *args) + + hash = hash_for_delegation + self.class.hash_attribute_names.each do |name| + hash.delete(name) unless instance_variable_defined?(:"@#{name}") + end + + hash.__send__(method_name, *args, &block).tap do + # apply mutations back to the object + hash.each do |name, value| + if directly_supports_attribute?(name) + set_value(name, value) + else + extra_hash_attributes[name] = value + end + end + end + end + end + + def [](key) + issue_deprecation(:[], key) + + if directly_supports_attribute?(key) + get_value(key) + else + extra_hash_attributes[key] + end + end + + def []=(key, value) + issue_deprecation(:[]=, key, value) + + if directly_supports_attribute?(key) + set_value(key, value) + else + extra_hash_attributes[key] = value + end + end + + private + + def extra_hash_attributes + @extra_hash_attributes ||= {} + end + + def directly_supports_attribute?(name) + self.class.hash_attribute_names.include?(name) + end + + def get_value(name) + __send__(name) + end + + def set_value(name, value) + __send__(:"#{name}=", value) + end + + def hash_for_delegation + to_h + end + + def issue_deprecation(_method_name, *_args) + # no-op by default: subclasses can override + end + + # @private + module ClassMethods + def hash_attribute_names + @hash_attribute_names ||= [] + end + + def attr_accessor(*names) + hash_attribute_names.concat(names) + super + end + end + end + + # @private + # Together with the example group metadata hash default block, + # provides backwards compatibility for the old `:example_group` + # key. In RSpec 2.x, the computed keys of a group's metadata + # were exposed from a nested subhash keyed by `[:example_group]`, and + # then the parent group's metadata was exposed by sub-subhash + # keyed by `[:example_group][:example_group]`. + # + # In RSpec 3, we reorganized this to that the computed keys are + # exposed directly of the group metadata hash (no nesting), and + # `:parent_example_group` returns the parent group's metadata. + # + # Maintaining backwards compatibility was difficult: we wanted + # `:example_group` to return an object that: + # + # * Exposes the top-level metadata keys that used to be nested + # under `:example_group`. + # * Supports mutation (rspec-rails, for example, assigns + # `metadata[:example_group][:described_class]` when you use + # anonymous controller specs) such that changes are written + # back to the top-level metadata hash. + # * Exposes the parent group metadata as + # `[:example_group][:example_group]`. + class LegacyExampleGroupHash + include HashImitatable + + def initialize(metadata) + @metadata = metadata + parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group] + self[:example_group] = parent_group_metadata if parent_group_metadata + end + + def to_h + super.merge(@metadata) + end + + private + + def directly_supports_attribute?(name) + name != :example_group + end + + def get_value(name) + @metadata[name] + end + + def set_value(name, value) + @metadata[name] = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata_filter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata_filter.rb new file mode 100644 index 0000000000..9906f7de52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/metadata_filter.rb @@ -0,0 +1,255 @@ +module RSpec + module Core + # Contains metadata filtering logic. This has been extracted from + # the metadata classes because it operates ON a metadata hash but + # does not manage any of the state in the hash. We're moving towards + # having metadata be a raw hash (not a custom subclass), so externalizing + # this filtering logic helps us move in that direction. + module MetadataFilter + class << self + # @private + def apply?(predicate, filters, metadata) + filters.__send__(predicate) { |k, v| filter_applies?(k, v, metadata) } + end + + # @private + def filter_applies?(key, filter_value, metadata) + silence_metadata_example_group_deprecations do + return location_filter_applies?(filter_value, metadata) if key == :locations + return id_filter_applies?(filter_value, metadata) if key == :ids + return filters_apply?(key, filter_value, metadata) if Hash === filter_value + + meta_value = metadata.fetch(key) { return false } + + return true if TrueClass === filter_value && meta_value + return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value + return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value + + filter_value === meta_value || filter_value.to_s == meta_value.to_s + end + end + + # @private + def silence_metadata_example_group_deprecations + RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true + yield + ensure + RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations) + end + + private + + def filter_applies_to_any_value?(key, value, metadata) + metadata[key].any? { |v| filter_applies?(key, v, key => value) } + end + + def id_filter_applies?(rerun_paths_to_scoped_ids, metadata) + scoped_ids = rerun_paths_to_scoped_ids.fetch(metadata[:rerun_file_path]) { return false } + + Metadata.ascend(metadata).any? do |meta| + scoped_ids.include?(meta[:scoped_id]) + end + end + + def location_filter_applies?(locations, metadata) + Metadata.ascend(metadata).any? do |meta| + file_path = meta[:absolute_file_path] + line_num = meta[:line_number] + + locations[file_path].any? do |filter_line_num| + line_num == RSpec.world.preceding_declaration_line(file_path, filter_line_num) + end + end + end + + def proc_filter_applies?(key, proc, metadata) + case proc.arity + when 0 then proc.call + when 2 then proc.call(metadata[key], metadata) + else proc.call(metadata[key]) + end + end + + def filters_apply?(key, value, metadata) + subhash = metadata[key] + return false unless Hash === subhash || HashImitatable === subhash + value.all? { |k, v| filter_applies?(k, v, subhash) } + end + end + end + + # Tracks a collection of filterable items (e.g. modules, hooks, etc) + # and provides an optimized API to get the applicable items for the + # metadata of an example or example group. + # + # There are two implementations, optimized for different uses. + # @private + module FilterableItemRepository + # This implementation is simple, and is optimized for frequent + # updates but rare queries. `append` and `prepend` do no extra + # processing, and no internal memoization is done, since this + # is not optimized for queries. + # + # This is ideal for use by a example or example group, which may + # be updated multiple times with globally configured hooks, etc, + # but will not be queried frequently by other examples or examle + # groups. + # @private + class UpdateOptimized + attr_reader :items_and_filters + + def initialize(applies_predicate) + @applies_predicate = applies_predicate + @items_and_filters = [] + end + + def append(item, metadata) + @items_and_filters << [item, metadata] + end + + def prepend(item, metadata) + @items_and_filters.unshift [item, metadata] + end + + def delete(item, metadata) + @items_and_filters.delete [item, metadata] + end + + def items_for(request_meta) + @items_and_filters.each_with_object([]) do |(item, item_meta), to_return| + to_return << item if item_meta.empty? || + MetadataFilter.apply?(@applies_predicate, item_meta, request_meta) + end + end + + unless [].respond_to?(:each_with_object) # For 1.8.7 + # :nocov: + undef items_for + def items_for(request_meta) + @items_and_filters.inject([]) do |to_return, (item, item_meta)| + to_return << item if item_meta.empty? || + MetadataFilter.apply?(@applies_predicate, item_meta, request_meta) + to_return + end + end + # :nocov: + end + end + + # This implementation is much more complex, and is optimized for + # rare (or hopefully no) updates once the queries start. Updates + # incur a cost as it has to clear the memoization and keep track + # of applicable keys. Queries will be O(N) the first time an item + # is provided with a given set of applicable metadata; subsequent + # queries with items with the same set of applicable metadata will + # be O(1) due to internal memoization. + # + # This is ideal for use by config, where filterable items (e.g. hooks) + # are typically added at the start of the process (e.g. in `spec_helper`) + # and then repeatedly queried as example groups and examples are defined. + # @private + class QueryOptimized < UpdateOptimized + alias find_items_for items_for + private :find_items_for + + def initialize(applies_predicate) + super + @applicable_keys = Set.new + @proc_keys = Set.new + @memoized_lookups = Hash.new do |hash, applicable_metadata| + hash[applicable_metadata] = find_items_for(applicable_metadata) + end + end + + def append(item, metadata) + super + handle_mutation(metadata) + end + + def prepend(item, metadata) + super + handle_mutation(metadata) + end + + def delete(item, metadata) + super + reconstruct_caches + end + + def items_for(metadata) + # The filtering of `metadata` to `applicable_metadata` is the key thing + # that makes the memoization actually useful in practice, since each + # example and example group have different metadata (e.g. location and + # description). By filtering to the metadata keys our items care about, + # we can ignore extra metadata keys that differ for each example/group. + # For example, given `config.include DBHelpers, :db`, example groups + # can be split into these two sets: those that are tagged with `:db` and those + # that are not. For each set, this method for the first group in the set is + # still an `O(N)` calculation, but all subsequent groups in the set will be + # constant time lookups when they call this method. + applicable_metadata = applicable_metadata_from(metadata) + + if applicable_metadata.any? { |k, _| @proc_keys.include?(k) } + # It's unsafe to memoize lookups involving procs (since they can + # be non-deterministic), so we skip the memoization in this case. + find_items_for(applicable_metadata) + else + @memoized_lookups[applicable_metadata] + end + end + + private + + def reconstruct_caches + @applicable_keys.clear + @proc_keys.clear + @items_and_filters.each do |_item, metadata| + handle_mutation(metadata) + end + end + + def handle_mutation(metadata) + @applicable_keys.merge(metadata.keys) + @proc_keys.merge(proc_keys_from metadata) + @memoized_lookups.clear + end + + def applicable_metadata_from(metadata) + MetadataFilter.silence_metadata_example_group_deprecations do + @applicable_keys.inject({}) do |hash, key| + # :example_group is treated special here because... + # - In RSpec 2, example groups had an `:example_group` key + # - In RSpec 3, that key is deprecated (it was confusing!). + # - The key is not technically present in an example group metadata hash + # (and thus would fail the `metadata.key?(key)` check) but a value + # is provided when accessed via the hash's `default_proc` + # - Thus, for backwards compatibility, we have to explicitly check + # for `:example_group` here if it is one of the keys being used to + # filter. + hash[key] = metadata[key] if metadata.key?(key) || key == :example_group + hash + end + end + end + + def proc_keys_from(metadata) + metadata.each_with_object([]) do |(key, value), to_return| + to_return << key if Proc === value + end + end + + unless [].respond_to?(:each_with_object) # For 1.8.7 + # :nocov: + undef proc_keys_from + def proc_keys_from(metadata) + metadata.inject([]) do |to_return, (key, value)| + to_return << key if Proc === value + to_return + end + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/minitest_assertions_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/minitest_assertions_adapter.rb new file mode 100644 index 0000000000..25db7514a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/minitest_assertions_adapter.rb @@ -0,0 +1,31 @@ +begin + # Only the minitest 5.x gem includes the minitest.rb and assertions.rb files. + require 'minitest' + require 'minitest/assertions' +rescue LoadError + # We must be using Ruby Core's MiniTest or the Minitest gem 4.x. + require 'minitest/unit' + Minitest = MiniTest +end + +module RSpec + module Core + # @private + module MinitestAssertionsAdapter + include ::Minitest::Assertions + # Need to forcefully include Pending after Minitest::Assertions + # to make sure our own #skip method beats Minitest's. + include ::RSpec::Core::Pending + + # Minitest 5.x requires this accessor to be available. See + # https://github.com/seattlerb/minitest/blob/38f0a5fcbd9c37c3f80a3eaad4ba84d3fc9947a0/lib/minitest/assertions.rb#L8 + # + # It is not required for other extension libraries, and RSpec does not + # report or make this information available to formatters. + attr_writer :assertions + def assertions + @assertions ||= 0 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/flexmock.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/flexmock.rb new file mode 100644 index 0000000000..91475ae7f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/flexmock.rb @@ -0,0 +1,31 @@ +# Created by Jim Weirich on 2007-04-10. +# Copyright (c) 2007. All rights reserved. + +require 'flexmock/rspec' + +module RSpec + module Core + module MockingAdapters + # @private + module Flexmock + include ::FlexMock::MockContainer + + def self.framework_name + :flexmock + end + + def setup_mocks_for_rspec + # No setup required. + end + + def verify_mocks_for_rspec + flexmock_verify + end + + def teardown_mocks_for_rspec + flexmock_close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/mocha.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/mocha.rb new file mode 100644 index 0000000000..8caf7b6442 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/mocha.rb @@ -0,0 +1,57 @@ +# In order to support all versions of mocha, we have to jump through some +# hoops here. +# +# mocha >= '0.13.0': +# require 'mocha/api' is required. +# require 'mocha/object' raises a LoadError b/c the file no longer exists. +# mocha < '0.13.0', >= '0.9.7' +# require 'mocha/api' is required. +# require 'mocha/object' is required. +# mocha < '0.9.7': +# require 'mocha/api' raises a LoadError b/c the file does not yet exist. +# require 'mocha/standalone' is required. +# require 'mocha/object' is required. +begin + require 'mocha/api' + + begin + require 'mocha/object' + rescue LoadError + # Mocha >= 0.13.0 no longer contains this file nor needs it to be loaded. + end +rescue LoadError + require 'mocha/standalone' + require 'mocha/object' +end + +module RSpec + module Core + module MockingAdapters + # @private + module Mocha + def self.framework_name + :mocha + end + + # Mocha::Standalone was deprecated as of Mocha 0.9.7. + begin + include ::Mocha::API + rescue NameError + include ::Mocha::Standalone + end + + def setup_mocks_for_rspec + mocha_setup + end + + def verify_mocks_for_rspec + mocha_verify + end + + def teardown_mocks_for_rspec + mocha_teardown + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/null.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/null.rb new file mode 100644 index 0000000000..442de9a700 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/null.rb @@ -0,0 +1,14 @@ +module RSpec + module Core + module MockingAdapters + # @private + module Null + def setup_mocks_for_rspec; end + + def verify_mocks_for_rspec; end + + def teardown_mocks_for_rspec; end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rr.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rr.rb new file mode 100644 index 0000000000..d72651a626 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rr.rb @@ -0,0 +1,31 @@ +require 'rr' + +RSpec.configuration.backtrace_exclusion_patterns.push(RR::Errors::BACKTRACE_IDENTIFIER) + +module RSpec + module Core + # @private + module MockingAdapters + # @private + module RR + def self.framework_name + :rr + end + + include ::RR::Extensions::InstanceMethods + + def setup_mocks_for_rspec + ::RR::Space.instance.reset + end + + def verify_mocks_for_rspec + ::RR::Space.instance.verify_doubles + end + + def teardown_mocks_for_rspec + ::RR::Space.instance.reset + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rspec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rspec.rb new file mode 100644 index 0000000000..bb3f0ae660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/mocking_adapters/rspec.rb @@ -0,0 +1,32 @@ +require 'rspec/mocks' + +module RSpec + module Core + module MockingAdapters + # @private + module RSpec + include ::RSpec::Mocks::ExampleMethods + + def self.framework_name + :rspec + end + + def self.configuration + ::RSpec::Mocks.configuration + end + + def setup_mocks_for_rspec + ::RSpec::Mocks.setup + end + + def verify_mocks_for_rspec + ::RSpec::Mocks.verify + end + + def teardown_mocks_for_rspec + ::RSpec::Mocks.teardown + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/notifications.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/notifications.rb new file mode 100644 index 0000000000..16a3255cce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/notifications.rb @@ -0,0 +1,521 @@ +RSpec::Support.require_rspec_core "formatters/console_codes" +RSpec::Support.require_rspec_core "formatters/exception_presenter" +RSpec::Support.require_rspec_core "formatters/helpers" +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec::Core + # Notifications are value objects passed to formatters to provide them + # with information about a particular event of interest. + module Notifications + # @private + module NullColorizer + module_function + + def wrap(line, _code_or_symbol) + line + end + end + + # The `StartNotification` represents a notification sent by the reporter + # when the suite is started. It contains the expected amount of examples + # to be executed, and the load time of RSpec. + # + # @attr count [Fixnum] the number counted + # @attr load_time [Float] the number of seconds taken to boot RSpec + # and load the spec files + StartNotification = Struct.new(:count, :load_time) + + # The `ExampleNotification` represents notifications sent by the reporter + # which contain information about the current (or soon to be) example. + # It is used by formatters to access information about that example. + # + # @example + # def example_started(notification) + # puts "Hey I started #{notification.example.description}" + # end + # + # @attr example [RSpec::Core::Example] the current example + ExampleNotification = Struct.new(:example) + class ExampleNotification + # @private + def self.for(example) + execution_result = example.execution_result + + return SkippedExampleNotification.new(example) if execution_result.example_skipped? + return new(example) unless execution_result.status == :pending || execution_result.status == :failed + + klass = if execution_result.pending_fixed? + PendingExampleFixedNotification + elsif execution_result.status == :pending + PendingExampleFailedAsExpectedNotification + else + FailedExampleNotification + end + + klass.new(example) + end + + private_class_method :new + end + + # The `ExamplesNotification` represents notifications sent by the reporter + # which contain information about the suites examples. + # + # @example + # def stop(notification) + # puts "Hey I ran #{notification.examples.size}" + # end + # + class ExamplesNotification + def initialize(reporter) + @reporter = reporter + end + + # @return [Array] list of examples + def examples + @reporter.examples + end + + # @return [Array] list of failed examples + def failed_examples + @reporter.failed_examples + end + + # @return [Array] list of pending examples + def pending_examples + @reporter.pending_examples + end + + # @return [Array] + # returns examples as notifications + def notifications + @notifications ||= format_examples(examples) + end + + # @return [Array] + # returns failed examples as notifications + def failure_notifications + @failed_notifications ||= format_examples(failed_examples) + end + + # @return [Array] + # returns pending examples as notifications + def pending_notifications + @pending_notifications ||= format_examples(pending_examples) + end + + # @return [String] The list of failed examples, fully formatted in the way + # that RSpec's built-in formatters emit. + def fully_formatted_failed_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nFailures:\n" + + failure_notifications.each_with_index do |failure, index| + formatted += failure.fully_formatted(index.next, colorizer) + end + + formatted + end + + # @return [String] The list of pending examples, fully formatted in the + # way that RSpec's built-in formatters emit. + def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n".dup + + pending_notifications.each_with_index do |notification, index| + formatted << notification.fully_formatted(index.next, colorizer) + end + + formatted + end + + private + + def format_examples(examples) + examples.map do |example| + ExampleNotification.for(example) + end + end + end + + # The `FailedExampleNotification` extends `ExampleNotification` with + # things useful for examples that have failure info -- typically a + # failed or pending spec. + # + # @example + # def example_failed(notification) + # puts "Hey I failed :(" + # puts "Here's my stack trace" + # puts notification.exception.backtrace.join("\n") + # end + # + # @attr [RSpec::Core::Example] example the current example + # @see ExampleNotification + class FailedExampleNotification < ExampleNotification + public_class_method :new + + # @return [Exception] The example failure + def exception + @exception_presenter.exception + end + + # @return [String] The example description + def description + @exception_presenter.description + end + + # Returns the message generated for this failure line by line. + # + # @return [Array] The example failure message + def message_lines + @exception_presenter.message_lines + end + + # Returns the message generated for this failure colorized line by line. + # + # @param colorizer [#wrap] An object to colorize the message_lines by + # @return [Array] The example failure message colorized + def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.colorized_message_lines(colorizer) + end + + # Returns the failures formatted backtrace. + # + # @return [Array] the examples backtrace lines + def formatted_backtrace + @exception_presenter.formatted_backtrace + end + + # Returns the failures colorized formatted backtrace. + # + # @param colorizer [#wrap] An object to colorize the message_lines by + # @return [Array] the examples colorized backtrace lines + def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.colorized_formatted_backtrace(colorizer) + end + + # @return [String] The failure information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.fully_formatted(failure_number, colorizer) + end + + # @return [Array] The failure information fully formatted in the way that + # RSpec's built-in formatters emit, split by line. + def fully_formatted_lines(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.fully_formatted_lines(failure_number, colorizer) + end + + private + + def initialize(example, exception_presenter=Formatters::ExceptionPresenter::Factory.new(example).build) + @exception_presenter = exception_presenter + super(example) + end + end + + # @deprecated Use {FailedExampleNotification} instead. + class PendingExampleFixedNotification < FailedExampleNotification; end + + # @deprecated Use {FailedExampleNotification} instead. + class PendingExampleFailedAsExpectedNotification < FailedExampleNotification; end + + # The `SkippedExampleNotification` extends `ExampleNotification` with + # things useful for specs that are skipped. + # + # @attr [RSpec::Core::Example] example the current example + # @see ExampleNotification + class SkippedExampleNotification < ExampleNotification + public_class_method :new + + # @return [String] The pending detail fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location) + + [ + colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending), + "\n ", + Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer), + "\n", + colorizer.wrap(" # #{formatted_caller}\n", :detail) + ].join("") + end + end + + # The `GroupNotification` represents notifications sent by the reporter + # which contain information about the currently running (or soon to be) + # example group. It is used by formatters to access information about that + # group. + # + # @example + # def example_group_started(notification) + # puts "Hey I started #{notification.group.description}" + # end + # @attr group [RSpec::Core::ExampleGroup] the current group + GroupNotification = Struct.new(:group) + + # The `MessageNotification` encapsulates generic messages that the reporter + # sends to formatters. + # + # @attr message [String] the message + MessageNotification = Struct.new(:message) + + # The `SeedNotification` holds the seed used to randomize examples and + # whether that seed has been used or not. + # + # @attr seed [Fixnum] the seed used to randomize ordering + # @attr used [Boolean] whether the seed has been used or not + SeedNotification = Struct.new(:seed, :used) + class SeedNotification + # @api + # @return [Boolean] has the seed been used? + def seed_used? + !!used + end + private :used + + # @return [String] The seed information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted + "\nRandomized with seed #{seed}\n" + end + end + + # The `SummaryNotification` holds information about the results of running + # a test suite. It is used by formatters to provide information at the end + # of the test run. + # + # @attr duration [Float] the time taken (in seconds) to run the suite + # @attr examples [Array] the examples run + # @attr failed_examples [Array] the failed examples + # @attr pending_examples [Array] the pending examples + # @attr load_time [Float] the number of seconds taken to boot RSpec + # and load the spec files + # @attr errors_outside_of_examples_count [Integer] the number of errors that + # have occurred processing + # the spec suite + SummaryNotification = Struct.new(:duration, :examples, :failed_examples, + :pending_examples, :load_time, + :errors_outside_of_examples_count) + class SummaryNotification + # @api + # @return [Fixnum] the number of examples run + def example_count + @example_count ||= examples.size + end + + # @api + # @return [Fixnum] the number of failed examples + def failure_count + @failure_count ||= failed_examples.size + end + + # @api + # @return [Fixnum] the number of pending examples + def pending_count + @pending_count ||= pending_examples.size + end + + # @api + # @return [String] A line summarising the result totals of the spec run. + def totals_line + summary = Formatters::Helpers.pluralize(example_count, "example") + + ", " + Formatters::Helpers.pluralize(failure_count, "failure") + summary += ", #{pending_count} pending" if pending_count > 0 + if errors_outside_of_examples_count > 0 + summary += ( + ", " + + Formatters::Helpers.pluralize(errors_outside_of_examples_count, "error") + + " occurred outside of examples" + ) + end + summary + end + + # @api public + # + # Wraps the results line with colors based on the configured + # colors for failure, pending, and success. Defaults to red, + # yellow, green accordingly. + # + # @param colorizer [#wrap] An object which supports wrapping text with + # specific colors. + # @return [String] A colorized results line. + def colorized_totals_line(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + if failure_count > 0 || errors_outside_of_examples_count > 0 + colorizer.wrap(totals_line, RSpec.configuration.failure_color) + elsif pending_count > 0 + colorizer.wrap(totals_line, RSpec.configuration.pending_color) + else + colorizer.wrap(totals_line, RSpec.configuration.success_color) + end + end + + # @api public + # + # Formats failures into a rerunable command format. + # + # @param colorizer [#wrap] An object which supports wrapping text with + # specific colors. + # @return [String] A colorized summary line. + def colorized_rerun_commands(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + "\nFailed examples:\n\n" + + failed_examples.map do |example| + colorizer.wrap("rspec #{rerun_argument_for(example)}", RSpec.configuration.failure_color) + " " + + colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color) + end.join("\n") + end + + # @return [String] a formatted version of the time it took to run the + # suite + def formatted_duration + Formatters::Helpers.format_duration(duration) + end + + # @return [String] a formatted version of the time it took to boot RSpec + # and load the spec files + def formatted_load_time + Formatters::Helpers.format_duration(load_time) + end + + # @return [String] The summary information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nFinished in #{formatted_duration} " \ + "(files took #{formatted_load_time} to load)\n" \ + "#{colorized_totals_line(colorizer)}\n" + + unless failed_examples.empty? + formatted += (colorized_rerun_commands(colorizer) + "\n") + end + + formatted + end + + private + + include RSpec::Core::ShellEscape + + def rerun_argument_for(example) + location = example.location_rerun_argument + return location unless duplicate_rerun_locations.include?(location) + conditionally_quote(example.id) + end + + def duplicate_rerun_locations + @duplicate_rerun_locations ||= begin + locations = RSpec.world.all_examples.map(&:location_rerun_argument) + + Set.new.tap do |s| + locations.group_by { |l| l }.each do |l, ls| + s << l if ls.count > 1 + end + end + end + end + end + + # The `ProfileNotification` holds information about the results of running a + # test suite when profiling is enabled. It is used by formatters to provide + # information at the end of the test run for profiling information. + # + # @attr duration [Float] the time taken (in seconds) to run the suite + # @attr examples [Array] the examples run + # @attr number_of_examples [Fixnum] the number of examples to profile + # @attr example_groups [Array] example groups run + class ProfileNotification + def initialize(duration, examples, number_of_examples, example_groups) + @duration = duration + @examples = examples + @number_of_examples = number_of_examples + @example_groups = example_groups + end + attr_reader :duration, :examples, :number_of_examples + + # @return [Array] the slowest examples + def slowest_examples + @slowest_examples ||= + examples.sort_by do |example| + -example.execution_result.run_time + end.first(number_of_examples) + end + + # @return [Float] the time taken (in seconds) to run the slowest examples + def slow_duration + @slow_duration ||= + slowest_examples.inject(0.0) do |i, e| + i + e.execution_result.run_time + end + end + + # @return [String] the percentage of total time taken + def percentage + @percentage ||= + begin + time_taken = slow_duration / duration + '%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100) + end + end + + # @return [Array] the slowest example groups + def slowest_groups + @slowest_groups ||= calculate_slowest_groups + end + + private + + def calculate_slowest_groups + # stop if we've only one example group + return {} if @example_groups.keys.length <= 1 + + @example_groups.each_value do |hash| + hash[:average] = hash[:total_time].to_f / hash[:count] + end + + groups = @example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples) + groups.map { |group, data| [group.location, data] } + end + end + + # The `DeprecationNotification` is issued by the reporter when a deprecated + # part of RSpec is encountered. It represents information about the + # deprecated call site. + # + # @attr message [String] A custom message about the deprecation + # @attr deprecated [String] A custom message about the deprecation (alias of + # message) + # @attr replacement [String] An optional replacement for the deprecation + # @attr call_site [String] An optional call site from which the deprecation + # was issued + DeprecationNotification = Struct.new(:deprecated, :message, :replacement, :call_site) + class DeprecationNotification + private_class_method :new + + # @api + # Convenience way to initialize the notification + def self.from_hash(data) + new data[:deprecated], data[:message], data[:replacement], data[:call_site] + end + end + + # `NullNotification` represents a placeholder value for notifications that + # currently require no information, but we may wish to extend in future. + class NullNotification + end + + # `CustomNotification` is used when sending custom events to formatters / + # other registered listeners, it creates attributes based on supplied hash + # of options. + class CustomNotification < Struct + # @param options [Hash] A hash of method / value pairs to create on this notification + # @return [CustomNotification] + # + # Build a custom notification based on the supplied option key / values. + def self.for(options={}) + return NullNotification if options.keys.empty? + new(*options.keys).new(*options.values) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/option_parser.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/option_parser.rb new file mode 100644 index 0000000000..7c27dcbed5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/option_parser.rb @@ -0,0 +1,324 @@ +# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html +require 'optparse' + +module RSpec::Core + # @private + class Parser + def self.parse(args, source=nil) + new(args).parse(source) + end + + attr_reader :original_args + + def initialize(original_args) + @original_args = original_args + end + + def parse(source=nil) + return { :files_or_directories_to_run => [] } if original_args.empty? + args = original_args.dup + + options = args.delete('--tty') ? { :tty => true } : {} + begin + parser(options).parse!(args) + rescue OptionParser::InvalidOption => e + abort "#{e.message}#{" (defined in #{source})" if source}\n\n" \ + "Please use --help for a listing of valid options" + end + + options[:files_or_directories_to_run] = args + options + end + + private + + # rubocop:disable MethodLength + # rubocop:disable Metrics/AbcSize + # rubocop:disable CyclomaticComplexity + # rubocop:disable PerceivedComplexity + # rubocop:disable Metrics/BlockLength + def parser(options) + OptionParser.new do |parser| + parser.summary_width = 34 + + parser.banner = "Usage: rspec [options] [files or directories]\n\n" + + parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs| + options[:libs] ||= [] + options[:libs].concat(dirs.split(File::PATH_SEPARATOR)) + end + + parser.on('-r', '--require PATH', 'Require a file.') do |path| + options[:requires] ||= [] + options[:requires] << path + end + + parser.on('-O', '--options PATH', 'Specify the path to a custom options file.') do |path| + options[:custom_options_file] = path + end + + parser.on('--order TYPE[:SEED]', 'Run examples by the specified order type.', + ' [defined] examples and groups are run in the order they are defined', + ' [rand] randomize the order of groups and examples', + ' [random] alias for rand', + ' [random:SEED] e.g. --order random:123') do |o| + options[:order] = o + end + + parser.on('--seed SEED', Integer, 'Equivalent of --order rand:SEED.') do |seed| + options[:order] = "rand:#{seed}" + end + + parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ', + ' smallest reproducible case.') do |argument| + options[:bisect] = argument || true + options[:runner] = RSpec::Core::Invocations::Bisect.new + end + + parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument| + if argument == true + value = 1 + elsif argument == false || argument == 0 + value = false + else + begin + value = Integer(argument) + rescue ArgumentError + RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil + end + end + set_fail_fast(options, value) + end + + parser.on('--failure-exit-code CODE', Integer, + 'Override the exit code used when there are failing specs.') do |code| + options[:failure_exit_code] = code + end + + parser.on('--error-exit-code CODE', Integer, + 'Override the exit code used when there are errors loading or running specs outside of examples.') do |code| + options[:error_exit_code] = code + end + + parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |use_drb| + options[:drb] = use_drb + options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb + end + + parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o| + options[:drb_port] = o.to_i + end + + parser.separator("\n **** Output ****\n\n") + + parser.on('-f', '--format FORMATTER', 'Choose a formatter.', + ' [p]rogress (default - dots)', + ' [d]ocumentation (group and example names)', + ' [h]tml', + ' [j]son', + ' [f]ailures ("file:line:reason", suitable for editors integration)', + ' custom formatter class name') do |o| + options[:formatters] ||= [] + options[:formatters] << [o] + end + + parser.on('-o', '--out FILE', + 'Write output to a file instead of $stdout. This option applies', + ' to the previously specified --format, or the default format', + ' if no format is specified.' + ) do |o| + options[:formatters] ||= [['progress']] + options[:formatters].last << o + end + + parser.on('--deprecation-out FILE', 'Write deprecation warnings to a file instead of $stderr.') do |file| + options[:deprecation_stream] = file + end + + parser.on('-b', '--backtrace', 'Enable full backtrace.') do |_o| + options[:full_backtrace] = true + end + + parser.on('-c', '--color', '--colour', '') do |_o| + # flag will be excluded from `--help` output because it is deprecated + options[:color] = true + options[:color_mode] = :automatic + end + + parser.on('--force-color', '--force-colour', 'Force the output to be in color, even if the output is not a TTY') do |_o| + if options[:color_mode] == :off + abort "Please only use one of `--force-color` and `--no-color`" + end + options[:color_mode] = :on + end + + parser.on('--no-color', '--no-colour', 'Force the output to not be in color, even if the output is a TTY') do |_o| + if options[:color_mode] == :on + abort "Please only use one of --force-color and --no-color" + end + options[:color_mode] = :off + end + + parser.on('-p', '--[no-]profile [COUNT]', + 'Enable profiling of examples and list the slowest examples (default: 10).') do |argument| + options[:profile_examples] = if argument.nil? + true + elsif argument == false + false + else + begin + Integer(argument) + rescue ArgumentError + RSpec.warning "Non integer specified as profile count, separate " \ + "your path from options with -- e.g. " \ + "`rspec --profile -- #{argument}`", + :call_site => nil + true + end + end + end + + parser.on('--dry-run', 'Print the formatter output of your suite without', + ' running any examples or hooks') do |_o| + options[:dry_run] = true + end + + parser.on('-w', '--warnings', 'Enable ruby warnings') do + if Object.const_defined?(:Warning) && Warning.respond_to?(:[]=) + Warning[:deprecated] = true + end + $VERBOSE = true + end + + parser.separator <<-FILTERING + + **** Filtering/tags **** + + In addition to the following options for selecting specific files, groups, or + examples, you can select individual examples by appending the line number(s) to + the filename: + + rspec path/to/a_spec.rb:37:87 + + You can also pass example ids enclosed in square brackets: + + rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group + +FILTERING + + parser.on('--only-failures', "Filter to just the examples that failed the last time they ran.") do + configure_only_failures(options) + end + + parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.", + " (Equivalent to `--only-failures --fail-fast --order defined`)") do + configure_only_failures(options) + set_fail_fast(options, 1) + options[:order] ||= 'defined' + end + + parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o| + if options[:pattern] + options[:pattern] += ',' + o + else + options[:pattern] = o + end + end + + parser.on('--exclude-pattern PATTERN', + 'Load files except those matching pattern. Opposite effect of --pattern.') do |o| + options[:exclude_pattern] = o + end + + parser.on('-e', '--example STRING', "Run examples whose full nested names include STRING (may be", + " used more than once)") do |o| + (options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o)) + end + + parser.on('-E', '--example-matches REGEX', "Run examples whose full nested names match REGEX (may be", + " used more than once)") do |o| + (options[:full_description] ||= []) << Regexp.compile(o) + end + + parser.on('-t', '--tag TAG[:VALUE]', + 'Run examples with the specified tag, or exclude examples', + 'by adding ~ before the tag.', + ' - e.g. ~slow', + ' - TAG is always converted to a symbol') do |tag| + filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter + + name, value = tag.gsub(/^(~@|~|@)/, '').split(':', 2) + name = name.to_sym + + parsed_value = case value + when nil then true # The default value for tags is true + when 'true' then true + when 'false' then false + when 'nil' then nil + when /^:/ then value[1..-1].to_sym + when /^\d+$/ then Integer(value) + when /^\d+.\d+$/ then Float(value) + else + value + end + + add_tag_filter(options, filter_type, name, parsed_value) + end + + parser.on('--default-path PATH', 'Set the default path where RSpec looks for examples (can', + ' be a path to a file or a directory).') do |path| + options[:default_path] = path + end + + parser.separator("\n **** Utility ****\n\n") + + parser.on('--init', 'Initialize your project with RSpec.') do |_cmd| + options[:runner] = RSpec::Core::Invocations::InitializeProject.new + end + + parser.on('-v', '--version', 'Display the version.') do + options[:runner] = RSpec::Core::Invocations::PrintVersion.new + end + + # These options would otherwise be confusing to users, so we forcibly + # prevent them from executing. + # + # * --I is too similar to -I. + # * -d was a shorthand for --debugger, which is removed, but now would + # trigger --default-path. + invalid_options = %w[-d --I] + + hidden_options = invalid_options + %w[-c] + + parser.on_tail('-h', '--help', "You're looking at it.") do + options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options) + end + + # This prevents usage of the invalid_options. + invalid_options.each do |option| + parser.on(option) do + raise OptionParser::InvalidOption.new + end + end + end + end + # rubocop:enable Metrics/BlockLength + # rubocop:enable Metrics/AbcSize + # rubocop:enable MethodLength + # rubocop:enable CyclomaticComplexity + # rubocop:enable PerceivedComplexity + + def add_tag_filter(options, filter_type, tag_name, value=true) + (options[filter_type] ||= {})[tag_name] = value + end + + def set_fail_fast(options, value) + options[:fail_fast] = value + end + + def configure_only_failures(options) + options[:only_failures] = true + add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed') + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ordering.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ordering.rb new file mode 100644 index 0000000000..f2284fb0d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ordering.rb @@ -0,0 +1,158 @@ +module RSpec + module Core + # @private + module Ordering + # @private + # The default global ordering (defined order). + class Identity + def order(items) + items + end + end + + # @private + # Orders items randomly. + class Random + def initialize(configuration) + @configuration = configuration + @used = false + end + + def used? + @used + end + + def order(items) + @used = true + + seed = @configuration.seed.to_s + items.sort_by { |item| jenkins_hash_digest(seed + item.id) } + end + + private + + # http://en.wikipedia.org/wiki/Jenkins_hash_function + # Jenkins provides a good distribution and is simpler than MD5. + # It's a bit slower than MD5 (primarily because `Digest::MD5` is + # implemented in C) but has the advantage of not requiring us + # to load another part of stdlib, which we try to minimize. + def jenkins_hash_digest(string) + hash = 0 + + string.each_byte do |byte| + hash += byte + hash &= MAX_32_BIT + hash += ((hash << 10) & MAX_32_BIT) + hash &= MAX_32_BIT + hash ^= hash >> 6 + end + + hash += ((hash << 3) & MAX_32_BIT) + hash &= MAX_32_BIT + hash ^= hash >> 11 + hash += ((hash << 15) & MAX_32_BIT) + hash &= MAX_32_BIT + hash + end + + MAX_32_BIT = 4_294_967_295 + end + + # @private + # Orders items based on a custom block. + class Custom + def initialize(callable) + @callable = callable + end + + def order(list) + @callable.call(list) + end + end + + # @private + # Stores the different ordering strategies. + class Registry + def initialize(configuration) + @configuration = configuration + @strategies = {} + + register(:random, Random.new(configuration)) + + identity = Identity.new + register(:defined, identity) + + # The default global ordering is --defined. + register(:global, identity) + end + + def fetch(name, &fallback) + @strategies.fetch(name, &fallback) + end + + def register(sym, strategy) + @strategies[sym] = strategy + end + + def used_random_seed? + @strategies[:random].used? + end + end + + # @private + # Manages ordering configuration. + # + # @note This is not intended to be used externally. Use + # the APIs provided by `RSpec::Core::Configuration` instead. + class ConfigurationManager + attr_reader :seed, :ordering_registry + + def initialize + @ordering_registry = Registry.new(self) + @seed = rand(0xFFFF) + @seed_forced = false + @order_forced = false + end + + def seed_used? + ordering_registry.used_random_seed? + end + + def seed=(seed) + return if @seed_forced + register_ordering(:global, ordering_registry.fetch(:random)) + @seed = seed.to_i + end + + def order=(type) + order, seed = type.to_s.split(':') + @seed = seed.to_i if seed + + ordering_name = if order.include?('rand') + :random + elsif order == 'defined' + :defined + end + + register_ordering(:global, ordering_registry.fetch(ordering_name)) if ordering_name + end + + def force(hash) + if hash.key?(:seed) + self.seed = hash[:seed] + @seed_forced = true + @order_forced = true + elsif hash.key?(:order) + self.order = hash[:order] + @order_forced = true + end + end + + def register_ordering(name, strategy=Custom.new(Proc.new { |l| yield l })) + return if @order_forced && name == :global + ordering_registry.register(name, strategy) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/output_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/output_wrapper.rb new file mode 100644 index 0000000000..b655025cbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/output_wrapper.rb @@ -0,0 +1,29 @@ +module RSpec + module Core + # @private + class OutputWrapper + # @private + attr_accessor :output + + # @private + def initialize(output) + @output = output + end + + def respond_to?(name, priv=false) + output.respond_to?(name, priv) + end + + def method_missing(name, *args, &block) + output.send(name, *args, &block) + end + + # Redirect calls for IO interface methods + IO.instance_methods(false).each do |method| + define_method(method) do |*args, &block| + output.send(method, *args, &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/pending.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/pending.rb new file mode 100644 index 0000000000..f04e3be3b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/pending.rb @@ -0,0 +1,165 @@ +module RSpec + module Core + # Provides methods to mark examples as pending. These methods are available + # to be called from within any example or hook. + module Pending + # Raised in the middle of an example to indicate that it should be marked + # as skipped. + class SkipDeclaredInExample < StandardError + attr_reader :argument + + def initialize(argument) + @argument = argument + end + end + + # If Test::Unit is loaded, we'll use its error as baseclass, so that + # Test::Unit will report unmet RSpec expectations as failures rather than + # errors. + begin + class PendingExampleFixedError < Test::Unit::AssertionFailedError; end + rescue + class PendingExampleFixedError < StandardError; end + end + + # @private + NO_REASON_GIVEN = 'No reason given' + + # @private + NOT_YET_IMPLEMENTED = 'Not yet implemented' + + # @overload pending() + # @overload pending(message) + # + # Marks an example as pending. The rest of the example will still be + # executed, and if it passes the example will fail to indicate that the + # pending can be removed. + # + # @param message [String] optional message to add to the summary report. + # + # @example + # describe "an example" do + # # reported as "Pending: no reason given" + # it "is pending with no message" do + # pending + # raise "broken" + # end + # + # # reported as "Pending: something else getting finished" + # it "is pending with a custom message" do + # pending("something else getting finished") + # raise "broken" + # end + # end + # + # @note `before(:example)` hooks are eval'd when you use the `pending` + # method within an example. If you want to declare an example `pending` + # and bypass the `before` hooks as well, you can pass `:pending => true` + # to the `it` method: + # + # it "does something", :pending => true do + # # ... + # end + # + # or pass `:pending => "something else getting finished"` to add a + # message to the summary report: + # + # it "does something", :pending => "something else getting finished" do + # # ... + # end + def pending(message=nil) + current_example = RSpec.current_example + + if block_given? + raise ArgumentError, <<-EOS.gsub(/^\s+\|/, '') + |The semantics of `RSpec::Core::Pending#pending` have changed in + |RSpec 3. In RSpec 2.x, it caused the example to be skipped. In + |RSpec 3, the rest of the example is still run but is expected to + |fail, and will be marked as a failure (rather than as pending) if + |the example passes. + | + |Passing a block within an example is now deprecated. Marking the + |example as pending provides the same behavior in RSpec 3 which was + |provided only by the block in RSpec 2.x. + | + |Move the code in the block provided to `pending` into the rest of + |the example body. + | + |Called from #{CallerFilter.first_non_rspec_line}. + | + EOS + elsif current_example + Pending.mark_pending! current_example, message + else + raise "`pending` may not be used outside of examples, such as in " \ + "before(:context). Maybe you want `skip`?" + end + end + + # @overload skip() + # @overload skip(message) + # + # Marks an example as pending and skips execution. + # + # @param message [String] optional message to add to the summary report. + # + # @example + # describe "an example" do + # # reported as "Pending: no reason given" + # it "is skipped with no message" do + # skip + # end + # + # # reported as "Pending: something else getting finished" + # it "is skipped with a custom message" do + # skip "something else getting finished" + # end + # end + def skip(message=nil) + current_example = RSpec.current_example + + Pending.mark_skipped!(current_example, message) if current_example + + raise SkipDeclaredInExample.new(message) + end + + # @private + # + # Mark example as skipped. + # + # @param example [RSpec::Core::Example] the example to mark as skipped + # @param message_or_bool [Boolean, String] the message to use, or true + def self.mark_skipped!(example, message_or_bool) + Pending.mark_pending! example, message_or_bool + example.metadata[:skip] = true + end + + # @private + # + # Mark example as pending. + # + # @param example [RSpec::Core::Example] the example to mark as pending + # @param message_or_bool [Boolean, String] the message to use, or true + def self.mark_pending!(example, message_or_bool) + message = if !message_or_bool || !(String === message_or_bool) + NO_REASON_GIVEN + else + message_or_bool + end + + example.metadata[:pending] = true + example.execution_result.pending_message = message + example.execution_result.pending_fixed = false + end + + # @private + # + # Mark example as fixed. + # + # @param example [RSpec::Core::Example] the example to mark as fixed + def self.mark_fixed!(example) + example.execution_result.pending_fixed = true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/profiler.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/profiler.rb new file mode 100644 index 0000000000..5e6527974e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/profiler.rb @@ -0,0 +1,34 @@ +module RSpec + module Core + # @private + class Profiler + NOTIFICATIONS = [:example_group_started, :example_group_finished, :example_started] + + def initialize + @example_groups = Hash.new { |h, k| h[k] = { :count => 0 } } + end + + attr_reader :example_groups + + def example_group_started(notification) + return unless notification.group.top_level? + + @example_groups[notification.group][:start] = Time.now + @example_groups[notification.group][:description] = notification.group.top_level_description + end + + def example_group_finished(notification) + return unless notification.group.top_level? + + group = @example_groups[notification.group] + return unless group.key?(:start) + group[:total_time] = Time.now - group[:start] + end + + def example_started(notification) + group = notification.example.example_group.parent_groups.last + @example_groups[group][:count] += 1 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer.rb new file mode 100644 index 0000000000..ca707e0367 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer.rb @@ -0,0 +1,48 @@ +RSpec::Support.require_rspec_support "directory_maker" + +module RSpec + module Core + # @private + # Generates conventional files for an RSpec project. + class ProjectInitializer + attr_reader :destination, :stream, :template_path + + DOT_RSPEC_FILE = '.rspec' + SPEC_HELPER_FILE = 'spec/spec_helper.rb' + + def initialize(opts={}) + @destination = opts.fetch(:destination, Dir.getwd) + @stream = opts.fetch(:report_stream, $stdout) + @template_path = opts.fetch(:template_path) do + File.expand_path("../project_initializer", __FILE__) + end + end + + def run + copy_template DOT_RSPEC_FILE + copy_template SPEC_HELPER_FILE + end + + private + + def copy_template(file) + destination_file = File.join(destination, file) + return report_exists(file) if File.exist?(destination_file) + + report_creating(file) + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(destination_file)) + File.open(destination_file, 'w') do |f| + f.write File.read(File.join(template_path, file)) + end + end + + def report_exists(file) + stream.puts " exist #{file}" + end + + def report_creating(file) + stream.puts " create #{file}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/.rspec b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/.rspec new file mode 100644 index 0000000000..c99d2e7396 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/spec/spec_helper.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/spec/spec_helper.rb new file mode 100644 index 0000000000..251aa51060 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/project_initializer/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/rake_task.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/rake_task.rb new file mode 100644 index 0000000000..8cf474a94f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/rake_task.rb @@ -0,0 +1,188 @@ +require 'rake' +require 'rake/tasklib' +require 'rspec/support' + +RSpec::Support.require_rspec_support "ruby_features" + +# :nocov: +unless RSpec::Support.respond_to?(:require_rspec_core) + RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative "../#{f}" } +end +# :nocov: + +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec + module Core + # RSpec rake task + # + # @see Rakefile + class RakeTask < ::Rake::TaskLib + include ::Rake::DSL if defined?(::Rake::DSL) + include RSpec::Core::ShellEscape + + # Default path to the RSpec executable. + DEFAULT_RSPEC_PATH = File.expand_path('../../../../exe/rspec', __FILE__) + + # Default pattern for spec files. + DEFAULT_PATTERN = 'spec/**{,/*/**}/*_spec.rb' + + # Name of task. Defaults to `:spec`. + attr_accessor :name + + # Files matching this pattern will be loaded. + # Defaults to `'spec/**{,/*/**}/*_spec.rb'`. + attr_accessor :pattern + + # Files matching this pattern will be excluded. + # Defaults to `nil`. + attr_accessor :exclude_pattern + + # Whether or not to fail Rake when an error occurs (typically when + # examples fail). Defaults to `true`. + attr_accessor :fail_on_error + + # A message to print to stderr when there are failures. + attr_accessor :failure_message + + if RUBY_VERSION < "1.9.0" || Support::Ruby.jruby? + # Run RSpec with a clean (empty) environment is not supported + def with_clean_environment=(_value) + raise ArgumentError, "Running in a clean environment is not supported on Ruby versions before 1.9.0" + end + + # Run RSpec with a clean (empty) environment is not supported + def with_clean_environment + false + end + else + # Run RSpec with a clean (empty) environment. + attr_accessor :with_clean_environment + end + + # Use verbose output. If this is set to true, the task will print the + # executed spec command to stdout. Defaults to `true`. + attr_accessor :verbose + + # Command line options to pass to ruby. Defaults to `nil`. + attr_accessor :ruby_opts + + # Path to RSpec. Defaults to the absolute path to the + # rspec binary from the loaded rspec-core gem. + attr_accessor :rspec_path + + # Command line options to pass to RSpec. Defaults to `nil`. + attr_accessor :rspec_opts + + def initialize(*args, &task_block) + @name = args.shift || :spec + @ruby_opts = nil + @rspec_opts = nil + @verbose = true + @fail_on_error = true + @rspec_path = DEFAULT_RSPEC_PATH + @pattern = DEFAULT_PATTERN + + define(args, &task_block) + end + + # @private + def run_task(verbose) + command = spec_command + puts command if verbose + + if with_clean_environment + return if system({}, command, :unsetenv_others => true) + else + return if system(command) + end + + puts failure_message if failure_message + + return unless fail_on_error + $stderr.puts "#{command} failed" if verbose + exit $?.exitstatus || 1 + end + + private + + # @private + def define(args, &task_block) + desc "Run RSpec code examples" unless ::Rake.application.last_description + + task name, *args do |_, task_args| + RakeFileUtils.__send__(:verbose, verbose) do + task_block.call(*[self, task_args].slice(0, task_block.arity)) if task_block + run_task verbose + end + end + end + + def file_inclusion_specification + if ENV['SPEC'] + FileList[ENV['SPEC']].sort + elsif String === pattern && !File.exist?(pattern) + return if [*rspec_opts].any? { |opt| opt =~ /--pattern/ } + "--pattern #{escape pattern}" + else + # Before RSpec 3.1, we used `FileList` to get the list of matched + # files, and then pass that along to the `rspec` command. Starting + # with 3.1, we prefer to pass along the pattern as-is to the `rspec` + # command, for 3 reasons: + # + # * It's *much* less verbose to pass one `--pattern` option than a + # long list of files. + # * It ensures `task.pattern` and `--pattern` have the same + # behavior. + # * It fixes a bug, where + # `task.pattern = pattern_that_matches_no_files` would run *all* + # files because it would cause no pattern or file args to get + # passed to `rspec`, which causes all files to get run. + # + # However, `FileList` is *far* more flexible than the `--pattern` + # option. Specifically, it supports individual files and directories, + # as well as arrays of files, directories and globs, as well as other + # `FileList` objects. + # + # For backwards compatibility, we have to fall back to using FileList + # if the user has passed a `pattern` option that will not work with + # `--pattern`. + # + # TODO: consider deprecating support for this and removing it in + # RSpec 4. + FileList[pattern].sort.map { |file| escape file } + end + end + + def file_exclusion_specification + " --exclude-pattern #{escape exclude_pattern}" if exclude_pattern + end + + def spec_command + cmd_parts = [] + cmd_parts << RUBY + cmd_parts << ruby_opts + cmd_parts << rspec_load_path + cmd_parts << escape(rspec_path) + cmd_parts << file_inclusion_specification + cmd_parts << file_exclusion_specification + cmd_parts << rspec_opts + cmd_parts.flatten.reject(&blank).join(" ") + end + + def blank + lambda { |s| s.nil? || s == "" } + end + + def rspec_load_path + @rspec_load_path ||= begin + core_and_support = $LOAD_PATH.grep( + /#{File::SEPARATOR}rspec-(core|support)[^#{File::SEPARATOR}]*#{File::SEPARATOR}lib/ + ).uniq + + "-I#{core_and_support.map { |file| escape file }.join(File::PATH_SEPARATOR)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/reporter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/reporter.rb new file mode 100644 index 0000000000..a016d0f860 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/reporter.rb @@ -0,0 +1,265 @@ +module RSpec::Core + # A reporter will send notifications to listeners, usually formatters for the + # spec suite run. + class Reporter + # @private + RSPEC_NOTIFICATIONS = Set.new( + [ + :close, :deprecation, :deprecation_summary, :dump_failures, :dump_pending, + :dump_profile, :dump_summary, :example_failed, :example_group_finished, + :example_group_started, :example_passed, :example_pending, :example_started, + :message, :seed, :start, :start_dump, :stop, :example_finished + ]) + + def initialize(configuration) + @configuration = configuration + @listeners = Hash.new { |h, k| h[k] = Set.new } + @examples = [] + @failed_examples = [] + @pending_examples = [] + @duration = @start = @load_time = nil + @non_example_exception_count = 0 + @setup_default = lambda {} + @setup = false + @profiler = nil + end + + # @private + attr_reader :examples, :failed_examples, :pending_examples + + # Registers a listener to a list of notifications. The reporter will send + # notification of events to all registered listeners. + # + # @param listener [Object] An obect that wishes to be notified of reporter + # events + # @param notifications [Array] Array of symbols represents the events a + # listener wishes to subscribe too + def register_listener(listener, *notifications) + notifications.each do |notification| + @listeners[notification.to_sym] << listener + end + true + end + + # @private + def prepare_default(loader, output_stream, deprecation_stream) + @setup_default = lambda do + loader.setup_default output_stream, deprecation_stream + end + end + + # @private + def registered_listeners(notification) + @listeners[notification].to_a + end + + # @overload report(count, &block) + # @overload report(count, &block) + # @param expected_example_count [Integer] the number of examples being run + # @yield [Block] block yields itself for further reporting. + # + # Initializes the report run and yields itself for further reporting. The + # block is required, so that the reporter can manage cleaning up after the + # run. + # + # @example + # + # reporter.report(group.examples.size) do |r| + # example_groups.map {|g| g.run(r) } + # end + # + def report(expected_example_count) + start(expected_example_count) + begin + yield self + ensure + finish + end + end + + # @param exit_code [Integer] the exit_code to be return by the reporter + # + # Reports a run that exited early without having run any examples. + # + def exit_early(exit_code) + report(0) { exit_code } + end + + # @private + def start(expected_example_count, time=RSpec::Core::Time.now) + @start = time + @load_time = (@start - @configuration.start_time).to_f + notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?) + notify :start, Notifications::StartNotification.new(expected_example_count, @load_time) + end + + # @param message [#to_s] A message object to send to formatters + # + # Send a custom message to supporting formatters. + def message(message) + notify :message, Notifications::MessageNotification.new(message) + end + + # @param event [Symbol] Name of the custom event to trigger on formatters + # @param options [Hash] Hash of arguments to provide via `CustomNotification` + # + # Publish a custom event to supporting registered formatters. + # @see RSpec::Core::Notifications::CustomNotification + def publish(event, options={}) + if RSPEC_NOTIFICATIONS.include? event + raise "RSpec::Core::Reporter#publish is intended for sending custom " \ + "events not internal RSpec ones, please rename your custom event." + end + notify event, Notifications::CustomNotification.for(options) + end + + # @private + def example_group_started(group) + notify :example_group_started, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty? + end + + # @private + def example_group_finished(group) + notify :example_group_finished, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty? + end + + # @private + def example_started(example) + @examples << example + notify :example_started, Notifications::ExampleNotification.for(example) + end + + # @private + def example_finished(example) + notify :example_finished, Notifications::ExampleNotification.for(example) + end + + # @private + def example_passed(example) + notify :example_passed, Notifications::ExampleNotification.for(example) + end + + # @private + def example_failed(example) + @failed_examples << example + notify :example_failed, Notifications::ExampleNotification.for(example) + end + + # @private + def example_pending(example) + @pending_examples << example + notify :example_pending, Notifications::ExampleNotification.for(example) + end + + # @private + def deprecation(hash) + notify :deprecation, Notifications::DeprecationNotification.from_hash(hash) + end + + # @private + # Provides a way to notify of an exception that is not tied to any + # particular example (such as an exception encountered in a :suite hook). + # Exceptions will be formatted the same way they normally are. + def notify_non_example_exception(exception, context_description) + @configuration.world.non_example_failure = true + @non_example_exception_count += 1 + + example = Example.new(AnonymousExampleGroup, context_description, {}) + presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 0) + message presenter.fully_formatted(nil) + end + + # @private + def finish + close_after do + stop + notify :start_dump, Notifications::NullNotification + notify :dump_pending, Notifications::ExamplesNotification.new(self) + notify :dump_failures, Notifications::ExamplesNotification.new(self) + notify :deprecation_summary, Notifications::NullNotification + unless mute_profile_output? + notify :dump_profile, Notifications::ProfileNotification.new(@duration, @examples, + @configuration.profile_examples, + @profiler.example_groups) + end + notify :dump_summary, Notifications::SummaryNotification.new(@duration, @examples, @failed_examples, + @pending_examples, @load_time, + @non_example_exception_count) + notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?) + end + end + + # @private + def close_after + yield + ensure + close + end + + # @private + def stop + @duration = (RSpec::Core::Time.now - @start).to_f if @start + notify :stop, Notifications::ExamplesNotification.new(self) + end + + # @private + def notify(event, notification) + ensure_listeners_ready + registered_listeners(event).each do |formatter| + formatter.__send__(event, notification) + end + end + + # @private + def abort_with(msg, exit_status) + message(msg) + close + exit!(exit_status) + end + + # @private + def fail_fast_limit_met? + return false unless (fail_fast = @configuration.fail_fast) + + if fail_fast == true + @failed_examples.any? + else + fail_fast <= @failed_examples.size + end + end + + private + + def ensure_listeners_ready + return if @setup + + @setup_default.call + @profiler = Profiler.new + register_listener @profiler, *Profiler::NOTIFICATIONS + @setup = true + end + + def close + notify :close, Notifications::NullNotification + end + + def mute_profile_output? + # Don't print out profiled info if there are failures and `--fail-fast` is + # used, it just clutters the output. + !@configuration.profile_examples? || fail_fast_limit_met? + end + + def seed_used? + @configuration.seed && @configuration.seed_used? + end + end + + # @private + # # Used in place of a {Reporter} for situations where we don't want reporting output. + class NullReporter + def self.method_missing(*) + # ignore + end + private_class_method :method_missing + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ruby_project.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ruby_project.rb new file mode 100644 index 0000000000..156f89bea9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/ruby_project.rb @@ -0,0 +1,53 @@ +# This is borrowed (slightly modified) from Scott Taylor's +# project_path project: +# http://github.com/smtlaissezfaire/project_path +module RSpec + module Core + # @private + module RubyProject + def add_to_load_path(*dirs) + dirs.each { |dir| add_dir_to_load_path(File.join(root, dir)) } + end + + def add_dir_to_load_path(dir) + $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) + end + + def root + @project_root ||= determine_root + end + + def determine_root + find_first_parent_containing('spec') || '.' + end + + def find_first_parent_containing(dir) + ascend_until { |path| File.exist?(File.join(path, dir)) } + end + + def ascend_until + fs = File::SEPARATOR + escaped_slash = "\\#{fs}" + special = "_RSPEC_ESCAPED_SLASH_" + project_path = File.expand_path(".") + parts = project_path.gsub(escaped_slash, special).squeeze(fs).split(fs).map do |x| + x.gsub(special, escaped_slash) + end + + until parts.empty? + path = parts.join(fs) + path = fs if path == "" + return path if yield(path) + parts.pop + end + end + + module_function :add_to_load_path + module_function :add_dir_to_load_path + module_function :root + module_function :determine_root + module_function :find_first_parent_containing + module_function :ascend_until + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb new file mode 100644 index 0000000000..caf9c871af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb @@ -0,0 +1,212 @@ +module RSpec + module Core + # Provides the main entry point to run a suite of RSpec examples. + class Runner + # @attr_reader + # @private + attr_reader :options, :configuration, :world + + # Register an `at_exit` hook that runs the suite when the process exits. + # + # @note This is not generally needed. The `rspec` command takes care + # of running examples for you without involving an `at_exit` + # hook. This is only needed if you are running specs using + # the `ruby` command, and even then, the normal way to invoke + # this is by requiring `rspec/autorun`. + def self.autorun + if autorun_disabled? + RSpec.deprecate("Requiring `rspec/autorun` when running RSpec via the `rspec` command") + return + elsif installed_at_exit? || running_in_drb? + return + end + + at_exit { perform_at_exit } + @installed_at_exit = true + end + + # @private + def self.perform_at_exit + # Don't bother running any specs and just let the program terminate + # if we got here due to an unrescued exception (anything other than + # SystemExit, which is raised when somebody calls Kernel#exit). + return unless $!.nil? || $!.is_a?(SystemExit) + + # We got here because either the end of the program was reached or + # somebody called Kernel#exit. Run the specs and then override any + # existing exit status with RSpec's exit status if any specs failed. + invoke + end + + # Runs the suite of specs and exits the process with an appropriate exit + # code. + def self.invoke + disable_autorun! + status = run(ARGV, $stderr, $stdout).to_i + exit(status) if status != 0 + end + + # Run a suite of RSpec examples. Does not exit. + # + # This is used internally by RSpec to run a suite, but is available + # for use by any other automation tool. + # + # If you want to run this multiple times in the same process, and you + # want files like `spec_helper.rb` to be reloaded, be sure to load `load` + # instead of `require`. + # + # @param args [Array] command-line-supported arguments + # @param err [IO] error stream + # @param out [IO] output stream + # @return [Fixnum] exit status code. 0 if all specs passed, + # or the configured failure exit code (1 by default) if specs + # failed. + def self.run(args, err=$stderr, out=$stdout) + trap_interrupt + options = ConfigurationOptions.new(args) + + if options.options[:runner] + options.options[:runner].call(options, err, out) + else + new(options).run(err, out) + end + end + + def initialize(options, configuration=RSpec.configuration, world=RSpec.world) + @options = options + @configuration = configuration + @world = world + end + + # Configures and runs a spec suite. + # + # @param err [IO] error stream + # @param out [IO] output stream + def run(err, out) + setup(err, out) + return @configuration.reporter.exit_early(exit_code) if RSpec.world.wants_to_quit + + run_specs(@world.ordered_example_groups).tap do + persist_example_statuses + end + end + + # Wires together the various configuration objects and state holders. + # + # @param err [IO] error stream + # @param out [IO] output stream + def setup(err, out) + configure(err, out) + return if RSpec.world.wants_to_quit + + @configuration.load_spec_files + ensure + @world.announce_filters + end + + # Runs the provided example groups. + # + # @param example_groups [Array] groups to run + # @return [Fixnum] exit status code. 0 if all specs passed, + # or the configured failure exit code (1 by default) if specs + # failed. + def run_specs(example_groups) + examples_count = @world.example_count(example_groups) + examples_passed = @configuration.reporter.report(examples_count) do |reporter| + @configuration.with_suite_hooks do + if examples_count == 0 && @configuration.fail_if_no_examples + return @configuration.failure_exit_code + end + + example_groups.map { |g| g.run(reporter) }.all? + end + end + + exit_code(examples_passed) + end + + # @private + def configure(err, out) + @configuration.error_stream = err + @configuration.output_stream = out if @configuration.output_stream == $stdout + @options.configure(@configuration) + end + + # @private + def self.disable_autorun! + @autorun_disabled = true + end + + # @private + def self.autorun_disabled? + @autorun_disabled ||= false + end + + # @private + def self.installed_at_exit? + @installed_at_exit ||= false + end + + # @private + def self.running_in_drb? + return false unless defined?(DRb) + + server = begin + DRb.current_server + rescue DRb::DRbServerNotFound + return false + end + + return false unless server && server.alive? + + require 'socket' + require 'uri' + + local_ipv4 = begin + IPSocket.getaddress(Socket.gethostname) + rescue SocketError + return false + end + + ["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host } + end + + # @private + def self.trap_interrupt + trap('INT') { handle_interrupt } + end + + # @private + def self.handle_interrupt + if RSpec.world.wants_to_quit + exit!(1) + else + RSpec.world.wants_to_quit = true + $stderr.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit." + end + end + + # @private + def exit_code(examples_passed=false) + return @configuration.error_exit_code || @configuration.failure_exit_code if @world.non_example_failure + return @configuration.failure_exit_code unless examples_passed + + 0 + end + + private + + def persist_example_statuses + return if @configuration.dry_run + return unless (path = @configuration.example_status_persistence_file_path) + + ExampleStatusPersister.persist(@world.all_examples, path) + rescue SystemCallError => e + RSpec.warning "Could not write example statuses to #{path} (configured as " \ + "`config.example_status_persistence_file_path`) due to a " \ + "system error: #{e.inspect}. Please check that the config " \ + "option is set to an accessible, valid file path", :call_site => nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/sandbox.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/sandbox.rb new file mode 100644 index 0000000000..e7d518c2e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/sandbox.rb @@ -0,0 +1,37 @@ +module RSpec + module Core + # A sandbox isolates the enclosed code into an environment that looks 'new' + # meaning globally accessed objects are reset for the duration of the + # sandbox. + # + # @note This module is not normally available. You must require + # `rspec/core/sandbox` to load it. + module Sandbox + # Execute a provided block with RSpec global objects (configuration, + # world) reset. This is used to test RSpec with RSpec. + # + # When calling this the configuration is passed into the provided block. + # Use this to set custom configs for your sandboxed examples. + # + # ``` + # Sandbox.sandboxed do |config| + # config.before(:context) { RSpec.current_example = nil } + # end + # ``` + def self.sandboxed + orig_config = RSpec.configuration + orig_world = RSpec.world + orig_example = RSpec.current_example + + RSpec.configuration = RSpec::Core::Configuration.new + RSpec.world = RSpec::Core::World.new(RSpec.configuration) + + yield RSpec.configuration + ensure + RSpec.configuration = orig_config + RSpec.world = orig_world + RSpec.current_example = orig_example + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/set.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/set.rb new file mode 100644 index 0000000000..ae978106c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/set.rb @@ -0,0 +1,54 @@ +module RSpec + module Core + # @private + # + # We use this to replace `::Set` so we can have the advantage of + # constant time key lookups for unique arrays but without the + # potential to pollute a developers environment with an extra + # piece of the stdlib. This helps to prevent false positive + # builds. + # + class Set + include Enumerable + + def initialize(array=[]) + @values = {} + merge(array) + end + + def empty? + @values.empty? + end + + def <<(key) + @values[key] = true + self + end + + def delete(key) + @values.delete(key) + end + + def each(&block) + @values.keys.each(&block) + self + end + + def include?(key) + @values.key?(key) + end + + def merge(values) + values.each do |key| + @values[key] = true + end + self + end + + def clear + @values.clear + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_context.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_context.rb new file mode 100644 index 0000000000..6de7f649d0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_context.rb @@ -0,0 +1,55 @@ +module RSpec + module Core + # Exposes {ExampleGroup}-level methods to a module, so you can include that + # module in an {ExampleGroup}. + # + # @example + # + # module LoggedInAsAdmin + # extend RSpec::Core::SharedContext + # before(:example) do + # log_in_as :admin + # end + # end + # + # describe "admin section" do + # include LoggedInAsAdmin + # # ... + # end + module SharedContext + # @private + def included(group) + __shared_context_recordings.each do |recording| + recording.playback_onto(group) + end + end + + # @private + def __shared_context_recordings + @__shared_context_recordings ||= [] + end + + # @private + Recording = Struct.new(:method_name, :args, :block) do + def playback_onto(group) + group.__send__(method_name, *args, &block) + end + end + + # @private + def self.record(methods) + methods.each do |meth| + define_method(meth) do |*args, &block| + __shared_context_recordings << Recording.new(meth, args, block) + end + end + end + + # @private + record [:describe, :context] + Hooks.instance_methods(false) + + MemoizedHelpers::ClassMethods.instance_methods(false) + end + end + # @private + SharedContext = Core::SharedContext +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_example_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_example_group.rb new file mode 100644 index 0000000000..3d9efce282 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shared_example_group.rb @@ -0,0 +1,271 @@ +RSpec::Support.require_rspec_support "with_keywords_when_needed" + +module RSpec + module Core + # Represents some functionality that is shared with multiple example groups. + # The functionality is defined by the provided block, which is lazily + # eval'd when the `SharedExampleGroupModule` instance is included in an example + # group. + class SharedExampleGroupModule < Module + # @private + attr_reader :definition + + def initialize(description, definition, metadata) + @description = description + @definition = definition + @metadata = metadata + end + + # Provides a human-readable representation of this module. + def inspect + "#<#{self.class.name} #{@description.inspect}>" + end + alias to_s inspect + + # Ruby callback for when a module is included in another module is class. + # Our definition evaluates the shared group block in the context of the + # including example group. + def included(klass) + inclusion_line = klass.metadata[:location] + include_in klass, inclusion_line, [], nil + end + + # @private + def include_in(klass, inclusion_line, args, customization_block) + klass.update_inherited_metadata(@metadata) unless @metadata.empty? + + SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do + RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition) + klass.class_exec(&customization_block) if customization_block + end + end + end + + # Shared example groups let you define common context and/or common + # examples that you wish to use in multiple example groups. + # + # When defined, the shared group block is stored for later evaluation. + # It can later be included in an example group either explicitly + # (using `include_examples`, `include_context` or `it_behaves_like`) + # or implicitly (via matching metadata). + # + # Named shared example groups are scoped based on where they are + # defined. Shared groups defined in an example group are available + # for inclusion in that example group or any child example groups, + # but not in any parent or sibling example groups. Shared example + # groups defined at the top level can be included from any example group. + module SharedExampleGroup + # @overload shared_examples(name, &block) + # @param name [String, Symbol, Module] identifer to use when looking up + # this shared group + # @param block The block to be eval'd + # @overload shared_examples(name, metadata, &block) + # @param name [String, Symbol, Module] identifer to use when looking up + # this shared group + # @param metadata [Array, Hash] metadata to attach to this + # group; any example group or example with matching metadata will + # automatically include this shared example group. + # @param block The block to be eval'd + # + # Stores the block for later use. The block will be evaluated + # in the context of an example group via `include_examples`, + # `include_context`, or `it_behaves_like`. + # + # @example + # shared_examples "auditable" do + # it "stores an audit record on save!" do + # expect { auditable.save! }.to change(Audit, :count).by(1) + # end + # end + # + # RSpec.describe Account do + # it_behaves_like "auditable" do + # let(:auditable) { Account.new } + # end + # end + # + # @see ExampleGroup.it_behaves_like + # @see ExampleGroup.include_examples + # @see ExampleGroup.include_context + def shared_examples(name, *args, &block) + top_level = self == ExampleGroup + if top_level && RSpec::Support.thread_local_data[:in_example_group] + raise "Creating isolated shared examples from within a context is " \ + "not allowed. Remove `RSpec.` prefix or move this to a " \ + "top-level scope." + end + + RSpec.world.shared_example_group_registry.add(self, name, *args, &block) + end + alias shared_context shared_examples + alias shared_examples_for shared_examples + + # @api private + # + # Shared examples top level DSL. + module TopLevelDSL + # @private + def self.definitions + proc do + def shared_examples(name, *args, &block) + RSpec.world.shared_example_group_registry.add(:main, name, *args, &block) + end + alias shared_context shared_examples + alias shared_examples_for shared_examples + end + end + + # @private + def self.exposed_globally? + @exposed_globally ||= false + end + + # @api private + # + # Adds the top level DSL methods to Module and the top level binding. + def self.expose_globally! + return if exposed_globally? + Core::DSL.change_global_dsl(&definitions) + @exposed_globally = true + end + + # @api private + # + # Removes the top level DSL methods to Module and the top level binding. + def self.remove_globally! + return unless exposed_globally? + + Core::DSL.change_global_dsl do + undef shared_examples + undef shared_context + undef shared_examples_for + end + + @exposed_globally = false + end + end + + # @private + class Registry + def add(context, name, *metadata_args, &block) + unless block + RSpec.warning "Shared example group #{name} was defined without a "\ + "block and will have no effect. Please define a "\ + "block or remove the definition." + end + + if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion + return legacy_add(context, name, *metadata_args, &block) + end + + unless valid_name?(name) + raise ArgumentError, "Shared example group names can only be a string, " \ + "symbol or module but got: #{name.inspect}" + end + + ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line } + warn_if_key_taken context, name, block + + metadata = Metadata.build_hash_from(metadata_args) + shared_module = SharedExampleGroupModule.new(name, block, metadata) + shared_example_groups[context][name] = shared_module + end + + def find(lookup_contexts, name) + lookup_contexts.each do |context| + found = shared_example_groups[context][name] + return found if found + end + + shared_example_groups[:main][name] + end + + private + + # TODO: remove this in RSpec 4. This exists only to support + # `config.shared_context_metadata_behavior == :trigger_inclusion`, + # the legacy behavior of shared context metadata, which we do + # not want to support in RSpec 4. + def legacy_add(context, name, *metadata_args, &block) + ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line } + shared_module = SharedExampleGroupModule.new(name, block, {}) + + if valid_name?(name) + warn_if_key_taken context, name, block + shared_example_groups[context][name] = shared_module + else + metadata_args.unshift name + end + + return if metadata_args.empty? + RSpec.configuration.include shared_module, *metadata_args + end + + def shared_example_groups + @shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} } + end + + def valid_name?(candidate) + case candidate + when String, Symbol, Module then true + else false + end + end + + def warn_if_key_taken(context, key, new_block) + existing_module = shared_example_groups[context][key] + return unless existing_module + + old_definition_location = formatted_location existing_module.definition + new_definition_location = formatted_location new_block + loaded_spec_files = RSpec.configuration.loaded_spec_files + + if loaded_spec_files.include?(new_definition_location) && old_definition_location == new_definition_location + RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil + |WARNING: Your shared example group, '#{key}', defined at: + | #{old_definition_location} + |was automatically loaded by RSpec because the file name + |matches the configured autoloading pattern (#{RSpec.configuration.pattern}), + |and is also being required from somewhere else. To fix this + |warning, either rename the file to not match the pattern, or + |do not explicitly require the file. + WARNING + else + RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil + |WARNING: Shared example group '#{key}' has been previously defined at: + | #{old_definition_location} + |...and you are now defining it at: + | #{new_definition_location} + |The new definition will overwrite the original one. + WARNING + end + end + + if RUBY_VERSION.to_f >= 1.9 + def formatted_location(block) + block.source_location.join(":") + end + else # 1.8.7 + # :nocov: + def formatted_location(block) + block.source_location.join(":").gsub(/:in.*$/, '') + end + # :nocov: + end + + if Proc.method_defined?(:source_location) + def ensure_block_has_source_location(_block); end + else # for 1.8.7 + # :nocov: + def ensure_block_has_source_location(block) + source_location = yield.split(':') + block.extend(Module.new { define_method(:source_location) { source_location } }) + end + # :nocov: + end + end + end + end + + instance_exec(&Core::SharedExampleGroup::TopLevelDSL.definitions) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shell_escape.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shell_escape.rb new file mode 100644 index 0000000000..a92feae800 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/shell_escape.rb @@ -0,0 +1,49 @@ +module RSpec + module Core + # @private + # Deals with the fact that `shellwords` only works on POSIX systems. + module ShellEscape + module_function + + def quote(argument) + "'#{argument.to_s.gsub("'", "\\\\'")}'" + end + + if RSpec::Support::OS.windows? + # :nocov: + alias escape quote + # :nocov: + else + require 'shellwords' + + def escape(shell_command) + Shellwords.escape(shell_command.to_s) + end + end + + # Known shells that require quoting: zsh, csh, tcsh. + # + # Feel free to add other shells to this list that are known to + # allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id. + # + # @private + SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ] + + def conditionally_quote(id) + return id if shell_allows_unquoted_ids? + quote(id) + end + + def shell_allows_unquoted_ids? + # Note: ENV['SHELL'] isn't necessarily the shell the user is currently running. + # According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html: + # "This variable shall represent a pathname of the user's preferred command language interpreter." + # + # It's the best we can easily do, though. We err on the side of safety (quoting + # the id when not actually needed) so it's not a big deal if the user is actually + # using a different shell. + SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/test_unit_assertions_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/test_unit_assertions_adapter.rb new file mode 100644 index 0000000000..d84ecb1441 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/test_unit_assertions_adapter.rb @@ -0,0 +1,30 @@ +require 'test/unit/assertions' + +module RSpec + module Core + # @private + module TestUnitAssertionsAdapter + include ::Test::Unit::Assertions + + # If using test/unit from Ruby core with Ruby 1.9+, it includes + # MiniTest::Assertions by default. Note the upcasing of 'Test'. + # + # If the test/unit gem is being loaded, it will not include any minitest + # assertions. + # + # Only if Minitest 5.x is included / loaded do we need to worry about + # adding a shim for the new updates. Thus instead of checking on the + # RUBY_VERSION we need to check ancestors. + begin + # MiniTest is 4.x. + # Minitest is 5.x. + if ancestors.include?(::Minitest::Assertions) + require 'rspec/core/minitest_assertions_adapter' + include ::RSpec::Core::MinitestAssertionsAdapter + end + rescue NameError + # No-op. Minitest 5.x was not loaded. + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/version.rb new file mode 100644 index 0000000000..bef31f7133 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/version.rb @@ -0,0 +1,9 @@ +module RSpec + module Core + # Version information for RSpec Core. + module Version + # Current version of RSpec Core, in semantic versioning format. + STRING = '3.10.1' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/warnings.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/warnings.rb new file mode 100644 index 0000000000..b8800591bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/warnings.rb @@ -0,0 +1,40 @@ +require "rspec/support/warnings" + +module RSpec + module Core + # @private + module Warnings + # @private + # + # Used internally to print deprecation warnings. + def deprecate(deprecated, data={}) + RSpec.configuration.reporter.deprecation( + { + :deprecated => deprecated, + :call_site => CallerFilter.first_non_rspec_line + }.merge(data) + ) + end + + # @private + # + # Used internally to print deprecation warnings. + def warn_deprecation(message, opts={}) + RSpec.configuration.reporter.deprecation opts.merge(:message => message) + end + + # @private + def warn_with(message, options={}) + if options[:use_spec_location_as_call_site] + message += "." unless message.end_with?(".") + + if RSpec.current_example + message += " Warning generated from spec at `#{RSpec.current_example.location}`." + end + end + + super(message, options) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/world.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/world.rb new file mode 100644 index 0000000000..12909cab01 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.10.1/lib/rspec/core/world.rb @@ -0,0 +1,277 @@ +module RSpec + module Core + # @api private + # + # Internal container for global non-configuration data. + class World + # @private + attr_reader :example_groups, :filtered_examples, :example_group_counts_by_spec_file + + # Used internally to determine what to do when a SIGINT is received. + attr_accessor :wants_to_quit + + # Used internally to signal that a failure outside of an example + # has occurred, and that therefore the exit status should indicate + # the run failed. + # @private + attr_accessor :non_example_failure + + def initialize(configuration=RSpec.configuration) + @wants_to_quit = false + @configuration = configuration + configuration.world = self + @example_groups = [] + @example_group_counts_by_spec_file = Hash.new(0) + prepare_example_filtering + end + + # @api public + # + # Prepares filters so that they apply to example groups when they run. + # + # This is a separate method so that filters can be modified/replaced and + # examples refiltered during a process's lifetime, which can be useful for + # a custom runner. + def prepare_example_filtering + @filtered_examples = Hash.new do |hash, group| + hash[group] = filter_manager.prune(group.examples) + end + end + + # @api private + # + # Apply ordering strategy from configuration to example groups. + def ordered_example_groups + ordering_strategy = @configuration.ordering_registry.fetch(:global) + ordering_strategy.order(@example_groups) + end + + # @api private + # + # Reset world to 'scratch' before running suite. + def reset + RSpec::ExampleGroups.remove_all_constants + example_groups.clear + @sources_by_path.clear if defined?(@sources_by_path) + @syntax_highlighter = nil + @example_group_counts_by_spec_file = Hash.new(0) + end + + # @private + def filter_manager + @configuration.filter_manager + end + + # @private + def registered_example_group_files + @example_group_counts_by_spec_file.keys + end + + # @api private + # + # Records an example group. + def record(example_group) + @configuration.on_example_group_definition_callbacks.each { |block| block.call(example_group) } + @example_group_counts_by_spec_file[example_group.metadata[:absolute_file_path]] += 1 + end + + # @private + def num_example_groups_defined_in(file) + @example_group_counts_by_spec_file[file] + end + + # @private + def shared_example_group_registry + @shared_example_group_registry ||= SharedExampleGroup::Registry.new + end + + # @private + def inclusion_filter + @configuration.inclusion_filter + end + + # @private + def exclusion_filter + @configuration.exclusion_filter + end + + # @api private + # + # Get count of examples to be run. + def example_count(groups=example_groups) + FlatMap.flat_map(groups) { |g| g.descendants }. + inject(0) { |a, e| a + e.filtered_examples.size } + end + + # @private + def all_example_groups + FlatMap.flat_map(example_groups) { |g| g.descendants } + end + + # @private + def all_examples + FlatMap.flat_map(all_example_groups) { |g| g.examples } + end + + # @private + # Traverses the tree of each top level group. + # For each it yields the group, then the children, recursively. + # Halts the traversal of a branch of the tree as soon as the passed block returns true. + # Note that siblings groups and their sub-trees will continue to be explored. + # This is intended to make it easy to find the top-most group that satisfies some + # condition. + def traverse_example_group_trees_until(&block) + example_groups.each do |group| + group.traverse_tree_until(&block) + end + end + + # @api private + # + # Find line number of previous declaration. + def preceding_declaration_line(absolute_file_name, filter_line) + line_numbers = descending_declaration_line_numbers_by_file.fetch(absolute_file_name) do + return nil + end + + line_numbers.find { |num| num <= filter_line } + end + + # @private + def reporter + @configuration.reporter + end + + # @private + def source_from_file(path) + unless defined?(@sources_by_path) + RSpec::Support.require_rspec_support 'source' + @sources_by_path = {} + end + + @sources_by_path[path] ||= Support::Source.from_file(path) + end + + # @private + def syntax_highlighter + @syntax_highlighter ||= Formatters::SyntaxHighlighter.new(@configuration) + end + + # @api private + # + # Notify reporter of filters. + def announce_filters + fail_if_config_and_cli_options_invalid + filter_announcements = [] + + announce_inclusion_filter filter_announcements + announce_exclusion_filter filter_announcements + + unless filter_manager.empty? + if filter_announcements.length == 1 + report_filter_message("Run options: #{filter_announcements[0]}") + else + report_filter_message("Run options:\n #{filter_announcements.join("\n ")}") + end + end + + if @configuration.run_all_when_everything_filtered? && example_count.zero? && !@configuration.only_failures? + report_filter_message("#{everything_filtered_message}; ignoring #{inclusion_filter.description}") + filtered_examples.clear + inclusion_filter.clear + end + + return unless example_count.zero? + + example_groups.clear + if filter_manager.empty? + report_filter_message("No examples found.") + elsif exclusion_filter.empty? || inclusion_filter.empty? + report_filter_message(everything_filtered_message) + end + end + + # @private + def report_filter_message(message) + reporter.message(message) unless @configuration.silence_filter_announcements? + end + + # @private + def everything_filtered_message + "\nAll examples were filtered out" + end + + # @api private + # + # Add inclusion filters to announcement message. + def announce_inclusion_filter(announcements) + return if inclusion_filter.empty? + + announcements << "include #{inclusion_filter.description}" + end + + # @api private + # + # Add exclusion filters to announcement message. + def announce_exclusion_filter(announcements) + return if exclusion_filter.empty? + + announcements << "exclude #{exclusion_filter.description}" + end + + private + + def descending_declaration_line_numbers_by_file + @descending_declaration_line_numbers_by_file ||= begin + declaration_locations = FlatMap.flat_map(example_groups, &:declaration_locations) + hash_of_arrays = Hash.new { |h, k| h[k] = [] } + + # TODO: change `inject` to `each_with_object` when we drop 1.8.7 support. + line_nums_by_file = declaration_locations.inject(hash_of_arrays) do |hash, (file_name, line_number)| + hash[file_name] << line_number + hash + end + + line_nums_by_file.each_value do |list| + list.sort! + list.reverse! + end + end + end + + def fail_if_config_and_cli_options_invalid + return unless @configuration.only_failures_but_not_configured? + + reporter.abort_with( + "\nTo use `--only-failures`, you must first set " \ + "`config.example_status_persistence_file_path`.", + 1 # exit code + ) + end + + # @private + # Provides a null implementation for initial use by configuration. + module Null + def self.non_example_failure; end + def self.non_example_failure=(_); end + + def self.registered_example_group_files + [] + end + + def self.traverse_example_group_trees_until + end + + # :nocov: + def self.example_groups + [] + end + + def self.all_example_groups + [] + end + # :nocov: + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.yardopts new file mode 100644 index 0000000000..0e4432664e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/.yardopts @@ -0,0 +1,8 @@ +--exclude features +--no-private +--markup markdown +--default-return void +- +Filtering.md +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/Changelog.md new file mode 100644 index 0000000000..5bb17fb30a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/Changelog.md @@ -0,0 +1,2368 @@ +### Development +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.12.1...3-12-maintenance) + +### 3.12.1 / 2023-02-03 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.12.0...v3.12.1) + +Bug fixes: + +* Prevent multiple calls to `extra_failure_lines` from adding additional whitespace + around them when the lines already contain whitespace. (Jon Rowe, #3006) + +### 3.12.0 / 2022-10-26 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.11.0...v3.12.0) + +* No changes, released to support other gems. + +### 3.11.0 / 2022-02-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.10.2...v3.11.0) + +Enhancements: + +* Improve pluralisation of words ending with `s` (like process). (Joshua Pinter, #2779) +* Add ordering by file modification time (most recent first). (Matheus Richard, #2778) +* Add `to_s` to reserved names for #let and #subject. (Nick Flückiger, #2886) +* Introduce `RSpec.current_scope` to expose the current scope in which + RSpec is executing. e.g. `:before_example_hook`, `:example` etc. (@odinhb, #2895) +* Add named bold colours as options for custom colours. (#2913, #2914) +* Warn when (but not prevent) a `SystemExit` occurs. (Jared Beck, #2926) + +### 3.10.2 / 2022-01-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.10.1...v3.10.2) + +Bug fixes: + +* Ensure bisect communication uses consistent encoding. (Mike Jarema, #2852) +* Fix exception presenter when the root cause exception has nil backtrace. + (Zinovyev Ivan, #2903) +* Fix `inspect` output of `RSpec::Core::Example::Procsy` to namespace correctly. + (Keiko Kaneko, #2915) +* Ensure formatters not exposing `#output` will not crash duplicate check. + (@niceking, #2916) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.10.0...v3.10.1) + +Bug fixes: + +* RSpec warning output was missing deprecations from Ruby, these are now included. + (Jon Rowe, #2811) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.3...v3.10.0) + +Enhancements: + +* Memoize `RSpec::Core::Formatters::ExceptionPresenter#exception_lines` to improve performance + with slow exception messages. (Maxime Lapointe, #2743) +* Add configuration for an error exit code (to disambiguate errored builds from failed builds + by exit status). (Dana Sherson, #2749) + +### 3.9.3 / 2020-09-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.2...v3.9.3) + +Bug Fixes: + +* Declare `ruby2_keywords` on `method_missing` for other gems. (Jon Rowe, #2731) +* Ensure custom error codes are returned from bisect runs. (Jon Rowe, #2732) +* Ensure `RSpec::Core::Configuration` predicate config methods return booleans. + (Marc-André Lafortune, #2736) +* Prevent `rspec --bisect` from generating zombie processes while executing + bisect runs. (Benoit Tigeot, Jon Rowe, #2739) +* Predicates for pending examples, (in `RSpec::Core::Example`, `#pending?`, `#skipped?` and + `#pending_fixed?`) now return boolean values rather than truthy values. + (Marc-André Lafortune, #2756, #2758) +* Exceptions which have a message which cannot be cast to a string will no longer + cause a crash. (Jon Rowe, #2761) + +### 3.9.2 / 2020-05-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Emit a warning when `around` hook is used with `:context` scope + (Phil Pirozhkov, #2687) +* Prevent invalid implementations of `Exception#cause` from being treated as a + valid cause (and causing strange errors) in `RSpec::Core::Formatters::ExceptionPresenter`. + (Jon Rowe, #2703) +* Correctly detect patterns when `rspec_opts` is an array in `RSpec::Core::RakeTask`. + (Marc-André Lafortune, #2704) +* Make `RSpec.clear_examples` reset example counts for example groups. This fixes + an issue with re-running specs not matching ids. (Agis Anastasopoulos, #2723) + +### 3.9.1 / 2019-12-28 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Prevent bisect command from blocking when number of specs exceeds file + descriptor limit on OSX or Linux. (Benoit Tigeot, #2669) +* Prevent warnings being issued on Ruby 2.7.0. (Jon Rowe, #2680) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.2...v3.9.0) + +Enhancements: + +* Improve the handling of errors during loading support files, if a file + errors before loading specs, RSpec will now skip loading the specs. + (David Rodríguez, #2568) +* Add support for --example-matches to run examples by regular expression. + (Sam Joseph, Matt Rider, @okothkongo1, #2586) +* Add `did_you_mean` suggestions for file names encountering a `LoadError` + outside of examples. (@obromios, #2601) +* Add a minimalist quick fix style formatter, only outputs failures as + `file:line:message`. (Romain Tartière, #2614) +* Convert string number values to integer when used for `RSpec::Configuration#fail_fast` + (Viktor Fonic, #2634) +* Issue warning when invalid values are used for `RSpec::Configuration#fail_fast` + (Viktor Fonic, #2634) +* Add support for running the Rake task in a clean environment. + (Jon Rowe, #2632) +* Indent messages by there example group / example in the documentation formatter. + (Samuel Williams, #2649) + +### 3.8.2 / 2019-06-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Fix `config.define_derived_metadata` so that cascades are not triggered + until metadata has been assigned to the example or example group + (Myron Marston, #2635). + +### 3.8.1 / 2019-06-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Handle RSpec description(s) with japanese chars in CP932 encoded files. + (Benoit Tigeot, #2575) +* When defining `let` methods that overwrite an existing method, prevent + a warning being issued by removing the old definition. (Jon Rowe, #2593) +* Prevent warning on Ruby 2.6.0-rc1 (Keiji Yoshimi, #2582) +* Fix `config.define_derived_metadata` so that it supports cascades. + (Myron Marston, #2630). + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.7.1...v3.8.0) + +Enhancements: + +* Improve shell escaping used by `RSpec::Core::RakeTask` and `--bisect` so + that it works on `Pathname` objects. (Andrew Vit, #2479) +* Nicely format errors encountered while loading files specified + by `--require` option. (Myron Marston, #2504) +* Significantly improve the performance of `--bisect` on platforms that + support forking by replacing the shell-based runner with one that uses + forking so that RSpec and the application environment can be booted only + once, instead of once per spec run. (Myron Marston, #2511) +* Provide a configuration API to pick which bisect runner is used for + `--bisect`. Pick a runner via `config.bisect_runner = :shell` or + `config.bisect_runner = :fork` in a file loaded by a `--require` + option passed at the command line or set in `.rspec`. (Myron Marston, #2511) +* Support the [XDG Base Directory + Specification](https://specifications.freedesktop.org/basedir-spec/latest/) + for the global options file. `~/.rspec` is still supported when no + options file is found in `$XDG_CONFIG_HOME/rspec/options` (Magnus Bergmark, #2538) +* Extract `RSpec.world.prepare_example_filtering` that sets up the + example filtering for custom RSpec runners. (Oleg Pudeyev, #2552) + +Bug Fixes: + +* Prevent an `ArgumentError` when truncating backtraces with two identical + backtraces. (Systho, #2515, Benoit Tigeot, #2539) + +### 3.7.1 / 2018-01-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.7.0...v3.7.1) + +Bug Fixes: + +* Work around duplicate config hook regression introduced + by Ruby 2.5's lazy proc allocation. (Myron Marston, #2497) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Add `-n` alias for `--next-failure`. (Ian Ker-Seymer, #2434) +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #2425, #2427, #2437) +* Do not run `:context` hooks for example groups that have been skipped. + (Devon Estes, #2442) +* Add `errors_outside_of_examples_count` to the JSON formatter. + (Takeshi Arabiki, #2448) + +Bug Fixes: + +* Improve compatibility with frozen string literal flag. (#2425, Pat Allan) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0.beta2...v3.6.0) + +Enhancements: + +* Add seed information to JSON formatter output. (#2388, Mitsutaka Mimura) +* Include example id in the JSON formatter output. (#2369, Xavier Shay) +* Respect changes to `config.output_stream` after formatters have been + setup. (#2401, #2419, Ilya Lavrov) + +Bug Fixes: + +* Delay formatter loading until the last minute to allow accessing the reporter + without triggering formatter setup. (Jon Rowe, #2243) +* Ensure context hook failures running before an example can access the + reporter. (Jon Jensen, #2387) +* Multiple fixes to allow using the runner multiple times within the same + process: `RSpec.clear_examples` resets the formatter and no longer clears + shared examples, and streams can be used across multiple runs rather than + being closed after the first. (#2368, Xavier Shay) +* Prevent unexpected `example_group_finished` notifications causing an error. + (#2396, VTJamie) +* Fix bugs where `config.when_first_matching_example_defined` hooks would fire + multiple times in some cases. (Yuji Nakayama, #2400) +* Default `last_run_status` to "unknown" when the `status` field in the + persistence file contains an unrecognized value. (#2360, matrinox) +* Prevent `let` from defining an `initialize` method. (#2414, Jon Rowe) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.6.0.beta1...v3.6.0.beta2) + +Enhancements: + +* Include count of errors occurring outside examples in default summaries. + (#2351, Jon Rowe) +* Warn when including shared example groups recursively. (#2356, Jon Rowe) +* Improve failure snippet syntax highlighting with CodeRay to highlight + RSpec "keywords" like `expect`. (#2358, Myron Marston) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.4...v3.6.0.beta1) + +Enhancements: + +* Warn when duplicate shared examples definitions are loaded due to being + defined in files matching the spec pattern (e.g. `_spec.rb`) (#2278, Devon Estes) +* Improve metadata filtering so that it can match against any object + that implements `===` instead of treating regular expressions as + special. (Myron Marston, #2294) +* Improve `rspec -v` so that it prints out the versions of each part of + RSpec to prevent confusion. (Myron Marston, #2304) +* Add `config.fail_if_no_examples` option which causes RSpec to fail if + no examples are found. (Ewa Czechowska, #2302) +* Nicely format errors encountered while loading spec files. + (Myron Marston, #2323) +* Improve the API for enabling and disabling color output (Josh + Justice, #2321): + * Automatically enable color if the output is a TTY, since color is + nearly always desirable if the output can handle it. + * Introduce new CLI flag to force color on (`--force-color`), even + if the output is not a TTY. `--no-color` continues to work as well. + * Introduce `config.color_mode` for configuring the color from Ruby. + `:automatic` is the default and will produce color if the output is + a TTY. `:on` forces it on and `:off` forces it off. + +### 3.5.4 / 2016-09-30 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.3...v3.5.4) + +Bug Fixes: + +* Remove accumulated `ExampleGroup` constants when reseting RSpec, + preventing a memory leak. (TravisSpangle, #2328) + +### 3.5.3 / 2016-09-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.2...v3.5.3) + +Bug Fixes: + +* When applying shared group metadata to a host group, overwrite + conflicting keys if the value in the host group was inherited from + a parent group instead of being specified at that level. + (Myron Marston, #2307) +* Handle errors in `:suite` hooks and provide the same nicely formatted + output as errors that happen in examples. (Myron Marston, #2316) +* Set the exit status to non-zero when an error occurs in an + `after(:context)` hook. (Myron Marston, #2320) + +### 3.5.2 / 2016-07-28 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.1...v3.5.2) + +Bug Fixes: + +* Wait to report `example_finished` until the example's `execution_result` + has been completely filled in. (Myron Marston, #2291) +* Make sure example block is still available when using `duplicate_with` + to clone examples. (bootstraponline, #2298) +* Don't include the default `--pattern` in the Rake task when + `rspec_opts` specifies its own. (Jon Rowe, #2305) + +### 3.5.1 / 2016-07-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0...v3.5.1) + +Bug Fixes: + +* Ensure that config hooks that are added to existing example groups are + added only once. (Eugene Kenny, #2280) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Include any `SPEC_OPTS` in reproduction command printed at the end of + a bisect run. (Simon Coffey, #2274) + +Bug Fixes: + +* Handle `--bisect` in `SPEC_OPTS` environment variable correctly so as + to avoid infinite recursion. (Simon Coffey, #2271) + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: + +* Filter out bundler stackframes from backtraces by default, since + Bundler 1.12 now includes its own frames in stack traces produced + by using `bundle exec`. (Myron Marston, #2240) +* HTML Formatter uses exception presenter to get failure message + for consistency with other formatters. (@mrageh, #2222) +* Load spec files in the order of the directories or files passed + at the command line, making it easy to make some specs run before + others in a one-off manner. For example, `rspec spec/unit + spec/acceptance --order defined` will run unit specs before acceptance + specs. (Myron Marston, #2253) +* Add new `config.include_context` API for configuring global or + filtered inclusion of shared contexts in example groups. + (Myron Marston, #2256) +* Add new `config.shared_context_metadata_behavior = :apply_to_host_groups` + option, which causes shared context metadata to be inherited by the + metadata hash of all host groups and examples instead of configuring + implicit auto-inclusion based on the passed metadata. (Myron Marston, #2256) + +Bug Fixes: + +* Fix `--bisect` so it works on large spec suites that were previously triggering + "Argument list too long errors" due to all the spec locations being passed as + CLI args. (Matt Jones, #2223). +* Fix deprecated `:example_group`-based filtering so that it properly + applies to matching example groups. (Myron Marston, #2234) +* Fix `NoMethodError` caused by Java backtraces on JRuby. (Michele Piccirillo, #2244) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Add new `config.filter_run_when_matching` API, intended to replace + the combination of `config.filter_run` and + `config.run_all_when_everything_filtered` (Myron Marston, #2206) + +Bug Fixes: + +* Use the encoded string logic for source extraction. (Jon Rowe, #2183) +* Fix rounding issue in duration formatting helper. (Fabersky, Jon Rowe, #2208) +* Fix failure snippet extraction so that `def-end` snippets + ending with `end`-only line can be extracted properly. + (Yuji Nakayama, #2215) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Remove unneeded `:execution_result` example group metadata, saving a + bit of memory. (Myron Marston, #2172) +* Apply hooks registered with `config` to previously defined groups. + (Myron Marston, #2189) +* `RSpec::Core::Configuration#reporter` is now public API under SemVer. + (Jon Rowe, #2193) +* Add new `config.when_first_matching_example_defined` hook. (Myron + Marston, #2175) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.4...v3.5.0.beta1) + +Enhancements: + +* Add `RSpec::Core::ExampleGroup.currently_executing_a_context_hook?`, + primarily for use by rspec-rails. (Sam Phippen, #2131) + +Bug Fixes: + +* Ensure `MultipleExceptionError` does not contain a recursive reference + to itself. (Sam Phippen, #2133) + +### 3.4.4 / 2016-03-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.3...v3.4.4) + +Bug Fixes: + +* Fix `RSpec::Core::RakeTask` so that it works with Rake 11. + (Travis Grathwell, #2197) + +### 3.4.3 / 2016-02-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.2...v3.4.3) + +Bug Fixes: + +* Prevent a `TypeError` from occurring when running via the rake task when + Ruby crashes. (Patrik Wenger, #2161) +* Only consider example and group declaration lines from a specific file + when applying line number filtering, instead of considering all + declaration lines from all spec files. (Myron Marston, #2170) +* Fix failure snippet extraction so that snippets that contain `do-end` style + block and end with `end`-only line can be extracted properly. + (Yuji Nakayama, #2173) +* Prevent infinite recursion when an exception is caused by itself. + (Jon Rowe, #2128) + +### 3.4.2 / 2016-01-26 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.1...v3.4.2) + +Bug Fixes: + +* Fix `rspec --profile` when an example calls `abort` or `exit`. + (Bradley Schaefer, #2144) +* Fix `--drb` so that when no DRb server is running, it prevents + the DRb connection error from being listed as the cause of all + expectation failures. (Myron Marston, #2156) +* Fix syntax highlighter so that it works when the `coderay` gem is + installed as a rubygem but not already available on your load path + (as happens when you use bundler). (Myron Marston, #2159) + +### 3.4.1 / 2015-11-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix backtrace formatter to handle backtraces that are `nil`. + (Myron Marston, #2118) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.2...v3.4.0) + +Enhancements: + +* Combine multiple `--pattern` arguments making them equivalent to + `--pattern=1,2,...,n`. (Jon Rowe, #2002) +* Improve `inspect` and `to_s` output for `RSpec::Core::Example` + objects, replacing Ruby's excessively verbose output. (Gavin Miller, #1922) +* Add `silence_filter_announcements` configuration option. + (David Raffensperger, #2007) +* Add optional `example_finished` notification to the reporter protocol for + when you don't care about the example outcome. (Jon Rowe, #2013) +* Switch `--bisect` to a recursion-based bisection algorithm rather than + a permutation-based one. This better handles cases where an example + depends upon multiple other examples instead of just one and minimizes + the number of runs necessary to determine that an example set cannot be + minimized further. (Simon Coffey, #1997) +* Allow simple filters (e.g. `:symbol` key only) to be triggered by truthey + values. (Tim Mertens, #2035) +* Remove unneeded warning about need for `ansicon` on Windows when using + RSpec's `--color` option. (Ashley Engelund, #2038) +* Add option to configure RSpec to raise errors when issuing warnings. + (Jon Rowe, #2052) +* Append the root `cause` of a failure or error to the printed failure + output when a `cause` is available. (Adam Magan) +* Stop rescuing `NoMemoryError`, `SignalExcepetion`, `Interrupt` and + `SystemExit`. It is dangerous to interfere with these. (Myron Marston, #2063) +* Add `config.project_source_dirs` setting which RSpec uses to determine + if a backtrace line comes from your project source or from some + external library. It defaults to `spec`, `lib` and `app` but can be + configured differently. (Myron Marston, #2088) +* Improve failure line detection so that it looks for the failure line + in any project source directory instead of just in the spec file. + In addition, if no backtrace lines can be found from a project source + file, we fall back to displaying the source of the first backtrace + line. This should virtually eliminate the "Unable to find matching + line from backtrace" messages. (Myron Marston, #2088) +* Add support for `:extra_failure_lines` example metadata that will + be appended to the failure output. (bootstraponline, #2092). +* Add `RSpec::Core::Example#duplicate_with` to produce new examples + with cloned metadata. (bootstraponline, #2098) +* Add `RSpec::Core::Configuration#on_example_group_definition` to register + hooks to be invoked when example groups are created. (bootstraponline, #2094) +* Add `add_example` and `remove_example` to `RSpec::Core::ExampleGroup` to + allow manipulating an example groups examples. (bootstraponline, #2095) +* Display multiline failure source lines in failure output when Ripper is + available (MRI >= 1.9.2, and JRuby >= 1.7.5 && < 9.0.0.0.rc1). + (Yuji Nakayama, #2083) +* Add `max_displayed_failure_line_count` configuration option + (defaults to 10). (Yuji Nakayama, #2083) +* Enhance `fail_fast` option so it can take a number (e.g. `--fail-fast=3`) + to force the run to abort after the specified number of failures. + (Jack Scotti, #2065) +* Syntax highlight the failure snippets in text formatters when `color` + is enabled and the `coderay` gem is installed on a POSIX system. + (Myron Marston, #2109) + +Bug Fixes: + +* Lock `example_status_persistence_file` when reading from and writing + to it to prevent race conditions when multiple processes try to use + it. (Ben Woosley, #2029) +* Fix regression in 3.3 that caused spec file names with square brackets in + them (such as `1[]_spec.rb`) to not be loaded properly. (Myron Marston, #2041) +* Fix output encoding issue caused by ASCII literal on 1.9.3 (Jon Rowe, #2072) +* Fix requires in `rspec/core/rake_task.rb` to avoid double requires + seen by some users. (Myron Marston, #2101) + +### 3.3.2 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.1...v3.3.2) + +Bug Fixes: + +* Fix formatters to handle exceptions for which `backtrace` returns `nil`. + (Myron Marston, #2023) +* Fix duplicate formatter detection so that it allows subclasses of formatters + to be added. (Sebastián Tello, #2019) + +### 3.3.1 / 2015-06-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Correctly run `before(:suite)` (and friends) in the context of an example + group instance, thus making the expected RSpec environment available. + (Jon Rowe, #1986) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.3...v3.3.0) + +Enhancements: + +* Expose the reporter used to run examples via `RSpec::Core::Example#reporter`. + (Jon Rowe, #1866) +* Make `RSpec::Core::Reporter#message` a public supported API. (Jon Rowe, #1866) +* Allow custom formatter events to be published via + `RSpec::Core::Reporter#publish(event_name, hash_of_attributes)`. (Jon Rowe, #1869) +* Remove dependency on the standard library `Set` and replace with `RSpec::Core::Set`. + (Jon Rowe, #1870) +* Assign a unique id to each example and group so that they can be + uniquely identified, even for shared examples (and similar situations) + where the location isn't unique. (Myron Marston, #1884) +* Use the example id in the rerun command printed for failed examples + when the location is not unique. (Myron Marston, #1884) +* Add `config.example_status_persistence_file_path` option, which is + used to persist the last run status of each example. (Myron Marston, #1888) +* Add `:last_run_status` metadata to each example, which indicates what + happened the last time an example ran. (Myron Marston, #1888) +* Add `--only-failures` CLI option which filters to only the examples + that failed the last time they ran. (Myron Marston, #1888) +* Add `--next-failure` CLI option which allows you to repeatedly focus + on just one of the currently failing examples, then move on to the + next failure, etc. (Myron Marston, #1888) +* Make `--order random` ordering stable, so that when you rerun a + subset with a given seed, the examples will be order consistently + relative to each other. (Myron Marston, #1908) +* Set example group constant earlier so errors when evaluating the context + include the example group name (Myron Marson, #1911) +* Make `let` and `subject` threadsafe. (Josh Cheek, #1858) +* Add version information into the JSON formatter. (Mark Swinson, #1883) +* Add `--bisect` CLI option, which will repeatedly run your suite in + order to isolate the failures to the smallest reproducible case. + (Myron Marston, #1917) +* For `config.include`, `config.extend` and `config.prepend`, apply the + module to previously defined matching example groups. (Eugene Kenny, #1935) +* When invalid options are parsed, notify users where they came from + (e.g. `.rspec` or `~/.rspec` or `ENV['SPEC_OPTS']`) so they can + easily find the source of the problem. (Myron Marston, #1940) +* Add pending message contents to the json formatter output. (Jon Rowe, #1949) +* Add shared group backtrace to the output displayed by the built-in + formatters for pending examples that have been fixed. (Myron Marston, #1946) +* Add support for `:aggregate_failures` metadata. Tag an example or + group with this metadata and it'll use rspec-expectations' + `aggregate_failures` feature to allow multiple failures in an example + and list them all, rather than aborting on the first failure. (Myron + Marston, #1946) +* When no formatter implements #message add a fallback to prevent those + messages being lost. (Jon Rowe, #1980) +* Profiling examples now takes into account time spent in `before(:context)` + hooks. (Denis Laliberté, Jon Rowe, #1971) +* Improve failure output when an example has multiple exceptions, such + as one from an `it` block and one from an `after` block. (Myron Marston, #1985) + +Bug Fixes: + +* Handle invalid UTF-8 strings within exception methods. (Benjamin Fleischer, #1760) +* Fix Rake Task quoting of file names with quotes to work properly on + Windows. (Myron Marston, #1887) +* Fix `RSpec::Core::RakeTask#failure_message` so that it gets printed + when the task failed. (Myron Marston, #1905) +* Make `let` work properly when defined in a shared context that is applied + to an individual example via metadata. (Myron Marston, #1912) +* Ensure `rspec/autorun` respects configuration defaults. (Jon Rowe, #1933) +* Prevent modules overriding example group defined methods when included, + prepended or extended by config defined after an example group. (Eugene Kenny, #1935) +* Fix regression which caused shared examples to be mistakenly run when specs + where filtered to a particular location. (Ben Axnick, #1963) +* Fix time formatting logic so that it displays 70 seconds as "1 minute, + 10 seconds" rather than "1 minute, 1 second". (Paul Brennan, #1984) +* Fix regression where the formatter loader would allow duplicate formatters. + (Jon Rowe, #1990) + +### 3.2.3 / 2015-04-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.2...v3.2.3) + +Bug Fixes: + +* Fix how the DSL methods are defined so that RSpec is compatible with + gems that define methods of the same name on `Kernel` (such as + the `its-it` gem). (Alex Kwiatkowski, Ryan Ong, #1907) +* Fix `before(:context) { skip }` so that it does not wrongly cause the + spec suite to exit with a non-zero status when no examples failed. + (Myron Marston, #1926) + +### 3.2.2 / 2015-03-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.1...v3.2.2) + +Bug Fixes: + +* Fix regression in 3.2.0 that allowed tag-filtered examples to + run even if there was a location filter applied to the spec + file that was intended to limit the file to other examples. + (#1894, Myron Marston) + +### 3.2.1 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Notify start-of-run seed _before_ `start` notification rather than + _after_ so that formatters like Fuubar work properly. (Samuel Esposito, #1882) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.7...v3.2.0) + +Enhancements: + +* Improve the `inspect` output of example groups. (Mike Dalton, #1687) +* When rake task fails, only output the command if `verbose` flag is + set. (Ben Snape, #1704) +* Add `RSpec.clear_examples` as a clear way to reset examples in between + spec runs, whilst retaining user configuration. (Alexey Fedorov, #1706) +* Reduce string allocations when defining and running examples by 70% + and 50% respectively. (Myron Marston, #1738) +* Removed dependency on pathname from stdlib. (Sam Phippen, #1703) +* Improve the message presented when a user hits Ctrl-C. + (Alex Chaffee #1717, #1742) +* Improve shared example group inclusion backtrace displayed + in failed example output so that it works for all methods + of including shared example groups and shows all inclusion + locations. (Myron Marston, #1763) +* Issue seed notification at start (as well as the end) of the reporter + run. (Arlandis Word, #1761) +* Improve the documentation of around hooks. (Jim Kingdon, #1772) +* Support prepending of modules into example groups from config and allow + filtering based on metadata. (Arlandis Word, #1806) +* Emit warnings when `:suite` hooks are registered on an example group + (where it has always been ignored) or are registered with metadata + (which has always been ignored). (Myron Marston, #1805) +* Provide a friendly error message when users call RSpec example group + APIs (e.g. `context`, `describe`, `it`, `let`, `before`, etc) from + within an example where those APIs are unavailable. (Myron Marston, #1819) +* Provide a friendly error message when users call RSpec example + APIs (e.g. `expect`, `double`, `stub_const`, etc) from + within an example group where those APIs are unavailable. + (Myron Marston, #1819) +* Add new `RSpec::Core::Sandbox.sandboxed { }` API that facilitates + testing RSpec with RSpec, allowing you to define example groups + and example from within an example without affecting the global + `RSpec.world` state. (Tyler Ball, 1808) +* Apply line-number filters only to the files they are scoped to, + allowing you to mix filtered and unfiltered files. (Myron Marston, #1839) +* When dumping pending examples, include the failure details so that you + don't have to un-pend the example to see it. (Myron Marston, #1844) +* Make `-I` option support multiple values when separated by + `File::PATH_SEPARATOR`, such as `rspec -I foo:bar`. This matches + the behavior of Ruby's `-I` option. (Fumiaki Matsushima, #1855). +* Treat each example as having a singleton example group for the + purposes of applying metadata-based features that normally apply + to example groups to individually tagged examples. For example, + `RSpec.shared_context "Uses redis", :uses_redis` will now apply + to individual examples tagged with `:uses_redis`, as will + `config.include RedisHelpers, :uses_redis`, and + `config.before(:context, :uses_redis) { }`, etc. (Myron Marston, #1749) + +Bug Fixes: + +* When assigning generated example descriptions, surface errors + raised by `matcher.description` in the example description. + (Myron Marston, #1771) +* Don't consider expectations from `after` hooks when generating + example descriptions. (Myron Marston, #1771) +* Don't apply metadata-filtered config hooks to examples in groups + with matching metadata when those examples override the parent + metadata value to not match. (Myron Marston, #1796) +* Fix `config.expect_with :minitest` so that `skip` uses RSpec's + implementation rather than Minitest's. (Jonathan Rochkind, #1822) +* Fix `NameError` caused when duplicate example group aliases are defined and + the DSL is not globally exposed. (Aaron Kromer, #1825) +* When a shared example defined in an external file fails, use the host + example group (from a loaded spec file) for the re-run command to + ensure the command will actually work. (Myron Marston, #1835) +* Fix location filtering to work properly for examples defined in + a nested example group within a shared example group defined in + an external file. (Bradley Schaefer, Xavier Shay, Myron Marston, #1837) +* When a pending example fails (as expected) due to a mock expectation, + set `RSpec::Core::Example::ExecutionResult#pending_exception` -- + previously it was not being set but should have been. (Myron Marston, #1844) +* Fix rake task to work when `rspec-core` is installed in a directory + containing a space. (Guido Günther, #1845) +* Fix regression in 3.1 that caused `describe Regexp` to raise errors. + (Durran Jordan, #1853) +* Fix regression in 3.x that caused the profile information to be printed + after the summary. (Max Lincoln, #1857) +* Apply `--seed` before loading `--require` files so that required files + can access the provided seed. (Myron Marston, #1745) +* Handle `RSpec::Core::Formatters::DeprecationFormatter::FileStream` being + reopened with an IO stream, which sometimes happens with spring. + (Kevin Mook, #1757) + +### 3.1.7 / 2014-10-11 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.6...v3.1.7) + +Bug Fixes: + +* Fix `Metadata.relative_path` so that for a current directory of + `/foo/bar`, `/foo/bar_1` is not wrongly converted to `._1`. + (Akos Vandra, #1730) +* Prevent constant lookup mistakenly finding `RSpec::ExampleGroups` generated + constants on 1.9.2 by appending a trailing `_` to the generated names. + (Jon Rowe, #1737) +* Fix bug in `:pending` metadata. If it got set in any way besides passing + it as part of the metadata literal passed to `it` (such as by using + `define_derived_metadata`), it did not have the desired effect, + instead marking the example as `:passed`. (Myron Marston, #1739) + +### 3.1.6 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.5...v3.1.6) + +Bug Fixes: + +* Fix regression in rake task pattern handling, that prevented patterns + that were relative from the current directory rather than from `spec` + from working properly. (Myron Marston, #1734) +* Prevent rake task from generating duplicate load path entries. + (Myron Marston, #1735) + +### 3.1.5 / 2014-09-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.4...v3.1.5) + +Bug Fixes: + +* Fix issue with the rake task incorrectly escaping strings on Windows. + (Jon Rowe #1718) +* Support absolute path patterns. While this wasn't officially supported + previously, setting `rake_task.pattern` to an absolute path pattern in + RSpec 3.0 and before worked since it delegated to `FileList` internally + (but now just forwards the pattern on to the `rspec` command). + (Myron Marston, #1726) + +### 3.1.4 / 2014-09-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.3...v3.1.4) + +Bug Fixes: + +* Fix implicit `subject` when using `describe false` or `describe nil` + so that it returns the provided primitive rather than the string + representation. (Myron Marston, #1710) +* Fix backtrace filtering to allow code in subdirectories of your + current working directory (such as vendor/bundle/...) to be filtered + from backtraces. (Myron Marston, #1708) + +### 3.1.3 / 2014-09-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.2...v3.1.3) + +Bug Fixes: + +* Fix yet another regression in rake task pattern handling, to allow + `task.pattern = FileList["..."]` to work. That was never intended + to be supported but accidentally worked in 3.0 and earlier. + (Myron Marston, #1701) +* Fix pattern handling so that files are normalized to absolute paths + before subtracting the `--exclude-pattern` matched files from the + `--pattern` matched files so that it still works even if the patterns + are in slightly different forms (e.g. one starting with `./`). + (Christian Nelson, #1698) + +### 3.1.2 / 2014-09-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix another regression in rake task pattern handling, so that patterns + that start with `./` still work. (Christian Nelson, #1696) + +### 3.1.1 / 2014-09-05 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix a regression in rake task pattern handling, so that `rake_task.pattern = array` + works again. While we never intended to support array values (or even knew that worked!), + the implementation from 3.0 and earlier used `FileList` internally, which allows arrays. + The fix restores the old behavior. (Myron Marston, #1694) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Update files generated by `rspec --init` so that warnings are enabled + in commented out section of `spec_helper` rather than `.rspec` so users + have to consciously opt-in to the setting. (Andrew Hooker, #1572) +* Update `spec_helper` generated by `rspec --init` so that it sets the new + rspec-expectations `include_chain_clauses_in_custom_matcher_descriptions` + config option (which will be on by default in RSpec 4) and also sets the + rspec-mocks `verify_partial_doubles` option (which will also default + to on in RSpec 4). (Myron Marston, #1647) +* Provide an `inspect` output for example procsy objects (used in around + hooks) that doesn't make them look like procs. (Jon Rowe, #1620) +* Remove a few unneeded `require` statements from + `rspec/core/rake_task.rb`, making it even more lighterweight. + (Myron Marston, #1640) +* Allow rspec-core to be used when neither rspec-mocks or + rspec-expectations are installed, without requiring any + user configuration. (Sam Phippen, Myron Marston, #1615) +* Don't filter out gems from backtraces by default. (The RSpec + gems will still be filtered). User feedback has indicated + that including gems in default backtraces will be useful. + (Myron Marston, #1641) +* Add new `config.filter_gems_from_backtrace "rack", "rake"` API + to easily filter the named gems from backtraces. (Myron Marston, #1682) +* Fix default backtrace filters so that the RSpec binary is + excluded when installing RSpec as a bundler `:git` dependency. + (Myron Marston, #1648) +* Simplify command generated by the rake task so that it no longer + includes unnecessary `-S`. (Myron Marston, #1559) +* Add `--exclude-pattern` CLI option, `config.exclude_pattern =` config + option and `task.exclude_pattern =` rake task config option. Matching + files will be excluded. (John Gesimondo, Myron Marston, #1651, #1671) +* When an around hook fails to execute the example, mark it as + pending (rather than passing) so the user is made aware of the + fact that the example did not actually run. (Myron Marston, #1660) +* Remove dependency on `FileUtils` from the standard library so that users do + not get false positives where their code relies on it but they are not + requiring it. (Sam Phippen, #1565) + +Bug Fixes: + +* Fix rake task `t.pattern =` option so that it does not run all specs + when it matches no files, by passing along a `--pattern` option to + the `rspec` command, rather than resolving the file list and passing + along the files individually. (Evgeny Zislis, #1653) +* Fix rake task default pattern so that it follows symlinks properly. + (Myron Marston, #1672) +* Fix default pattern used with `rspec` command so that it follows + symlinks properly. (Myron Marston, #1672) +* Change how we assign constant names to example group classes so that + it avoids a problem with `describe "Core"`. (Daniela Wellisz, #1679) +* Handle rendering exceptions that have a different encoding than that + of their original source file. (Jon Rowe, #1681) +* Allow access to message_lines without colour for failed examples even + when they're part of a shared example group. (tomykaira, #1689) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix processing order of CLI options so that if `config.files_to_run` + is accessed from a file loaded by `--require`, `--pattern` is still + applied. (Myron Marston, #1652) +* Fix `config.pattern=` so that it still takes affect even if + `config.files_to_run` has already been accessed. (Myron Marston, #1652) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Properly convert both parts of a description into strings before + concatenation. (@nicklink483, #1636) +* Exclude the working directory when figuring out folders to ignore. + (Jon Rowe, Myron Marston, #1616) +* Allow `::RSpec::Core::Notifications::FailedExampleNotification#message_lines` + to be accessed without a colouriser. (@tomykaira, #1637) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix regression in CLI option handling that prevented `--tag slow` + passed at the command line from overriding `--tag ~slow` in `.rspec`. + (Colin Jones, #1602) +* Fix metadata `:example_group` deprecation warning so that it gets + issued at the call site of the configuration that specified it as + a filter rather than later when an example group is defined. + (Myron Marston, #1562) +* Make the line that is printed when a shared example group fails indicating + where the concrete example group is white, separating it from the stack trace + that is produced for the failure. (Sam Phippen, Jon Rowe, #1606) + +### 3.0.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Fix a couple ruby warnings caused by rspec-core when loaded. + (Prem Sichanugrist, #1584) +* Example groups named `Config` will no longer cause a Ruby warning to be + issued. (Jimmy Cuadra, #1580) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.rc1...v3.0.0) + +Bug Fixes: + +* Fix `BaseTextFormatter` so that it does not re-close a closed output + stream. (Myron Marston) +* Fix regression in metadata that caused the metadata hash of a top-level + example group to have a `:parent_example_group` key even though it has + no parent example group. (Myron Marston) + +Enhancements: + +* Alter the default `spec_helper.rb` to no longer recommend + `config.full_backtrace = true` see #1536 for discussion. (Jon Rowe) + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Change `described_class` so that in a nested group like `describe + MyClass`, it returns `MyClass` rather than the outer group's described + class. (Myron Marston) +* Refactor filter manager so that it no longer subclasses Hash and has a + tighter, more domain-specific interface. (Sergey Pchelincev) +* Remove legacy colours definitions from `BaseTextFormatter`. (Jon Rowe) +* Remove console color definitions from `BaseTextFormatter`. (Jon Rowe) +* Restructure example group metadata so that the computed keys are + exposed directly off of the metadata hash rather than being on + a nested `:example_group` subhash. In addition, the parent example + group metadata is now available as `[:parent_example_group]` rather + than `[:example_group][:example_group]`. Deprecated access via the + old key structure is still provided. (Myron Marston) +* Remove `:describes` metadata key. It duplicates `:described_class` + for no good reason. Deprecated access via `:describes` is still + provided. (Myron Marston) +* Rename `:example_group_block` metadata key to `:block`. + (Myron Marston) +* Remove deprecated `RSpec::Core::Example#options`. (Myron Marston) +* Move `BaseTextFormatter#colorize_summary` to `SummaryNotification#colorize_with` + (Jon Rowe). +* `describe some_hash` treated `some_hash` as metadata in RSpec 2.x but + will treat it as the described object in RSpec 3.0. Metadata must + always come after the description args. (Myron Marston) +* Remove deprecated `display_name` alias of `ExampleGroup.description`. + (Myron Marston) +* Remove deprecated `describes` alias of `ExampleGroup.described_class`. + (Myron Marston) +* Remove deprecated `RSpec::Core::ExampleGroup.alias_it_behaves_like_to`. + Use `RSpec::Core::Configuration#alias_it_behaves_like_to` instead. + (Myron Marston) +* Remove deprecated `RSpec::Core::ExampleGroup.alias_example_to`. + Use `RSpec::Core::Configuration#alias_example_to` instead. + (Myron Marston) +* Removed `focused` example alias and change example/group aliases + `fit`, `focus`, `fcontext` and `fdescribe` to no longer include + `:focused => true` metadata. They only contain `:focus => true` + metadata now. This means that you will need to filter them with + `filter_run :focus`, not `filter_run :focused`. (Myron Marston) +* Remove `--line-number` filtering. It's semantically dubious since it's + a global filter (potentially applied to multiple files) but there's no + meaningful connection between the same line number in multiple files. + Instead use the `rspec path/to/spec.rb:23:46` form, which is terser + and makes more sense as it is scoped to a file. (Myron Marston) +* Remove `--default_path` as an alias for `--default-path`. (Jon Rowe) +* Remove deprecated `share_examples_for`. There's still + `shared_examples` and `shared_examples_for`. (Myron Marston) +* Rename `RSpec::Core::Configuration#warnings` to + `RSpec::Core::Configuration#warnings?` since it's a boolean flag. + (Myron Marston) +* RSpec's global state is no longer reset after a spec run. This gives + more flexibility to alternate runners to decide when and if they + want the state reset. Alternate runners are now responsible for + calling this (or doing a similar reset) if they are going to run + the spec suite multiple times in the same process. (Sam Phippen) +* Merge `RSpec::Core::CommandLine` (never formally declared public) + into `RSpec::Core::Runner`. (Myron Marston) +* Remove `color_enabled` as an alias of `color`. (Jon Rowe) +* Remove `backtrace_cleaner` as an alias of `backtrace_formatter`. (Jon Rowe) +* Remove `filename_pattern` as an alias of `pattern`. (Jon Rowe) +* Extract support for legacy formatters to `rspec-legacy_formatters`. (Jon Rowe) +* `RSpec::Configuration#formatters` now returns a dup to prevent mutation. (Jon Rowe) +* Replace `stdlib` as an available expectation framework with `test_unit` and + `minitest`. (Aaron Kromer) +* Remove backtrace formatting helpers from `BaseTextFormatter`. (Jon Rowe) +* Extract profiler support to `ProfileFormatter` and `ProfileNotification`. + Formatters should implement `dump_profile` if they wish to respond to `--profile`. + (Jon Rowe) +* Extract remaining formatter state to reporter and notifications. Introduce + `ExamplesNotification` to share information about examples that was previously + held in `BaseFormatter`. (Jon Rowe) + +Enhancements: + +* Add `config.default_formatter` attribute, which can be used to set a + formatter which will only be used if no other formatter is set + (e.g. via `--formatter`). (Myron Marston) +* Support legacy colour definitions in `LegacyFormatterAdaptor`. (Jon Rowe) +* Migrate `execution_result` (exposed by metadata) from a hash to a + first-class object with appropriate attributes. `status` is now + stored and returned as a symbol rather than a string. It retains + deprecated hash behavior for backwards compatibility. (Myron Marston) +* Provide console code helper for formatters. (Jon Rowe) +* Use raw ruby hashes for the metadata hashes rather than a subclass of + a hash. Computed metadata entries are now computed in advance rather + than being done lazily on first access. (Myron Marston) +* Add `:block` metadata entry to the example metadata, bringing + parity with `:block` in the example group metadata. (Myron Marston) +* Add `fspecify` and `fexample` as aliases of `specify` and `example` + with `:focus => true` metadata for parity with `fit`. (Myron Marston) +* Add legacy support for `colorize_summary`. (Jon Rowe) +* Restructure runner so it can be more easily customized in a subclass + for an alternate runner. (Ben Hoskings) +* Document `RSpec::Core::ConfigurationOptions` as an officially + supported public API. (Myron Marston) +* Add `--deprecation-out` CLI option which directs deprecation warnings + to the named file. (Myron Marston) +* Minitest 5 compatability for `expect_with :stdlib` (now available as + `expect_with :minitest`). (Xavier Shay) +* Reporter now notifies formatters of the load time of RSpec and your + specs via `StartNotification` and `SummaryNotification`. (Jon Rowe) +* Add `disable_monkey_patching!` config option that disables all monkey + patching from whatever pieces of RSpec you use. (Alexey Fedorov) +* Add `Pathname` support for setting all output streams. (Aaron Kromer) +* Add `config.define_derived_metadata`, which can be used to apply + additional metadata to all groups or examples that match a given + filter. (Myron Marston) +* Provide formatted and colorized backtraces via `FailedExampleNotification` + and send `PendingExampleFixedNotifications` when the error is due to a + passing spec you expect to fail. (Jon Rowe) +* Add `dump_profile` to formatter API to allow formatters to implement + support for `--profile`. (Jon Rowe) +* Allow colourising text via `ConsoleCodes` with RSpec 'states' + (e.g. `:success`, `:failure`) rather than direct colour codes. (Jon Rowe) +* Expose `fully_formatted` methods off the formatter notification objects + that make it easy for a custom formatter to produce formatted output + like rspec-core's. (Myron Marston) + +Bug Fixes: + +* Fix `spec_helper.rb` file generated by `rspec --init` so that the + recommended settings correctly use the documentation formatter + when running one file. (Myron Marston) +* Fix ordering problem where descriptions were generated after + tearing down mocks, which resulted in unexpected exceptions. + (Bradley Schaefer, Aaron Kromer, Andrey Savchenko) +* Allow a symbol to be used as an implicit subject (e.g. `describe + :foo`). (Myron Marston) +* Prevent creating an isolated context (i.e. using `RSpec.describe`) when + already inside a context. There is no reason to do this, and it could + potentially cause unexpected bugs. (Xavier Shay) +* Fix shared example group scoping so that when two shared example + groups share the same name at different levels of nested contexts, + the one in the nearest context is used. (Myron Marston) +* Fix `--warnings` option so that it enables warnings immediately so + that it applies to files loaded by `--require`. (Myron Marston) +* Issue a warning when you set `config.deprecation_stream` too late for + it to take effect because the reporter has already been setup. (Myron Marston) +* Add the full `RSpec::Core::Example` interface to the argument yielded + to `around` hooks. (Myron Marston) +* Line number always takes precendence when running specs with filters. + (Xavier Shay) +* Ensure :if and :unless metadata filters are treated as a special case + and are always in-effect. (Bradley Schaefer) +* Ensure the currently running installation of RSpec is used when + the rake task shells out to `rspec`, even if a newer version is also + installed. (Postmodern) +* Using a legacy formatter as default no longer causes an infinite loop. + (Xavier Shay) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Make `mock_with` option more strict. Strings are no longer supported + (e.g. `mock_with "mocha"`) -- use a symbol instead. Also, unrecognized + values will now result in an error rather than falling back to the + null mocking adapter. If you want to use the null mocking adapter, + use `mock_with :nothing` (as has been documented for a long time). + (Myron Marston) +* Remove support for overriding RSpec's built-in `:if` and `:unless` + filters. (Ashish Dixit) +* Custom formatters are now required to call + `RSpec::Core::Formatters.register(formatter_class, *notifications)` + where `notifications` is the list of events the formatter wishes to + be notified about. Notifications are handled by methods matching the + names on formatters. This allows us to add or remove notifications + without breaking existing formatters. (Jon Rowe) +* Change arguments passed to formatters. Rather than passing multiple + arguments (which limits are ability to add additional arguments as + doing so would break existing formatters), we now pass a notification + value object that exposes the same data via attributes. This will + allow us to add new bits of data to a notification event without + breaking existing formatters. (Jon Rowe) +* Remove support for deprecated `:alias` option for + `RSpec.configuration.add_setting`. (Myron Marston) +* Remove support for deprecated `RSpec.configuration.requires = [...]`. + (Myron Marston) +* Remove support for deprecated `--formatter` CLI option. (Myron Marston) +* Remove support for deprecated `--configure` CLI option. (Myron Marston) +* Remove support for deprecated `RSpec::Core::RakeTask#spec_opts=`. + (Myron Marston) +* An example group level `pending` block or `:pending` metadata now executes + the example and cause a failure if it passes, otherwise it will be pending if + it fails. The old "never run" behaviour is still used for `xexample`, `xit`, + and `xspecify`, or via a new `skip` method or `:skip` metadata option. + (Xavier Shay) +* After calling `pending` inside an example, the remainder of the example will + now be run. If it passes a failure is raised, otherwise the example is marked + pending. The old "never run" behaviour is provided a by a new `skip` method. + (Xavier Shay) +* Pending blocks inside an example have been removed as a feature with no + direct replacement. Use `skip` or `pending` without a block. (Xavier Shay) +* Pending statement is no longer allowed in `before(:all)` hooks. Use `skip` + instead. (Xavier Shay) +* Remove `show_failures_in_pending_blocks` configuration option. (Xavier Shay) +* Remove support for specifying the documentation formatter using + 's', 'n', 'spec' or 'nested'. (Jon Rowe) + +Enhancements: + +* Add example run time to JSON formatter output. (Karthik Kastury) +* Add more suggested settings to the files generated by + `rspec --init`. (Myron Marston) +* Add `config.alias_example_group_to`, which can be used to define a + new method that defines an example group with the provided metadata. + (Michi Huber) +* Add `xdescribe` and `xcontext` as shortcuts to skip an example group. + (Myron Marston) +* Add `fdescribe` and `fcontext` as shortcuts to focus an example group. + (Myron Marston) +* Don't autorun specs via `#at_exit` by default. `require 'rspec/autorun'` + is only needed when running specs via `ruby`, as it always has been. + Running specs via `rake` or `rspec` are both unaffected. (Ben Hoskings) +* Add `expose_dsl_globally` config option, defaulting to true. When disabled + it will remove the monkey patches rspec-core adds to `main` and `Module` + (e.g. `describe`, `shared_examples_for`, etc). (Jon Rowe) +* Expose RSpec DSL entry point methods (`describe`, + `shared_examples_for`, etc) on the `RSpec` constant. Intended for use + when `expose_dsl_globally` is set to `false`. (Jon Rowe) +* For consistency, expose all example group aliases (including + `context`) on the `RSpec` constant. If `expose_dsl_globally` is set to + `true`, also expose them on `main` and `Module`. Historically, only `describe` + was exposed. (Jon Rowe, Michi Huber) +* Add hook scope `:example` as an alias for `:each`, and `:context` as an alias + for `:all`. (John Feminella) + +Bug Fixes: + +* Fix failure (undefined method `path`) in end-of-run summary + when `raise_errors_for_deprecations!` is configured. (Myron Marston) +* Issue error when attempting to use `-i` or `--I` on command line, + too close to `-I` to be considered short hand for `--init`. (Jon Rowe) +* Prevent adding formatters to an output target if the same + formatter has already been added to that output. (Alex Peattie) +* Allow a matcher-generated example description to be used when + the example is pending. (Myron Marston) +* Ensure the configured `failure_exit_code` is used by the rake + task when there is a failure. (Jon Rowe) +* Restore behaviour whereby system exclusion filters take priority over working + directory (was broken in beta1). (Jon Rowe) +* Prevent RSpec mangling file names that have substrings containing `line_number` + or `default_path`. (Matijs van Zuijlen) +* Fix failure line detection so that it handles relative file paths + (which can happen when running specs through `ruby` using `rspec/autorun`). + (Myron Marston, #1829) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.1...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Remove explicit support for 1.8.6. (Jon Rowe) +* Remove `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. If you need + access to the example (e.g. to get its metadata), use a block arg + instead. (David Chelimsky) +* Remove `TextMateFormatter`, it has been moved to `rspec-tmbundle`. + (Aaron Kromer) +* Remove RCov integration. (Jon Rowe) +* Remove deprecated support for RSpec 1 constructs (Myron Marston): + * The `Spec` and `Rspec` constants (rather than `RSpec`). + * `Spec::Runner.configure` rather than `RSpec.configure`. + * `Rake::SpecTask` rather than `RSpec::Core::RakeTask`. +* Remove deprecated support for `share_as`. (Myron Marston) +* Remove `--debug` option (and corresponding option on + `RSpec::Core::Configuration`). Instead, use `-r` to + load whichever debugger gem you wish to use (e.g. `ruby-debug`, + `debugger`, or `pry`). (Myron Marston) +* Extract Autotest support to a seperate gem. (Jon Rowe) +* Raise an error when a `let` or `subject` declaration is + accessed in a `before(:all)` or `after(:all)` hook. (Myron Marston) +* Extract `its` support to a separate gem. (Peter Alfvin) +* Disallow use of a shared example group from sibling contexts, making them + fully isolated. 2.14 and 2.99 allowed this but printed a deprecation warning. + (Jon Rowe) +* Remove `RSpec::Core::Configuration#output` and + `RSpec::Core::Configuration#out` aliases of + `RSpec::Core::Configuration#output_stream`. (Myron Marston) +* Remove legacy ordering APIs deprecated in 2.99.0.beta1. (Myron + Marston) + +Enhancements: + +* Replace unmaintained syntax gem with coderay gem. (Xavier Shay) +* Times in profile output are now bold instead of `failure_color`. + (Matthew Boedicker) +* Add `--no-fail-fast` command line option. (Gonzalo Rodríguez-Baltanás Díaz) +* Runner now considers the local system ip address when running under Drb. + (Adrian CB) +* JsonFormatter now includes `--profile` information. (Alex / @MasterLambaster) +* Always treat symbols passed as metadata args as hash + keys with true values. RSpec 2 supported this with the + `treat_symbols_as_metadata_keys_with_true_values` but + now this behavior is always enabled. (Myron Marston) +* Add `--dry-run` option, which prints the formatter output + of your suite without running any examples or hooks. + (Thomas Stratmann, Myron Marston) +* Document the configuration options and default values in the `spec_helper.rb` + file that is generated by RSpec. (Parker Selbert) +* Give generated example group classes a friendly name derived + from the docstring, rather than something like "Nested_2". + (Myron Marston) +* Avoid affecting randomization of user code when shuffling + examples so that users can count on their own seeds + working. (Travis Herrick) +* Ordering is no longer a single global property of the test suite. + Each group can pick an ordering using `:order` metadata. (Andy + Lindeman, Sam Phippen, Myron Marston) +* Allow named custom ordering strategies to be registered, which can + then be used on individual example groups. (Andy Lindeman, Sam + Phippen, Myron Marston) + +Deprecations: + +* `treat_symbols_as_metadata_keys_with_true_values` is deprecated and no + longer has an affect now that the behavior it enabled is always + enabled. (Myron Marston) + +### 2.99.2 / 2014-08-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.1...v2.99.2) + +Enhancements: + +* Improve deprecation warning for RSpec 3 change in `describe ` + behavior. (Jon Rowe, #1667) + +### 2.99.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Add missing deprecation warning for when `RSpec::Core::Runner` is used + multiple times in the same process. In 2.x RSpec's global state was + automatically cleared between runs but in 3.0 you need to call `RSpec.reset` + manually in these situations. (Sam Phippen, #1587) +* Prevent deprecation being accidentally issues when doubles used with `be_` + matchers due to automatically generated descriptions. (Jon Rowe, #1573) +* Load `rspec/core` when loading `rspec/core/rake_task` to ensure we can + issue deprecations correctly. (Jon Rowe, #1612) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.rc1...v2.99.0) + +Bug Fixes: + +* Fix `BaseTextFormatter` so that it does not re-close a closed output + stream. (Myron Marston) +* Use `RSpec::Configuration#backtrace_exclusion_patterns` rather than the + deprecated `RSpec::Configuration#backtrace_clean_patterns` when mocking + with rr. (David Dollar) + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.beta2...v2.99.0.rc1) + +Enhancements: + +* Add `--deprecation-out` CLI option which directs deprecation warnings + to the named file. (Myron Marston) +* Backport support for `skip` in metadata to skip execution of an example. + (Xavier Shay, #1472) +* Add `Pathname` support for setting all output streams. (Aaron Kromer) +* Add `test_unit` and `minitest` expectation frameworks. (Aaron Kromer) + +Deprecations: + +* Deprecate `RSpec::Core::Pending::PendingDeclaredInExample`, use + `SkipDeclaredInExample` instead. (Xavier Shay) +* Issue a deprecation when `described_class` is accessed from within + a nested `describe ` example group, since `described_class` + will return the innermost described class in RSpec 3 rather than the + outermost described class, as it behaved in RSpec 2. (Myron Marston) +* Deprecate `RSpec::Core::FilterManager::DEFAULT_EXCLUSIONS`, + `RSpec::Core::FilterManager::STANDALONE_FILTERS` and use of + `#empty_without_conditional_filters?` on those filters. (Sergey Pchelincev) +* Deprecate `RSpec::Core::Example#options` in favor of + `RSpec::Core::Example#metadata`. (Myron Marston) +* Issue warning when passing a symbol or hash to `describe` or `context` + as the first argument. In RSpec 2.x this would be treated as metadata + but in RSpec 3 it'll be treated as the described object. To continue + having it treated as metadata, pass a description before the symbol or + hash. (Myron Marston) +* Deprecate `RSpec::Core::BaseTextFormatter::VT100_COLORS` and + `RSpec::Core::BaseTextFormatter::VT100_COLOR_CODES` in favour + of `RSpec::Core::BaseTextFormatter::ConsoleCodes::VT100_CODES` and + `RSpec::Core::BaseTextFormatter::ConsoleCodes::VT100_CODE_VALUES`. + (Jon Rowe) +* Deprecate `RSpec::Core::ExampleGroup.display_name` in favor of + `RSpec::Core::ExampleGroup.description`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.describes` in favor of + `RSpec::Core::ExampleGroup.described_class`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.alias_example_to` in favor of + `RSpec::Core::Configuration#alias_example_to`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.alias_it_behaves_like_to` in favor + of `RSpec::Core::Configuration#alias_it_behaves_like_to`. (Myron Marston) +* Deprecate `RSpec::Core::ExampleGroup.focused` in favor of + `RSpec::Core::ExampleGroup.focus`. (Myron Marston) +* Add deprecation warning for `config.filter_run :focused` since + example aliases `fit` and `focus` will no longer include + `:focused` metadata but will continue to include `:focus`. (Myron Marston) +* Deprecate filtering by `:line_number` (e.g. `--line-number` from the + CLI). Use location filtering instead. (Myron Marston) +* Deprecate `--default_path` as an alternative to `--default-path`. (Jon Rowe) +* Deprecate `RSpec::Core::Configuration#warnings` in favor of + `RSpec::Core::Configuration#warnings?`. (Myron Marston) +* Deprecate `share_examples_for` in favor of `shared_examples_for` or + just `shared_examples`. (Myron Marston) +* Deprecate `RSpec::Core::CommandLine` in favor of + `RSpec::Core::Runner`. (Myron Marston) +* Deprecate `#color_enabled`, `#color_enabled=` and `#color?` in favour of + `#color`, `#color=` and `#color_enabled? output`. (Jon Rowe) +* Deprecate `#filename_pattern` in favour of `#pattern`. (Jon Rowe) +* Deprecate `#backtrace_cleaner` in favour of `#backtrace_formatter`. (Jon Rowe) +* Deprecate mutating `RSpec::Configuration#formatters`. (Jon Rowe) +* Deprecate `stdlib` as an available expectation framework in favour of + `test_unit` and `minitest`. (Aaron Kromer) + +Bug Fixes: + +* Issue a warning when you set `config.deprecation_stream` too late for + it to take effect because the reporter has already been setup. (Myron Marston) +* `skip` with a block should not execute the block. (Xavier Shay) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.99.0.beta1...v2.99.0.beta2) + +Enhancements: + +* Add `is_expected` for one-liners that read well with the + `expect`-based syntax. `is_expected` is simply defined as + `expect(subject)` and can be used in an expression like: + `it { is_expected.to read_well }`. (Myron Marston) +* Backport `skip` from RSpec 3, which acts like `pending` did in RSpec 2 + when not given a block, since the behavior of `pending` is changing in + RSpec 3. (Xavier Shay) + +Deprecations: + +* Deprecate inexact `mock_with` config options. RSpec 3 will only support + the exact symbols `:rspec`, `:mocha`, `:flexmock`, `:rr` or `:nothing` + (or any module that implements the adapter interface). RSpec 2 did + fuzzy matching but this will not be supported going forward. + (Myron Marston) +* Deprecate `show_failures_in_pending_blocks` config option. To achieve + the same behavior as the option enabled, you can use a custom + formatter instead. (Xavier Shay) +* Add a deprecation warning for the fact that the behavior of `pending` + is changing in RSpec 3 -- rather than skipping the example (as it did + in 2.x when no block was provided), it will run the example and mark + it as failed if no exception is raised. Use `skip` instead to preserve + the old behavior. (Xavier Shay) +* Deprecate 's', 'n', 'spec' and 'nested' as aliases for documentation + formatter. (Jon Rowe) +* Deprecate `RSpec::Core::Reporter#abort` in favor of + `RSpec::Core::Reporter#finish`. (Jon Rowe) + +Bug Fixes: + +* Fix failure (undefined method `path`) in end-of-run summary + when `raise_errors_for_deprecations!` is configured. (Myron Marston) +* Fix issue were overridding spec ordering from the command line wasn't + fully recognised interally. (Jon Rowe) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.7...v2.99.0.beta1) + +Enhancements + +* Block-based DSL methods that run in the context of an example + (`it`, `before(:each)`, `after(:each)`, `let` and `subject`) + now yield the example as a block argument. (David Chelimsky) +* Warn when the name of more than one example group is submitted to + `include_examples` and it's aliases. (David Chelimsky) +* Add `expose_current_running_example_as` config option for + use during the upgrade process when external gems use the + deprecated `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. (Myron Marston) +* Limit spamminess of deprecation messages. (Bradley Schaefer, Loren Segal) +* Add `config.raise_errors_for_deprecations!` option, which turns + deprecations warnings into errors to surface the full backtrace + of the call site. (Myron Marston) + +Deprecations + +* Deprecate `RSpec::Core::ExampleGroup#example` and + `RSpec::Core::ExampleGroup#running_example` methods. If you need + access to the example (e.g. to get its metadata), use a block argument + instead. (David Chelimsky) +* Deprecate use of `autotest/rspec2` in favour of `rspec-autotest`. (Jon Rowe) +* Deprecate RSpec's built-in debugger support. Use a CLI option like + `-rruby-debug` (for the ruby-debug gem) or `-rdebugger` (for the + debugger gem) instead. (Myron Marston) +* Deprecate `RSpec.configuration.treat_symbols_as_metadata_keys_with_true_values = false`. + RSpec 3 will not support having this option set to `false`. (Myron Marston) +* Deprecate accessing a `let` or `subject` declaration in + a `after(:all)` hook. (Myron Marston, Jon Rowe) +* Deprecate built-in `its` usage in favor of `rspec-its` gem due to planned + removal in RSpec 3. (Peter Alfvin) +* Deprecate `RSpec::Core::PendingExampleFixedError` in favor of + `RSpec::Core::Pending::PendingExampleFixedError`. (Myron Marston) +* Deprecate `RSpec::Core::Configuration#out` and + `RSpec::Core::Configuration#output` in favor of + `RSpec::Core::Configuration#output_stream`. (Myron Marston) +* Deprecate legacy ordering APIs. + * You should use `register_ordering(:global)` instead of these: + * `RSpec::Core::Configuration#order_examples` + * `RSpec::Core::Configuration#order_groups` + * `RSpec::Core::Configuration#order_groups_and_examples` + * These are deprecated with no replacement because in RSpec 3 + ordering is a property of individual example groups rather than + just a global property of the entire test suite: + * `RSpec::Core::Configuration#order` + * `RSpec::Core::Configuration#randomize?` + * `--order default` is deprecated in favor of `--order defined` + (Myron Marston) + +### 2.14.8 / 2014-02-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.7...v2.14.8) + +Bug fixes: + +* Fix regression with the `textmateformatter` that prevented backtrace links + from being clickable. (Stefan Daschek) + +### 2.14.7 / 2013-10-29 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.6...v2.14.7) + +Bug fixes: + +* Fix regression in 2.14.6 that broke the Fivemat formatter. + It depended upon either + `example.execution_result[:exception].pending_fixed?` (which + was removed in 2.14.6 to fix an issue with frozen error objects) + or `RSpec::Core::PendingExampleFixedError` (which was renamed + to `RSpec::Core::Pending::PendingExampleFixedError` in 2.8. + This fix makes a constant alias for the old error name. + (Myron Marston) + +### 2.14.6 / 2013-10-15 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.5...v2.14.6) + +Bug fixes: + +* Format stringified numbers correctly when mathn library is loaded. + (Jay Hayes) +* Fix an issue that prevented the use of frozen error objects. (Lars + Gierth) + +### 2.14.5 / 2013-08-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.4...v2.14.5) + +Bug fixes: + +* Fix a `NoMethodError` that was being raised when there were no shared + examples or contexts declared and `RSpec.world.reset` is invoked. + (thepoho, Jon Rowe, Myron Marston) +* Fix a deprecation warning that was being incorrectly displayed when + `shared_examples` are declared at top level in a `module` scope. + (Jon Rowe) +* Fix after(:all) hooks so consecutive (same context) scopes will run even if + one raises an error. (Jon Rowe, Trejkaz) +* JsonFormatter no longer dies if `dump_profile` isn't defined (Alex / @MasterLambaster, Jon Rowe) + +### 2.14.4 / 2013-07-21 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.3...v2.14.4) + +Bug fixes + +* Fix regression in 2.14: ensure configured requires (via `-r` option) + are loaded before spec files are loaded. This allows the spec files + to programatically change the file pattern (Jon Rowe). +* Autoload `RSpec::Mocks` and `RSpec::Expectations` when referenced if + they are not already loaded (`RSpec::Matches` has been autoloaded + for a while). In the `rspec` gem, we changed it recently to stop + loading `rspec/mocks` and `rspec/expectations` by default, as some + users reported problems where they were intending to use mocha, + not rspec-mocks, but rspec-mocks was loaded and causing a conflict. + rspec-core loads mocks and expectations at the appropriate time, so + it seemed like a safe change -- but caused a problem for some authors + of libraries that integrate with RSpec. This fixes that problem. + (Myron Marston) +* Gracefully handle a command like `rspec --profile path/to/spec.rb`: + the `path/to/spec.rb` arg was being wrongly treated as the `profile` + integer arg, which got cast `0` using `to_i`, causing no profiled + examples to be printed. (Jon Rowe) + +### 2.14.3 / 2013-07-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.2...v2.14.3) + +Bug fixes + +* Fix deprecation notices issued from `RSpec::Core::RakeTask` so + that they work properly when all of rspec-core is not loaded. + (This was a regression in 2.14) (Jon Rowe) + +### 2.14.2 / 2013-07-09 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.1...v2.14.2) + +Bug fixes + +* Fix regression caused by 2.14.1 release: formatters that + report that they `respond_to?` a notification, but had + no corresponding method would raise an error when registered. + The new fix is to just implement `start` on the deprecation + formatter to fix the original JRuby/ruby-debug issue. + (Jon Rowe) + +### 2.14.1 / 2013-07-08 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.0...v2.14.1) + +Bug fixes + +* Address deprecation formatter failure when using `ruby-debug` on + JRuby: fix `RSpec::Core::Reporter` to not send a notification + when the formatter's implementation of the notification method + comes from `Kernel` (Alex Portnov, Jon Rowe). + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.14.0.rc1...v2.14.0) + +Enhancements + +* Apply focus to examples defined with `fit` (equivalent of + `it "description", focus: true`) (Michael de Silva) + +Bug fix + +* Ensure methods defined by `let` take precedence over others + when there is a name collision (e.g. from an included module). + (Jon Rowe, Andy Lindeman and Myron Marston) + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.13.1...v2.14.0.rc1) + +Enhancements + +* Improved Windows detection inside Git Bash, for better `--color` handling. +* Add profiling of the slowest example groups to `--profile` option. + The output is sorted by the slowest average example groups. +* Don't show slow examples if there's a failure and both `--fail-fast` + and `--profile` options are used (Paweł Gościcki). +* Rather than always adding `spec` to the load path, add the configured + `--default-path` to the load path (which defaults to `spec`). This + better supports folks who choose to put their specs in a different + directory (John Feminella). +* Add some logic to test time duration precision. Make it a + function of time, dropping precision as the time increases. (Aaron Kromer) +* Add new `backtrace_inclusion_patterns` config option. Backtrace lines + that match one of these patterns will _always_ be included in the + backtrace, even if they match an exclusion pattern, too (Sam Phippen). +* Support ERB trim mode using the `-` when parsing `.rspec` as ERB + (Gabor Garami). +* Give a better error message when let and subject are called without a block. + (Sam Phippen). +* List the precedence of `.rspec-local` in the configuration documentation + (Sam Phippen) +* Support `{a,b}` shell expansion syntax in `--pattern` option + (Konstantin Haase). +* Add cucumber documentation for --require command line option + (Bradley Schaefer) +* Expose configuration options via config: + * `config.libs` returns the libs configured to be added onto the load path + * `full_backtrace?` returns the state of the backtrace cleaner + * `debug?` returns true when the debugger is loaded + * `line_numbers` returns the line numbers we are filtering by (if any) + * `full_description` returns the RegExp used to filter descriptions + (Jon Rowe) +* Add setters for RSpec.world and RSpec.configuration (Alex Soulim) +* Configure ruby's warning behaviour with `--warnings` (Jon Rowe) +* Fix an obscure issue on old versions of `1.8.7` where `Time.dup` wouldn't + allow access to `Time.now` (Jon Rowe) +* Make `shared_examples_for` context aware, so that keys may be safely reused + in multiple contexts without colliding. (Jon Rowe) +* Add a configurable `deprecation_stream` (Jon Rowe) +* Publish deprecations through a formatter (David Chelimsky) + +Bug fixes + +* Make JSON formatter behave the same when it comes to `--profile` as + the text formatter (Paweł Gościcki). +* Fix named subjects so that if an inner group defines a method that + overrides the named method, `subject` still retains the originally + declared value (Myron Marston). +* Fix random ordering so that it does not cause `rand` in examples in + nested sibling contexts to return the same value (Max Shytikov). +* Use the new `backtrace_inclusion_patterns` config option to ensure + that folks who develop code in a directory matching one of the default + exclusion patterns (e.g. `gems`) still get the normal backtrace + filtering (Sam Phippen). +* Fix ordering of `before` hooks so that `before` hooks declared in + `RSpec.configure` run before `before` hooks declared in a shared + context (Michi Huber and Tejas Dinkar). +* Fix `Example#full_description` so that it gets filled in by the last + matcher description (as `Example#description` already did) when no + doc string has been provided (David Chelimsky). +* Fix the memoized methods (`let` and `subject`) leaking `define_method` + as a `public` method. (Thomas Holmes and Jon Rowe) (#873) +* Fix warnings coming from the test suite. (Pete Higgins) + +Deprecations + +* Deprecate `Configuration#backtrace_clean_patterns` in favor of + `Configuration#backtrace_exclusion_patterns` for greater consistency + and symmetry with new `backtrace_inclusion_patterns` config option + (Sam Phippen). +* Deprecate `Configuration#requires=` in favor of using ruby's + `require`. Requires specified by the command line can still be + accessed by the `Configuration#require` reader. (Bradley Schaefer) +* Deprecate calling `SharedExampleGroups` defined across sibling contexts + (Jon Rowe) + +### 2.13.1 / 2013-03-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.13.0...v2.13.1) + +Bug fixes + +* Use hook classes as proxies rather than extending hook blocks to support + lambdas for before/after/around hooks. (David Chelimsky) +* Fix regression in 2.13.0 that caused confusing behavior when overriding + a named subject with an unnamed subject in an inner group and then + referencing the outer group subject's name. The fix for this required + us to disallow using `super` in a named subject (which is confusing, + anyway -- named subjects create 2 methods, so which method on the + parent example group are you `super`ing to?) but `super` in an unnamed + subject continues to work (Myron Marston). +* Do not allow a referenced `let` or `subject` in `before(:all)` to cause + other `let` declarations to leak across examples (Myron Marston). +* Work around odd ruby 1.9 bug with `String#match` that was triggered + by passing it a regex from a `let` declaration. For more info, see + http://bugs.ruby-lang.org/issues/8059 (Aaron Kromer). +* Add missing `require 'set'` to `base_text_formatter.rb` (Tom + Anderson). + +Deprecations + +* Deprecate accessing `let` or `subject` declarations in `before(:all)`. + These were not intended to be called in a `before(:all)` hook, as + they exist to define state that is reset between each example, while + `before(:all)` exists to define state that is shared across examples + in an example group (Myron Marston). + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.2...v2.13.0) + +Enhancements + +* Allow `--profile` option to take a count argument that + determines the number of slow examples to dump + (Greggory Rothmeier). +* Add `subject!` that is the analog to `let!`. It defines an + explicit subject and sets a `before` hook that will invoke + the subject (Zubin Henner). +* Fix `let` and `subject` declaration so that `super` + and `return` can be used in them, just like in a normal + method. (Myron Marston) +* Allow output colors to be configured individually. + (Charlie Maffitt) +* Always dump slow examples when `--profile` option is given, + even when an example failed (Myron Marston). + +Bug fixes + +* Don't blow up when dumping error output for instances + of anonymous error classes (Myron Marston). +* Fix default backtrace filters so lines from projects + containing "gems" in the name are not filtered, but + lines from installed gems still are (Myron Marston). +* Fix autotest command so that is uses double quotes + rather than single quotes for windows compatibility + (Jonas Tingeborn). +* Fix `its` so that uses of `subject` in a `before` or `let` + declaration in the parent group continue to reference the + parent group's subject. (Olek Janiszewski) + +### 2.12.2 / 2012-12-13 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.1...v2.12.2) + +Bug fixes + +* Fix `RSpec::Core::RakeTask` so that it is compatible with rake 0.8.7 + on ruby 1.8.7. We had accidentally broke it in the 2.12 release + (Myron Marston). +* Fix `RSpec::Core::RakeTask` so it is tolerant of the `Rspec` constant + for backwards compatibility (Patrick Van Stee) + +### 2.12.1 / 2012-12-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Specs are run even if another at\_exit hook calls `exit`. This allows + Test::Unit and RSpec to run together. (Suraj N. Kurapati) +* Fix full doc string concatenation so that it handles the case of a + method string (e.g. "#foo") being nested under a context string + (e.g. "when it is tuesday"), so that we get "when it is tuesday #foo" + rather than "when it is tuesday#foo". (Myron Marston) +* Restore public API I unintentionally broke in 2.12.0: + `RSpec::Core::Formatters::BaseFormatter#format_backtrce(backtrace, example)` + (Myron Marston). + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.11.1...v2.12.0) + +Enhancements + +* Add support for custom ordering strategies for groups and examples. + (Myron Marston) +* JSON Formatter (Alex Chaffee) +* Refactor rake task internals (Sam Phippen) +* Refactor HtmlFormatter (Pete Hodgson) +* Autotest supports a path to Ruby that contains spaces (dsisnero) +* Provide a helpful warning when a shared example group is redefined. + (Mark Burns). +* `--default_path` can be specified as `--default-line`. `--line_number` can be + specified as `--line-number`. Hyphens are more idiomatic command line argument + separators (Sam Phippen). +* A more useful error message is shown when an invalid command line option is + used (Jordi Polo). +* Add `format_docstrings { |str| }` config option. It can be used to + apply formatting rules to example group and example docstrings. + (Alex Tan) +* Add support for an `.rspec-local` options file. This is intended to + allow individual developers to set options in a git-ignored file that + override the common project options in `.rspec`. (Sam Phippen) +* Support for mocha 0.13.0. (Andy Lindeman) + +Bug fixes + +* Remove override of `ExampleGroup#ancestors`. This is a core ruby method that + RSpec shouldn't override. Instead, define `ExampleGroup#parent_groups`. (Myron + Marston) +* Limit monkey patching of shared example/context declaration methods + (`shared_examples_for`, etc.) to just the objects that need it rather than + every object in the system (Myron Marston). +* Fix Metadata#fetch to support computed values (Sam Goldman). +* Named subject can now be referred to from within subject block in a nested + group (tomykaira). +* Fix `fail_fast` so that it properly exits when an error occurs in a + `before(:all) hook` (Bradley Schaefer). +* Make the order spec files are loaded consistent, regardless of the + order of the files returned by the OS or the order passed at + the command line (Jo Liss and Sam Phippen). +* Ensure instance variables from `before(:all)` are always exposed + from `after(:all)`, even if an error occurs in `before(:all)` + (Sam Phippen). +* `rspec --init` no longer generates an incorrect warning about `--configure` + being deprecated (Sam Phippen). +* Fix pluralization of `1 seconds` (Odin Dutton) +* Fix ANSICON url (Jarmo Pertman) +* Use dup of Time so reporting isn't clobbered by examples that modify Time + without properly restoring it. (David Chelimsky) + +Deprecations + +* `share_as` is no longer needed. `shared_context` and/or + `RSpec::SharedContext` provide better mechanisms (Sam Phippen). +* Deprecate `RSpec.configuration` with a block (use `RSpec.configure`). + + +### 2.11.1 / 2012-07-18 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Fix the way we autoload RSpec::Matchers so that custom matchers can be + defined before rspec-core has been configured to definitely use + rspec-expectations. (Myron Marston) +* Fix typo in --help message printed for -e option. (Jo Liss) +* Fix ruby warnings. (Myron Marston) +* Ignore mock expectation failures when the example has already failed. + Mock expectation failures have always been ignored in this situation, + but due to my changes in 27059bf1 it was printing a confusing message. + (Myron Marston). + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.10.1...v2.11.0) + +Enhancements + +* Support multiple `--example` options. (Daniel Doubrovkine @dblock) +* Named subject e.g. `subject(:article) { Article.new }` + * see [http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/](http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/) + for background. + * thanks to Bradley Schaefer for suggesting it and Avdi Grimm for almost + suggesting it. +* `config.mock_with` and `config.expect_with` yield custom config object to a + block if given + * aids decoupling from rspec-core's configuation +* `include_context` and `include_examples` support a block, which gets eval'd + in the current context (vs the nested context generated by `it_behaves_like`). +* Add `config.order = 'random'` to the `spec_helper.rb` generated by `rspec + --init`. +* Delay the loading of DRb (Myron Marston). +* Limit monkey patching of `describe` onto just the objects that need it rather + than every object in the system (Myron Marston). + +Bug fixes + +* Support alternative path separators. For example, on Windows, you can now do + this: `rspec spec\subdir`. (Jarmo Pertman @jarmo) +* When an example raises an error and an after or around hook does as + well, print out the hook error. Previously, the error was silenced and + the user got no feedback about what happened. (Myron Marston) +* `--require` and `-I` are merged among different configuration sources (Andy + Lindeman) +* Delegate to mocha methods instead of aliasing them in mocha adapter. + +### 2.10.1 / 2012-05-19 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.10.0...v2.10.1) + +Bug fixes + +* `RSpec.reset` properly reinits configuration and world +* Call `to_s` before `split` on exception messages that might not always be + Strings (slyphon) + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.9.0...v2.10.0) + +Enhancements + +* Add `prepend_before` and `append_after` hooks (preethiramdev) + * intended for extension libs + * restores rspec-1 behavior +* Reporting of profiled examples (moro) + * Report the total amount of time taken for the top slowest examples. + * Report what percentage the slowest examples took from the total runtime. + +Bug fixes + +* Properly parse `SPEC_OPTS` options. +* `example.description` returns the location of the example if there is no + explicit description or matcher-generated description. +* RDoc fixes (Grzegorz Świrski) +* Do not modify example ancestry when dumping errors (Michael Grosser) + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0...v2.9.0) + +Enhancements + +* Support for "X minutes X seconds" spec run duration in formatter. (uzzz) +* Strip whitespace from group and example names in doc formatter. +* Removed spork-0.9 shim. If you're using spork-0.8.x, you'll need to upgrade + to 0.9.0. + +Bug fixes + +* Restore `--full_backtrace` option +* Ensure that values passed to `config.filter_run` are respected when running + over DRb (using spork). +* Ensure shared example groups are reset after a run (as example groups are). +* Remove `rescue false` from calls to filters represented as Procs +* Ensure `described_class` gets the closest constant (pyromaniac) +* In "autorun", don't run the specs in the `at_exit` hook if there was an + exception (most likely due to a SyntaxError). (sunaku) +* Don't extend groups with modules already used to extend ancestor groups. +* `its` correctly memoizes nil or false values (Yamada Masaki) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0.rc2...v2.8.0) + +Bug fixes + +* For metadata filtering, restore passing the entire array to the proc, rather + than each item in the array (weidenfreak) +* Ensure each spec file is loaded only once + * Fixes a bug that caused all the examples in a file to be run when + referenced twice with line numbers in a command, e.g. + * `rspec path/to/file:37 path/to/file:42` + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.8.0.rc1...v2.8.0.rc2) + +Enhancments + +* new `--init` command (Peter Schröder) + * generates `spec/spec_helper.rb` + * deletes obsolete files (on confirmation) + * merged with and deprecates `--configure` command, which generated + `.rspec` +* use `require_relative` when available (Ian Leitch) +* `include_context` and `include_examples` accept params (Calvin Bascom) +* print the time for every example in the html formatter (Richie Vos) +* several tasty refactoring niblets (Sasha) +* `it "does something", :x => [:foo,'bar',/baz/] (Ivan Neverov) + * supports matching n command line tag values with an example or group + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.7.1...v2.8.0.rc1) + +Enhancements + +* `--order` (Justin Ko) + * run examples in random order: `--order rand` + * specify the seed: `--order rand:123` +* `--seed SEED` + * equivalent of `--order rand:SEED` +* SharedContext supports `let` (David Chelimsky) +* Filter improvements (David Chelimsky) + * override opposing tags from the command line + * override RSpec.configure tags from the command line + * `--line_number 37` overrides all other filters + * `path/to/file.rb:37` overrides all other filters + * refactor: consolidate filter management in a FilterManger object +* Eliminate Ruby warnings (Matijs van Zuijlen) +* Make reporter.report an API (David Chelimsky) + * supports extension tools like interative_rspec + +Changes + +* change `config.color_enabled` (getter/setter/predicate) to `color` to align + with `--[no]-color` CLI option. + * `color_enabled` is still supported for now, but will likley be deprecated + in a 2.x release so we can remove it in 3.0. + +Bug fixes + +* Make sure the `bar` in `--tag foo:bar` makes it to DRb (Aaron Gibralter) +* Fix bug where full descriptions of groups nested 3 deep were repeated. +* Restore report of time to run to start after files are loaded. + * fixes bug where run times were cumalitive in spork + * fixes compatibility with time-series metrics +* Don't error out when `config.mock_with` or `expect_with` is re-specifying the + current config (Myron Marston) + +* Deprecations + * :alias option on `configuration.add_setting`. Use `:alias_with` on the + original setting declaration instead. + +### 2.7.1 / 2011-10-20 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.7.0...v2.7.1) + +Bug fixes + +* tell autotest the correct place to find the rspec executable + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.4...v2.7.0) + +NOTE: RSpec's release policy dictates that there should not be any backward +incompatible changes in minor releases, but we're making an exception to +release a change to how RSpec interacts with other command line tools. + +As of 2.7.0, you must explicity `require "rspec/autorun"` unless you use the +`rspec` command (which already does this for you). + +Enhancements + +* Add `example.exception` (David Chelimsky) +* `--default_path` command line option (Justin Ko) +* support multiple `--line_number` options (David J. Hamilton) + * also supports `path/to/file.rb:5:9` (runs examples on lines 5 and 9) +* Allow classes/modules to be used as shared example group identifiers (Arthur + Gunn) +* Friendly error message when shared context cannot be found (Sławosz + Sławiński) +* Clear formatters when resetting config (John Bintz) +* Add `xspecify` and xexample as temp-pending methods (David Chelimsky) +* Add `--no-drb` option (Iain Hecker) +* Provide more accurate run time by registering start time before code is + loaded (David Chelimsky) + * reverted in 2.8.0 +* Rake task default pattern finds specs in symlinked dirs (Kelly Felkins) +* Rake task no longer does anything to invoke bundler since Bundler already + handles it for us. Thanks to Andre Arko for the tip. +* Add `--failure-exit-code` option (Chris Griego) + +Bug fixes + +* Include `Rake::DSL` to remove deprecation warnings in Rake > 0.8.7 (Pivotal + Casebook) +* Only eval `let` block once even if it returns `nil` (Adam Meehan) +* Fix `--pattern` option (wasn't being recognized) (David Chelimsky) +* Only implicitly `require "rspec/autorun"` with the `rspec` command (David + Chelimsky) +* Ensure that rspec's `at_exit` defines the exit code (Daniel Doubrovkine) +* Show the correct snippet in the HTML and TextMate formatters (Brian Faherty) + +### 2.6.4 / 2011-06-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.3...v2.6.4) + +NOTE: RSpec's release policy dictates that there should not be new +functionality in patch releases, but this minor enhancement slipped in by +accident. As it doesn't add a new API, we decided to leave it in rather than +roll back this release. + +Enhancements + +* Add summary of commands to run individual failed examples. + +Bug fixes + +* Support exclusion filters in DRb. (Yann Lugrin) +* Fix --example escaping when run over DRb. (Elliot Winkler) +* Use standard ANSI codes for color formatting so colors work in a wider set of + color schemes. + +### 2.6.3 / 2011-05-24 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.2...v2.6.3) + +Bug fixes + +* Explicitly convert exit code to integer, avoiding TypeError when return + value of run is IO object proxied by `DRb::DRbObject` (Julian Scheid) +* Clarify behavior of `--example` command line option +* Build using a rubygems-1.6.2 to avoid downstream yaml parsing error + +### 2.6.2 / 2011-05-21 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.1...v2.6.2) + +Bug fixes + +* Warn rather than raise when HOME env var is not defined +* Properly merge command-line exclusions with default :if and :unless (joshcooper) + +### 2.6.1 / 2011-05-19 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.6.0...v2.6.1) + +Bug fixes + +* Don't extend nil when filters are nil +* `require 'rspec/autorun'` when running rcov. + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.5.1...v2.6.0) + +Enhancements + +* `shared_context` (Damian Nurzynski) + * extend groups matching specific metadata with: + * method definitions + * subject declarations + * let/let! declarations + * etc (anything you can do in a group) +* `its([:key])` works for any subject with #[]. (Peter Jaros) +* `treat_symbols_as_metadata_keys_with_true_values` (Myron Marston) +* Print a deprecation warning when you configure RSpec after defining an + example. All configuration should happen before any examples are defined. + (Myron Marston) +* Pass the exit status of a DRb run to the invoking process. This causes specs + run via DRb to not just return true or false. (Ilkka Laukkanen) +* Refactoring of `ConfigurationOptions#parse_options` (Rodrigo Rosenfeld Rosas) +* Report excluded filters in runner output (tip from andyl) +* Clean up messages for filters/tags. +* Restore --pattern/-P command line option from rspec-1 +* Support false as well as true in config.full_backtrace= (Andreas Tolf + Tolfsen) + +Bug fixes + +* Don't stumble over an exception without a message (Hans Hasselberg) +* Remove non-ascii characters from comments that were choking rcov (Geoffrey + Byers) +* Fixed backtrace so it doesn't include lines from before the autorun at_exit + hook (Myron Marston) +* Include RSpec::Matchers when first example group is defined, rather than just + before running the examples. This works around an obscure bug in ruby 1.9 + that can cause infinite recursion. (Myron Marston) +* Don't send `example_group_[started|finished]` to formatters for empty groups. +* Get specs passing on jruby (Sidu Ponnappa) +* Fix bug where mixing nested groups and outer-level examples gave + unpredictable :line_number behavior (Artur Małecki) +* Regexp.escape the argument to --example (tip from Elliot Winkler) +* Correctly pass/fail pending block with message expectations +* CommandLine returns exit status (0/1) instead of true/false +* Create path to formatter output file if it doesn't exist (marekj). + + +### 2.5.1 / 2011-02-06 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.5.0...v2.5.1) + +NOTE: this release breaks compatibility with rspec/autotest/bundler +integration, but does so in order to greatly simplify it. + +With this release, if you want the generated autotest command to include +'bundle exec', require Autotest's bundler plugin in a .autotest file in the +project's root directory or in your home directory: + + require "autotest/bundler" + +Now you can just type 'autotest' on the command line and it will work as you expect. + +If you don't want 'bundle exec', there is nothing you have to do. + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.4.0...v2.5.0) + +Enhancements + +* Autotest::Rspec2 parses command line args passed to autotest after '--' +* --skip-bundler option for autotest command +* Autotest regexp fixes (Jon Rowe) +* Add filters to html and textmate formatters (Daniel Quimper) +* Explicit passing of block (need for JRuby 1.6) (John Firebaugh) + +Bug fixes + +* fix dom IDs in HTML formatter (Brian Faherty) +* fix bug with --drb + formatters when not running in drb +* include --tag options in drb args (monocle) +* fix regression so now SPEC_OPTS take precedence over CLI options again (Roman + Chernyatchik) +* only call its(:attribute) once (failing example from Brian Dunn) +* fix bizarre bug where rspec would hang after String.alias :to_int :to_i + (Damian Nurzynski) + +Deprecations + +* implicit inclusion of 'bundle exec' when Gemfile present (use autotest's + bundler plugin instead) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.3.1...v2.4.0) + +Enhancements + +* start the debugger on -d so the stack trace is visible when it stops + (Clifford Heath) +* apply hook filtering to examples as well as groups (Myron Marston) +* support multiple formatters, each with their own output +* show exception classes in failure messages unless they come from RSpec + matchers or message expectations +* before(:all) { pending } sets all examples to pending + +Bug fixes + +* fix bug due to change in behavior of reject in Ruby 1.9.3-dev (Shota + Fukumori) +* fix bug when running in jruby: be explicit about passing block to super (John + Firebaugh) +* rake task doesn't choke on paths with quotes (Janmejay Singh) +* restore --options option from rspec-1 +* require 'ostruct' to fix bug with its([key]) (Kim Burgestrand) +* --configure option generates .rspec file instead of autotest/discover.rb + +### 2.3.1 / 2010-12-16 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.3.0...v2.3.1) + +Bug fixes + +* send debugger warning message to $stdout if RSpec.configuration.error_stream + has not been defined yet. +* HTML Formatter _finally_ properly displays nested groups (Jarmo Pertman) +* eliminate some warnings when running RSpec's own suite (Jarmo Pertman) + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.2.1...v2.3.0) + +Enhancements + +* tell autotest to use "rspec2" if it sees a .rspec file in the project's root + directory + * replaces the need for ./autotest/discover.rb, which will not work with + all versions of ZenTest and/or autotest +* config.expect_with + * :rspec # => rspec/expectations + * :stdlib # => test/unit/assertions + * :rspec, :stdlib # => both + +Bug fixes + +* fix dev Gemfile to work on non-mac-os machines (Lake Denman) +* ensure explicit subject is only eval'd once (Laszlo Bacsi) + +### 2.2.1 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.2.0...v2.2.1) + +Bug fixes + +* alias_method instead of override Kernel#method_missing (John Wilger) +* changed --autotest to --tty in generated command (MIKAMI Yoshiyuki) +* revert change to debugger (had introduced conflict with Rails) + * also restored --debugger/-debug option + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.1.0...v2.2.0) + +Deprecations/changes + +* --debug/-d on command line is deprecated and now has no effect +* win32console is now ignored; Windows users must use ANSICON for color support + (Bosko Ivanisevic) + +Enhancements + +* When developing locally rspec-core now works with the rspec-dev setup or your + local gems +* Raise exception with helpful message when rspec-1 is loaded alongside rspec-2 + (Justin Ko) +* debugger statements _just work_ as long as ruby-debug is installed + * otherwise you get warned, but not fired +* Expose example.metadata in around hooks +* Performance improvments (much faster now) + +Bug fixes + +* Make sure --fail-fast makes it across drb +* Pass -Ilib:spec to rcov + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.1...v2.1.0) + +Enhancments + +* Add skip_bundler option to rake task to tell rake task to ignore the presence + of a Gemfile (jfelchner) +* Add gemfile option to rake task to tell rake task what Gemfile to look for + (defaults to 'Gemfile') +* Allow passing caller trace into Metadata to support extensions (Glenn + Vanderburg) +* Add deprecation warning for Spec::Runner.configure to aid upgrade from + RSpec-1 +* Add deprecated Spec::Rake::SpecTask to aid upgrade from RSpec-1 +* Add 'autospec' command with helpful message to aid upgrade from RSpec-1 +* Add support for filtering with tags on CLI (Lailson Bandeira) +* Add a helpful message about RUBYOPT when require fails in bin/rspec (slyphon) +* Add "-Ilib" to the default rcov options (Tianyi Cui) +* Make the expectation framework configurable (default rspec, of course) + (Justin Ko) +* Add 'pending' to be conditional (Myron Marston) +* Add explicit support for :if and :unless as metadata keys for conditional run + of examples (Myron Marston) +* Add --fail-fast command line option (Jeff Kreeftmeijer) + +Bug fixes + +* Eliminate stack overflow with "subject { self }" +* Require 'rspec/core' in the Raketask (ensures it required when running rcov) + +### 2.0.1 / 2010-10-18 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0...v2.0.1) + +Bug fixes + +* Restore color when using spork + autotest +* Pending examples without docstrings render the correct message (Josep M. + Bach) +* Fixed bug where a failure in a spec file ending in anything but _spec.rb + would fail in a confusing way. +* Support backtrace lines from erb templates in html formatter (Alex Crichton) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.rc...v2.0.0) + +RSpec-1 compatibility + +* Rake task uses ENV["SPEC"] as file list if present + +Bug fixes + +* Bug Fix: optparse --out foo.txt (Leonardo Bessa) +* Suppress color codes for non-tty output (except autotest) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* implicitly require unknown formatters so you don't have to require the file + explicitly on the command line (Michael Grosser) +* add --out/-o option to assign output target +* added fail_fast configuration option to abort on first failure +* support a Hash subject (its([:key]) { should == value }) (Josep M. Bach) + +Bug fixes + +* Explicitly require rspec version to fix broken rdoc task (Hans de Graaff) +* Ignore backtrace lines that come from other languages, like Java or + Javascript (Charles Lowell) +* Rake task now does what is expected when setting (or not setting) + fail_on_error and verbose +* Fix bug in which before/after(:all) hooks were running on excluded nested + groups (Myron Marston) +* Fix before(:all) error handling so that it fails examples in nested groups, + too (Myron Marston) + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-core/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Enhancements + +* removed at_exit hook +* CTRL-C stops the run (almost) immediately + * first it cleans things up by running the appropriate after(:all) and + after(:suite) hooks + * then it reports on any examples that have already run +* cleaned up rake task + * generate correct task under variety of conditions + * options are more consistent + * deprecated redundant options +* run 'bundle exec autotest' when Gemfile is present +* support ERB in .rspec options files (Justin Ko) +* depend on bundler for development tasks (Myron Marston) +* add example_group_finished to formatters and reporter (Roman Chernyatchik) + +Bug fixes + +* support paths with spaces when using autotest (Andreas Neuhaus) +* fix module_exec with ruby 1.8.6 (Myron Marston) +* remove context method from top-level + * was conflicting with irb, for example +* errors in before(:all) are now reported correctly (Chad Humphries) + +Removals + +* removed -o --options-file command line option + * use ./.rspec and ~/.rspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/LICENSE.md new file mode 100644 index 0000000000..76dc17d739 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/LICENSE.md @@ -0,0 +1,26 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 Chad Humphries, David Chelimsky, Myron Marston +* Copyright © 2009 Chad Humphries, David Chelimsky +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-core-3.12.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/README.md new file mode 100644 index 0000000000..af9d87ea2c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/README.md @@ -0,0 +1,384 @@ +# rspec-core [![Build Status](https://github.com/rspec/rspec-core/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-core/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-core.svg)](https://codeclimate.com/github/rspec/rspec-core) + +rspec-core provides the structure for writing executable examples of how your +code should behave, and an `rspec` command with tools to constrain which +examples get run and tailor the output. + +## Install + + gem install rspec # for rspec-core, rspec-expectations, rspec-mocks + gem install rspec-core # for rspec-core only + rspec --help + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +## Basic Structure + +RSpec uses the words "describe" and "it" so we can express concepts like a conversation: + + "Describe an order." + "It sums the prices of its line items." + +```ruby +RSpec.describe Order do + it "sums the prices of its line items" do + order = Order.new + + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(1.11, :USD) + ))) + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(2.22, :USD), + :quantity => 2 + ))) + + expect(order.total).to eq(Money.new(5.55, :USD)) + end +end +``` + +The `describe` method creates an [ExampleGroup](http://rubydoc.info/gems/rspec-core/RSpec/Core/ExampleGroup). Within the +block passed to `describe` you can declare examples using the `it` method. + +Under the hood, an example group is a class in which the block passed to +`describe` is evaluated. The blocks passed to `it` are evaluated in the +context of an _instance_ of that class. + +## Nested Groups + +You can also declare nested groups using the `describe` or `context` +methods: + +```ruby +RSpec.describe Order do + context "with no items" do + it "behaves one way" do + # ... + end + end + + context "with one item" do + it "behaves another way" do + # ... + end + end +end +``` + +Nested groups are subclasses of the outer example group class, providing +the inheritance semantics you'd want for free. + +## Aliases + +You can declare example groups using either `describe` or `context`. +For a top level example group, `describe` and `context` are available +off of `RSpec`. For backwards compatibility, they are also available +off of the `main` object and `Module` unless you disable monkey +patching. + +You can declare examples within a group using any of `it`, `specify`, or +`example`. + +## Shared Examples and Contexts + +Declare a shared example group using `shared_examples`, and then include it +in any group using `include_examples`. + +```ruby +RSpec.shared_examples "collections" do |collection_class| + it "is empty when first created" do + expect(collection_class.new).to be_empty + end +end + +RSpec.describe Array do + include_examples "collections", Array +end + +RSpec.describe Hash do + include_examples "collections", Hash +end +``` + +Nearly anything that can be declared within an example group can be declared +within a shared example group. This includes `before`, `after`, and `around` +hooks, `let` declarations, and nested groups/contexts. + +You can also use the names `shared_context` and `include_context`. These are +pretty much the same as `shared_examples` and `include_examples`, providing +more accurate naming when you share hooks, `let` declarations, helper methods, +etc, but no examples. + +## Metadata + +rspec-core stores a metadata hash with every example and group, which +contains their descriptions, the locations at which they were +declared, etc, etc. This hash powers many of rspec-core's features, +including output formatters (which access descriptions and locations), +and filtering before and after hooks. + +Although you probably won't ever need this unless you are writing an +extension, you can access it from an example like this: + +```ruby +it "does something" do |example| + expect(example.metadata[:description]).to eq("does something") +end +``` + +### `described_class` + +When a class is passed to `describe`, you can access it from an example +using the `described_class` method, which is a wrapper for +`example.metadata[:described_class]`. + +```ruby +RSpec.describe Widget do + example do + expect(described_class).to equal(Widget) + end +end +``` + +This is useful in extensions or shared example groups in which the specific +class is unknown. Taking the collections shared example group from above, we can +clean it up a bit using `described_class`: + +```ruby +RSpec.shared_examples "collections" do + it "is empty when first created" do + expect(described_class.new).to be_empty + end +end + +RSpec.describe Array do + include_examples "collections" +end + +RSpec.describe Hash do + include_examples "collections" +end +``` + +## A Word on Scope + +RSpec has two scopes: + +* **Example Group**: Example groups are defined by a `describe` or + `context` block, which is eagerly evaluated when the spec file is + loaded. The block is evaluated in the context of a subclass of + `RSpec::Core::ExampleGroup`, or a subclass of the parent example group + when you're nesting them. +* **Example**: Examples -- typically defined by an `it` block -- and any other + blocks with per-example semantics -- such as a `before(:example)` hook -- are + evaluated in the context of + an _instance_ of the example group class to which the example belongs. + Examples are _not_ executed when the spec file is loaded; instead, + RSpec waits to run any examples until all spec files have been loaded, + at which point it can apply filtering, randomization, etc. + +To make this more concrete, consider this code snippet: + +``` ruby +RSpec.describe "Using an array as a stack" do + def build_stack + [] + end + + before(:example) do + @stack = build_stack + end + + it 'is initially empty' do + expect(@stack).to be_empty + end + + context "after an item has been pushed" do + before(:example) do + @stack.push :item + end + + it 'allows the pushed item to be popped' do + expect(@stack.pop).to eq(:item) + end + end +end +``` + +Under the covers, this is (roughly) equivalent to: + +``` ruby +class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup + def build_stack + [] + end + + def before_example_1 + @stack = build_stack + end + + def it_is_initially_empty + expect(@stack).to be_empty + end + + class AfterAnItemHasBeenPushed < self + def before_example_2 + @stack.push :item + end + + def it_allows_the_pushed_item_to_be_popped + expect(@stack.pop).to eq(:item) + end + end +end +``` + +To run these examples, RSpec would (roughly) do the following: + +``` ruby +example_1 = UsingAnArrayAsAStack.new +example_1.before_example_1 +example_1.it_is_initially_empty + +example_2 = UsingAnArrayAsAStack::AfterAnItemHasBeenPushed.new +example_2.before_example_1 +example_2.before_example_2 +example_2.it_allows_the_pushed_item_to_be_popped +``` + +## The `rspec` Command + +When you install the rspec-core gem, it installs the `rspec` executable, +which you'll use to run rspec. The `rspec` command comes with many useful +options. +Run `rspec --help` to see the complete list. + +## Store Command Line Options `.rspec` + +You can store command line options in a `.rspec` file in the project's root +directory, and the `rspec` command will read them as though you typed them on +the command line. + +## Get Started + +Start with a simple example of behavior you expect from your system. Do +this before you write any implementation code: + +```ruby +# in spec/calculator_spec.rb +RSpec.describe Calculator do + describe '#add' do + it 'returns the sum of its arguments' do + expect(Calculator.new.add(1, 2)).to eq(3) + end + end +end +``` + +Run this with the rspec command, and watch it fail: + +``` +$ rspec spec/calculator_spec.rb +./spec/calculator_spec.rb:1: uninitialized constant Calculator +``` + +Address the failure by defining a skeleton of the `Calculator` class: + +```ruby +# in lib/calculator.rb +class Calculator + def add(a, b) + end +end +``` + +Be sure to require the implementation file in the spec: + +```ruby +# in spec/calculator_spec.rb +# - RSpec adds ./lib to the $LOAD_PATH +require "calculator" +``` + +Now run the spec again, and watch the expectation fail: + +``` +$ rspec spec/calculator_spec.rb +F + +Failures: + + 1) Calculator#add returns the sum of its arguments + Failure/Error: expect(Calculator.new.add(1, 2)).to eq(3) + + expected: 3 + got: nil + + (compared using ==) + # ./spec/calculator_spec.rb:6:in `block (3 levels) in ' + +Finished in 0.00131 seconds (files took 0.10968 seconds to load) +1 example, 1 failure + +Failed examples: + +rspec ./spec/calculator_spec.rb:5 # Calculator#add returns the sum of its arguments +``` + +Implement the simplest solution, by changing the definition of `Calculator#add` to: + +```ruby +def add(a, b) + a + b +end +``` + +Now run the spec again, and watch it pass: + +``` +$ rspec spec/calculator_spec.rb +. + +Finished in 0.000315 seconds +1 example, 0 failures +``` + +Use the `documentation` formatter to see the resulting spec: + +``` +$ rspec spec/calculator_spec.rb --format doc +Calculator + #add + returns the sum of its arguments + +Finished in 0.000379 seconds +1 example, 0 failures +``` + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +* [Build details](BUILD_DETAIL.md) +* [Code of Conduct](CODE_OF_CONDUCT.md) +* [Detailed contributing guide](CONTRIBUTING.md) +* [Development setup guide](DEVELOPMENT.md) + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/exe/rspec b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/exe/rspec new file mode 100755 index 0000000000..7ee5fd89f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/exe/rspec @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require 'rspec/core' +RSpec::Core::Runner.invoke diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/autorun.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/autorun.rb new file mode 100644 index 0000000000..3080cfdd4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/autorun.rb @@ -0,0 +1,3 @@ +require 'rspec/core' +# Ensure the default config is loaded +RSpec::Core::Runner.autorun diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core.rb new file mode 100644 index 0000000000..ad9553c9bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core.rb @@ -0,0 +1,212 @@ +# rubocop:disable Style/GlobalVars +$_rspec_core_load_started_at = Time.now +# rubocop:enable Style/GlobalVars + +require "rspec/support" +RSpec::Support.require_rspec_support "caller_filter" + +RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative f } + +%w[ + version + warnings + + set + flat_map + filter_manager + dsl + notifications + reporter + + hooks + memoized_helpers + metadata + metadata_filter + pending + formatters + ordering + + world + configuration + option_parser + configuration_options + runner + invocations + example + shared_example_group + example_group +].each { |name| RSpec::Support.require_rspec_core name } + +# Namespace for all core RSpec code. +module RSpec + autoload :SharedContext, 'rspec/core/shared_context' + + extend RSpec::Core::Warnings + + class << self + # Setters for shared global objects + # @api private + attr_writer :configuration, :world + end + + # Used to ensure examples get reloaded and user configuration gets reset to + # defaults between multiple runs in the same process. + # + # Users must invoke this if they want to have the configuration reset when + # they use the runner multiple times within the same process. Users must deal + # themselves with re-configuration of RSpec before run. + def self.reset + RSpec::ExampleGroups.remove_all_constants + @world = nil + @configuration = nil + end + + # Used to ensure examples get reloaded between multiple runs in the same + # process and ensures user configuration is persisted. + # + # Users must invoke this if they want to clear all examples but preserve + # current configuration when they use the runner multiple times within the + # same process. + def self.clear_examples + world.reset + configuration.reset_reporter + configuration.start_time = ::RSpec::Core::Time.now + configuration.reset_filters + end + + # Returns the global [Configuration](RSpec/Core/Configuration) object. While + # you _can_ use this method to access the configuration, the more common + # convention is to use [RSpec.configure](RSpec#configure-class_method). + # + # @example + # RSpec.configuration.drb_port = 1234 + # @see RSpec.configure + # @see Core::Configuration + def self.configuration + @configuration ||= RSpec::Core::Configuration.new + end + + # Yields the global configuration to a block. + # @yield [Configuration] global configuration + # + # @example + # RSpec.configure do |config| + # config.add_formatter 'documentation' + # end + # @see Core::Configuration + def self.configure + yield configuration if block_given? + end + + # The example being executed. + # + # The primary audience for this method is library authors who need access + # to the example currently being executed and also want to support all + # versions of RSpec 2 and 3. + # + # @example + # + # RSpec.configure do |c| + # # context.example is deprecated, but RSpec.current_example is not + # # available until RSpec 3.0. + # fetch_current_example = RSpec.respond_to?(:current_example) ? + # proc { RSpec.current_example } : proc { |context| context.example } + # + # c.before(:example) do + # example = fetch_current_example.call(self) + # + # # ... + # end + # end + # + def self.current_example + RSpec::Support.thread_local_data[:current_example] + end + + # Set the current example being executed. + # @api private + def self.current_example=(example) + RSpec::Support.thread_local_data[:current_example] = example + end + + # Set the current scope rspec is executing in + # @api private + def self.current_scope=(scope) + RSpec::Support.thread_local_data[:current_scope] = scope + end + RSpec.current_scope = :suite + + # Get the current RSpec execution scope + # + # Returns (in order of lifecycle): + # * `:suite` as an initial value, this is outside of the test lifecycle. + # * `:before_suite_hook` during `before(:suite)` hooks. + # * `:before_context_hook` during `before(:context)` hooks. + # * `:before_example_hook` during `before(:example)` hooks and `around(:example)` before `example.run`. + # * `:example` within the example run. + # * `:after_example_hook` during `after(:example)` hooks and `around(:example)` after `example.run`. + # * `:after_context_hook` during `after(:context)` hooks. + # * `:after_suite_hook` during `after(:suite)` hooks. + # * `:suite` as a final value, again this is outside of the test lifecycle. + # + # Reminder, `:context` hooks have `:all` alias and `:example` hooks have `:each` alias. + # @return [Symbol] + def self.current_scope + RSpec::Support.thread_local_data[:current_scope] + end + + # @private + # Internal container for global non-configuration data. + def self.world + @world ||= RSpec::Core::World.new + end + + # Namespace for the rspec-core code. + module Core + autoload :ExampleStatusPersister, "rspec/core/example_status_persister" + autoload :Profiler, "rspec/core/profiler" + autoload :DidYouMean, "rspec/core/did_you_mean" + + # @private + # This avoids issues with reporting time caused by examples that + # change the value/meaning of Time.now without properly restoring + # it. + class Time + class << self + define_method(:now, &::Time.method(:now)) + end + end + + # @private path to executable file. + def self.path_to_executable + @path_to_executable ||= File.expand_path('../../../exe/rspec', __FILE__) + end + end + + # @private + MODULES_TO_AUTOLOAD = { + :Matchers => "rspec/expectations", + :Expectations => "rspec/expectations", + :Mocks => "rspec/mocks" + } + + # @private + def self.const_missing(name) + # Load rspec-expectations when RSpec::Matchers is referenced. This allows + # people to define custom matchers (using `RSpec::Matchers.define`) before + # rspec-core has loaded rspec-expectations (since it delays the loading of + # it to allow users to configure a different assertion/expectation + # framework). `autoload` can't be used since it works with ruby's built-in + # require (e.g. for files that are available relative to a load path dir), + # but not with rubygems' extended require. + # + # As of rspec 2.14.1, we no longer require `rspec/mocks` and + # `rspec/expectations` when `rspec` is required, so we want + # to make them available as an autoload. + require MODULES_TO_AUTOLOAD.fetch(name) { return super } + ::RSpec.const_get(name) + end + + Core::DSL.expose_globally! + Core::SharedExampleGroup::TopLevelDSL.expose_globally! +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/backtrace_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/backtrace_formatter.rb new file mode 100644 index 0000000000..e0bee52a99 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/backtrace_formatter.rb @@ -0,0 +1,65 @@ +module RSpec + module Core + # @private + class BacktraceFormatter + # @private + attr_accessor :exclusion_patterns, :inclusion_patterns + + def initialize + @full_backtrace = false + + patterns = %w[ /lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle: ] + patterns << "org/jruby/" if RUBY_PLATFORM == 'java' + patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) } + + @exclusion_patterns = [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *patterns)] + @inclusion_patterns = [] + + return unless matches?(@exclusion_patterns, File.join(Dir.getwd, "lib", "foo.rb:13")) + inclusion_patterns << Regexp.new(Dir.getwd) + end + + attr_writer :full_backtrace + + def full_backtrace? + @full_backtrace || exclusion_patterns.empty? + end + + def filter_gem(gem_name) + sep = File::SEPARATOR + exclusion_patterns << /#{sep}#{gem_name}(-[^#{sep}]+)?#{sep}/ + end + + def format_backtrace(backtrace, options={}) + return [] unless backtrace + return backtrace if options[:full_backtrace] || backtrace.empty? + + backtrace.map { |l| backtrace_line(l) }.compact. + tap do |filtered| + if filtered.empty? + filtered.concat backtrace + filtered << "" + filtered << " Showing full backtrace because every line was filtered out." + filtered << " See docs for RSpec::Configuration#backtrace_exclusion_patterns and" + filtered << " RSpec::Configuration#backtrace_inclusion_patterns for more information." + end + end + end + + def backtrace_line(line) + Metadata.relative_path(line) unless exclude?(line) + end + + def exclude?(line) + return false if @full_backtrace + matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line) + end + + private + + def matches?(patterns, line) + patterns.any? { |p| line =~ p } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/coordinator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/coordinator.rb new file mode 100644 index 0000000000..c4d304bce4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/coordinator.rb @@ -0,0 +1,62 @@ +RSpec::Support.require_rspec_core "bisect/shell_command" +RSpec::Support.require_rspec_core "bisect/example_minimizer" +RSpec::Support.require_rspec_core "bisect/utilities" +RSpec::Support.require_rspec_core "formatters/bisect_progress_formatter" + +module RSpec + module Core + module Bisect + # The main entry point into the bisect logic. Coordinates among: + # - Bisect::ShellCommand: Generates shell commands to run spec subsets + # - Bisect::ExampleMinimizer: Contains the core bisect logic. + # - A bisect runner: runs a set of examples and returns the results. + # - A bisect formatter: provides progress updates to the user. + # @private + class Coordinator + def self.bisect_with(spec_runner, original_cli_args, formatter) + new(spec_runner, original_cli_args, formatter).bisect + end + + def initialize(spec_runner, original_cli_args, formatter) + @spec_runner = spec_runner + @shell_command = ShellCommand.new(original_cli_args) + @notifier = Bisect::Notifier.new(formatter) + end + + def bisect + repro = start_bisect_runner do |runner| + minimizer = ExampleMinimizer.new(@shell_command, runner, @notifier) + + gracefully_abort_on_sigint(minimizer) + minimizer.find_minimal_repro + minimizer.repro_command_for_currently_needed_ids + end + + @notifier.publish(:bisect_repro_command, :repro => repro) + + true + rescue BisectFailedError => e + @notifier.publish(:bisect_failed, :failure_explanation => e.message) + false + ensure + @notifier.publish(:close) + end + + private + + def start_bisect_runner(&block) + klass = @spec_runner.configuration.bisect_runner_class + klass.start(@shell_command, @spec_runner, &block) + end + + def gracefully_abort_on_sigint(minimizer) + trap('INT') do + repro = minimizer.repro_command_for_currently_needed_ids + @notifier.publish(:bisect_aborted, :repro => repro) + exit(1) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/example_minimizer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/example_minimizer.rb new file mode 100644 index 0000000000..7ee5a4f8bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/example_minimizer.rb @@ -0,0 +1,173 @@ +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Bisect + # @private + # Contains the core bisect logic. Searches for examples we can ignore by + # repeatedly running different subsets of the suite. + class ExampleMinimizer + attr_reader :shell_command, :runner, :all_example_ids, :failed_example_ids + attr_accessor :remaining_ids + + def initialize(shell_command, runner, notifier) + @shell_command = shell_command + @runner = runner + @notifier = notifier + end + + def find_minimal_repro + prep + + _, duration = track_duration do + bisect(non_failing_example_ids) + end + + notify(:bisect_complete, :duration => duration, + :original_non_failing_count => non_failing_example_ids.size, + :remaining_count => remaining_ids.size) + + remaining_ids + failed_example_ids + end + + def bisect(candidate_ids) + notify(:bisect_dependency_check_started) + if get_expected_failures_for?([]) + notify(:bisect_dependency_check_failed) + self.remaining_ids = [] + return + end + notify(:bisect_dependency_check_passed) + + bisect_over(candidate_ids) + end + + def bisect_over(candidate_ids) + return if candidate_ids.one? + + notify( + :bisect_round_started, + :candidate_range => example_range(candidate_ids), + :candidates_count => candidate_ids.size + ) + + slice_size = (candidate_ids.length / 2.0).ceil + lhs, rhs = candidate_ids.each_slice(slice_size).to_a + + ids_to_ignore, duration = track_duration do + [lhs, rhs].find do |ids| + get_expected_failures_for?(remaining_ids - ids) + end + end + + if ids_to_ignore + self.remaining_ids -= ids_to_ignore + notify( + :bisect_round_ignoring_ids, + :ids_to_ignore => ids_to_ignore, + :ignore_range => example_range(ids_to_ignore), + :remaining_ids => remaining_ids, + :duration => duration + ) + bisect_over(candidate_ids - ids_to_ignore) + else + notify( + :bisect_round_detected_multiple_culprits, + :duration => duration + ) + bisect_over(lhs) + bisect_over(rhs) + end + end + + def currently_needed_ids + remaining_ids + failed_example_ids + end + + def repro_command_for_currently_needed_ids + return shell_command.repro_command_from(currently_needed_ids) if remaining_ids + "(Not yet enough information to provide any repro command)" + end + + # @private + # Convenience class for describing a subset of the candidate examples + ExampleRange = Struct.new(:start, :finish) do + def description + if start == finish + "example #{start}" + else + "examples #{start}-#{finish}" + end + end + end + + private + + def example_range(ids) + ExampleRange.new( + non_failing_example_ids.find_index(ids.first) + 1, + non_failing_example_ids.find_index(ids.last) + 1 + ) + end + + def prep + notify(:bisect_starting, :original_cli_args => shell_command.original_cli_args, + :bisect_runner => runner.class.name) + + _, duration = track_duration do + original_results = runner.original_results + @all_example_ids = original_results.all_example_ids + @failed_example_ids = original_results.failed_example_ids + @remaining_ids = non_failing_example_ids + end + + if @failed_example_ids.empty? + raise BisectFailedError, "\n\nNo failures found. Bisect only works " \ + "in the presence of one or more failing examples." + else + notify(:bisect_original_run_complete, :failed_example_ids => failed_example_ids, + :non_failing_example_ids => non_failing_example_ids, + :duration => duration) + end + end + + def non_failing_example_ids + @non_failing_example_ids ||= all_example_ids - failed_example_ids + end + + def get_expected_failures_for?(ids) + ids_to_run = ids + failed_example_ids + notify( + :bisect_individual_run_start, + :command => shell_command.repro_command_from(ids_to_run), + :ids_to_run => ids_to_run + ) + + results, duration = track_duration { runner.run(ids_to_run) } + notify(:bisect_individual_run_complete, :duration => duration, :results => results) + + abort_if_ordering_inconsistent(results) + (failed_example_ids & results.failed_example_ids) == failed_example_ids + end + + def track_duration + start = ::RSpec::Core::Time.now + [yield, ::RSpec::Core::Time.now - start] + end + + def abort_if_ordering_inconsistent(results) + expected_order = all_example_ids & results.all_example_ids + return if expected_order == results.all_example_ids + + raise BisectFailedError, "\n\nThe example ordering is inconsistent. " \ + "`--bisect` relies upon consistent ordering (e.g. by passing " \ + "`--seed` if you're using random ordering) to work properly." + end + + def notify(*args) + @notifier.publish(*args) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/fork_runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/fork_runner.rb new file mode 100644 index 0000000000..4641a14429 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/fork_runner.rb @@ -0,0 +1,138 @@ +require 'stringio' +RSpec::Support.require_rspec_core "formatters/base_bisect_formatter" +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Bisect + # A Bisect runner that runs requested subsets of the suite by forking + # sub-processes. The main process bootstraps RSpec and the application + # environment (including preloading files specified via `--require`) so + # that the individual spec runs do not have to re-pay that cost. Each + # spec run happens in a forked process, ensuring that the spec files are + # not loaded in the main process. + # + # For most projects, bisections that use `ForkRunner` instead of + # `ShellRunner` will finish significantly faster, because the `ShellRunner` + # pays the cost of booting RSpec and the app environment on _every_ run of + # a subset. In contrast, `ForkRunner` pays that cost only once. + # + # However, not all projects can use `ForkRunner`. Obviously, on platforms + # that do not support forking (e.g. Windows), it cannot be used. In addition, + # it can cause problems for some projects that put side-effectful spec + # bootstrapping logic that should run on every spec run directly at the top + # level in a file loaded by `--require`, rather than in a `before(:suite)` + # hook. For example, consider a project that relies on some top-level logic + # in `spec_helper` to boot a Redis server for the test suite, intending the + # Redis bootstrapping to happen on every spec run. With `ShellRunner`, the + # bootstrapping logic will happen for each run of any subset of the suite, + # but for `ForkRunner`, such logic will only get run once, when the + # `RunDispatcher` boots the application environment. This might cause + # problems. The solution is for users to move the bootstrapping logic into + # a `before(:suite)` hook, or use the slower `ShellRunner`. + # + # @private + class ForkRunner + def self.start(shell_command, spec_runner) + instance = new(shell_command, spec_runner) + yield instance + ensure + instance.shutdown + end + + def self.name + :fork + end + + def initialize(shell_command, spec_runner) + @shell_command = shell_command + @channel = Channel.new + @run_dispatcher = RunDispatcher.new(spec_runner, @channel) + end + + def run(locations) + run_descriptor = ExampleSetDescriptor.new(locations, original_results.failed_example_ids) + dispatch_run(run_descriptor) + end + + def original_results + @original_results ||= dispatch_run(ExampleSetDescriptor.new( + @shell_command.original_locations, [])) + end + + def shutdown + @channel.close + end + + private + + def dispatch_run(run_descriptor) + @run_dispatcher.dispatch_specs(run_descriptor) + @channel.receive.tap do |result| + if result.is_a?(String) + raise BisectFailedError.for_failed_spec_run(result) + end + end + end + + # @private + class RunDispatcher + def initialize(runner, channel) + @runner = runner + @channel = channel + + @spec_output = StringIO.new + + runner.configuration.tap do |c| + c.reset_reporter + c.output_stream = @spec_output + c.error_stream = @spec_output + end + end + + def dispatch_specs(run_descriptor) + pid = fork { run_specs(run_descriptor) } + # We don't use Process.waitpid here as it was causing bisects to + # block due to the file descriptor limit on OSX / Linux. We need + # to detach the process to avoid having zombie processes + # consuming slots in the kernel process table during bisect runs. + Process.detach(pid) + end + + private + + def run_specs(run_descriptor) + $stdout = $stderr = @spec_output + formatter = CaptureFormatter.new(run_descriptor.failed_example_ids) + + @runner.configuration.tap do |c| + c.files_or_directories_to_run = run_descriptor.all_example_ids + c.formatter = formatter + c.load_spec_files + end + + # `announce_filters` has the side effect of implementing the logic + # that honors `config.run_all_when_everything_filtered` so we need + # to call it here. When we remove `run_all_when_everything_filtered` + # (slated for RSpec 4), we can remove this call to `announce_filters`. + @runner.world.announce_filters + + @runner.run_specs(@runner.world.ordered_example_groups) + latest_run_results = formatter.results + + if latest_run_results.nil? || latest_run_results.all_example_ids.empty? + @channel.send(@spec_output.string) + else + @channel.send(latest_run_results) + end + end + end + + class CaptureFormatter < Formatters::BaseBisectFormatter + attr_accessor :results + alias_method :notify_results, :results= + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/server.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/server.rb new file mode 100644 index 0000000000..73f02998cf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/server.rb @@ -0,0 +1,61 @@ +require 'drb/drb' +require 'drb/acl' +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + # @private + module Bisect + # @private + # A DRb server that receives run results from a separate RSpec process + # started by the bisect process. + class Server + def self.run + server = new + server.start + yield server + ensure + server.stop + end + + def capture_run_results(files_or_directories_to_run=[], expected_failures=[]) + self.expected_failures = expected_failures + self.files_or_directories_to_run = files_or_directories_to_run + self.latest_run_results = nil + run_output = yield + + if latest_run_results.nil? || latest_run_results.all_example_ids.empty? + raise BisectFailedError.for_failed_spec_run(run_output) + end + + latest_run_results + end + + def start + # Only allow remote DRb requests from this machine. + DRb.install_acl ACL.new(%w[ deny all allow localhost allow 127.0.0.1 allow ::1 ]) + + # We pass `nil` as the first arg to allow it to pick a DRb port. + @drb = DRb.start_service(nil, self) + end + + def stop + @drb.stop_service + end + + def drb_port + @drb_port ||= Integer(@drb.uri[/\d+$/]) + end + + # Fetched via DRb by the BisectDRbFormatter to determine when to abort. + attr_accessor :expected_failures + + # Set via DRb by the BisectDRbFormatter with the results of the run. + attr_accessor :latest_run_results + + # Fetched via DRb to tell clients which files to run + attr_accessor :files_or_directories_to_run + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_command.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_command.rb new file mode 100644 index 0000000000..b68f8b16eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_command.rb @@ -0,0 +1,126 @@ +RSpec::Support.require_rspec_core "shell_escape" +require 'shellwords' + +module RSpec + module Core + module Bisect + # Provides an API to generate shell commands to run the suite for a + # set of locations, using the given bisect server to capture the results. + # @private + class ShellCommand + attr_reader :original_cli_args + + def initialize(original_cli_args) + @original_cli_args = original_cli_args.reject { |arg| arg.start_with?("--bisect") } + end + + def command_for(locations, server) + parts = [] + + parts << RUBY << load_path + parts << open3_safe_escape(RSpec::Core.path_to_executable) + + parts << "--format" << "bisect-drb" + parts << "--drb-port" << server.drb_port + + parts.concat(reusable_cli_options) + parts.concat(locations.map { |l| open3_safe_escape(l) }) + + parts.join(" ") + end + + def repro_command_from(locations) + parts = [] + + parts.concat environment_repro_parts + parts << "rspec" + parts.concat Formatters::Helpers.organize_ids(locations) + parts.concat original_cli_args_without_locations + + parts.join(" ") + end + + def original_locations + parsed_original_cli_options.fetch(:files_or_directories_to_run) + end + + def bisect_environment_hash + if ENV.key?('SPEC_OPTS') + { 'SPEC_OPTS' => spec_opts_without_bisect } + else + {} + end + end + + def spec_opts_without_bisect + Shellwords.join( + Shellwords.split(ENV.fetch('SPEC_OPTS', '')).reject do |arg| + arg =~ /^--bisect/ + end + ) + end + + private + + include RSpec::Core::ShellEscape + # On JRuby, Open3.popen3 does not handle shellescaped args properly: + # https://github.com/jruby/jruby/issues/2767 + if RSpec::Support::Ruby.jruby? + # :nocov: + alias open3_safe_escape quote + # :nocov: + else + alias open3_safe_escape escape + end + + def environment_repro_parts + bisect_environment_hash.map do |k, v| + %Q(#{k}="#{v}") + end + end + + def reusable_cli_options + @reusable_cli_options ||= begin + opts = original_cli_args_without_locations + + if (port = parsed_original_cli_options[:drb_port]) + opts -= %W[ --drb-port #{port} ] + end + + parsed_original_cli_options.fetch(:formatters) { [] }.each do |(name, out)| + opts -= %W[ --format #{name} -f -f#{name} ] + opts -= %W[ --out #{out} -o -o#{out} ] + end + + opts + end + end + + def original_cli_args_without_locations + @original_cli_args_without_locations ||= begin + files_or_dirs = parsed_original_cli_options.fetch(:files_or_directories_to_run) + @original_cli_args - files_or_dirs + end + end + + def parsed_original_cli_options + @parsed_original_cli_options ||= Parser.parse(@original_cli_args) + end + + def load_path + @load_path ||= "-I#{$LOAD_PATH.map { |p| open3_safe_escape(p) }.join(':')}" + end + + # Path to the currently running Ruby executable, borrowed from Rake: + # https://github.com/ruby/rake/blob/v10.4.2/lib/rake/file_utils.rb#L8-L12 + # Note that we skip `ENV['RUBY']` because we don't have to deal with running + # RSpec from within a MRI source repository: + # https://github.com/ruby/rake/commit/968682759b3b65e42748cd2befb2ff3e982272d9 + RUBY = File.join( + RbConfig::CONFIG['bindir'], + RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']). + sub(/.*\s.*/m, '"\&"') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_runner.rb new file mode 100644 index 0000000000..34afb1926d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/shell_runner.rb @@ -0,0 +1,73 @@ +require 'open3' +RSpec::Support.require_rspec_core "bisect/server" + +module RSpec + module Core + module Bisect + # Provides an API to run the suite for a set of locations, using + # the given bisect server to capture the results. + # + # Sets of specs are run by shelling out. + # @private + class ShellRunner + def self.start(shell_command, _spec_runner) + Server.run do |server| + yield new(server, shell_command) + end + end + + def self.name + :shell + end + + def initialize(server, shell_command) + @server = server + @shell_command = shell_command + end + + def run(locations) + run_locations(locations, original_results.failed_example_ids) + end + + def original_results + @original_results ||= run_locations(@shell_command.original_locations) + end + + private + + def run_locations(*capture_args) + @server.capture_run_results(*capture_args) do + run_command @shell_command.command_for([], @server) + end + end + + # `Open3.capture2e` does not work on JRuby: + # https://github.com/jruby/jruby/issues/2766 + if Open3.respond_to?(:capture2e) && !RSpec::Support::Ruby.jruby? + def run_command(cmd) + Open3.capture2e(@shell_command.bisect_environment_hash, cmd).first + end + else # for 1.8.7 + # :nocov: + def run_command(cmd) + out = err = nil + + original_spec_opts = ENV['SPEC_OPTS'] + ENV['SPEC_OPTS'] = @shell_command.spec_opts_without_bisect + + Open3.popen3(cmd) do |_, stdout, stderr| + # Reading the streams blocks until the process is complete + out = stdout.read + err = stderr.read + end + + "Stdout:\n#{out}\n\nStderr:\n#{err}" + ensure + ENV['SPEC_OPTS'] = original_spec_opts + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/utilities.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/utilities.rb new file mode 100644 index 0000000000..4600f35bf5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/bisect/utilities.rb @@ -0,0 +1,69 @@ +module RSpec + module Core + module Bisect + # @private + ExampleSetDescriptor = Struct.new(:all_example_ids, :failed_example_ids) + + # @private + class BisectFailedError < StandardError + def self.for_failed_spec_run(spec_output) + new("Failed to get results from the spec run. Spec run output:\n\n" + + spec_output) + end + end + + # Wraps a `formatter` providing a simple means to notify it in place + # of an `RSpec::Core::Reporter`, without involving configuration in + # any way. + # @private + class Notifier + def initialize(formatter) + @formatter = formatter + end + + def publish(event, *args) + return unless @formatter.respond_to?(event) + notification = Notifications::CustomNotification.for(*args) + @formatter.__send__(event, notification) + end + end + + # Wraps a pipe to support sending objects between a child and + # parent process. Where supported, encoding is explicitly + # set to ensure binary data is able to pass from child to + # parent. + # @private + class Channel + if String.method_defined?(:encoding) + MARSHAL_DUMP_ENCODING = Marshal.dump("").encoding + end + + def initialize + @read_io, @write_io = IO.pipe + + if defined?(MARSHAL_DUMP_ENCODING) && IO.method_defined?(:set_encoding) + # Ensure the pipe can send any content produced by Marshal.dump + @write_io.set_encoding MARSHAL_DUMP_ENCODING + end + end + + def send(message) + packet = Marshal.dump(message) + @write_io.write("#{packet.bytesize}\n#{packet}") + end + + # rubocop:disable Security/MarshalLoad + def receive + packet_size = Integer(@read_io.gets) + Marshal.load(@read_io.read(packet_size)) + end + # rubocop:enable Security/MarshalLoad + + def close + @read_io.close + @write_io.close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration.rb new file mode 100644 index 0000000000..b956cf72f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration.rb @@ -0,0 +1,2385 @@ +RSpec::Support.require_rspec_core "backtrace_formatter" +RSpec::Support.require_rspec_core "ruby_project" +RSpec::Support.require_rspec_core "formatters/deprecation_formatter" +RSpec::Support.require_rspec_core "output_wrapper" + +module RSpec + module Core + # rubocop:disable Metrics/ClassLength + + # Stores runtime configuration information. + # + # Configuration options are loaded from multiple files and joined together + # with command-line switches and the `SPEC_OPTS` environment variable. + # + # Precedence order (where later entries overwrite earlier entries on + # conflicts): + # + # * Global (`$XDG_CONFIG_HOME/rspec/options`, or `~/.rspec` if it does + # not exist) + # * Project-specific (`./.rspec`) + # * Local (`./.rspec-local`) + # * Command-line options + # * `SPEC_OPTS` + # + # For example, an option set in the local file will override an option set + # in your global file. + # + # The global, project-specific and local files can all be overridden with a + # separate custom file using the --options command-line parameter. + # + # @example Standard settings + # RSpec.configure do |c| + # c.drb = true + # c.drb_port = 1234 + # c.default_path = 'behavior' + # end + # + # @example Hooks + # RSpec.configure do |c| + # c.before(:suite) { establish_connection } + # c.before(:example) { log_in_as :authorized } + # c.around(:example) { |ex| Database.transaction(&ex) } + # end + # + # @see RSpec.configure + # @see Hooks + class Configuration + include RSpec::Core::Hooks + + # Module that holds `attr_reader` declarations. It's in a separate + # module to allow us to override those methods and use `super`. + # @private + Readers = Module.new + include Readers + + # @private + class MustBeConfiguredBeforeExampleGroupsError < StandardError; end + + # @private + def self.define_reader(name) + Readers.class_eval do + remove_method name if method_defined?(name) + attr_reader name + end + + define_method(name) { value_for(name) { super() } } + end + + # @private + def self.define_alias(name, alias_name) + alias_method alias_name, name + alias_method "#{alias_name}=", "#{name}=" + define_predicate alias_name + end + + # @private + def self.define_predicate(name) + define_method "#{name}?" do + !!send(name) + end + end + + # @private + # + # Invoked by the `add_setting` instance method. Use that method on a + # `Configuration` instance rather than this class method. + def self.add_setting(name, opts={}) + raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default) + attr_writer name + add_read_only_setting name + + Array(opts[:alias_with]).each do |alias_name| + define_alias(name, alias_name) + end + end + + # @private + # + # As `add_setting` but only add the reader. + def self.add_read_only_setting(name, opts={}) + raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default) + define_reader name + define_predicate name + end + + # @macro [attach] add_setting + # @!attribute [rw] $1 + # + # @macro [attach] define_reader + # @!attribute [r] $1 + + # @macro add_setting + # Path to use if no path is provided to the `rspec` command (default: + # `"spec"`). Allows you to just type `rspec` instead of `rspec spec` to + # run all the examples in the `spec` directory. + # + # @note Other scripts invoking `rspec` indirectly will ignore this + # setting. + # @return [String] + add_read_only_setting :default_path + def default_path=(path) + project_source_dirs << path + @default_path = path + end + + # @macro add_setting + # Run examples over DRb (default: `false`). RSpec doesn't supply the DRb + # server, but you can use tools like spork. + # @return [Boolean] + add_setting :drb + + # @macro add_setting + # The drb_port (default: nil). + add_setting :drb_port + + # @macro add_setting + # Default: `$stderr`. + add_setting :error_stream + + # Indicates if the DSL has been exposed off of modules and `main`. + # Default: true + # @return [Boolean] + def expose_dsl_globally? + Core::DSL.exposed_globally? + end + + # Use this to expose the core RSpec DSL via `Module` and the `main` + # object. It will be set automatically but you can override it to + # remove the DSL. + # Default: true + def expose_dsl_globally=(value) + if value + Core::DSL.expose_globally! + Core::SharedExampleGroup::TopLevelDSL.expose_globally! + else + Core::DSL.remove_globally! + Core::SharedExampleGroup::TopLevelDSL.remove_globally! + end + end + + # Determines where deprecation warnings are printed. + # Defaults to `$stderr`. + # @return [IO, String] IO or filename to write to + define_reader :deprecation_stream + + # Determines where deprecation warnings are printed. + # @param value [IO, String] IO to write to or filename to write to + def deprecation_stream=(value) + if @reporter && !value.equal?(@deprecation_stream) + warn "RSpec's reporter has already been initialized with " \ + "#{deprecation_stream.inspect} as the deprecation stream, so your change to "\ + "`deprecation_stream` will be ignored. You should configure it earlier for " \ + "it to take effect, or use the `--deprecation-out` CLI option. " \ + "(Called from #{CallerFilter.first_non_rspec_line})" + else + @deprecation_stream = value + end + end + + # @macro define_reader + # The file path to use for persisting example statuses. Necessary for the + # `--only-failures` and `--next-failure` CLI options. + # + # @overload example_status_persistence_file_path + # @return [String] the file path + # @overload example_status_persistence_file_path=(value) + # @param value [String] the file path + define_reader :example_status_persistence_file_path + + # Sets the file path to use for persisting example statuses. Necessary for the + # `--only-failures` and `--next-failure` CLI options. + def example_status_persistence_file_path=(value) + @example_status_persistence_file_path = value + clear_values_derived_from_example_status_persistence_file_path + end + + # @macro define_reader + # Indicates if the `--only-failures` (or `--next-failure`) flag is being used. + define_reader :only_failures + alias_method :only_failures?, :only_failures + + # @private + def only_failures_but_not_configured? + only_failures? && !example_status_persistence_file_path + end + + # @macro define_reader + # If specified, indicates the number of failures required before cleaning + # up and exit (default: `nil`). Can also be `true` to fail and exit on first + # failure + define_reader :fail_fast + + # @see fail_fast + def fail_fast=(value) + case value + when true, 'true' + @fail_fast = true + when false, 'false', 0 + @fail_fast = false + when nil + @fail_fast = nil + else + @fail_fast = value.to_i + + if value.to_i == 0 + # TODO: in RSpec 4, consider raising an error here. + RSpec.warning "Cannot set `RSpec.configuration.fail_fast`" \ + " to `#{value.inspect}`. Only `true`, `false`, `nil` and integers" \ + " are valid values." + @fail_fast = true + end + end + end + + # @macro add_setting + # Prints the formatter output of your suite without running any + # examples or hooks. + add_setting :dry_run + + # @macro add_setting + # The exit code to return if there are any failures (default: 1). + # @return [Integer] + add_setting :failure_exit_code + + # @macro add_setting + # The exit code to return if there are any errors outside examples (default: failure_exit_code) + # @return [Integer] + add_setting :error_exit_code + + # @macro add_setting + # Whether or not to fail when there are no RSpec examples (default: false). + # @return [Boolean] + add_setting :fail_if_no_examples + + # @macro define_reader + # Indicates files configured to be required. + # @return [Array] + define_reader :requires + + # @macro define_reader + # Returns dirs that have been prepended to the load path by the `-I` + # command line option. + # @return [Array] + define_reader :libs + + # @macro add_setting + # Determines where RSpec will send its output. + # Default: `$stdout`. + # @return [IO, String] + define_reader :output_stream + + # Set the output stream for reporter. + # @attr value [IO, String] IO to write to or filename to write to, defaults to $stdout + def output_stream=(value) + if @reporter && !value.equal?(@output_stream) + warn "RSpec's reporter has already been initialized with " \ + "#{output_stream.inspect} as the output stream, so your change to "\ + "`output_stream` will be ignored. You should configure it earlier for " \ + "it to take effect. (Called from #{CallerFilter.first_non_rspec_line})" + else + @output_stream = value + output_wrapper.output = @output_stream + end + end + + # @macro define_reader + # Load files matching this pattern (default: `'**{,/*/**}/*_spec.rb'`). + # @return [String] + define_reader :pattern + + # Set pattern to match files to load. + # @attr value [String] the filename pattern to filter spec files by + def pattern=(value) + update_pattern_attr :pattern, value + end + + # @macro define_reader + # Exclude files matching this pattern. + # @return [String] + define_reader :exclude_pattern + + # Set pattern to match files to exclude. + # @attr value [String] the filename pattern to exclude spec files by + def exclude_pattern=(value) + update_pattern_attr :exclude_pattern, value + end + + # @macro add_setting + # Specifies which directories contain the source code for your project. + # When a failure occurs, RSpec looks through the backtrace to find a + # a line of source to print. It first looks for a line coming from + # one of the project source directories so that, for example, it prints + # the expectation or assertion call rather than the source code from + # the expectation or assertion framework. + # @return [Array] + add_setting :project_source_dirs + + # @macro add_setting + # Report the times for the slowest examples (default: `false`). + # Use this to specify the number of examples to include in the profile. + # @return [Boolean] + attr_writer :profile_examples + define_predicate :profile_examples + + # @macro add_setting + # Run all examples if none match the configured filters + # (default: `false`). + # @deprecated Use {#filter_run_when_matching} instead for the specific + # filters that you want to be ignored if none match. + add_setting :run_all_when_everything_filtered + + # @macro add_setting + # Color to use to indicate success. Defaults to `:green` but can be set + # to one of the following: `[:black, :white, :red, :green, :yellow, + # :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :success_color + + # @macro add_setting + # Color to use to print pending examples. Defaults to `:yellow` but can + # be set to one of the following: `[:black, :white, :red, :green, + # :yellow, :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :pending_color + + # @macro add_setting + # Color to use to indicate failure. Defaults to `:red` but can be set to + # one of the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :failure_color + + # @macro add_setting + # The default output color. Defaults to `:white` but can be set to one of + # the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :default_color + + # @macro add_setting + # Color used when a pending example is fixed. Defaults to `:blue` but can + # be set to one of the following: `[:black, :white, :red, :green, + # :yellow, :blue, :magenta, :cyan]` + # @return [Symbol] + add_setting :fixed_color + + # @macro add_setting + # Color used to print details. Defaults to `:cyan` but can be set to one + # of the following: `[:black, :white, :red, :green, :yellow, :blue, + # :magenta, :cyan]` + # @return [Symbol] + add_setting :detail_color + + # @macro add_setting + # Don't print filter info i.e. "Run options: include {:focus=>true}" + # (default `false`). + # return [Boolean] + add_setting :silence_filter_announcements + + # @deprecated This config option was added in RSpec 2 to pave the way + # for this being the default behavior in RSpec 3. Now this option is + # a no-op. + def treat_symbols_as_metadata_keys_with_true_values=(_value) + RSpec.deprecate( + "RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values=", + :message => "RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values= " \ + "is deprecated, it is now set to true as default and " \ + "setting it to false has no effect." + ) + end + + # @macro define_reader + # Configures how RSpec treats metadata passed as part of a shared example + # group definition. For example, given this shared example group definition: + # + # RSpec.shared_context "uses DB", :db => true do + # around(:example) do |ex| + # MyORM.transaction(:rollback => true, &ex) + # end + # end + # + # ...there are two ways RSpec can treat the `:db => true` metadata, each + # of which has a corresponding config option: + # + # 1. `:trigger_inclusion`: this shared context will be implicitly included + # in any groups (or examples) that have `:db => true` metadata. + # 2. `:apply_to_host_groups`: the metadata will be inherited by the metadata + # hash of all host groups and examples. + # + # `:trigger_inclusion` is the legacy behavior from before RSpec 3.5 but should + # be considered deprecated. Instead, you can explicitly include a group with + # `include_context`: + # + # RSpec.describe "My model" do + # include_context "uses DB" + # end + # + # ...or you can configure RSpec to include the context based on matching metadata + # using an API that mirrors configured module inclusion: + # + # RSpec.configure do |rspec| + # rspec.include_context "uses DB", :db => true + # end + # + # `:apply_to_host_groups` is a new feature of RSpec 3.5 and will be the only + # supported behavior in RSpec 4. + # + # @overload shared_context_metadata_behavior + # @return [:trigger_inclusion, :apply_to_host_groups] the configured behavior + # @overload shared_context_metadata_behavior=(value) + # @param value [:trigger_inclusion, :apply_to_host_groups] sets the configured behavior + define_reader :shared_context_metadata_behavior + # @see shared_context_metadata_behavior + def shared_context_metadata_behavior=(value) + case value + when :trigger_inclusion, :apply_to_host_groups + @shared_context_metadata_behavior = value + else + raise ArgumentError, "Cannot set `RSpec.configuration." \ + "shared_context_metadata_behavior` to `#{value.inspect}`. Only " \ + "`:trigger_inclusion` and `:apply_to_host_groups` are valid values." + end + end + + # Record the start time of the spec suite to measure load time. + # return [Time] + add_setting :start_time + + # @macro add_setting + # Use threadsafe options where available. + # Currently this will place a mutex around memoized values such as let blocks. + # return [Boolean] + add_setting :threadsafe + + # @macro add_setting + # Maximum count of failed source lines to display in the failure reports. + # (default `10`). + # return [Integer] + add_setting :max_displayed_failure_line_count + + # Determines which bisect runner implementation gets used to run subsets + # of the suite during a bisection. Your choices are: + # + # - `:shell`: Performs a spec run by shelling out, booting RSpec and your + # application environment each time. This runner is the most widely + # compatible runner, but is not as fast. On platforms that do not + # support forking, this is the default. + # - `:fork`: Pre-boots RSpec and your application environment in a parent + # process, and then forks a child process for each spec run. This runner + # tends to be significantly faster than the `:shell` runner but cannot + # be used in some situations. On platforms that support forking, this + # is the default. If you use this runner, you should ensure that all + # of your one-time setup logic goes in a `before(:suite)` hook instead + # of getting run at the top-level of a file loaded by `--require`. + # + # @note This option will only be used by `--bisect` if you set it in a file + # loaded via `--require`. + # + # @return [Symbol] + attr_reader :bisect_runner + def bisect_runner=(value) + if @bisect_runner_class && value != @bisect_runner + raise "`config.bisect_runner = #{value.inspect}` can no longer take " \ + "effect as the #{@bisect_runner.inspect} bisect runnner is already " \ + "in use. This config setting must be set in a file loaded by a " \ + "`--require` option (passed at the CLI or in a `.rspec` file) for " \ + "it to have any effect." + end + + @bisect_runner = value + end + + # @private + # @deprecated Use {#color_mode} = :on, instead of {#color} with {#tty} + add_setting :tty + # @private + attr_writer :files_to_run + # @private + attr_accessor :filter_manager, :world + # @private + attr_accessor :static_config_filter_manager + # @private + attr_reader :backtrace_formatter, :ordering_manager, :loaded_spec_files + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + + # Build an object to store runtime configuration options and set defaults + def initialize + # rubocop:disable Style/GlobalVars + @start_time = $_rspec_core_load_started_at || ::RSpec::Core::Time.now + # rubocop:enable Style/GlobalVars + @expectation_frameworks = [] + @include_modules = FilterableItemRepository::QueryOptimized.new(:any?) + @extend_modules = FilterableItemRepository::QueryOptimized.new(:any?) + @prepend_modules = FilterableItemRepository::QueryOptimized.new(:any?) + + @bisect_runner = RSpec::Support::RubyFeatures.fork_supported? ? :fork : :shell + @bisect_runner_class = nil + + @before_suite_hooks = [] + @after_suite_hooks = [] + + @mock_framework = nil + @files_or_directories_to_run = [] + @loaded_spec_files = Set.new + @color = false + @color_mode = :automatic + @pattern = '**{,/*/**}/*_spec.rb' + @exclude_pattern = '' + @failure_exit_code = 1 + @error_exit_code = nil # so it can be overridden by failure exit code + @fail_if_no_examples = false + @spec_files_loaded = false + + @backtrace_formatter = BacktraceFormatter.new + + @default_path = 'spec' + @project_source_dirs = %w[ spec lib app ] + @deprecation_stream = $stderr + @output_stream = $stdout + @reporter = nil + @reporter_buffer = nil + @filter_manager = FilterManager.new + @static_config_filter_manager = FilterManager.new + @ordering_manager = Ordering::ConfigurationManager.new + @preferred_options = {} + @failure_color = :red + @success_color = :green + @pending_color = :yellow + @default_color = :white + @fixed_color = :blue + @detail_color = :cyan + @profile_examples = false + @requires = [] + @libs = [] + @derived_metadata_blocks = FilterableItemRepository::QueryOptimized.new(:any?) + @threadsafe = true + @max_displayed_failure_line_count = 10 + @world = World::Null + @shared_context_metadata_behavior = :trigger_inclusion + + define_built_in_hooks + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # @private + # + # Used to set higher priority option values from the command line. + def force(hash) + ordering_manager.force(hash) + @preferred_options.merge!(hash) + + return unless hash.key?(:example_status_persistence_file_path) + clear_values_derived_from_example_status_persistence_file_path + end + + # @private + def reset + @spec_files_loaded = false + reset_reporter + end + + # @private + def reset_reporter + @reporter = nil + @formatter_loader = nil + @output_wrapper = nil + end + + # @private + def reset_filters + self.filter_manager = FilterManager.new + filter_manager.include_only( + Metadata.deep_hash_dup(static_config_filter_manager.inclusions.rules) + ) + filter_manager.exclude_only( + Metadata.deep_hash_dup(static_config_filter_manager.exclusions.rules) + ) + end + + # @overload add_setting(name) + # @overload add_setting(name, opts) + # @option opts [Symbol] :default + # + # Set a default value for the generated getter and predicate methods: + # + # add_setting(:foo, :default => "default value") + # + # @option opts [Symbol] :alias_with + # + # Use `:alias_with` to alias the setter, getter, and predicate to + # another name, or names: + # + # add_setting(:foo, :alias_with => :bar) + # add_setting(:foo, :alias_with => [:bar, :baz]) + # + # Adds a custom setting to the RSpec.configuration object. + # + # RSpec.configuration.add_setting :foo + # + # Used internally and by extension frameworks like rspec-rails, so they + # can add config settings that are domain specific. For example: + # + # RSpec.configure do |c| + # c.add_setting :use_transactional_fixtures, + # :default => true, + # :alias_with => :use_transactional_examples + # end + # + # `add_setting` creates three methods on the configuration object, a + # setter, a getter, and a predicate: + # + # RSpec.configuration.foo=(value) + # RSpec.configuration.foo + # RSpec.configuration.foo? # Returns true if foo returns anything but nil or false. + def add_setting(name, opts={}) + default = opts.delete(:default) + (class << self; self; end).class_exec do + add_setting(name, opts) + end + __send__("#{name}=", default) if default + end + + # Returns the configured mock framework adapter module. + # @return [Symbol] + def mock_framework + if @mock_framework.nil? + begin + mock_with :rspec + rescue LoadError + mock_with :nothing + end + end + @mock_framework + end + + # Delegates to mock_framework=(framework). + def mock_framework=(framework) + mock_with framework + end + + # Regexps used to exclude lines from backtraces. + # + # Excludes lines from ruby (and jruby) source, installed gems, anything + # in any "bin" directory, and any of the RSpec libs (outside gem + # installs) by default. + # + # You can modify the list via the getter, or replace it with the setter. + # + # To override this behaviour and display a full backtrace, use + # `--backtrace` on the command line, in a `.rspec` file, or in the + # `rspec_options` attribute of RSpec's rake task. + # @return [Array] + def backtrace_exclusion_patterns + @backtrace_formatter.exclusion_patterns + end + + # Set regular expressions used to exclude lines in backtrace. + # @param patterns [Array] set backtrace_formatter exclusion_patterns + def backtrace_exclusion_patterns=(patterns) + @backtrace_formatter.exclusion_patterns = patterns + end + + # Regexps used to include lines in backtraces. + # + # Defaults to [Regexp.new Dir.getwd]. + # + # Lines that match an exclusion _and_ an inclusion pattern + # will be included. + # + # You can modify the list via the getter, or replace it with the setter. + # @return [Array] + def backtrace_inclusion_patterns + @backtrace_formatter.inclusion_patterns + end + + # Set regular expressions used to include lines in backtrace. + # @attr patterns [Array] set backtrace_formatter inclusion_patterns + def backtrace_inclusion_patterns=(patterns) + @backtrace_formatter.inclusion_patterns = patterns + end + + # Adds {#backtrace_exclusion_patterns} that will filter lines from + # the named gems from backtraces. + # + # @param gem_names [Array] Names of the gems to filter + # + # @example + # RSpec.configure do |config| + # config.filter_gems_from_backtrace "rack", "rake" + # end + # + # @note The patterns this adds will match the named gems in their common + # locations (e.g. system gems, vendored with bundler, installed as a + # :git dependency with bundler, etc) but is not guaranteed to work for + # all possible gem locations. For example, if you have the gem source + # in a directory with a completely unrelated name, and use bundler's + # :path option, this will not filter it. + def filter_gems_from_backtrace(*gem_names) + gem_names.each do |name| + @backtrace_formatter.filter_gem(name) + end + end + + # @private + MOCKING_ADAPTERS = { + :rspec => :RSpec, + :flexmock => :Flexmock, + :rr => :RR, + :mocha => :Mocha, + :nothing => :Null + } + + # Sets the mock framework adapter module. + # + # `framework` can be a Symbol or a Module. + # + # Given any of `:rspec`, `:mocha`, `:flexmock`, or `:rr`, configures the + # named framework. + # + # Given `:nothing`, configures no framework. Use this if you don't use + # any mocking framework to save a little bit of overhead. + # + # Given a Module, includes that module in every example group. The module + # should adhere to RSpec's mock framework adapter API: + # + # setup_mocks_for_rspec + # - called before each example + # + # verify_mocks_for_rspec + # - called after each example if the example hasn't yet failed. + # Framework should raise an exception when expectations fail + # + # teardown_mocks_for_rspec + # - called after verify_mocks_for_rspec (even if there are errors) + # + # If the module responds to `configuration` and `mock_with` receives a + # block, it will yield the configuration object to the block e.g. + # + # config.mock_with OtherMockFrameworkAdapter do |mod_config| + # mod_config.custom_setting = true + # end + def mock_with(framework) + framework_module = + if framework.is_a?(Module) + framework + else + const_name = MOCKING_ADAPTERS.fetch(framework) do + raise ArgumentError, + "Unknown mocking framework: #{framework.inspect}. " \ + "Pass a module or one of #{MOCKING_ADAPTERS.keys.inspect}" + end + + RSpec::Support.require_rspec_core "mocking_adapters/#{const_name.to_s.downcase}" + RSpec::Core::MockingAdapters.const_get(const_name) + end + + new_name, old_name = [framework_module, @mock_framework].map do |mod| + mod.respond_to?(:framework_name) ? mod.framework_name : :unnamed + end + + unless new_name == old_name + assert_no_example_groups_defined(:mock_framework) + end + + if block_given? + raise "#{framework_module} must respond to `configuration` so that " \ + "mock_with can yield it." unless framework_module.respond_to?(:configuration) + yield framework_module.configuration + end + + @mock_framework = framework_module + end + + # Returns the configured expectation framework adapter module(s) + def expectation_frameworks + if @expectation_frameworks.empty? + begin + expect_with :rspec + rescue LoadError + expect_with Module.new + end + end + @expectation_frameworks + end + + # Delegates to expect_with(framework). + def expectation_framework=(framework) + expect_with(framework) + end + + # Sets the expectation framework module(s) to be included in each example + # group. + # + # `frameworks` can be `:rspec`, `:test_unit`, `:minitest`, a custom + # module, or any combination thereof: + # + # config.expect_with :rspec + # config.expect_with :test_unit + # config.expect_with :minitest + # config.expect_with :rspec, :minitest + # config.expect_with OtherExpectationFramework + # + # RSpec will translate `:rspec`, `:minitest`, and `:test_unit` into the + # appropriate modules. + # + # ## Configuration + # + # If the module responds to `configuration`, `expect_with` will + # yield the `configuration` object if given a block: + # + # config.expect_with OtherExpectationFramework do |custom_config| + # custom_config.custom_setting = true + # end + def expect_with(*frameworks) + modules = frameworks.map do |framework| + case framework + when Module + framework + when :rspec + require 'rspec/expectations' + + # Tag this exception class so our exception formatting logic knows + # that it satisfies the `MultipleExceptionError` interface. + ::RSpec::Expectations::MultipleExpectationsNotMetError.__send__( + :include, MultipleExceptionError::InterfaceTag + ) + + ::RSpec::Matchers + when :test_unit + require 'rspec/core/test_unit_assertions_adapter' + ::RSpec::Core::TestUnitAssertionsAdapter + when :minitest + require 'rspec/core/minitest_assertions_adapter' + ::RSpec::Core::MinitestAssertionsAdapter + else + raise ArgumentError, "#{framework.inspect} is not supported" + end + end + + if (modules - @expectation_frameworks).any? + assert_no_example_groups_defined(:expect_with) + end + + if block_given? + raise "expect_with only accepts a block with a single argument. " \ + "Call expect_with #{modules.length} times, " \ + "once with each argument, instead." if modules.length > 1 + raise "#{modules.first} must respond to `configuration` so that " \ + "expect_with can yield it." unless modules.first.respond_to?(:configuration) + yield modules.first.configuration + end + + @expectation_frameworks.push(*modules) + end + + # Check if full backtrace is enabled. + # @return [Boolean] is full backtrace enabled + def full_backtrace? + @backtrace_formatter.full_backtrace? + end + + # Toggle full backtrace. + # @attr true_or_false [Boolean] toggle full backtrace display + def full_backtrace=(true_or_false) + @backtrace_formatter.full_backtrace = true_or_false + end + + # Enables color output if the output is a TTY. As of RSpec 3.6, this is + # the default behavior and this option is retained only for backwards + # compatibility. + # + # @deprecated No longer recommended because of complex behavior. Instead, + # rely on the fact that TTYs will display color by default, or set + # {#color_mode} to :on to display color on a non-TTY output. + # @see color_mode + # @see color_enabled? + # @return [Boolean] + def color + value_for(:color) { @color } + end + + # The mode for determining whether to display output in color. One of: + # + # - :automatic - the output will be in color if the output is a TTY (the + # default) + # - :on - the output will be in color, whether or not the output is a TTY + # - :off - the output will not be in color + # + # @see color_enabled? + # @return [Boolean] + def color_mode + value_for(:color_mode) { @color_mode } + end + + # Check if color is enabled for a particular output. + # @param output [IO] an output stream to use, defaults to the current + # `output_stream` + # @return [Boolean] + def color_enabled?(output=output_stream) + case color_mode + when :on then true + when :off then false + else # automatic + output_to_tty?(output) || (color && tty?) + end + end + + # Set the color mode. + attr_writer :color_mode + + # Toggle output color. + # + # @deprecated No longer recommended because of complex behavior. Instead, + # rely on the fact that TTYs will display color by default, or set + # {:color_mode} to :on to display color on a non-TTY output. + attr_writer :color + + # @private + def libs=(libs) + libs.map do |lib| + @libs.unshift lib + $LOAD_PATH.unshift lib + end + end + + # Run examples matching on `description` in all files to run. + # @param description [String, Regexp] the pattern to filter on + def full_description=(description) + filter_run :full_description => Regexp.union(*Array(description).map { |d| Regexp.new(d) }) + end + + # @return [Array] full description filter + def full_description + filter.fetch :full_description, nil + end + + # @overload add_formatter(formatter) + # @overload add_formatter(formatter, output) + # + # @param formatter [Class, String, Object] formatter to use. Can be any of the + # string values supported from the CLI (`p`/`progress`, + # `d`/`doc`/`documentation`, `h`/`html`, or `j`/`json`), any + # class that implements the formatter protocol and has registered + # itself with RSpec as a formatter, or a formatter instance. + # @param output [String, IO] where the formatter will write its output. + # Can be an IO object or a string path to a file. If not provided, + # the configured `output_stream` (`$stdout`, by default) will be used. + # + # Adds a formatter to the set RSpec will use for this run. + # + # @see RSpec::Core::Formatters::Protocol + def add_formatter(formatter, output=output_wrapper) + formatter_loader.add(formatter, output) + end + alias_method :formatter=, :add_formatter + + # The formatter that will be used if no formatter has been set. + # Defaults to 'progress'. + def default_formatter + formatter_loader.default_formatter + end + + # Sets a fallback formatter to use if none other has been set. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.default_formatter = 'doc' + # end + def default_formatter=(value) + formatter_loader.default_formatter = value + end + + # Returns a duplicate of the formatters currently loaded in + # the `FormatterLoader` for introspection. + # + # Note as this is a duplicate, any mutations will be disregarded. + # + # @return [Array] the formatters currently loaded + def formatters + formatter_loader.formatters.dup + end + + # @private + def formatter_loader + @formatter_loader ||= Formatters::Loader.new(Reporter.new(self)) + end + + # @private + # + # This buffer is used to capture all messages sent to the reporter during + # reporter initialization. It can then replay those messages after the + # formatter is correctly initialized. Otherwise, deprecation warnings + # during formatter initialization can cause an infinite loop. + class DeprecationReporterBuffer + def initialize + @calls = [] + end + + def deprecation(*args) + @calls << args + end + + def play_onto(reporter) + @calls.each do |args| + reporter.deprecation(*args) + end + end + end + + # @return [RSpec::Core::Reporter] the currently configured reporter + def reporter + # @reporter_buffer should only ever be set in this method to cover + # initialization of @reporter. + @reporter_buffer || @reporter ||= + begin + @reporter_buffer = DeprecationReporterBuffer.new + formatter_loader.prepare_default output_wrapper, deprecation_stream + @reporter_buffer.play_onto(formatter_loader.reporter) + @reporter_buffer = nil + formatter_loader.reporter + end + end + + # @api private + # + # Defaults `profile_examples` to 10 examples when `@profile_examples` is + # `true`. + def profile_examples + profile = value_for(:profile_examples) { @profile_examples } + if profile && !profile.is_a?(Integer) + 10 + else + profile + end + end + + # @private + def files_or_directories_to_run=(*files) + files = files.flatten + + if (command == 'rspec' || Runner.running_in_drb?) && default_path && files.empty? + files << default_path + end + + @files_or_directories_to_run = files + @files_to_run = nil + end + + # The spec files RSpec will run. + # @return [Array] specified files about to run + def files_to_run + @files_to_run ||= get_files_to_run(@files_or_directories_to_run) + end + + # @private + def last_run_statuses + @last_run_statuses ||= Hash.new(UNKNOWN_STATUS).tap do |statuses| + if (path = example_status_persistence_file_path) + begin + ExampleStatusPersister.load_from(path).inject(statuses) do |hash, example| + status = example[:status] + status = UNKNOWN_STATUS unless VALID_STATUSES.include?(status) + hash[example.fetch(:example_id)] = status + hash + end + rescue SystemCallError => e + RSpec.warning "Could not read from #{path.inspect} (configured as " \ + "`config.example_status_persistence_file_path`) due " \ + "to a system error: #{e.inspect}. Please check that " \ + "the config option is set to an accessible, valid " \ + "file path", :call_site => nil + end + end + end + end + + # @private + UNKNOWN_STATUS = "unknown".freeze + + # @private + FAILED_STATUS = "failed".freeze + + # @private + PASSED_STATUS = "passed".freeze + + # @private + PENDING_STATUS = "pending".freeze + + # @private + VALID_STATUSES = [UNKNOWN_STATUS, FAILED_STATUS, PASSED_STATUS, PENDING_STATUS] + + # @private + def spec_files_with_failures + @spec_files_with_failures ||= last_run_statuses.inject(Set.new) do |files, (id, status)| + files << Example.parse_id(id).first if status == FAILED_STATUS + files + end.to_a + end + + # Creates a method that delegates to `example` including the submitted + # `args`. Used internally to add variants of `example` like `pending`: + # @param name [String] example name alias + # @param args [Array, Hash] metadata for the generated example + # + # @note The specific example alias below (`pending`) is already + # defined for you. + # @note Use with caution. This extends the language used in your + # specs, but does not add any additional documentation. We use this + # in RSpec to define methods like `focus` and `xit`, but we also add + # docs for those methods. + # + # @example + # RSpec.configure do |config| + # config.alias_example_to :pending, :pending => true + # end + # + # # This lets you do this: + # + # RSpec.describe Thing do + # pending "does something" do + # thing = Thing.new + # end + # end + # + # # ... which is the equivalent of + # + # RSpec.describe Thing do + # it "does something", :pending => true do + # thing = Thing.new + # end + # end + def alias_example_to(name, *args) + extra_options = Metadata.build_hash_from(args) + RSpec::Core::ExampleGroup.define_example_method(name, extra_options) + end + + # Creates a method that defines an example group with the provided + # metadata. Can be used to define example group/metadata shortcuts. + # + # @example + # RSpec.configure do |config| + # config.alias_example_group_to :describe_model, :type => :model + # end + # + # shared_context_for "model tests", :type => :model do + # # define common model test helper methods, `let` declarations, etc + # end + # + # # This lets you do this: + # + # RSpec.describe_model User do + # end + # + # # ... which is the equivalent of + # + # RSpec.describe User, :type => :model do + # end + # + # @note The defined aliased will also be added to the top level + # (e.g. `main` and from within modules) if + # `expose_dsl_globally` is set to true. + # @see #alias_example_to + # @see #expose_dsl_globally= + def alias_example_group_to(new_name, *args) + extra_options = Metadata.build_hash_from(args) + RSpec::Core::ExampleGroup.define_example_group_method(new_name, extra_options) + end + + # Define an alias for it_should_behave_like that allows different + # language (like "it_has_behavior" or "it_behaves_like") to be + # employed when including shared examples. + # + # @example + # RSpec.configure do |config| + # config.alias_it_behaves_like_to(:it_has_behavior, 'has behavior:') + # end + # + # # allows the user to include a shared example group like: + # + # RSpec.describe Entity do + # it_has_behavior 'sortability' do + # let(:sortable) { Entity.new } + # end + # end + # + # # which is reported in the output as: + # # Entity + # # has behavior: sortability + # # ...sortability examples here + # + # @note Use with caution. This extends the language used in your + # specs, but does not add any additional documentation. We use this + # in RSpec to define `it_should_behave_like` (for backward + # compatibility), but we also add docs for that method. + def alias_it_behaves_like_to(new_name, report_label='') + RSpec::Core::ExampleGroup.define_nested_shared_group_method(new_name, report_label) + end + alias_method :alias_it_should_behave_like_to, :alias_it_behaves_like_to + + # Adds key/value pairs to the `inclusion_filter`. If `args` + # includes any symbols that are not part of the hash, each symbol + # is treated as a key in the hash with the value `true`. + # + # ### Note + # + # Filters set using this method can be overridden from the command line + # or config files (e.g. `.rspec`). + # + # @example + # # Given this declaration. + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # Any of the following will include that group. + # config.filter_run_including :foo => 'bar' + # config.filter_run_including :foo => /^ba/ + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # Given a proc with an arity of 1, the lambda is passed the value + # # related to the key, e.g. + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # + # # Given a proc with an arity of 2, the lambda is passed the value + # # related to the key, and the metadata itself e.g. + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # filter_run_including :foo # same as filter_run_including :foo => true + def filter_run_including(*args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + filter_manager.include_with_low_priority meta + static_config_filter_manager.include_with_low_priority Metadata.deep_hash_dup(meta) + end + alias_method :filter_run, :filter_run_including + + # Applies the provided filter only if any of examples match, in constrast + # to {#filter_run}, which always applies even if no examples match, in + # which case no examples will be run. This allows you to leave configured + # filters in place that are intended only for temporary use. The most common + # example is focus filtering: `config.filter_run_when_matching :focus`. + # With that configured, you can temporarily focus an example or group + # by tagging it with `:focus` metadata, or prefixing it with an `f` + # (as in `fdescribe`, `fcontext` and `fit`) since those are aliases for + # `describe`/`context`/`it` with `:focus` metadata. + def filter_run_when_matching(*args) + when_first_matching_example_defined(*args) do + filter_run(*args) + end + end + + # Clears and reassigns the `inclusion_filter`. Set to `nil` if you don't + # want any inclusion filter at all. + # + # ### Warning + # + # This overrides any inclusion filters/tags set on the command line or in + # configuration files. + def inclusion_filter=(filter) + meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + filter_manager.include_only meta + end + + alias_method :filter=, :inclusion_filter= + + # Returns the `inclusion_filter`. If none has been set, returns an empty + # hash. + def inclusion_filter + filter_manager.inclusions + end + + alias_method :filter, :inclusion_filter + + # Adds key/value pairs to the `exclusion_filter`. If `args` + # includes any symbols that are not part of the hash, each symbol + # is treated as a key in the hash with the value `true`. + # + # ### Note + # + # Filters set using this method can be overridden from the command line + # or config files (e.g. `.rspec`). + # + # @example + # # Given this declaration. + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # Any of the following will exclude that group. + # config.filter_run_excluding :foo => 'bar' + # config.filter_run_excluding :foo => /^ba/ + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # Given a proc with an arity of 1, the lambda is passed the value + # # related to the key, e.g. + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # + # # Given a proc with an arity of 2, the lambda is passed the value + # # related to the key, and the metadata itself e.g. + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # filter_run_excluding :foo # same as filter_run_excluding :foo => true + def filter_run_excluding(*args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + filter_manager.exclude_with_low_priority meta + static_config_filter_manager.exclude_with_low_priority Metadata.deep_hash_dup(meta) + end + + # Clears and reassigns the `exclusion_filter`. Set to `nil` if you don't + # want any exclusion filter at all. + # + # ### Warning + # + # This overrides any exclusion filters/tags set on the command line or in + # configuration files. + def exclusion_filter=(filter) + meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + filter_manager.exclude_only meta + end + + # Returns the `exclusion_filter`. If none has been set, returns an empty + # hash. + def exclusion_filter + filter_manager.exclusions + end + + # Tells RSpec to include `mod` in example groups. Methods defined in + # `mod` are exposed to examples (not example groups). Use `filters` to + # constrain the groups or examples in which to include the module. + # + # @example + # + # module AuthenticationHelpers + # def login_as(user) + # # ... + # end + # end + # + # module PreferencesHelpers + # def preferences(user, preferences = {}) + # # ... + # end + # end + # + # module UserHelpers + # def users(username) + # # ... + # end + # end + # + # RSpec.configure do |config| + # config.include(UserHelpers) # included in all groups + # + # # included in examples with `:preferences` metadata + # config.include(PreferenceHelpers, :preferences) + # + # # included in examples with `:type => :request` metadata + # config.include(AuthenticationHelpers, :type => :request) + # end + # + # describe "edit profile", :preferences, :type => :request do + # it "can be viewed by owning user" do + # login_as preferences(users(:jdoe), :lang => 'es') + # get "/profiles/jdoe" + # assert_select ".username", :text => 'jdoe' + # end + # end + # + # @note Filtered module inclusions can also be applied to + # individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # @see #include_context + # @see #extend + # @see #prepend + def include(mod, *filters) + define_mixed_in_module(mod, filters, @include_modules, :include) do |group| + safe_include(mod, group) + end + end + + # Tells RSpec to include the named shared example group in example groups. + # Use `filters` to constrain the groups or examples in which to include + # the example group. + # + # @example + # + # RSpec.shared_context "example admin user" do + # let(:admin_user) { create_user(:admin) } + # end + # + # RSpec.shared_context "example guest user" do + # let(:guest_user) { create_user(:guest) } + # end + # + # RSpec.configure do |config| + # config.include_context "example guest user", :type => :request + # config.include_context "example admin user", :admin, :type => :request + # end + # + # RSpec.describe "The admin page", :type => :request do + # it "can be viewed by admins", :admin do + # login_with admin_user + # get "/admin" + # expect(response).to be_ok + # end + # + # it "cannot be viewed by guests" do + # login_with guest_user + # get "/admin" + # expect(response).to be_forbidden + # end + # end + # + # @note Filtered context inclusions can also be applied to + # individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # @see #include + def include_context(shared_group_name, *filters) + shared_module = world.shared_example_group_registry.find([:main], shared_group_name) + include shared_module, *filters + end + + # Tells RSpec to extend example groups with `mod`. Methods defined in + # `mod` are exposed to example groups (not examples). Use `filters` to + # constrain the groups to extend. + # + # Similar to `include`, but behavior is added to example groups, which + # are classes, rather than the examples, which are instances of those + # classes. + # + # @example + # + # module UiHelpers + # def run_in_browser + # # ... + # end + # end + # + # module PermissionHelpers + # def define_permissions + # # ... + # end + # end + # + # RSpec.configure do |config| + # config.extend(UiHelpers, :type => :request) + # config.extend(PermissionHelpers, :with_permissions, :type => :request) + # end + # + # describe "edit profile", :with_permissions, :type => :request do + # run_in_browser + # define_permissions + # + # it "does stuff in the client" do + # # ... + # end + # end + # + # @see #include + # @see #prepend + def extend(mod, *filters) + define_mixed_in_module(mod, filters, @extend_modules, :extend) do |group| + safe_extend(mod, group) + end + end + + if RSpec::Support::RubyFeatures.module_prepends_supported? + # Tells RSpec to prepend example groups with `mod`. Methods defined in + # `mod` are exposed to examples (not example groups). Use `filters` to + # constrain the groups in which to prepend the module. + # + # Similar to `include`, but module is included before the example group's class + # in the ancestor chain. + # + # @example + # + # module OverrideMod + # def override_me + # "overridden" + # end + # end + # + # RSpec.configure do |config| + # config.prepend(OverrideMod, :method => :prepend) + # end + # + # describe "overriding example's class", :method => :prepend do + # it "finds the user" do + # self.class.class_eval do + # def override_me + # end + # end + # override_me # => "overridden" + # # ... + # end + # end + # + # @see #include + # @see #extend + def prepend(mod, *filters) + define_mixed_in_module(mod, filters, @prepend_modules, :prepend) do |group| + safe_prepend(mod, group) + end + end + end + + # @private + # + # Used internally to extend a group with modules using `include`, `prepend` and/or + # `extend`. + def configure_group(group) + group.hooks.register_globals(group, hooks) + + configure_group_with group, @include_modules, :safe_include + configure_group_with group, @extend_modules, :safe_extend + configure_group_with group, @prepend_modules, :safe_prepend + end + + # @private + # + # Used internally to extend the singleton class of a single example's + # example group instance with modules using `include` and/or `extend`. + def configure_example(example, example_hooks) + example_hooks.register_global_singleton_context_hooks(example, hooks) + singleton_group = example.example_group_instance.singleton_class + + # We replace the metadata so that SharedExampleGroupModule#included + # has access to the example's metadata[:location]. + singleton_group.with_replaced_metadata(example.metadata) do + modules = @include_modules.items_for(example.metadata) + modules.each do |mod| + safe_include(mod, example.example_group_instance.singleton_class) + end + + MemoizedHelpers.define_helpers_on(singleton_group) unless modules.empty? + end + end + + # @private + def requires=(paths) + directories = ['lib', default_path].select { |p| File.directory? p } + RSpec::Core::RubyProject.add_to_load_path(*directories) + paths.each { |path| load_file_handling_errors(:require, path) } + @requires += paths + end + + # @private + def in_project_source_dir_regex + regexes = project_source_dirs.map do |dir| + /\A#{Regexp.escape(File.expand_path(dir))}\// + end + + Regexp.union(regexes) + end + + # @private + def configure_mock_framework + RSpec::Core::ExampleGroup.__send__(:include, mock_framework) + conditionally_disable_mocks_monkey_patching + end + + # @private + def configure_expectation_framework + expectation_frameworks.each do |framework| + RSpec::Core::ExampleGroup.__send__(:include, framework) + end + conditionally_disable_expectations_monkey_patching + end + + # @private + def load_spec_files + # Note which spec files world is already aware of. + # This is generally only needed for when the user runs + # `ruby path/to/spec.rb` (and loads `rspec/autorun`) -- + # in that case, the spec file was loaded by `ruby` and + # isn't loaded by us here so we only know about it because + # of an example group being registered in it. + world.registered_example_group_files.each do |f| + loaded_spec_files << f # the registered files are already expended absolute paths + end + + files_to_run.uniq.each do |f| + file = File.expand_path(f) + load_file_handling_errors(:load, file) + loaded_spec_files << file + end + + @spec_files_loaded = true + end + + # @private + DEFAULT_FORMATTER = lambda { |string| string } + + # Formats the docstring output using the block provided. + # + # @example + # # This will strip the descriptions of both examples and example + # # groups. + # RSpec.configure do |config| + # config.format_docstrings { |s| s.strip } + # end + def format_docstrings(&block) + @format_docstrings_block = block_given? ? block : DEFAULT_FORMATTER + end + + # @private + def format_docstrings_block + @format_docstrings_block ||= DEFAULT_FORMATTER + end + + # @private + def self.delegate_to_ordering_manager(*methods) + methods.each do |method| + define_method method do |*args, &block| + ordering_manager.__send__(method, *args, &block) + end + end + end + + # @!method seed=(value) + # + # Sets the seed value and sets the default global ordering to random. + delegate_to_ordering_manager :seed= + + # @!method seed + # Seed for random ordering (default: generated randomly each run). + # + # When you run specs with `--order random`, RSpec generates a random seed + # for the randomization and prints it to the `output_stream` (assuming + # you're using RSpec's built-in formatters). If you discover an ordering + # dependency (i.e. examples fail intermittently depending on order), set + # this (on Configuration or on the command line with `--seed`) to run + # using the same seed while you debug the issue. + # + # We recommend, actually, that you use the command line approach so you + # don't accidentally leave the seed encoded. + delegate_to_ordering_manager :seed + + # @!method order=(value) + # + # Sets the default global ordering strategy. By default this can be one + # of `:defined`, `:random`, but is customizable through the + # `register_ordering` API. If order is set to `'rand:'`, + # the seed will also be set. + # + # @see #register_ordering + delegate_to_ordering_manager :order= + + # @!method register_ordering(name) + # + # Registers a named ordering strategy that can later be + # used to order an example group's subgroups by adding + # `:order => ` metadata to the example group. + # + # @param name [Symbol] The name of the ordering. + # @yield Block that will order the given examples or example groups + # @yieldparam list [Array, + # Array] The examples or groups to order + # @yieldreturn [Array, + # Array] The re-ordered examples or groups + # + # @example + # RSpec.configure do |rspec| + # rspec.register_ordering :reverse do |list| + # list.reverse + # end + # end + # + # RSpec.describe 'MyClass', :order => :reverse do + # # ... + # end + # + # @note Pass the symbol `:global` to set the ordering strategy that + # will be used to order the top-level example groups and any example + # groups that do not have declared `:order` metadata. + # + # @example + # RSpec.configure do |rspec| + # rspec.register_ordering :global do |examples| + # acceptance, other = examples.partition do |example| + # example.metadata[:type] == :acceptance + # end + # other + acceptance + # end + # end + # + # RSpec.describe 'MyClass', :type => :acceptance do + # # will run last + # end + # + # RSpec.describe 'MyClass' do + # # will run first + # end + # + delegate_to_ordering_manager :register_ordering + + # @private + delegate_to_ordering_manager :seed_used?, :ordering_registry + + # Set Ruby warnings on or off. + def warnings=(value) + $VERBOSE = !!value + end + + # @return [Boolean] Whether or not ruby warnings are enabled. + def warnings? + $VERBOSE + end + + # @private + RAISE_ERROR_WARNING_NOTIFIER = lambda { |message| raise message } + + # Turns warnings into errors. This can be useful when + # you want RSpec to run in a 'strict' no warning situation. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.raise_on_warning = true + # end + def raise_on_warning=(value) + if value + RSpec::Support.warning_notifier = RAISE_ERROR_WARNING_NOTIFIER + else + RSpec::Support.warning_notifier = RSpec::Support::DEFAULT_WARNING_NOTIFIER + end + end + + # Exposes the current running example via the named + # helper method. RSpec 2.x exposed this via `example`, + # but in RSpec 3.0, the example is instead exposed via + # an arg yielded to `it`, `before`, `let`, etc. However, + # some extension gems (such as Capybara) depend on the + # RSpec 2.x's `example` method, so this config option + # can be used to maintain compatibility. + # + # @param method_name [Symbol] the name of the helper method + # + # @example + # + # RSpec.configure do |rspec| + # rspec.expose_current_running_example_as :example + # end + # + # RSpec.describe MyClass do + # before do + # # `example` can be used here because of the above config. + # do_something if example.metadata[:type] == "foo" + # end + # end + def expose_current_running_example_as(method_name) + ExposeCurrentExample.module_exec do + extend RSpec::SharedContext + let(method_name) { |ex| ex } + end + + include ExposeCurrentExample + end + + # @private + module ExposeCurrentExample; end + + # Turns deprecation warnings into errors, in order to surface + # the full backtrace of the call site. This can be useful when + # you need more context to address a deprecation than the + # single-line call site normally provided. + # + # @example + # + # RSpec.configure do |rspec| + # rspec.raise_errors_for_deprecations! + # end + def raise_errors_for_deprecations! + self.deprecation_stream = Formatters::DeprecationFormatter::RaiseErrorStream.new + end + + # Enables zero monkey patching mode for RSpec. It removes monkey + # patching of the top-level DSL methods (`describe`, + # `shared_examples_for`, etc) onto `main` and `Module`, instead + # requiring you to prefix these methods with `RSpec.`. It enables + # expect-only syntax for rspec-mocks and rspec-expectations. It + # simply disables monkey patching on whatever pieces of RSpec + # the user is using. + # + # @note It configures rspec-mocks and rspec-expectations only + # if the user is using those (either explicitly or implicitly + # by not setting `mock_with` or `expect_with` to anything else). + # + # @note If the user uses this options with `mock_with :mocha` + # (or similar) they will still have monkey patching active + # in their test environment from mocha. + # + # @example + # + # # It disables all monkey patching. + # RSpec.configure do |config| + # config.disable_monkey_patching! + # end + # + # # Is an equivalent to + # RSpec.configure do |config| + # config.expose_dsl_globally = false + # + # config.mock_with :rspec do |mocks| + # mocks.syntax = :expect + # mocks.patch_marshal_to_support_partial_doubles = false + # end + # + # config.expect_with :rspec do |expectations| + # expectations.syntax = :expect + # end + # end + def disable_monkey_patching! + self.expose_dsl_globally = false + self.disable_monkey_patching = true + conditionally_disable_mocks_monkey_patching + conditionally_disable_expectations_monkey_patching + end + + # @private + attr_accessor :disable_monkey_patching + + # Defines a callback that can assign derived metadata values. + # + # @param filters [Array, Hash] metadata filters that determine + # which example or group metadata hashes the callback will be triggered + # for. If none are given, the callback will be run against the metadata + # hashes of all groups and examples. + # @yieldparam metadata [Hash] original metadata hash from an example or + # group. Mutate this in your block as needed. + # + # @example + # RSpec.configure do |config| + # # Tag all groups and examples in the spec/unit directory with + # # :type => :unit + # config.define_derived_metadata(:file_path => %r{/spec/unit/}) do |metadata| + # metadata[:type] = :unit + # end + # end + def define_derived_metadata(*filters, &block) + meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + @derived_metadata_blocks.append(block, meta) + end + + # Defines a callback that runs after the first example with matching + # metadata is defined. If no examples are defined with matching metadata, + # it will not get called at all. + # + # This can be used to ensure some setup is performed (such as bootstrapping + # a DB or loading a specific file that adds significantly to the boot time) + # if needed (as indicated by the presence of an example with matching metadata) + # but avoided otherwise. + # + # @example + # RSpec.configure do |config| + # config.when_first_matching_example_defined(:db) do + # # Load a support file that does some heavyweight setup, + # # including bootstrapping the DB, but only if we have loaded + # # any examples tagged with `:db`. + # require 'support/db' + # end + # end + def when_first_matching_example_defined(*filters) + specified_meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + + callback = lambda do |example_or_group_meta| + # Example groups do not have `:example_group` metadata + # (instead they have `:parent_example_group` metadata). + return unless example_or_group_meta.key?(:example_group) + + # Ensure the callback only fires once. + @derived_metadata_blocks.delete(callback, specified_meta) + + yield + end + + @derived_metadata_blocks.append(callback, specified_meta) + end + + # @private + def apply_derived_metadata_to(metadata) + already_run_blocks = Set.new + + # We loop and attempt to re-apply metadata blocks to support cascades + # (e.g. where a derived bit of metadata triggers the application of + # another piece of derived metadata, etc) + # + # We limit our looping to 200 times as a way to detect infinitely recursing derived metadata blocks. + # It's hard to imagine a valid use case for a derived metadata cascade greater than 200 iterations. + 200.times do + return if @derived_metadata_blocks.items_for(metadata).all? do |block| + already_run_blocks.include?(block).tap do |skip_block| + block.call(metadata) unless skip_block + already_run_blocks << block + end + end + end + + # If we got here, then `@derived_metadata_blocks.items_for(metadata).all?` never returned + # `true` above and we treat this as an attempt to recurse infinitely. It's better to fail + # with a clear # error than hang indefinitely, which is what would happen if we didn't limit + # the looping above. + raise SystemStackError, "Attempted to recursively derive metadata indefinitely." + end + + # Defines a `before` hook. See {Hooks#before} for full docs. + # + # This method differs from {Hooks#before} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once before + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #prepend_before + # @see #after + # @see #append_after + def before(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @before_suite_hooks << Hooks::BeforeHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.before(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + alias_method :append_before, :before + + # Adds `block` to the start of the list of `before` blocks in the same + # scope (`:example`, `:context`, or `:suite`), in contrast to {#before}, + # which adds the hook to the end of the list. + # + # See {Hooks#before} for full `before` hook docs. + # + # This method differs from {Hooks#prepend_before} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once before + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #before + # @see #after + # @see #append_after + def prepend_before(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @before_suite_hooks.unshift Hooks::BeforeHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.prepend_before(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + + # Defines a `after` hook. See {Hooks#after} for full docs. + # + # This method differs from {Hooks#after} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once after + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #append_after + # @see #before + # @see #prepend_before + def after(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @after_suite_hooks.unshift Hooks::AfterHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.after(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + alias_method :prepend_after, :after + + # Adds `block` to the end of the list of `after` blocks in the same + # scope (`:example`, `:context`, or `:suite`), in contrast to {#after}, + # which adds the hook to the start of the list. + # + # See {Hooks#after} for full `after` hook docs. + # + # This method differs from {Hooks#append_after} in only one way: it supports + # the `:suite` scope. Hooks with the `:suite` scope will be run once after + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. + # + # @see #append_after + # @see #before + # @see #prepend_before + def append_after(scope=nil, *meta, &block) + handle_suite_hook(scope, meta) do + @after_suite_hooks << Hooks::AfterHook.new(block, {}) + end || begin + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.append_after(scope, *meta, &block) } + super(scope, *meta, &block) + end + end + + # Registers `block` as an `around` hook. + # + # See {Hooks#around} for full `around` hook docs. + def around(scope=nil, *meta, &block) + # defeat Ruby 2.5 lazy proc allocation to ensure + # the methods below are passed the same proc instances + # so `Hook` equality is preserved. For more info, see: + # https://bugs.ruby-lang.org/issues/14045#note-5 + block.__id__ + + add_hook_to_existing_matching_groups(meta, scope) { |g| g.around(scope, *meta, &block) } + super(scope, *meta, &block) + end + + # @private + def with_suite_hooks + return yield if dry_run? + + begin + RSpec.current_scope = :before_suite_hook + run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) + yield + ensure + RSpec.current_scope = :after_suite_hook + run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) + RSpec.current_scope = :suite + end + end + + # @private + # Holds the various registered hooks. Here we use a FilterableItemRepository + # implementation that is specifically optimized for the read/write patterns + # of the config object. + def hooks + @hooks ||= HookCollections.new(self, FilterableItemRepository::QueryOptimized) + end + + # Invokes block before defining an example group + def on_example_group_definition(&block) + on_example_group_definition_callbacks << block + end + + # @api private + # Returns an array of blocks to call before defining an example group + def on_example_group_definition_callbacks + @on_example_group_definition_callbacks ||= [] + end + + # @private + def bisect_runner_class + @bisect_runner_class ||= begin + case bisect_runner + when :fork + RSpec::Support.require_rspec_core 'bisect/fork_runner' + Bisect::ForkRunner + when :shell + RSpec::Support.require_rspec_core 'bisect/shell_runner' + Bisect::ShellRunner + else + raise "Unsupported value for `bisect_runner` (#{bisect_runner.inspect}). " \ + "Only `:fork` and `:shell` are supported." + end + end + end + + private + + def load_file_handling_errors(method, file) + __send__(method, file) + rescue LoadError => ex + relative_file = Metadata.relative_path(file) + suggestions = DidYouMean.new(relative_file).call + reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.#{suggestions}") + RSpec.world.wants_to_quit = true + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + relative_file = Metadata.relative_path(file) + reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.") + RSpec.world.wants_to_quit = true + rescue SystemExit => ex + relative_file = Metadata.relative_path(file) + reporter.notify_non_example_exception( + ex, + "While loading #{relative_file} an `exit` / `raise SystemExit` occurred, RSpec will now quit." + ) + RSpec.world.rspec_is_quitting = true + raise ex + end + + def handle_suite_hook(scope, meta) + return nil unless scope == :suite + + unless meta.empty? + # TODO: in RSpec 4, consider raising an error here. + # We warn only for backwards compatibility. + RSpec.warn_with "WARNING: `:suite` hooks do not support metadata since " \ + "they apply to the suite as a whole rather than " \ + "any individual example or example group that has metadata. " \ + "The metadata you have provided (#{meta.inspect}) will be ignored." + end + + yield + end + + def run_suite_hooks(hook_description, hooks) + context = SuiteHookContext.new(hook_description, reporter) + + hooks.each do |hook| + begin + hook.run(context) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + context.set_exception(ex) + + # Do not run subsequent `before` hooks if one fails. + # But for `after` hooks, we run them all so that all + # cleanup bits get a chance to complete, minimizing the + # chance that resources get left behind. + break if hooks.equal?(@before_suite_hooks) + end + end + end + + def get_files_to_run(paths) + files = FlatMap.flat_map(paths_to_check(paths)) do |path| + path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + File.directory?(path) ? gather_directories(path) : extract_location(path) + end.uniq + + return files unless only_failures? + relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) } + intersection = (relative_files & spec_files_with_failures.to_a) + intersection.empty? ? files : intersection + end + + def paths_to_check(paths) + return paths if pattern_might_load_specs_from_vendored_dirs? + paths + [Dir.getwd] + end + + def pattern_might_load_specs_from_vendored_dirs? + pattern.split(File::SEPARATOR).first.include?('**') + end + + def gather_directories(path) + include_files = get_matching_files(path, pattern) + exclude_files = get_matching_files(path, exclude_pattern) + (include_files - exclude_files).uniq + end + + def get_matching_files(path, pattern) + raw_files = Dir[file_glob_from(path, pattern)] + raw_files.map { |file| File.expand_path(file) }.sort + end + + def file_glob_from(path, pattern) + stripped = "{#{pattern.gsub(/\s*,\s*/, ',')}}" + return stripped if pattern =~ /^(\.\/)?#{Regexp.escape path}/ || absolute_pattern?(pattern) + File.join(path, stripped) + end + + if RSpec::Support::OS.windows? + # :nocov: + def absolute_pattern?(pattern) + pattern =~ /\A[A-Z]:\\/ || windows_absolute_network_path?(pattern) + end + + def windows_absolute_network_path?(pattern) + return false unless ::File::ALT_SEPARATOR + pattern.start_with?(::File::ALT_SEPARATOR + ::File::ALT_SEPARATOR) + end + # :nocov: + else + def absolute_pattern?(pattern) + pattern.start_with?(File::Separator) + end + end + + def extract_location(path) + match = /^(.*?)((?:\:\d+)+)$/.match(path) + + if match + captures = match.captures + path = captures[0] + lines = captures[1][1..-1].split(":").map(&:to_i) + filter_manager.add_location path, lines + else + path, scoped_ids = Example.parse_id(path) + filter_manager.add_ids(path, scoped_ids.split(/\s*,\s*/)) if scoped_ids + end + + return [] if path == default_path + File.expand_path(path) + end + + def command + $0.split(File::SEPARATOR).last + end + + def value_for(key) + @preferred_options.fetch(key) { yield } + end + + def define_built_in_hooks + around(:example, :aggregate_failures => true) do |procsy| + begin + aggregate_failures(nil, :hide_backtrace => true, &procsy) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception + procsy.example.set_aggregate_failures_exception(exception) + end + end + end + + def assert_no_example_groups_defined(config_option) + return unless world.example_groups.any? + + raise MustBeConfiguredBeforeExampleGroupsError.new( + "RSpec's #{config_option} configuration option must be configured before " \ + "any example groups are defined, but you have already defined a group." + ) + end + + def output_wrapper + @output_wrapper ||= OutputWrapper.new(output_stream) + end + + def output_to_tty?(output=output_stream) + output.respond_to?(:tty?) && output.tty? + end + + def conditionally_disable_mocks_monkey_patching + return unless disable_monkey_patching && rspec_mocks_loaded? + + RSpec::Mocks.configuration.tap do |config| + config.syntax = :expect + config.patch_marshal_to_support_partial_doubles = false + end + end + + def conditionally_disable_expectations_monkey_patching + return unless disable_monkey_patching && rspec_expectations_loaded? + + RSpec::Expectations.configuration.syntax = :expect + end + + def rspec_mocks_loaded? + defined?(RSpec::Mocks.configuration) + end + + def rspec_expectations_loaded? + defined?(RSpec::Expectations.configuration) + end + + def update_pattern_attr(name, value) + if @spec_files_loaded + RSpec.warning "Configuring `#{name}` to #{value} has no effect since " \ + "RSpec has already loaded the spec files." + end + + instance_variable_set(:"@#{name}", value) + @files_to_run = nil + end + + def clear_values_derived_from_example_status_persistence_file_path + @last_run_statuses = nil + @spec_files_with_failures = nil + end + + def configure_group_with(group, module_list, application_method) + module_list.items_for(group.metadata).each do |mod| + __send__(application_method, mod, group) + end + end + + def add_hook_to_existing_matching_groups(meta, scope, &block) + # For example hooks, we have to apply it to each of the top level + # groups, even if the groups do not match. When we apply it, we + # apply it with the metadata, so it will only apply to examples + # in the group that match the metadata. + # #2280 for background and discussion. + if scope == :example || scope == :each || scope.nil? + world.example_groups.each(&block) + else + meta = Metadata.build_hash_from(meta.dup) + on_existing_matching_groups(meta, &block) + end + end + + def on_existing_matching_groups(meta) + world.traverse_example_group_trees_until do |group| + metadata_applies_to_group?(meta, group).tap do |applies| + yield group if applies + end + end + end + + def metadata_applies_to_group?(meta, group) + meta.empty? || MetadataFilter.apply?(:any?, meta, group.metadata) + end + + if RSpec::Support::RubyFeatures.module_prepends_supported? + def safe_prepend(mod, host) + host.__send__(:prepend, mod) unless host < mod + end + end + + if RUBY_VERSION.to_f >= 1.9 + def safe_include(mod, host) + host.__send__(:include, mod) unless host < mod + end + + def safe_extend(mod, host) + host.extend(mod) unless host.singleton_class < mod + end + else # for 1.8.7 + # :nocov: + def safe_include(mod, host) + host.__send__(:include, mod) unless host.included_modules.include?(mod) + end + + def safe_extend(mod, host) + host.extend(mod) unless (class << host; self; end).included_modules.include?(mod) + end + # :nocov: + end + + def define_mixed_in_module(mod, filters, mod_list, config_method, &block) + unless Module === mod + raise TypeError, "`RSpec.configuration.#{config_method}` expects a module but got: #{mod.inspect}" + end + + meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + mod_list.append(mod, meta) + on_existing_matching_groups(meta, &block) + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration_options.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration_options.rb new file mode 100644 index 0000000000..f669cda5f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/configuration_options.rb @@ -0,0 +1,233 @@ +require 'erb' +require 'shellwords' + +module RSpec + module Core + # Responsible for utilizing externally provided configuration options, + # whether via the command line, `.rspec`, `~/.rspec`, + # `$XDG_CONFIG_HOME/rspec/options`, `.rspec-local` or a custom options + # file. + class ConfigurationOptions + # @param args [Array] command line arguments + def initialize(args) + @args = args.dup + organize_options + end + + # Updates the provided {Configuration} instance based on the provided + # external configuration options. + # + # @param config [Configuration] the configuration instance to update + def configure(config) + process_options_into config + configure_filter_manager config.filter_manager + load_formatters_into config + end + + # @api private + # Updates the provided {FilterManager} based on the filter options. + # @param filter_manager [FilterManager] instance to update + def configure_filter_manager(filter_manager) + @filter_manager_options.each do |command, value| + filter_manager.__send__ command, value + end + end + + # @return [Hash] the final merged options, drawn from all external sources + attr_reader :options + + # @return [Array] the original command-line arguments + attr_reader :args + + private + + def organize_options + @filter_manager_options = [] + + @options = (file_options << command_line_options << env_options).each do |opts| + @filter_manager_options << [:include, opts.delete(:inclusion_filter)] if opts.key?(:inclusion_filter) + @filter_manager_options << [:exclude, opts.delete(:exclusion_filter)] if opts.key?(:exclusion_filter) + end + + @options = @options.inject(:libs => [], :requires => []) do |hash, opts| + hash.merge(opts) do |key, oldval, newval| + [:libs, :requires].include?(key) ? oldval + newval : newval + end + end + end + + UNFORCED_OPTIONS = Set.new([ + :requires, :profile, :drb, :libs, :files_or_directories_to_run, + :full_description, :full_backtrace, :tty + ]) + + UNPROCESSABLE_OPTIONS = Set.new([:formatters]) + + def force?(key) + !UNFORCED_OPTIONS.include?(key) + end + + def order(keys) + OPTIONS_ORDER.reverse_each do |key| + keys.unshift(key) if keys.delete(key) + end + keys + end + + OPTIONS_ORDER = [ + # It's important to set this before anything that might issue a + # deprecation (or otherwise access the reporter). + :deprecation_stream, + + # load paths depend on nothing, but must be set before `requires` + # to support load-path-relative requires. + :libs, + + # `files_or_directories_to_run` uses `default_path` so it must be + # set before it. + :default_path, :only_failures, + + # These must be set before `requires` to support checking + # `config.files_to_run` from within `spec_helper.rb` when a + # `-rspec_helper` option is used. + :files_or_directories_to_run, :pattern, :exclude_pattern, + + # Necessary so that the `--seed` option is applied before requires, + # in case required files do something with the provided seed. + # (such as seed global randomization with it). + :order, + + # In general, we want to require the specified files as early as + # possible. The `--require` option is specifically intended to allow + # early requires. For later requires, they can just put the require in + # their spec files, but `--require` provides a unique opportunity for + # users to instruct RSpec to load an extension file early for maximum + # flexibility. + :requires + ] + + def process_options_into(config) + opts = options.reject { |k, _| UNPROCESSABLE_OPTIONS.include? k } + + order(opts.keys).each do |key| + force?(key) ? config.force(key => opts[key]) : config.__send__("#{key}=", opts[key]) + end + end + + def load_formatters_into(config) + options[:formatters].each { |pair| config.add_formatter(*pair) } if options[:formatters] + end + + def file_options + if custom_options_file + [custom_options] + else + [global_options, project_options, local_options] + end + end + + def env_options + return {} unless ENV['SPEC_OPTS'] + + parse_args_ignoring_files_or_dirs_to_run( + Shellwords.split(ENV["SPEC_OPTS"]), + "ENV['SPEC_OPTS']" + ) + end + + def command_line_options + @command_line_options ||= Parser.parse(@args) + end + + def custom_options + options_from(custom_options_file) + end + + def local_options + @local_options ||= options_from(local_options_file) + end + + def project_options + @project_options ||= options_from(project_options_file) + end + + def global_options + @global_options ||= options_from(global_options_file) + end + + def options_from(path) + args = args_from_options_file(path) + parse_args_ignoring_files_or_dirs_to_run(args, path) + end + + def parse_args_ignoring_files_or_dirs_to_run(args, source) + options = Parser.parse(args, source) + options.delete(:files_or_directories_to_run) + options + end + + def args_from_options_file(path) + return [] unless path && File.exist?(path) + config_string = options_file_as_erb_string(path) + FlatMap.flat_map(config_string.split(/\n+/), &:shellsplit) + end + + def options_file_as_erb_string(path) + if RUBY_VERSION >= '2.6' + ERB.new(File.read(path), :trim_mode => '-').result(binding) + else + ERB.new(File.read(path), nil, '-').result(binding) + end + end + + def custom_options_file + command_line_options[:custom_options_file] + end + + def project_options_file + "./.rspec" + end + + def local_options_file + "./.rspec-local" + end + + def global_options_file + xdg_options_file_if_exists || home_options_file_path + end + + def xdg_options_file_if_exists + path = xdg_options_file_path + if path && File.exist?(path) + path + end + end + + def home_options_file_path + File.join(File.expand_path("~"), ".rspec") + rescue ArgumentError + # :nocov: + RSpec.warning "Unable to find ~/.rspec because the HOME environment variable is not set" + nil + # :nocov: + end + + def xdg_options_file_path + xdg_config_home = resolve_xdg_config_home + if xdg_config_home + File.join(xdg_config_home, "rspec", "options") + end + end + + def resolve_xdg_config_home + File.expand_path(ENV.fetch("XDG_CONFIG_HOME", "~/.config")) + rescue ArgumentError + # :nocov: + # On Ruby 2.4, `File.expand("~")` works even if `ENV['HOME']` is not set. + # But on earlier versions, it fails. + nil + # :nocov: + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/did_you_mean.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/did_you_mean.rb new file mode 100644 index 0000000000..07daea966d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/did_you_mean.rb @@ -0,0 +1,46 @@ +module RSpec + module Core + # @private + # Wrapper around Ruby's `DidYouMean::SpellChecker` when available to provide file name suggestions. + class DidYouMean + attr_reader :relative_file_name + + def initialize(relative_file_name) + @relative_file_name = relative_file_name + end + + if defined?(::DidYouMean::SpellChecker) + # provide probable suggestions + def call + checker = ::DidYouMean::SpellChecker.new(:dictionary => Dir["spec/**/*.rb"]) + probables = checker.correct(relative_file_name.sub('./', ''))[0..2] + return '' unless probables.any? + + formats probables + end + else + # return a hint if API for ::DidYouMean::SpellChecker not supported + def call + "\nHint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files." + end + end + + private + + def formats(probables) + rspec_format = probables.map { |s, _| "rspec ./#{s}" } + red_font(top_and_tail rspec_format) + end + + def top_and_tail(rspec_format) + spaces = ' ' * 20 + rspec_format.insert(0, ' - Did you mean?').join("\n#{spaces}") + "\n" + end + + def red_font(mytext) + colorizer = ::RSpec::Core::Formatters::ConsoleCodes + colorizer.wrap mytext, :failure + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/drb.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/drb.rb new file mode 100644 index 0000000000..e44db97c3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/drb.rb @@ -0,0 +1,120 @@ +require 'drb/drb' + +module RSpec + module Core + # @private + class DRbRunner + def initialize(options, configuration=RSpec.configuration) + @options = options + @configuration = configuration + end + + def drb_port + @options.options[:drb_port] || ENV['RSPEC_DRB'] || 8989 + end + + def run(err, out) + begin + DRb.start_service("druby://localhost:0") + rescue SocketError, Errno::EADDRNOTAVAIL + DRb.start_service("druby://:0") + end + spec_server = DRbObject.new_with_uri("druby://127.0.0.1:#{drb_port}") + spec_server.run(drb_argv, err, out) + end + + def drb_argv + @drb_argv ||= begin + @options.configure_filter_manager(@configuration.filter_manager) + DRbOptions.new(@options.options, @configuration.filter_manager).options + end + end + end + + # @private + class DRbOptions + def initialize(submitted_options, filter_manager) + @submitted_options = submitted_options + @filter_manager = filter_manager + end + + def options + argv = [] + argv << "--color" if @submitted_options[:color] + argv << "--force-color" if @submitted_options[:color_mode] == :on + argv << "--no-color" if @submitted_options[:color_mode] == :off + argv << "--profile" if @submitted_options[:profile_examples] + argv << "--backtrace" if @submitted_options[:full_backtrace] + argv << "--tty" if @submitted_options[:tty] + argv << "--fail-fast" if @submitted_options[:fail_fast] + argv << "--options" << @submitted_options[:custom_options_file] if @submitted_options[:custom_options_file] + argv << "--order" << @submitted_options[:order] if @submitted_options[:order] + + add_failure_exit_code(argv) + add_error_exit_code(argv) + add_full_description(argv) + add_filter(argv, :inclusion, @filter_manager.inclusions) + add_filter(argv, :exclusion, @filter_manager.exclusions) + add_formatters(argv) + add_libs(argv) + add_requires(argv) + + argv + @submitted_options[:files_or_directories_to_run] + end + + def add_failure_exit_code(argv) + return unless @submitted_options[:failure_exit_code] + + argv << "--failure-exit-code" << @submitted_options[:failure_exit_code].to_s + end + + def add_error_exit_code(argv) + return unless @submitted_options[:error_exit_code] + + argv << "--error-exit-code" << @submitted_options[:error_exit_code].to_s + end + + def add_full_description(argv) + return unless @submitted_options[:full_description] + + # The argument to --example is regexp-escaped before being stuffed + # into a regexp when received for the first time (see OptionParser). + # Hence, merely grabbing the source of this regexp will retain the + # backslashes, so we must remove them. + @submitted_options[:full_description].each do |description| + argv << "--example" << description.source.delete('\\') + end + end + + CONDITIONAL_FILTERS = [:if, :unless] + + def add_filter(argv, name, hash) + hash.each_pair do |k, v| + next if CONDITIONAL_FILTERS.include?(k) + tag = name == :inclusion ? k.to_s : "~#{k}".dup + tag << ":#{v}" if v.is_a?(String) + argv << "--tag" << tag + end unless hash.empty? + end + + def add_formatters(argv) + @submitted_options[:formatters].each do |pair| + argv << "--format" << pair[0] + argv << "--out" << pair[1] if pair[1] + end if @submitted_options[:formatters] + end + + def add_libs(argv) + @submitted_options[:libs].each do |path| + argv << "-I" << path + end if @submitted_options[:libs] + end + + def add_requires(argv) + @submitted_options[:requires].each do |path| + argv << "--require" << path + end if @submitted_options[:requires] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/dsl.rb new file mode 100644 index 0000000000..220403e6e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/dsl.rb @@ -0,0 +1,98 @@ +module RSpec + module Core + # DSL defines methods to group examples, most notably `describe`, + # and exposes them as class methods of {RSpec}. They can also be + # exposed globally (on `main` and instances of `Module`) through + # the {Configuration} option `expose_dsl_globally`. + # + # By default the methods `describe`, `context` and `example_group` + # are exposed. These methods define a named context for one or + # more examples. The given block is evaluated in the context of + # a generated subclass of {RSpec::Core::ExampleGroup}. + # + # ## Examples: + # + # RSpec.describe "something" do + # context "when something is a certain way" do + # it "does something" do + # # example code goes here + # end + # end + # end + # + # @see ExampleGroup + # @see ExampleGroup.example_group + module DSL + # @private + def self.example_group_aliases + @example_group_aliases ||= [] + end + + # @private + def self.exposed_globally? + @exposed_globally ||= false + end + + # @private + def self.expose_example_group_alias(name) + return if example_group_aliases.include?(name) + + example_group_aliases << name + + (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block| + group = RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block) + RSpec.world.record(group) + group + end + + expose_example_group_alias_globally(name) if exposed_globally? + end + + class << self + # @private + attr_accessor :top_level + end + + # Adds the describe method to Module and the top level binding. + # @api private + def self.expose_globally! + return if exposed_globally? + + example_group_aliases.each do |method_name| + expose_example_group_alias_globally(method_name) + end + + @exposed_globally = true + end + + # Removes the describe method from Module and the top level binding. + # @api private + def self.remove_globally! + return unless exposed_globally? + + example_group_aliases.each do |method_name| + change_global_dsl { undef_method method_name } + end + + @exposed_globally = false + end + + # @private + def self.expose_example_group_alias_globally(method_name) + change_global_dsl do + remove_method(method_name) if method_defined?(method_name) + define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) } + end + end + + # @private + def self.change_global_dsl(&changes) + (class << top_level; self; end).class_exec(&changes) + Module.class_exec(&changes) + end + end + end +end + +# Capture main without an eval. +::RSpec::Core::DSL.top_level = self diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example.rb new file mode 100644 index 0000000000..4e7618b755 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example.rb @@ -0,0 +1,666 @@ +module RSpec + module Core + # Wrapper for an instance of a subclass of {ExampleGroup}. An instance of + # `RSpec::Core::Example` is returned by example definition methods + # such as {ExampleGroup.it it} and is yielded to the {ExampleGroup.it it}, + # {Hooks#before before}, {Hooks#after after}, {Hooks#around around}, + # {MemoizedHelpers::ClassMethods#let let} and + # {MemoizedHelpers::ClassMethods#subject subject} blocks. + # + # This allows us to provide rich metadata about each individual + # example without adding tons of methods directly to the ExampleGroup + # that users may inadvertently redefine. + # + # Useful for configuring logging and/or taking some action based + # on the state of an example's metadata. + # + # @example + # + # RSpec.configure do |config| + # config.before do |example| + # log example.description + # end + # + # config.after do |example| + # log example.description + # end + # + # config.around do |example| + # log example.description + # example.run + # end + # end + # + # shared_examples "auditable" do + # it "does something" do + # log "#{example.full_description}: #{auditable.inspect}" + # auditable.should do_something + # end + # end + # + # @see ExampleGroup + # @note Example blocks are evaluated in the context of an instance + # of an `ExampleGroup`, not in the context of an instance of `Example`. + class Example + # @private + # + # Used to define methods that delegate to this example's metadata. + def self.delegate_to_metadata(key) + define_method(key) { @metadata[key] } + end + + # @return [ExecutionResult] represents the result of running this example. + delegate_to_metadata :execution_result + # @return [String] the relative path to the file where this example was + # defined. + delegate_to_metadata :file_path + # @return [String] the full description (including the docstrings of + # all parent example groups). + delegate_to_metadata :full_description + # @return [String] the exact source location of this example in a form + # like `./path/to/spec.rb:17` + delegate_to_metadata :location + # @return [Boolean] flag that indicates that the example is not expected + # to pass. It will be run and will either have a pending result (if a + # failure occurs) or a failed result (if no failure occurs). + delegate_to_metadata :pending + # @return [Boolean] flag that will cause the example to not run. + # The {ExecutionResult} status will be `:pending`. + delegate_to_metadata :skip + + # Returns the string submitted to `example` or its aliases (e.g. + # `specify`, `it`, etc). If no string is submitted (e.g. + # `it { is_expected.to do_something }`) it returns the message generated + # by the matcher if there is one, otherwise returns a message including + # the location of the example. + def description + description = if metadata[:description].to_s.empty? + location_description + else + metadata[:description] + end + + RSpec.configuration.format_docstrings_block.call(description) + end + + # Returns a description of the example that always includes the location. + def inspect_output + inspect_output = "\"#{description}\"" + unless metadata[:description].to_s.empty? + inspect_output += " (#{location})" + end + inspect_output + end + + # Returns the location-based argument that can be passed to the `rspec` command to rerun this example. + def location_rerun_argument + @location_rerun_argument ||= begin + loaded_spec_files = RSpec.configuration.loaded_spec_files + + Metadata.ascending(metadata) do |meta| + return meta[:location] if loaded_spec_files.include?(meta[:absolute_file_path]) + end + end + end + + # Returns the location-based argument that can be passed to the `rspec` command to rerun this example. + # + # @deprecated Use {#location_rerun_argument} instead. + # @note If there are multiple examples identified by this location, they will use {#id} + # to rerun instead, but this method will still return the location (that's why it is deprecated!). + def rerun_argument + location_rerun_argument + end + + # @return [String] the unique id of this example. Pass + # this at the command line to re-run this exact example. + def id + @id ||= Metadata.id_from(metadata) + end + + # @private + def self.parse_id(id) + # http://rubular.com/r/OMZSAPcAfn + id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures + end + + # Duplicates the example and overrides metadata with the provided + # hash. + # + # @param metadata_overrides [Hash] the hash to override the example metadata + # @return [Example] a duplicate of the example with modified metadata + def duplicate_with(metadata_overrides={}) + new_metadata = metadata.clone.merge(metadata_overrides) + + RSpec::Core::Metadata::RESERVED_KEYS.each do |reserved_key| + new_metadata.delete reserved_key + end + + # don't clone the example group because the new example + # must belong to the same example group (not a clone). + # + # block is nil in new_metadata so we have to get it from metadata. + Example.new(example_group, description.clone, + new_metadata, metadata[:block]) + end + + # @private + def update_inherited_metadata(updates) + metadata.update(updates) do |_key, existing_example_value, _new_inherited_value| + existing_example_value + end + end + + # @attr_reader + # + # Returns the first exception raised in the context of running this + # example (nil if no exception is raised). + attr_reader :exception + + # @attr_reader + # + # Returns the metadata object associated with this example. + attr_reader :metadata + + # @attr_reader + # @private + # + # Returns the example_group_instance that provides the context for + # running this example. + attr_reader :example_group_instance + + # @attr + # @private + attr_accessor :clock + + # Creates a new instance of Example. + # @param example_group_class [Class] the subclass of ExampleGroup in which + # this Example is declared + # @param description [String] the String passed to the `it` method (or + # alias) + # @param user_metadata [Hash] additional args passed to `it` to be used as + # metadata + # @param example_block [Proc] the block of code that represents the + # example + # @api private + def initialize(example_group_class, description, user_metadata, example_block=nil) + @example_group_class = example_group_class + @example_block = example_block + + # Register the example with the group before creating the metadata hash. + # This is necessary since creating the metadata hash triggers + # `when_first_matching_example_defined` callbacks, in which users can + # load RSpec support code which defines hooks. For that to work, the + # examples and example groups must be registered at the time the + # support code is called or be defined afterwards. + # Begin defined beforehand but registered afterwards causes hooks to + # not be applied where they should. + example_group_class.examples << self + + @metadata = Metadata::ExampleHash.create( + @example_group_class.metadata, user_metadata, + example_group_class.method(:next_runnable_index_for), + description, example_block + ) + + config = RSpec.configuration + config.apply_derived_metadata_to(@metadata) + + # This should perhaps be done in `Metadata::ExampleHash.create`, + # but the logic there has no knowledge of `RSpec.world` and we + # want to keep it that way. It's easier to just assign it here. + @metadata[:last_run_status] = config.last_run_statuses[id] + + @example_group_instance = @exception = nil + @clock = RSpec::Core::Time + @reporter = RSpec::Core::NullReporter + end + + # Provide a human-readable representation of this class + def inspect + "#<#{self.class.name} #{description.inspect}>" + end + alias to_s inspect + + # @return [RSpec::Core::Reporter] the current reporter for the example + attr_reader :reporter + + # Returns the example group class that provides the context for running + # this example. + def example_group + @example_group_class + end + + def pending? + !!pending + end + + def skipped? + !!skip + end + + # @api private + # instance_execs the block passed to the constructor in the context of + # the instance of {ExampleGroup}. + # @param example_group_instance the instance of an ExampleGroup subclass + def run(example_group_instance, reporter) + @example_group_instance = example_group_instance + @reporter = reporter + RSpec.configuration.configure_example(self, hooks) + RSpec.current_example = self + + start(reporter) + Pending.mark_pending!(self, pending) if pending? + + begin + if skipped? + Pending.mark_pending! self, skip + elsif !RSpec.configuration.dry_run? + with_around_and_singleton_context_hooks do + begin + run_before_example + RSpec.current_scope = :example + @example_group_instance.instance_exec(self, &@example_block) + + if pending? + Pending.mark_fixed! self + + raise Pending::PendingExampleFixedError, + 'Expected example to fail since it is pending, but it passed.', + [location] + end + rescue Pending::SkipDeclaredInExample => _ + # The "=> _" is normally useless but on JRuby it is a workaround + # for a bug that prevents us from getting backtraces: + # https://github.com/jruby/jruby/issues/4467 + # + # no-op, required metadata has already been set by the `skip` + # method. + rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e + set_exception(e) + ensure + RSpec.current_scope = :after_example_hook + run_after_example + end + end + end + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + ensure + @example_group_instance = nil # if you love something... let it go + end + + finish(reporter) + ensure + execution_result.ensure_timing_set(clock) + RSpec.current_example = nil + end + + if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9 + # :nocov: + # For some reason, rescuing `Support::AllExceptionsExceptOnesWeMustNotRescue` + # in place of `Exception` above can cause the exit status to be the wrong + # thing. I have no idea why. See: + # https://github.com/rspec/rspec-core/pull/2063#discussion_r38284978 + # @private + AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Exception + # :nocov: + else + # @private + AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Support::AllExceptionsExceptOnesWeMustNotRescue + end + + # Wraps both a `Proc` and an {Example} for use in {Hooks#around + # around} hooks. In around hooks we need to yield this special + # kind of object (rather than the raw {Example}) because when + # there are multiple `around` hooks we have to wrap them recursively. + # + # @example + # + # RSpec.configure do |c| + # c.around do |ex| # Procsy which wraps the example + # if ex.metadata[:key] == :some_value && some_global_condition + # raise "some message" + # end + # ex.run # run delegates to ex.call. + # end + # end + # + # @note This class also exposes the instance methods of {Example}, + # proxying them through to the wrapped {Example} instance. + class Procsy + # The {Example} instance. + attr_reader :example + + Example.public_instance_methods(false).each do |name| + name_sym = name.to_sym + next if name_sym == :run || name_sym == :inspect || name_sym == :to_s + + define_method(name) { |*a, &b| @example.__send__(name, *a, &b) } + end + + Proc.public_instance_methods(false).each do |name| + name_sym = name.to_sym + next if name_sym == :call || name_sym == :inspect || name_sym == :to_s || name_sym == :to_proc + + define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) } + end + + # Calls the proc and notes that the example has been executed. + def call(*args, &block) + @executed = true + @proc.call(*args, &block) + end + alias run call + + # Provides a wrapped proc that will update our `executed?` state when + # executed. + def to_proc + method(:call).to_proc + end + + def initialize(example, &block) + @example = example + @proc = block + @executed = false + end + + # @private + def wrap(&block) + self.class.new(example, &block) + end + + # Indicates whether or not the around hook has executed the example. + def executed? + @executed + end + + # @private + def inspect + @example.inspect.gsub('Example', 'Example::Procsy') + end + end + + # @private + # + # The exception that will be displayed to the user -- either the failure of + # the example or the `pending_exception` if the example is pending. + def display_exception + @exception || execution_result.pending_exception + end + + # @private + # + # Assigns the exception that will be displayed to the user -- either the failure of + # the example or the `pending_exception` if the example is pending. + def display_exception=(ex) + if pending? && !(Pending::PendingExampleFixedError === ex) + @exception = nil + execution_result.pending_fixed = false + execution_result.pending_exception = ex + else + @exception = ex + end + end + + # rubocop:disable Naming/AccessorMethodName + + # @private + # + # Used internally to set an exception in an after hook, which + # captures the exception but doesn't raise it. + def set_exception(exception) + return self.display_exception = exception unless display_exception + + unless RSpec::Core::MultipleExceptionError === display_exception + self.display_exception = RSpec::Core::MultipleExceptionError.new(display_exception) + end + + display_exception.add exception + end + + # @private + # + # Used to set the exception when `aggregate_failures` fails. + def set_aggregate_failures_exception(exception) + return set_exception(exception) unless display_exception + + exception = RSpec::Core::MultipleExceptionError::InterfaceTag.for(exception) + exception.add display_exception + self.display_exception = exception + end + + # rubocop:enable Naming/AccessorMethodName + + # @private + # + # Used internally to set an exception and fail without actually executing + # the example when an exception is raised in before(:context). + def fail_with_exception(reporter, exception) + start(reporter) + set_exception(exception) + finish(reporter) + end + + # @private + # + # Used internally to skip without actually executing the example when + # skip is used in before(:context). + def skip_with_exception(reporter, exception) + start(reporter) + Pending.mark_skipped! self, exception.argument + finish(reporter) + end + + # @private + def instance_exec(*args, &block) + @example_group_instance.instance_exec(*args, &block) + end + + private + + def hooks + example_group_instance.singleton_class.hooks + end + + def with_around_example_hooks + RSpec.current_scope = :before_example_hook + hooks.run(:around, :example, self) { yield } + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + end + + def start(reporter) + reporter.example_started(self) + execution_result.started_at = clock.now + end + + def finish(reporter) + pending_message = execution_result.pending_message + + if @exception + execution_result.exception = @exception + record_finished :failed, reporter + reporter.example_failed self + false + elsif pending_message + execution_result.pending_message = pending_message + record_finished :pending, reporter + reporter.example_pending self + true + else + record_finished :passed, reporter + reporter.example_passed self + true + end + end + + def record_finished(status, reporter) + execution_result.record_finished(status, clock.now) + reporter.example_finished(self) + end + + def run_before_example + @example_group_instance.setup_mocks_for_rspec + hooks.run(:before, :example, self) + end + + def with_around_and_singleton_context_hooks + singleton_context_hooks_host = example_group_instance.singleton_class + singleton_context_hooks_host.run_before_context_hooks(example_group_instance) + with_around_example_hooks { yield } + ensure + singleton_context_hooks_host.run_after_context_hooks(example_group_instance) + end + + def run_after_example + assign_generated_description if defined?(::RSpec::Matchers) + hooks.run(:after, :example, self) + verify_mocks + ensure + @example_group_instance.teardown_mocks_for_rspec + end + + def verify_mocks + @example_group_instance.verify_mocks_for_rspec if mocks_need_verification? + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + set_exception(e) + end + + def mocks_need_verification? + exception.nil? || execution_result.pending_fixed? + end + + def assign_generated_description + if metadata[:description].empty? && (description = generate_description) + metadata[:description] = description + metadata[:full_description] += description + end + ensure + RSpec::Matchers.clear_generated_description + end + + def generate_description + RSpec::Matchers.generated_description + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + location_description + " (Got an error when generating description " \ + "from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})" + end + + def location_description + "example at #{location}" + end + + # Represents the result of executing an example. + # Behaves like a hash for backwards compatibility. + class ExecutionResult + include HashImitatable + + # @return [Symbol] `:passed`, `:failed` or `:pending`. + attr_accessor :status + + # @return [Exception, nil] The failure, if there was one. + attr_accessor :exception + + # @return [Time] When the example started. + attr_accessor :started_at + + # @return [Time] When the example finished. + attr_accessor :finished_at + + # @return [Float] How long the example took in seconds. + attr_accessor :run_time + + # @return [String, nil] The reason the example was pending, + # or nil if the example was not pending. + attr_accessor :pending_message + + # @return [Exception, nil] The exception triggered while + # executing the pending example. If no exception was triggered + # it would no longer get a status of `:pending` unless it was + # tagged with `:skip`. + attr_accessor :pending_exception + + # @return [Boolean] For examples tagged with `:pending`, + # this indicates whether or not it now passes. + attr_accessor :pending_fixed + + def pending_fixed? + !!pending_fixed + end + + # @return [Boolean] Indicates if the example was completely skipped + # (typically done via `:skip` metadata or the `skip` method). Skipped examples + # will have a `:pending` result. A `:pending` result can also come from examples + # that were marked as `:pending`, which causes them to be run, and produces a + # `:failed` result if the example passes. + def example_skipped? + status == :pending && !pending_exception + end + + # @api private + # Records the finished status of the example. + def record_finished(status, finished_at) + self.status = status + calculate_run_time(finished_at) + end + + # @api private + # Populates finished_at and run_time if it has not yet been set + def ensure_timing_set(clock) + calculate_run_time(clock.now) unless finished_at + end + + private + + def calculate_run_time(finished_at) + self.finished_at = finished_at + self.run_time = (finished_at - started_at).to_f + end + + # For backwards compatibility we present `status` as a string + # when presenting the legacy hash interface. + def hash_for_delegation + super.tap do |hash| + hash[:status] &&= status.to_s + end + end + + def set_value(name, value) + value &&= value.to_sym if name == :status + super(name, value) + end + + def get_value(name) + if name == :status + status.to_s if status + else + super + end + end + + def issue_deprecation(_method_name, *_args) + RSpec.deprecate("Treating `metadata[:execution_result]` as a hash", + :replacement => "the attributes methods to access the data") + end + end + end + + # @private + # Provides an execution context for before/after :suite hooks. + class SuiteHookContext < Example + def initialize(hook_description, reporter) + super(AnonymousExampleGroup, hook_description, {}) + @example_group_instance = AnonymousExampleGroup.new + @reporter = reporter + end + + # rubocop:disable Naming/AccessorMethodName + def set_exception(exception) + reporter.notify_non_example_exception(exception, "An error occurred in #{description}.") + RSpec.world.wants_to_quit = true + end + # rubocop:enable Naming/AccessorMethodName + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_group.rb new file mode 100644 index 0000000000..a434f07601 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_group.rb @@ -0,0 +1,905 @@ +RSpec::Support.require_rspec_support 'recursive_const_methods' + +module RSpec + module Core + # rubocop:disable Metrics/ClassLength + + # ExampleGroup and {Example} are the main structural elements of + # rspec-core. Consider this example: + # + # RSpec.describe Thing do + # it "does something" do + # end + # end + # + # The object returned by `describe Thing` is a subclass of ExampleGroup. + # The object returned by `it "does something"` is an instance of Example, + # which serves as a wrapper for an instance of the ExampleGroup in which it + # is declared. + # + # Example group bodies (e.g. `describe` or `context` blocks) are evaluated + # in the context of a new subclass of ExampleGroup. Individual examples are + # evaluated in the context of an instance of the specific ExampleGroup + # subclass to which they belong. + # + # Besides the class methods defined here, there are other interesting macros + # defined in {Hooks}, {MemoizedHelpers::ClassMethods} and + # {SharedExampleGroup}. There are additional instance methods available to + # your examples defined in {MemoizedHelpers} and {Pending}. + class ExampleGroup + extend Hooks + + include MemoizedHelpers + extend MemoizedHelpers::ClassMethods + include Pending + extend SharedExampleGroup + + # Define a singleton method for the singleton class (remove the method if + # it's already been defined). + # @private + def self.idempotently_define_singleton_method(name, &definition) + (class << self; self; end).module_exec do + remove_method(name) if method_defined?(name) && instance_method(name).owner == self + define_method(name, &definition) + end + end + + # @!group Metadata + + # The [Metadata](Metadata) object associated with this group. + # @see Metadata + def self.metadata + @metadata ||= nil + end + + # Temporarily replace the provided metadata. + # Intended primarily to allow an example group's singleton class + # to return the metadata of the example that it exists for. This + # is necessary for shared example group inclusion to work properly + # with singleton example groups. + # @private + def self.with_replaced_metadata(meta) + orig_metadata = metadata + @metadata = meta + yield + ensure + @metadata = orig_metadata + end + + # @private + # @return [Metadata] belonging to the parent of a nested {ExampleGroup} + def self.superclass_metadata + @superclass_metadata ||= superclass.respond_to?(:metadata) ? superclass.metadata : nil + end + + # @private + def self.delegate_to_metadata(*names) + names.each do |name| + idempotently_define_singleton_method(name) { metadata.fetch(name) } + end + end + + delegate_to_metadata :described_class, :file_path, :location + + # @return [String] the current example group description + def self.description + description = metadata[:description] + RSpec.configuration.format_docstrings_block.call(description) + end + + # Returns the class or module passed to the `describe` method (or alias). + # Returns nil if the subject is not a class or module. + # @example + # RSpec.describe Thing do + # it "does something" do + # described_class == Thing + # end + # end + # + def described_class + self.class.described_class + end + + # @!endgroup + + # @!group Defining Examples + + # @private + # @macro [attach] define_example_method + # @!scope class + # @method $1 + # @overload $1 + # @overload $1(&example_implementation) + # @param example_implementation [Block] The implementation of the example. + # @overload $1(doc_string, *metadata) + # @param doc_string [String] The example's doc string. + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. + # @overload $1(doc_string, *metadata, &example_implementation) + # @param doc_string [String] The example's doc string. + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. + # @param example_implementation [Block] The implementation of the example. + # @yield [Example] the example object + # @example + # $1 do + # end + # + # $1 "does something" do + # end + # + # $1 "does something", :slow, :uses_js do + # end + # + # $1 "does something", :with => 'additional metadata' do + # end + # + # $1 "does something" do |ex| + # # ex is the Example object that contains metadata about the example + # end + # + # @example + # $1 "does something", :slow, :load_factor => 100 do + # end + # + def self.define_example_method(name, extra_options={}) + idempotently_define_singleton_method(name) do |*all_args, &block| + desc, *args = *all_args + + options = Metadata.build_hash_from(args) + options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block + options.update(extra_options) + + RSpec::Core::Example.new(self, desc, options, block) + end + end + + # Defines an example within a group. + define_example_method :example + # Defines an example within a group. + # This is the primary API to define a code example. + define_example_method :it + # Defines an example within a group. + # Useful for when your docstring does not read well off of `it`. + # @example + # RSpec.describe MyClass do + # specify "#do_something is deprecated" do + # # ... + # end + # end + define_example_method :specify + + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :focus, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fexample, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fit, :focus => true + # Shortcut to define an example with `:focus => true`. + # @see example + define_example_method :fspecify, :focus => true + # Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'`. + # @see example + define_example_method :xexample, :skip => 'Temporarily skipped with xexample' + # Shortcut to define an example with `:skip => 'Temporarily skipped with xit'`. + # @see example + define_example_method :xit, :skip => 'Temporarily skipped with xit' + # Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'`. + # @see example + define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify' + # Shortcut to define an example with `:skip => true` + # @see example + define_example_method :skip, :skip => true + # Shortcut to define an example with `:pending => true` + # @see example + define_example_method :pending, :pending => true + + # @!endgroup + + # @!group Defining Example Groups + + # @private + # @macro [attach] define_example_group_method + # @!scope class + # @overload $1 + # @overload $1(&example_group_definition) + # @param example_group_definition [Block] The definition of the example group. + # @overload $1(doc_string, *metadata, &example_implementation) + # @param doc_string [String] The group's doc string. + # @param metadata [Array, Hash] Metadata for the group. + # Symbols will be transformed into hash entries with `true` values. + # @param example_group_definition [Block] The definition of the example group. + # + # Generates a subclass of this example group which inherits + # everything except the examples themselves. + # + # @example + # + # RSpec.describe "something" do # << This describe method is defined in + # # << RSpec::Core::DSL, included in the + # # << global namespace (optional) + # before do + # do_something_before + # end + # + # before(:example, :clean_env) do + # env.clear! + # end + # + # let(:thing) { Thing.new } + # + # $1 "attribute (of something)" do + # # examples in the group get the before hook + # # declared above, and can access `thing` + # end + # + # $1 "needs additional setup", :clean_env, :implementation => JSON do + # # specifies that hooks with matching metadata + # # should be be run additionally + # end + # end + # + # @see DSL#describe + def self.define_example_group_method(name, metadata={}) + idempotently_define_singleton_method(name) do |*args, &example_group_block| + thread_data = RSpec::Support.thread_local_data + top_level = self == ExampleGroup + + registration_collection = + if top_level + if thread_data[:in_example_group] + raise "Creating an isolated context from within a context is " \ + "not allowed. Change `RSpec.#{name}` to `#{name}` or " \ + "move this to a top-level scope." + end + + thread_data[:in_example_group] = true + RSpec.world.example_groups + else + children + end + + begin + description = args.shift + combined_metadata = metadata.dup + combined_metadata.merge!(args.pop) if args.last.is_a? Hash + args << combined_metadata + + subclass(self, description, args, registration_collection, &example_group_block) + ensure + thread_data.delete(:in_example_group) if top_level + end + end + + RSpec::Core::DSL.expose_example_group_alias(name) + end + + define_example_group_method :example_group + + # An alias of `example_group`. Generally used when grouping examples by a + # thing you are describing (e.g. an object, class or method). + # @see example_group + define_example_group_method :describe + + # An alias of `example_group`. Generally used when grouping examples + # contextually (e.g. "with xyz", "when xyz" or "if xyz"). + # @see example_group + define_example_group_method :context + + # Shortcut to temporarily make an example group skipped. + # @see example_group + define_example_group_method :xdescribe, :skip => "Temporarily skipped with xdescribe" + + # Shortcut to temporarily make an example group skipped. + # @see example_group + define_example_group_method :xcontext, :skip => "Temporarily skipped with xcontext" + + # Shortcut to define an example group with `:focus => true`. + # @see example_group + define_example_group_method :fdescribe, :focus => true + + # Shortcut to define an example group with `:focus => true`. + # @see example_group + define_example_group_method :fcontext, :focus => true + + # @!endgroup + + # @!group Including Shared Example Groups + + # @private + # @macro [attach] define_nested_shared_group_method + # @!scope class + # + # @see SharedExampleGroup + def self.define_nested_shared_group_method(new_name, report_label="it should behave like") + idempotently_define_singleton_method(new_name) do |name, *args, &customization_block| + # Pass :caller so the :location metadata is set properly. + # Otherwise, it'll be set to the next line because that's + # the block's source_location. + group = example_group("#{report_label} #{name}", :caller => (the_caller = caller)) do + find_and_eval_shared("examples", name, the_caller.first, *args, &customization_block) + end + group.metadata[:shared_group_name] = name + group + end + end + + # Generates a nested example group and includes the shared content + # mapped to `name` in the nested group. + define_nested_shared_group_method :it_behaves_like, "behaves like" + # Generates a nested example group and includes the shared content + # mapped to `name` in the nested group. + define_nested_shared_group_method :it_should_behave_like + + # Includes shared content mapped to `name` directly in the group in which + # it is declared, as opposed to `it_behaves_like`, which creates a nested + # group. If given a block, that block is also eval'd in the current + # context. + # + # @see SharedExampleGroup + def self.include_context(name, *args, &block) + find_and_eval_shared("context", name, caller.first, *args, &block) + end + + # Includes shared content mapped to `name` directly in the group in which + # it is declared, as opposed to `it_behaves_like`, which creates a nested + # group. If given a block, that block is also eval'd in the current + # context. + # + # @see SharedExampleGroup + def self.include_examples(name, *args, &block) + find_and_eval_shared("examples", name, caller.first, *args, &block) + end + + # Clear memoized values when adding/removing examples + # @private + def self.reset_memoized + @descendant_filtered_examples = nil + @_descendants = nil + @parent_groups = nil + @declaration_locations = nil + end + + # Adds an example to the example group + def self.add_example(example) + reset_memoized + examples << example + end + + # Removes an example from the example group + def self.remove_example(example) + reset_memoized + examples.delete example + end + + # @private + def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block) + shared_module = RSpec.world.shared_example_group_registry.find(parent_groups, name) + + unless shared_module + raise ArgumentError, "Could not find shared #{label} #{name.inspect}" + end + + shared_module.include_in( + self, Metadata.relative_path(inclusion_location), + args, customization_block + ) + end + + # @!endgroup + + # @private + def self.subclass(parent, description, args, registration_collection, &example_group_block) + subclass = Class.new(parent) + subclass.set_it_up(description, args, registration_collection, &example_group_block) + subclass.module_exec(&example_group_block) if example_group_block + + # The LetDefinitions module must be included _after_ other modules + # to ensure that it takes precedence when there are name collisions. + # Thus, we delay including it until after the example group block + # has been eval'd. + MemoizedHelpers.define_helpers_on(subclass) + + subclass + end + + # @private + def self.set_it_up(description, args, registration_collection, &example_group_block) + # Ruby 1.9 has a bug that can lead to infinite recursion and a + # SystemStackError if you include a module in a superclass after + # including it in a subclass: https://gist.github.com/845896 + # To prevent this, we must include any modules in + # RSpec::Core::ExampleGroup before users create example groups and have + # a chance to include the same module in a subclass of + # RSpec::Core::ExampleGroup. So we need to configure example groups + # here. + ensure_example_groups_are_configured + + # Register the example with the group before creating the metadata hash. + # This is necessary since creating the metadata hash triggers + # `when_first_matching_example_defined` callbacks, in which users can + # load RSpec support code which defines hooks. For that to work, the + # examples and example groups must be registered at the time the + # support code is called or be defined afterwards. + # Begin defined beforehand but registered afterwards causes hooks to + # not be applied where they should. + registration_collection << self + + @user_metadata = Metadata.build_hash_from(args) + + @metadata = Metadata::ExampleGroupHash.create( + superclass_metadata, @user_metadata, + superclass.method(:next_runnable_index_for), + description, *args, &example_group_block + ) + + config = RSpec.configuration + config.apply_derived_metadata_to(@metadata) + + ExampleGroups.assign_const(self) + + @currently_executing_a_context_hook = false + + config.configure_group(self) + end + + # @private + def self.examples + @examples ||= [] + end + + # @private + def self.filtered_examples + RSpec.world.filtered_examples[self] + end + + # @private + def self.descendant_filtered_examples + @descendant_filtered_examples ||= filtered_examples + + FlatMap.flat_map(children, &:descendant_filtered_examples) + end + + # @private + def self.children + @children ||= [] + end + + # @private + # Traverses the tree of groups, starting with `self`, then the children, recursively. + # Halts the traversal of a branch of the tree as soon as the passed block returns true. + # Note that siblings groups and their sub-trees will continue to be explored. + # This is intended to make it easy to find the top-most group that satisfies some + # condition. + def self.traverse_tree_until(&block) + return if yield self + + children.each do |child| + child.traverse_tree_until(&block) + end + end + + # @private + def self.next_runnable_index_for(file) + if self == ExampleGroup + # We add 1 so the ids start at 1 instead of 0. This is + # necessary for this branch (but not for the other one) + # because we register examples and groups with the + # `children` and `examples` collection BEFORE this + # method is called as part of metadata hash creation, + # but the example group is recorded with + # `RSpec.world.example_group_counts_by_spec_file` AFTER + # the metadata hash is created and the group is returned + # to the caller. + RSpec.world.num_example_groups_defined_in(file) + 1 + else + children.count + examples.count + end + end + + # @private + def self.descendants + @_descendants ||= [self] + FlatMap.flat_map(children, &:descendants) + end + + ## @private + def self.parent_groups + @parent_groups ||= ancestors.select { |a| a < RSpec::Core::ExampleGroup } + end + + # @private + def self.top_level? + superclass == ExampleGroup + end + + # @private + def self.ensure_example_groups_are_configured + unless defined?(@@example_groups_configured) + RSpec.configuration.configure_mock_framework + RSpec.configuration.configure_expectation_framework + # rubocop:disable Style/ClassVars + @@example_groups_configured = true + # rubocop:enable Style/ClassVars + end + end + + # @private + def self.before_context_ivars + @before_context_ivars ||= {} + end + + # @private + def self.store_before_context_ivars(example_group_instance) + each_instance_variable_for_example(example_group_instance) do |ivar| + before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar) + end + end + + # Returns true if a `before(:context)` or `after(:context)` + # hook is currently executing. + def self.currently_executing_a_context_hook? + @currently_executing_a_context_hook + end + + # @private + def self.run_before_context_hooks(example_group_instance) + set_ivars(example_group_instance, superclass_before_context_ivars) + + @currently_executing_a_context_hook = true + + ContextHookMemoized::Before.isolate_for_context_hook(example_group_instance) do + hooks.run(:before, :context, example_group_instance) + end + ensure + store_before_context_ivars(example_group_instance) + @currently_executing_a_context_hook = false + end + + if RUBY_VERSION.to_f >= 1.9 + # @private + def self.superclass_before_context_ivars + superclass.before_context_ivars + end + else # 1.8.7 + # :nocov: + # @private + def self.superclass_before_context_ivars + if superclass.respond_to?(:before_context_ivars) + superclass.before_context_ivars + else + # `self` must be the singleton class of an ExampleGroup instance. + # On 1.8.7, the superclass of a singleton class of an instance of A + # is A's singleton class. On 1.9+, it's A. On 1.8.7, the first ancestor + # is A, so we can mirror 1.8.7's behavior here. Note that we have to + # search for the first that responds to `before_context_ivars` + # in case a module has been included in the singleton class. + ancestors.find { |a| a.respond_to?(:before_context_ivars) }.before_context_ivars + end + end + # :nocov: + end + + # @private + def self.run_after_context_hooks(example_group_instance) + set_ivars(example_group_instance, before_context_ivars) + + @currently_executing_a_context_hook = true + + ContextHookMemoized::After.isolate_for_context_hook(example_group_instance) do + hooks.run(:after, :context, example_group_instance) + end + ensure + before_context_ivars.clear + @currently_executing_a_context_hook = false + end + + # Runs all the examples in this group. + def self.run(reporter=RSpec::Core::NullReporter) + return if RSpec.world.wants_to_quit + reporter.example_group_started(self) + + should_run_context_hooks = descendant_filtered_examples.any? + begin + RSpec.current_scope = :before_context_hook + run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks + result_for_this_group = run_examples(reporter) + results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all? + result_for_this_group && results_for_descendants + rescue Pending::SkipDeclaredInExample => ex + for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) } + true + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) } + RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met? + false + ensure + RSpec.current_scope = :after_context_hook + run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks + reporter.example_group_finished(self) + end + end + + # @private + def self.ordering_strategy + order = metadata.fetch(:order, :global) + registry = RSpec.configuration.ordering_registry + + registry.fetch(order) do + warn <<-WARNING.gsub(/^ +\|/, '') + |WARNING: Ignoring unknown ordering specified using `:order => #{order.inspect}` metadata. + | Falling back to configured global ordering. + | Unrecognized ordering specified at: #{location} + WARNING + + registry.fetch(:global) + end + end + + # @private + def self.run_examples(reporter) + ordering_strategy.order(filtered_examples).map do |example| + next if RSpec.world.wants_to_quit + instance = new(example.inspect_output) + set_ivars(instance, before_context_ivars) + succeeded = example.run(instance, reporter) + if !succeeded && reporter.fail_fast_limit_met? + RSpec.world.wants_to_quit = true + end + succeeded + end.all? + end + + # @private + def self.for_filtered_examples(reporter, &block) + filtered_examples.each(&block) + + children.each do |child| + reporter.example_group_started(child) + child.for_filtered_examples(reporter, &block) + reporter.example_group_finished(child) + end + false + end + + # @private + def self.declaration_locations + @declaration_locations ||= [Metadata.location_tuple_from(metadata)] + + examples.map { |e| Metadata.location_tuple_from(e.metadata) } + + FlatMap.flat_map(children, &:declaration_locations) + end + + # @return [String] the unique id of this example group. Pass + # this at the command line to re-run this exact example group. + def self.id + Metadata.id_from(metadata) + end + + # @private + def self.top_level_description + parent_groups.last.description + end + + # @private + def self.set_ivars(instance, ivars) + ivars.each { |name, value| instance.instance_variable_set(name, value) } + end + + if RUBY_VERSION.to_f < 1.9 + # :nocov: + # @private + INSTANCE_VARIABLE_TO_IGNORE = '@__inspect_output'.freeze + # :nocov: + else + # @private + INSTANCE_VARIABLE_TO_IGNORE = :@__inspect_output + end + + # @private + def self.each_instance_variable_for_example(group) + group.instance_variables.each do |ivar| + yield ivar unless ivar == INSTANCE_VARIABLE_TO_IGNORE + end + end + + # @private + def initialize(inspect_output=nil) + @__inspect_output = inspect_output || '(no description provided)' + super() # no args get passed + end + + # @private + def inspect + "#<#{self.class} #{@__inspect_output}>" + end + + unless method_defined?(:singleton_class) # for 1.8.7 + # :nocov: + # @private + def singleton_class + class << self; self; end + end + # :nocov: + end + + # @private + def self.update_inherited_metadata(updates) + metadata.update(updates) do |key, existing_group_value, new_inherited_value| + @user_metadata.key?(key) ? existing_group_value : new_inherited_value + end + + RSpec.configuration.configure_group(self) + examples.each { |ex| ex.update_inherited_metadata(updates) } + children.each { |group| group.update_inherited_metadata(updates) } + end + + # Raised when an RSpec API is called in the wrong scope, such as `before` + # being called from within an example rather than from within an example + # group block. + WrongScopeError = Class.new(NoMethodError) + + def self.method_missing(name, *args) + if method_defined?(name) + raise WrongScopeError, + "`#{name}` is not available on an example group (e.g. a " \ + "`describe` or `context` block). It is only available from " \ + "within individual examples (e.g. `it` blocks) or from " \ + "constructs that run in the scope of an example (e.g. " \ + "`before`, `let`, etc)." + end + + super + end + private_class_method :method_missing + + private + + def method_missing(name, *args) + if self.class.respond_to?(name) + raise WrongScopeError, + "`#{name}` is not available from within an example (e.g. an " \ + "`it` block) or from constructs that run in the scope of an " \ + "example (e.g. `before`, `let`, etc). It is only available " \ + "on an example group (e.g. a `describe` or `context` block)." + end + + super(name, *args) + end + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + end + # rubocop:enable Metrics/ClassLength + + # @private + # Unnamed example group used by `SuiteHookContext`. + class AnonymousExampleGroup < ExampleGroup + def self.metadata + {} + end + end + + # Contains information about the inclusion site of a shared example group. + class SharedExampleGroupInclusionStackFrame + # @return [String] the name of the shared example group + attr_reader :shared_group_name + # @return [String] the location where the shared example was included + attr_reader :inclusion_location + + # @private + def initialize(shared_group_name, inclusion_location) + @shared_group_name = shared_group_name + @inclusion_location = inclusion_location + end + + # @return [String] The {#inclusion_location}, formatted for display by a formatter. + def formatted_inclusion_location + @formatted_inclusion_location ||= begin + RSpec.configuration.backtrace_formatter.backtrace_line( + inclusion_location.sub(/(:\d+):in .+$/, '\1') + ) + end + end + + # @return [String] Description of this stack frame, in the form used by + # RSpec's built-in formatters. + def description + @description ||= "Shared Example Group: #{shared_group_name.inspect} " \ + "called from #{formatted_inclusion_location}" + end + + # @private + def self.current_backtrace + shared_example_group_inclusions.reverse + end + + # @private + def self.with_frame(name, location) + current_stack = shared_example_group_inclusions + if current_stack.any? { |frame| frame.shared_group_name == name } + raise ArgumentError, "can't include shared examples recursively" + else + current_stack << new(name, location) + yield + end + ensure + current_stack.pop + end + + # @private + def self.shared_example_group_inclusions + RSpec::Support.thread_local_data[:shared_example_group_inclusions] ||= [] + end + end + end + + # @private + # + # Namespace for the example group subclasses generated by top-level + # `describe`. + module ExampleGroups + extend Support::RecursiveConstMethods + + def self.assign_const(group) + base_name = base_name_for(group) + const_scope = constant_scope_for(group) + name = disambiguate(base_name, const_scope) + + const_scope.const_set(name, group) + end + + def self.constant_scope_for(group) + const_scope = group.superclass + const_scope = self if const_scope == ::RSpec::Core::ExampleGroup + const_scope + end + + def self.remove_all_constants + constants.each do |constant| + __send__(:remove_const, constant) + end + end + + def self.base_name_for(group) + return "Anonymous".dup if group.description.empty? + + # Convert to CamelCase. + name = ' ' + group.description + name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do + match = ::Regexp.last_match[1] + match.upcase! + match + end + + name.lstrip! # Remove leading whitespace + name.gsub!(/\W/, ''.freeze) # JRuby, RBX and others don't like non-ascii in const names + + # Ruby requires first const letter to be A-Z. Use `Nested` + # as necessary to enforce that. + name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1'.freeze) + + name + end + + if RUBY_VERSION == '1.9.2' + # :nocov: + class << self + alias _base_name_for base_name_for + def base_name_for(group) + _base_name_for(group) + '_' + end + end + private_class_method :_base_name_for + # :nocov: + end + + def self.disambiguate(name, const_scope) + return name unless const_defined_on?(const_scope, name) + + # Add a trailing number if needed to disambiguate from an existing + # constant. + name << "_2" + name.next! while const_defined_on?(const_scope, name) + name + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_status_persister.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_status_persister.rb new file mode 100644 index 0000000000..d628b76e6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/example_status_persister.rb @@ -0,0 +1,235 @@ +RSpec::Support.require_rspec_support "directory_maker" + +module RSpec + module Core + # Persists example ids and their statuses so that we can filter + # to just the ones that failed the last time they ran. + # @private + class ExampleStatusPersister + def self.load_from(file_name) + return [] unless File.exist?(file_name) + ExampleStatusParser.parse(File.read(file_name)) + end + + def self.persist(examples, file_name) + new(examples, file_name).persist + end + + def initialize(examples, file_name) + @examples = examples + @file_name = file_name + end + + def persist + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name)) + File.open(@file_name, File::RDWR | File::CREAT) do |f| + # lock the file while reading / persisting to avoid a race + # condition where parallel or unrelated spec runs race to + # update the same file + f.flock(File::LOCK_EX) + unparsed_previous_runs = f.read + f.rewind + f.write(dump_statuses(unparsed_previous_runs)) + f.flush + f.truncate(f.pos) + end + end + + private + + def dump_statuses(unparsed_previous_runs) + statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs) + merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs) + ExampleStatusDumper.dump(merged_statuses) + end + + def statuses_from_this_run + @examples.map do |ex| + result = ex.execution_result + + { + :example_id => ex.id, + :status => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS, + :run_time => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : "" + } + end + end + end + + # Merges together a list of example statuses from this run + # and a list from previous runs (presumably loaded from disk). + # Each example status object is expected to be a hash with + # at least an `:example_id` and a `:status` key. Examples that + # were loaded but not executed (due to filtering, `--fail-fast` + # or whatever) should have a `:status` of `UNKNOWN_STATUS`. + # + # This will produce a new list that: + # - Will be missing examples from previous runs that we know for sure + # no longer exist. + # - Will have the latest known status for any examples that either + # definitively do exist or may still exist. + # - Is sorted by file name and example definition order, so that + # the saved file is easily scannable if users want to inspect it. + # @private + class ExampleStatusMerger + def self.merge(this_run, from_previous_runs) + new(this_run, from_previous_runs).merge + end + + def initialize(this_run, from_previous_runs) + @this_run = hash_from(this_run) + @from_previous_runs = hash_from(from_previous_runs) + @file_exists_cache = Hash.new { |hash, file| hash[file] = File.exist?(file) } + end + + def merge + delete_previous_examples_that_no_longer_exist + + @this_run.merge(@from_previous_runs) do |_ex_id, new, old| + new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new + end.values.sort_by(&method(:sort_value_from)) + end + + private + + def hash_from(example_list) + example_list.inject({}) do |hash, example| + hash[example.fetch(:example_id)] = example + hash + end + end + + def delete_previous_examples_that_no_longer_exist + @from_previous_runs.delete_if do |ex_id, _| + example_must_no_longer_exist?(ex_id) + end + end + + def example_must_no_longer_exist?(ex_id) + # Obviously, it exists if it was loaded for this spec run... + return false if @this_run.key?(ex_id) + + spec_file = spec_file_from(ex_id) + + # `this_run` includes examples that were loaded but not executed. + # Given that, if the spec file for this example was loaded, + # but the id does not still exist, it's safe to assume that + # the example must no longer exist. + return true if loaded_spec_files.include?(spec_file) + + # The example may still exist as long as the file exists... + !@file_exists_cache[spec_file] + end + + def loaded_spec_files + @loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from))) + end + + def spec_file_from(ex_id) + ex_id.split("[").first + end + + def sort_value_from(example) + file, scoped_id = Example.parse_id(example.fetch(:example_id)) + [file, *scoped_id.split(":").map(&method(:Integer))] + end + end + + # Dumps a list of hashes in a pretty, human readable format + # for later parsing. The hashes are expected to have symbol + # keys and string values, and each hash should have the same + # set of keys. + # @private + class ExampleStatusDumper + def self.dump(examples) + new(examples).dump + end + + def initialize(examples) + @examples = examples + end + + def dump + return nil if @examples.empty? + (formatted_header_rows + formatted_value_rows).join("\n") << "\n" + end + + private + + def formatted_header_rows + @formatted_header_rows ||= begin + dividers = column_widths.map { |w| "-" * w } + [formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)] + end + end + + def formatted_value_rows + @formatted_value_rows ||= rows.map do |row| + formatted_row_from(row) + end + end + + def rows + @rows ||= @examples.map { |ex| ex.values_at(*headers) } + end + + def formatted_row_from(row_values) + padded_values = row_values.each_with_index.map do |value, index| + value.ljust(column_widths[index]) + end + + padded_values.join(" | ") << " |" + end + + def headers + @headers ||= @examples.first.keys + end + + def column_widths + @column_widths ||= begin + value_sets = rows.transpose + + headers.each_with_index.map do |header, index| + values = value_sets[index] << header.to_s + values.map(&:length).max + end + end + end + end + + # Parses a string that has been previously dumped by ExampleStatusDumper. + # Note that this parser is a bit naive in that it does a simple split on + # "\n" and " | ", with no concern for handling escaping. For now, that's + # OK because the values we plan to persist (example id, status, and perhaps + # example duration) are highly unlikely to contain "\n" or " | " -- after + # all, who puts those in file names? + # @private + class ExampleStatusParser + def self.parse(string) + new(string).parse + end + + def initialize(string) + @header_line, _, *@row_lines = string.lines.to_a + end + + def parse + @row_lines.map { |line| parse_row(line) } + end + + private + + def parse_row(line) + Hash[headers.zip(split_line(line))] + end + + def headers + @headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym) + end + + def split_line(line) + line.split(/\s+\|\s+?/, -1) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/filter_manager.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/filter_manager.rb new file mode 100644 index 0000000000..7fe6cb18d2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/filter_manager.rb @@ -0,0 +1,231 @@ +module RSpec + module Core + # @private + class FilterManager + attr_reader :exclusions, :inclusions + + def initialize + @exclusions, @inclusions = FilterRules.build + end + + # @api private + # + # @param file_path [String] + # @param line_numbers [Array] + def add_location(file_path, line_numbers) + # locations is a hash of expanded paths to arrays of line + # numbers to match against. e.g. + # { "path/to/file.rb" => [37, 42] } + add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers) + end + + def add_ids(rerun_path, scoped_ids) + # ids is a hash of relative paths to arrays of ids + # to match against. e.g. + # { "./path/to/file.rb" => ["1:1", "2:4"] } + rerun_path = Metadata.relative_path(File.expand_path rerun_path) + add_path_to_arrays_filter(:ids, rerun_path, scoped_ids) + end + + def empty? + inclusions.empty? && exclusions.empty? + end + + def prune(examples) + # Semantically, this is unnecessary (the filtering below will return the empty + # array unmodified), but for perf reasons it's worth exiting early here. Users + # commonly have top-level examples groups that do not have any direct examples + # and instead have nested groups with examples. In that kind of situation, + # `examples` will be empty. + return examples if examples.empty? + + examples = prune_conditionally_filtered_examples(examples) + + if inclusions.standalone? + examples.select { |e| inclusions.include_example?(e) } + else + locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules + + examples.select do |ex| + file_scoped_include?(ex.metadata, ids, locations) do + !exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex) + end + end + end + end + + def exclude(*args) + exclusions.add(args.last) + end + + def exclude_only(*args) + exclusions.use_only(args.last) + end + + def exclude_with_low_priority(*args) + exclusions.add_with_low_priority(args.last) + end + + def include(*args) + inclusions.add(args.last) + end + + def include_only(*args) + inclusions.use_only(args.last) + end + + def include_with_low_priority(*args) + inclusions.add_with_low_priority(args.last) + end + + private + + def add_path_to_arrays_filter(filter_key, path, values) + filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] } + filter[path].concat(values) + inclusions.add(filter_key => filter) + end + + def prune_conditionally_filtered_examples(examples) + examples.reject do |ex| + meta = ex.metadata + !meta.fetch(:if, true) || meta[:unless] + end + end + + # When a user specifies a particular spec location, that takes priority + # over any exclusion filters (such as if the spec is tagged with `:slow` + # and there is a `:slow => true` exclusion filter), but only for specs + # defined in the same file as the location filters. Excluded specs in + # other files should still be excluded. + def file_scoped_include?(ex_metadata, ids, locations) + no_id_filters = ids[ex_metadata[:rerun_file_path]].empty? + no_location_filters = locations[ + File.expand_path(ex_metadata[:rerun_file_path]) + ].empty? + + return yield if no_location_filters && no_id_filters + + MetadataFilter.filter_applies?(:ids, ids, ex_metadata) || + MetadataFilter.filter_applies?(:locations, locations, ex_metadata) + end + end + + # @private + class FilterRules + PROC_HEX_NUMBER = /0x[0-9a-f]+@?/ + PROJECT_DIR = File.expand_path('.') + + attr_accessor :opposite + attr_reader :rules + + def self.build + exclusions = ExclusionRules.new + inclusions = InclusionRules.new + exclusions.opposite = inclusions + inclusions.opposite = exclusions + [exclusions, inclusions] + end + + def initialize(rules={}) + @rules = rules + end + + def add(updated) + @rules.merge!(updated).each_key { |k| opposite.delete(k) } + end + + def add_with_low_priority(updated) + updated = updated.merge(@rules) + opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v } + @rules.replace(updated) + end + + def use_only(updated) + updated.each_key { |k| opposite.delete(k) } + @rules.replace(updated) + end + + def clear + @rules.clear + end + + def delete(key) + @rules.delete(key) + end + + def fetch(*args, &block) + @rules.fetch(*args, &block) + end + + def [](key) + @rules[key] + end + + def empty? + rules.empty? + end + + def each_pair(&block) + @rules.each_pair(&block) + end + + def description + rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '') + end + + def include_example?(example) + MetadataFilter.apply?(:any?, @rules, example.metadata) + end + end + + # @private + ExclusionRules = FilterRules + + # @private + class InclusionRules < FilterRules + def add(*args) + apply_standalone_filter(*args) || super + end + + def add_with_low_priority(*args) + apply_standalone_filter(*args) || super + end + + def include_example?(example) + @rules.empty? || super + end + + def standalone? + is_standalone_filter?(@rules) + end + + def split_file_scoped_rules + rules_dup = @rules.dup + locations = rules_dup.delete(:locations) { Hash.new([]) } + ids = rules_dup.delete(:ids) { Hash.new([]) } + + return locations, ids, self.class.new(rules_dup) + end + + private + + def apply_standalone_filter(updated) + return true if standalone? + return nil unless is_standalone_filter?(updated) + + replace_filters(updated) + true + end + + def replace_filters(new_rules) + @rules.replace(new_rules) + opposite.clear + end + + def is_standalone_filter?(rules) + rules.key?(:full_description) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/flat_map.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/flat_map.rb new file mode 100644 index 0000000000..0e30cceb51 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/flat_map.rb @@ -0,0 +1,20 @@ +module RSpec + module Core + # @private + module FlatMap + if [].respond_to?(:flat_map) + def flat_map(array, &block) + array.flat_map(&block) + end + else # for 1.8.7 + # :nocov: + def flat_map(array, &block) + array.map(&block).flatten(1) + end + # :nocov: + end + + module_function :flat_map + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters.rb new file mode 100644 index 0000000000..f0238f85cd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters.rb @@ -0,0 +1,279 @@ +RSpec::Support.require_rspec_support "directory_maker" + +# ## Built-in Formatters +# +# * progress (default) - Prints dots for passing examples, `F` for failures, `*` +# for pending. +# * documentation - Prints the docstrings passed to `describe` and `it` methods +# (and their aliases). +# * html +# * json - Useful for archiving data for subsequent analysis. +# +# The progress formatter is the default, but you can choose any one or more of +# the other formatters by passing with the `--format` (or `-f` for short) +# command-line option, e.g. +# +# rspec --format documentation +# +# You can also send the output of multiple formatters to different streams, e.g. +# +# rspec --format documentation --format html --out results.html +# +# This example sends the output of the documentation formatter to `$stdout`, and +# the output of the html formatter to results.html. +# +# ## Custom Formatters +# +# You can tell RSpec to use a custom formatter by passing its path and name to +# the `rspec` command. For example, if you define MyCustomFormatter in +# path/to/my_custom_formatter.rb, you would type this command: +# +# rspec --require path/to/my_custom_formatter.rb --format MyCustomFormatter +# +# The reporter calls every formatter with this protocol: +# +# * To start +# * `start(StartNotification)` +# * Once per example group +# * `example_group_started(GroupNotification)` +# * Once per example +# * `example_started(ExampleNotification)` +# * One of these per example, depending on outcome +# * `example_passed(ExampleNotification)` +# * `example_failed(FailedExampleNotification)` +# * `example_pending(ExampleNotification)` +# * Optionally at any time +# * `message(MessageNotification)` +# * At the end of the suite +# * `stop(ExamplesNotification)` +# * `start_dump(NullNotification)` +# * `dump_pending(ExamplesNotification)` +# * `dump_failures(ExamplesNotification)` +# * `dump_summary(SummaryNotification)` +# * `seed(SeedNotification)` +# * `close(NullNotification)` +# +# Only the notifications to which you subscribe your formatter will be called +# on your formatter. To subscribe your formatter use: +# `RSpec::Core::Formatters#register` e.g. +# +# `RSpec::Core::Formatters.register FormatterClassName, :example_passed, :example_failed` +# +# We recommend you implement the methods yourself; for simplicity we provide the +# default formatter output via our notification objects but if you prefer you +# can subclass `RSpec::Core::Formatters::BaseTextFormatter` and override the +# methods you wish to enhance. +# +# @see RSpec::Core::Formatters::BaseTextFormatter +# @see RSpec::Core::Reporter +module RSpec::Core::Formatters + autoload :DocumentationFormatter, 'rspec/core/formatters/documentation_formatter' + autoload :HtmlFormatter, 'rspec/core/formatters/html_formatter' + autoload :FallbackMessageFormatter, 'rspec/core/formatters/fallback_message_formatter' + autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter' + autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter' + autoload :JsonFormatter, 'rspec/core/formatters/json_formatter' + autoload :BisectDRbFormatter, 'rspec/core/formatters/bisect_drb_formatter' + autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter' + autoload :FailureListFormatter, 'rspec/core/formatters/failure_list_formatter' + + # Register the formatter class + # @param formatter_class [Class] formatter class to register + # @param notifications [Array] one or more notifications to be + # registered to the specified formatter + # + # @see RSpec::Core::Formatters::BaseFormatter + def self.register(formatter_class, *notifications) + Loader.formatters[formatter_class] = notifications + end + + # @api private + # + # `RSpec::Core::Formatters::Loader` is an internal class for + # managing formatters used by a particular configuration. It is + # not expected to be used directly, but only through the configuration + # interface. + class Loader + # @api private + # + # Internal formatters are stored here when loaded. + def self.formatters + @formatters ||= {} + end + + # @api private + def initialize(reporter) + @formatters = [] + @reporter = reporter + self.default_formatter = 'progress' + end + + # @return [Array] the loaded formatters + attr_reader :formatters + + # @return [Reporter] the reporter + attr_reader :reporter + + # @return [String] the default formatter to setup, defaults to `progress` + attr_accessor :default_formatter + + # @private + def prepare_default(output_stream, deprecation_stream) + reporter.prepare_default(self, output_stream, deprecation_stream) + end + + # @private + def setup_default(output_stream, deprecation_stream) + add default_formatter, output_stream if @formatters.empty? + + unless @formatters.any? { |formatter| DeprecationFormatter === formatter } + add DeprecationFormatter, deprecation_stream, output_stream + end + + unless existing_formatter_implements?(:message) + add FallbackMessageFormatter, output_stream + end + + return unless RSpec.configuration.profile_examples? + return if existing_formatter_implements?(:dump_profile) + + add RSpec::Core::Formatters::ProfileFormatter, output_stream + end + + # @private + def add(formatter_to_use, *paths) + # If a formatter instance was passed, we can register it directly, + # with no need for any of the further processing that happens below. + if Loader.formatters.key?(formatter_to_use.class) + register formatter_to_use, notifications_for(formatter_to_use.class) + return + end + + formatter_class = find_formatter(formatter_to_use) + + args = paths.map { |p| p.respond_to?(:puts) ? p : open_stream(p) } + + if !Loader.formatters[formatter_class].nil? + formatter = formatter_class.new(*args) + register formatter, notifications_for(formatter_class) + elsif defined?(RSpec::LegacyFormatters) + formatter = RSpec::LegacyFormatters.load_formatter formatter_class, *args + register formatter, formatter.notifications + else + call_site = "Formatter added at: #{::RSpec::CallerFilter.first_non_rspec_line}" + + RSpec.warn_deprecation <<-WARNING.gsub(/\s*\|/, ' ') + |The #{formatter_class} formatter uses the deprecated formatter + |interface not supported directly by RSpec 3. + | + |To continue to use this formatter you must install the + |`rspec-legacy_formatters` gem, which provides support + |for legacy formatters or upgrade the formatter to a + |compatible version. + | + |#{call_site} + WARNING + end + end + + private + + def find_formatter(formatter_to_use) + built_in_formatter(formatter_to_use) || + custom_formatter(formatter_to_use) || + (raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - " \ + "maybe you meant 'documentation' or 'progress'?.") + end + + def register(formatter, notifications) + return if duplicate_formatter_exists?(formatter) + @reporter.register_listener formatter, *notifications + @formatters << formatter + formatter + end + + def duplicate_formatter_exists?(new_formatter) + @formatters.any? do |formatter| + formatter.class == new_formatter.class && + has_matching_output?(formatter, new_formatter) + end + end + + def has_matching_output?(formatter, new_formatter) + return true unless formatter.respond_to?(:output) && new_formatter.respond_to?(:output) + formatter.output == new_formatter.output + end + + def existing_formatter_implements?(notification) + @reporter.registered_listeners(notification).any? + end + + def built_in_formatter(key) + case key.to_s + when 'd', 'doc', 'documentation' + DocumentationFormatter + when 'h', 'html' + HtmlFormatter + when 'p', 'progress' + ProgressFormatter + when 'j', 'json' + JsonFormatter + when 'bisect-drb' + BisectDRbFormatter + when 'f', 'failures' + FailureListFormatter + end + end + + def notifications_for(formatter_class) + formatter_class.ancestors.inject(::RSpec::Core::Set.new) do |notifications, klass| + notifications.merge Loader.formatters.fetch(klass) { ::RSpec::Core::Set.new } + end + end + + def custom_formatter(formatter_ref) + if Class === formatter_ref + formatter_ref + elsif string_const?(formatter_ref) + begin + formatter_ref.gsub(/^::/, '').split('::').inject(Object) { |a, e| a.const_get e } + rescue NameError + require(path_for(formatter_ref)) ? retry : raise + end + end + end + + def string_const?(str) + str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str + end + + def path_for(const_ref) + underscore_with_fix_for_non_standard_rspec_naming(const_ref) + end + + def underscore_with_fix_for_non_standard_rspec_naming(string) + underscore(string).sub(%r{(^|/)r_spec($|/)}, '\\1rspec\\2') + end + + # activesupport/lib/active_support/inflector/methods.rb, line 48 + def underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + 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 + + def open_stream(path_or_wrapper) + if RSpec::Core::OutputWrapper === path_or_wrapper + path_or_wrapper.output = open_stream(path_or_wrapper.output) + path_or_wrapper + else + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path_or_wrapper)) + File.new(path_or_wrapper, 'w') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_bisect_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_bisect_formatter.rb new file mode 100644 index 0000000000..4159cba891 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_bisect_formatter.rb @@ -0,0 +1,45 @@ +RSpec::Support.require_rspec_core "bisect/utilities" + +module RSpec + module Core + module Formatters + # Contains common logic for formatters used by `--bisect` to communicate results + # back to the bisect runner. + # + # Subclasses must define a `notify_results(all_example_ids, failed_example_ids)` + # method. + # @private + class BaseBisectFormatter + def self.inherited(formatter) + Formatters.register formatter, :start_dump, :example_failed, :example_finished + end + + def initialize(expected_failures) + @all_example_ids = [] + @failed_example_ids = [] + @remaining_failures = expected_failures + end + + def example_failed(notification) + @failed_example_ids << notification.example.id + end + + def example_finished(notification) + @all_example_ids << notification.example.id + return unless @remaining_failures.include?(notification.example.id) + @remaining_failures.delete(notification.example.id) + + status = notification.example.execution_result.status + return if status == :failed && !@remaining_failures.empty? + RSpec.world.wants_to_quit = true + end + + def start_dump(_notification) + # `notify_results` is defined in the subclass + notify_results(Bisect::ExampleSetDescriptor.new( + @all_example_ids, @failed_example_ids)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_formatter.rb new file mode 100644 index 0000000000..dca4b6d609 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_formatter.rb @@ -0,0 +1,70 @@ +RSpec::Support.require_rspec_core "formatters/helpers" +require 'stringio' + +module RSpec + module Core + module Formatters + # RSpec's built-in formatters are all subclasses of + # RSpec::Core::Formatters::BaseFormatter. + # + # @see RSpec::Core::Formatters::BaseTextFormatter + # @see RSpec::Core::Reporter + # @see RSpec::Core::Formatters::Protocol + class BaseFormatter + # All formatters inheriting from this formatter will receive these + # notifications. + Formatters.register self, :start, :example_group_started, :close + attr_accessor :example_group + attr_reader :output + + # @api public + # @param output [IO] the formatter output + # @see RSpec::Core::Formatters::Protocol#initialize + def initialize(output) + @output = output || StringIO.new + @example_group = nil + end + + # @api public + # + # @param notification [StartNotification] + # @see RSpec::Core::Formatters::Protocol#start + def start(notification) + start_sync_output + @example_count = notification.count + end + + # @api public + # + # @param notification [GroupNotification] containing example_group + # subclass of `RSpec::Core::ExampleGroup` + # @see RSpec::Core::Formatters::Protocol#example_group_started + def example_group_started(notification) + @example_group = notification.group + end + + # @api public + # + # @param _notification [NullNotification] (Ignored) + # @see RSpec::Core::Formatters::Protocol#close + def close(_notification) + restore_sync_output + end + + private + + def start_sync_output + @old_sync, output.sync = output.sync, true if output_supports_sync + end + + def restore_sync_output + output.sync = @old_sync if output_supports_sync && !output.closed? + end + + def output_supports_sync + output.respond_to?(:sync=) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_text_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_text_formatter.rb new file mode 100644 index 0000000000..5d9daba4dc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/base_text_formatter.rb @@ -0,0 +1,75 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" + +module RSpec + module Core + module Formatters + # Base for all of RSpec's built-in formatters. See + # RSpec::Core::Formatters::BaseFormatter to learn more about all of the + # methods called by the reporter. + # + # @see RSpec::Core::Formatters::BaseFormatter + # @see RSpec::Core::Reporter + class BaseTextFormatter < BaseFormatter + Formatters.register self, + :message, :dump_summary, :dump_failures, :dump_pending, :seed + + # @api public + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [MessageNotification] containing message + def message(notification) + output.puts notification.message + end + + # @api public + # + # Dumps detailed information about each example failure. + # + # @param notification [NullNotification] + def dump_failures(notification) + return if notification.failure_notifications.empty? + output.puts notification.fully_formatted_failed_examples + end + + # @api public + # + # This method is invoked after the dumping of examples and failures. + # Each parameter is assigned to a corresponding attribute. + # + # @param summary [SummaryNotification] containing duration, + # example_count, failure_count and pending_count + def dump_summary(summary) + output.puts summary.fully_formatted + end + + # @private + def dump_pending(notification) + return if notification.pending_examples.empty? + output.puts notification.fully_formatted_pending_examples + end + + # @private + def seed(notification) + return unless notification.seed_used? + output.puts notification.fully_formatted + end + + # @api public + # + # Invoked at the end of a suite run. Allows the formatter to do any + # tidying up, but be aware that formatter output streams may be used + # elsewhere so don't actually close them. + # + # @param _notification [NullNotification] (Ignored) + def close(_notification) + return if output.closed? + + output.puts + + output.flush + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_drb_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_drb_formatter.rb new file mode 100644 index 0000000000..9fb1299d81 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_drb_formatter.rb @@ -0,0 +1,29 @@ +require 'drb/drb' +RSpec::Support.require_rspec_core "formatters/base_bisect_formatter" + +module RSpec + module Core + module Formatters + # Used by `--bisect`. When it shells out and runs a portion of the suite, it uses + # this formatter as a means to have the status reported back to it, via DRb. + # + # Note that since DRb calls carry considerable overhead compared to normal + # method calls, we try to minimize the number of DRb calls for perf reasons, + # opting to communicate only at the start and the end of the run, rather than + # after each example. + # @private + class BisectDRbFormatter < BaseBisectFormatter + def initialize(_output) + drb_uri = "druby://localhost:#{RSpec.configuration.drb_port}" + @bisect_server = DRbObject.new_with_uri(drb_uri) + RSpec.configuration.files_or_directories_to_run = @bisect_server.files_or_directories_to_run + super(Set.new(@bisect_server.expected_failures)) + end + + def notify_results(results) + @bisect_server.latest_run_results = results + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_progress_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_progress_formatter.rb new file mode 100644 index 0000000000..35f01a663f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/bisect_progress_formatter.rb @@ -0,0 +1,157 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" + +module RSpec + module Core + module Formatters + # @private + # Produces progress output while bisecting. + class BisectProgressFormatter < BaseTextFormatter + def initialize(output, bisect_runner) + super(output) + @bisect_runner = bisect_runner + end + + def bisect_starting(notification) + @round_count = 0 + output.puts bisect_started_message(notification) + output.print "Running suite to find failures..." + end + + def bisect_original_run_complete(notification) + failures = Helpers.pluralize(notification.failed_example_ids.size, "failing example") + non_failures = Helpers.pluralize(notification.non_failing_example_ids.size, "non-failing example") + + output.puts " (#{Helpers.format_duration(notification.duration)})" + output.puts "Starting bisect with #{failures} and #{non_failures}." + end + + def bisect_dependency_check_started(_notification) + output.print "Checking that failure(s) are order-dependent.." + end + + def bisect_dependency_check_passed(_notification) + output.puts " failure appears to be order-dependent" + end + + def bisect_dependency_check_failed(_notification) + output.puts " failure(s) do not require any non-failures to run first" + + if @bisect_runner == :fork + output.puts + output.puts "=" * 80 + output.puts "NOTE: this bisect run used `config.bisect_runner = :fork`, which generally" + output.puts "provides significantly faster bisection runs than the old shell-based runner," + output.puts "but may inaccurately report that no non-failures are required. If this result" + output.puts "is unexpected, consider setting `config.bisect_runner = :shell` and trying again." + output.puts "=" * 80 + end + end + + def bisect_round_started(notification, include_trailing_space=true) + @round_count += 1 + range_desc = notification.candidate_range.description + + output.print "\nRound #{@round_count}: bisecting over non-failing #{range_desc}" + output.print " " if include_trailing_space + end + + def bisect_round_ignoring_ids(notification) + range_desc = notification.ignore_range.description + + output.print " ignoring #{range_desc}" + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_round_detected_multiple_culprits(notification) + output.print " multiple culprits detected - splitting candidates" + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_individual_run_complete(_) + output.print '.' + end + + def bisect_complete(notification) + output.puts "\nBisect complete! Reduced necessary non-failing examples " \ + "from #{notification.original_non_failing_count} to " \ + "#{notification.remaining_count} in " \ + "#{Helpers.format_duration(notification.duration)}." + end + + def bisect_repro_command(notification) + output.puts "\nThe minimal reproduction command is:\n #{notification.repro}" + end + + def bisect_failed(notification) + output.puts "\nBisect failed! #{notification.failure_explanation}" + end + + def bisect_aborted(notification) + output.puts "\n\nBisect aborted!" + output.puts "\nThe most minimal reproduction command discovered so far is:\n #{notification.repro}" + end + + private + + def bisect_started_message(notification) + options = notification.original_cli_args.join(' ') + "Bisect started using options: #{options.inspect}" + end + end + + # @private + # Produces detailed debug output while bisecting. Used when bisect is + # performed with `--bisect=verbose`. Designed to provide details for + # us when we need to troubleshoot bisect bugs. + class BisectDebugFormatter < BisectProgressFormatter + def bisect_original_run_complete(notification) + output.puts " (#{Helpers.format_duration(notification.duration)})" + + output.puts " - #{describe_ids 'Failing examples', notification.failed_example_ids}" + output.puts " - #{describe_ids 'Non-failing examples', notification.non_failing_example_ids}" + end + + def bisect_individual_run_start(notification) + output.print "\n - Running: #{notification.command}" + end + + def bisect_individual_run_complete(notification) + output.print " (#{Helpers.format_duration(notification.duration)})" + end + + def bisect_dependency_check_passed(_notification) + output.print "\n - Failure appears to be order-dependent" + end + + def bisect_dependency_check_failed(_notification) + output.print "\n - Failure is not order-dependent" + end + + def bisect_round_started(notification) + super(notification, false) + end + + def bisect_round_ignoring_ids(notification) + output.print "\n - #{describe_ids 'Examples we can safely ignore', notification.ids_to_ignore}" + output.print "\n - #{describe_ids 'Remaining non-failing examples', notification.remaining_ids}" + end + + def bisect_round_detected_multiple_culprits(_notification) + output.print "\n - Multiple culprits detected - splitting candidates" + end + + private + + def describe_ids(description, ids) + organized_ids = Formatters::Helpers.organize_ids(ids) + formatted_ids = organized_ids.map { |id| " - #{id}" }.join("\n") + "#{description} (#{ids.size}):\n#{formatted_ids}" + end + + def bisect_started_message(notification) + "#{super} and bisect runner: #{notification.bisect_runner.inspect}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/console_codes.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/console_codes.rb new file mode 100644 index 0000000000..79e3062682 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/console_codes.rb @@ -0,0 +1,76 @@ +module RSpec + module Core + module Formatters + # ConsoleCodes provides helpers for formatting console output + # with ANSI codes, e.g. color's and bold. + module ConsoleCodes + # @private + VT100_CODES = + { + :black => 30, + :red => 31, + :green => 32, + :yellow => 33, + :blue => 34, + :magenta => 35, + :cyan => 36, + :white => 37, + :bold_black => '1;30', + :bold_red => '1;31', + :bold_green => '1;32', + :bold_yellow => '1;33', + :bold_blue => '1;34', + :bold_magenta => '1;35', + :bold_cyan => '1;36', + :bold_white => '1;37', + :bold => 1, + } + # @private + VT100_CODE_VALUES = VT100_CODES.invert + + module_function + + # @private + def config_colors_to_methods + @config_colors_to_methods ||= + Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method| + hash[method.to_s.sub(/_color\z/, '').to_sym] = method + hash + end + end + + # Fetches the correct code for the supplied symbol, or checks + # that a code is valid. Defaults to white (37). + # + # @param code_or_symbol [Symbol, Fixnum] Symbol or code to check + # @return [Fixnum] a console code + def console_code_for(code_or_symbol) + if (config_method = config_colors_to_methods[code_or_symbol]) + console_code_for RSpec.configuration.__send__(config_method) + elsif VT100_CODE_VALUES.key?(code_or_symbol) + code_or_symbol + else + VT100_CODES.fetch(code_or_symbol) do + console_code_for(:white) + end + end + end + + # Wraps a piece of text in ANSI codes with the supplied code. Will + # only apply the control code if `RSpec.configuration.color_enabled?` + # returns true. + # + # @param text [String] the text to wrap + # @param code_or_symbol [Symbol, Fixnum] the desired control code + # @return [String] the wrapped text + def wrap(text, code_or_symbol) + if RSpec.configuration.color_enabled? + "\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m" + else + text + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/deprecation_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/deprecation_formatter.rb new file mode 100644 index 0000000000..110a71f5e7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/deprecation_formatter.rb @@ -0,0 +1,223 @@ +RSpec::Support.require_rspec_core "formatters/helpers" + +module RSpec + module Core + module Formatters + # @private + class DeprecationFormatter + Formatters.register self, :deprecation, :deprecation_summary + + attr_reader :count, :deprecation_stream, :summary_stream + + def initialize(deprecation_stream, summary_stream) + @deprecation_stream = deprecation_stream + @summary_stream = summary_stream + @seen_deprecations = Set.new + @count = 0 + end + alias :output :deprecation_stream + + def printer + @printer ||= case deprecation_stream + when File + ImmediatePrinter.new(FileStream.new(deprecation_stream), + summary_stream, self) + when RaiseErrorStream + ImmediatePrinter.new(deprecation_stream, summary_stream, self) + else + DelayedPrinter.new(deprecation_stream, summary_stream, self) + end + end + + def deprecation(notification) + return if @seen_deprecations.include? notification + + @count += 1 + printer.print_deprecation_message notification + @seen_deprecations << notification + end + + def deprecation_summary(_notification) + printer.deprecation_summary + end + + def deprecation_message_for(data) + if data.message + SpecifiedDeprecationMessage.new(data) + else + GeneratedDeprecationMessage.new(data) + end + end + + RAISE_ERROR_CONFIG_NOTICE = <<-EOS.gsub(/^\s+\|/, '') + | + |If you need more of the backtrace for any of these deprecations to + |identify where to make the necessary changes, you can configure + |`config.raise_errors_for_deprecations!`, and it will turn the + |deprecation warnings into errors, giving you the full backtrace. + EOS + + DEPRECATION_STREAM_NOTICE = "Pass `--deprecation-out` or set " \ + "`config.deprecation_stream` to a file for full output." + TOO_MANY_WARNINGS_NOTICE = "Too many similar deprecation messages " \ + "reported, disregarding further reports. #{DEPRECATION_STREAM_NOTICE}" + + # @private + SpecifiedDeprecationMessage = Struct.new(:type) do + def initialize(data) + @message = data.message + super deprecation_type_for(data) + end + + def to_s + output_formatted @message + end + + def too_many_warnings_message + TOO_MANY_WARNINGS_NOTICE + end + + private + + def output_formatted(str) + return str unless str.lines.count > 1 + separator = '-' * 80 + "#{separator}\n#{str.chomp}\n#{separator}" + end + + def deprecation_type_for(data) + data.message.gsub(/(\w+\/)+\w+\.rb:\d+/, '') + end + end + + # @private + GeneratedDeprecationMessage = Struct.new(:type) do + def initialize(data) + @data = data + super data.deprecated + end + + def to_s + msg = String.new("#{@data.deprecated} is deprecated.") + msg << " Use #{@data.replacement} instead." if @data.replacement + msg << " Called from #{@data.call_site}." if @data.call_site + msg + end + + def too_many_warnings_message + "Too many uses of deprecated '#{type}'. #{DEPRECATION_STREAM_NOTICE}" + end + end + + # @private + class ImmediatePrinter + attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter + + def initialize(deprecation_stream, summary_stream, deprecation_formatter) + @deprecation_stream = deprecation_stream + + @summary_stream = summary_stream + @deprecation_formatter = deprecation_formatter + end + + def print_deprecation_message(data) + deprecation_message = deprecation_formatter.deprecation_message_for(data) + deprecation_stream.puts deprecation_message.to_s + end + + def deprecation_summary + return if deprecation_formatter.count.zero? + deprecation_stream.summarize(summary_stream, deprecation_formatter.count) + end + end + + # @private + class DelayedPrinter + TOO_MANY_USES_LIMIT = 4 + + attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter + + def initialize(deprecation_stream, summary_stream, deprecation_formatter) + @deprecation_stream = deprecation_stream + @summary_stream = summary_stream + @deprecation_formatter = deprecation_formatter + @seen_deprecations = Hash.new { 0 } + @deprecation_messages = Hash.new { |h, k| h[k] = [] } + end + + def print_deprecation_message(data) + deprecation_message = deprecation_formatter.deprecation_message_for(data) + @seen_deprecations[deprecation_message] += 1 + + stash_deprecation_message(deprecation_message) + end + + def stash_deprecation_message(deprecation_message) + if @seen_deprecations[deprecation_message] < TOO_MANY_USES_LIMIT + @deprecation_messages[deprecation_message] << deprecation_message.to_s + elsif @seen_deprecations[deprecation_message] == TOO_MANY_USES_LIMIT + @deprecation_messages[deprecation_message] << deprecation_message.too_many_warnings_message + end + end + + def deprecation_summary + return unless @deprecation_messages.any? + + print_deferred_deprecation_warnings + deprecation_stream.puts RAISE_ERROR_CONFIG_NOTICE + + summary_stream.puts "\n#{Helpers.pluralize(deprecation_formatter.count, 'deprecation warning')} total" + end + + def print_deferred_deprecation_warnings + deprecation_stream.puts "\nDeprecation Warnings:\n\n" + @deprecation_messages.keys.sort_by(&:type).each do |deprecation| + messages = @deprecation_messages[deprecation] + messages.each { |msg| deprecation_stream.puts msg } + deprecation_stream.puts + end + end + end + + # @private + # Not really a stream, but is usable in place of one. + class RaiseErrorStream + def puts(message) + raise DeprecationError, message + end + + def summarize(summary_stream, deprecation_count) + summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} found." + end + end + + # @private + # Wraps a File object and provides file-specific operations. + class FileStream + def initialize(file) + @file = file + + # In one of my test suites, I got lots of duplicate output in the + # deprecation file (e.g. 200 of the same deprecation, even though + # the `puts` below was only called 6 times). Setting `sync = true` + # fixes this (but we really have no idea why!). + @file.sync = true + end + + def puts(*args) + @file.puts(*args) + end + + def summarize(summary_stream, deprecation_count) + path = @file.respond_to?(:path) ? @file.path : @file.inspect + summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} logged to #{path}" + puts RAISE_ERROR_CONFIG_NOTICE + end + end + end + end + + # Deprecation Error. + DeprecationError = Class.new(StandardError) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/documentation_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/documentation_formatter.rb new file mode 100644 index 0000000000..f64919c29c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/documentation_formatter.rb @@ -0,0 +1,102 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @private + class DocumentationFormatter < BaseTextFormatter + Formatters.register self, :example_started, :example_group_started, :example_group_finished, + :example_passed, :example_pending, :example_failed + + def initialize(output) + super + @group_level = 0 + + @example_running = false + @messages = [] + end + + def example_started(_notification) + @example_running = true + end + + def example_group_started(notification) + output.puts if @group_level == 0 + output.puts "#{current_indentation}#{notification.group.description.strip}" + + @group_level += 1 + end + + def example_group_finished(_notification) + @group_level -= 1 if @group_level > 0 + end + + def example_passed(passed) + output.puts passed_output(passed.example) + + flush_messages + @example_running = false + end + + def example_pending(pending) + output.puts pending_output(pending.example, + pending.example.execution_result.pending_message) + + flush_messages + @example_running = false + end + + def example_failed(failure) + output.puts failure_output(failure.example) + + flush_messages + @example_running = false + end + + def message(notification) + if @example_running + @messages << notification.message + else + output.puts "#{current_indentation}#{notification.message}" + end + end + + private + + def flush_messages + @messages.each do |message| + output.puts "#{current_indentation(1)}#{message}" + end + + @messages.clear + end + + def passed_output(example) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip}", :success) + end + + def pending_output(example, message) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \ + "(PENDING: #{message})", + :pending) + end + + def failure_output(example) + ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \ + "(FAILED - #{next_failure_index})", + :failure) + end + + def next_failure_index + @next_failure_index ||= 0 + @next_failure_index += 1 + end + + def current_indentation(offset=0) + ' ' * (@group_level + offset) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/exception_presenter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/exception_presenter.rb new file mode 100644 index 0000000000..dcf78a75f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/exception_presenter.rb @@ -0,0 +1,525 @@ +# encoding: utf-8 +RSpec::Support.require_rspec_core "formatters/console_codes" +RSpec::Support.require_rspec_core "formatters/snippet_extractor" +RSpec::Support.require_rspec_core 'formatters/syntax_highlighter' +RSpec::Support.require_rspec_support "encoded_string" + +module RSpec + module Core + module Formatters + # @private + class ExceptionPresenter + attr_reader :exception, :example, :description, :message_color, + :detail_formatter, :extra_detail_formatter, :backtrace_formatter + private :message_color, :detail_formatter, :extra_detail_formatter, :backtrace_formatter + + def initialize(exception, example, options={}) + @exception = exception + @example = example + @message_color = options.fetch(:message_color) { RSpec.configuration.failure_color } + @description = options.fetch(:description) { example.full_description } + @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} } + @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} } + @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter } + @indentation = options.fetch(:indentation, 2) + @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false) + @failure_lines = options[:failure_lines] + end + + def message_lines + add_shared_group_lines(failure_lines, Notifications::NullColorizer) + end + + def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + add_shared_group_lines(failure_lines, colorizer).map do |line| + colorizer.wrap line, message_color + end + end + + def formatted_backtrace(exception=@exception) + backtrace_formatter.format_backtrace(exception.backtrace, example.metadata) + + formatted_cause(exception) + end + + if RSpec::Support::RubyFeatures.supports_exception_cause? + def formatted_cause(exception) + last_cause = final_exception(exception, [exception]) + cause = [] + + if exception.cause + cause << '------------------' + cause << '--- Caused by: ---' + cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/ + + encoded_string(exception_message_string(last_cause)).split("\n").each do |line| + cause << " #{line}" + end + + unless last_cause.backtrace.nil? || last_cause.backtrace.empty? + cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}") + end + end + + cause + end + else + # :nocov: + def formatted_cause(_) + [] + end + # :nocov: + end + + def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted_backtrace.map do |backtrace_info| + colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color + end + end + + def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + lines = fully_formatted_lines(failure_number, colorizer) + lines.join("\n") << "\n" + end + + def fully_formatted_lines(failure_number, colorizer) + lines = [ + encoded_description(description), + detail_formatter.call(example, colorizer), + formatted_message_and_backtrace(colorizer), + extra_detail_formatter.call(failure_number, colorizer), + ].compact.flatten + + lines = indent_lines(lines, failure_number) + lines.unshift("") + lines + end + + private + + def final_exception(exception, previous=[]) + cause = exception.cause + + if cause && Exception === cause && !previous.include?(cause) + previous << cause + final_exception(cause, previous) + else + exception + end + end + + if String.method_defined?(:encoding) + def encoding_of(string) + string.encoding + end + + def encoded_string(string) + RSpec::Support::EncodedString.new(string, Encoding.default_external) + end + else # for 1.8.7 + # :nocov: + def encoding_of(_string) + end + + def encoded_string(string) + RSpec::Support::EncodedString.new(string) + end + # :nocov: + end + + def indent_lines(lines, failure_number) + alignment_basis = ' ' * @indentation + alignment_basis << "#{failure_number}) " if failure_number + indentation = ' ' * alignment_basis.length + + lines.each_with_index.map do |line, index| + if index == 0 + "#{alignment_basis}#{line}" + elsif line.empty? + line + else + "#{indentation}#{line}" + end + end + end + + def exception_class_name(exception=@exception) + name = exception.class.name.to_s + name = "(anonymous error class)" if name == '' + name + end + + def failure_lines + @failure_lines ||= [].tap do |lines| + lines.concat(failure_slash_error_lines) + + sections = [failure_slash_error_lines, exception_lines] + if sections.any? { |section| section.size > 1 } && !exception_lines.first.empty? + lines << '' + end + + lines.concat(exception_lines) + lines.concat(extra_failure_lines) + end + end + + def failure_slash_error_lines + lines = read_failed_lines + if lines.count == 1 + lines[0] = "Failure/Error: #{lines[0].strip}" + else + least_indentation = SnippetExtractor.least_indentation_from(lines) + lines = lines.map { |line| line.sub(/^#{least_indentation}/, ' ') } + lines.unshift('Failure/Error:') + end + lines + end + + # rubocop:disable Lint/RescueException + def exception_message_string(exception) + exception.message.to_s + rescue Exception => other + "A #{exception.class} for which `exception.message.to_s` raises #{other.class}." + end + # rubocop:enable Lint/RescueException + + def exception_lines + @exception_lines ||= begin + lines = [] + lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/ + encoded_string(exception_message_string(exception)).split("\n").each do |line| + lines << (line.empty? ? line : " #{line}") + end + lines + end + end + + def extra_failure_lines + @extra_failure_lines ||= begin + lines = Array(example.metadata[:extra_failure_lines]) + unless lines.empty? + lines.unshift('') unless lines.first == '' + lines.push('') unless lines.last == '' + end + lines + end + end + + def add_shared_group_lines(lines, colorizer) + return lines if @skip_shared_group_trace + + example.metadata[:shared_group_inclusion_backtrace].each do |frame| + lines << colorizer.wrap(frame.description, RSpec.configuration.default_color) + end + + lines + end + + def read_failed_lines + matching_line = find_failed_line + unless matching_line + return ["Unable to find matching line from backtrace"] + end + + file_and_line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/) + + unless file_and_line_number + return ["Unable to infer file and line number from backtrace"] + end + + file_path, line_number = file_and_line_number[1..2] + max_line_count = RSpec.configuration.max_displayed_failure_line_count + lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count) + RSpec.world.syntax_highlighter.highlight(lines) + rescue SnippetExtractor::NoSuchFileError + ["Unable to find #{file_path} to read failed line"] + rescue SnippetExtractor::NoSuchLineError + ["Unable to find matching line in #{file_path}"] + rescue SecurityError + ["Unable to read failed line"] + end + + def find_failed_line + line_regex = RSpec.configuration.in_project_source_dir_regex + loaded_spec_files = RSpec.configuration.loaded_spec_files + + exception_backtrace.reject! do |line| + line.start_with?(" "#{@example.full_description} FIXED", + :message_color => RSpec.configuration.fixed_color, + :failure_lines => [ + "Expected pending '#{@execution_result.pending_message}' to fail. No error was raised." + ] + } + elsif @execution_result.status == :pending + { + :message_color => RSpec.configuration.pending_color, + :detail_formatter => PENDING_DETAIL_FORMATTER + } + end + end + + def with_multiple_error_options_as_needed(exception, options) + return options unless multiple_exceptions_error?(exception) + + options = options.merge( + :failure_lines => [], + :extra_detail_formatter => sub_failure_list_formatter(exception, options[:message_color]), + :detail_formatter => multiple_exception_summarizer(exception, + options[:detail_formatter], + options[:message_color]) + ) + + return options unless exception.aggregation_metadata[:hide_backtrace] + options[:backtrace_formatter] = EmptyBacktraceFormatter + options + end + + def multiple_exceptions_error?(exception) + MultipleExceptionError::InterfaceTag === exception + end + + def multiple_exception_summarizer(exception, prior_detail_formatter, color) + lambda do |example, colorizer| + summary = if exception.aggregation_metadata[:hide_backtrace] + # Since the backtrace is hidden, the subfailures will come + # immediately after this, and using `:` will read well. + "Got #{exception.exception_count_description}:" + else + # The backtrace comes after this, so using a `:` doesn't make sense + # since the failures may be many lines below. + "#{exception.summary}." + end + + summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color) + return summary unless prior_detail_formatter + [ + prior_detail_formatter.call(example, colorizer), + summary + ] + end + end + + def sub_failure_list_formatter(exception, message_color) + common_backtrace_truncater = CommonBacktraceTruncater.new(exception) + + lambda do |failure_number, colorizer| + FlatMap.flat_map(exception.all_exceptions.each_with_index) do |failure, index| + options = with_multiple_error_options_as_needed( + failure, + :description => nil, + :indentation => 0, + :message_color => message_color || RSpec.configuration.failure_color, + :skip_shared_group_trace => true + ) + + failure = common_backtrace_truncater.with_truncated_backtrace(failure) + presenter = ExceptionPresenter.new(failure, @example, options) + presenter.fully_formatted_lines( + "#{failure_number ? "#{failure_number}." : ''}#{index + 1}", + colorizer + ) + end + end + end + + # @private + # Used to prevent a confusing backtrace from showing up from the `aggregate_failures` + # block declared for `:aggregate_failures` metadata. + module EmptyBacktraceFormatter + def self.format_backtrace(*) + [] + end + end + + # @private + class CommonBacktraceTruncater + def initialize(parent) + @parent = parent + end + + def with_truncated_backtrace(child) + child_bt = child.backtrace + parent_bt = @parent.backtrace + return child if child_bt.nil? || child_bt.empty? || parent_bt.nil? + + index_before_first_common_frame = -1.downto(-child_bt.size).find do |index| + parent_bt[index] != child_bt[index] + end + + return child if index_before_first_common_frame.nil? + return child if index_before_first_common_frame == -1 + + child = child.dup + child.set_backtrace(child_bt[0..index_before_first_common_frame]) + child + end + end + end + + # @private + PENDING_DETAIL_FORMATTER = Proc.new do |example, colorizer| + colorizer.wrap("# #{example.execution_result.pending_message}", :detail) + end + end + end + + # Provides a single exception instance that provides access to + # multiple sub-exceptions. This is used in situations where a single + # individual spec has multiple exceptions, such as one in the `it` block + # and one in an `after` block. + class MultipleExceptionError < StandardError + # @private + # Used so there is a common module in the ancestor chain of this class + # and `RSpec::Expectations::MultipleExpectationsNotMetError`, which allows + # code to detect exceptions that are instances of either, without first + # checking to see if rspec-expectations is loaded. + module InterfaceTag + # Appends the provided exception to the list. + # @param exception [Exception] Exception to append to the list. + # @private + def add(exception) + # `PendingExampleFixedError` can be assigned to an example that initially has no + # failures, but when the `aggregate_failures` around hook completes, it notifies of + # a failure. If we do not ignore `PendingExampleFixedError` it would be surfaced to + # the user as part of a multiple exception error, which is undesirable. While it's + # pretty weird we handle this here, it's the best solution I've been able to come + # up with, and `PendingExampleFixedError` always represents the _lack_ of any exception + # so clearly when we are transitioning to a `MultipleExceptionError`, it makes sense to + # ignore it. + return if Pending::PendingExampleFixedError === exception + + return if exception == self + + all_exceptions << exception + + if exception.class.name =~ /RSpec/ + failures << exception + else + other_errors << exception + end + end + + # Provides a way to force `ex` to be something that satisfies the multiple + # exception error interface. If it already satisfies it, it will be returned; + # otherwise it will wrap it in a `MultipleExceptionError`. + # @private + def self.for(ex) + return ex if self === ex + MultipleExceptionError.new(ex) + end + end + + include InterfaceTag + + # @return [Array] The list of failures. + attr_reader :failures + + # @return [Array] The list of other errors. + attr_reader :other_errors + + # @return [Array] The list of failures and other exceptions, combined. + attr_reader :all_exceptions + + # @return [Hash] Metadata used by RSpec for formatting purposes. + attr_reader :aggregation_metadata + + # @return [nil] Provided only for interface compatibility with + # `RSpec::Expectations::MultipleExpectationsNotMetError`. + attr_reader :aggregation_block_label + + # @param exceptions [Array] The initial list of exceptions. + def initialize(*exceptions) + super() + + @failures = [] + @other_errors = [] + @all_exceptions = [] + @aggregation_metadata = { :hide_backtrace => true } + @aggregation_block_label = nil + + exceptions.each { |e| add e } + end + + # @return [String] Combines all the exception messages into a single string. + # @note RSpec does not actually use this -- instead it formats each exception + # individually. + def message + all_exceptions.map(&:message).join("\n\n") + end + + # @return [String] A summary of the failure, including the block label and a count of failures. + def summary + "Got #{exception_count_description}" + end + + # return [String] A description of the failure/error counts. + def exception_count_description + failure_count = Formatters::Helpers.pluralize(failures.size, "failure") + return failure_count if other_errors.empty? + error_count = Formatters::Helpers.pluralize(other_errors.size, "other error") + "#{failure_count} and #{error_count}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/failure_list_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/failure_list_formatter.rb new file mode 100644 index 0000000000..a071dfe82f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/failure_list_formatter.rb @@ -0,0 +1,23 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" + +module RSpec + module Core + module Formatters + # @private + class FailureListFormatter < BaseFormatter + Formatters.register self, :example_failed, :dump_profile, :message + + def example_failed(failure) + output.puts "#{failure.example.location}:#{failure.example.description}" + end + + # Discard profile and messages + # + # These outputs are not really relevant in the context of this failure + # list formatter. + def dump_profile(_profile); end + def message(_message); end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/fallback_message_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/fallback_message_formatter.rb new file mode 100644 index 0000000000..db4423ff18 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/fallback_message_formatter.rb @@ -0,0 +1,28 @@ +module RSpec + module Core + module Formatters + # @api private + # Formatter for providing message output as a fallback when no other + # profiler implements #message + class FallbackMessageFormatter + Formatters.register self, :message + + def initialize(output) + @output = output + end + + # @private + attr_reader :output + + # @api public + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [MessageNotification] containing message + def message(notification) + output.puts notification.message + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/helpers.rb new file mode 100644 index 0000000000..f62709da8d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/helpers.rb @@ -0,0 +1,118 @@ +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec + module Core + module Formatters + # Formatters helpers. + module Helpers + # @private + SUB_SECOND_PRECISION = 5 + + # @private + DEFAULT_PRECISION = 2 + + # @api private + # + # Formats seconds into a human-readable string. + # + # @param duration [Float, Fixnum] in seconds + # @return [String] human-readable time + # + # @example + # format_duration(1) #=> "1 minute 1 second" + # format_duration(135.14) #=> "2 minutes 15.14 seconds" + def self.format_duration(duration) + precision = case + when duration < 1 then SUB_SECOND_PRECISION + when duration < 120 then DEFAULT_PRECISION + when duration < 300 then 1 + else 0 + end + + if duration > 60 + minutes = (duration.round / 60).to_i + seconds = (duration - minutes * 60) + + "#{pluralize(minutes, 'minute')} #{pluralize(format_seconds(seconds, precision), 'second')}" + else + pluralize(format_seconds(duration, precision), 'second') + end + end + + # @api private + # + # Formats seconds to have 5 digits of precision with trailing zeros + # removed if the number is less than 1 or with 2 digits of precision if + # the number is greater than zero. + # + # @param float [Float] + # @return [String] formatted float + # + # @example + # format_seconds(0.000006) #=> "0.00001" + # format_seconds(0.020000) #=> "0.02" + # format_seconds(1.00000000001) #=> "1" + # + # The precision used is set in {Helpers::SUB_SECOND_PRECISION} and + # {Helpers::DEFAULT_PRECISION}. + # + # @see #strip_trailing_zeroes + def self.format_seconds(float, precision=nil) + return '0' if float < 0 + precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION + formatted = "%.#{precision}f" % float + strip_trailing_zeroes(formatted) + end + + # @api private + # + # Remove trailing zeros from a string. + # + # Only remove trailing zeros after a decimal place. + # see: http://rubular.com/r/ojtTydOgpn + # + # @param string [String] string with trailing zeros + # @return [String] string with trailing zeros removed + def self.strip_trailing_zeroes(string) + string.sub(/(?:(\..*[^0])0+|\.0+)$/, '\1') + end + private_class_method :strip_trailing_zeroes + + # @api private + # + # Pluralize a word based on a count. + # + # @param count [Fixnum] number of objects + # @param string [String] word to be pluralized + # @return [String] pluralized word + def self.pluralize(count, string) + pluralized_string = if count.to_f == 1 + string + elsif string.end_with?('s') # e.g. "process" + "#{string}es" # e.g. "processes" + else + "#{string}s" + end + + "#{count} #{pluralized_string}" + end + + # @api private + # Given a list of example ids, organizes them into a compact, ordered list. + def self.organize_ids(ids) + grouped = ids.inject(Hash.new { |h, k| h[k] = [] }) do |hash, id| + file, id = Example.parse_id(id) + hash[file] << id + hash + end + + grouped.sort_by(&:first).map do |file, grouped_ids| + grouped_ids = grouped_ids.sort_by { |id| id.split(':').map(&:to_i) } + id = Metadata.id_from(:rerun_file_path => file, :scoped_id => grouped_ids.join(',')) + ShellEscape.conditionally_quote(id) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_formatter.rb new file mode 100644 index 0000000000..e7eff6619d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_formatter.rb @@ -0,0 +1,153 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/html_printer" + +module RSpec + module Core + module Formatters + # @private + class HtmlFormatter < BaseFormatter + Formatters.register self, :start, :example_group_started, :start_dump, + :example_started, :example_passed, :example_failed, + :example_pending, :dump_summary + + def initialize(output) + super(output) + @failed_examples = [] + @example_group_number = 0 + @example_number = 0 + @header_red = nil + @printer = HtmlPrinter.new(output) + end + + def start(notification) + super + @printer.print_html_start + @printer.flush + end + + def example_group_started(notification) + super + @example_group_red = false + @example_group_number += 1 + + @printer.print_example_group_end unless example_group_number == 1 + @printer.print_example_group_start(example_group_number, + notification.group.description, + notification.group.parent_groups.size) + @printer.flush + end + + def start_dump(_notification) + @printer.print_example_group_end + @printer.flush + end + + def example_started(_notification) + @example_number += 1 + end + + def example_passed(passed) + @printer.move_progress(percent_done) + @printer.print_example_passed(passed.example.description, passed.example.execution_result.run_time) + @printer.flush + end + + def example_failed(failure) + @failed_examples << failure.example + unless @header_red + @header_red = true + @printer.make_header_red + end + + unless @example_group_red + @example_group_red = true + @printer.make_example_group_header_red(example_group_number) + end + + @printer.move_progress(percent_done) + + example = failure.example + + exception = failure.exception + message_lines = failure.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer) + exception_details = if exception + { + # drop 2 removes the description (regardless of newlines) and leading blank line + :message => message_lines.drop(2).join("\n"), + :backtrace => failure.formatted_backtrace.join("\n"), + } + end + extra = extra_failure_content(failure) + + @printer.print_example_failed( + example.execution_result.pending_fixed, + example.description, + example.execution_result.run_time, + @failed_examples.size, + exception_details, + (extra == "") ? false : extra + ) + @printer.flush + end + + def example_pending(pending) + example = pending.example + + @printer.make_header_yellow unless @header_red + @printer.make_example_group_header_yellow(example_group_number) unless @example_group_red + @printer.move_progress(percent_done) + @printer.print_example_pending(example.description, example.execution_result.pending_message) + @printer.flush + end + + def dump_summary(summary) + @printer.print_summary( + summary.duration, + summary.example_count, + summary.failure_count, + summary.pending_count + ) + @printer.flush + end + + private + + # If these methods are declared with attr_reader Ruby will issue a + # warning because they are private. + # rubocop:disable Style/TrivialAccessors + + # The number of the currently running example_group. + def example_group_number + @example_group_number + end + + # The number of the currently running example (a global counter). + def example_number + @example_number + end + # rubocop:enable Style/TrivialAccessors + + def percent_done + result = 100.0 + if @example_count > 0 + result = (((example_number).to_f / @example_count.to_f * 1000).to_i / 10.0).to_f + end + result + end + + # Override this method if you wish to output extra HTML for a failed + # spec. For example, you could output links to images or other files + # produced during the specs. + def extra_failure_content(failure) + RSpec::Support.require_rspec_core "formatters/html_snippet_extractor" + backtrace = (failure.exception.backtrace || []).map do |line| + RSpec.configuration.backtrace_formatter.backtrace_line(line) + end + backtrace.compact! + @snippet_extractor ||= HtmlSnippetExtractor.new + "
    #{@snippet_extractor.snippet(backtrace)}
    " + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_printer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_printer.rb new file mode 100644 index 0000000000..79d27e1f37 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_printer.rb @@ -0,0 +1,412 @@ +require 'erb' + +module RSpec + module Core + module Formatters + # @private + class HtmlPrinter + include ERB::Util # For the #h method. + def initialize(output) + @output = output + end + + def print_html_start + @output.puts HTML_HEADER + @output.puts REPORT_HEADER + end + + def print_example_group_end + @output.puts " " + @output.puts "
    " + end + + def print_example_group_start(group_id, description, number_of_parents) + @output.puts "
    " + @output.puts "
    " + @output.puts "
    #{h(description)}
    " + end + + def print_example_passed(description, run_time) + formatted_run_time = "%.5f" % run_time + @output.puts "
    " \ + "#{h(description)}" \ + "#{formatted_run_time}s
    " + end + + def print_example_failed(pending_fixed, description, run_time, failure_id, + exception, extra_content) + formatted_run_time = "%.5f" % run_time + + @output.puts "
    " + @output.puts " #{h(description)}" + @output.puts " #{formatted_run_time}s" + @output.puts "
    " + if exception + @output.puts "
    #{h(exception[:message])}
    " + @output.puts "
    #{h exception[:backtrace]}
    " + end + @output.puts extra_content if extra_content + @output.puts "
    " + @output.puts "
    " + end + + def print_example_pending(description, pending_message) + @output.puts "
    " \ + "#{h(description)} " \ + "(PENDING: #{h(pending_message)})
    " + end + + def print_summary(duration, example_count, failure_count, pending_count) + totals = String.new( + "#{example_count} example#{'s' unless example_count == 1}, " + ) + totals << "#{failure_count} failure#{'s' unless failure_count == 1}" + totals << ", #{pending_count} pending" if pending_count > 0 + + formatted_duration = "%.5f" % duration + + @output.puts "" + @output.puts "" + @output.puts "
    " + @output.puts "
    " + @output.puts "" + @output.puts "" + end + + def flush + @output.flush + end + + def move_progress(percent_done) + @output.puts " " + @output.flush + end + + def make_header_red + @output.puts " " + end + + def make_header_yellow + @output.puts " " + end + + def make_example_group_header_red(group_id) + @output.puts " " + @output.puts " " + end + + def make_example_group_header_yellow(group_id) + @output.puts " " + @output.puts " " + end + + private + + def indentation_style(number_of_parents) + "style=\"margin-left: #{(number_of_parents - 1) * 15}px;\"" + end + + REPORT_HEADER = <<-EOF +
    + +
    +
    +

    RSpec Code Examples

    +
    + +
    + + + +
    + +
    +

     

    +

     

    +
    +
    + + +
    +EOF + + GLOBAL_SCRIPTS = <<-EOF + +function addClass(element_id, classname) { + document.getElementById(element_id).className += (" " + classname); +} + +function removeClass(element_id, classname) { + var elem = document.getElementById(element_id); + var classlist = elem.className.replace(classname,''); + elem.className = classlist; +} + +function moveProgressBar(percentDone) { + document.getElementById("rspec-header").style.width = percentDone +"%"; +} + +function makeRed(element_id) { + removeClass(element_id, 'passed'); + removeClass(element_id, 'not_implemented'); + addClass(element_id,'failed'); +} + +function makeYellow(element_id) { + var elem = document.getElementById(element_id); + if (elem.className.indexOf("failed") == -1) { // class doesn't includes failed + if (elem.className.indexOf("not_implemented") == -1) { // class doesn't include not_implemented + removeClass(element_id, 'passed'); + addClass(element_id,'not_implemented'); + } + } +} + +function apply_filters() { + var passed_filter = document.getElementById('passed_checkbox').checked; + var failed_filter = document.getElementById('failed_checkbox').checked; + var pending_filter = document.getElementById('pending_checkbox').checked; + + assign_display_style("example passed", passed_filter); + assign_display_style("example failed", failed_filter); + assign_display_style("example not_implemented", pending_filter); + + assign_display_style_for_group("example_group passed", passed_filter); + assign_display_style_for_group("example_group not_implemented", pending_filter, pending_filter || passed_filter); + assign_display_style_for_group("example_group failed", failed_filter, failed_filter || pending_filter || passed_filter); +} + +function get_display_style(display_flag) { + var style_mode = 'none'; + if (display_flag == true) { + style_mode = 'block'; + } + return style_mode; +} + +function assign_display_style(classname, display_flag) { + var style_mode = get_display_style(display_flag); + var elems = document.getElementsByClassName(classname) + for (var i=0; i + + + RSpec results + + + + + + + + +EOF + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_snippet_extractor.rb new file mode 100644 index 0000000000..50fe297cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/html_snippet_extractor.rb @@ -0,0 +1,120 @@ +module RSpec + module Core + module Formatters + # @api private + # + # Extracts code snippets by looking at the backtrace of the passed error + # and applies synax highlighting and line numbers using html. + class HtmlSnippetExtractor + # @private + module NullConverter + def self.convert(code) + %Q(#{code}\n# Install the coderay gem to get syntax highlighting) + end + end + + # @private + module CoderayConverter + def self.convert(code) + CodeRay.scan(code, :ruby).html(:line_numbers => false) + end + end + + # rubocop:disable Style/ClassVars + # @private + @@converter = NullConverter + + begin + require 'coderay' + RSpec::Support.require_rspec_core 'formatters/syntax_highlighter' + RSpec::Core::Formatters::SyntaxHighlighter.attempt_to_add_rspec_terms_to_coderay_keywords + @@converter = CoderayConverter + # rubocop:disable Lint/HandleExceptions + rescue LoadError + # it'll fall back to the NullConverter assigned above + # rubocop:enable Lint/HandleExceptions + end + + # rubocop:enable Style/ClassVars + + # @api private + # + # Extract lines of code corresponding to a backtrace. + # + # @param backtrace [String] the backtrace from a test failure + # @return [String] highlighted code snippet indicating where the test + # failure occurred + # + # @see #post_process + def snippet(backtrace) + raw_code, line = snippet_for(backtrace[0]) + highlighted = @@converter.convert(raw_code) + post_process(highlighted, line) + end + # rubocop:enable Style/ClassVars + + # @api private + # + # Create a snippet from a line of code. + # + # @param error_line [String] file name with line number (i.e. + # 'foo_spec.rb:12') + # @return [String] lines around the target line within the file + # + # @see #lines_around + def snippet_for(error_line) + if error_line =~ /(.*):(\d+)/ + file = Regexp.last_match[1] + line = Regexp.last_match[2].to_i + [lines_around(file, line), line] + else + ["# Couldn't get snippet for #{error_line}", 1] + end + end + + # @api private + # + # Extract lines of code centered around a particular line within a + # source file. + # + # @param file [String] filename + # @param line [Fixnum] line number + # @return [String] lines around the target line within the file (2 above + # and 1 below). + def lines_around(file, line) + if File.file?(file) + lines = File.read(file).split("\n") + min = [0, line - 3].max + max = [line + 1, lines.length - 1].min + selected_lines = [] + selected_lines.join("\n") + lines[min..max].join("\n") + else + "# Couldn't get snippet for #{file}" + end + rescue SecurityError + "# Couldn't get snippet for #{file}" + end + + # @api private + # + # Adds line numbers to all lines and highlights the line where the + # failure occurred using html `span` tags. + # + # @param highlighted [String] syntax-highlighted snippet surrounding the + # offending line of code + # @param offending_line [Fixnum] line where failure occurred + # @return [String] completed snippet + def post_process(highlighted, offending_line) + new_lines = [] + highlighted.split("\n").each_with_index do |line, i| + new_line = "#{offending_line + i - 2}#{line}" + new_line = "#{new_line}" if i == 2 + new_lines << new_line + end + new_lines.join("\n") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/json_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/json_formatter.rb new file mode 100644 index 0000000000..ff15d90b26 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/json_formatter.rb @@ -0,0 +1,102 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" +require 'json' + +module RSpec + module Core + module Formatters + # @private + class JsonFormatter < BaseFormatter + Formatters.register self, :message, :dump_summary, :dump_profile, :stop, :seed, :close + + attr_reader :output_hash + + def initialize(output) + super + @output_hash = { + :version => RSpec::Core::Version::STRING + } + end + + def message(notification) + (@output_hash[:messages] ||= []) << notification.message + end + + def dump_summary(summary) + @output_hash[:summary] = { + :duration => summary.duration, + :example_count => summary.example_count, + :failure_count => summary.failure_count, + :pending_count => summary.pending_count, + :errors_outside_of_examples_count => summary.errors_outside_of_examples_count + } + @output_hash[:summary_line] = summary.totals_line + end + + def stop(notification) + @output_hash[:examples] = notification.examples.map do |example| + format_example(example).tap do |hash| + e = example.exception + if e + hash[:exception] = { + :class => e.class.name, + :message => e.message, + :backtrace => e.backtrace, + } + end + end + end + end + + def seed(notification) + return unless notification.seed_used? + @output_hash[:seed] = notification.seed + end + + def close(_notification) + output.write @output_hash.to_json + end + + def dump_profile(profile) + @output_hash[:profile] = {} + dump_profile_slowest_examples(profile) + dump_profile_slowest_example_groups(profile) + end + + # @api private + def dump_profile_slowest_examples(profile) + @output_hash[:profile] = {} + @output_hash[:profile][:examples] = profile.slowest_examples.map do |example| + format_example(example).tap do |hash| + hash[:run_time] = example.execution_result.run_time + end + end + @output_hash[:profile][:slowest] = profile.slow_duration + @output_hash[:profile][:total] = profile.duration + end + + # @api private + def dump_profile_slowest_example_groups(profile) + @output_hash[:profile] ||= {} + @output_hash[:profile][:groups] = profile.slowest_groups.map do |loc, hash| + hash.update(:location => loc) + end + end + + private + + def format_example(example) + { + :id => example.id, + :description => example.description, + :full_description => example.full_description, + :status => example.execution_result.status.to_s, + :file_path => example.metadata[:file_path], + :line_number => example.metadata[:line_number], + :run_time => example.execution_result.run_time, + :pending_message => example.execution_result.pending_message, + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/profile_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/profile_formatter.rb new file mode 100644 index 0000000000..4b95d9386f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/profile_formatter.rb @@ -0,0 +1,68 @@ +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @api private + # Formatter for providing profile output. + class ProfileFormatter + Formatters.register self, :dump_profile + + def initialize(output) + @output = output + end + + # @private + attr_reader :output + + # @api public + # + # This method is invoked after the dumping the summary if profiling is + # enabled. + # + # @param profile [ProfileNotification] containing duration, + # slowest_examples and slowest_example_groups + def dump_profile(profile) + dump_profile_slowest_examples(profile) + dump_profile_slowest_example_groups(profile) + end + + private + + def dump_profile_slowest_examples(profile) + @output.puts "\nTop #{profile.slowest_examples.size} slowest " \ + "examples (#{Helpers.format_seconds(profile.slow_duration)} " \ + "seconds, #{profile.percentage}% of total time):\n" + + profile.slowest_examples.each do |example| + @output.puts " #{example.full_description}" + @output.puts " #{bold(Helpers.format_seconds(example.execution_result.run_time))} " \ + "#{bold("seconds")} #{format_caller(example.location)}" + end + end + + def dump_profile_slowest_example_groups(profile) + return if profile.slowest_groups.empty? + + @output.puts "\nTop #{profile.slowest_groups.size} slowest example groups:" + profile.slowest_groups.each do |loc, hash| + average = "#{bold(Helpers.format_seconds(hash[:average]))} #{bold("seconds")} average" + total = "#{Helpers.format_seconds(hash[:total_time])} seconds" + count = Helpers.pluralize(hash[:count], "example") + @output.puts " #{hash[:description]}" + @output.puts " #{average} (#{total} / #{count}) #{loc}" + end + end + + def format_caller(caller_info) + RSpec.configuration.backtrace_formatter.backtrace_line( + caller_info.to_s.split(':in `block').first) + end + + def bold(text) + ConsoleCodes.wrap(text, :bold) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/progress_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/progress_formatter.rb new file mode 100644 index 0000000000..81e0beb407 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/progress_formatter.rb @@ -0,0 +1,29 @@ +RSpec::Support.require_rspec_core "formatters/base_text_formatter" +RSpec::Support.require_rspec_core "formatters/console_codes" + +module RSpec + module Core + module Formatters + # @private + class ProgressFormatter < BaseTextFormatter + Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump + + def example_passed(_notification) + output.print ConsoleCodes.wrap('.', :success) + end + + def example_pending(_notification) + output.print ConsoleCodes.wrap('*', :pending) + end + + def example_failed(_notification) + output.print ConsoleCodes.wrap('F', :failure) + end + + def start_dump(_notification) + output.puts + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/protocol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/protocol.rb new file mode 100644 index 0000000000..12fc71f8d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/protocol.rb @@ -0,0 +1,182 @@ +module RSpec + module Core + module Formatters + # This class isn't loaded at runtime but serves to document all of the + # notifications implemented as part of the standard interface. The + # reporter will issue these during a normal test suite run, but a + # formatter will only receive those notifications it has registered + # itself to receive. To register a formatter call: + # + # `::RSpec::Core::Formatters.register class, :list, :of, :notifications` + # + # e.g. + # + # `::RSpec::Core::Formatters.register self, :start, :example_started` + # + # @see RSpec::Core::Formatters::BaseFormatter + # @see RSpec::Core::Formatters::BaseTextFormatter + # @see RSpec::Core::Reporter + class Protocol + # @method initialize(output) + # @api public + # + # @param output [IO] the formatter output + + # @method start(notification) + # @api public + # @group Suite Notifications + # + # This method is invoked before any examples are run, right after + # they have all been collected. This can be useful for special + # formatters that need to provide progress on feedback (graphical ones). + # + # This will only be invoked once, and the next one to be invoked + # is {#example_group_started}. + # + # @param notification [Notifications::StartNotification] + + # @method example_group_started(notification) + # @api public + # @group Group Notifications + # + # This method is invoked at the beginning of the execution of each + # example group. + # + # The next method to be invoked after this is {#example_passed}, + # {#example_pending}, or {#example_group_finished}. + # + # @param notification [Notifications::GroupNotification] containing example_group + # subclass of {ExampleGroup} + + # @method example_group_finished(notification) + # @api public + # @group Group Notifications + # + # Invoked at the end of the execution of each example group. + # + # @param notification [Notifications::GroupNotification] containing example_group + # subclass of {ExampleGroup} + + # @method example_started(notification) + # @api public + # @group Example Notifications + # + # Invoked at the beginning of the execution of each example. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_finished(notification) + # @api public + # @group Example Notifications + # + # Invoked at the end of the execution of each example. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_passed(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example passes. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_pending(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example is pending. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method example_failed(notification) + # @api public + # @group Example Notifications + # + # Invoked when an example fails. + # + # @param notification [Notifications::ExampleNotification] containing example subclass + # of {Example} + + # @method message(notification) + # @api public + # @group Suite Notifications + # + # Used by the reporter to send messages to the output stream. + # + # @param notification [Notifications::MessageNotification] containing message + + # @method stop(notification) + # @api public + # @group Suite Notifications + # + # Invoked after all examples have executed, before dumping post-run + # reports. + # + # @param notification [Notifications::NullNotification] + + # @method start_dump(notification) + # @api public + # @group Suite Notifications + # + # This method is invoked after all of the examples have executed. The + # next method to be invoked after this one is {#dump_failures} + # (BaseTextFormatter then calls {#dump_failures} once for each failed + # example). + # + # @param notification [Notifications::NullNotification] + + # @method dump_failures(notification) + # @api public + # @group Suite Notifications + # + # Dumps detailed information about each example failure. + # + # @param notification [Notifications::NullNotification] + + # @method dump_summary(summary) + # @api public + # @group Suite Notifications + # + # This method is invoked after the dumping of examples and failures. + # Each parameter is assigned to a corresponding attribute. + # + # @param summary [Notifications::SummaryNotification] containing duration, + # example_count, failure_count and pending_count + + # @method dump_profile(profile) + # @api public + # @group Suite Notifications + # + # This method is invoked after the dumping the summary if profiling is + # enabled. + # + # @param profile [Notifications::ProfileNotification] containing duration, + # slowest_examples and slowest_example_groups + + # @method dump_pending(notification) + # @api public + # @group Suite Notifications + # + # Outputs a report of pending examples. This gets invoked + # after the summary if option is set to do so. + # + # @param notification [Notifications::NullNotification] + + # @method close(notification) + # @api public + # @group Suite Notifications + # + # Invoked at the end of a suite run. Allows the formatter to do any + # tidying up, but be aware that formatter output streams may be used + # elsewhere so don't actually close them. + # + # @param notification [Notifications::NullNotification] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/snippet_extractor.rb new file mode 100644 index 0000000000..c585db41b0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/snippet_extractor.rb @@ -0,0 +1,134 @@ +module RSpec + module Core + module Formatters + # @private + class SnippetExtractor + NoSuchFileError = Class.new(StandardError) + NoSuchLineError = Class.new(StandardError) + + def self.extract_line_at(file_path, line_number) + source = source_from_file(file_path) + line = source.lines[line_number - 1] + raise NoSuchLineError unless line + line + end + + def self.source_from_file(path) + raise NoSuchFileError unless File.exist?(path) + RSpec.world.source_from_file(path) + end + + if RSpec::Support::RubyFeatures.ripper_supported? + NoExpressionAtLineError = Class.new(StandardError) + + attr_reader :source, :beginning_line_number, :max_line_count + + def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil) + if max_line_count == 1 + [extract_line_at(file_path, beginning_line_number)] + else + source = source_from_file(file_path) + new(source, beginning_line_number, max_line_count).expression_lines + end + end + + def initialize(source, beginning_line_number, max_line_count=nil) + @source = source + @beginning_line_number = beginning_line_number + @max_line_count = max_line_count + end + + def expression_lines + line_range = line_range_of_expression + + if max_line_count && line_range.count > max_line_count + line_range = (line_range.begin)..(line_range.begin + max_line_count - 1) + end + + source.lines[(line_range.begin - 1)..(line_range.end - 1)] + rescue SyntaxError, NoExpressionAtLineError + [self.class.extract_line_at(source.path, beginning_line_number)] + end + + private + + def line_range_of_expression + @line_range_of_expression ||= begin + line_range = line_range_of_location_nodes_in_expression + initial_unclosed_tokens = unclosed_tokens_in_line_range(line_range) + unclosed_tokens = initial_unclosed_tokens + + until (initial_unclosed_tokens & unclosed_tokens).empty? + line_range = (line_range.begin)..(line_range.end + 1) + unclosed_tokens = unclosed_tokens_in_line_range(line_range) + end + + line_range + end + end + + def unclosed_tokens_in_line_range(line_range) + tokens = FlatMap.flat_map(line_range) do |line_number| + source.tokens_by_line_number[line_number] + end + + tokens.each_with_object([]) do |token, unclosed_tokens| + if token.opening? + unclosed_tokens << token + else + index = unclosed_tokens.rindex do |unclosed_token| + unclosed_token.closed_by?(token) + end + unclosed_tokens.delete_at(index) if index + end + end + end + + def line_range_of_location_nodes_in_expression + line_numbers = expression_node.each_with_object(Set.new) do |node, set| + set << node.location.line if node.location + end + + line_numbers.min..line_numbers.max + end + + def expression_node + raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty? + + @expression_node ||= begin + common_ancestor_nodes = location_nodes_at_beginning_line.map do |node| + node.each_ancestor.to_a + end.reduce(:&) + + common_ancestor_nodes.find { |node| expression_outmost_node?(node) } + end + end + + def expression_outmost_node?(node) + return true unless node.parent + return false if node.type.to_s.start_with?('@') + ![node, node.parent].all? do |n| + # See `Ripper::PARSER_EVENTS` for the complete list of sexp types. + type = n.type.to_s + type.end_with?('call') || type.start_with?('method_add_') + end + end + + def location_nodes_at_beginning_line + source.nodes_by_line_number[beginning_line_number] + end + else + # :nocov: + def self.extract_expression_lines_at(file_path, beginning_line_number, *) + [extract_line_at(file_path, beginning_line_number)] + end + # :nocov: + end + + def self.least_indentation_from(lines) + lines.map { |line| line[/^[ \t]*/] }.min + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/syntax_highlighter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/syntax_highlighter.rb new file mode 100644 index 0000000000..e7766f6a62 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/formatters/syntax_highlighter.rb @@ -0,0 +1,91 @@ +module RSpec + module Core + module Formatters + # @private + # Provides terminal syntax highlighting of code snippets + # when coderay is available. + class SyntaxHighlighter + def initialize(configuration) + @configuration = configuration + end + + def highlight(lines) + implementation.highlight_syntax(lines) + end + + # rubocop:disable Lint/RescueException + # rubocop:disable Lint/HandleExceptions + def self.attempt_to_add_rspec_terms_to_coderay_keywords + CodeRay::Scanners::Ruby::Patterns::IDENT_KIND.add(%w[ + describe context + it specify + before after around + let subject + expect allow + ], :keyword) + rescue Exception + # Mutating CodeRay's contants like this is not a public API + # and might not always work. If we cannot add our keywords + # to CodeRay it is not a big deal and not worth raising an + # error over, so we ignore it. + end + # rubocop:enable Lint/HandleExceptions + # rubocop:enable Lint/RescueException + + private + + if RSpec::Support::OS.windows? + # :nocov: + def implementation + WindowsImplementation + end + # :nocov: + else + def implementation + return color_enabled_implementation if @configuration.color_enabled? + NoSyntaxHighlightingImplementation + end + end + + def color_enabled_implementation + @color_enabled_implementation ||= begin + require 'coderay' + self.class.attempt_to_add_rspec_terms_to_coderay_keywords + CodeRayImplementation + rescue LoadError + NoSyntaxHighlightingImplementation + end + end + + # @private + module CodeRayImplementation + RESET_CODE = "\e[0m" + + def self.highlight_syntax(lines) + highlighted = begin + CodeRay.encode(lines.join("\n"), :ruby, :terminal) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + return lines + end + + highlighted.split("\n").map do |line| + line.sub(/\S/) { |char| char.insert(0, RESET_CODE) } + end + end + end + + # @private + module NoSyntaxHighlightingImplementation + def self.highlight_syntax(lines) + lines + end + end + + # @private + # Not sure why, but our code above (and/or coderay itself) does not work + # on Windows, so we disable the feature on Windows. + WindowsImplementation = NoSyntaxHighlightingImplementation + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/hooks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/hooks.rb new file mode 100644 index 0000000000..97bd0fc501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/hooks.rb @@ -0,0 +1,646 @@ +module RSpec + module Core + # Provides `before`, `after` and `around` hooks as a means of + # supporting common setup and teardown. This module is extended + # onto {ExampleGroup}, making the methods available from any `describe` + # or `context` block and included in {Configuration}, making them + # available off of the configuration object to define global setup + # or teardown logic. + module Hooks + # @api public + # + # @overload before(&block) + # @overload before(scope, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` + # (defaults to `:example`) + # @overload before(scope, *conditions, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` + # (defaults to `:example`) + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `before(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload before(conditions, &block) + # @param conditions [Hash] + # constrains this hook to examples matching these conditions e.g. + # `before(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. + # + # @see #after + # @see #around + # @see ExampleGroup + # @see SharedContext + # @see SharedExampleGroup + # @see Configuration + # + # Declare a block of code to be run before each example (using `:example`) + # or once before any example (using `:context`). These are usually + # declared directly in the {ExampleGroup} to which they apply, but they + # can also be shared across multiple groups. + # + # You can also use `before(:suite)` to run a block of code before any + # example groups are run. This should be declared in {RSpec.configure}. + # + # Instance variables declared in `before(:example)` or `before(:context)` + # are accessible within each example. + # + # ### Order + # + # `before` hooks are stored in three scopes, which are run in order: + # `:suite`, `:context`, and `:example`. They can also be declared in + # several different places: `RSpec.configure`, a parent group, the current + # group. They are run in the following order: + # + # before(:suite) # Declared in RSpec.configure. + # before(:context) # Declared in RSpec.configure. + # before(:context) # Declared in a parent group. + # before(:context) # Declared in the current group. + # before(:example) # Declared in RSpec.configure. + # before(:example) # Declared in a parent group. + # before(:example) # Declared in the current group. + # + # If more than one `before` is declared within any one example group, they + # are run in the order in which they are declared. Any `around` hooks will + # execute after `before` context hooks but before any `before` example + # hook regardless of where they are declared. + # + # ### Conditions + # + # When you add a conditions hash to `before(:example)` or + # `before(:context)`, RSpec will only apply that hook to groups or + # examples that match the conditions. e.g. + # + # RSpec.configure do |config| + # config.before(:example, :authorized => true) do + # log_in_as :authorized_user + # end + # end + # + # RSpec.describe Something, :authorized => true do + # # The before hook will run in before each example in this group. + # end + # + # RSpec.describe SomethingElse do + # it "does something", :authorized => true do + # # The before hook will run before this example. + # end + # + # it "does something else" do + # # The hook will not run before this example. + # end + # end + # + # Note that filtered config `:context` hooks can still be applied + # to individual examples that have matching metadata. Just like + # Ruby's object model is that every object has a singleton class + # which has only a single instance, RSpec's model is that every + # example has a singleton example group containing just the one + # example. + # + # ### Warning: `before(:suite, :with => :conditions)` + # + # The conditions hash is used to match against specific examples. Since + # `before(:suite)` is not run in relation to any specific example or + # group, conditions passed along with `:suite` are effectively ignored. + # + # ### Exceptions + # + # When an exception is raised in a `before` block, RSpec skips any + # subsequent `before` blocks and the example, but runs all of the + # `after(:example)` and `after(:context)` hooks. + # + # ### Warning: implicit before blocks + # + # `before` hooks can also be declared in shared contexts which get + # included implicitly either by you or by extension libraries. Since + # RSpec runs these in the order in which they are declared within each + # scope, load order matters, and can lead to confusing results when one + # before block depends on state that is prepared in another before block + # that gets run later. + # + # ### Warning: `before(:context)` + # + # It is very tempting to use `before(:context)` to speed things up, but we + # recommend that you avoid this as there are a number of gotchas, as well + # as things that simply don't work. + # + # #### Context + # + # `before(:context)` is run in an example that is generated to provide + # group context for the block. + # + # #### Instance variables + # + # Instance variables declared in `before(:context)` are shared across all + # the examples in the group. This means that each example can change the + # state of a shared object, resulting in an ordering dependency that can + # make it difficult to reason about failures. + # + # #### Unsupported RSpec constructs + # + # RSpec has several constructs that reset state between each example + # automatically. These are not intended for use from within + # `before(:context)`: + # + # * `let` declarations + # * `subject` declarations + # * Any mocking, stubbing or test double declaration + # + # ### other frameworks + # + # Mock object frameworks and database transaction managers (like + # ActiveRecord) are typically designed around the idea of setting up + # before an example, running that one example, and then tearing down. This + # means that mocks and stubs can (sometimes) be declared in + # `before(:context)`, but get torn down before the first real example is + # ever run. + # + # You _can_ create database-backed model objects in a `before(:context)` + # in rspec-rails, but it will not be wrapped in a transaction for you, so + # you are on your own to clean up in an `after(:context)` block. + # + # @example before(:example) declared in an {ExampleGroup} + # + # RSpec.describe Thing do + # before(:example) do + # @thing = Thing.new + # end + # + # it "does something" do + # # Here you can access @thing. + # end + # end + # + # @example before(:context) declared in an {ExampleGroup} + # + # RSpec.describe Parser do + # before(:context) do + # File.open(file_to_parse, 'w') do |f| + # f.write <<-CONTENT + # stuff in the file + # CONTENT + # end + # end + # + # it "parses the file" do + # Parser.parse(file_to_parse) + # end + # + # after(:context) do + # File.delete(file_to_parse) + # end + # end + # + # @note The `:example` and `:context` scopes are also available as + # `:each` and `:all`, respectively. Use whichever you prefer. + # @note The `:suite` scope is only supported for hooks registered on + # `RSpec.configuration` since they exist independently of any + # example or example group. + def before(*args, &block) + hooks.register :append, :before, *args, &block + end + + alias_method :append_before, :before + + # Adds `block` to the front of the list of `before` blocks in the same + # scope (`:example`, `:context`, or `:suite`). + # + # See {#before} for scoping semantics. + def prepend_before(*args, &block) + hooks.register :prepend, :before, *args, &block + end + + # @api public + # @overload after(&block) + # @overload after(scope, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to + # `:example`) + # @overload after(scope, *conditions, &block) + # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to + # `:example`) + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `after(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload after(conditions, &block) + # @param conditions [Hash] + # constrains this hook to examples matching these conditions e.g. + # `after(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. + # + # @see #before + # @see #around + # @see ExampleGroup + # @see SharedContext + # @see SharedExampleGroup + # @see Configuration + # + # Declare a block of code to be run after each example (using `:example`) + # or once after all examples n the context (using `:context`). See + # {#before} for more information about ordering. + # + # ### Exceptions + # + # `after` hooks are guaranteed to run even when there are exceptions in + # `before` hooks or examples. When an exception is raised in an after + # block, the exception is captured for later reporting, and subsequent + # `after` blocks are run. + # + # ### Order + # + # `after` hooks are stored in three scopes, which are run in order: + # `:example`, `:context`, and `:suite`. They can also be declared in + # several different places: `RSpec.configure`, a parent group, the current + # group. They are run in the following order: + # + # after(:example) # Declared in the current group. + # after(:example) # Declared in a parent group. + # after(:example) # Declared in RSpec.configure. + # after(:context) # Declared in the current group. + # after(:context) # Declared in a parent group. + # after(:context) # Declared in RSpec.configure. + # after(:suite) # Declared in RSpec.configure. + # + # This is the reverse of the order in which `before` hooks are run. + # Similarly, if more than one `after` is declared within any example + # group, they are run in reverse order of that in which they are declared. + # Also `around` hooks will run after any `after` example hooks are + # invoked but before any `after` context hooks. + # + # @note The `:example` and `:context` scopes are also available as + # `:each` and `:all`, respectively. Use whichever you prefer. + # @note The `:suite` scope is only supported for hooks registered on + # `RSpec.configuration` since they exist independently of any + # example or example group. + def after(*args, &block) + hooks.register :prepend, :after, *args, &block + end + + alias_method :prepend_after, :after + + # Adds `block` to the back of the list of `after` blocks in the same + # scope (`:example`, `:context`, or `:suite`). + # + # See {#after} for scoping semantics. + def append_after(*args, &block) + hooks.register :append, :after, *args, &block + end + + # @api public + # @overload around(&block) + # @overload around(scope, &block) + # @param scope [Symbol] `:example` (defaults to `:example`) + # present for syntax parity with `before` and `after`, but + # `:example`/`:each` is the only supported value. + # @overload around(scope, *conditions, &block) + # @param scope [Symbol] `:example` (defaults to `:example`) + # present for syntax parity with `before` and `after`, but + # `:example`/`:each` is the only supported value. + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `around(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. + # @overload around(conditions, &block) + # @param conditions [Hash] constrains this hook to examples matching + # these conditions e.g. `around(:example, :ui => true) { ... }` will + # only run with examples or groups declared with `:ui => true`. + # + # @yield [Example] the example to run + # + # @note the syntax of `around` is similar to that of `before` and `after` + # but the semantics are quite different. `before` and `after` hooks are + # run in the context of the examples with which they are associated, + # whereas `around` hooks are actually responsible for running the + # examples. Consequently, `around` hooks do not have direct access to + # resources that are made available within the examples and their + # associated `before` and `after` hooks. + # + # @note `:example`/`:each` is the only supported scope. + # + # Declare a block of code, parts of which will be run before and parts + # after the example. It is your responsibility to run the example: + # + # around(:example) do |ex| + # # Do some stuff before. + # ex.run + # # Do some stuff after. + # end + # + # The yielded example aliases `run` with `call`, which lets you treat it + # like a `Proc`. This is especially handy when working with libraries + # that manage their own setup and teardown using a block or proc syntax, + # e.g. + # + # around(:example) {|ex| Database.transaction(&ex)} + # around(:example) {|ex| FakeFS(&ex)} + # + # ### Order + # + # The `around` hooks execute surrounding an example and its hooks. + # + # This means after any `before` context hooks, but before any `before` + # example hooks, and similarly after any `after` example hooks but before + # any `after` context hooks. + # + # They are not a synonym for `before`/`after`. + def around(*args, &block) + hooks.register :prepend, :around, *args, &block + end + + # @private + # Holds the various registered hooks. + def hooks + @hooks ||= HookCollections.new(self, FilterableItemRepository::UpdateOptimized) + end + + # @private + Hook = Struct.new(:block, :options) + + # @private + class BeforeHook < Hook + def run(example) + example.instance_exec(example, &block) + end + end + + # @private + class AfterHook < Hook + def run(example) + example.instance_exec(example, &block) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex + example.set_exception(ex) + end + end + + # @private + class AfterContextHook < Hook + def run(example) + example.instance_exec(example, &block) + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + RSpec.configuration.reporter.notify_non_example_exception(e, "An error occurred in an `after(:context)` hook.") + end + end + + # @private + class AroundHook < Hook + def execute_with(example, procsy) + example.instance_exec(procsy, &block) + return if procsy.executed? + Pending.mark_skipped!(example, + "#{hook_description} did not execute the example") + end + + if Proc.method_defined?(:source_location) + def hook_description + "around hook at #{Metadata.relative_path(block.source_location.join(':'))}" + end + else # for 1.8.7 + # :nocov: + def hook_description + "around hook" + end + # :nocov: + end + end + + # @private + # + # This provides the primary API used by other parts of rspec-core. By hiding all + # implementation details behind this facade, it's allowed us to heavily optimize + # this, so that, for example, hook collection objects are only instantiated when + # a hook is added. This allows us to avoid many object allocations for the common + # case of a group having no hooks. + # + # This is only possible because this interface provides a "tell, don't ask"-style + # API, so that callers _tell_ this class what to do with the hooks, rather than + # asking this class for a list of hooks, and then doing something with them. + class HookCollections + def initialize(owner, filterable_item_repo_class) + @owner = owner + @filterable_item_repo_class = filterable_item_repo_class + @before_example_hooks = nil + @after_example_hooks = nil + @before_context_hooks = nil + @after_context_hooks = nil + @around_example_hooks = nil + end + + def register_globals(host, globals) + parent_groups = host.parent_groups + + process(host, parent_groups, globals, :before, :example, &:options) + process(host, parent_groups, globals, :after, :example, &:options) + process(host, parent_groups, globals, :around, :example, &:options) + + process(host, parent_groups, globals, :before, :context, &:options) + process(host, parent_groups, globals, :after, :context, &:options) + end + + def register_global_singleton_context_hooks(example, globals) + parent_groups = example.example_group.parent_groups + + process(example, parent_groups, globals, :before, :context) { {} } + process(example, parent_groups, globals, :after, :context) { {} } + end + + def register(prepend_or_append, position, *args, &block) + scope, options = scope_and_options_from(*args) + + if scope == :suite + # TODO: consider making this an error in RSpec 4. For SemVer reasons, + # we are only warning in RSpec 3. + RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \ + "the RSpec configuration object. This " \ + "`#{position}(:suite)` hook, registered on an example " \ + "group, will be ignored." + return + elsif scope == :context && position == :around + # TODO: consider making this an error in RSpec 4. For SemVer reasons, + # we are only warning in RSpec 3. + RSpec.warn_with "WARNING: `around(:context)` hooks are not supported and " \ + "behave like `around(:example)." + end + + hook = HOOK_TYPES[position][scope].new(block, options) + ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options) + end + + # @private + # + # Runs all of the blocks stored with the hook in the context of the + # example. If no example is provided, just calls the hook directly. + def run(position, scope, example_or_group) + return if RSpec.configuration.dry_run? + + if scope == :context + unless example_or_group.class.metadata[:skip] + run_owned_hooks_for(position, :context, example_or_group) + end + else + case position + when :before then run_example_hooks_for(example_or_group, :before, :reverse_each) + when :after then run_example_hooks_for(example_or_group, :after, :each) + when :around then run_around_example_hooks_for(example_or_group) { yield } + end + end + end + + SCOPES = [:example, :context] + + SCOPE_ALIASES = { :each => :example, :all => :context } + + HOOK_TYPES = { + :before => Hash.new { BeforeHook }, + :after => Hash.new { AfterHook }, + :around => Hash.new { AroundHook } + } + + HOOK_TYPES[:after][:context] = AfterContextHook + + protected + + EMPTY_HOOK_ARRAY = [].freeze + + def matching_hooks_for(position, scope, example_or_group) + repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY } + + # It would be nice to not have to switch on type here, but + # we don't want to define `ExampleGroup#metadata` because then + # `metadata` from within an individual example would return the + # group's metadata but the user would probably expect it to be + # the example's metadata. + metadata = case example_or_group + when ExampleGroup then example_or_group.class.metadata + else example_or_group.metadata + end + + repository.items_for(metadata) + end + + def all_hooks_for(position, scope) + hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first) + end + + def run_owned_hooks_for(position, scope, example_or_group) + matching_hooks_for(position, scope, example_or_group).each do |hook| + hook.run(example_or_group) + end + end + + def processable_hooks_for(position, scope, host) + if scope == :example + all_hooks_for(position, scope) + else + matching_hooks_for(position, scope, host) + end + end + + private + + def hooks_for(position, scope) + if position == :before + scope == :example ? @before_example_hooks : @before_context_hooks + elsif position == :after + scope == :example ? @after_example_hooks : @after_context_hooks + else # around + @around_example_hooks + end || yield + end + + def ensure_hooks_initialized_for(position, scope) + if position == :before + if scope == :example + @before_example_hooks ||= @filterable_item_repo_class.new(:all?) + else + @before_context_hooks ||= @filterable_item_repo_class.new(:all?) + end + elsif position == :after + if scope == :example + @after_example_hooks ||= @filterable_item_repo_class.new(:all?) + else + @after_context_hooks ||= @filterable_item_repo_class.new(:all?) + end + else # around + @around_example_hooks ||= @filterable_item_repo_class.new(:all?) + end + end + + def process(host, parent_groups, globals, position, scope) + hooks_to_process = globals.processable_hooks_for(position, scope, host) + return if hooks_to_process.empty? + + hooks_to_process -= FlatMap.flat_map(parent_groups) do |group| + group.hooks.all_hooks_for(position, scope) + end + return if hooks_to_process.empty? + + repository = ensure_hooks_initialized_for(position, scope) + hooks_to_process.each { |hook| repository.append hook, (yield hook) } + end + + def scope_and_options_from(*args) + return :suite if args.first == :suite + scope = extract_scope_from(args) + meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + return scope, meta + end + + def extract_scope_from(args) + if known_scope?(args.first) + normalized_scope_for(args.shift) + elsif args.any? { |a| a.is_a?(Symbol) } + error_message = "You must explicitly give a scope " \ + "(#{SCOPES.join(", ")}) or scope alias " \ + "(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \ + "metadata for a hook." + raise ArgumentError.new error_message + else + :example + end + end + + def known_scope?(scope) + SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope) + end + + def normalized_scope_for(scope) + SCOPE_ALIASES[scope] || scope + end + + def run_example_hooks_for(example, position, each_method) + owner_parent_groups.__send__(each_method) do |group| + group.hooks.run_owned_hooks_for(position, :example, example) + end + end + + def run_around_example_hooks_for(example) + hooks = FlatMap.flat_map(owner_parent_groups) do |group| + group.hooks.matching_hooks_for(:around, :example, example) + end + + return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy` + + initial_procsy = Example::Procsy.new(example) { yield } + hooks.inject(initial_procsy) do |procsy, around_hook| + procsy.wrap { around_hook.execute_with(example, procsy) } + end.call + end + + if respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class) + def owner_parent_groups + @owner.parent_groups + end + else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035) + # :nocov: + def owner_parent_groups + @owner_parent_groups ||= [@owner] + @owner.parent_groups + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/invocations.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/invocations.rb new file mode 100644 index 0000000000..4719085b36 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/invocations.rb @@ -0,0 +1,87 @@ +module RSpec + module Core + # @private + module Invocations + # @private + class InitializeProject + def call(*_args) + RSpec::Support.require_rspec_core "project_initializer" + ProjectInitializer.new.run + 0 + end + end + + # @private + class DRbWithFallback + def call(options, err, out) + require 'rspec/core/drb' + begin + return DRbRunner.new(options).run(err, out) + rescue DRb::DRbConnError + err.puts "No DRb server is running. Running in local process instead ..." + end + RSpec::Core::Runner.new(options).run(err, out) + end + end + + # @private + class Bisect + def call(options, err, out) + RSpec::Support.require_rspec_core "bisect/coordinator" + runner = Runner.new(options).tap { |r| r.configure(err, out) } + formatter = bisect_formatter_klass_for(options.options[:bisect]).new( + out, runner.configuration.bisect_runner + ) + + success = RSpec::Core::Bisect::Coordinator.bisect_with( + runner, options.args, formatter + ) + + runner.exit_code(success) + end + + private + + def bisect_formatter_klass_for(argument) + return Formatters::BisectDebugFormatter if argument == "verbose" + Formatters::BisectProgressFormatter + end + end + + # @private + class PrintVersion + def call(_options, _err, out) + overall_version = RSpec::Core::Version::STRING + unless overall_version =~ /[a-zA-Z]+/ + overall_version = overall_version.split('.').first(2).join('.') + end + + out.puts "RSpec #{overall_version}" + + [:Core, :Expectations, :Mocks, :Rails, :Support].each do |const_name| + lib_name = const_name.to_s.downcase + begin + require "rspec/#{lib_name}/version" + rescue LoadError + # Not worth mentioning libs that are not installed + nil + else + out.puts " - rspec-#{lib_name} #{RSpec.const_get(const_name)::Version::STRING}" + end + end + + 0 + end + end + + # @private + PrintHelp = Struct.new(:parser, :hidden_options) do + def call(_options, _err, out) + # Removing the hidden options from the output. + out.puts parser.to_s.gsub(/^\s+(#{hidden_options.join('|')})\b.*$\n/, '') + 0 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/memoized_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/memoized_helpers.rb new file mode 100644 index 0000000000..adcfce7af3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/memoized_helpers.rb @@ -0,0 +1,580 @@ +RSpec::Support.require_rspec_support 'reentrant_mutex' + +module RSpec + module Core + # This module is included in {ExampleGroup}, making the methods + # available to be called from within example blocks. + # + # @see ClassMethods + module MemoizedHelpers + # @note `subject` was contributed by Joe Ferris to support the one-liner + # syntax embraced by shoulda matchers: + # + # RSpec.describe Widget do + # it { is_expected.to validate_presence_of(:name) } + # # or + # it { should validate_presence_of(:name) } + # end + # + # While the examples below demonstrate how to use `subject` + # explicitly in examples, we recommend that you define a method with + # an intention revealing name instead. + # + # @example + # + # # Explicit declaration of subject. + # RSpec.describe Person do + # subject { Person.new(:birthdate => 19.years.ago) } + # it "should be eligible to vote" do + # subject.should be_eligible_to_vote + # # ^ ^ explicit reference to subject not recommended + # end + # end + # + # # Implicit subject => { Person.new }. + # RSpec.describe Person do + # it "should be eligible to vote" do + # subject.should be_eligible_to_vote + # # ^ ^ explicit reference to subject not recommended + # end + # end + # + # # One-liner syntax - expectation is set on the subject. + # RSpec.describe Person do + # it { is_expected.to be_eligible_to_vote } + # # or + # it { should be_eligible_to_vote } + # end + # + # @note Because `subject` is designed to create state that is reset + # between each example, and `before(:context)` is designed to setup + # state that is shared across _all_ examples in an example group, + # `subject` is _not_ intended to be used in a `before(:context)` hook. + # + # @see #should + # @see #should_not + # @see #is_expected + def subject + __memoized.fetch_or_store(:subject) do + described = described_class || self.class.metadata.fetch(:description_args).first + Class === described ? described.new : described + end + end + + # When `should` is called with no explicit receiver, the call is + # delegated to the object returned by `subject`. Combined with an + # implicit subject this supports very concise expressions. + # + # @example + # + # RSpec.describe Person do + # it { should be_eligible_to_vote } + # end + # + # @see #subject + # @see #is_expected + # + # @note This only works if you are using rspec-expectations. + # @note If you are using RSpec's newer expect-based syntax you may + # want to use `is_expected.to` instead of `should`. + def should(matcher=nil, message=nil) + enforce_value_expectation(matcher, 'should') + RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message) + end + + # Just like `should`, `should_not` delegates to the subject (implicit or + # explicit) of the example group. + # + # @example + # + # RSpec.describe Person do + # it { should_not be_eligible_to_vote } + # end + # + # @see #subject + # @see #is_expected + # + # @note This only works if you are using rspec-expectations. + # @note If you are using RSpec's newer expect-based syntax you may + # want to use `is_expected.to_not` instead of `should_not`. + def should_not(matcher=nil, message=nil) + enforce_value_expectation(matcher, 'should_not') + RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message) + end + + # Wraps the `subject` in `expect` to make it the target of an expectation. + # Designed to read nicely for one-liners. + # + # @example + # + # describe [1, 2, 3] do + # it { is_expected.to be_an Array } + # it { is_expected.not_to include 4 } + # end + # + # @see #subject + # @see #should + # @see #should_not + # + # @note This only works if you are using rspec-expectations. + def is_expected + expect(subject) + end + + # @private + # should just be placed in private section, + # but Ruby issues warnings on private attributes. + # and expanding it to the equivalent method upsets Rubocop, + # b/c it should obviously be a reader + attr_reader :__memoized + private :__memoized + + private + + # @private + def initialize(*) + __init_memoized + super + end + + # @private + def __init_memoized + @__memoized = if RSpec.configuration.threadsafe? + ThreadsafeMemoized.new + else + NonThreadSafeMemoized.new + end + end + + # @private + def enforce_value_expectation(matcher, method_name) + return if matcher_supports_value_expectations?(matcher) + + RSpec.deprecate( + "#{method_name} #{RSpec::Support::ObjectFormatter.format(matcher)}", + :message => + "The implicit block expectation syntax is deprecated, you should pass " \ + "a block to `expect` to use the provided block expectation matcher " \ + "(#{RSpec::Support::ObjectFormatter.format(matcher)}), " \ + "or the matcher must implement `supports_value_expectations?`." + ) + end + + def matcher_supports_value_expectations?(matcher) + matcher.supports_value_expectations? + rescue + true + end + + # @private + class ThreadsafeMemoized + def initialize + @memoized = {} + @mutex = Support::ReentrantMutex.new + end + + def fetch_or_store(key) + @memoized.fetch(key) do # only first access pays for synchronization + @mutex.synchronize do + @memoized.fetch(key) { @memoized[key] = yield } + end + end + end + end + + # @private + class NonThreadSafeMemoized + def initialize + @memoized = {} + end + + def fetch_or_store(key) + @memoized.fetch(key) { @memoized[key] = yield } + end + end + + # Used internally to customize the behavior of the + # memoized hash when used in a `before(:context)` hook. + # + # @private + class ContextHookMemoized + def self.isolate_for_context_hook(example_group_instance) + exploding_memoized = self + + example_group_instance.instance_exec do + @__memoized = exploding_memoized + + begin + yield + ensure + # This is doing a reset instead of just isolating for context hook. + # Really, this should set the old @__memoized back into place. + # + # Caller is the before and after context hooks + # which are both called from self.run + # I didn't look at why it made tests fail, maybe an object was getting reused in RSpec tests, + # if so, then that probably already works, and its the tests that are wrong. + __init_memoized + end + end + end + + def self.fetch_or_store(key, &_block) + description = if key == :subject + "subject" + else + "let declaration `#{key}`" + end + + raise <<-EOS +#{description} accessed in #{article} #{hook_expression} hook at: + #{CallerFilter.first_non_rspec_line} + +`let` and `subject` declarations are not intended to be called +in #{article} #{hook_expression} hook, as they exist to define state that +is reset between each example, while #{hook_expression} exists to +#{hook_intention}. +EOS + end + + # @private + class Before < self + def self.hook_expression + "`before(:context)`" + end + + def self.article + "a" + end + + def self.hook_intention + "define state that is shared across examples in an example group" + end + end + + # @private + class After < self + def self.hook_expression + "`after(:context)`" + end + + def self.article + "an" + end + + def self.hook_intention + "cleanup state that is shared across examples in an example group" + end + end + end + + # This module is extended onto {ExampleGroup}, making the methods + # available to be called from within example group blocks. + # You can think of them as being analagous to class macros. + module ClassMethods + # Generates a method whose return value is memoized after the first + # call. Useful for reducing duplication between examples that assign + # values to the same local variable. + # + # @note `let` _can_ enhance readability when used sparingly (1,2, or + # maybe 3 declarations) in any given example group, but that can + # quickly degrade with overuse. YMMV. + # + # @note `let` can be configured to be threadsafe or not. + # If it is threadsafe, it will take longer to access the value. + # If it is not threadsafe, it may behave in surprising ways in examples + # that spawn separate threads. Specify this on `RSpec.configure` + # + # @note Because `let` is designed to create state that is reset between + # each example, and `before(:context)` is designed to setup state that + # is shared across _all_ examples in an example group, `let` is _not_ + # intended to be used in a `before(:context)` hook. + # + # @example + # + # RSpec.describe Thing do + # let(:thing) { Thing.new } + # + # it "does something" do + # # First invocation, executes block, memoizes and returns result. + # thing.do_something + # + # # Second invocation, returns the memoized value. + # thing.should be_something + # end + # end + def let(name, &block) + # We have to pass the block directly to `define_method` to + # allow it to use method constructs like `super` and `return`. + raise "#let or #subject called without a block" if block.nil? + + # A list of reserved words that can't be used as a name for a memoized helper + # Matches for both symbols and passed strings + if [:initialize, :to_s].include?(name.to_sym) + raise ArgumentError, "#let or #subject called with reserved name `#{name}`" + end + + our_module = MemoizedHelpers.module_for(self) + + # If we have a module clash in our helper module + # then we need to remove it to prevent a warning. + # + # Note we do not check ancestor modules (see: `instance_methods(false)`) + # as we can override them. + if our_module.instance_methods(false).include?(name) + our_module.__send__(:remove_method, name) + end + our_module.__send__(:define_method, name, &block) + + # If we have a module clash in the example module + # then we need to remove it to prevent a warning. + # + # Note we do not check ancestor modules (see: `instance_methods(false)`) + # as we can override them. + if instance_methods(false).include?(name) + remove_method(name) + end + + # Apply the memoization. The method has been defined in an ancestor + # module so we can use `super` here to get the value. + if block.arity == 1 + define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } } + else + define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } } + end + end + + # Just like `let`, except the block is invoked by an implicit `before` + # hook. This serves a dual purpose of setting up state and providing a + # memoized reference to that state. + # + # @example + # + # class Thing + # def self.count + # @count ||= 0 + # end + # + # def self.count=(val) + # @count += val + # end + # + # def self.reset_count + # @count = 0 + # end + # + # def initialize + # self.class.count += 1 + # end + # end + # + # RSpec.describe Thing do + # after(:example) { Thing.reset_count } + # + # context "using let" do + # let(:thing) { Thing.new } + # + # it "is not invoked implicitly" do + # Thing.count.should eq(0) + # end + # + # it "can be invoked explicitly" do + # thing + # Thing.count.should eq(1) + # end + # end + # + # context "using let!" do + # let!(:thing) { Thing.new } + # + # it "is invoked implicitly" do + # Thing.count.should eq(1) + # end + # + # it "returns memoized version on first invocation" do + # thing + # Thing.count.should eq(1) + # end + # end + # end + def let!(name, &block) + let(name, &block) + before { __send__(name) } + end + + # Declares a `subject` for an example group which can then be wrapped + # with `expect` using `is_expected` to make it the target of an + # expectation in a concise, one-line example. + # + # Given a `name`, defines a method with that name which returns the + # `subject`. This lets you declare the subject once and access it + # implicitly in one-liners and explicitly using an intention revealing + # name. + # + # When given a `name`, calling `super` in the block is not supported. + # + # @note `subject` can be configured to be threadsafe or not. + # If it is threadsafe, it will take longer to access the value. + # If it is not threadsafe, it may behave in surprising ways in examples + # that spawn separate threads. Specify this on `RSpec.configure` + # + # @param name [String,Symbol] used to define an accessor with an + # intention revealing name + # @param block defines the value to be returned by `subject` in examples + # + # @example + # + # RSpec.describe CheckingAccount, "with $50" do + # subject { CheckingAccount.new(Money.new(50, :USD)) } + # it { is_expected.to have_a_balance_of(Money.new(50, :USD)) } + # it { is_expected.not_to be_overdrawn } + # end + # + # RSpec.describe CheckingAccount, "with a non-zero starting balance" do + # subject(:account) { CheckingAccount.new(Money.new(50, :USD)) } + # it { is_expected.not_to be_overdrawn } + # it "has a balance equal to the starting balance" do + # account.balance.should eq(Money.new(50, :USD)) + # end + # end + # + # @see MemoizedHelpers#should + # @see MemoizedHelpers#should_not + # @see MemoizedHelpers#is_expected + def subject(name=nil, &block) + if name + let(name, &block) + alias_method :subject, name + + self::NamedSubjectPreventSuper.__send__(:define_method, name) do + raise NotImplementedError, "`super` in named subjects is not supported" + end + else + let(:subject, &block) + end + end + + # Just like `subject`, except the block is invoked by an implicit + # `before` hook. This serves a dual purpose of setting up state and + # providing a memoized reference to that state. + # + # @example + # + # class Thing + # def self.count + # @count ||= 0 + # end + # + # def self.count=(val) + # @count += val + # end + # + # def self.reset_count + # @count = 0 + # end + # + # def initialize + # self.class.count += 1 + # end + # end + # + # RSpec.describe Thing do + # after(:example) { Thing.reset_count } + # + # context "using subject" do + # subject { Thing.new } + # + # it "is not invoked implicitly" do + # Thing.count.should eq(0) + # end + # + # it "can be invoked explicitly" do + # subject + # Thing.count.should eq(1) + # end + # end + # + # context "using subject!" do + # subject!(:thing) { Thing.new } + # + # it "is invoked implicitly" do + # Thing.count.should eq(1) + # end + # + # it "returns memoized version on first invocation" do + # subject + # Thing.count.should eq(1) + # end + # end + # end + def subject!(name=nil, &block) + subject(name, &block) + before { subject } + end + end + + # @private + # + # Gets the LetDefinitions module. The module is mixed into + # the example group and is used to hold all let definitions. + # This is done so that the block passed to `let` can be + # forwarded directly on to `define_method`, so that all method + # constructs (including `super` and `return`) can be used in + # a `let` block. + # + # The memoization is provided by a method definition on the + # example group that supers to the LetDefinitions definition + # in order to get the value to memoize. + def self.module_for(example_group) + get_constant_or_yield(example_group, :LetDefinitions) do + mod = Module.new do + include(Module.new { + example_group.const_set(:NamedSubjectPreventSuper, self) + }) + end + + example_group.const_set(:LetDefinitions, mod) + mod + end + end + + # @private + def self.define_helpers_on(example_group) + example_group.__send__(:include, module_for(example_group)) + end + + if Module.method(:const_defined?).arity == 1 # for 1.8 + # @private + # + # Gets the named constant or yields. + # On 1.8, const_defined? / const_get do not take into + # account the inheritance hierarchy. + # :nocov: + def self.get_constant_or_yield(example_group, name) + if example_group.const_defined?(name) + example_group.const_get(name) + else + yield + end + end + # :nocov: + else + # @private + # + # Gets the named constant or yields. + # On 1.9, const_defined? / const_get take into account the + # the inheritance by default, and accept an argument to + # disable this behavior. It's important that we don't + # consider inheritance here; each example group level that + # uses a `let` should get its own `LetDefinitions` module. + def self.get_constant_or_yield(example_group, name) + if example_group.const_defined?(name, (check_ancestors = false)) + example_group.const_get(name, check_ancestors) + else + yield + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata.rb new file mode 100644 index 0000000000..5e0d7e2bdb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata.rb @@ -0,0 +1,498 @@ +module RSpec + module Core + # Each ExampleGroup class and Example instance owns an instance of + # Metadata, which is Hash extended to support lazy evaluation of values + # associated with keys that may or may not be used by any example or group. + # + # In addition to metadata that is used internally, this also stores + # user-supplied metadata, e.g. + # + # RSpec.describe Something, :type => :ui do + # it "does something", :slow => true do + # # ... + # end + # end + # + # `:type => :ui` is stored in the Metadata owned by the example group, and + # `:slow => true` is stored in the Metadata owned by the example. These can + # then be used to select which examples are run using the `--tag` option on + # the command line, or several methods on `Configuration` used to filter a + # run (e.g. `filter_run_including`, `filter_run_excluding`, etc). + # + # @see Example#metadata + # @see ExampleGroup.metadata + # @see FilterManager + # @see Configuration#filter_run_including + # @see Configuration#filter_run_excluding + module Metadata + # Matches strings either at the beginning of the input or prefixed with a + # whitespace, containing the current path, either postfixed with the + # separator, or at the end of the string. Match groups are the character + # before and the character after the string if any. + # + # http://rubular.com/r/fT0gmX6VJX + # http://rubular.com/r/duOrD4i3wb + # http://rubular.com/r/sbAMHFrOx1 + def self.relative_path_regex + @relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/ + end + + # @api private + # + # @param line [String] current code line + # @return [String] relative path to line + def self.relative_path(line) + line = line.sub(relative_path_regex, "\\1.\\2".freeze) + line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze) + return nil if line == '-e:1'.freeze + line + rescue SecurityError + # :nocov: + nil + # :nocov: + end + + # @private + # Iteratively walks up from the given metadata through all + # example group ancestors, yielding each metadata hash along the way. + def self.ascending(metadata) + yield metadata + return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] }) + + loop do + yield group_metadata + break unless (group_metadata = group_metadata[:parent_example_group]) + end + end + + # @private + # Returns an enumerator that iteratively walks up the given metadata through all + # example group ancestors, yielding each metadata hash along the way. + def self.ascend(metadata) + enum_for(:ascending, metadata) + end + + # @private + # Used internally to build a hash from an args array. + # Symbols are converted into hash keys with a value of `true`. + # This is done to support simple tagging using a symbol, rather + # than needing to do `:symbol => true`. + def self.build_hash_from(args, warn_about_example_group_filtering=false) + hash = args.last.is_a?(Hash) ? args.pop : {} + + hash[args.pop] = true while args.last.is_a?(Symbol) + + if warn_about_example_group_filtering && hash.key?(:example_group) + RSpec.deprecate("Filtering by an `:example_group` subhash", + :replacement => "the subhash to filter directly") + end + + hash + end + + # @private + def self.deep_hash_dup(object) + return object.dup if Array === object + return object unless Hash === object + + object.inject(object.dup) do |duplicate, (key, value)| + duplicate[key] = deep_hash_dup(value) + duplicate + end + end + + # @private + def self.id_from(metadata) + "#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]" + end + + # @private + def self.location_tuple_from(metadata) + [metadata[:absolute_file_path], metadata[:line_number]] + end + + # @private + # Used internally to populate metadata hashes with computed keys + # managed by RSpec. + class HashPopulator + attr_reader :metadata, :user_metadata, :description_args, :block + + def initialize(metadata, user_metadata, index_provider, description_args, block) + @metadata = metadata + @user_metadata = user_metadata + @index_provider = index_provider + @description_args = description_args + @block = block + end + + def populate + ensure_valid_user_keys + + metadata[:block] = block + metadata[:description_args] = description_args + metadata[:description] = build_description_from(*metadata[:description_args]) + metadata[:full_description] = full_description + metadata[:described_class] = described_class + + populate_location_attributes + metadata.update(user_metadata) + end + + private + + def populate_location_attributes + backtrace = user_metadata.delete(:caller) + + file_path, line_number = if backtrace + file_path_and_line_number_from(backtrace) + elsif block.respond_to?(:source_location) + block.source_location + else + file_path_and_line_number_from(caller) + end + + relative_file_path = Metadata.relative_path(file_path) + absolute_file_path = File.expand_path(relative_file_path) + metadata[:file_path] = relative_file_path + metadata[:line_number] = line_number.to_i + metadata[:location] = "#{relative_file_path}:#{line_number}" + metadata[:absolute_file_path] = absolute_file_path + metadata[:rerun_file_path] ||= relative_file_path + metadata[:scoped_id] = build_scoped_id_for(absolute_file_path) + end + + def file_path_and_line_number_from(backtrace) + first_caller_from_outside_rspec = backtrace.find { |l| l !~ CallerFilter::LIB_REGEX } + first_caller_from_outside_rspec ||= backtrace.first + /(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures + end + + def description_separator(parent_part, child_part) + if parent_part.is_a?(Module) && /^(?:#|::|\.)/.match(child_part.to_s) + ''.freeze + else + ' '.freeze + end + end + + def build_description_from(parent_description=nil, my_description=nil) + return parent_description.to_s unless my_description + return my_description.to_s if parent_description.to_s == '' + separator = description_separator(parent_description, my_description) + (parent_description.to_s + separator) << my_description.to_s + end + + def build_scoped_id_for(file_path) + index = @index_provider.call(file_path).to_s + parent_scoped_id = metadata.fetch(:scoped_id) { return index } + "#{parent_scoped_id}:#{index}" + end + + def ensure_valid_user_keys + RESERVED_KEYS.each do |key| + next unless user_metadata.key?(key) + raise <<-EOM.gsub(/^\s+\|/, '') + |#{"*" * 50} + |:#{key} is not allowed + | + |RSpec reserves some hash keys for its own internal use, + |including :#{key}, which is used on: + | + | #{CallerFilter.first_non_rspec_line}. + | + |Here are all of RSpec's reserved hash keys: + | + | #{RESERVED_KEYS.join("\n ")} + |#{"*" * 50} + EOM + end + end + end + + # @private + class ExampleHash < HashPopulator + def self.create(group_metadata, user_metadata, index_provider, description, block) + example_metadata = group_metadata.dup + group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash| + hash[:parent_example_group] + end) + group_metadata.update(example_metadata) + + example_metadata[:execution_result] = Example::ExecutionResult.new + example_metadata[:example_group] = group_metadata + example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace + example_metadata.delete(:parent_example_group) + + description_args = description.nil? ? [] : [description] + hash = new(example_metadata, user_metadata, index_provider, description_args, block) + hash.populate + hash.metadata + end + + private + + def described_class + metadata[:example_group][:described_class] + end + + def full_description + build_description_from( + metadata[:example_group][:full_description], + metadata[:description] + ) + end + end + + # @private + class ExampleGroupHash < HashPopulator + def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block) + group_metadata = hash_with_backwards_compatibility_default_proc + + if parent_group_metadata + group_metadata.update(parent_group_metadata) + group_metadata[:parent_example_group] = parent_group_metadata + end + + hash = new(group_metadata, user_metadata, example_group_index, args, block) + hash.populate + hash.metadata + end + + def self.hash_with_backwards_compatibility_default_proc + Hash.new(&backwards_compatibility_default_proc { |hash| hash }) + end + + def self.backwards_compatibility_default_proc(&example_group_selector) + Proc.new do |hash, key| + case key + when :example_group + # We commonly get here when rspec-core is applying a previously + # configured filter rule, such as when a gem configures: + # + # RSpec.configure do |c| + # c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ } + # end + # + # It's confusing for a user to get a deprecation at this point in + # the code, so instead we issue a deprecation from the config APIs + # that take a metadata hash, and MetadataFilter sets this thread + # local to silence the warning here since it would be so + # confusing. + unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] + RSpec.deprecate("The `:example_group` key in an example group's metadata hash", + :replacement => "the example group's hash directly for the " \ + "computed keys and `:parent_example_group` to access the parent " \ + "example group metadata") + end + + group_hash = example_group_selector.call(hash) + LegacyExampleGroupHash.new(group_hash) if group_hash + when :example_group_block + RSpec.deprecate("`metadata[:example_group_block]`", + :replacement => "`metadata[:block]`") + hash[:block] + when :describes + RSpec.deprecate("`metadata[:describes]`", + :replacement => "`metadata[:described_class]`") + hash[:described_class] + end + end + end + + private + + def described_class + candidate = metadata[:description_args].first + return candidate unless NilClass === candidate || String === candidate + parent_group = metadata[:parent_example_group] + parent_group && parent_group[:described_class] + end + + def full_description + description = metadata[:description] + parent_example_group = metadata[:parent_example_group] + return description unless parent_example_group + + parent_description = parent_example_group[:full_description] + separator = description_separator(parent_example_group[:description_args].last, + metadata[:description_args].first) + + parent_description + separator + description + end + end + + # @private + RESERVED_KEYS = [ + :description, + :description_args, + :described_class, + :example_group, + :parent_example_group, + :execution_result, + :last_run_status, + :file_path, + :absolute_file_path, + :rerun_file_path, + :full_description, + :line_number, + :location, + :scoped_id, + :block, + :shared_group_inclusion_backtrace + ] + end + + # Mixin that makes the including class imitate a hash for backwards + # compatibility. The including class should use `attr_accessor` to + # declare attributes. + # @private + module HashImitatable + def self.included(klass) + klass.extend ClassMethods + end + + def to_h + hash = extra_hash_attributes.dup + + self.class.hash_attribute_names.each do |name| + hash[name] = __send__(name) + end + + hash + end + + (Hash.public_instance_methods - Object.public_instance_methods).each do |method_name| + next if [:[], :[]=, :to_h].include?(method_name.to_sym) + + define_method(method_name) do |*args, &block| + issue_deprecation(method_name, *args) + + hash = hash_for_delegation + self.class.hash_attribute_names.each do |name| + hash.delete(name) unless instance_variable_defined?(:"@#{name}") + end + + hash.__send__(method_name, *args, &block).tap do + # apply mutations back to the object + hash.each do |name, value| + if directly_supports_attribute?(name) + set_value(name, value) + else + extra_hash_attributes[name] = value + end + end + end + end + end + + def [](key) + issue_deprecation(:[], key) + + if directly_supports_attribute?(key) + get_value(key) + else + extra_hash_attributes[key] + end + end + + def []=(key, value) + issue_deprecation(:[]=, key, value) + + if directly_supports_attribute?(key) + set_value(key, value) + else + extra_hash_attributes[key] = value + end + end + + private + + def extra_hash_attributes + @extra_hash_attributes ||= {} + end + + def directly_supports_attribute?(name) + self.class.hash_attribute_names.include?(name) + end + + def get_value(name) + __send__(name) + end + + def set_value(name, value) + __send__(:"#{name}=", value) + end + + def hash_for_delegation + to_h + end + + def issue_deprecation(_method_name, *_args) + # no-op by default: subclasses can override + end + + # @private + module ClassMethods + def hash_attribute_names + @hash_attribute_names ||= [] + end + + def attr_accessor(*names) + hash_attribute_names.concat(names) + super + end + end + end + + # @private + # Together with the example group metadata hash default block, + # provides backwards compatibility for the old `:example_group` + # key. In RSpec 2.x, the computed keys of a group's metadata + # were exposed from a nested subhash keyed by `[:example_group]`, and + # then the parent group's metadata was exposed by sub-subhash + # keyed by `[:example_group][:example_group]`. + # + # In RSpec 3, we reorganized this to that the computed keys are + # exposed directly of the group metadata hash (no nesting), and + # `:parent_example_group` returns the parent group's metadata. + # + # Maintaining backwards compatibility was difficult: we wanted + # `:example_group` to return an object that: + # + # * Exposes the top-level metadata keys that used to be nested + # under `:example_group`. + # * Supports mutation (rspec-rails, for example, assigns + # `metadata[:example_group][:described_class]` when you use + # anonymous controller specs) such that changes are written + # back to the top-level metadata hash. + # * Exposes the parent group metadata as + # `[:example_group][:example_group]`. + class LegacyExampleGroupHash + include HashImitatable + + def initialize(metadata) + @metadata = metadata + parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group] + self[:example_group] = parent_group_metadata if parent_group_metadata + end + + def to_h + super.merge(@metadata) + end + + private + + def directly_supports_attribute?(name) + name != :example_group + end + + def get_value(name) + @metadata[name] + end + + def set_value(name, value) + @metadata[name] = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata_filter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata_filter.rb new file mode 100644 index 0000000000..2e63baf942 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/metadata_filter.rb @@ -0,0 +1,255 @@ +module RSpec + module Core + # Contains metadata filtering logic. This has been extracted from + # the metadata classes because it operates ON a metadata hash but + # does not manage any of the state in the hash. We're moving towards + # having metadata be a raw hash (not a custom subclass), so externalizing + # this filtering logic helps us move in that direction. + module MetadataFilter + class << self + # @private + def apply?(predicate, filters, metadata) + filters.__send__(predicate) { |k, v| filter_applies?(k, v, metadata) } + end + + # @private + def filter_applies?(key, filter_value, metadata) + silence_metadata_example_group_deprecations do + return location_filter_applies?(filter_value, metadata) if key == :locations + return id_filter_applies?(filter_value, metadata) if key == :ids + return filters_apply?(key, filter_value, metadata) if Hash === filter_value + + meta_value = metadata.fetch(key) { return false } + + return true if TrueClass === filter_value && meta_value + return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value + return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value + + filter_value === meta_value || filter_value.to_s == meta_value.to_s + end + end + + # @private + def silence_metadata_example_group_deprecations + RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true + yield + ensure + RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations) + end + + private + + def filter_applies_to_any_value?(key, value, metadata) + metadata[key].any? { |v| filter_applies?(key, v, key => value) } + end + + def id_filter_applies?(rerun_paths_to_scoped_ids, metadata) + scoped_ids = rerun_paths_to_scoped_ids.fetch(metadata[:rerun_file_path]) { return false } + + Metadata.ascend(metadata).any? do |meta| + scoped_ids.include?(meta[:scoped_id]) + end + end + + def location_filter_applies?(locations, metadata) + Metadata.ascend(metadata).any? do |meta| + file_path = meta[:absolute_file_path] + line_num = meta[:line_number] + + locations[file_path].any? do |filter_line_num| + line_num == RSpec.world.preceding_declaration_line(file_path, filter_line_num) + end + end + end + + def proc_filter_applies?(key, proc, metadata) + case proc.arity + when 0 then proc.call + when 2 then proc.call(metadata[key], metadata) + else proc.call(metadata[key]) + end + end + + def filters_apply?(key, value, metadata) + subhash = metadata[key] + return false unless Hash === subhash || HashImitatable === subhash + value.all? { |k, v| filter_applies?(k, v, subhash) } + end + end + end + + # Tracks a collection of filterable items (e.g. modules, hooks, etc) + # and provides an optimized API to get the applicable items for the + # metadata of an example or example group. + # + # There are two implementations, optimized for different uses. + # @private + module FilterableItemRepository + # This implementation is simple, and is optimized for frequent + # updates but rare queries. `append` and `prepend` do no extra + # processing, and no internal memoization is done, since this + # is not optimized for queries. + # + # This is ideal for use by a example or example group, which may + # be updated multiple times with globally configured hooks, etc, + # but will not be queried frequently by other examples or example + # groups. + # @private + class UpdateOptimized + attr_reader :items_and_filters + + def initialize(applies_predicate) + @applies_predicate = applies_predicate + @items_and_filters = [] + end + + def append(item, metadata) + @items_and_filters << [item, metadata] + end + + def prepend(item, metadata) + @items_and_filters.unshift [item, metadata] + end + + def delete(item, metadata) + @items_and_filters.delete [item, metadata] + end + + def items_for(request_meta) + @items_and_filters.each_with_object([]) do |(item, item_meta), to_return| + to_return << item if item_meta.empty? || + MetadataFilter.apply?(@applies_predicate, item_meta, request_meta) + end + end + + unless [].respond_to?(:each_with_object) # For 1.8.7 + # :nocov: + undef items_for + def items_for(request_meta) + @items_and_filters.inject([]) do |to_return, (item, item_meta)| + to_return << item if item_meta.empty? || + MetadataFilter.apply?(@applies_predicate, item_meta, request_meta) + to_return + end + end + # :nocov: + end + end + + # This implementation is much more complex, and is optimized for + # rare (or hopefully no) updates once the queries start. Updates + # incur a cost as it has to clear the memoization and keep track + # of applicable keys. Queries will be O(N) the first time an item + # is provided with a given set of applicable metadata; subsequent + # queries with items with the same set of applicable metadata will + # be O(1) due to internal memoization. + # + # This is ideal for use by config, where filterable items (e.g. hooks) + # are typically added at the start of the process (e.g. in `spec_helper`) + # and then repeatedly queried as example groups and examples are defined. + # @private + class QueryOptimized < UpdateOptimized + alias find_items_for items_for + private :find_items_for + + def initialize(applies_predicate) + super + @applicable_keys = Set.new + @proc_keys = Set.new + @memoized_lookups = Hash.new do |hash, applicable_metadata| + hash[applicable_metadata] = find_items_for(applicable_metadata) + end + end + + def append(item, metadata) + super + handle_mutation(metadata) + end + + def prepend(item, metadata) + super + handle_mutation(metadata) + end + + def delete(item, metadata) + super + reconstruct_caches + end + + def items_for(metadata) + # The filtering of `metadata` to `applicable_metadata` is the key thing + # that makes the memoization actually useful in practice, since each + # example and example group have different metadata (e.g. location and + # description). By filtering to the metadata keys our items care about, + # we can ignore extra metadata keys that differ for each example/group. + # For example, given `config.include DBHelpers, :db`, example groups + # can be split into these two sets: those that are tagged with `:db` and those + # that are not. For each set, this method for the first group in the set is + # still an `O(N)` calculation, but all subsequent groups in the set will be + # constant time lookups when they call this method. + applicable_metadata = applicable_metadata_from(metadata) + + if applicable_metadata.any? { |k, _| @proc_keys.include?(k) } + # It's unsafe to memoize lookups involving procs (since they can + # be non-deterministic), so we skip the memoization in this case. + find_items_for(applicable_metadata) + else + @memoized_lookups[applicable_metadata] + end + end + + private + + def reconstruct_caches + @applicable_keys.clear + @proc_keys.clear + @items_and_filters.each do |_item, metadata| + handle_mutation(metadata) + end + end + + def handle_mutation(metadata) + @applicable_keys.merge(metadata.keys) + @proc_keys.merge(proc_keys_from metadata) + @memoized_lookups.clear + end + + def applicable_metadata_from(metadata) + MetadataFilter.silence_metadata_example_group_deprecations do + @applicable_keys.inject({}) do |hash, key| + # :example_group is treated special here because... + # - In RSpec 2, example groups had an `:example_group` key + # - In RSpec 3, that key is deprecated (it was confusing!). + # - The key is not technically present in an example group metadata hash + # (and thus would fail the `metadata.key?(key)` check) but a value + # is provided when accessed via the hash's `default_proc` + # - Thus, for backwards compatibility, we have to explicitly check + # for `:example_group` here if it is one of the keys being used to + # filter. + hash[key] = metadata[key] if metadata.key?(key) || key == :example_group + hash + end + end + end + + def proc_keys_from(metadata) + metadata.each_with_object([]) do |(key, value), to_return| + to_return << key if Proc === value + end + end + + unless [].respond_to?(:each_with_object) # For 1.8.7 + # :nocov: + undef proc_keys_from + def proc_keys_from(metadata) + metadata.inject([]) do |to_return, (key, value)| + to_return << key if Proc === value + to_return + end + end + # :nocov: + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/minitest_assertions_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/minitest_assertions_adapter.rb new file mode 100644 index 0000000000..25db7514a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/minitest_assertions_adapter.rb @@ -0,0 +1,31 @@ +begin + # Only the minitest 5.x gem includes the minitest.rb and assertions.rb files. + require 'minitest' + require 'minitest/assertions' +rescue LoadError + # We must be using Ruby Core's MiniTest or the Minitest gem 4.x. + require 'minitest/unit' + Minitest = MiniTest +end + +module RSpec + module Core + # @private + module MinitestAssertionsAdapter + include ::Minitest::Assertions + # Need to forcefully include Pending after Minitest::Assertions + # to make sure our own #skip method beats Minitest's. + include ::RSpec::Core::Pending + + # Minitest 5.x requires this accessor to be available. See + # https://github.com/seattlerb/minitest/blob/38f0a5fcbd9c37c3f80a3eaad4ba84d3fc9947a0/lib/minitest/assertions.rb#L8 + # + # It is not required for other extension libraries, and RSpec does not + # report or make this information available to formatters. + attr_writer :assertions + def assertions + @assertions ||= 0 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/flexmock.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/flexmock.rb new file mode 100644 index 0000000000..91475ae7f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/flexmock.rb @@ -0,0 +1,31 @@ +# Created by Jim Weirich on 2007-04-10. +# Copyright (c) 2007. All rights reserved. + +require 'flexmock/rspec' + +module RSpec + module Core + module MockingAdapters + # @private + module Flexmock + include ::FlexMock::MockContainer + + def self.framework_name + :flexmock + end + + def setup_mocks_for_rspec + # No setup required. + end + + def verify_mocks_for_rspec + flexmock_verify + end + + def teardown_mocks_for_rspec + flexmock_close + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/mocha.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/mocha.rb new file mode 100644 index 0000000000..8caf7b6442 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/mocha.rb @@ -0,0 +1,57 @@ +# In order to support all versions of mocha, we have to jump through some +# hoops here. +# +# mocha >= '0.13.0': +# require 'mocha/api' is required. +# require 'mocha/object' raises a LoadError b/c the file no longer exists. +# mocha < '0.13.0', >= '0.9.7' +# require 'mocha/api' is required. +# require 'mocha/object' is required. +# mocha < '0.9.7': +# require 'mocha/api' raises a LoadError b/c the file does not yet exist. +# require 'mocha/standalone' is required. +# require 'mocha/object' is required. +begin + require 'mocha/api' + + begin + require 'mocha/object' + rescue LoadError + # Mocha >= 0.13.0 no longer contains this file nor needs it to be loaded. + end +rescue LoadError + require 'mocha/standalone' + require 'mocha/object' +end + +module RSpec + module Core + module MockingAdapters + # @private + module Mocha + def self.framework_name + :mocha + end + + # Mocha::Standalone was deprecated as of Mocha 0.9.7. + begin + include ::Mocha::API + rescue NameError + include ::Mocha::Standalone + end + + def setup_mocks_for_rspec + mocha_setup + end + + def verify_mocks_for_rspec + mocha_verify + end + + def teardown_mocks_for_rspec + mocha_teardown + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/null.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/null.rb new file mode 100644 index 0000000000..442de9a700 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/null.rb @@ -0,0 +1,14 @@ +module RSpec + module Core + module MockingAdapters + # @private + module Null + def setup_mocks_for_rspec; end + + def verify_mocks_for_rspec; end + + def teardown_mocks_for_rspec; end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rr.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rr.rb new file mode 100644 index 0000000000..d72651a626 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rr.rb @@ -0,0 +1,31 @@ +require 'rr' + +RSpec.configuration.backtrace_exclusion_patterns.push(RR::Errors::BACKTRACE_IDENTIFIER) + +module RSpec + module Core + # @private + module MockingAdapters + # @private + module RR + def self.framework_name + :rr + end + + include ::RR::Extensions::InstanceMethods + + def setup_mocks_for_rspec + ::RR::Space.instance.reset + end + + def verify_mocks_for_rspec + ::RR::Space.instance.verify_doubles + end + + def teardown_mocks_for_rspec + ::RR::Space.instance.reset + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rspec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rspec.rb new file mode 100644 index 0000000000..bb3f0ae660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/mocking_adapters/rspec.rb @@ -0,0 +1,32 @@ +require 'rspec/mocks' + +module RSpec + module Core + module MockingAdapters + # @private + module RSpec + include ::RSpec::Mocks::ExampleMethods + + def self.framework_name + :rspec + end + + def self.configuration + ::RSpec::Mocks.configuration + end + + def setup_mocks_for_rspec + ::RSpec::Mocks.setup + end + + def verify_mocks_for_rspec + ::RSpec::Mocks.verify + end + + def teardown_mocks_for_rspec + ::RSpec::Mocks.teardown + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/notifications.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/notifications.rb new file mode 100644 index 0000000000..16a3255cce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/notifications.rb @@ -0,0 +1,521 @@ +RSpec::Support.require_rspec_core "formatters/console_codes" +RSpec::Support.require_rspec_core "formatters/exception_presenter" +RSpec::Support.require_rspec_core "formatters/helpers" +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec::Core + # Notifications are value objects passed to formatters to provide them + # with information about a particular event of interest. + module Notifications + # @private + module NullColorizer + module_function + + def wrap(line, _code_or_symbol) + line + end + end + + # The `StartNotification` represents a notification sent by the reporter + # when the suite is started. It contains the expected amount of examples + # to be executed, and the load time of RSpec. + # + # @attr count [Fixnum] the number counted + # @attr load_time [Float] the number of seconds taken to boot RSpec + # and load the spec files + StartNotification = Struct.new(:count, :load_time) + + # The `ExampleNotification` represents notifications sent by the reporter + # which contain information about the current (or soon to be) example. + # It is used by formatters to access information about that example. + # + # @example + # def example_started(notification) + # puts "Hey I started #{notification.example.description}" + # end + # + # @attr example [RSpec::Core::Example] the current example + ExampleNotification = Struct.new(:example) + class ExampleNotification + # @private + def self.for(example) + execution_result = example.execution_result + + return SkippedExampleNotification.new(example) if execution_result.example_skipped? + return new(example) unless execution_result.status == :pending || execution_result.status == :failed + + klass = if execution_result.pending_fixed? + PendingExampleFixedNotification + elsif execution_result.status == :pending + PendingExampleFailedAsExpectedNotification + else + FailedExampleNotification + end + + klass.new(example) + end + + private_class_method :new + end + + # The `ExamplesNotification` represents notifications sent by the reporter + # which contain information about the suites examples. + # + # @example + # def stop(notification) + # puts "Hey I ran #{notification.examples.size}" + # end + # + class ExamplesNotification + def initialize(reporter) + @reporter = reporter + end + + # @return [Array] list of examples + def examples + @reporter.examples + end + + # @return [Array] list of failed examples + def failed_examples + @reporter.failed_examples + end + + # @return [Array] list of pending examples + def pending_examples + @reporter.pending_examples + end + + # @return [Array] + # returns examples as notifications + def notifications + @notifications ||= format_examples(examples) + end + + # @return [Array] + # returns failed examples as notifications + def failure_notifications + @failed_notifications ||= format_examples(failed_examples) + end + + # @return [Array] + # returns pending examples as notifications + def pending_notifications + @pending_notifications ||= format_examples(pending_examples) + end + + # @return [String] The list of failed examples, fully formatted in the way + # that RSpec's built-in formatters emit. + def fully_formatted_failed_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nFailures:\n" + + failure_notifications.each_with_index do |failure, index| + formatted += failure.fully_formatted(index.next, colorizer) + end + + formatted + end + + # @return [String] The list of pending examples, fully formatted in the + # way that RSpec's built-in formatters emit. + def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n".dup + + pending_notifications.each_with_index do |notification, index| + formatted << notification.fully_formatted(index.next, colorizer) + end + + formatted + end + + private + + def format_examples(examples) + examples.map do |example| + ExampleNotification.for(example) + end + end + end + + # The `FailedExampleNotification` extends `ExampleNotification` with + # things useful for examples that have failure info -- typically a + # failed or pending spec. + # + # @example + # def example_failed(notification) + # puts "Hey I failed :(" + # puts "Here's my stack trace" + # puts notification.exception.backtrace.join("\n") + # end + # + # @attr [RSpec::Core::Example] example the current example + # @see ExampleNotification + class FailedExampleNotification < ExampleNotification + public_class_method :new + + # @return [Exception] The example failure + def exception + @exception_presenter.exception + end + + # @return [String] The example description + def description + @exception_presenter.description + end + + # Returns the message generated for this failure line by line. + # + # @return [Array] The example failure message + def message_lines + @exception_presenter.message_lines + end + + # Returns the message generated for this failure colorized line by line. + # + # @param colorizer [#wrap] An object to colorize the message_lines by + # @return [Array] The example failure message colorized + def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.colorized_message_lines(colorizer) + end + + # Returns the failures formatted backtrace. + # + # @return [Array] the examples backtrace lines + def formatted_backtrace + @exception_presenter.formatted_backtrace + end + + # Returns the failures colorized formatted backtrace. + # + # @param colorizer [#wrap] An object to colorize the message_lines by + # @return [Array] the examples colorized backtrace lines + def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.colorized_formatted_backtrace(colorizer) + end + + # @return [String] The failure information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.fully_formatted(failure_number, colorizer) + end + + # @return [Array] The failure information fully formatted in the way that + # RSpec's built-in formatters emit, split by line. + def fully_formatted_lines(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + @exception_presenter.fully_formatted_lines(failure_number, colorizer) + end + + private + + def initialize(example, exception_presenter=Formatters::ExceptionPresenter::Factory.new(example).build) + @exception_presenter = exception_presenter + super(example) + end + end + + # @deprecated Use {FailedExampleNotification} instead. + class PendingExampleFixedNotification < FailedExampleNotification; end + + # @deprecated Use {FailedExampleNotification} instead. + class PendingExampleFailedAsExpectedNotification < FailedExampleNotification; end + + # The `SkippedExampleNotification` extends `ExampleNotification` with + # things useful for specs that are skipped. + # + # @attr [RSpec::Core::Example] example the current example + # @see ExampleNotification + class SkippedExampleNotification < ExampleNotification + public_class_method :new + + # @return [String] The pending detail fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location) + + [ + colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending), + "\n ", + Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer), + "\n", + colorizer.wrap(" # #{formatted_caller}\n", :detail) + ].join("") + end + end + + # The `GroupNotification` represents notifications sent by the reporter + # which contain information about the currently running (or soon to be) + # example group. It is used by formatters to access information about that + # group. + # + # @example + # def example_group_started(notification) + # puts "Hey I started #{notification.group.description}" + # end + # @attr group [RSpec::Core::ExampleGroup] the current group + GroupNotification = Struct.new(:group) + + # The `MessageNotification` encapsulates generic messages that the reporter + # sends to formatters. + # + # @attr message [String] the message + MessageNotification = Struct.new(:message) + + # The `SeedNotification` holds the seed used to randomize examples and + # whether that seed has been used or not. + # + # @attr seed [Fixnum] the seed used to randomize ordering + # @attr used [Boolean] whether the seed has been used or not + SeedNotification = Struct.new(:seed, :used) + class SeedNotification + # @api + # @return [Boolean] has the seed been used? + def seed_used? + !!used + end + private :used + + # @return [String] The seed information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted + "\nRandomized with seed #{seed}\n" + end + end + + # The `SummaryNotification` holds information about the results of running + # a test suite. It is used by formatters to provide information at the end + # of the test run. + # + # @attr duration [Float] the time taken (in seconds) to run the suite + # @attr examples [Array] the examples run + # @attr failed_examples [Array] the failed examples + # @attr pending_examples [Array] the pending examples + # @attr load_time [Float] the number of seconds taken to boot RSpec + # and load the spec files + # @attr errors_outside_of_examples_count [Integer] the number of errors that + # have occurred processing + # the spec suite + SummaryNotification = Struct.new(:duration, :examples, :failed_examples, + :pending_examples, :load_time, + :errors_outside_of_examples_count) + class SummaryNotification + # @api + # @return [Fixnum] the number of examples run + def example_count + @example_count ||= examples.size + end + + # @api + # @return [Fixnum] the number of failed examples + def failure_count + @failure_count ||= failed_examples.size + end + + # @api + # @return [Fixnum] the number of pending examples + def pending_count + @pending_count ||= pending_examples.size + end + + # @api + # @return [String] A line summarising the result totals of the spec run. + def totals_line + summary = Formatters::Helpers.pluralize(example_count, "example") + + ", " + Formatters::Helpers.pluralize(failure_count, "failure") + summary += ", #{pending_count} pending" if pending_count > 0 + if errors_outside_of_examples_count > 0 + summary += ( + ", " + + Formatters::Helpers.pluralize(errors_outside_of_examples_count, "error") + + " occurred outside of examples" + ) + end + summary + end + + # @api public + # + # Wraps the results line with colors based on the configured + # colors for failure, pending, and success. Defaults to red, + # yellow, green accordingly. + # + # @param colorizer [#wrap] An object which supports wrapping text with + # specific colors. + # @return [String] A colorized results line. + def colorized_totals_line(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + if failure_count > 0 || errors_outside_of_examples_count > 0 + colorizer.wrap(totals_line, RSpec.configuration.failure_color) + elsif pending_count > 0 + colorizer.wrap(totals_line, RSpec.configuration.pending_color) + else + colorizer.wrap(totals_line, RSpec.configuration.success_color) + end + end + + # @api public + # + # Formats failures into a rerunable command format. + # + # @param colorizer [#wrap] An object which supports wrapping text with + # specific colors. + # @return [String] A colorized summary line. + def colorized_rerun_commands(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + "\nFailed examples:\n\n" + + failed_examples.map do |example| + colorizer.wrap("rspec #{rerun_argument_for(example)}", RSpec.configuration.failure_color) + " " + + colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color) + end.join("\n") + end + + # @return [String] a formatted version of the time it took to run the + # suite + def formatted_duration + Formatters::Helpers.format_duration(duration) + end + + # @return [String] a formatted version of the time it took to boot RSpec + # and load the spec files + def formatted_load_time + Formatters::Helpers.format_duration(load_time) + end + + # @return [String] The summary information fully formatted in the way that + # RSpec's built-in formatters emit. + def fully_formatted(colorizer=::RSpec::Core::Formatters::ConsoleCodes) + formatted = "\nFinished in #{formatted_duration} " \ + "(files took #{formatted_load_time} to load)\n" \ + "#{colorized_totals_line(colorizer)}\n" + + unless failed_examples.empty? + formatted += (colorized_rerun_commands(colorizer) + "\n") + end + + formatted + end + + private + + include RSpec::Core::ShellEscape + + def rerun_argument_for(example) + location = example.location_rerun_argument + return location unless duplicate_rerun_locations.include?(location) + conditionally_quote(example.id) + end + + def duplicate_rerun_locations + @duplicate_rerun_locations ||= begin + locations = RSpec.world.all_examples.map(&:location_rerun_argument) + + Set.new.tap do |s| + locations.group_by { |l| l }.each do |l, ls| + s << l if ls.count > 1 + end + end + end + end + end + + # The `ProfileNotification` holds information about the results of running a + # test suite when profiling is enabled. It is used by formatters to provide + # information at the end of the test run for profiling information. + # + # @attr duration [Float] the time taken (in seconds) to run the suite + # @attr examples [Array] the examples run + # @attr number_of_examples [Fixnum] the number of examples to profile + # @attr example_groups [Array] example groups run + class ProfileNotification + def initialize(duration, examples, number_of_examples, example_groups) + @duration = duration + @examples = examples + @number_of_examples = number_of_examples + @example_groups = example_groups + end + attr_reader :duration, :examples, :number_of_examples + + # @return [Array] the slowest examples + def slowest_examples + @slowest_examples ||= + examples.sort_by do |example| + -example.execution_result.run_time + end.first(number_of_examples) + end + + # @return [Float] the time taken (in seconds) to run the slowest examples + def slow_duration + @slow_duration ||= + slowest_examples.inject(0.0) do |i, e| + i + e.execution_result.run_time + end + end + + # @return [String] the percentage of total time taken + def percentage + @percentage ||= + begin + time_taken = slow_duration / duration + '%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100) + end + end + + # @return [Array] the slowest example groups + def slowest_groups + @slowest_groups ||= calculate_slowest_groups + end + + private + + def calculate_slowest_groups + # stop if we've only one example group + return {} if @example_groups.keys.length <= 1 + + @example_groups.each_value do |hash| + hash[:average] = hash[:total_time].to_f / hash[:count] + end + + groups = @example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples) + groups.map { |group, data| [group.location, data] } + end + end + + # The `DeprecationNotification` is issued by the reporter when a deprecated + # part of RSpec is encountered. It represents information about the + # deprecated call site. + # + # @attr message [String] A custom message about the deprecation + # @attr deprecated [String] A custom message about the deprecation (alias of + # message) + # @attr replacement [String] An optional replacement for the deprecation + # @attr call_site [String] An optional call site from which the deprecation + # was issued + DeprecationNotification = Struct.new(:deprecated, :message, :replacement, :call_site) + class DeprecationNotification + private_class_method :new + + # @api + # Convenience way to initialize the notification + def self.from_hash(data) + new data[:deprecated], data[:message], data[:replacement], data[:call_site] + end + end + + # `NullNotification` represents a placeholder value for notifications that + # currently require no information, but we may wish to extend in future. + class NullNotification + end + + # `CustomNotification` is used when sending custom events to formatters / + # other registered listeners, it creates attributes based on supplied hash + # of options. + class CustomNotification < Struct + # @param options [Hash] A hash of method / value pairs to create on this notification + # @return [CustomNotification] + # + # Build a custom notification based on the supplied option key / values. + def self.for(options={}) + return NullNotification if options.keys.empty? + new(*options.keys).new(*options.values) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/option_parser.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/option_parser.rb new file mode 100644 index 0000000000..35ed0c9501 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/option_parser.rb @@ -0,0 +1,323 @@ +# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html +require 'optparse' + +module RSpec::Core + # @private + class Parser + def self.parse(args, source=nil) + new(args).parse(source) + end + + attr_reader :original_args + + def initialize(original_args) + @original_args = original_args + end + + def parse(source=nil) + return { :files_or_directories_to_run => [] } if original_args.empty? + args = original_args.dup + + options = args.delete('--tty') ? { :tty => true } : {} + begin + parser(options).parse!(args) + rescue OptionParser::InvalidOption => e + abort "#{e.message}#{" (defined in #{source})" if source}\n\n" \ + "Please use --help for a listing of valid options" + end + + options[:files_or_directories_to_run] = args + options + end + + private + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def parser(options) + OptionParser.new do |parser| + parser.summary_width = 34 + + parser.banner = "Usage: rspec [options] [files or directories]\n\n" + + parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs| + options[:libs] ||= [] + options[:libs].concat(dirs.split(File::PATH_SEPARATOR)) + end + + parser.on('-r', '--require PATH', 'Require a file.') do |path| + options[:requires] ||= [] + options[:requires] << path + end + + parser.on('-O', '--options PATH', 'Specify the path to a custom options file.') do |path| + options[:custom_options_file] = path + end + + parser.on('--order TYPE[:SEED]', 'Run examples by the specified order type.', + ' [defined] examples and groups are run in the order they are defined', + ' [rand] randomize the order of groups and examples', + ' [random] alias for rand', + ' [random:SEED] e.g. --order random:123', + ' [recently-modified] run the most recently modified files first') do |o| + options[:order] = o + end + + parser.on('--seed SEED', Integer, 'Equivalent of --order rand:SEED.') do |seed| + options[:order] = "rand:#{seed}" + end + + parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ', + ' smallest reproducible case.') do |argument| + options[:bisect] = argument || true + options[:runner] = RSpec::Core::Invocations::Bisect.new + end + + parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument| + if argument == true + value = 1 + elsif argument == false || argument == 0 + value = false + else + begin + value = Integer(argument) + rescue ArgumentError + RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil + end + end + set_fail_fast(options, value) + end + + parser.on('--failure-exit-code CODE', Integer, + 'Override the exit code used when there are failing specs.') do |code| + options[:failure_exit_code] = code + end + + parser.on('--error-exit-code CODE', Integer, + 'Override the exit code used when there are errors loading or running specs outside of examples.') do |code| + options[:error_exit_code] = code + end + + parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |use_drb| + options[:drb] = use_drb + options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb + end + + parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o| + options[:drb_port] = o.to_i + end + + parser.separator("\n **** Output ****\n\n") + + parser.on('-f', '--format FORMATTER', 'Choose a formatter.', + ' [p]rogress (default - dots)', + ' [d]ocumentation (group and example names)', + ' [h]tml', + ' [j]son', + ' [f]ailures ("file:line:reason", suitable for editors integration)', + ' custom formatter class name') do |o| + options[:formatters] ||= [] + options[:formatters] << [o] + end + + parser.on('-o', '--out FILE', + 'Write output to a file instead of $stdout. This option applies', + ' to the previously specified --format, or the default format', + ' if no format is specified.' + ) do |o| + options[:formatters] ||= [['progress']] + options[:formatters].last << o + end + + parser.on('--deprecation-out FILE', 'Write deprecation warnings to a file instead of $stderr.') do |file| + options[:deprecation_stream] = file + end + + parser.on('-b', '--backtrace', 'Enable full backtrace.') do |_o| + options[:full_backtrace] = true + end + + parser.on('-c', '--color', '--colour', '') do |_o| + # flag will be excluded from `--help` output because it is deprecated + options[:color] = true + options[:color_mode] = :automatic + end + + parser.on('--force-color', '--force-colour', 'Force the output to be in color, even if the output is not a TTY') do |_o| + if options[:color_mode] == :off + abort "Please only use one of `--force-color` and `--no-color`" + end + options[:color_mode] = :on + end + + parser.on('--no-color', '--no-colour', 'Force the output to not be in color, even if the output is a TTY') do |_o| + if options[:color_mode] == :on + abort "Please only use one of --force-color and --no-color" + end + options[:color_mode] = :off + end + + parser.on('-p', '--[no-]profile [COUNT]', + 'Enable profiling of examples and list the slowest examples (default: 10).') do |argument| + options[:profile_examples] = if argument.nil? + true + elsif argument == false + false + else + begin + Integer(argument) + rescue ArgumentError + RSpec.warning "Non integer specified as profile count, separate " \ + "your path from options with -- e.g. " \ + "`rspec --profile -- #{argument}`", + :call_site => nil + true + end + end + end + + parser.on('--dry-run', 'Print the formatter output of your suite without', + ' running any examples or hooks') do |_o| + options[:dry_run] = true + end + + parser.on('-w', '--warnings', 'Enable ruby warnings') do + if Object.const_defined?(:Warning) && Warning.respond_to?(:[]=) + Warning[:deprecated] = true + end + $VERBOSE = true + end + + parser.separator <<-FILTERING + + **** Filtering/tags **** + + In addition to the following options for selecting specific files, groups, or + examples, you can select individual examples by appending the line number(s) to + the filename: + + rspec path/to/a_spec.rb:37:87 + + You can also pass example ids enclosed in square brackets: + + rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group + +FILTERING + + parser.on('--only-failures', "Filter to just the examples that failed the last time they ran.") do + configure_only_failures(options) + end + + parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.", + " (Equivalent to `--only-failures --fail-fast --order defined`)") do + configure_only_failures(options) + set_fail_fast(options, 1) + options[:order] ||= 'defined' + end + + parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o| + if options[:pattern] + options[:pattern] += ',' + o + else + options[:pattern] = o + end + end + + parser.on('--exclude-pattern PATTERN', + 'Load files except those matching pattern. Opposite effect of --pattern.') do |o| + options[:exclude_pattern] = o + end + + parser.on('-e', '--example STRING', "Run examples whose full nested names include STRING (may be", + " used more than once)") do |o| + (options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o)) + end + + parser.on('-E', '--example-matches REGEX', "Run examples whose full nested names match REGEX (may be", + " used more than once)") do |o| + (options[:full_description] ||= []) << Regexp.compile(o) + end + + parser.on('-t', '--tag TAG[:VALUE]', + 'Run examples with the specified tag, or exclude examples', + 'by adding ~ before the tag.', + ' - e.g. ~slow', + ' - TAG is always converted to a symbol') do |tag| + filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter + + name, value = tag.gsub(/^(~@|~|@)/, '').split(':', 2) + name = name.to_sym + + parsed_value = case value + when nil then true # The default value for tags is true + when 'true' then true + when 'false' then false + when 'nil' then nil + when /^:/ then value[1..-1].to_sym + when /^\d+$/ then Integer(value) + when /^\d+.\d+$/ then Float(value) + else + value + end + + add_tag_filter(options, filter_type, name, parsed_value) + end + + parser.on('--default-path PATH', 'Set the default path where RSpec looks for examples (can', + ' be a path to a file or a directory).') do |path| + options[:default_path] = path + end + + parser.separator("\n **** Utility ****\n\n") + + parser.on('--init', 'Initialize your project with RSpec.') do |_cmd| + options[:runner] = RSpec::Core::Invocations::InitializeProject.new + end + + parser.on('-v', '--version', 'Display the version.') do + options[:runner] = RSpec::Core::Invocations::PrintVersion.new + end + + # These options would otherwise be confusing to users, so we forcibly + # prevent them from executing. + # + # * --I is too similar to -I. + # * -d was a shorthand for --debugger, which is removed, but now would + # trigger --default-path. + invalid_options = %w[-d --I] + + hidden_options = invalid_options + %w[-c] + + parser.on_tail('-h', '--help', "You're looking at it.") do + options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options) + end + + # This prevents usage of the invalid_options. + invalid_options.each do |option| + parser.on(option) do + raise OptionParser::InvalidOption.new + end + end + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + + def add_tag_filter(options, filter_type, tag_name, value=true) + (options[filter_type] ||= {})[tag_name] = value + end + + def set_fail_fast(options, value) + options[:fail_fast] = value + end + + def configure_only_failures(options) + options[:only_failures] = true + add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed') + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ordering.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ordering.rb new file mode 100644 index 0000000000..d852324dc5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ordering.rb @@ -0,0 +1,169 @@ +module RSpec + module Core + # @private + module Ordering + # @private + # The default global ordering (defined order). + class Identity + def order(items) + items + end + end + + # @private + # Orders items randomly. + class Random + def initialize(configuration) + @configuration = configuration + @used = false + end + + def used? + @used + end + + def order(items) + @used = true + + seed = @configuration.seed.to_s + items.sort_by { |item| jenkins_hash_digest(seed + item.id) } + end + + private + + # http://en.wikipedia.org/wiki/Jenkins_hash_function + # Jenkins provides a good distribution and is simpler than MD5. + # It's a bit slower than MD5 (primarily because `Digest::MD5` is + # implemented in C) but has the advantage of not requiring us + # to load another part of stdlib, which we try to minimize. + def jenkins_hash_digest(string) + hash = 0 + + string.each_byte do |byte| + hash += byte + hash &= MAX_32_BIT + hash += ((hash << 10) & MAX_32_BIT) + hash &= MAX_32_BIT + hash ^= hash >> 6 + end + + hash += ((hash << 3) & MAX_32_BIT) + hash &= MAX_32_BIT + hash ^= hash >> 11 + hash += ((hash << 15) & MAX_32_BIT) + hash &= MAX_32_BIT + hash + end + + MAX_32_BIT = 4_294_967_295 + end + + # @private + # Orders items by modification time (most recent modified first). + class RecentlyModified + def order(list) + list.sort_by { |item| -File.mtime(item.metadata[:absolute_file_path]).to_i } + end + end + + # @private + # Orders items based on a custom block. + class Custom + def initialize(callable) + @callable = callable + end + + def order(list) + @callable.call(list) + end + end + + # @private + # Stores the different ordering strategies. + class Registry + def initialize(configuration) + @configuration = configuration + @strategies = {} + + register(:random, Random.new(configuration)) + register(:recently_modified, RecentlyModified.new) + + identity = Identity.new + register(:defined, identity) + + # The default global ordering is --defined. + register(:global, identity) + end + + def fetch(name, &fallback) + @strategies.fetch(name, &fallback) + end + + def register(sym, strategy) + @strategies[sym] = strategy + end + + def used_random_seed? + @strategies[:random].used? + end + end + + # @private + # Manages ordering configuration. + # + # @note This is not intended to be used externally. Use + # the APIs provided by `RSpec::Core::Configuration` instead. + class ConfigurationManager + attr_reader :seed, :ordering_registry + + def initialize + @ordering_registry = Registry.new(self) + @seed = rand(0xFFFF) + @seed_forced = false + @order_forced = false + end + + def seed_used? + ordering_registry.used_random_seed? + end + + def seed=(seed) + return if @seed_forced + register_ordering(:global, ordering_registry.fetch(:random)) + @seed = seed.to_i + end + + def order=(type) + order, seed = type.to_s.split(':') + @seed = seed.to_i if seed + + ordering_name = if order.include?('rand') + :random + elsif order == 'defined' + :defined + elsif order == 'recently-modified' + :recently_modified + end + + register_ordering(:global, ordering_registry.fetch(ordering_name)) if ordering_name + end + + def force(hash) + if hash.key?(:seed) + self.seed = hash[:seed] + @seed_forced = true + @order_forced = true + elsif hash.key?(:order) + self.order = hash[:order] + @order_forced = true + end + end + + def register_ordering(name, strategy=Custom.new(Proc.new { |l| yield l })) + return if @order_forced && name == :global + ordering_registry.register(name, strategy) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/output_wrapper.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/output_wrapper.rb new file mode 100644 index 0000000000..b655025cbf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/output_wrapper.rb @@ -0,0 +1,29 @@ +module RSpec + module Core + # @private + class OutputWrapper + # @private + attr_accessor :output + + # @private + def initialize(output) + @output = output + end + + def respond_to?(name, priv=false) + output.respond_to?(name, priv) + end + + def method_missing(name, *args, &block) + output.send(name, *args, &block) + end + + # Redirect calls for IO interface methods + IO.instance_methods(false).each do |method| + define_method(method) do |*args, &block| + output.send(method, *args, &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/pending.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/pending.rb new file mode 100644 index 0000000000..c0f394aeee --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/pending.rb @@ -0,0 +1,157 @@ +module RSpec + module Core + # Provides methods to mark examples as pending. These methods are available + # to be called from within any example or hook. + module Pending + # Raised in the middle of an example to indicate that it should be marked + # as skipped. + class SkipDeclaredInExample < StandardError + attr_reader :argument + + def initialize(argument) + @argument = argument + end + end + + # If Test::Unit is loaded, we'll use its error as baseclass, so that + # Test::Unit will report unmet RSpec expectations as failures rather than + # errors. + begin + class PendingExampleFixedError < Test::Unit::AssertionFailedError; end + rescue + class PendingExampleFixedError < StandardError; end + end + + # @private + NO_REASON_GIVEN = 'No reason given' + + # @private + NOT_YET_IMPLEMENTED = 'Not yet implemented' + + # @overload pending() + # @overload pending(message) + # + # Marks an example as pending. The rest of the example will still be + # executed, and if it passes the example will fail to indicate that the + # pending can be removed. + # + # @param message [String] optional message to add to the summary report. + # + # @example + # describe "some behaviour" do + # # reported as "Pending: no reason given" + # it "is pending with no message" do + # pending + # raise "broken" + # end + # + # # reported as "Pending: something else getting finished" + # it "is pending with a custom message" do + # pending("something else getting finished") + # raise "broken" + # end + # end + # + # @note When using `pending` inside an example body using this method + # hooks, such as `before(:example)`, have already be run. This means that + # a failure from the code in the `before` hook will prevent the example + # from being considered pending, as the example body would not be + # executed. If you need to consider hooks as pending as well you can use + # the pending metadata as an alternative, e.g. + # `it "does something", pending: "message"`. + def pending(message=nil) + current_example = RSpec.current_example + + if block_given? + raise ArgumentError, <<-EOS.gsub(/^\s+\|/, '') + |The semantics of `RSpec::Core::Pending#pending` have changed in + |RSpec 3. In RSpec 2.x, it caused the example to be skipped. In + |RSpec 3, the rest of the example is still run but is expected to + |fail, and will be marked as a failure (rather than as pending) if + |the example passes. + | + |Passing a block within an example is now deprecated. Marking the + |example as pending provides the same behavior in RSpec 3 which was + |provided only by the block in RSpec 2.x. + | + |Move the code in the block provided to `pending` into the rest of + |the example body. + | + |Called from #{CallerFilter.first_non_rspec_line}. + | + EOS + elsif current_example + Pending.mark_pending! current_example, message + else + raise "`pending` may not be used outside of examples, such as in " \ + "before(:context). Maybe you want `skip`?" + end + end + + # @overload skip() + # @overload skip(message) + # + # Marks an example as pending and skips execution. + # + # @param message [String] optional message to add to the summary report. + # + # @example + # describe "an example" do + # # reported as "Pending: no reason given" + # it "is skipped with no message" do + # skip + # end + # + # # reported as "Pending: something else getting finished" + # it "is skipped with a custom message" do + # skip "something else getting finished" + # end + # end + def skip(message=nil) + current_example = RSpec.current_example + + Pending.mark_skipped!(current_example, message) if current_example + + raise SkipDeclaredInExample.new(message) + end + + # @private + # + # Mark example as skipped. + # + # @param example [RSpec::Core::Example] the example to mark as skipped + # @param message_or_bool [Boolean, String] the message to use, or true + def self.mark_skipped!(example, message_or_bool) + Pending.mark_pending! example, message_or_bool + example.metadata[:skip] = true + end + + # @private + # + # Mark example as pending. + # + # @param example [RSpec::Core::Example] the example to mark as pending + # @param message_or_bool [Boolean, String] the message to use, or true + def self.mark_pending!(example, message_or_bool) + message = if !message_or_bool || !(String === message_or_bool) + NO_REASON_GIVEN + else + message_or_bool + end + + example.metadata[:pending] = true + example.execution_result.pending_message = message + example.execution_result.pending_fixed = false + end + + # @private + # + # Mark example as fixed. + # + # @param example [RSpec::Core::Example] the example to mark as fixed + def self.mark_fixed!(example) + example.execution_result.pending_fixed = true + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/profiler.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/profiler.rb new file mode 100644 index 0000000000..5e6527974e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/profiler.rb @@ -0,0 +1,34 @@ +module RSpec + module Core + # @private + class Profiler + NOTIFICATIONS = [:example_group_started, :example_group_finished, :example_started] + + def initialize + @example_groups = Hash.new { |h, k| h[k] = { :count => 0 } } + end + + attr_reader :example_groups + + def example_group_started(notification) + return unless notification.group.top_level? + + @example_groups[notification.group][:start] = Time.now + @example_groups[notification.group][:description] = notification.group.top_level_description + end + + def example_group_finished(notification) + return unless notification.group.top_level? + + group = @example_groups[notification.group] + return unless group.key?(:start) + group[:total_time] = Time.now - group[:start] + end + + def example_started(notification) + group = notification.example.example_group.parent_groups.last + @example_groups[group][:count] += 1 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer.rb new file mode 100644 index 0000000000..ca707e0367 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer.rb @@ -0,0 +1,48 @@ +RSpec::Support.require_rspec_support "directory_maker" + +module RSpec + module Core + # @private + # Generates conventional files for an RSpec project. + class ProjectInitializer + attr_reader :destination, :stream, :template_path + + DOT_RSPEC_FILE = '.rspec' + SPEC_HELPER_FILE = 'spec/spec_helper.rb' + + def initialize(opts={}) + @destination = opts.fetch(:destination, Dir.getwd) + @stream = opts.fetch(:report_stream, $stdout) + @template_path = opts.fetch(:template_path) do + File.expand_path("../project_initializer", __FILE__) + end + end + + def run + copy_template DOT_RSPEC_FILE + copy_template SPEC_HELPER_FILE + end + + private + + def copy_template(file) + destination_file = File.join(destination, file) + return report_exists(file) if File.exist?(destination_file) + + report_creating(file) + RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(destination_file)) + File.open(destination_file, 'w') do |f| + f.write File.read(File.join(template_path, file)) + end + end + + def report_exists(file) + stream.puts " exist #{file}" + end + + def report_creating(file) + stream.puts " create #{file}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/.rspec b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/.rspec new file mode 100644 index 0000000000..c99d2e7396 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/spec/spec_helper.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/spec/spec_helper.rb new file mode 100644 index 0000000000..5ae5b6962c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/project_initializer/spec/spec_helper.rb @@ -0,0 +1,98 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/rake_task.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/rake_task.rb new file mode 100644 index 0000000000..8cf474a94f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/rake_task.rb @@ -0,0 +1,188 @@ +require 'rake' +require 'rake/tasklib' +require 'rspec/support' + +RSpec::Support.require_rspec_support "ruby_features" + +# :nocov: +unless RSpec::Support.respond_to?(:require_rspec_core) + RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative "../#{f}" } +end +# :nocov: + +RSpec::Support.require_rspec_core "shell_escape" + +module RSpec + module Core + # RSpec rake task + # + # @see Rakefile + class RakeTask < ::Rake::TaskLib + include ::Rake::DSL if defined?(::Rake::DSL) + include RSpec::Core::ShellEscape + + # Default path to the RSpec executable. + DEFAULT_RSPEC_PATH = File.expand_path('../../../../exe/rspec', __FILE__) + + # Default pattern for spec files. + DEFAULT_PATTERN = 'spec/**{,/*/**}/*_spec.rb' + + # Name of task. Defaults to `:spec`. + attr_accessor :name + + # Files matching this pattern will be loaded. + # Defaults to `'spec/**{,/*/**}/*_spec.rb'`. + attr_accessor :pattern + + # Files matching this pattern will be excluded. + # Defaults to `nil`. + attr_accessor :exclude_pattern + + # Whether or not to fail Rake when an error occurs (typically when + # examples fail). Defaults to `true`. + attr_accessor :fail_on_error + + # A message to print to stderr when there are failures. + attr_accessor :failure_message + + if RUBY_VERSION < "1.9.0" || Support::Ruby.jruby? + # Run RSpec with a clean (empty) environment is not supported + def with_clean_environment=(_value) + raise ArgumentError, "Running in a clean environment is not supported on Ruby versions before 1.9.0" + end + + # Run RSpec with a clean (empty) environment is not supported + def with_clean_environment + false + end + else + # Run RSpec with a clean (empty) environment. + attr_accessor :with_clean_environment + end + + # Use verbose output. If this is set to true, the task will print the + # executed spec command to stdout. Defaults to `true`. + attr_accessor :verbose + + # Command line options to pass to ruby. Defaults to `nil`. + attr_accessor :ruby_opts + + # Path to RSpec. Defaults to the absolute path to the + # rspec binary from the loaded rspec-core gem. + attr_accessor :rspec_path + + # Command line options to pass to RSpec. Defaults to `nil`. + attr_accessor :rspec_opts + + def initialize(*args, &task_block) + @name = args.shift || :spec + @ruby_opts = nil + @rspec_opts = nil + @verbose = true + @fail_on_error = true + @rspec_path = DEFAULT_RSPEC_PATH + @pattern = DEFAULT_PATTERN + + define(args, &task_block) + end + + # @private + def run_task(verbose) + command = spec_command + puts command if verbose + + if with_clean_environment + return if system({}, command, :unsetenv_others => true) + else + return if system(command) + end + + puts failure_message if failure_message + + return unless fail_on_error + $stderr.puts "#{command} failed" if verbose + exit $?.exitstatus || 1 + end + + private + + # @private + def define(args, &task_block) + desc "Run RSpec code examples" unless ::Rake.application.last_description + + task name, *args do |_, task_args| + RakeFileUtils.__send__(:verbose, verbose) do + task_block.call(*[self, task_args].slice(0, task_block.arity)) if task_block + run_task verbose + end + end + end + + def file_inclusion_specification + if ENV['SPEC'] + FileList[ENV['SPEC']].sort + elsif String === pattern && !File.exist?(pattern) + return if [*rspec_opts].any? { |opt| opt =~ /--pattern/ } + "--pattern #{escape pattern}" + else + # Before RSpec 3.1, we used `FileList` to get the list of matched + # files, and then pass that along to the `rspec` command. Starting + # with 3.1, we prefer to pass along the pattern as-is to the `rspec` + # command, for 3 reasons: + # + # * It's *much* less verbose to pass one `--pattern` option than a + # long list of files. + # * It ensures `task.pattern` and `--pattern` have the same + # behavior. + # * It fixes a bug, where + # `task.pattern = pattern_that_matches_no_files` would run *all* + # files because it would cause no pattern or file args to get + # passed to `rspec`, which causes all files to get run. + # + # However, `FileList` is *far* more flexible than the `--pattern` + # option. Specifically, it supports individual files and directories, + # as well as arrays of files, directories and globs, as well as other + # `FileList` objects. + # + # For backwards compatibility, we have to fall back to using FileList + # if the user has passed a `pattern` option that will not work with + # `--pattern`. + # + # TODO: consider deprecating support for this and removing it in + # RSpec 4. + FileList[pattern].sort.map { |file| escape file } + end + end + + def file_exclusion_specification + " --exclude-pattern #{escape exclude_pattern}" if exclude_pattern + end + + def spec_command + cmd_parts = [] + cmd_parts << RUBY + cmd_parts << ruby_opts + cmd_parts << rspec_load_path + cmd_parts << escape(rspec_path) + cmd_parts << file_inclusion_specification + cmd_parts << file_exclusion_specification + cmd_parts << rspec_opts + cmd_parts.flatten.reject(&blank).join(" ") + end + + def blank + lambda { |s| s.nil? || s == "" } + end + + def rspec_load_path + @rspec_load_path ||= begin + core_and_support = $LOAD_PATH.grep( + /#{File::SEPARATOR}rspec-(core|support)[^#{File::SEPARATOR}]*#{File::SEPARATOR}lib/ + ).uniq + + "-I#{core_and_support.map { |file| escape file }.join(File::PATH_SEPARATOR)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/reporter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/reporter.rb new file mode 100644 index 0000000000..a016d0f860 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/reporter.rb @@ -0,0 +1,265 @@ +module RSpec::Core + # A reporter will send notifications to listeners, usually formatters for the + # spec suite run. + class Reporter + # @private + RSPEC_NOTIFICATIONS = Set.new( + [ + :close, :deprecation, :deprecation_summary, :dump_failures, :dump_pending, + :dump_profile, :dump_summary, :example_failed, :example_group_finished, + :example_group_started, :example_passed, :example_pending, :example_started, + :message, :seed, :start, :start_dump, :stop, :example_finished + ]) + + def initialize(configuration) + @configuration = configuration + @listeners = Hash.new { |h, k| h[k] = Set.new } + @examples = [] + @failed_examples = [] + @pending_examples = [] + @duration = @start = @load_time = nil + @non_example_exception_count = 0 + @setup_default = lambda {} + @setup = false + @profiler = nil + end + + # @private + attr_reader :examples, :failed_examples, :pending_examples + + # Registers a listener to a list of notifications. The reporter will send + # notification of events to all registered listeners. + # + # @param listener [Object] An obect that wishes to be notified of reporter + # events + # @param notifications [Array] Array of symbols represents the events a + # listener wishes to subscribe too + def register_listener(listener, *notifications) + notifications.each do |notification| + @listeners[notification.to_sym] << listener + end + true + end + + # @private + def prepare_default(loader, output_stream, deprecation_stream) + @setup_default = lambda do + loader.setup_default output_stream, deprecation_stream + end + end + + # @private + def registered_listeners(notification) + @listeners[notification].to_a + end + + # @overload report(count, &block) + # @overload report(count, &block) + # @param expected_example_count [Integer] the number of examples being run + # @yield [Block] block yields itself for further reporting. + # + # Initializes the report run and yields itself for further reporting. The + # block is required, so that the reporter can manage cleaning up after the + # run. + # + # @example + # + # reporter.report(group.examples.size) do |r| + # example_groups.map {|g| g.run(r) } + # end + # + def report(expected_example_count) + start(expected_example_count) + begin + yield self + ensure + finish + end + end + + # @param exit_code [Integer] the exit_code to be return by the reporter + # + # Reports a run that exited early without having run any examples. + # + def exit_early(exit_code) + report(0) { exit_code } + end + + # @private + def start(expected_example_count, time=RSpec::Core::Time.now) + @start = time + @load_time = (@start - @configuration.start_time).to_f + notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?) + notify :start, Notifications::StartNotification.new(expected_example_count, @load_time) + end + + # @param message [#to_s] A message object to send to formatters + # + # Send a custom message to supporting formatters. + def message(message) + notify :message, Notifications::MessageNotification.new(message) + end + + # @param event [Symbol] Name of the custom event to trigger on formatters + # @param options [Hash] Hash of arguments to provide via `CustomNotification` + # + # Publish a custom event to supporting registered formatters. + # @see RSpec::Core::Notifications::CustomNotification + def publish(event, options={}) + if RSPEC_NOTIFICATIONS.include? event + raise "RSpec::Core::Reporter#publish is intended for sending custom " \ + "events not internal RSpec ones, please rename your custom event." + end + notify event, Notifications::CustomNotification.for(options) + end + + # @private + def example_group_started(group) + notify :example_group_started, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty? + end + + # @private + def example_group_finished(group) + notify :example_group_finished, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty? + end + + # @private + def example_started(example) + @examples << example + notify :example_started, Notifications::ExampleNotification.for(example) + end + + # @private + def example_finished(example) + notify :example_finished, Notifications::ExampleNotification.for(example) + end + + # @private + def example_passed(example) + notify :example_passed, Notifications::ExampleNotification.for(example) + end + + # @private + def example_failed(example) + @failed_examples << example + notify :example_failed, Notifications::ExampleNotification.for(example) + end + + # @private + def example_pending(example) + @pending_examples << example + notify :example_pending, Notifications::ExampleNotification.for(example) + end + + # @private + def deprecation(hash) + notify :deprecation, Notifications::DeprecationNotification.from_hash(hash) + end + + # @private + # Provides a way to notify of an exception that is not tied to any + # particular example (such as an exception encountered in a :suite hook). + # Exceptions will be formatted the same way they normally are. + def notify_non_example_exception(exception, context_description) + @configuration.world.non_example_failure = true + @non_example_exception_count += 1 + + example = Example.new(AnonymousExampleGroup, context_description, {}) + presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 0) + message presenter.fully_formatted(nil) + end + + # @private + def finish + close_after do + stop + notify :start_dump, Notifications::NullNotification + notify :dump_pending, Notifications::ExamplesNotification.new(self) + notify :dump_failures, Notifications::ExamplesNotification.new(self) + notify :deprecation_summary, Notifications::NullNotification + unless mute_profile_output? + notify :dump_profile, Notifications::ProfileNotification.new(@duration, @examples, + @configuration.profile_examples, + @profiler.example_groups) + end + notify :dump_summary, Notifications::SummaryNotification.new(@duration, @examples, @failed_examples, + @pending_examples, @load_time, + @non_example_exception_count) + notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?) + end + end + + # @private + def close_after + yield + ensure + close + end + + # @private + def stop + @duration = (RSpec::Core::Time.now - @start).to_f if @start + notify :stop, Notifications::ExamplesNotification.new(self) + end + + # @private + def notify(event, notification) + ensure_listeners_ready + registered_listeners(event).each do |formatter| + formatter.__send__(event, notification) + end + end + + # @private + def abort_with(msg, exit_status) + message(msg) + close + exit!(exit_status) + end + + # @private + def fail_fast_limit_met? + return false unless (fail_fast = @configuration.fail_fast) + + if fail_fast == true + @failed_examples.any? + else + fail_fast <= @failed_examples.size + end + end + + private + + def ensure_listeners_ready + return if @setup + + @setup_default.call + @profiler = Profiler.new + register_listener @profiler, *Profiler::NOTIFICATIONS + @setup = true + end + + def close + notify :close, Notifications::NullNotification + end + + def mute_profile_output? + # Don't print out profiled info if there are failures and `--fail-fast` is + # used, it just clutters the output. + !@configuration.profile_examples? || fail_fast_limit_met? + end + + def seed_used? + @configuration.seed && @configuration.seed_used? + end + end + + # @private + # # Used in place of a {Reporter} for situations where we don't want reporting output. + class NullReporter + def self.method_missing(*) + # ignore + end + private_class_method :method_missing + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ruby_project.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ruby_project.rb new file mode 100644 index 0000000000..156f89bea9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/ruby_project.rb @@ -0,0 +1,53 @@ +# This is borrowed (slightly modified) from Scott Taylor's +# project_path project: +# http://github.com/smtlaissezfaire/project_path +module RSpec + module Core + # @private + module RubyProject + def add_to_load_path(*dirs) + dirs.each { |dir| add_dir_to_load_path(File.join(root, dir)) } + end + + def add_dir_to_load_path(dir) + $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) + end + + def root + @project_root ||= determine_root + end + + def determine_root + find_first_parent_containing('spec') || '.' + end + + def find_first_parent_containing(dir) + ascend_until { |path| File.exist?(File.join(path, dir)) } + end + + def ascend_until + fs = File::SEPARATOR + escaped_slash = "\\#{fs}" + special = "_RSPEC_ESCAPED_SLASH_" + project_path = File.expand_path(".") + parts = project_path.gsub(escaped_slash, special).squeeze(fs).split(fs).map do |x| + x.gsub(special, escaped_slash) + end + + until parts.empty? + path = parts.join(fs) + path = fs if path == "" + return path if yield(path) + parts.pop + end + end + + module_function :add_to_load_path + module_function :add_dir_to_load_path + module_function :root + module_function :determine_root + module_function :find_first_parent_containing + module_function :ascend_until + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/runner.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/runner.rb new file mode 100644 index 0000000000..caf9c871af --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/runner.rb @@ -0,0 +1,212 @@ +module RSpec + module Core + # Provides the main entry point to run a suite of RSpec examples. + class Runner + # @attr_reader + # @private + attr_reader :options, :configuration, :world + + # Register an `at_exit` hook that runs the suite when the process exits. + # + # @note This is not generally needed. The `rspec` command takes care + # of running examples for you without involving an `at_exit` + # hook. This is only needed if you are running specs using + # the `ruby` command, and even then, the normal way to invoke + # this is by requiring `rspec/autorun`. + def self.autorun + if autorun_disabled? + RSpec.deprecate("Requiring `rspec/autorun` when running RSpec via the `rspec` command") + return + elsif installed_at_exit? || running_in_drb? + return + end + + at_exit { perform_at_exit } + @installed_at_exit = true + end + + # @private + def self.perform_at_exit + # Don't bother running any specs and just let the program terminate + # if we got here due to an unrescued exception (anything other than + # SystemExit, which is raised when somebody calls Kernel#exit). + return unless $!.nil? || $!.is_a?(SystemExit) + + # We got here because either the end of the program was reached or + # somebody called Kernel#exit. Run the specs and then override any + # existing exit status with RSpec's exit status if any specs failed. + invoke + end + + # Runs the suite of specs and exits the process with an appropriate exit + # code. + def self.invoke + disable_autorun! + status = run(ARGV, $stderr, $stdout).to_i + exit(status) if status != 0 + end + + # Run a suite of RSpec examples. Does not exit. + # + # This is used internally by RSpec to run a suite, but is available + # for use by any other automation tool. + # + # If you want to run this multiple times in the same process, and you + # want files like `spec_helper.rb` to be reloaded, be sure to load `load` + # instead of `require`. + # + # @param args [Array] command-line-supported arguments + # @param err [IO] error stream + # @param out [IO] output stream + # @return [Fixnum] exit status code. 0 if all specs passed, + # or the configured failure exit code (1 by default) if specs + # failed. + def self.run(args, err=$stderr, out=$stdout) + trap_interrupt + options = ConfigurationOptions.new(args) + + if options.options[:runner] + options.options[:runner].call(options, err, out) + else + new(options).run(err, out) + end + end + + def initialize(options, configuration=RSpec.configuration, world=RSpec.world) + @options = options + @configuration = configuration + @world = world + end + + # Configures and runs a spec suite. + # + # @param err [IO] error stream + # @param out [IO] output stream + def run(err, out) + setup(err, out) + return @configuration.reporter.exit_early(exit_code) if RSpec.world.wants_to_quit + + run_specs(@world.ordered_example_groups).tap do + persist_example_statuses + end + end + + # Wires together the various configuration objects and state holders. + # + # @param err [IO] error stream + # @param out [IO] output stream + def setup(err, out) + configure(err, out) + return if RSpec.world.wants_to_quit + + @configuration.load_spec_files + ensure + @world.announce_filters + end + + # Runs the provided example groups. + # + # @param example_groups [Array] groups to run + # @return [Fixnum] exit status code. 0 if all specs passed, + # or the configured failure exit code (1 by default) if specs + # failed. + def run_specs(example_groups) + examples_count = @world.example_count(example_groups) + examples_passed = @configuration.reporter.report(examples_count) do |reporter| + @configuration.with_suite_hooks do + if examples_count == 0 && @configuration.fail_if_no_examples + return @configuration.failure_exit_code + end + + example_groups.map { |g| g.run(reporter) }.all? + end + end + + exit_code(examples_passed) + end + + # @private + def configure(err, out) + @configuration.error_stream = err + @configuration.output_stream = out if @configuration.output_stream == $stdout + @options.configure(@configuration) + end + + # @private + def self.disable_autorun! + @autorun_disabled = true + end + + # @private + def self.autorun_disabled? + @autorun_disabled ||= false + end + + # @private + def self.installed_at_exit? + @installed_at_exit ||= false + end + + # @private + def self.running_in_drb? + return false unless defined?(DRb) + + server = begin + DRb.current_server + rescue DRb::DRbServerNotFound + return false + end + + return false unless server && server.alive? + + require 'socket' + require 'uri' + + local_ipv4 = begin + IPSocket.getaddress(Socket.gethostname) + rescue SocketError + return false + end + + ["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host } + end + + # @private + def self.trap_interrupt + trap('INT') { handle_interrupt } + end + + # @private + def self.handle_interrupt + if RSpec.world.wants_to_quit + exit!(1) + else + RSpec.world.wants_to_quit = true + $stderr.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit." + end + end + + # @private + def exit_code(examples_passed=false) + return @configuration.error_exit_code || @configuration.failure_exit_code if @world.non_example_failure + return @configuration.failure_exit_code unless examples_passed + + 0 + end + + private + + def persist_example_statuses + return if @configuration.dry_run + return unless (path = @configuration.example_status_persistence_file_path) + + ExampleStatusPersister.persist(@world.all_examples, path) + rescue SystemCallError => e + RSpec.warning "Could not write example statuses to #{path} (configured as " \ + "`config.example_status_persistence_file_path`) due to a " \ + "system error: #{e.inspect}. Please check that the config " \ + "option is set to an accessible, valid file path", :call_site => nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/sandbox.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/sandbox.rb new file mode 100644 index 0000000000..e7d518c2e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/sandbox.rb @@ -0,0 +1,37 @@ +module RSpec + module Core + # A sandbox isolates the enclosed code into an environment that looks 'new' + # meaning globally accessed objects are reset for the duration of the + # sandbox. + # + # @note This module is not normally available. You must require + # `rspec/core/sandbox` to load it. + module Sandbox + # Execute a provided block with RSpec global objects (configuration, + # world) reset. This is used to test RSpec with RSpec. + # + # When calling this the configuration is passed into the provided block. + # Use this to set custom configs for your sandboxed examples. + # + # ``` + # Sandbox.sandboxed do |config| + # config.before(:context) { RSpec.current_example = nil } + # end + # ``` + def self.sandboxed + orig_config = RSpec.configuration + orig_world = RSpec.world + orig_example = RSpec.current_example + + RSpec.configuration = RSpec::Core::Configuration.new + RSpec.world = RSpec::Core::World.new(RSpec.configuration) + + yield RSpec.configuration + ensure + RSpec.configuration = orig_config + RSpec.world = orig_world + RSpec.current_example = orig_example + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/set.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/set.rb new file mode 100644 index 0000000000..ae978106c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/set.rb @@ -0,0 +1,54 @@ +module RSpec + module Core + # @private + # + # We use this to replace `::Set` so we can have the advantage of + # constant time key lookups for unique arrays but without the + # potential to pollute a developers environment with an extra + # piece of the stdlib. This helps to prevent false positive + # builds. + # + class Set + include Enumerable + + def initialize(array=[]) + @values = {} + merge(array) + end + + def empty? + @values.empty? + end + + def <<(key) + @values[key] = true + self + end + + def delete(key) + @values.delete(key) + end + + def each(&block) + @values.keys.each(&block) + self + end + + def include?(key) + @values.key?(key) + end + + def merge(values) + values.each do |key| + @values[key] = true + end + self + end + + def clear + @values.clear + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_context.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_context.rb new file mode 100644 index 0000000000..6de7f649d0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_context.rb @@ -0,0 +1,55 @@ +module RSpec + module Core + # Exposes {ExampleGroup}-level methods to a module, so you can include that + # module in an {ExampleGroup}. + # + # @example + # + # module LoggedInAsAdmin + # extend RSpec::Core::SharedContext + # before(:example) do + # log_in_as :admin + # end + # end + # + # describe "admin section" do + # include LoggedInAsAdmin + # # ... + # end + module SharedContext + # @private + def included(group) + __shared_context_recordings.each do |recording| + recording.playback_onto(group) + end + end + + # @private + def __shared_context_recordings + @__shared_context_recordings ||= [] + end + + # @private + Recording = Struct.new(:method_name, :args, :block) do + def playback_onto(group) + group.__send__(method_name, *args, &block) + end + end + + # @private + def self.record(methods) + methods.each do |meth| + define_method(meth) do |*args, &block| + __shared_context_recordings << Recording.new(meth, args, block) + end + end + end + + # @private + record [:describe, :context] + Hooks.instance_methods(false) + + MemoizedHelpers::ClassMethods.instance_methods(false) + end + end + # @private + SharedContext = Core::SharedContext +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_example_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_example_group.rb new file mode 100644 index 0000000000..3d9efce282 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shared_example_group.rb @@ -0,0 +1,271 @@ +RSpec::Support.require_rspec_support "with_keywords_when_needed" + +module RSpec + module Core + # Represents some functionality that is shared with multiple example groups. + # The functionality is defined by the provided block, which is lazily + # eval'd when the `SharedExampleGroupModule` instance is included in an example + # group. + class SharedExampleGroupModule < Module + # @private + attr_reader :definition + + def initialize(description, definition, metadata) + @description = description + @definition = definition + @metadata = metadata + end + + # Provides a human-readable representation of this module. + def inspect + "#<#{self.class.name} #{@description.inspect}>" + end + alias to_s inspect + + # Ruby callback for when a module is included in another module is class. + # Our definition evaluates the shared group block in the context of the + # including example group. + def included(klass) + inclusion_line = klass.metadata[:location] + include_in klass, inclusion_line, [], nil + end + + # @private + def include_in(klass, inclusion_line, args, customization_block) + klass.update_inherited_metadata(@metadata) unless @metadata.empty? + + SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do + RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition) + klass.class_exec(&customization_block) if customization_block + end + end + end + + # Shared example groups let you define common context and/or common + # examples that you wish to use in multiple example groups. + # + # When defined, the shared group block is stored for later evaluation. + # It can later be included in an example group either explicitly + # (using `include_examples`, `include_context` or `it_behaves_like`) + # or implicitly (via matching metadata). + # + # Named shared example groups are scoped based on where they are + # defined. Shared groups defined in an example group are available + # for inclusion in that example group or any child example groups, + # but not in any parent or sibling example groups. Shared example + # groups defined at the top level can be included from any example group. + module SharedExampleGroup + # @overload shared_examples(name, &block) + # @param name [String, Symbol, Module] identifer to use when looking up + # this shared group + # @param block The block to be eval'd + # @overload shared_examples(name, metadata, &block) + # @param name [String, Symbol, Module] identifer to use when looking up + # this shared group + # @param metadata [Array, Hash] metadata to attach to this + # group; any example group or example with matching metadata will + # automatically include this shared example group. + # @param block The block to be eval'd + # + # Stores the block for later use. The block will be evaluated + # in the context of an example group via `include_examples`, + # `include_context`, or `it_behaves_like`. + # + # @example + # shared_examples "auditable" do + # it "stores an audit record on save!" do + # expect { auditable.save! }.to change(Audit, :count).by(1) + # end + # end + # + # RSpec.describe Account do + # it_behaves_like "auditable" do + # let(:auditable) { Account.new } + # end + # end + # + # @see ExampleGroup.it_behaves_like + # @see ExampleGroup.include_examples + # @see ExampleGroup.include_context + def shared_examples(name, *args, &block) + top_level = self == ExampleGroup + if top_level && RSpec::Support.thread_local_data[:in_example_group] + raise "Creating isolated shared examples from within a context is " \ + "not allowed. Remove `RSpec.` prefix or move this to a " \ + "top-level scope." + end + + RSpec.world.shared_example_group_registry.add(self, name, *args, &block) + end + alias shared_context shared_examples + alias shared_examples_for shared_examples + + # @api private + # + # Shared examples top level DSL. + module TopLevelDSL + # @private + def self.definitions + proc do + def shared_examples(name, *args, &block) + RSpec.world.shared_example_group_registry.add(:main, name, *args, &block) + end + alias shared_context shared_examples + alias shared_examples_for shared_examples + end + end + + # @private + def self.exposed_globally? + @exposed_globally ||= false + end + + # @api private + # + # Adds the top level DSL methods to Module and the top level binding. + def self.expose_globally! + return if exposed_globally? + Core::DSL.change_global_dsl(&definitions) + @exposed_globally = true + end + + # @api private + # + # Removes the top level DSL methods to Module and the top level binding. + def self.remove_globally! + return unless exposed_globally? + + Core::DSL.change_global_dsl do + undef shared_examples + undef shared_context + undef shared_examples_for + end + + @exposed_globally = false + end + end + + # @private + class Registry + def add(context, name, *metadata_args, &block) + unless block + RSpec.warning "Shared example group #{name} was defined without a "\ + "block and will have no effect. Please define a "\ + "block or remove the definition." + end + + if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion + return legacy_add(context, name, *metadata_args, &block) + end + + unless valid_name?(name) + raise ArgumentError, "Shared example group names can only be a string, " \ + "symbol or module but got: #{name.inspect}" + end + + ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line } + warn_if_key_taken context, name, block + + metadata = Metadata.build_hash_from(metadata_args) + shared_module = SharedExampleGroupModule.new(name, block, metadata) + shared_example_groups[context][name] = shared_module + end + + def find(lookup_contexts, name) + lookup_contexts.each do |context| + found = shared_example_groups[context][name] + return found if found + end + + shared_example_groups[:main][name] + end + + private + + # TODO: remove this in RSpec 4. This exists only to support + # `config.shared_context_metadata_behavior == :trigger_inclusion`, + # the legacy behavior of shared context metadata, which we do + # not want to support in RSpec 4. + def legacy_add(context, name, *metadata_args, &block) + ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line } + shared_module = SharedExampleGroupModule.new(name, block, {}) + + if valid_name?(name) + warn_if_key_taken context, name, block + shared_example_groups[context][name] = shared_module + else + metadata_args.unshift name + end + + return if metadata_args.empty? + RSpec.configuration.include shared_module, *metadata_args + end + + def shared_example_groups + @shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} } + end + + def valid_name?(candidate) + case candidate + when String, Symbol, Module then true + else false + end + end + + def warn_if_key_taken(context, key, new_block) + existing_module = shared_example_groups[context][key] + return unless existing_module + + old_definition_location = formatted_location existing_module.definition + new_definition_location = formatted_location new_block + loaded_spec_files = RSpec.configuration.loaded_spec_files + + if loaded_spec_files.include?(new_definition_location) && old_definition_location == new_definition_location + RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil + |WARNING: Your shared example group, '#{key}', defined at: + | #{old_definition_location} + |was automatically loaded by RSpec because the file name + |matches the configured autoloading pattern (#{RSpec.configuration.pattern}), + |and is also being required from somewhere else. To fix this + |warning, either rename the file to not match the pattern, or + |do not explicitly require the file. + WARNING + else + RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil + |WARNING: Shared example group '#{key}' has been previously defined at: + | #{old_definition_location} + |...and you are now defining it at: + | #{new_definition_location} + |The new definition will overwrite the original one. + WARNING + end + end + + if RUBY_VERSION.to_f >= 1.9 + def formatted_location(block) + block.source_location.join(":") + end + else # 1.8.7 + # :nocov: + def formatted_location(block) + block.source_location.join(":").gsub(/:in.*$/, '') + end + # :nocov: + end + + if Proc.method_defined?(:source_location) + def ensure_block_has_source_location(_block); end + else # for 1.8.7 + # :nocov: + def ensure_block_has_source_location(block) + source_location = yield.split(':') + block.extend(Module.new { define_method(:source_location) { source_location } }) + end + # :nocov: + end + end + end + end + + instance_exec(&Core::SharedExampleGroup::TopLevelDSL.definitions) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shell_escape.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shell_escape.rb new file mode 100644 index 0000000000..a92feae800 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/shell_escape.rb @@ -0,0 +1,49 @@ +module RSpec + module Core + # @private + # Deals with the fact that `shellwords` only works on POSIX systems. + module ShellEscape + module_function + + def quote(argument) + "'#{argument.to_s.gsub("'", "\\\\'")}'" + end + + if RSpec::Support::OS.windows? + # :nocov: + alias escape quote + # :nocov: + else + require 'shellwords' + + def escape(shell_command) + Shellwords.escape(shell_command.to_s) + end + end + + # Known shells that require quoting: zsh, csh, tcsh. + # + # Feel free to add other shells to this list that are known to + # allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id. + # + # @private + SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ] + + def conditionally_quote(id) + return id if shell_allows_unquoted_ids? + quote(id) + end + + def shell_allows_unquoted_ids? + # Note: ENV['SHELL'] isn't necessarily the shell the user is currently running. + # According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html: + # "This variable shall represent a pathname of the user's preferred command language interpreter." + # + # It's the best we can easily do, though. We err on the side of safety (quoting + # the id when not actually needed) so it's not a big deal if the user is actually + # using a different shell. + SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/test_unit_assertions_adapter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/test_unit_assertions_adapter.rb new file mode 100644 index 0000000000..d84ecb1441 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/test_unit_assertions_adapter.rb @@ -0,0 +1,30 @@ +require 'test/unit/assertions' + +module RSpec + module Core + # @private + module TestUnitAssertionsAdapter + include ::Test::Unit::Assertions + + # If using test/unit from Ruby core with Ruby 1.9+, it includes + # MiniTest::Assertions by default. Note the upcasing of 'Test'. + # + # If the test/unit gem is being loaded, it will not include any minitest + # assertions. + # + # Only if Minitest 5.x is included / loaded do we need to worry about + # adding a shim for the new updates. Thus instead of checking on the + # RUBY_VERSION we need to check ancestors. + begin + # MiniTest is 4.x. + # Minitest is 5.x. + if ancestors.include?(::Minitest::Assertions) + require 'rspec/core/minitest_assertions_adapter' + include ::RSpec::Core::MinitestAssertionsAdapter + end + rescue NameError + # No-op. Minitest 5.x was not loaded. + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/version.rb new file mode 100644 index 0000000000..aeaa3610f1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/version.rb @@ -0,0 +1,9 @@ +module RSpec + module Core + # Version information for RSpec Core. + module Version + # Current version of RSpec Core, in semantic versioning format. + STRING = '3.12.1' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/warnings.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/warnings.rb new file mode 100644 index 0000000000..b8800591bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/warnings.rb @@ -0,0 +1,40 @@ +require "rspec/support/warnings" + +module RSpec + module Core + # @private + module Warnings + # @private + # + # Used internally to print deprecation warnings. + def deprecate(deprecated, data={}) + RSpec.configuration.reporter.deprecation( + { + :deprecated => deprecated, + :call_site => CallerFilter.first_non_rspec_line + }.merge(data) + ) + end + + # @private + # + # Used internally to print deprecation warnings. + def warn_deprecation(message, opts={}) + RSpec.configuration.reporter.deprecation opts.merge(:message => message) + end + + # @private + def warn_with(message, options={}) + if options[:use_spec_location_as_call_site] + message += "." unless message.end_with?(".") + + if RSpec.current_example + message += " Warning generated from spec at `#{RSpec.current_example.location}`." + end + end + + super(message, options) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/world.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/world.rb new file mode 100644 index 0000000000..6fb439659b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-core-3.12.1/lib/rspec/core/world.rb @@ -0,0 +1,287 @@ +module RSpec + module Core + # @api private + # + # Internal container for global non-configuration data. + class World + # @private + attr_reader :example_groups, :filtered_examples, :example_group_counts_by_spec_file + + # Used internally to determine what to do when a SIGINT is received. + attr_accessor :wants_to_quit + + # Used internally to signify that a SystemExit occurred in + # `Configuration#load_file_handling_errors`, and thus examples cannot + # be counted accurately. Specifically, we cannot accurately report + # "No examples found". + # @private + attr_accessor :rspec_is_quitting + + # Used internally to signal that a failure outside of an example + # has occurred, and that therefore the exit status should indicate + # the run failed. + # @private + attr_accessor :non_example_failure + + def initialize(configuration=RSpec.configuration) + @wants_to_quit = false + @rspec_is_quitting = false + @configuration = configuration + configuration.world = self + @example_groups = [] + @example_group_counts_by_spec_file = Hash.new(0) + prepare_example_filtering + end + + # @api public + # + # Prepares filters so that they apply to example groups when they run. + # + # This is a separate method so that filters can be modified/replaced and + # examples refiltered during a process's lifetime, which can be useful for + # a custom runner. + def prepare_example_filtering + @filtered_examples = Hash.new do |hash, group| + hash[group] = filter_manager.prune(group.examples) + end + end + + # @api private + # + # Apply ordering strategy from configuration to example groups. + def ordered_example_groups + ordering_strategy = @configuration.ordering_registry.fetch(:global) + ordering_strategy.order(@example_groups) + end + + # @api private + # + # Reset world to 'scratch' before running suite. + def reset + RSpec::ExampleGroups.remove_all_constants + example_groups.clear + @sources_by_path.clear if defined?(@sources_by_path) + @syntax_highlighter = nil + @example_group_counts_by_spec_file = Hash.new(0) + end + + # @private + def filter_manager + @configuration.filter_manager + end + + # @private + def registered_example_group_files + @example_group_counts_by_spec_file.keys + end + + # @api private + # + # Records an example group. + def record(example_group) + @configuration.on_example_group_definition_callbacks.each { |block| block.call(example_group) } + @example_group_counts_by_spec_file[example_group.metadata[:absolute_file_path]] += 1 + end + + # @private + def num_example_groups_defined_in(file) + @example_group_counts_by_spec_file[file] + end + + # @private + def shared_example_group_registry + @shared_example_group_registry ||= SharedExampleGroup::Registry.new + end + + # @private + def inclusion_filter + @configuration.inclusion_filter + end + + # @private + def exclusion_filter + @configuration.exclusion_filter + end + + # @api private + # + # Get count of examples to be run. + def example_count(groups=example_groups) + FlatMap.flat_map(groups) { |g| g.descendants }. + inject(0) { |a, e| a + e.filtered_examples.size } + end + + # @private + def all_example_groups + FlatMap.flat_map(example_groups) { |g| g.descendants } + end + + # @private + def all_examples + FlatMap.flat_map(all_example_groups) { |g| g.examples } + end + + # @private + # Traverses the tree of each top level group. + # For each it yields the group, then the children, recursively. + # Halts the traversal of a branch of the tree as soon as the passed block returns true. + # Note that siblings groups and their sub-trees will continue to be explored. + # This is intended to make it easy to find the top-most group that satisfies some + # condition. + def traverse_example_group_trees_until(&block) + example_groups.each do |group| + group.traverse_tree_until(&block) + end + end + + # @api private + # + # Find line number of previous declaration. + def preceding_declaration_line(absolute_file_name, filter_line) + line_numbers = descending_declaration_line_numbers_by_file.fetch(absolute_file_name) do + return nil + end + + line_numbers.find { |num| num <= filter_line } + end + + # @private + def reporter + @configuration.reporter + end + + # @private + def source_from_file(path) + unless defined?(@sources_by_path) + RSpec::Support.require_rspec_support 'source' + @sources_by_path = {} + end + + @sources_by_path[path] ||= Support::Source.from_file(path) + end + + # @private + def syntax_highlighter + @syntax_highlighter ||= Formatters::SyntaxHighlighter.new(@configuration) + end + + # @api private + # + # Notify reporter of filters. + def announce_filters + fail_if_config_and_cli_options_invalid + filter_announcements = [] + + announce_inclusion_filter filter_announcements + announce_exclusion_filter filter_announcements + + unless filter_manager.empty? + if filter_announcements.length == 1 + report_filter_message("Run options: #{filter_announcements[0]}") + else + report_filter_message("Run options:\n #{filter_announcements.join("\n ")}") + end + end + + if @configuration.run_all_when_everything_filtered? && example_count.zero? && !@configuration.only_failures? + report_filter_message("#{everything_filtered_message}; ignoring #{inclusion_filter.description}") + filtered_examples.clear + inclusion_filter.clear + end + + return unless example_count.zero? + + example_groups.clear + unless rspec_is_quitting + if filter_manager.empty? + report_filter_message("No examples found.") + elsif exclusion_filter.empty? || inclusion_filter.empty? + report_filter_message(everything_filtered_message) + end + end + end + + # @private + def report_filter_message(message) + reporter.message(message) unless @configuration.silence_filter_announcements? + end + + # @private + def everything_filtered_message + "\nAll examples were filtered out" + end + + # @api private + # + # Add inclusion filters to announcement message. + def announce_inclusion_filter(announcements) + return if inclusion_filter.empty? + + announcements << "include #{inclusion_filter.description}" + end + + # @api private + # + # Add exclusion filters to announcement message. + def announce_exclusion_filter(announcements) + return if exclusion_filter.empty? + + announcements << "exclude #{exclusion_filter.description}" + end + + private + + def descending_declaration_line_numbers_by_file + @descending_declaration_line_numbers_by_file ||= begin + declaration_locations = FlatMap.flat_map(example_groups, &:declaration_locations) + hash_of_arrays = Hash.new { |h, k| h[k] = [] } + + # TODO: change `inject` to `each_with_object` when we drop 1.8.7 support. + line_nums_by_file = declaration_locations.inject(hash_of_arrays) do |hash, (file_name, line_number)| + hash[file_name] << line_number + hash + end + + line_nums_by_file.each_value do |list| + list.sort! + list.reverse! + end + end + end + + def fail_if_config_and_cli_options_invalid + return unless @configuration.only_failures_but_not_configured? + + reporter.abort_with( + "\nTo use `--only-failures`, you must first set " \ + "`config.example_status_persistence_file_path`.", + 1 # exit code + ) + end + + # @private + # Provides a null implementation for initial use by configuration. + module Null + def self.non_example_failure; end + def self.non_example_failure=(_); end + + def self.registered_example_group_files + [] + end + + def self.traverse_example_group_trees_until + end + + # :nocov: + def self.example_groups + [] + end + + def self.all_example_groups + [] + end + # :nocov: + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.yardopts new file mode 100644 index 0000000000..9555b8e5c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/.yardopts @@ -0,0 +1,6 @@ +--exclude features +--no-private +--markup markdown +- +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/Changelog.md new file mode 100644 index 0000000000..0818b10eb1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/Changelog.md @@ -0,0 +1,1240 @@ +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Allow JRuby 9.2.x.x to generate backtraces normally rather than via our + backfill workaround. (#1230, Jon Rowe) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.10.0) + +Enhancements: + +* Allow `include` matcher to be chained with `once`, `at_least`, etc. for simple cases. + (Marc-André Lafortune, #1168) +* Add an explicit warning when `nil` is passed to `raise_error`. (Phil Pirozhkov, #1143) +* Improve `include` matcher's composability. (Phil Pirozhkov, #1155) +* Mocks expectations can now set a custom failure message. + (Benoit Tigeot and Nicolas Zermati, #1156) +* `aggregate_failures` now shows the backtrace line for each failure. (Fabricio Bedin, #1163) +* Support multiple combinations of `yield_control` modifiers like `at_least`, `at_most`. + (Jon Rowe, #1169) +* Dynamic `have_` matchers now have output consistent with other dynamic matchers. + (Marc-André Lafortune, #1195) +* New config option `strict_predicate_matchers` allows predicate matcher to be strict + (i.e. match for `true` or `false`) instead of the default (match truthy vs `false` or `nil`). + (Marc-André Lafortune, #1196) + +### 3.9.4 / 2020-10-29 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.9.4) + +Bug Fixes: + +* Fix regression with `be_` and `have_` matchers and arguments implementing `to_hash` + were they would act like keywords and be cast to a hash. (Jon Rowe, #1222) + +### 3.9.3 / 2020-10-23 + +Bug Fixes: + +* Swap the comparison of the delta vs the expected for the `be_within` matcher allowing + more complicated oobjects to be compared providing they provide `abs` and other + comparison methods. (Kelly Stannard, #1182) +* Properly format expected in the description of the `be_within` matcher. (Jon Rowe, #1185) +* Remove warning when using keyword arguments with `be_` and `have_` matchers on 2.7.x + (Jon Rowe, #1187) +* Prevent formatting a single hash as a list of key value pairs in default failure messages + for custom matches (fixes formatting in `EnglishPhrasing#list`). (Robert Eshleman, #1193) +* Prevent errors from causing false positives when using `be ` comparison, e.g. + `expect(1).not_to be < 'a'` will now correctly fail rather than pass. (Jon Rowe, #1208) + + +### 3.9.2 / 2020-05-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Issue a proper `ArgumentError` when invalid arguments are given to `yield_control` + modifiers such as `at_least` et al. (Marc-André Lafortune, #1167) +* Prevent Ruby 2.7 keyword arguments warning from being issued by custom + matcher definitions. (Jon Rowe, #1176) + +### 3.9.1 / 2020-03-13 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Issue an improved warning when using `respond_to(...).with(n).arguments` and ignore + the warning when using with `have_attributes(...)`. (Jon Rowe, #1164) + +### 3.9.0 / 2019-10-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.6...v3.9.0) + +Enhancements: + +* The `respond_to` matcher now uses the signature from `initialize` to validate checks + for `new` (unless `new` is non standard). (Jon Rowe, #1072) +* Generated descriptions for matchers now use `is expected to` rather than `should` in + line with our preferred DSL. (Pete Johns, #1080, rspec/rspec-core#2572) +* Add the ability to re-raise expectation errors when matching + with `match_when_negated` blocks. (Jon Rowe, #1130) +* Add a warning when an empty diff is produce due to identical inspect output. + (Benoit Tigeot, #1126) + +### 3.8.6 / 2019-10-07 + +Bug Fixes: + +* Revert #1125 due to the change being incompatible with our semantic versioning + policy. + +### 3.8.5 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.4...v3.8.5) + +Bug Fixes: + +* Prevent unsupported implicit block expectation syntax from being used. + (Phil Pirozhkov, #1125) + +### 3.8.4 / 2019-06-10 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.3...v3.8.4) + +Bug Fixes: + +* Prevent false negatives when checking objects for the methods required to run the + the `be_an_instance_of` and `be_kind_of` matchers. (Nazar Matus, #1112) + +### 3.8.3 / 2019-04-20 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.2...v3.8.3) + +Bug Fixes: + +* Prevent composed `all` matchers from leaking into their siblings leading to duplicate + failures. (Jamie English, #1086) +* Prevent objects which change their hash on comparison from failing change checks. + (Phil Pirozhkov, #1110) +* Issue an `ArgumentError` rather than a `NoMethodError` when `be_an_instance_of` and + `be_kind_of` matchers encounter objects not supporting those methods. + (Taichi Ishitani, #1107) + +### 3.8.2 / 2018-10-09 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Change `include` matcher to rely on a `respond_to?(:include?)` check rather than a direct + Hash comparison before calling `to_hash` to convert to a hash. (Jordan Owens, #1073) +* Prevent unexpected call stack jumps from causing an obscure error (`IndexError`), and + replace that error with a proper informative message. (Jon Rowe, #1076) + +### 3.8.1 / 2018-08-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Fix regression in `include` matcher so stopped + `expect(hash.with_indifferent_access).to include(:symbol_key)` + from working. (Eito Katagiri, #1069) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.7.0...v3.8.0) + +Enhancements: + +* Improve failure message of `change(receiver, :message)` by including the + receiver as `SomeClass#some_message`. (Tomohiro Hashidate, #1005) +* Improve `change` matcher so that it can correctly detect changes in + deeply nested mutable objects (such as arrays-of-hashes-of-arrays). + The improved logic uses the before/after `hash` value to see if the + object has been mutated, rather than shallow duping the object. + (Myron Marston, #1034) +* Improve `include` matcher so that pseudo-hash objects (e.g. objects + that decorate a hash using a `SimpleDelegator` or similar) are treated + as a hash, as long as they implement `to_hash`. (Pablo Brasero, #1012) +* Add `max_formatted_output_length=` to configuration, allowing changing + the length at which we truncate large output strings. + (Sam Phippen #951, Benoit Tigeot #1056) +* Improve error message when passing a matcher that doesn't support block + expectations to a block based `expect`. (@nicktime, #1066) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #997) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta2...v3.6.0) + +Enhancements: + +* Treat NoMethodError as a failure for comparison matchers. (Jon Rowe, #972) +* Allow for scoped aliased and negated matchers--just call + `alias_matcher` or `define_negated_matcher` from within an example + group. (Markus Reiter, #974) +* Improve failure message of `change` matcher with block and `satisfy` matcher + by including the block snippet instead of just describing it as `result` or + `block` when Ripper is available. (Yuji Nakayama, #987) + +Bug Fixes: + +* Fix `yield_with_args` and `yield_successive_args` matchers so that + they compare expected to actual args at the time the args are yielded + instead of at the end, in case the method that is yielding mutates the + arguments after yielding. (Alyssa Ross, #965) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta1...v3.6.0.beta2) + +Bug Fixes: + +* Using the exist matcher on `File` no longer produces a deprecation warning. + (Jon Rowe, #954) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Fix `contain_exactly` to work correctly with ranges. (Myron Marston, #940) +* Fix `change` to work correctly with sets. (Marcin Gajewski, #939) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Add support for keyword arguments to the `respond_to` matcher. (Rob Smith, #915). + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta3...v3.5.0.beta4) + +Bug Fixes: + +* Fix `include` matcher so that it provides a valid diff for hashes. (Yuji Nakayama, #916) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Make `rspec/expectations/minitest_integration` work on Minitest::Spec + 5.6+. (Myron Marston, #904) +* Add an alias `having_attributes` for `have_attributes` matcher. + (Yuji Nakayama, #905) +* Improve `change` matcher error message when block is mis-used. + (Alex Altair, #908) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Add the ability to raise an error on encountering false positives via + `RSpec::Configuration#on_potential_false_positives = :raise`. (Jon Rowe, #900) +* When using the custom matcher DSL, support new + `notify_expectation_failures: true` option for the `match` method to + allow expectation failures to be raised as normal instead of being + converted into a `false` return value for `matches?`. (Jon Rowe, #892) + +Bug Fixes: + +* Allow `should` deprecation check to work on `BasicObject`s. (James Coleman, #898) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.4.0...v3.5.0.beta1) + +Enhancements: + +* Make `match_when_negated` in custom matcher DSL support use of + expectations within the match logic. (Chris Arcand, #789) + +Bug Fixes: + +* Return `true` as expected from passing negated expectations + (such as `expect("foo").not_to eq "bar"`), so they work + properly when used within a `match` or `match_when_negated` + block. (Chris Arcand, #789) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.3.1...v3.4.0) + +Enhancements: + +* Warn when `RSpec::Matchers` is included in a superclass after it has + already been included in a subclass on MRI 1.9, since that situation + can cause uses of `super` to trigger infinite recursion. (Myron Marston, #816) +* Stop rescuing `NoMemoryError`, `SignalExcepetion`, `Interrupt` and + `SystemExit`. It is dangerous to interfere with these. (Myron Marston, #845) +* Add `#with_captures` to the + [match matcher](https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/match-matcher) + which allows a user to specify expected captures when matching a regex + against a string. (Sam Phippen, #848) +* Always print compound failure messages in the multi-line form. Trying + to print it all on a single line didn't read very well. (Myron Marston, #859) + +Bug Fixes: + +* Fix failure message from dynamic predicate matchers when the object + does not respond to the predicate so that it is inspected rather + than relying upon its `to_s` -- that way for `nil`, `"nil"` is + printed rather than an empty string. (Myron Marston, #841) +* Fix SystemStackError raised when diffing an Enumerable object + whose `#each` includes the object itself. (Yuji Nakayama, #857) + +### 3.3.1 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Fix `be >`, `be <`, etc so that it fails rather than allowing an + argument error to be raised when compared against an object of the + wrong type. This allows it to be used in composed matcher expressions + against heterogeneous objects. (Dennis Günnewig, #809) +* Fix `respond_to` to work properly on target objects + that redefine the `method` method. (unmanbearpig, #821) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.1...v3.3.0) + +Enhancements: + +* Expose `RSpec::Matchers::EnglishPhrasing` to make it easier to write + nice failure messages in custom matchers. (Jared Beck, #736) +* Add `RSpec::Matchers::FailMatchers`, a mixin which provides + `fail`, `fail_with` and `fail_including` matchers for use in + specifying that an expectation fails for use by + extension/plugin authors. (Charlie Rudolph, #729) +* Avoid loading `tempfile` (and its dependencies) unless + it is absolutely needed. (Myron Marston, #735) +* Improve failure output when attempting to use `be_true` or `be_false`. + (Tim Wade, #744) +* Define `RSpec::Matchers#respond_to_missing?` so that + `RSpec::Matchers#respond_to?` and `RSpec::Matchers#method` handle + dynamic predicate matchers. (Andrei Botalov, #751) +* Use custom Time/DateTime/BigDecimal formatting for all matchers + so they are consistently represented in failure messages. + (Gavin Miller, #740) +* Add configuration to turn off warnings about matcher combinations that + may cause false positives. (Jon Rowe, #768) +* Warn when using a bare `raise_error` matcher that you may be subject to + false positives. (Jon Rowe, #768) +* Warn rather than raise when using the`raise_error` matcher in negative + expectations that may be subject to false positives. (Jon Rowe, #775) +* Improve failure message for `include(a, b, c)` so that if `a` and `b` + are included the failure message only mentions `c`. (Chris Arcand, #780) +* Allow `satisfy` matcher to take an optional description argument + that will be used in the `description`, `failure_message` and + `failure_message_when_negated` in place of the undescriptive + "sastify block". (Chris Arcand, #783) +* Add new `aggregate_failures` API that allows multiple independent + expectations to all fail and be listed in the failure output, rather + than the example aborting on the first failure. (Myron Marston, #776) +* Improve `raise_error` matcher so that it can accept a matcher as a single argument + that matches the message. (Time Wade, #782) + +Bug Fixes: + +* Make `contain_exactly` / `match_array` work with strict test doubles + that have not defined `<=>`. (Myron Marston, #758) +* Fix `include` matcher so that it omits the diff when it would + confusingly highlight items that are actually included but are not + an exact match in a line-by-line diff. (Tim Wade, #763) +* Fix `match` matcher so that it does not blow up when matching a string + or regex against another matcher (rather than a string or regex). + (Myron Marston, #772) +* Silence whitespace-only diffs. (Myron Marston, #801) + +### 3.2.1 / 2015-04-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Prevent `Range`s from being enumerated when generating matcher + descriptions. (Jon Rowe, #755) +* Ensure exception messages are compared as strings in the `raise_error` + matcher. (Jon Rowe, #755) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.2...v3.2.0) + +Enhancements: + +* Add `block_arg` method to custom matcher API, which allows you to + access the block passed to a custom matcher, if there is one. + (Mike Dalton, #645) +* Provide more detail in failure message of `yield_control` matcher. + (Jon Rowe, #650) +* Add a shorthand syntax for `chain` in the matcher DSL which assigns values + for use elsewhere, for example `chain :and_smaller_than, :small_value` + creates an `attr_reader` for `small_value` (Tom Stuart, #644) +* Provide a more helpful deprecation message when using the `should` syntax. + (Elia Schito, #663) +* Provide more detail in the `have_attributes` matcher failure message. + (Jon Rowe, #668) +* Make the `have_attributes` matcher diffable. + (Jon Rowe, Alexey Fedorov, #668) +* Add `output(...).to_std(out|err)_from_any_process` as alternatives + to `output(...).to_std(out|err)`. The latter doesn't work when a sub + process writes to the named stream but is much faster. + (Alex Genco, #700) +* Improve compound matchers (created by `and` and `or`) so that diffs + are included in failures when one or more of their matchers + are diffable. (Alexey Fedorov, #713) + +Bug Fixes: + +* Avoid calling `private_methods` from the `be` predicate matcher on + the target object if the object publicly responds to the predicate + method. This avoids a possible error that can occur if the object + raises errors from `private_methods` (which can happen with celluloid + objects). (@chapmajs, #670) +* Make `yield_control` (with no modifier) default to + `at_least(:once)` rather than raising a confusing error + when multiple yields are encountered. + (Myron Marston, #675) +* Fix "instance variable @color not initialized" warning when using + rspec-expectations outside of an rspec-core context. (Myron Marston, #689) +* Fix `start_with` and `end_with` to work properly when checking a + string against an array of strings. (Myron Marston, #690) +* Don't use internally delegated matchers when generating descriptions + for examples without doc strings. (Myron Marston, #692) + +### 3.1.2 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix `define_negated_matcher` so that matchers that support fluent + interfaces continue to be negated after you use the chained method. + (Myron Marston, #656) +* Fix `define_negated_matcher` so that the matchers fail with an + appropriate failure message. (Myron Marston, #659) + +### 3.1.1 / 2014-09-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix regression in `all` matcher in 3.1.0 that prevented it from + working on objects that are not `Enumerable` but do implement + `each_with_index` (such as an ActiveRecord proxy). (Jori Hardman, #647) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Add `have_attributes` matcher, that passes if actual's attribute + values match the expected attributes hash: + `Person = Struct.new(:name, :age)` + `person = Person.new("Bob", 32)` + `expect(person).to have_attributes(:name => "Bob", :age => 32)`. + (Adam Farhi, #571) +* Extended compound matcher support to block matchers, for cases like: + `expect { ... }.to change { x }.to(3).and change { y }.to(4)`. (Myron + Marston, #567) +* Include chained methods in custom matcher description and failure message + when new `include_chain_clauses_in_custom_matcher_descriptions` config + option is enabled. (Dan Oved, #600) +* Add `thrice` modifier to `yield_control` matcher as a synonym for + `exactly(3).times`. (Dennis Taylor, #615) +* Add `RSpec::Matchers.define_negated_matcher`, which defines a negated + version of the named matcher. (Adam Farhi, Myron Marston, #618) +* Document and support negation of `contain_exactly`/`match_array`. + (Jon Rowe, #626). + +Bug Fixes: + +* Rename private `LegacyMacherAdapter` constant to `LegacyMatcherAdapter` + to fix typo. (Abdelkader Boudih, #563) +* Fix `all` matcher so that it fails properly (rather than raising a + `NoMethodError`) when matched against a non-enumerable. (Hao Su, #622) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix `start_with` and `end_with` so that they work properly with + structs. (Myron Marston, #620) +* Fix failure message generation so that structs are printed properly + in failures. Previously failure messages would represent them as + an array. (Myron Marston, #620) +* Fix composable matcher support so that it does not wrongly treat + structs as arrays. (Myron Marston, #620) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Fix issue with detection of generic operator matchers so they work + correctly when undefined. (Myron Marston, #597) +* Don't inadvertently define `BasicObject` in 1.8.7. (Chris Griego, #603) +* Fix `include` matcher so that it fails gracefully when matched against + an object that does not respond to `include?`. (Myron Marston, #607) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix regression in `contain_exactly` (AKA `match_array`) that caused it + to wrongly pass when the expected array was empty. (Myron Marston, #581) +* Provide a better error message when you use the `change(obj, :msg)` + form of the change matcher but forget the message argument. (Alex + Sunderland, #585) +* Make the `contain_exactly` matcher work with arrays that contain hashes in + arbitrary ordering. (Sam Phippen, #578) + +### 3.0.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Add a missing `require` that would cause the `respond_to` matcher to + fail when used in a project where the rest of RSpec (e.g. core and + expecatations) weren't being used. (Myron Marston, #566) +* Structs are no longer treated as arrays when diffed. (Jon Rowe, #576) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.rc1...v3.0.0) + +No code changes. Just taking it out of pre-release. + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Remove `matcher_execution_context` attribute from DSL-defined + custom matchers. (Myron Marston) +* Remove `RSpec::Matchers::Pretty#_pretty_print`. (Myron Marston) +* Remove `RSpec::Matchers::Pretty#expected_to_sentence`. (Myron Marston) +* Rename `RSpec::Matchers::Configuration` constant to + `RSpec::Expectations::Configuration`. (Myron Marston) +* Prevent `have_xyz` predicate matchers using private methods. + (Adrian Gonzalez) +* Block matchers must now implement `supports_block_expectations?`. + (Myron Marston) +* Stop supporting `require 'rspec-expectations'`. + Use `require 'rspec/expectations'` instead. (Myron Marston) + +Bug Fixes: + +* Fix `NoMethodError` triggered by beta2 when `YARD` was loaded in + the test environment. (Myron Marston) +* Fix `be_xyz` matcher to accept a `do...end` block. (Myron Marston) +* Fix composable matcher failure message generation logic + so that it does not blow up when given `$stdout` or `$stderr`. + (Myron Marston) +* Fix `change` matcher to work properly with `IO` objects. + (Myron Marston) +* Fix `exist` matcher so that it can be used in composed matcher + expressions involving objects that do not implement `exist?` or + `exists?`. (Daniel Fone) +* Fix composable matcher match logic so that it clones matchers + before using them in order to work properly with matchers + that use internal memoization based on a given `actual` value. + (Myron Marston) +* Fix `be_xyz` and `has_xyz` predicate matchers so that they can + be used in composed matcher expressions involving objects that + do not implement the predicate method. (Daniel Fone) + +Enhancements: + +* Document the remaining public APIs. rspec-expectations now has 100% of + the public API documented and will remain that way (as new undocumented + methods will fail the build). (Myron Marston) +* Improve the formatting of BigDecimal objects in `eq` matcher failure + messages. (Daniel Fone) +* Improve the failure message for `be_xyz` predicate matchers so + that it includes the `inspect` output of the receiver. + (Erik Michaels-Ober, Sam Phippen) +* Add `all` matcher, to allow you to specify that a given matcher + matches all elements in a collection: + `expect([1, 3, 5]).to all( be_odd )`. (Adam Farhi) +* Add boolean aliases (`&`/`|`) for compound operators (`and`/`or`). (Adam Farhi) +* Give users a clear error when they wrongly use a value matcher + in a block expectation expression (e.g. `expect { 3 }.to eq(3)`) + or vice versa. (Myron Marston) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Remove deprecated support for accessing the `RSpec` constant using + `Rspec` or `Spec`. (Myron Marston) +* Remove deprecated `RSpec::Expectations.differ=`. (Myron Marston) +* Remove support for deprecated `expect(...).should`. (Myron Marston) +* Explicitly disallow `expect { }.not_to change { }` with `by`, + `by_at_least`, `by_at_most` or `to`. These have never been supported + but did not raise explicit errors. (Myron Marston) +* Provide `===` rather than `==` as an alias of `matches?` for + all matchers. The semantics of `===` are closer to an RSpec + matcher than `==`. (Myron Marston) +* Remove deprecated `RSpec::Matchers::OperatorMatcher` constant. + (Myron Marston) +* Make `RSpec::Expectations::ExpectationNotMetError` subclass + `Exception` rather than `StandardError` so they can bypass + a bare `rescue` in end-user code (e.g. when an expectation is + set from within a rspec-mocks stub implementation). (Myron Marston) +* Remove Test::Unit and Minitest 4.x integration. (Myron Marston) + +Enhancements: + +* Simplify the failure message of the `be` matcher when matching against: + `true`, `false` and `nil`. (Sam Phippen) +* Update matcher protocol and custom matcher DSL to better align + with the newer `expect` syntax. If you want your matchers to + maintain compatibility with multiple versions of RSpec, you can + alias the new names to the old. (Myron Marston) + * `failure_message_for_should` => `failure_message` + * `failure_message_for_should_not` => `failure_message_when_negated` + * `match_for_should` => `match` + * `match_for_should_not` => `match_when_negated` +* Improve generated descriptions from `change` matcher. (Myron Marston) +* Add support for compound matcher expressions using `and` and `or`. + Simply chain them off of any existing matcher to create an expression + like `expect(alphabet).to start_with("a").and end_with("z")`. + (Eloy Espinaco) +* Add `contain_exactly` as a less ambiguous version of `match_array`. + Note that it expects the expected array to be splatted as + individual args: `expect(array).to contain_exactly(1, 2)` is + the same as `expect(array).to match_array([1, 2])`. (Myron Marston) +* Update `contain_exactly`/`match_array` so that it can match against + other non-array collections (such as a `Set`). (Myron Marston) +* Update built-in matchers so that they can accept matchers as arguments + to allow you to compose matchers in arbitrary ways. (Myron Marston) +* Add `RSpec::Matchers::Composable` mixin that can be used to make + a custom matcher composable as well. Note that custom matchers + defined via `RSpec::Matchers.define` already have this. (Myron + Marston) +* Define noun-phrase aliases for built-in matchers, which can be + used when creating composed matcher expressions that read better + and provide better failure messages. (Myron Marston) +* Add `RSpec::Matchers.alias_matcher` so users can define their own + matcher aliases. The `description` of the matcher will reflect the + alternate matcher name. (Myron Marston) +* Add explicit `be_between` matcher. `be_between` has worked for a + long time as a dynamic predicate matcher, but the failure message + was suboptimal. The new matcher provides a much better failure + message. (Erik Michaels-Ober) +* Enhance the `be_between` matcher to allow for `inclusive` or `exclusive` + comparison (e.g. inclusive of min/max or exclusive of min/max). + (Pedro Gimenez) +* Make failure message for `not_to be #{operator}` less confusing by + only saying it's confusing when comparison operators are used. + (Prathamesh Sonpatki) +* Improve failure message of `eq` matcher when `Time` or `DateTime` + objects are used so that the full sub-second precision is included. + (Thomas Holmes, Jeff Wallace) +* Add `output` matcher for expecting that a block outputs `to_stdout` + or `to_stderr`. (Luca Pette, Matthias Günther) +* Forward a provided block on to the `has_xyz?` method call when + the `have_xyz` matcher is used. (Damian Galarza) +* Provide integration with Minitest 5.x. Require + `rspec/expectations/minitest_integration` after loading minitest + to use rspec-expectations with minitest. (Myron Marston) + +Bug Fixes: + +* Fix wrong matcher descriptions with falsey expected value (yujinakayama) +* Fix `expect { }.not_to change { }.from(x)` so that the matcher only + passes if the starting value is `x`. (Tyler Rick, Myron Marston) +* Fix hash diffing, so that it colorizes properly and doesn't consider trailing + commas when performing the diff. (Jared Norman) +* Fix built-in matchers to fail normally rather than raising + `ArgumentError` when given an object of the wrong type to match + against, so that they work well in composite matcher expressions like + `expect([1.51, "foo"]).to include(a_string_matching(/foo/), a_value_within(0.1).of(1.5))`. + (Myron Marston) + +Deprecations: + +* Retain support for RSpec 2 matcher protocol (e.g. for matchers + in 3rd party extension gems like `shoulda`), but it will print + a deprecation warning. (Myron Marston) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.2...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Remove explicit support for 1.8.6. (Jon Rowe) +* Remove the deprecated `be_close` matcher, preferring `be_within` instead. + (Sam Phippen) +* Remove the deprecated `have`, `have_at_least` and `have_at_most` matchers. + You can continue using those matchers through https://github.com/rspec/rspec-collection_matchers, + or you can rewrite your expectations with something like + `expect(your_object.size).to eq(num)`. (Hugo Baraúna) +* Rename `be_true` and `be_false` to `be_truthy` and `be_falsey`. (Sam Phippen) +* Make `expect { }.to_not raise_error(SomeSpecificClass, message)`, + `expect { }.to_not raise_error(SomeSpecificClass)` and + `expect { }.to_not raise_error(message)` invalid, since they are prone + to hiding failures. Instead, use `expect { }.to_not raise_error` (with no + args). (Sam Phippen) +* Within `RSpec::Matchers.define` blocks, helper methods made available + either via `def self.helper` or `extend HelperModule` are no longer + available to the `match` block (or any of the others). Instead + `include` your helper module and define the helper method as an + instance method. (Myron Marston) +* Force upgrading Diff::LCS for encoding compatability with diffs. (Jon Rowe) + +Enhancements: + +* Support `do..end` style block with `raise_error` matcher. (Yuji Nakayama) +* Rewrote custom matcher DSL to simplify its implementation and solve a + few issues. (Myron Marston) +* Allow early `return` from within custom matcher DSL blocks. (Myron + Marston) +* The custom matcher DSL's `chain` can now accept a block. (Myron + Marston) +* Support setting an expectation on a `raise_error` matcher via a chained + `with_message` method call. (Sam Phippen) + +Bug Fixes: + +* Allow `include` and `match` matchers to be used from within a + DSL-defined custom matcher's `match` block. (Myron Marston) +* Correct encoding error message on diff failure (Jon Rowe) + +Deprecations: + + * Using the old `:should` syntax without explicitly configuring it is deprecated. + It will continue to work but will emit a deprecation warning in RSpec 3 if + you do not explicitly enable it. (Sam Phippen) + +### 2.99.2 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.1...v2.99.2) + +Bug Fixes: + +* Fix regression in `Expectations#method_handle_for` where proxy objects + with method delegated would wrongly not return a method handle. + (Jon Rowe, #594) +* Fix issue with detection of generic operator matchers so they work + correctly when undefined. (Myron Marston, #597) + +### 2.99.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Fix typo in custom matcher `expected` deprecation warning -- it's + `expected_as_array`, not `expected_array`. (Frederick Cheung, #562) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.rc1...v2.99.0) + +Enhancements: + +* Special case deprecation message for `errors_on` with `rspec-rails` to be more useful. + (Aaron Kromer) + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.beta2...2.99.0.rc1) + +Deprecations: + +* Deprecate `matcher_execution_context` attribute on DSL-defined + custom matchers. (Myron Marston) +* Deprecate `RSpec::Matchers::Pretty#_pretty_print`. (Myron Marston) +* Deprecate `RSpec::Matchers::Pretty#expected_to_sentence`. (Myron Marston) +* Deprecate `RSpec::Matchers::Configuration` in favor of + `RSpec::Expectations::Configuration`. (Myron Marston) +* Deprecate `be_xyz` predicate matcher on an object that doesn't respond to + `xyz?` or `xyzs?`. (Daniel Fone) +* Deprecate `have_xyz` matcher on an object that doesn't respond to `has_xyz?`. + (Daniel Fone) +* Deprecate `have_xyz` matcher on an object that has a private method `has_xyz?`. + (Jon Rowe) +* Issue a deprecation warning when a block expectation expression is + used with a matcher that doesn't explicitly support block expectations + via `supports_block_expectations?`. (Myron Marston) +* Deprecate `require 'rspec-expectations'`. Use + `require 'rspec/expectations'` instead. (Myron Marston) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.beta1...v2.99.0.beta2) + +Deprecations: + +* Deprecate chaining `by`, `by_at_least`, `by_at_most` or `to` off of + `expect { }.not_to change { }`. The docs have always said these are + not supported for the negative form but now they explicitly raise + errors in RSpec 3. (Myron Marston) +* Change the semantics of `expect { }.not_to change { x }.from(y)`. + In RSpec 2.x, this expectation would only fail if `x` started with + the value of `y` and changed. If it started with a different value + and changed, it would pass. In RSpec 3, it will pass only if the + value starts at `y` and it does not change. (Myron Marston) +* Deprecate `matcher == value` as an alias for `matcher.matches?(value)`, + in favor of `matcher === value`. (Myron Marston) +* Deprecate `RSpec::Matchers::OperatorMatcher` in favor of + `RSpec::Matchers::BuiltIn::OperatorMatcher`. (Myron Marston) +* Deprecate auto-integration with Test::Unit and minitest. + Instead, include `RSpec::Matchers` in the appropriate test case + base class yourself. (Myron Marston) +* Deprecate treating `#expected` on a DSL-generated custom matcher + as an array when only 1 argument is passed to the matcher method. + In RSpec 3 it will be the single value in order to make diffs + work properly. (Jon Rowe) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.4...v2.99.0.beta1) + +Deprecations + +* Deprecate `have`, `have_at_least` and `have_at_most`. You can continue using those + matchers through https://github.com/rspec/rspec-collection_matchers, or + you can rewrite your expectations with something like + `expect(your_object.size).to eq(num)`. (Hugo Baraúna) +* Deprecate `be_xyz` predicate matcher when `xyz?` is a private method. + (Jon Rowe) +* Deprecate `be_true`/`be_false` in favour of `be_truthy`/`be_falsey` + (for Ruby's conditional semantics) or `be true`/`be false` + (for exact equality). (Sam Phippen) +* Deprecate calling helper methods from a custom matcher with the wrong + scope. (Myron Marston) + * `def self.foo` / `extend Helper` can be used to add macro methods + (e.g. methods that call the custom matcher DSL methods), but should + not be used to define helper methods called from within the DSL + blocks. + * `def foo` / `include Helper` is the opposite: it's for helper methods + callable from within a DSL block, but not for defining macros. + * RSpec 2.x allowed helper methods defined either way to be used for + either purpose, but RSpec 3.0 will not. + +### 2.14.5 / 2014-02-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.4...v2.14.5) + +Bug fixes + +* Fix wrong matcher descriptions with falsey expected value + (yujinakayama) + +### 2.14.4 / 2013-11-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.3...v2.14.4) + +Bug fixes + +* Make the `match` matcher produce a diff output. (Jon Rowe, Ben Moss) +* Choose encoding for diff's more intelligently, and when all else fails fall + back to default internal encoding with replacing characters. (Jon Rowe) + +### 2.14.3 / 2013-09-22 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.2...v2.14.3) + +Bug fixes + +* Fix operator matchers (`should` syntax) when `method` is redefined on target. + (Brandon Turner) +* Fix diffing of hashes with object based keys. (Jon Rowe) +* Fix operator matchers (`should` syntax) when operator is defined via + `method_missing` (Jon Rowe) + +### 2.14.2 / 2013-08-14 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.1...v2.14.2) + +Bug fixes + +* Fix `be_` matcher to not support operator chaining like the + `be` matcher does (e.g. `be == 5`). This led to some odd behaviors + since `be_ == anything` returned a `BeComparedTo` matcher + and was thus always truthy. This was a consequence of the implementation + (e.g. subclassing the basic `Be` matcher) and was not intended behavior. + (Myron Marston). +* Fix `change` matcher to compare using `==` in addition to `===`. This + is important for an expression like: + `expect {}.to change { a.class }.from(ClassA).to(ClassB)` because + `SomeClass === SomeClass` returns false. (Myron Marston) + +### 2.14.1 / 2013-08-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.0...2.14.1) + +Bug fixes + +* Ensure diff output uses the same encoding as the encoding of + the string being diff'd to prevent `Encoding::UndefinedConversionError` + errors (Jon Rowe). + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.0.rc1...v2.14.0) + +Bug fixes + +* Values that are not matchers use `#inspect`, rather than `#description` for + documentation output (Andy Lindeman, Sam Phippen). +* Make `expect(a).to be_within(x).percent_of(y)` work with negative y + (Katsuhiko Nishimra). +* Make the `be_predicate` matcher work as expected used with `expect{...}.to + change...` (Sam Phippen). + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.13.0...v2.14.0.rc1) + +Enhancements + +* Enhance `yield_control` so that you can specify an exact or relative + number of times: `expect { }.to yield_control.exactly(3).times`, + `expect { }.to yield_control.at_least(2).times`, etc (Bartek + Borkowski). +* Make the differ that is used when an expectation fails better handle arrays + by splitting each element of the array onto its own line. (Sam Phippen) +* Accept duck-typed strings that respond to `:to_str` as expectation messages. + (Toby Ovod-Everett) + +Bug fixes + +* Fix differ to not raise errors when dealing with differently-encoded + strings (Jon Rowe). +* Fix `expect(something).to be_within(x).percent_of(y)` where x and y are both + integers (Sam Phippen). +* Fix `have` matcher to handle the fact that on ruby 2.0, + `Enumerator#size` may return nil (Kenta Murata). +* Fix `expect { raise s }.to raise_error(s)` where s is an error instance + on ruby 2.0 (Sam Phippen). +* Fix `expect(object).to raise_error` passing. This now warns the user and + fails the spec (tomykaira). + +Deprecations + +* Deprecate `expect { }.not_to raise_error(SpecificErrorClass)` or + `expect { }.not_to raise_error("some specific message")`. Using + these was prone to hiding failures as they would allow _any other + error_ to pass. (Sam Phippen and David Chelimsky) + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.12.1...v2.13.0) + +Enhancements + +* Add support for percent deltas to `be_within` matcher: + `expect(value).to be_within(10).percent_of(expected)` + (Myron Marston). +* Add support to `include` matcher to allow it to be given a list + of matchers as the expecteds to match against (Luke Redpath). + +Bug fixes + +* Fix `change` matcher so that it dups strings in order to handle + mutated strings (Myron Marston). +* Fix `should be =~ /some regex/` / `expect(...).to be =~ /some regex/`. + Previously, these either failed with a confusing `undefined method + matches?' for false:FalseClass` error or were no-ops that didn't + actually verify anything (Myron Marston). +* Add compatibility for diff-lcs 1.2 and relax the version + constraint (Peter Goldstein). +* Fix DSL-generated matchers to allow multiple instances of the + same matcher in the same example to have different description + and failure messages based on the expected value (Myron Marston). +* Prevent `undefined method #split for Array` error when dumping + the diff of an array of multiline strings (Myron Marston). +* Don't blow up when comparing strings that are in an encoding + that is not ASCII compatible (Myron Marston). +* Remove confusing "Check the implementation of #==" message + printed for empty diffs (Myron Marston). + +### 2.12.1 / 2012-12-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Improve the failure message for an expression like + `{}.should =~ {}`. (Myron Marston and Andy Lindeman) +* Provide a `match_regex` alias so that custom matchers + built using the matcher DSL can use it (since `match` + is a different method in that context). + (Steven Harman) + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.3...v2.12.0) + +Enhancements + +* Colorize diffs if the `--color` option is configured. (Alex Coplan) +* Include backtraces in unexpected errors handled by `raise_error` + matcher (Myron Marston) +* Print a warning when users accidentally pass a non-string argument + as an expectation message (Sam Phippen) +* `=~` and `match_array` matchers output a more useful error message when + the actual value is not an array (or an object that responds to `#to_ary`) + (Sam Phippen) + +Bug fixes + +* Fix `include` matcher so that `expect({}).to include(:a => nil)` + fails as it should (Sam Phippen). +* Fix `be_an_instance_of` matcher so that `Class#to_s` is used in the + description rather than `Class#inspect`, since some classes (like + `ActiveRecord::Base`) define a long, verbose `#inspect`. + (Tom Stuart) + +### 2.11.3 / 2012-09-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.2...v2.11.3) + +Bug fixes + +* Fix (and deprecate) `expect { }.should` syntax so that it works even + though it was never a documented or intended syntax. It worked as a + consequence of the implementation of `expect` in RSpec 2.10 and + earlier. (Myron Marston) +* Ensure #== is defined on built in matchers so that they can be composed. + For example: + + expect { + user.emailed! + }.to change { user.last_emailed_at }.to be_within(1.second).of(Time.zone.now) + +### 2.11.2 / 2012-07-25 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.1...v2.11.2) + +Bug fixes + +* Define `should` and `should_not` on `Object` rather than `BasicObject` + on MacRuby. On MacRuby, `BasicObject` is defined but is not the root + of the object hierarchy. (Gabriel Gilder) + +### 2.11.1 / 2012-07-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Constrain `actual` in `be_within` matcher to values that respond to `-` instead + of requiring a specific type. + * `Time`, for example, is a legit alternative. + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.10.0...v2.11.0) + +Enhancements + +* Expand `expect` syntax so that it supports expections on bare values + in addition to blocks (Myron Marston). +* Add configuration options to control available expectation syntaxes + (Myron Marston): + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :expect }` + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :should }` + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }` + * `RSpec.configuration.add_should_and_should_not_to Delegator` + +Bug fixes + +* Allow only `Numeric` values to be the "actual" in the `be_within` matcher. + This prevents confusing error messages. (Su Zhang @zhangsu) +* Define `should` and `should_not` on `BasicObject` rather than `Kernel` + on 1.9. This makes `should` and `should_not` work properly with + `BasicObject`-subclassed proxy objects like `Delegator`. (Myron + Marston) + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.9.1...v2.10.0) + +Enhancements + +* Add new `start_with` and `end_with` matchers (Jeremy Wadsack) +* Add new matchers for specifying yields (Myron Marston): + * `expect {...}.to yield_control` + * `expect {...}.to yield_with_args(1, 2, 3)` + * `expect {...}.to yield_with_no_args` + * `expect {...}.to yield_successive_args(1, 2, 3)` +* `match_unless_raises` takes multiple exception args + +Bug fixes + +* Fix `be_within` matcher to be inclusive of delta. +* Fix message-specific specs to pass on Rubinius (John Firebaugh) + +### 2.9.1 / 2012-04-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.9.0...v2.9.1) + +Bug fixes + +* Provide a helpful message if the diff between two objects is empty. +* Fix bug diffing single strings with multiline strings. +* Fix for error with using custom matchers inside other custom matchers + (mirasrael) +* Fix using execution context methods in nested DSL matchers (mirasrael) + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0...v2.9.0) + +Enhancements + +* Move built-in matcher classes to RSpec::Matchers::BuiltIn to reduce pollution + of RSpec::Matchers (which is included in every example). +* Autoload files with matcher classes to improve load time. + +Bug fixes + +* Align `respond_to?` and `method_missing` in DSL-defined matchers. +* Clear out user-defined instance variables between invocations of DSL-defined + matchers. +* Dup the instance of a DSL generated matcher so its state is not changed by + subsequent invocations. +* Treat expected args consistently across positive and negative expectations + (thanks to Ralf Kistner for the heads up) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0.rc2...v2.8.0) + +Enhancements + +* Better diff output for Hash (Philippe Creux) +* Eliminate Ruby warnings (Olek Janiszewski) + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0.rc1...v2.8.0.rc2) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.7.0...v2.8.0.rc1) + +Enhancements + +* Use classes for the built-in matchers (they're faster). +* Eliminate Ruby warnings (Matijs van Zuijlen) + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.6.0...v2.7.0) + +Enhancements + +* `HaveMatcher` converts argument using `to_i` (Alex Bepple & Pat Maddox) +* Improved failure message for the `have_xxx` matcher (Myron Marston) +* `HaveMatcher` supports `count` (Matthew Bellantoni) +* Change matcher dups `Enumerable` before the action, supporting custom + `Enumerable` types like `CollectionProxy` in Rails (David Chelimsky) + +Bug fixes + +* Fix typo in `have(n).xyz` documentation (Jean Boussier) +* fix `safe_sort` for ruby 1.9.2 (`Kernel` now defines `<=>` for Object) (Peter + van Hardenberg) + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.5.0...v2.6.0) + +Enhancements + +* `change` matcher accepts regexps (Robert Davis) +* better descriptions for `have_xxx` matchers (Magnus Bergmark) +* `range.should cover(*values)` (Anders Furseth) + +Bug fixes + +* Removed non-ascii characters that were choking rcov (Geoffrey Byers) +* change matcher dups arrays and hashes so their before/after states can be + compared correctly. +* Fix the order of inclusion of RSpec::Matchers in Test::Unit::TestCase and + MiniTest::Unit::TestCase to prevent a SystemStackError (Myron Marston) + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.4.0...v2.5.0) + +Enhancements + +* `should exist` works with `exist?` or `exists?` (Myron Marston) +* `expect { ... }.not_to do_something` (in addition to `to_not`) + +Documentation + +* improved docs for raise_error matcher (James Almond) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.3.0...v2.4.0) + +No functional changes in this release, which was made to align with the +rspec-core-2.4.0 release. + +Enhancements + +* improved RDoc for change matcher (Jo Liss) + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.2.1...v2.3.0) + +Enhancements + +* diff strings when include matcher fails (Mike Sassak) + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.1.0...v2.2.0) + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.1...v2.1.0) + +Enhancements + +* `be_within(delta).of(expected)` matcher (Myron Marston) +* Lots of new Cucumber features (Myron Marston) +* Raise error if you try `should != expected` on Ruby-1.9 (Myron Marston) +* Improved failure messages from `throw_symbol` (Myron Marston) + +Bug fixes + +* Eliminate hard dependency on `RSpec::Core` (Myron Marston) +* `have_matcher` - use pluralize only when ActiveSupport inflections are indeed + defined (Josep M Bach) +* throw_symbol matcher no longer swallows exceptions (Myron Marston) +* fix matcher chaining to avoid name collisions (Myron Marston) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.rc...v2.0.0) + +Enhancements + +* Add match_for_should_not method to matcher DSL (Myron Marston) + +Bug fixes + +* `respond_to` matcher works correctly with `should_not` with multiple methods + (Myron Marston) +* `include` matcher works correctly with `should_not` with multiple values + (Myron Marston) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* `require 'rspec/expectations'` in a T::U or MiniUnit suite (Josep M. Bach) + +Bug fixes + +* change by 0 passes/fails correctly (Len Smith) +* Add description to satisfy matcher + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Enhancements + +* diffing improvements + * diff multiline strings + * don't diff single line strings + * don't diff numbers (silly) + * diff regexp + multiline string + +Bug fixes + * `should[_not]` change now handles boolean values correctly diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/LICENSE.md new file mode 100644 index 0000000000..dae02d8a7d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 David Chelimsky, Myron Marston +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-expectations-3.10.1/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/README.md new file mode 100644 index 0000000000..3888c93774 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/README.md @@ -0,0 +1,320 @@ +# RSpec Expectations [![Build Status](https://secure.travis-ci.org/rspec/rspec-expectations.svg?branch=main)](http://travis-ci.org/rspec/rspec-expectations) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.svg)](https://codeclimate.com/github/rspec/rspec-expectations) + +RSpec::Expectations lets you express expected outcomes on an object in an +example. + +```ruby +expect(account.balance).to eq(Money.new(37.42, :USD)) +``` + +## Install + +If you want to use rspec-expectations with rspec, just install the rspec gem +and RubyGems will also install rspec-expectations for you (along with +rspec-core and rspec-mocks): + + gem install rspec + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +If you want to use rspec-expectations with another tool, like Test::Unit, +Minitest, or Cucumber, you can install it directly: + + gem install rspec-expectations + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +- [Build details](BUILD_DETAIL.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Detailed contributing guide](CONTRIBUTING.md) +- [Development setup guide](DEVELOPMENT.md) + +## Basic usage + +Here's an example using rspec-core: + +```ruby +RSpec.describe Order do + it "sums the prices of the items in its line items" do + order = Order.new + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(1.11, :USD) + ))) + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(2.22, :USD), + :quantity => 2 + ))) + expect(order.total).to eq(Money.new(5.55, :USD)) + end +end +``` + +The `describe` and `it` methods come from rspec-core. The `Order`, `LineItem`, `Item` and `Money` classes would be from _your_ code. The last line of the example +expresses an expected outcome. If `order.total == Money.new(5.55, :USD)`, then +the example passes. If not, it fails with a message like: + + expected: # + got: # + +## Built-in matchers + +### Equivalence + +```ruby +expect(actual).to eq(expected) # passes if actual == expected +expect(actual).to eql(expected) # passes if actual.eql?(expected) +expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected)) +``` + +Note: The new `expect` syntax no longer supports the `==` matcher. + +### Identity + +```ruby +expect(actual).to be(expected) # passes if actual.equal?(expected) +expect(actual).to equal(expected) # passes if actual.equal?(expected) +``` + +### Comparisons + +```ruby +expect(actual).to be > expected +expect(actual).to be >= expected +expect(actual).to be <= expected +expect(actual).to be < expected +expect(actual).to be_within(delta).of(expected) +``` + +### Regular expressions + +```ruby +expect(actual).to match(/expression/) +``` + +Note: The new `expect` syntax no longer supports the `=~` matcher. + +### Types/classes + +```ruby +expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected +expect(actual).to be_a(expected) # passes if actual.kind_of?(expected) +expect(actual).to be_an(expected) # an alias for be_a +expect(actual).to be_a_kind_of(expected) # another alias +``` + +### Truthiness + +```ruby +expect(actual).to be_truthy # passes if actual is truthy (not nil or false) +expect(actual).to be true # passes if actual == true +expect(actual).to be_falsy # passes if actual is falsy (nil or false) +expect(actual).to be false # passes if actual == false +expect(actual).to be_nil # passes if actual is nil +expect(actual).to_not be_nil # passes if actual is not nil +``` + +### Expecting errors + +```ruby +expect { ... }.to raise_error +expect { ... }.to raise_error(ErrorClass) +expect { ... }.to raise_error("message") +expect { ... }.to raise_error(ErrorClass, "message") +``` + +### Expecting throws + +```ruby +expect { ... }.to throw_symbol +expect { ... }.to throw_symbol(:symbol) +expect { ... }.to throw_symbol(:symbol, 'value') +``` + +### Yielding + +```ruby +expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args + +expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded + +expect { |b| 5.tap(&b) }.to yield_with_args(5) +expect { |b| 5.tap(&b) }.to yield_with_args(Integer) +expect { |b| "a string".tap(&b) }.to yield_with_args(/str/) + +expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) +expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) +``` + +### Predicate matchers + +```ruby +expect(actual).to be_xxx # passes if actual.xxx? +expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg) +``` + +### Ranges (Ruby >= 1.9 only) + +```ruby +expect(1..10).to cover(3) +``` + +### Collection membership + +```ruby +# exact order, entire collection +expect(actual).to eq(expected) + +# exact order, partial collection (based on an exact position) +expect(actual).to start_with(expected) +expect(actual).to end_with(expected) + +# any order, entire collection +expect(actual).to match_array(expected) + +# You can also express this by passing the expected elements +# as individual arguments +expect(actual).to contain_exactly(expected_element1, expected_element2) + + # any order, partial collection +expect(actual).to include(expected) +``` + +#### Examples + +```ruby +expect([1, 2, 3]).to eq([1, 2, 3]) # Order dependent equality check +expect([1, 2, 3]).to include(1) # Exact ordering, partial collection matches +expect([1, 2, 3]).to include(2, 3) # +expect([1, 2, 3]).to start_with(1) # As above, but from the start of the collection +expect([1, 2, 3]).to start_with(1, 2) # +expect([1, 2, 3]).to end_with(3) # As above but from the end of the collection +expect([1, 2, 3]).to end_with(2, 3) # +expect({:a => 'b'}).to include(:a => 'b') # Matching within hashes +expect("this string").to include("is str") # Matching within strings +expect("this string").to start_with("this") # +expect("this string").to end_with("ring") # +expect([1, 2, 3]).to contain_exactly(2, 3, 1) # Order independent matches +expect([1, 2, 3]).to match_array([3, 2, 1]) # + +# Order dependent compound matchers +expect( + [{:a => 'hash'},{:a => 'another'}] +).to match([a_hash_including(:a => 'hash'), a_hash_including(:a => 'another')]) +``` + +## `should` syntax + +In addition to the `expect` syntax, rspec-expectations continues to support the +`should` syntax: + +```ruby +actual.should eq expected +actual.should be > 3 +[1, 2, 3].should_not include 4 +``` + +See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/main/Should.md) + +## Compound Matcher Expressions + +You can also create compound matcher expressions using `and` or `or`: + +``` ruby +expect(alphabet).to start_with("a").and end_with("z") +expect(stoplight.color).to eq("red").or eq("green").or eq("yellow") +``` + +## Composing Matchers + +Many of the built-in matchers are designed to take matchers as +arguments, to allow you to flexibly specify only the essential +aspects of an object or data structure. In addition, all of the +built-in matchers have one or more aliases that provide better +phrasing for when they are used as arguments to another matcher. + +### Examples + +```ruby +expect { k += 1.05 }.to change { k }.by( a_value_within(0.1).of(1.0) ) + +expect { s = "barn" }.to change { s } + .from( a_string_matching(/foo/) ) + .to( a_string_matching(/bar/) ) + +expect(["barn", 2.45]).to contain_exactly( + a_value_within(0.1).of(2.5), + a_string_starting_with("bar") +) + +expect(["barn", "food", 2.45]).to end_with( + a_string_matching("foo"), + a_value > 2 +) + +expect(["barn", 2.45]).to include( a_string_starting_with("bar") ) + +expect(:a => "food", :b => "good").to include(:a => a_string_matching(/foo/)) + +hash = { + :a => { + :b => ["foo", 5], + :c => { :d => 2.05 } + } +} + +expect(hash).to match( + :a => { + :b => a_collection_containing_exactly( + a_string_starting_with("f"), + an_instance_of(Integer) + ), + :c => { :d => (a_value < 3) } + } +) + +expect { |probe| + [1, 2, 3].each(&probe) +}.to yield_successive_args( a_value < 2, 2, a_value > 2 ) +``` + +## Usage outside rspec-core + +You always need to load `rspec/expectations` even if you only want to use one part of the library: + +```ruby +require 'rspec/expectations' +``` + +Then simply include `RSpec::Matchers` in any class: + +```ruby +class MyClass + include RSpec::Matchers + + def do_something(arg) + expect(arg).to be > 0 + # do other stuff + end +end +``` + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations.rb new file mode 100644 index 0000000000..9e11ea00c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations.rb @@ -0,0 +1,82 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "caller_filter" +RSpec::Support.require_rspec_support "warnings" +RSpec::Support.require_rspec_support "object_formatter" + +require 'rspec/matchers' + +RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) } + +%w[ + expectation_target + configuration + fail_with + handler + version +].each { |file| RSpec::Support.require_rspec_expectations(file) } + +module RSpec + # RSpec::Expectations provides a simple, readable API to express + # the expected outcomes in a code example. To express an expected + # outcome, wrap an object or block in `expect`, call `to` or `to_not` + # (aliased as `not_to`) and pass it a matcher object: + # + # expect(order.total).to eq(Money.new(5.55, :USD)) + # expect(list).to include(user) + # expect(message).not_to match(/foo/) + # expect { do_something }.to raise_error + # + # The last form (the block form) is needed to match against ruby constructs + # that are not objects, but can only be observed when executing a block + # of code. This includes raising errors, throwing symbols, yielding, + # and changing values. + # + # When `expect(...).to` is invoked with a matcher, it turns around + # and calls `matcher.matches?()`. For example, + # in the expression: + # + # expect(order.total).to eq(Money.new(5.55, :USD)) + # + # ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results + # in the equivalent of `eq.matches?(order.total)`. If `matches?` returns + # `true`, the expectation is met and execution continues. If `false`, then + # the spec fails with the message returned by `eq.failure_message`. + # + # Given the expression: + # + # expect(order.entries).not_to include(entry) + # + # ...the `not_to` method (also available as `to_not`) invokes the equivalent of + # `include.matches?(order.entries)`, but it interprets `false` as success, and + # `true` as a failure, using the message generated by + # `include.failure_message_when_negated`. + # + # rspec-expectations ships with a standard set of useful matchers, and writing + # your own matchers is quite simple. + # + # See [RSpec::Matchers](../RSpec/Matchers) for more information about the + # built-in matchers that ship with rspec-expectations, and how to write your + # own custom matchers. + module Expectations + # Exception raised when an expectation fails. + # + # @note We subclass Exception so that in a stub implementation if + # the user sets an expectation, it can't be caught in their + # code by a bare `rescue`. + # @api public + class ExpectationNotMetError < Exception + end + + # Exception raised from `aggregate_failures` when multiple expectations fail. + # + # @note The constant is defined here but the extensive logic of this class + # is lazily defined when `FailureAggregator` is autoloaded, since we do + # not need to waste time defining that functionality unless + # `aggregate_failures` is used. + class MultipleExpectationsNotMetError < ExpectationNotMetError + end + + autoload :BlockSnippetExtractor, "rspec/expectations/block_snippet_extractor" + autoload :FailureAggregator, "rspec/expectations/failure_aggregator" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/block_snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/block_snippet_extractor.rb new file mode 100644 index 0000000000..02f1155968 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/block_snippet_extractor.rb @@ -0,0 +1,253 @@ +module RSpec + module Expectations + # @private + class BlockSnippetExtractor # rubocop:disable Metrics/ClassLength + # rubocop should properly handle `Struct.new {}` as an inner class definition. + + attr_reader :proc, :method_name + + def self.try_extracting_single_line_body_of(proc, method_name) + lines = new(proc, method_name).body_content_lines + return nil unless lines.count == 1 + lines.first + rescue Error + nil + end + + def initialize(proc, method_name) + @proc = proc + @method_name = method_name.to_s.freeze + end + + # Ideally we should properly handle indentations of multiline snippet, + # but it's not implemented yet since because we use result of this method only when it's a + # single line and implementing the logic introduces additional complexity. + def body_content_lines + raw_body_lines.map(&:strip).reject(&:empty?) + end + + private + + def raw_body_lines + raw_body_snippet.split("\n") + end + + def raw_body_snippet + block_token_extractor.body_tokens.map(&:string).join + end + + def block_token_extractor + @block_token_extractor ||= BlockTokenExtractor.new(method_name, source, beginning_line_number) + end + + if RSpec.respond_to?(:world) + def source + raise TargetNotFoundError unless File.exist?(file_path) + RSpec.world.source_from_file(file_path) + end + else + RSpec::Support.require_rspec_support 'source' + def source + raise TargetNotFoundError unless File.exist?(file_path) + @source ||= RSpec::Support::Source.from_file(file_path) + end + end + + def file_path + source_location.first + end + + def beginning_line_number + source_location.last + end + + def source_location + proc.source_location || raise(TargetNotFoundError) + end + + Error = Class.new(StandardError) + TargetNotFoundError = Class.new(Error) + AmbiguousTargetError = Class.new(Error) + + # @private + # Performs extraction of block body snippet using tokens, + # which cannot be done with node information. + BlockTokenExtractor = Struct.new(:method_name, :source, :beginning_line_number) do + attr_reader :state, :body_tokens + + def initialize(*) + super + parse! + end + + private + + def parse! + @state = :initial + + catch(:finish) do + source.tokens.each do |token| + invoke_state_handler(token) + end + end + end + + def finish! + throw :finish + end + + def invoke_state_handler(token) + __send__("#{state}_state", token) + end + + def initial_state(token) + @state = :after_method_call if token.location == block_locator.method_call_location + end + + def after_method_call_state(token) + @state = :after_opener if handle_opener_token(token) + end + + def after_opener_state(token) + if handle_closer_token(token) + finish_or_find_next_block_if_incorrect! + elsif pipe_token?(token) + finalize_pending_tokens! + @state = :after_beginning_of_args + else + pending_tokens << token + handle_opener_token(token) + @state = :after_beginning_of_body unless token.type == :on_sp + end + end + + def after_beginning_of_args_state(token) + @state = :after_beginning_of_body if pipe_token?(token) + end + + def after_beginning_of_body_state(token) + if handle_closer_token(token) + finish_or_find_next_block_if_incorrect! + else + pending_tokens << token + handle_opener_token(token) + end + end + + def pending_tokens + @pending_tokens ||= [] + end + + def finalize_pending_tokens! + pending_tokens.freeze.tap do + @pending_tokens = nil + end + end + + def finish_or_find_next_block_if_incorrect! + body_tokens = finalize_pending_tokens! + + if correct_block?(body_tokens) + @body_tokens = body_tokens + finish! + else + @state = :after_method_call + end + end + + def handle_opener_token(token) + opener_token?(token).tap do |boolean| + opener_token_stack.push(token) if boolean + end + end + + def opener_token?(token) + token.type == :on_lbrace || (token.type == :on_kw && token.string == 'do') + end + + def handle_closer_token(token) + if opener_token_stack.last.closed_by?(token) + opener_token_stack.pop + opener_token_stack.empty? + else + false + end + end + + def opener_token_stack + @opener_token_stack ||= [] + end + + def pipe_token?(token) + token.type == :on_op && token.string == '|' + end + + def correct_block?(body_tokens) + return true if block_locator.body_content_locations.empty? + content_location = block_locator.body_content_locations.first + content_location.between?(body_tokens.first.location, body_tokens.last.location) + end + + def block_locator + @block_locator ||= BlockLocator.new(method_name, source, beginning_line_number) + end + end + + # @private + # Locates target block with node information (semantics), which tokens don't have. + BlockLocator = Struct.new(:method_name, :source, :beginning_line_number) do + def method_call_location + @method_call_location ||= method_ident_node.location + end + + def body_content_locations + @body_content_locations ||= block_body_node.map(&:location).compact + end + + private + + def method_ident_node + method_call_node = block_wrapper_node.children.first + method_call_node.find do |node| + method_ident_node?(node) + end + end + + def block_body_node + block_node = block_wrapper_node.children[1] + block_node.children.last + end + + def block_wrapper_node + case candidate_block_wrapper_nodes.size + when 1 + candidate_block_wrapper_nodes.first + when 0 + raise TargetNotFoundError + else + raise AmbiguousTargetError + end + end + + def candidate_block_wrapper_nodes + @candidate_block_wrapper_nodes ||= candidate_method_ident_nodes.map do |method_ident_node| + block_wrapper_node = method_ident_node.each_ancestor.find { |node| node.type == :method_add_block } + next nil unless block_wrapper_node + method_call_node = block_wrapper_node.children.first + method_call_node.include?(method_ident_node) ? block_wrapper_node : nil + end.compact + end + + def candidate_method_ident_nodes + source.nodes_by_line_number[beginning_line_number].select do |node| + method_ident_node?(node) + end + end + + def method_ident_node?(node) + node.type == :@ident && node.args.first == method_name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/configuration.rb new file mode 100644 index 0000000000..974a9cdf22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/configuration.rb @@ -0,0 +1,230 @@ +RSpec::Support.require_rspec_expectations "syntax" + +module RSpec + module Expectations + # Provides configuration options for rspec-expectations. + # If you are using rspec-core, you can access this via a + # block passed to `RSpec::Core::Configuration#expect_with`. + # Otherwise, you can access it via RSpec::Expectations.configuration. + # + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # # c is the config object + # end + # end + # + # # or + # + # RSpec::Expectations.configuration + class Configuration + # @private + FALSE_POSITIVE_BEHAVIOURS = + { + :warn => lambda { |message| RSpec.warning message }, + :raise => lambda { |message| raise ArgumentError, message }, + :nothing => lambda { |_| true }, + } + + def initialize + @on_potential_false_positives = :warn + @strict_predicate_matchers = false + end + + # Configures the supported syntax. + # @param [Array, Symbol] values the syntaxes to enable + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # c.syntax = :should + # # or + # c.syntax = :expect + # # or + # c.syntax = [:should, :expect] + # end + # end + def syntax=(values) + if Array(values).include?(:expect) + Expectations::Syntax.enable_expect + else + Expectations::Syntax.disable_expect + end + + if Array(values).include?(:should) + Expectations::Syntax.enable_should + else + Expectations::Syntax.disable_should + end + end + + # Configures the maximum character length that RSpec will print while + # formatting an object. You can set length to nil to prevent RSpec from + # doing truncation. + # @param [Fixnum] length the number of characters to limit the formatted output to. + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # c.max_formatted_output_length = 200 + # end + # end + def max_formatted_output_length=(length) + RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = length + end + + # The list of configured syntaxes. + # @return [Array] the list of configured syntaxes. + # @example + # unless RSpec::Matchers.configuration.syntax.include?(:expect) + # raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax" + # end + def syntax + syntaxes = [] + syntaxes << :should if Expectations::Syntax.should_enabled? + syntaxes << :expect if Expectations::Syntax.expect_enabled? + syntaxes + end + + if ::RSpec.respond_to?(:configuration) + def color? + ::RSpec.configuration.color_enabled? + end + else + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + attr_writer :color + + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + def color? + defined?(@color) && @color + end + end + + # Adds `should` and `should_not` to the given classes + # or modules. This can be used to ensure `should` works + # properly on things like proxy objects (particular + # `Delegator`-subclassed objects on 1.8). + # + # @param [Array] modules the list of classes or modules + # to add `should` and `should_not` to. + def add_should_and_should_not_to(*modules) + modules.each do |mod| + Expectations::Syntax.enable_should(mod) + end + end + + # Sets or gets the backtrace formatter. The backtrace formatter should + # implement `#format_backtrace(Array)`. This is used + # to format backtraces of errors handled by the `raise_error` + # matcher. + # + # If you are using rspec-core, rspec-core's backtrace formatting + # will be used (including respecting the presence or absence of + # the `--backtrace` option). + # + # @!attribute [rw] backtrace_formatter + attr_writer :backtrace_formatter + def backtrace_formatter + @backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter) + ::RSpec.configuration.backtrace_formatter + else + NullBacktraceFormatter + end + end + + # Sets if custom matcher descriptions and failure messages + # should include clauses from methods defined using `chain`. + # @param value [Boolean] + attr_writer :include_chain_clauses_in_custom_matcher_descriptions + + # Indicates whether or not custom matcher descriptions and failure messages + # should include clauses from methods defined using `chain`. It is + # false by default for backwards compatibility. + def include_chain_clauses_in_custom_matcher_descriptions? + @include_chain_clauses_in_custom_matcher_descriptions ||= false + end + + # @private + def reset_syntaxes_to_default + self.syntax = [:should, :expect] + RSpec::Expectations::Syntax.warn_about_should! + end + + # @api private + # Null implementation of a backtrace formatter used by default + # when rspec-core is not loaded. Does no filtering. + NullBacktraceFormatter = Module.new do + def self.format_backtrace(backtrace) + backtrace + end + end + + # Configures whether RSpec will warn about matcher use which will + # potentially cause false positives in tests. + # + # @param [Boolean] boolean + def warn_about_potential_false_positives=(boolean) + if boolean + self.on_potential_false_positives = :warn + elsif warn_about_potential_false_positives? + self.on_potential_false_positives = :nothing + else + # no-op, handler is something else + end + end + # + # Configures what RSpec will do about matcher use which will + # potentially cause false positives in tests. + # + # @param [Symbol] behavior can be set to :warn, :raise or :nothing + def on_potential_false_positives=(behavior) + unless FALSE_POSITIVE_BEHAVIOURS.key?(behavior) + raise ArgumentError, "Supported values are: #{FALSE_POSITIVE_BEHAVIOURS.keys}" + end + @on_potential_false_positives = behavior + end + + # Configures RSpec to check predicate matchers to `be(true)` / `be(false)` (strict), + # or `be_truthy` / `be_falsey` (not strict). + # Historically, the default was `false`, but `true` is recommended. + def strict_predicate_matchers=(flag) + raise ArgumentError, "Pass `true` or `false`" unless flag == true || flag == false + @strict_predicate_matchers = flag + end + + attr_reader :strict_predicate_matchers + + def strict_predicate_matchers? + @strict_predicate_matchers + end + + # Indicates what RSpec will do about matcher use which will + # potentially cause false positives in tests, generally you want to + # avoid such scenarios so this defaults to `true`. + attr_reader :on_potential_false_positives + + # Indicates whether RSpec will warn about matcher use which will + # potentially cause false positives in tests, generally you want to + # avoid such scenarios so this defaults to `true`. + def warn_about_potential_false_positives? + on_potential_false_positives == :warn + end + + # @private + def false_positives_handler + FALSE_POSITIVE_BEHAVIOURS.fetch(@on_potential_false_positives) + end + end + + # The configuration object. + # @return [RSpec::Expectations::Configuration] the configuration object + def self.configuration + @configuration ||= Configuration.new + end + + # set default syntax + configuration.reset_syntaxes_to_default + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/expectation_target.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/expectation_target.rb new file mode 100644 index 0000000000..176b31224d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/expectation_target.rb @@ -0,0 +1,127 @@ +module RSpec + module Expectations + # Wraps the target of an expectation. + # + # @example + # expect(something) # => ExpectationTarget wrapping something + # expect { do_something } # => ExpectationTarget wrapping the block + # + # # used with `to` + # expect(actual).to eq(3) + # + # # with `not_to` + # expect(actual).not_to eq(3) + # + # @note `ExpectationTarget` is not intended to be instantiated + # directly by users. Use `expect` instead. + class ExpectationTarget + # @private + # Used as a sentinel value to be able to tell when the user + # did not pass an argument. We can't use `nil` for that because + # `nil` is a valid value to pass. + UndefinedValue = Module.new + + # @note this name aligns with `Minitest::Expectation` so that our + # {InstanceMethods} module can be included in that class when + # used in a Minitest context. + # @return [Object] the target of the expectation + attr_reader :target + + # @api private + def initialize(value) + @target = value + end + + # @private + def self.for(value, block) + if UndefinedValue.equal?(value) + unless block + raise ArgumentError, "You must pass either an argument or a block to `expect`." + end + BlockExpectationTarget.new(block) + elsif block + raise ArgumentError, "You cannot pass both an argument and a block to `expect`." + else + new(value) + end + end + + # Defines instance {ExpectationTarget} instance methods. These are defined + # in a module so we can include it in `Minitest::Expectation` when + # `rspec/expectations/minitest_integration` is loaded in order to + # support usage with Minitest. + module InstanceMethods + # Runs the given expectation, passing if `matcher` returns true. + # @example + # expect(value).to eq(5) + # expect { perform }.to raise_error + # @param [Matcher] + # matcher + # @param [String or Proc] message optional message to display when the expectation fails + # @return [Boolean] true if the expectation succeeds (else raises) + # @see RSpec::Matchers + def to(matcher=nil, message=nil, &block) + prevent_operator_matchers(:to) unless matcher + RSpec::Expectations::PositiveExpectationHandler.handle_matcher(target, matcher, message, &block) + end + + # Runs the given expectation, passing if `matcher` returns false. + # @example + # expect(value).not_to eq(5) + # @param [Matcher] + # matcher + # @param [String or Proc] message optional message to display when the expectation fails + # @return [Boolean] false if the negative expectation succeeds (else raises) + # @see RSpec::Matchers + def not_to(matcher=nil, message=nil, &block) + prevent_operator_matchers(:not_to) unless matcher + RSpec::Expectations::NegativeExpectationHandler.handle_matcher(target, matcher, message, &block) + end + alias to_not not_to + + private + + def prevent_operator_matchers(verb) + raise ArgumentError, "The expect syntax does not support operator matchers, " \ + "so you must pass a matcher to `##{verb}`." + end + end + + include InstanceMethods + end + + # @private + # Validates the provided matcher to ensure it supports block + # expectations, in order to avoid user confusion when they + # use a block thinking the expectation will be on the return + # value of the block rather than the block itself. + class BlockExpectationTarget < ExpectationTarget + def to(matcher, message=nil, &block) + enforce_block_expectation(matcher) + super + end + + def not_to(matcher, message=nil, &block) + enforce_block_expectation(matcher) + super + end + alias to_not not_to + + private + + def enforce_block_expectation(matcher) + return if supports_block_expectations?(matcher) + + raise ExpectationNotMetError, "You must pass an argument rather than a block to `expect` to use the provided " \ + "matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \ + "`supports_block_expectations?`." + end + + def supports_block_expectations?(matcher) + matcher.supports_block_expectations? + rescue NoMethodError + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/fail_with.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/fail_with.rb new file mode 100644 index 0000000000..212e71c421 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/fail_with.rb @@ -0,0 +1,39 @@ +module RSpec + module Expectations + class << self + # @private + class Differ + # @private + OBJECT_PREPARER = lambda do |object| + RSpec::Matchers::Composable.surface_descriptions_in(object) + end + end + + # @private + def differ + RSpec::Support::Differ.new( + :object_preparer => Differ::OBJECT_PREPARER, + :color => RSpec::Matchers.configuration.color? + ) + end + + # Raises an RSpec::Expectations::ExpectationNotMetError with message. + # @param [String] message + # @param [Object] expected + # @param [Object] actual + # + # Adds a diff to the failure message when `expected` and `actual` are + # both present. + def fail_with(message, expected=nil, actual=nil) + unless message + raise ArgumentError, "Failure message is nil. Does your matcher define the " \ + "appropriate failure_message[_when_negated] method to return a string?" + end + + message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual) + + RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/failure_aggregator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/failure_aggregator.rb new file mode 100644 index 0000000000..e55929a133 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/failure_aggregator.rb @@ -0,0 +1,212 @@ +module RSpec + module Expectations + # @private + class FailureAggregator + attr_reader :block_label, :metadata + + def aggregate + RSpec::Support.with_failure_notifier(self) do + begin + yield + rescue ExpectationNotMetError => e + # Normally, expectation failures will be notified via the `call` method, below, + # but since the failure notifier uses a thread local variable, failing expectations + # in another thread will still raise. We handle that here and categorize it as part + # of `failures` rather than letting it fall through and be categorized as part of + # `other_errors`. + failures << e + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + # While it is normally a bad practice to rescue `Exception`, it's important we do + # so here. It's low risk (`notify_aggregated_failures` below will re-raise the exception, + # or raise a `MultipleExpectationsNotMetError` that includes the exception), and it's + # essential that the user is notified of expectation failures that may have already + # occurred in the `aggregate_failures` block. Those expectation failures may provide + # important diagnostics for understanding why this exception occurred, and if we simply + # allowed this exception to be raised as-is, it would (wrongly) suggest to the user + # that the expectation passed when it did not, which would be quite confusing. + other_errors << e + end + end + + notify_aggregated_failures + end + + def failures + @failures ||= [] + end + + def other_errors + @other_errors ||= [] + end + + # This method is defined to satisfy the callable interface + # expected by `RSpec::Support.with_failure_notifier`. + def call(failure, options) + source_id = options[:source_id] + return if source_id && @seen_source_ids.key?(source_id) + + @seen_source_ids[source_id] = true + assign_backtrace(failure) unless failure.backtrace + failures << failure + end + + private + + if RSpec::Support::Ruby.jruby? && RSpec::Support::Ruby.jruby_version < '9.2.0.0' + # On JRuby 9.1.x.x and before, `caller` and `raise` produce different backtraces with + # regards to `.java` stack frames. It's important that we use `raise` for JRuby to produce + # a backtrace that has a continuous common section with the raised `MultipleExpectationsNotMetError`, + # so that rspec-core's truncation logic can work properly on it to list the backtrace + # relative to the `aggregate_failures` block. + def assign_backtrace(failure) + raise failure + rescue failure.class => e + failure.set_backtrace(e.backtrace) + end + else + # Using `caller` performs better (and is simpler) than `raise` on most Rubies. + def assign_backtrace(failure) + failure.set_backtrace(caller) + end + end + + def initialize(block_label, metadata) + @block_label = block_label + @metadata = metadata + @seen_source_ids = {} # don't want to load stdlib set + end + + def notify_aggregated_failures + all_errors = failures + other_errors + + case all_errors.size + when 0 then return nil + when 1 then RSpec::Support.notify_failure all_errors.first + else RSpec::Support.notify_failure MultipleExpectationsNotMetError.new(self) + end + end + end + + # Exception raised from `aggregate_failures` when multiple expectations fail. + class MultipleExpectationsNotMetError + # @return [String] The fully formatted exception message. + def message + @message ||= (["#{summary}:"] + enumerated_failures + enumerated_errors).join("\n\n") + end + + # @return [Array] The list of expectation failures. + def failures + @failure_aggregator.failures + end + + # @return [Array] The list of other exceptions. + def other_errors + @failure_aggregator.other_errors + end + + # @return [Array] The list of expectation failures and other exceptions, combined. + attr_reader :all_exceptions + + # @return [String] The user-assigned label for the aggregation block. + def aggregation_block_label + @failure_aggregator.block_label + end + + # @return [Hash] The metadata hash passed to `aggregate_failures`. + def aggregation_metadata + @failure_aggregator.metadata + end + + # @return [String] A summary of the failure, including the block label and a count of failures. + def summary + "Got #{exception_count_description} from failure aggregation " \ + "block#{block_description}" + end + + # return [String] A description of the failure/error counts. + def exception_count_description + failure_count = pluralize("failure", failures.size) + return failure_count if other_errors.empty? + error_count = pluralize("other error", other_errors.size) + "#{failure_count} and #{error_count}" + end + + private + + def initialize(failure_aggregator) + @failure_aggregator = failure_aggregator + @all_exceptions = failures + other_errors + end + + def block_description + return "" unless aggregation_block_label + " #{aggregation_block_label.inspect}" + end + + def pluralize(noun, count) + "#{count} #{noun}#{'s' unless count == 1}" + end + + def enumerated(exceptions, index_offset) + exceptions.each_with_index.map do |exception, index| + index += index_offset + formatted_message = "#{yield exception}\n#{format_backtrace(exception.backtrace).first}" + "#{index_label index}#{indented formatted_message, index}" + end + end + + def exclusion_patterns + patterns = %w[/lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle:] + patterns << "org/jruby/" if RSpec::Support::Ruby.jruby? + patterns.map! { |s| Regexp.new(s.gsub('/', File::SEPARATOR)) } + end + + def format_backtrace(backtrace) + backtrace.map { |l| backtrace_line(l) }.compact.tap { |filtered| filtered.concat backtrace if filtered.empty? } + end + + def backtrace_line(line) + return if [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *exclusion_patterns)].any? { |p| line =~ p } + + # It changes the current path that is relative to + # system root to be relative to the project root. + line.sub(/(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/, '\\1.\\2'.freeze).sub(/\A([^:]+:\d+)$/, '\\1'.freeze) + end + + def enumerated_failures + enumerated(failures, 0, &:message) + end + + def enumerated_errors + enumerated(other_errors, failures.size) do |error| + "#{error.class}: #{error.message}" + end + end + + def indented(failure_message, index) + line_1, *rest = failure_message.strip.lines.to_a + first_line_indentation = ' ' * (longest_index_label_width - width_of_label(index)) + + first_line_indentation + line_1 + rest.map do |line| + line =~ /\S/ ? indentation + line : line + end.join + end + + def indentation + @indentation ||= ' ' * longest_index_label_width + end + + def longest_index_label_width + @longest_index_label_width ||= width_of_label(failures.size) + end + + def width_of_label(index) + index_label(index).chars.count + end + + def index_label(index) + " #{index + 1}) " + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb new file mode 100644 index 0000000000..a8f7c05318 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb @@ -0,0 +1,182 @@ +module RSpec + module Expectations + # @private + module ExpectationHelper + def self.check_message(msg) + unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call) + ::Kernel.warn [ + "WARNING: ignoring the provided expectation message argument (", + msg.inspect, + ") since it is not a string or a proc." + ].join + end + end + + # Returns an RSpec-3+ compatible matcher, wrapping a legacy one + # in an adapter if necessary. + # + # @private + def self.modern_matcher_from(matcher) + LegacyMatcherAdapter::RSpec2.wrap(matcher) || + LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher + end + + def self.with_matcher(handler, matcher, message) + check_message(message) + matcher = modern_matcher_from(matcher) + yield matcher + ensure + ::RSpec::Matchers.last_expectation_handler = handler + ::RSpec::Matchers.last_matcher = matcher + end + + def self.handle_failure(matcher, message, failure_message_method) + message = message.call if message.respond_to?(:call) + message ||= matcher.__send__(failure_message_method) + + if matcher.respond_to?(:diffable?) && matcher.diffable? + ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual + else + ::RSpec::Expectations.fail_with message + end + end + end + + # @private + class PositiveExpectationHandler + def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) + ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| + return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher + + match_result = matcher.matches?(actual, &block) + if custom_message && match_result.respond_to?(:error_generator) + match_result.error_generator.opts[:message] = custom_message + end + + match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message) + end + end + + def self.verb + 'is expected to' + end + + def self.should_method + :should + end + + def self.opposite_should_method + :should_not + end + end + + # @private + class NegativeExpectationHandler + def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) + ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| + return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher + + negated_match_result = does_not_match?(matcher, actual, &block) + if custom_message && negated_match_result.respond_to?(:error_generator) + negated_match_result.error_generator.opts[:message] = custom_message + end + + negated_match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message_when_negated) + end + end + + def self.does_not_match?(matcher, actual, &block) + if matcher.respond_to?(:does_not_match?) + matcher.does_not_match?(actual, &block) + else + !matcher.matches?(actual, &block) + end + end + + def self.verb + 'is expected not to' + end + + def self.should_method + :should_not + end + + def self.opposite_should_method + :should + end + end + + # Wraps a matcher written against one of the legacy protocols in + # order to present the current protocol. + # + # @private + class LegacyMatcherAdapter < Matchers::MatcherDelegator + def initialize(matcher) + super + ::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher") + |#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher + |protocol. For the current protocol you should expose the failure messages + |via the `failure_message` and `failure_message_when_negated` methods. + |(Used from #{CallerFilter.first_non_rspec_line}) + EOS + end + + def self.wrap(matcher) + new(matcher) if interface_matches?(matcher) + end + + # Starting in RSpec 1.2 (and continuing through all 2.x releases), + # the failure message protocol was: + # * `failure_message_for_should` + # * `failure_message_for_should_not` + # @private + class RSpec2 < self + def failure_message + base_matcher.failure_message_for_should + end + + def failure_message_when_negated + base_matcher.failure_message_for_should_not + end + + def self.interface_matches?(matcher) + ( + !matcher.respond_to?(:failure_message) && + matcher.respond_to?(:failure_message_for_should) + ) || ( + !matcher.respond_to?(:failure_message_when_negated) && + matcher.respond_to?(:failure_message_for_should_not) + ) + end + end + + # Before RSpec 1.2, the failure message protocol was: + # * `failure_message` + # * `negative_failure_message` + # @private + class RSpec1 < self + def failure_message + base_matcher.failure_message + end + + def failure_message_when_negated + base_matcher.negative_failure_message + end + + # Note: `failure_message` is part of the RSpec 3 protocol + # (paired with `failure_message_when_negated`), so we don't check + # for `failure_message` here. + def self.interface_matches?(matcher) + !matcher.respond_to?(:failure_message_when_negated) && + matcher.respond_to?(:negative_failure_message) + end + end + end + + # RSpec 3.0 was released with the class name misspelled. For SemVer compatibility, + # we will provide this misspelled alias until 4.0. + # @deprecated Use LegacyMatcherAdapter instead. + # @private + LegacyMacherAdapter = LegacyMatcherAdapter + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/minitest_integration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/minitest_integration.rb new file mode 100644 index 0000000000..94cdb5f3ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/minitest_integration.rb @@ -0,0 +1,58 @@ +require 'rspec/expectations' + +Minitest::Test.class_eval do + include ::RSpec::Matchers + + # This `expect` will only be called if the user is using Minitest < 5.6 + # or if they are _not_ using Minitest::Spec on 5.6+. Minitest::Spec on 5.6+ + # defines its own `expect` and will have the assertions incremented via our + # definitions of `to`/`not_to`/`to_not` below. + def expect(*a, &b) + self.assertions += 1 + super + end + + # Convert a `MultipleExpectationsNotMetError` to a `Minitest::Assertion` error so + # it gets counted in minitest's summary stats as a failure rather than an error. + # It would be nice to make `MultipleExpectationsNotMetError` subclass + # `Minitest::Assertion`, but Minitest's implementation does not treat subclasses + # the same, so this is the best we can do. + def aggregate_failures(*args, &block) + super + rescue RSpec::Expectations::MultipleExpectationsNotMetError => e + assertion_failed = Minitest::Assertion.new(e.message) + assertion_failed.set_backtrace e.backtrace + raise assertion_failed + end +end + +# Older versions of Minitest (e.g. before 5.6) do not define +# `Minitest::Expectation`. +if defined?(::Minitest::Expectation) + Minitest::Expectation.class_eval do + include RSpec::Expectations::ExpectationTarget::InstanceMethods + + def to(*args) + ctx.assertions += 1 + super + end + + def not_to(*args) + ctx.assertions += 1 + super + end + + def to_not(*args) + ctx.assertions += 1 + super + end + end +end + +module RSpec + module Expectations + remove_const :ExpectationNotMetError + # Exception raised when an expectation fails. + const_set :ExpectationNotMetError, ::Minitest::Assertion + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/syntax.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/syntax.rb new file mode 100644 index 0000000000..b8430346f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/syntax.rb @@ -0,0 +1,132 @@ +module RSpec + module Expectations + # @api private + # Provides methods for enabling and disabling the available + # syntaxes provided by rspec-expectations. + module Syntax + module_function + + # @api private + # Determines where we add `should` and `should_not`. + def default_should_host + @default_should_host ||= ::Object.ancestors.last + end + + # @api private + # Instructs rspec-expectations to warn on first usage of `should` or `should_not`. + # Enabled by default. This is largely here to facilitate testing. + def warn_about_should! + @warn_about_should = true + end + + # @api private + # Generates a deprecation warning for the given method if no warning + # has already been issued. + def warn_about_should_unless_configured(method_name) + return unless @warn_about_should + + RSpec.deprecate( + "Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax", + :replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`" + ) + + @warn_about_should = false + end + + # @api private + # Enables the `should` syntax. + def enable_should(syntax_host=default_should_host) + @warn_about_should = false if syntax_host == default_should_host + return if should_enabled?(syntax_host) + + syntax_host.module_exec do + def should(matcher=nil, message=nil, &block) + ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) + ::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block) + end + + def should_not(matcher=nil, message=nil, &block) + ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) + ::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block) + end + end + end + + # @api private + # Disables the `should` syntax. + def disable_should(syntax_host=default_should_host) + return unless should_enabled?(syntax_host) + + syntax_host.module_exec do + undef should + undef should_not + end + end + + # @api private + # Enables the `expect` syntax. + def enable_expect(syntax_host=::RSpec::Matchers) + return if expect_enabled?(syntax_host) + + syntax_host.module_exec do + def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block) + ::RSpec::Expectations::ExpectationTarget.for(value, block) + end + end + end + + # @api private + # Disables the `expect` syntax. + def disable_expect(syntax_host=::RSpec::Matchers) + return unless expect_enabled?(syntax_host) + + syntax_host.module_exec do + undef expect + end + end + + # @api private + # Indicates whether or not the `should` syntax is enabled. + def should_enabled?(syntax_host=default_should_host) + syntax_host.method_defined?(:should) + end + + # @api private + # Indicates whether or not the `expect` syntax is enabled. + def expect_enabled?(syntax_host=::RSpec::Matchers) + syntax_host.method_defined?(:expect) + end + end + end +end + +if defined?(BasicObject) + # The legacy `:should` syntax adds the following methods directly to + # `BasicObject` so that they are available off of any object. Note, however, + # that this syntax does not always play nice with delegate/proxy objects. + # We recommend you use the non-monkeypatching `:expect` syntax instead. + class BasicObject + # @method should(matcher, message) + # Passes if `matcher` returns true. Available on every `Object`. + # @example + # actual.should eq expected + # actual.should match /expression/ + # @param [Matcher] + # matcher + # @param [String] message optional message to display when the expectation fails + # @return [Boolean] true if the expectation succeeds (else raises) + # @note This is only available when you have enabled the `:should` syntax. + # @see RSpec::Matchers + + # @method should_not(matcher, message) + # Passes if `matcher` returns false. Available on every `Object`. + # @example + # actual.should_not eq expected + # @param [Matcher] + # matcher + # @param [String] message optional message to display when the expectation fails + # @return [Boolean] false if the negative expectation succeeds (else raises) + # @note This is only available when you have enabled the `:should` syntax. + # @see RSpec::Matchers + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/version.rb new file mode 100644 index 0000000000..f48854f3b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/expectations/version.rb @@ -0,0 +1,8 @@ +module RSpec + module Expectations + # @private + module Version + STRING = '3.10.1' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers.rb new file mode 100644 index 0000000000..443c8f158a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers.rb @@ -0,0 +1,1041 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support 'matcher_definition' +RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) } + +%w[ + english_phrasing + composable + built_in + generated_descriptions + dsl + matcher_delegator + aliased_matcher + expecteds_for_multiple_diffs +].each { |file| RSpec::Support.require_rspec_matchers(file) } + +# RSpec's top level namespace. All of rspec-expectations is contained +# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces. +module RSpec + # RSpec::Matchers provides a number of useful matchers we use to define + # expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol) + # can be used as a matcher. + # + # ## Predicates + # + # In addition to matchers that are defined explicitly, RSpec will create + # custom matchers on the fly for any arbitrary predicate, giving your specs a + # much more natural language feel. + # + # A Ruby predicate is a method that ends with a "?" and returns true or false. + # Common examples are `empty?`, `nil?`, and `instance_of?`. + # + # All you need to do is write `expect(..).to be_` followed by the predicate + # without the question mark, and RSpec will figure it out from there. + # For example: + # + # expect([]).to be_empty # => [].empty?() | passes + # expect([]).not_to be_empty # => [].empty?() | fails + # + # In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_" + # and "be_an_", making your specs read much more naturally: + # + # expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes + # + # expect(3).to be_a_kind_of(Integer) # => 3.kind_of?(Numeric) | passes + # expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes + # expect(3).to be_an_instance_of(Integer) # => 3.instance_of?(Integer) | passes + # expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails + # + # RSpec will also create custom matchers for predicates like `has_key?`. To + # use this feature, just state that the object should have_key(:key) and RSpec will + # call has_key?(:key) on the target. For example: + # + # expect(:a => "A").to have_key(:a) + # expect(:a => "A").to have_key(:b) # fails + # + # You can use this feature to invoke any predicate that begins with "has_", whether it is + # part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class. + # + # Note that RSpec does not provide composable aliases for these dynamic predicate + # matchers. You can easily define your own aliases, though: + # + # RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin + # expect(user_list).to include(a_user_who_is_an_admin) + # + # ## Alias Matchers + # + # With {RSpec::Matchers.alias_matcher}, you can easily create an + # alternate name for a given matcher. + # + # The description will also change according to the new name: + # + # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to + # sum_to(3).description # => "sum to 3" + # a_list_that_sums_to(3).description # => "a list that sums to 3" + # + # or you can specify a custom description like this: + # + # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description| + # description.sub("be sorted by", "a list sorted by") + # end + # + # be_sorted_by(:age).description # => "be sorted by age" + # a_list_sorted_by(:age).description # => "a list sorted by age" + # + # ## Custom Matchers + # + # When you find that none of the stock matchers provide a natural feeling + # expectation, you can very easily write your own using RSpec's matcher DSL + # or writing one from scratch. + # + # ### Matcher DSL + # + # Imagine that you are writing a game in which players can be in various + # zones on a virtual board. To specify that bob should be in zone 4, you + # could say: + # + # expect(bob.current_zone).to eql(Zone.new("4")) + # + # But you might find it more expressive to say: + # + # expect(bob).to be_in_zone("4") + # + # and/or + # + # expect(bob).not_to be_in_zone("3") + # + # You can create such a matcher like so: + # + # RSpec::Matchers.define :be_in_zone do |zone| + # match do |player| + # player.in_zone?(zone) + # end + # end + # + # This will generate a be_in_zone method that returns a matcher + # with logical default messages for failures. You can override the failure + # messages and the generated description as follows: + # + # RSpec::Matchers.define :be_in_zone do |zone| + # match do |player| + # player.in_zone?(zone) + # end + # + # failure_message do |player| + # # generate and return the appropriate string. + # end + # + # failure_message_when_negated do |player| + # # generate and return the appropriate string. + # end + # + # description do + # # generate and return the appropriate string. + # end + # end + # + # Each of the message-generation methods has access to the block arguments + # passed to the create method (in this case, zone). The + # failure message methods (failure_message and + # failure_message_when_negated) are passed the actual value (the + # receiver of expect(..) or expect(..).not_to). + # + # ### Custom Matcher from scratch + # + # You could also write a custom matcher from scratch, as follows: + # + # class BeInZone + # def initialize(expected) + # @expected = expected + # end + # + # def matches?(target) + # @target = target + # @target.current_zone.eql?(Zone.new(@expected)) + # end + # + # def failure_message + # "expected #{@target.inspect} to be in Zone #{@expected}" + # end + # + # def failure_message_when_negated + # "expected #{@target.inspect} not to be in Zone #{@expected}" + # end + # end + # + # ... and a method like this: + # + # def be_in_zone(expected) + # BeInZone.new(expected) + # end + # + # And then expose the method to your specs. This is normally done + # by including the method and the class in a module, which is then + # included in your spec: + # + # module CustomGameMatchers + # class BeInZone + # # ... + # end + # + # def be_in_zone(expected) + # # ... + # end + # end + # + # describe "Player behaviour" do + # include CustomGameMatchers + # # ... + # end + # + # or you can include in globally in a spec_helper.rb file required + # from your spec file(s): + # + # RSpec::configure do |config| + # config.include(CustomGameMatchers) + # end + # + # ### Making custom matchers composable + # + # RSpec's built-in matchers are designed to be composed, in expressions like: + # + # expect(["barn", 2.45]).to contain_exactly( + # a_value_within(0.1).of(2.5), + # a_string_starting_with("bar") + # ) + # + # Custom matchers can easily participate in composed matcher expressions like these. + # Include {RSpec::Matchers::Composable} in your custom matcher to make it support + # being composed (matchers defined using the DSL have this included automatically). + # Within your matcher's `matches?` method (or the `match` block, if using the DSL), + # use `values_match?(expected, actual)` rather than `expected == actual`. + # Under the covers, `values_match?` is able to match arbitrary + # nested data structures containing a mix of both matchers and non-matcher objects. + # It uses `===` and `==` to perform the matching, considering the values to + # match if either returns `true`. The `Composable` mixin also provides some helper + # methods for surfacing the matcher descriptions within your matcher's description + # or failure messages. + # + # RSpec's built-in matchers each have a number of aliases that rephrase the matcher + # from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`), + # which reads better when the matcher is passed as an argument in a composed matcher + # expressions, and also uses the noun-phrase wording in the matcher's `description`, + # for readable failure messages. You can alias your custom matchers in similar fashion + # using {RSpec::Matchers.alias_matcher}. + # + # ## Negated Matchers + # + # Sometimes if you want to test for the opposite using a more descriptive name + # instead of using `not_to`, you can use {RSpec::Matchers.define_negated_matcher}: + # + # RSpec::Matchers.define_negated_matcher :exclude, :include + # include(1, 2).description # => "include 1 and 2" + # exclude(1, 2).description # => "exclude 1 and 2" + # + # While the most obvious negated form may be to add a `not_` prefix, + # the failure messages you get with that form can be confusing (e.g. + # "expected [actual] to not [verb], but did not"). We've found it works + # best to find a more positive name for the negated form, such as + # `avoid_changing` rather than `not_change`. + # + module Matchers # rubocop:disable Metrics/ModuleLength + extend ::RSpec::Matchers::DSL + + # @!macro [attach] alias_matcher + # @!parse + # alias $1 $2 + # @!visibility private + # We define this override here so we can attach a YARD macro to it. + # It ensures that our docs list all the matcher aliases. + def self.alias_matcher(*args, &block) + super(*args, &block) + end + + # @!method self.alias_matcher(new_name, old_name, options={}, &description_override) + # Extended from {RSpec::Matchers::DSL#alias_matcher}. + + # @!method self.define(name, &declarations) + # Extended from {RSpec::Matchers::DSL#define}. + + # @!method self.define_negated_matcher(negated_name, base_name, &description_override) + # Extended from {RSpec::Matchers::DSL#define_negated_matcher}. + + # @method expect + # Supports `expect(actual).to matcher` syntax by wrapping `actual` in an + # `ExpectationTarget`. + # @example + # expect(actual).to eq(expected) + # expect(actual).not_to eq(expected) + # @return [Expectations::ExpectationTarget] + # @see Expectations::ExpectationTarget#to + # @see Expectations::ExpectationTarget#not_to + + # Allows multiple expectations in the provided block to fail, and then + # aggregates them into a single exception, rather than aborting on the + # first expectation failure like normal. This allows you to see all + # failures from an entire set of expectations without splitting each + # off into its own example (which may slow things down if the example + # setup is expensive). + # + # @param label [String] label for this aggregation block, which will be + # included in the aggregated exception message. + # @param metadata [Hash] additional metadata about this failure aggregation + # block. If multiple expectations fail, it will be exposed from the + # {Expectations::MultipleExpectationsNotMetError} exception. Mostly + # intended for internal RSpec use but you can use it as well. + # @yield Block containing as many expectation as you want. The block is + # simply yielded to, so you can trust that anything that works outside + # the block should work within it. + # @raise [Expectations::MultipleExpectationsNotMetError] raised when + # multiple expectations fail. + # @raise [Expectations::ExpectationNotMetError] raised when a single + # expectation fails. + # @raise [Exception] other sorts of exceptions will be raised as normal. + # + # @example + # aggregate_failures("verifying response") do + # expect(response.status).to eq(200) + # expect(response.headers).to include("Content-Type" => "text/plain") + # expect(response.body).to include("Success") + # end + # + # @note The implementation of this feature uses a thread-local variable, + # which means that if you have an expectation failure in another thread, + # it'll abort like normal. + def aggregate_failures(label=nil, metadata={}, &block) + Expectations::FailureAggregator.new(label, metadata).aggregate(&block) + end + + # Passes if actual is truthy (anything but false or nil) + def be_truthy + BuiltIn::BeTruthy.new + end + alias_matcher :a_truthy_value, :be_truthy + + # Passes if actual is falsey (false or nil) + def be_falsey + BuiltIn::BeFalsey.new + end + alias_matcher :be_falsy, :be_falsey + alias_matcher :a_falsey_value, :be_falsey + alias_matcher :a_falsy_value, :be_falsey + + # Passes if actual is nil + def be_nil + BuiltIn::BeNil.new + end + alias_matcher :a_nil_value, :be_nil + + # @example + # expect(actual).to be_truthy + # expect(actual).to be_falsey + # expect(actual).to be_nil + # expect(actual).to be_[arbitrary_predicate](*args) + # expect(actual).not_to be_nil + # expect(actual).not_to be_[arbitrary_predicate](*args) + # + # Given true, false, or nil, will pass if actual value is true, false or + # nil (respectively). Given no args means the caller should satisfy an if + # condition (to be or not to be). + # + # Predicates are any Ruby method that ends in a "?" and returns true or + # false. Given be_ followed by arbitrary_predicate (without the "?"), + # RSpec will match convert that into a query against the target object. + # + # The arbitrary_predicate feature will handle any predicate prefixed with + # "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_" + # (e.g. be_empty), letting you choose the prefix that best suits the + # predicate. + def be(*args) + args.empty? ? Matchers::BuiltIn::Be.new : equal(*args) + end + alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport + + # passes if target.kind_of?(klass) + def be_a(klass) + be_a_kind_of(klass) + end + alias_method :be_an, :be_a + + # Passes if actual.instance_of?(expected) + # + # @example + # expect(5).to be_an_instance_of(Integer) + # expect(5).not_to be_an_instance_of(Numeric) + # expect(5).not_to be_an_instance_of(Float) + def be_an_instance_of(expected) + BuiltIn::BeAnInstanceOf.new(expected) + end + alias_method :be_instance_of, :be_an_instance_of + alias_matcher :an_instance_of, :be_an_instance_of + + # Passes if actual.kind_of?(expected) + # + # @example + # expect(5).to be_a_kind_of(Integer) + # expect(5).to be_a_kind_of(Numeric) + # expect(5).not_to be_a_kind_of(Float) + def be_a_kind_of(expected) + BuiltIn::BeAKindOf.new(expected) + end + alias_method :be_kind_of, :be_a_kind_of + alias_matcher :a_kind_of, :be_a_kind_of + + # Passes if actual.between?(min, max). Works with any Comparable object, + # including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer, + # Float, Complex, and Rational). + # + # By default, `be_between` is inclusive (i.e. passes when given either the max or min value), + # but you can make it `exclusive` by chaining that off the matcher. + # + # @example + # expect(5).to be_between(1, 10) + # expect(11).not_to be_between(1, 10) + # expect(10).not_to be_between(1, 10).exclusive + def be_between(min, max) + BuiltIn::BeBetween.new(min, max) + end + alias_matcher :a_value_between, :be_between + + # Passes if actual == expected +/- delta + # + # @example + # expect(result).to be_within(0.5).of(3.0) + # expect(result).not_to be_within(0.5).of(3.0) + def be_within(delta) + BuiltIn::BeWithin.new(delta) + end + alias_matcher :a_value_within, :be_within + alias_matcher :within, :be_within + + # Applied to a proc, specifies that its execution will cause some value to + # change. + # + # @param [Object] receiver + # @param [Symbol] message the message to send the receiver + # + # You can either pass receiver and message, or a block, + # but not both. + # + # When passing a block, it must use the `{ ... }` format, not + # do/end, as `{ ... }` binds to the `change` method, whereas do/end + # would errantly bind to the `expect(..).to` or `expect(...).not_to` method. + # + # You can chain any of the following off of the end to specify details + # about the change: + # + # * `from` + # * `to` + # + # or any one of: + # + # * `by` + # * `by_at_least` + # * `by_at_most` + # + # @example + # expect { + # team.add_player(player) + # }.to change(roster, :count) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by(1) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by_at_least(1) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by_at_most(1) + # + # string = "string" + # expect { + # string.reverse! + # }.to change { string }.from("string").to("gnirts") + # + # string = "string" + # expect { + # string + # }.not_to change { string }.from("string") + # + # expect { + # person.happy_birthday + # }.to change(person, :birthday).from(32).to(33) + # + # expect { + # employee.develop_great_new_social_networking_app + # }.to change(employee, :title).from("Mail Clerk").to("CEO") + # + # expect { + # doctor.leave_office + # }.to change(doctor, :sign).from(/is in/).to(/is out/) + # + # user = User.new(:type => "admin") + # expect { + # user.symbolize_type + # }.to change(user, :type).from(String).to(Symbol) + # + # == Notes + # + # Evaluates `receiver.message` or `block` before and after it + # evaluates the block passed to `expect`. If the value is the same + # object, its before/after `hash` value is used to see if it has changed. + # Therefore, your object needs to properly implement `hash` to work correctly + # with this matcher. + # + # `expect( ... ).not_to change` supports the form that specifies `from` + # (which specifies what you expect the starting, unchanged value to be) + # but does not support forms with subsequent calls to `by`, `by_at_least`, + # `by_at_most` or `to`. + def change(receiver=nil, message=nil, &block) + BuiltIn::Change.new(receiver, message, &block) + end + alias_matcher :a_block_changing, :change + alias_matcher :changing, :change + + # Passes if actual contains all of the expected regardless of order. + # This works for collections. Pass in multiple args and it will only + # pass if all args are found in collection. + # + # @note This is also available using the `=~` operator with `should`, + # but `=~` is not supported with `expect`. + # + # @example + # expect([1, 2, 3]).to contain_exactly(1, 2, 3) + # expect([1, 2, 3]).to contain_exactly(1, 3, 2) + # + # @see #match_array + def contain_exactly(*items) + BuiltIn::ContainExactly.new(items) + end + alias_matcher :a_collection_containing_exactly, :contain_exactly + alias_matcher :containing_exactly, :contain_exactly + + # Passes if actual covers expected. This works for + # Ranges. You can also pass in multiple args + # and it will only pass if all args are found in Range. + # + # @example + # expect(1..10).to cover(5) + # expect(1..10).to cover(4, 6) + # expect(1..10).to cover(4, 6, 11) # fails + # expect(1..10).not_to cover(11) + # expect(1..10).not_to cover(5) # fails + # + # ### Warning:: Ruby >= 1.9 only + def cover(*values) + BuiltIn::Cover.new(*values) + end + alias_matcher :a_range_covering, :cover + alias_matcher :covering, :cover + + # Matches if the actual value ends with the expected value(s). In the case + # of a string, matches against the last `expected.length` characters of the + # actual string. In the case of an array, matches against the last + # `expected.length` elements of the actual array. + # + # @example + # expect("this string").to end_with "string" + # expect([0, 1, 2, 3, 4]).to end_with 4 + # expect([0, 2, 3, 4, 4]).to end_with 3, 4 + def end_with(*expected) + BuiltIn::EndWith.new(*expected) + end + alias_matcher :a_collection_ending_with, :end_with + alias_matcher :a_string_ending_with, :end_with + alias_matcher :ending_with, :end_with + + # Passes if actual == expected. + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to eq(5) + # expect(5).not_to eq(3) + def eq(expected) + BuiltIn::Eq.new(expected) + end + alias_matcher :an_object_eq_to, :eq + alias_matcher :eq_to, :eq + + # Passes if `actual.eql?(expected)` + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to eql(5) + # expect(5).not_to eql(3) + def eql(expected) + BuiltIn::Eql.new(expected) + end + alias_matcher :an_object_eql_to, :eql + alias_matcher :eql_to, :eql + + # Passes if actual.equal?(expected) (object identity). + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to equal(5) # Integers are equal + # expect("5").not_to equal("5") # Strings that look the same are not the same object + def equal(expected) + BuiltIn::Equal.new(expected) + end + alias_matcher :an_object_equal_to, :equal + alias_matcher :equal_to, :equal + + # Passes if `actual.exist?` or `actual.exists?` + # + # @example + # expect(File).to exist("path/to/file") + def exist(*args) + BuiltIn::Exist.new(*args) + end + alias_matcher :an_object_existing, :exist + alias_matcher :existing, :exist + + # Passes if actual's attribute values match the expected attributes hash. + # This works no matter how you define your attribute readers. + # + # @example + # Person = Struct.new(:name, :age) + # person = Person.new("Bob", 32) + # + # expect(person).to have_attributes(:name => "Bob", :age => 32) + # expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) ) + # + # @note It will fail if actual doesn't respond to any of the expected attributes. + # + # @example + # expect(person).to have_attributes(:color => "red") + def have_attributes(expected) + BuiltIn::HaveAttributes.new(expected) + end + alias_matcher :an_object_having_attributes, :have_attributes + alias_matcher :having_attributes, :have_attributes + + # Passes if actual includes expected. This works for + # collections and Strings. You can also pass in multiple args + # and it will only pass if all args are found in collection. + # + # @example + # expect([1,2,3]).to include(3) + # expect([1,2,3]).to include(2,3) + # expect([1,2,3]).to include(2,3,4) # fails + # expect([1,2,3]).not_to include(4) + # expect("spread").to include("read") + # expect("spread").not_to include("red") + # expect(:a => 1, :b => 2).to include(:a) + # expect(:a => 1, :b => 2).to include(:a, :b) + # expect(:a => 1, :b => 2).to include(:a => 1) + # expect(:a => 1, :b => 2).to include(:b => 2, :a => 1) + # expect(:a => 1, :b => 2).to include(:c) # fails + # expect(:a => 1, :b => 2).not_to include(:a => 2) + def include(*expected) + BuiltIn::Include.new(*expected) + end + alias_matcher :a_collection_including, :include + alias_matcher :a_string_including, :include + alias_matcher :a_hash_including, :include + alias_matcher :including, :include + + # Passes if the provided matcher passes when checked against all + # elements of the collection. + # + # @example + # expect([1, 3, 5]).to all be_odd + # expect([1, 3, 6]).to all be_odd # fails + # + # @note The negative form `not_to all` is not supported. Instead + # use `not_to include` or pass a negative form of a matcher + # as the argument (e.g. `all exclude(:foo)`). + # + # @note You can also use this with compound matchers as well. + # + # @example + # expect([1, 3, 5]).to all( be_odd.and be_an(Integer) ) + def all(expected) + BuiltIn::All.new(expected) + end + + # Given a `Regexp` or `String`, passes if `actual.match(pattern)` + # Given an arbitrary nested data structure (e.g. arrays and hashes), + # matches if `expected === actual` || `actual == expected` for each + # pair of elements. + # + # @example + # expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) + # expect(email).to match("@example.com") + # + # @example + # hash = { + # :a => { + # :b => ["foo", 5], + # :c => { :d => 2.05 } + # } + # } + # + # expect(hash).to match( + # :a => { + # :b => a_collection_containing_exactly( + # a_string_starting_with("f"), + # an_instance_of(Integer) + # ), + # :c => { :d => (a_value < 3) } + # } + # ) + # + # @note The `match_regex` alias is deprecated and is not recommended for use. + # It was added in 2.12.1 to facilitate its use from within custom + # matchers (due to how the custom matcher DSL was evaluated in 2.x, + # `match` could not be used there), but is no longer needed in 3.x. + def match(expected) + BuiltIn::Match.new(expected) + end + alias_matcher :match_regex, :match + alias_matcher :an_object_matching, :match + alias_matcher :a_string_matching, :match + alias_matcher :matching, :match + + # An alternate form of `contain_exactly` that accepts + # the expected contents as a single array arg rather + # that splatted out as individual items. + # + # @example + # expect(results).to contain_exactly(1, 2) + # # is identical to: + # expect(results).to match_array([1, 2]) + # + # @see #contain_exactly + def match_array(items) + contain_exactly(*items) + end + + # With no arg, passes if the block outputs `to_stdout` or `to_stderr`. + # With a string, passes if the block outputs that specific string `to_stdout` or `to_stderr`. + # With a regexp or matcher, passes if the block outputs a string `to_stdout` or `to_stderr` that matches. + # + # To capture output from any spawned subprocess as well, use `to_stdout_from_any_process` or + # `to_stderr_from_any_process`. Output from any process that inherits the main process's corresponding + # standard stream will be captured. + # + # @example + # expect { print 'foo' }.to output.to_stdout + # expect { print 'foo' }.to output('foo').to_stdout + # expect { print 'foo' }.to output(/foo/).to_stdout + # + # expect { do_something }.to_not output.to_stdout + # + # expect { warn('foo') }.to output.to_stderr + # expect { warn('foo') }.to output('foo').to_stderr + # expect { warn('foo') }.to output(/foo/).to_stderr + # + # expect { do_something }.to_not output.to_stderr + # + # expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process + # expect { system('echo foo', out: :err) }.to output("foo\n").to_stderr_from_any_process + # + # @note `to_stdout` and `to_stderr` work by temporarily replacing `$stdout` or `$stderr`, + # so they're not able to intercept stream output that explicitly uses `STDOUT`/`STDERR` + # or that uses a reference to `$stdout`/`$stderr` that was stored before the + # matcher was used. + # @note `to_stdout_from_any_process` and `to_stderr_from_any_process` use Tempfiles, and + # are thus significantly (~30x) slower than `to_stdout` and `to_stderr`. + def output(expected=nil) + BuiltIn::Output.new(expected) + end + alias_matcher :a_block_outputting, :output + + # With no args, matches if any error is raised. + # With a named error, matches only if that specific error is raised. + # With a named error and messsage specified as a String, matches only if both match. + # With a named error and messsage specified as a Regexp, matches only if both match. + # Pass an optional block to perform extra verifications on the exception matched + # + # @example + # expect { do_something_risky }.to raise_error + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError) + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 } + # expect { do_something_risky }.to raise_error { |error| expect(error.data).to eq 42 } + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky") + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/) + # expect { do_something_risky }.to raise_error("that was too risky") + # + # expect { do_something_risky }.not_to raise_error + def raise_error(error=BuiltIn::RaiseError::UndefinedValue, message=nil, &block) + BuiltIn::RaiseError.new(error, message, &block) + end + alias_method :raise_exception, :raise_error + + alias_matcher :a_block_raising, :raise_error do |desc| + desc.sub("raise", "a block raising") + end + + alias_matcher :raising, :raise_error do |desc| + desc.sub("raise", "raising") + end + + # Matches if the target object responds to all of the names + # provided. Names can be Strings or Symbols. + # + # @example + # expect("string").to respond_to(:length) + # + def respond_to(*names) + BuiltIn::RespondTo.new(*names) + end + alias_matcher :an_object_responding_to, :respond_to + alias_matcher :responding_to, :respond_to + + # Passes if the submitted block returns true. Yields target to the + # block. + # + # Generally speaking, this should be thought of as a last resort when + # you can't find any other way to specify the behaviour you wish to + # specify. + # + # If you do find yourself in such a situation, you could always write + # a custom matcher, which would likely make your specs more expressive. + # + # @param description [String] optional description to be used for this matcher. + # + # @example + # expect(5).to satisfy { |n| n > 3 } + # expect(5).to satisfy("be greater than 3") { |n| n > 3 } + def satisfy(description=nil, &block) + BuiltIn::Satisfy.new(description, &block) + end + alias_matcher :an_object_satisfying, :satisfy + alias_matcher :satisfying, :satisfy + + # Matches if the actual value starts with the expected value(s). In the + # case of a string, matches against the first `expected.length` characters + # of the actual string. In the case of an array, matches against the first + # `expected.length` elements of the actual array. + # + # @example + # expect("this string").to start_with "this s" + # expect([0, 1, 2, 3, 4]).to start_with 0 + # expect([0, 2, 3, 4, 4]).to start_with 0, 1 + def start_with(*expected) + BuiltIn::StartWith.new(*expected) + end + alias_matcher :a_collection_starting_with, :start_with + alias_matcher :a_string_starting_with, :start_with + alias_matcher :starting_with, :start_with + + # Given no argument, matches if a proc throws any Symbol. + # + # Given a Symbol, matches if the given proc throws the specified Symbol. + # + # Given a Symbol and an arg, matches if the given proc throws the + # specified Symbol with the specified arg. + # + # @example + # expect { do_something_risky }.to throw_symbol + # expect { do_something_risky }.to throw_symbol(:that_was_risky) + # expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit') + # + # expect { do_something_risky }.not_to throw_symbol + # expect { do_something_risky }.not_to throw_symbol(:that_was_risky) + # expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit') + def throw_symbol(expected_symbol=nil, expected_arg=nil) + BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg) + end + + alias_matcher :a_block_throwing, :throw_symbol do |desc| + desc.sub("throw", "a block throwing") + end + + alias_matcher :throwing, :throw_symbol do |desc| + desc.sub("throw", "throwing") + end + + # Passes if the method called in the expect block yields, regardless + # of whether or not arguments are yielded. + # + # @example + # expect { |b| 5.tap(&b) }.to yield_control + # expect { |b| "a".to_sym(&b) }.not_to yield_control + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + def yield_control + BuiltIn::YieldControl.new + end + alias_matcher :a_block_yielding_control, :yield_control + alias_matcher :yielding_control, :yield_control + + # Passes if the method called in the expect block yields with + # no arguments. Fails if it does not yield, or yields with arguments. + # + # @example + # expect { |b| User.transaction(&b) }.to yield_with_no_args + # expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5` + # expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + # @note This matcher is not designed for use with methods that yield + # multiple times. + def yield_with_no_args + BuiltIn::YieldWithNoArgs.new + end + alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args + alias_matcher :yielding_with_no_args, :yield_with_no_args + + # Given no arguments, matches if the method called in the expect + # block yields with arguments (regardless of what they are or how + # many there are). + # + # Given arguments, matches if the method called in the expect block + # yields with arguments that match the given arguments. + # + # Argument matching is done using `===` (the case match operator) + # and `==`. If the expected and actual arguments match with either + # operator, the matcher will pass. + # + # @example + # expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg + # expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5 + # expect { |b| 5.tap(&b) }.to yield_with_args(Integer) # because Integer === 5 + # expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt" + # + # expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args + # expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3) + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + # @note This matcher is not designed for use with methods that yield + # multiple times. + def yield_with_args(*args) + BuiltIn::YieldWithArgs.new(*args) + end + alias_matcher :a_block_yielding_with_args, :yield_with_args + alias_matcher :yielding_with_args, :yield_with_args + + # Designed for use with methods that repeatedly yield (such as + # iterators). Passes if the method called in the expect block yields + # multiple times with arguments matching those given. + # + # Argument matching is done using `===` (the case match operator) + # and `==`. If the expected and actual arguments match with either + # operator, the matcher will pass. + # + # @example + # expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) + # expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) + # expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2) + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + def yield_successive_args(*args) + BuiltIn::YieldSuccessiveArgs.new(*args) + end + alias_matcher :a_block_yielding_successive_args, :yield_successive_args + alias_matcher :yielding_successive_args, :yield_successive_args + + # Delegates to {RSpec::Expectations.configuration}. + # This is here because rspec-core's `expect_with` option + # looks for a `configuration` method on the mixin + # (`RSpec::Matchers`) to yield to a block. + # @return [RSpec::Expectations::Configuration] the configuration object + def self.configuration + Expectations.configuration + end + + private + + BE_PREDICATE_REGEX = /^(?:be_(?:an?_)?)(.*)/ + HAS_REGEX = /^(?:have_)(.*)/ + DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX) + + def method_missing(method, *args, &block) + case method.to_s + when BE_PREDICATE_REGEX + BuiltIn::BePredicate.new(method, *args, &block) + when HAS_REGEX + BuiltIn::Has.new(method, *args, &block) + else + super + end + end + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + + if RUBY_VERSION.to_f >= 1.9 + def respond_to_missing?(method, *) + method =~ DYNAMIC_MATCHER_REGEX || super + end + else # for 1.8.7 + # :nocov: + def respond_to?(method, *) + method = method.to_s + method =~ DYNAMIC_MATCHER_REGEX || super + end + public :respond_to? + # :nocov: + end + + # @api private + def self.is_a_matcher?(obj) + return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj + begin + return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher) + rescue NoMethodError + # Some objects, like BasicObject, don't implemented standard + # reflection methods. + return false + end + return false unless obj.respond_to?(:matches?) + + obj.respond_to?(:failure_message) || + obj.respond_to?(:failure_message_for_should) # support legacy matchers + end + + ::RSpec::Support.register_matcher_definition do |obj| + is_a_matcher?(obj) + end + + # @api private + def self.is_a_describable_matcher?(obj) + is_a_matcher?(obj) && obj.respond_to?(:description) + end + + class << self + private + + if RSpec::Support::Ruby.mri? && RUBY_VERSION[0, 3] == '1.9' + # Note that `included` doesn't work for this because it is triggered + # _after_ `RSpec::Matchers` is an ancestor of the inclusion host, rather + # than _before_, like `append_features`. It's important we check this before + # in order to find the cases where it was already previously included. + # @api private + def append_features(mod) + return super if mod < self # `mod < self` indicates a re-inclusion. + + subclasses = ObjectSpace.each_object(Class).select { |c| c < mod && c < self } + return super unless subclasses.any? + + subclasses.reject! { |s| subclasses.any? { |s2| s < s2 } } # Filter to the root ancestor. + subclasses = subclasses.map { |s| "`#{s}`" }.join(", ") + + RSpec.warning "`#{self}` has been included in a superclass (`#{mod}`) " \ + "after previously being included in subclasses (#{subclasses}), " \ + "which can trigger infinite recursion from `super` due to an MRI 1.9 bug " \ + "(https://redmine.ruby-lang.org/issues/3351). To work around this, " \ + "either upgrade to MRI 2.0+, include a dup of the module (e.g. " \ + "`include #{self}.dup`), or find a way to include `#{self}` in `#{mod}` " \ + "before it is included in subclasses (#{subclasses}). See " \ + "https://github.com/rspec/rspec-expectations/issues/814 for more info" + + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/aliased_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/aliased_matcher.rb new file mode 100644 index 0000000000..c384d2ac25 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/aliased_matcher.rb @@ -0,0 +1,116 @@ +module RSpec + module Matchers + # Decorator that wraps a matcher and overrides `description` + # using the provided block in order to support an alias + # of a matcher. This is intended for use when composing + # matchers, so that you can use an expression like + # `include( a_value_within(0.1).of(3) )` rather than + # `include( be_within(0.1).of(3) )`, and have the corresponding + # description read naturally. + # + # @api private + class AliasedMatcher < MatcherDelegator + def initialize(base_matcher, description_block) + @description_block = description_block + super(base_matcher) + end + + # Forward messages on to the wrapped matcher. + # Since many matchers provide a fluent interface + # (e.g. `a_value_within(0.1).of(3)`), we need to wrap + # the returned value if it responds to `description`, + # so that our override can be applied when it is eventually + # used. + def method_missing(*) + return_val = super + return return_val unless RSpec::Matchers.is_a_matcher?(return_val) + self.class.new(return_val, @description_block) + end + + # Provides the description of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The description is different + # to reflect the aliased name. + # + # @api private + def description + @description_block.call(super) + end + + # Provides the failure_message of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The failure_message is different + # to reflect the aliased name. + # + # @api private + def failure_message + @description_block.call(super) + end + + # Provides the failure_message_when_negated of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The failure_message_when_negated is different + # to reflect the aliased name. + # + # @api private + def failure_message_when_negated + @description_block.call(super) + end + end + + # Decorator used for matchers that have special implementations of + # operators like `==` and `===`. + # @private + class AliasedMatcherWithOperatorSupport < AliasedMatcher + # We undef these so that they get delegated via `method_missing`. + undef == + undef === + end + + # @private + class AliasedNegatedMatcher < AliasedMatcher + def matches?(*args, &block) + if @base_matcher.respond_to?(:does_not_match?) + @base_matcher.does_not_match?(*args, &block) + else + !super + end + end + + def does_not_match?(*args, &block) + @base_matcher.matches?(*args, &block) + end + + def failure_message + optimal_failure_message(__method__, :failure_message_when_negated) + end + + def failure_message_when_negated + optimal_failure_message(__method__, :failure_message) + end + + private + + DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages + + # For a matcher that uses the default failure messages, we prefer to + # use the override provided by the `description_block`, because it + # includes the phrasing that the user has expressed a preference for + # by going through the effort of defining a negated matcher. + # + # However, if the override didn't actually change anything, then we + # should return the opposite failure message instead -- the overriden + # message is going to be confusing if we return it as-is, as it represents + # the non-negated failure message for a negated match (or vice versa). + def optimal_failure_message(same, inverted) + if DefaultFailureMessages.has_default_failure_messages?(@base_matcher) + base_message = @base_matcher.__send__(same) + overriden = @description_block.call(base_message) + return overriden if overriden != base_message + end + + @base_matcher.__send__(inverted) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in.rb new file mode 100644 index 0000000000..e6237ff08b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in.rb @@ -0,0 +1,53 @@ +RSpec::Support.require_rspec_matchers "built_in/base_matcher" + +module RSpec + module Matchers + # Container module for all built-in matchers. The matcher classes are here + # (rather than directly under `RSpec::Matchers`) in order to prevent name + # collisions, since `RSpec::Matchers` gets included into the user's namespace. + # + # Autoloading is used to delay when the matcher classes get loaded, allowing + # rspec-matchers to boot faster, and avoiding loading matchers the user is + # not using. + module BuiltIn + autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of' + autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of' + autoload :BeBetween, 'rspec/matchers/built_in/be_between' + autoload :Be, 'rspec/matchers/built_in/be' + autoload :BeComparedTo, 'rspec/matchers/built_in/be' + autoload :BeFalsey, 'rspec/matchers/built_in/be' + autoload :BeHelpers, 'rspec/matchers/built_in/be' + autoload :BeNil, 'rspec/matchers/built_in/be' + autoload :BePredicate, 'rspec/matchers/built_in/has' + autoload :BeTruthy, 'rspec/matchers/built_in/be' + autoload :BeWithin, 'rspec/matchers/built_in/be_within' + autoload :Change, 'rspec/matchers/built_in/change' + autoload :Compound, 'rspec/matchers/built_in/compound' + autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly' + autoload :Cover, 'rspec/matchers/built_in/cover' + autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with' + autoload :Eq, 'rspec/matchers/built_in/eq' + autoload :Eql, 'rspec/matchers/built_in/eql' + autoload :Equal, 'rspec/matchers/built_in/equal' + autoload :Exist, 'rspec/matchers/built_in/exist' + autoload :Has, 'rspec/matchers/built_in/has' + autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes' + autoload :Include, 'rspec/matchers/built_in/include' + autoload :All, 'rspec/matchers/built_in/all' + autoload :Match, 'rspec/matchers/built_in/match' + autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :OperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :Output, 'rspec/matchers/built_in/output' + autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :RaiseError, 'rspec/matchers/built_in/raise_error' + autoload :RespondTo, 'rspec/matchers/built_in/respond_to' + autoload :Satisfy, 'rspec/matchers/built_in/satisfy' + autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with' + autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol' + autoload :YieldControl, 'rspec/matchers/built_in/yield' + autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield' + autoload :YieldWithArgs, 'rspec/matchers/built_in/yield' + autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/all.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/all.rb new file mode 100644 index 0000000000..27cce20d17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/all.rb @@ -0,0 +1,86 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `all`. + # Not intended to be instantiated directly. + class All < BaseMatcher + # @private + attr_reader :matcher, :failed_objects + + def initialize(matcher) + @matcher = matcher + @failed_objects = {} + end + + # @private + def does_not_match?(_actual) + raise NotImplementedError, '`expect().not_to all( matcher )` is not supported.' + end + + # @api private + # @return [String] + def failure_message + unless iterable? + return "#{improve_hash_formatting(super)}, but was not iterable" + end + + all_messages = [improve_hash_formatting(super)] + failed_objects.each do |index, matcher_failure_message| + all_messages << failure_message_for_item(index, matcher_failure_message) + end + all_messages.join("\n\n") + end + + # @api private + # @return [String] + def description + improve_hash_formatting "all #{description_of matcher}" + end + + private + + def match(_expected, _actual) + return false unless iterable? + + index_failed_objects + failed_objects.empty? + end + + def index_failed_objects + actual.each_with_index do |actual_item, index| + cloned_matcher = matcher.clone + matches = cloned_matcher.matches?(actual_item) + failed_objects[index] = cloned_matcher.failure_message unless matches + end + end + + def failure_message_for_item(index, failure_message) + failure_message = indent_multiline_message(add_new_line_if_needed(failure_message)) + indent_multiline_message("object at index #{index} failed to match:#{failure_message}") + end + + def add_new_line_if_needed(message) + message.start_with?("\n") ? message : "\n#{message}" + end + + def indent_multiline_message(message) + message = message.sub(/\n+\z/, '') + message.lines.map do |line| + line =~ /\S/ ? ' ' + line : line + end.join + end + + def initialize_copy(other) + @matcher = @matcher.clone + @failed_objects = @failed_objects.clone + super + end + + def iterable? + @actual.respond_to?(:each_with_index) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/base_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/base_matcher.rb new file mode 100644 index 0000000000..7699c92ac1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/base_matcher.rb @@ -0,0 +1,193 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # + # Used _internally_ as a base class for matchers that ship with + # rspec-expectations and rspec-rails. + # + # ### Warning: + # + # This class is for internal use, and subject to change without notice. + # We strongly recommend that you do not base your custom matchers on this + # class. If/when this changes, we will announce it and remove this warning. + class BaseMatcher + include RSpec::Matchers::Composable + + # @api private + # Used to detect when no arg is passed to `initialize`. + # `nil` cannot be used because it's a valid value to pass. + UNDEFINED = Object.new.freeze + + # @private + attr_reader :actual, :expected, :rescued_exception + + # @private + attr_writer :matcher_name + + def initialize(expected=UNDEFINED) + @expected = expected unless UNDEFINED.equal?(expected) + end + + # @api private + # Indicates if the match is successful. Delegates to `match`, which + # should be defined on a subclass. Takes care of consistently + # initializing the `actual` attribute. + def matches?(actual) + @actual = actual + match(expected, actual) + end + + # @api private + # Used to wrap a block of code that will indicate failure by + # raising one of the named exceptions. + # + # This is used by rspec-rails for some of its matchers that + # wrap rails' assertions. + def match_unless_raises(*exceptions) + exceptions.unshift Exception if exceptions.empty? + begin + yield + true + rescue *exceptions => @rescued_exception + false + end + end + + # @api private + # Generates a description using {EnglishPhrasing}. + # @return [String] + def description + desc = EnglishPhrasing.split_words(self.class.matcher_name) + desc << EnglishPhrasing.list(@expected) if defined?(@expected) + desc + end + + # @api private + # Matchers are not diffable by default. Override this to make your + # subclass diffable. + def diffable? + false + end + + # @api private + # Most matchers are value matchers (i.e. meant to work with `expect(value)`) + # rather than block matchers (i.e. meant to work with `expect { }`), so + # this defaults to false. Block matchers must override this to return true. + def supports_block_expectations? + false + end + + # @api private + def expects_call_stack_jump? + false + end + + # @private + def expected_formatted + RSpec::Support::ObjectFormatter.format(@expected) + end + + # @private + def actual_formatted + RSpec::Support::ObjectFormatter.format(@actual) + end + + # @private + def self.matcher_name + @matcher_name ||= underscore(name.split('::').last) + end + + # @private + def matcher_name + if defined?(@matcher_name) + @matcher_name + else + self.class.matcher_name + end + end + + # @private + # Borrowed from ActiveSupport. + def self.underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + 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 + private_class_method :underscore + + private + + def assert_ivars(*expected_ivars) + return unless (expected_ivars - present_ivars).any? + ivar_list = EnglishPhrasing.list(expected_ivars) + raise "#{self.class.name} needs to supply#{ivar_list}" + end + + if RUBY_VERSION.to_f < 1.9 + # :nocov: + def present_ivars + instance_variables.map(&:to_sym) + end + # :nocov: + else + alias present_ivars instance_variables + end + + # @private + module HashFormatting + # `{ :a => 5, :b => 2 }.inspect` produces: + # + # {:a=>5, :b=>2} + # + # ...but it looks much better as: + # + # {:a => 5, :b => 2} + # + # This is idempotent and safe to run on a string multiple times. + def improve_hash_formatting(inspect_string) + inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2') + end + module_function :improve_hash_formatting + end + + include HashFormatting + + # @api private + # Provides default implementations of failure messages, based on the `description`. + module DefaultFailureMessages + # @api private + # Provides a good generic failure message. Based on `description`. + # When subclassing, if you are not satisfied with this failure message + # you often only need to override `description`. + # @return [String] + def failure_message + "expected #{description_of @actual} to #{description}".dup + end + + # @api private + # Provides a good generic negative failure message. Based on `description`. + # When subclassing, if you are not satisfied with this failure message + # you often only need to override `description`. + # @return [String] + def failure_message_when_negated + "expected #{description_of @actual} not to #{description}".dup + end + + # @private + def self.has_default_failure_messages?(matcher) + matcher.method(:failure_message).owner == self && + matcher.method(:failure_message_when_negated).owner == self + rescue NameError + false + end + end + + include DefaultFailureMessages + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be.rb new file mode 100644 index 0000000000..40d4017181 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be.rb @@ -0,0 +1,191 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_truthy`. + # Not intended to be instantiated directly. + class BeTruthy < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: truthy value\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: falsey value\n got: #{actual_formatted}" + end + + private + + def match(_, actual) + !!actual + end + end + + # @api private + # Provides the implementation for `be_falsey`. + # Not intended to be instantiated directly. + class BeFalsey < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: falsey value\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: truthy value\n got: #{actual_formatted}" + end + + private + + def match(_, actual) + !actual + end + end + + # @api private + # Provides the implementation for `be_nil`. + # Not intended to be instantiated directly. + class BeNil < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: nil\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: not nil\n got: nil" + end + + private + + def match(_, actual) + actual.nil? + end + end + + # @private + module BeHelpers + private + + def args_to_s + @args.empty? ? "" : parenthesize(inspected_args.join(', ')) + end + + def parenthesize(string) + "(#{string})" + end + + def inspected_args + @args.map { |a| RSpec::Support::ObjectFormatter.format(a) } + end + + def expected_to_sentence + EnglishPhrasing.split_words(@expected) + end + + def args_to_sentence + EnglishPhrasing.list(@args) + end + end + + # @api private + # Provides the implementation for `be`. + # Not intended to be instantiated directly. + class Be < BaseMatcher + include BeHelpers + + def initialize(*args) + @args = args + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to evaluate to true" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} to evaluate to false" + end + + [:==, :<, :<=, :>=, :>, :===, :=~].each do |operator| + define_method operator do |operand| + BeComparedTo.new(operand, operator) + end + end + + private + + def match(_, actual) + !!actual + end + end + + # @api private + # Provides the implementation of `be value`. + # Not intended to be instantiated directly. + class BeComparedTo < BaseMatcher + include BeHelpers + + def initialize(operand, operator) + @expected = operand + @operator = operator + @args = [] + end + + def matches?(actual) + perform_match(actual) + rescue ArgumentError, NoMethodError + false + end + + def does_not_match?(actual) + !perform_match(actual) + rescue ArgumentError, NoMethodError + false + end + + # @api private + # @return [String] + def failure_message + "expected: #{@operator} #{expected_formatted}\n" \ + " got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + message = "`expect(#{actual_formatted}).not_to " \ + "be #{@operator} #{expected_formatted}`" + if [:<, :>, :<=, :>=].include?(@operator) + message + " not only FAILED, it is a bit confusing." + else + message + end + end + + # @api private + # @return [String] + def description + "be #{@operator} #{expected_to_sentence}#{args_to_sentence}" + end + + private + + def perform_match(actual) + @actual = actual + @actual.__send__ @operator, @expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_between.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_between.rb new file mode 100644 index 0000000000..55f084e4b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_between.rb @@ -0,0 +1,77 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_between`. + # Not intended to be instantiated directly. + class BeBetween < BaseMatcher + def initialize(min, max) + @min, @max = min, max + inclusive + end + + # @api public + # Makes the between comparison inclusive. + # + # @example + # expect(3).to be_between(2, 3).inclusive + # + # @note The matcher is inclusive by default; this simply provides + # a way to be more explicit about it. + def inclusive + @less_than_operator = :<= + @greater_than_operator = :>= + @mode = :inclusive + self + end + + # @api public + # Makes the between comparison exclusive. + # + # @example + # expect(3).to be_between(2, 4).exclusive + def exclusive + @less_than_operator = :< + @greater_than_operator = :> + @mode = :exclusive + self + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + comparable? && compare + rescue ArgumentError + false + end + + # @api private + # @return [String] + def failure_message + "#{super}#{not_comparable_clause}" + end + + # @api private + # @return [String] + def description + "be between #{description_of @min} and #{description_of @max} (#{@mode})" + end + + private + + def comparable? + @actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator) + end + + def not_comparable_clause + ", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable? + end + + def compare + @actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_instance_of.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_instance_of.rb new file mode 100644 index 0000000000..e71d380a03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_instance_of.rb @@ -0,0 +1,26 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_an_instance_of`. + # Not intended to be instantiated directly. + class BeAnInstanceOf < BaseMatcher + # @api private + # @return [String] + def description + "be an instance of #{expected}" + end + + private + + def match(expected, actual) + actual.instance_of?(expected) + rescue NoMethodError + raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object responds to #instance_of? method " \ + "but a `NoMethodError` was encountered instead." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_kind_of.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_kind_of.rb new file mode 100644 index 0000000000..4fe23bd92e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_kind_of.rb @@ -0,0 +1,20 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_a_kind_of`. + # Not intended to be instantiated directly. + class BeAKindOf < BaseMatcher + private + + def match(expected, actual) + actual.kind_of?(expected) + rescue NoMethodError + raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object responds to #kind_of? method " \ + "but a `NoMethodError` was encountered instead." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_within.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_within.rb new file mode 100644 index 0000000000..7a2b5b5bef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/be_within.rb @@ -0,0 +1,72 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_within`. + # Not intended to be instantiated directly. + class BeWithin < BaseMatcher + def initialize(delta) + @delta = delta + end + + # @api public + # Sets the expected value. + def of(expected) + @expected = expected + @tolerance = @delta + @unit = '' + self + end + + # @api public + # Sets the expected value, and makes the matcher do + # a percent comparison. + def percent_of(expected) + @expected = expected + @tolerance = @expected.abs * @delta / 100.0 + @unit = '%' + self + end + + # @private + def matches?(actual) + @actual = actual + raise needs_expected unless defined? @expected + numeric? && (@actual - @expected).abs <= @tolerance + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to #{description}#{not_numeric_clause}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to #{description}" + end + + # @api private + # @return [String] + def description + "be within #{@delta}#{@unit} of #{expected_formatted}" + end + + private + + def numeric? + @actual.respond_to?(:-) + end + + def needs_expected + ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)" + end + + def not_numeric_clause + ", but it could not be treated as a numeric value" unless numeric? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/change.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/change.rb new file mode 100644 index 0000000000..2641c19a4d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/change.rb @@ -0,0 +1,428 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `change`. + # Not intended to be instantiated directly. + class Change < BaseMatcher + # @api public + # Specifies the delta of the expected change. + def by(expected_delta) + ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta| + values_match?(expected_delta, actual_delta) + end + end + + # @api public + # Specifies a minimum delta of the expected change. + def by_at_least(minimum) + ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta| + actual_delta >= minimum + end + end + + # @api public + # Specifies a maximum delta of the expected change. + def by_at_most(maximum) + ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta| + actual_delta <= maximum + end + end + + # @api public + # Specifies the new value you expect. + def to(value) + ChangeToValue.new(change_details, value) + end + + # @api public + # Specifies the original value. + def from(value) + ChangeFromValue.new(change_details, value) + end + + # @private + def matches?(event_proc) + raise_block_syntax_error if block_given? + perform_change(event_proc) && change_details.changed? + end + + def does_not_match?(event_proc) + raise_block_syntax_error if block_given? + perform_change(event_proc) && !change_details.changed? + end + + # @api private + # @return [String] + def failure_message + "expected #{change_details.value_representation} to have changed, " \ + "but #{positive_failure_reason}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{change_details.value_representation} not to have changed, " \ + "but #{negative_failure_reason}" + end + + # @api private + # @return [String] + def description + "change #{change_details.value_representation}" + end + + # @private + def supports_block_expectations? + true + end + + private + + def initialize(receiver=nil, message=nil, &block) + @receiver = receiver + @message = message + @block = block + end + + def change_details + @change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block) + end + + def perform_change(event_proc) + @event_proc = event_proc + change_details.perform_change(event_proc) do |actual_before| + # pre-compute values derived from the `before` value before the + # mutation is applied, in case the specified mutation is mutation + # of a single object (rather than a changing what object a method + # returns). We need to cache these values before the `before` value + # they are based on potentially gets mutated. + @actual_before_description = description_of(actual_before) + end + end + + def raise_block_syntax_error + raise SyntaxError, "Block not received by the `change` matcher. " \ + "Perhaps you want to use `{ ... }` instead of do/end?" + end + + def positive_failure_reason + return "was not given a block" unless Proc === @event_proc + "is still #{@actual_before_description}" + end + + def negative_failure_reason + return "was not given a block" unless Proc === @event_proc + "did change from #{@actual_before_description} " \ + "to #{description_of change_details.actual_after}" + end + end + + # Used to specify a relative change. + # @api private + class ChangeRelatively < BaseMatcher + def initialize(change_details, expected_delta, relativity, &comparer) + @change_details = change_details + @expected_delta = expected_delta + @relativity = relativity + @comparer = comparer + end + + # @private + def failure_message + "expected #{@change_details.value_representation} to have changed " \ + "#{@relativity.to_s.tr('_', ' ')} " \ + "#{description_of @expected_delta}, but #{failure_reason}" + end + + # @private + def matches?(event_proc) + @event_proc = event_proc + @change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta) + end + + # @private + def does_not_match?(_event_proc) + raise NotImplementedError, "`expect { }.not_to change " \ + "{ }.#{@relativity}()` is not supported" + end + + # @private + def description + "change #{@change_details.value_representation} " \ + "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}" + end + + # @private + def supports_block_expectations? + true + end + + private + + def failure_reason + return "was not given a block" unless Proc === @event_proc + "was changed by #{description_of @change_details.actual_delta}" + end + end + + # @api private + # Base class for specifying a change from and/or to specific values. + class SpecificValuesChange < BaseMatcher + # @private + MATCH_ANYTHING = ::Object.ancestors.last + + def initialize(change_details, from, to) + @change_details = change_details + @expected_before = from + @expected_after = to + end + + # @private + def matches?(event_proc) + perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after? + end + + # @private + def description + "change #{@change_details.value_representation} #{change_description}" + end + + # @private + def failure_message + return not_given_a_block_failure unless Proc === @event_proc + return before_value_failure unless @matches_before + return did_not_change_failure unless @change_details.changed? + after_value_failure + end + + # @private + def supports_block_expectations? + true + end + + private + + def perform_change(event_proc) + @event_proc = event_proc + @change_details.perform_change(event_proc) do |actual_before| + # pre-compute values derived from the `before` value before the + # mutation is applied, in case the specified mutation is mutation + # of a single object (rather than a changing what object a method + # returns). We need to cache these values before the `before` value + # they are based on potentially gets mutated. + @matches_before = values_match?(@expected_before, actual_before) + @actual_before_description = description_of(actual_before) + end + end + + def matches_after? + values_match?(@expected_after, @change_details.actual_after) + end + + def before_value_failure + "expected #{@change_details.value_representation} " \ + "to have initially been #{description_of @expected_before}, " \ + "but was #{@actual_before_description}" + end + + def after_value_failure + "expected #{@change_details.value_representation} " \ + "to have changed to #{description_of @expected_after}, " \ + "but is now #{description_of @change_details.actual_after}" + end + + def did_not_change_failure + "expected #{@change_details.value_representation} " \ + "to have changed #{change_description}, but did not change" + end + + def did_change_failure + "expected #{@change_details.value_representation} not to have changed, but " \ + "did change from #{@actual_before_description} " \ + "to #{description_of @change_details.actual_after}" + end + + def not_given_a_block_failure + "expected #{@change_details.value_representation} to have changed " \ + "#{change_description}, but was not given a block" + end + end + + # @api private + # Used to specify a change from a specific value + # (and, optionally, to a specific value). + class ChangeFromValue < SpecificValuesChange + def initialize(change_details, expected_before) + @description_suffix = nil + super(change_details, expected_before, MATCH_ANYTHING) + end + + # @api public + # Specifies the new value you expect. + def to(value) + @expected_after = value + @description_suffix = " to #{description_of value}" + self + end + + # @private + def does_not_match?(event_proc) + if @description_suffix + raise NotImplementedError, "`expect { }.not_to change { }.to()` " \ + "is not supported" + end + + perform_change(event_proc) && !@change_details.changed? && @matches_before + end + + # @private + def failure_message_when_negated + return not_given_a_block_failure unless Proc === @event_proc + return before_value_failure unless @matches_before + did_change_failure + end + + private + + def change_description + "from #{description_of @expected_before}#{@description_suffix}" + end + end + + # @api private + # Used to specify a change to a specific value + # (and, optionally, from a specific value). + class ChangeToValue < SpecificValuesChange + def initialize(change_details, expected_after) + @description_suffix = nil + super(change_details, MATCH_ANYTHING, expected_after) + end + + # @api public + # Specifies the original value. + def from(value) + @expected_before = value + @description_suffix = " from #{description_of value}" + self + end + + # @private + def does_not_match?(_event_proc) + raise NotImplementedError, "`expect { }.not_to change { }.to()` " \ + "is not supported" + end + + private + + def change_description + "to #{description_of @expected_after}#{@description_suffix}" + end + end + + # @private + # Encapsulates the details of the before/after values. + # + # Note that this class exposes the `actual_after` value, to allow the + # matchers above to derive failure messages, etc from the value on demand + # as needed, but it intentionally does _not_ expose the `actual_before` + # value. Some usages of the `change` matcher mutate a specific object + # returned by the value proc, which means that failure message snippets, + # etc, which are derived from the `before` value may not be accurate if + # they are lazily computed as needed. We must pre-compute them before + # applying the change in the `expect` block. To ensure that all `change` + # matchers do that properly, we do not expose the `actual_before` value. + # Instead, matchers must pass a block to `perform_change`, which yields + # the `actual_before` value before applying the change. + class ChangeDetails + attr_reader :actual_after + + def initialize(matcher_name, receiver=nil, message=nil, &block) + if receiver && !message + raise( + ArgumentError, + "`change` requires either an object and message " \ + "(`change(obj, :msg)`) or a block (`change { }`). " \ + "You passed an object but no message." + ) + end + + @matcher_name = matcher_name + @receiver = receiver + @message = message + @value_proc = block + end + + def value_representation + @value_representation ||= + if @message + "`#{message_notation(@receiver, @message)}`" + elsif (value_block_snippet = extract_value_block_snippet) + "`#{value_block_snippet}`" + else + 'result' + end + end + + def perform_change(event_proc) + @actual_before = evaluate_value_proc + @before_hash = @actual_before.hash + yield @actual_before if block_given? + + return false unless Proc === event_proc + event_proc.call + + @actual_after = evaluate_value_proc + @actual_hash = @actual_after.hash + true + end + + def changed? + # Consider it changed if either: + # + # - The before/after values are unequal + # - The before/after values have different hash values + # + # The latter case specifically handles the case when the value proc + # returns the exact same object, but it has been mutated. + # + # Note that it is not sufficient to only check the hashes; it is + # possible for two values to be unequal (and of different classes) + # but to return the same hash value. Also, some objects may change + # their hash after being compared with `==`/`!=`. + @actual_before != @actual_after || @before_hash != @actual_hash + end + + def actual_delta + @actual_after - @actual_before + end + + private + + def evaluate_value_proc + @value_proc ? @value_proc.call : @receiver.__send__(@message) + end + + def message_notation(receiver, message) + case receiver + when Module + "#{receiver}.#{message}" + else + "#{Support.class_of(receiver)}##{message}" + end + end + + if RSpec::Support::RubyFeatures.ripper_supported? + def extract_value_block_snippet + return nil unless @value_proc + Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name) + end + else + def extract_value_block_snippet + nil + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/compound.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/compound.rb new file mode 100644 index 0000000000..97f05dd61e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/compound.rb @@ -0,0 +1,276 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Base class for `and` and `or` compound matchers. + class Compound < BaseMatcher + # @private + attr_reader :matcher_1, :matcher_2, :evaluator + + def initialize(matcher_1, matcher_2) + @matcher_1 = matcher_1 + @matcher_2 = matcher_2 + end + + # @private + def does_not_match?(_actual) + raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \ + "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \ + "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \ + "use `expect(...).to matcher.#{conjunction} matcher`." + end + + # @api private + # @return [String] + def description + "#{matcher_1.description} #{conjunction} #{matcher_2.description}" + end + + def supports_block_expectations? + matcher_supports_block_expectations?(matcher_1) && + matcher_supports_block_expectations?(matcher_2) + end + + def expects_call_stack_jump? + NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) || + NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2) + end + + # @api private + # @return [Boolean] + def diffable? + matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2) + end + + # @api private + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def expected + return nil unless evaluator + ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list) + end + + protected + + def diffable_matcher_list + list = [] + list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches? + list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches? + list + end + + private + + def initialize_copy(other) + @matcher_1 = @matcher_1.clone + @matcher_2 = @matcher_2.clone + super + end + + def match(_expected, actual) + evaluator_klass = if supports_block_expectations? && Proc === actual + NestedEvaluator + else + SequentialEvaluator + end + + @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2) + end + + def indent_multiline_message(message) + message.lines.map do |line| + line =~ /\S/ ? ' ' + line : line + end.join + end + + def compound_failure_message + "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \ + "\n\n...#{conjunction}:" \ + "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}" + end + + def matcher_1_matches? + evaluator.matcher_matches?(matcher_1) + end + + def matcher_2_matches? + evaluator.matcher_matches?(matcher_2) + end + + def matcher_supports_block_expectations?(matcher) + matcher.supports_block_expectations? + rescue NoMethodError + false + end + + def matcher_is_diffable?(matcher) + matcher.diffable? + rescue NoMethodError + false + end + + def diffable_matcher_list_for(matcher) + return [] unless matcher_is_diffable?(matcher) + return matcher.diffable_matcher_list if Compound === matcher + [matcher] + end + + # For value expectations, we can evaluate the matchers sequentially. + class SequentialEvaluator + def initialize(actual, *) + @actual = actual + end + + def matcher_matches?(matcher) + matcher.matches?(@actual) + end + end + + # Normally, we evaluate the matching sequentially. For an expression like + # `expect(x).to foo.and bar`, this becomes: + # + # expect(x).to foo + # expect(x).to bar + # + # For block expectations, we need to nest them instead, so that + # `expect { x }.to foo.and bar` becomes: + # + # expect { + # expect { x }.to foo + # }.to bar + # + # This is necessary so that the `expect` block is only executed once. + class NestedEvaluator + def initialize(actual, matcher_1, matcher_2) + @actual = actual + @matcher_1 = matcher_1 + @matcher_2 = matcher_2 + @match_results = {} + + inner, outer = order_block_matchers + + @match_results[outer] = outer.matches?(Proc.new do |*args| + @match_results[inner] = inner.matches?(inner_matcher_block(args)) + end) + end + + def matcher_matches?(matcher) + @match_results.fetch(matcher) do + raise ArgumentError, "Your #{matcher.description} has no match " \ + "results, this can occur when an unexpected call stack or " \ + "local jump occurs. Prehaps one of your matchers needs to " \ + "declare `expects_call_stack_jump?` as `true`?" + end + end + + private + + # Some block matchers (such as `yield_xyz`) pass args to the `expect` block. + # When such a matcher is used as the outer matcher, we need to forward the + # the args on to the `expect` block. + def inner_matcher_block(outer_args) + return @actual if outer_args.empty? + + Proc.new do |*inner_args| + unless inner_args.empty? + raise ArgumentError, "(#{@matcher_1.description}) and " \ + "(#{@matcher_2.description}) cannot be combined in a compound expectation " \ + "since both matchers pass arguments to the block." + end + + @actual.call(*outer_args) + end + end + + # For a matcher like `raise_error` or `throw_symbol`, where the block will jump + # up the call stack, we need to order things so that it is the inner matcher. + # For example, we need it to be this: + # + # expect { + # expect { + # x += 1 + # raise "boom" + # }.to raise_error("boom") + # }.to change { x }.by(1) + # + # ...rather than: + # + # expect { + # expect { + # x += 1 + # raise "boom" + # }.to change { x }.by(1) + # }.to raise_error("boom") + # + # In the latter case, the after-block logic in the `change` matcher would never + # get executed because the `raise "boom"` line would jump to the `rescue` in the + # `raise_error` logic, so only the former case will work properly. + # + # This method figures out which matcher should be the inner matcher and which + # should be the outer matcher. + def order_block_matchers + return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2) + return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1) + + raise ArgumentError, "(#{@matcher_1.description}) and " \ + "(#{@matcher_2.description}) cannot be combined in a compound expectation " \ + "because they both expect a call stack jump." + end + + def self.matcher_expects_call_stack_jump?(matcher) + matcher.expects_call_stack_jump? + rescue NoMethodError + false + end + end + + # @api public + # Matcher used to represent a compound `and` expectation. + class And < self + # @api private + # @return [String] + def failure_message + if matcher_1_matches? + matcher_2.failure_message + elsif matcher_2_matches? + matcher_1.failure_message + else + compound_failure_message + end + end + + private + + def match(*) + super + matcher_1_matches? && matcher_2_matches? + end + + def conjunction + "and" + end + end + + # @api public + # Matcher used to represent a compound `or` expectation. + class Or < self + # @api private + # @return [String] + def failure_message + compound_failure_message + end + + private + + def match(*) + super + matcher_1_matches? || matcher_2_matches? + end + + def conjunction + "or" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/contain_exactly.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/contain_exactly.rb new file mode 100644 index 0000000000..1bf7f98310 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/contain_exactly.rb @@ -0,0 +1,302 @@ +module RSpec + module Matchers + module BuiltIn + # rubocop:disable ClassLength + # @api private + # Provides the implementation for `contain_exactly` and `match_array`. + # Not intended to be instantiated directly. + class ContainExactly < BaseMatcher + # @api private + # @return [String] + def failure_message + if Array === actual + generate_failure_message + else + "expected a collection that can be converted to an array with " \ + "`#to_ary` or `#to_a`, but got #{actual_formatted}" + end + end + + # @api private + # @return [String] + def failure_message_when_negated + list = EnglishPhrasing.list(surface_descriptions_in(expected)) + "expected #{actual_formatted} not to contain exactly#{list}" + end + + # @api private + # @return [String] + def description + list = EnglishPhrasing.list(surface_descriptions_in(expected)) + "contain exactly#{list}" + end + + private + + def generate_failure_message + message = expected_collection_line + message += actual_collection_line + message += missing_elements_line unless missing_items.empty? + message += extra_elements_line unless extra_items.empty? + message + end + + def expected_collection_line + message_line('expected collection contained', expected, true) + end + + def actual_collection_line + message_line('actual collection contained', actual) + end + + def missing_elements_line + message_line('the missing elements were', missing_items, true) + end + + def extra_elements_line + message_line('the extra elements were', extra_items) + end + + def describe_collection(collection, surface_descriptions=false) + if surface_descriptions + "#{description_of(safe_sort(surface_descriptions_in collection))}\n" + else + "#{description_of(safe_sort(collection))}\n" + end + end + + def message_line(prefix, collection, surface_descriptions=false) + "%-32s%s" % [prefix + ':', + describe_collection(collection, surface_descriptions)] + end + + def match(_expected, _actual) + return false unless convert_actual_to_an_array + match_when_sorted? || (extra_items.empty? && missing_items.empty?) + end + + # This cannot always work (e.g. when dealing with unsortable items, + # or matchers as expected items), but it's practically free compared to + # the slowness of the full matching algorithm, and in common cases this + # works, so it's worth a try. + def match_when_sorted? + values_match?(safe_sort(expected), safe_sort(actual)) + end + + def convert_actual_to_an_array + if actual.respond_to?(:to_ary) + @actual = actual.to_ary + elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual) + @actual = actual.to_a + else + false + end + end + + def safe_sort(array) + array.sort + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + array + end + + if RUBY_VERSION == "1.8.7" + def to_a_disallowed?(object) + case object + when NilClass, String then true + else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner + end + end + else + def to_a_disallowed?(object) + NilClass === object + end + end + + def missing_items + @missing_items ||= best_solution.unmatched_expected_indexes.map do |index| + expected[index] + end + end + + def extra_items + @extra_items ||= best_solution.unmatched_actual_indexes.map do |index| + actual[index] + end + end + + def best_solution + @best_solution ||= pairings_maximizer.find_best_solution + end + + def pairings_maximizer + @pairings_maximizer ||= begin + expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }] + actual_matches = Hash[Array.new(actual.size) { |i| [i, []] }] + + expected.each_with_index do |e, ei| + actual.each_with_index do |a, ai| + next unless values_match?(e, a) + + expected_matches[ei] << ai + actual_matches[ai] << ei + end + end + + PairingsMaximizer.new(expected_matches, actual_matches) + end + end + + # Once we started supporting composing matchers, the algorithm for this matcher got + # much more complicated. Consider this expression: + # + # expect(["fool", "food"]).to contain_exactly(/foo/, /fool/) + # + # This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but + # the original algorithm used by this matcher would pair the first elements it could + # (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have + # an expected element which is a matcher that matches a superset of actual items + # compared to another expected element matcher, we need to consider every possible pairing. + # + # This class is designed to maximize the number of actual/expected pairings -- or, + # conversely, to minimize the number of unpaired items. It's essentially a brute + # force solution, but with a few heuristics applied to reduce the size of the + # problem space: + # + # * Any items which match none of the items in the other list are immediately + # placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array. + # The extra items and missing items in the matcher failure message are derived + # from these arrays. + # * Any items which reciprocally match only each other are paired up and not + # considered further. + # + # What's left is only the items which match multiple items from the other list + # (or vice versa). From here, it performs a brute-force depth-first search, + # looking for a solution which pairs all elements in both lists, or, barring that, + # that produces the fewest unmatched items. + # + # @private + class PairingsMaximizer + # @private + Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes, + :indeterminate_expected_indexes, :indeterminate_actual_indexes) do + def worse_than?(other) + unmatched_item_count > other.unmatched_item_count + end + + def candidate? + indeterminate_expected_indexes.empty? && + indeterminate_actual_indexes.empty? + end + + def ideal? + candidate? && ( + unmatched_expected_indexes.empty? || + unmatched_actual_indexes.empty? + ) + end + + def unmatched_item_count + unmatched_expected_indexes.count + unmatched_actual_indexes.count + end + + def +(derived_candidate_solution) + self.class.new( + unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes, + unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes, + # Ignore the indeterminate indexes: by the time we get here, + # we've dealt with all indeterminates. + [], [] + ) + end + end + + attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution + + def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes) + @expected_to_actual_matched_indexes = expected_to_actual_matched_indexes + @actual_to_expected_matched_indexes = actual_to_expected_matched_indexes + + unmatched_expected_indexes, indeterminate_expected_indexes = + categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes) + + unmatched_actual_indexes, indeterminate_actual_indexes = + categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes) + + @solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes, + indeterminate_expected_indexes, indeterminate_actual_indexes) + end + + def find_best_solution + return solution if solution.candidate? + best_solution_so_far = NullSolution + + expected_index = solution.indeterminate_expected_indexes.first + actuals = expected_to_actual_matched_indexes[expected_index] + + actuals.each do |actual_index| + solution = best_solution_for_pairing(expected_index, actual_index) + return solution if solution.ideal? + best_solution_so_far = solution if best_solution_so_far.worse_than?(solution) + end + + best_solution_so_far + end + + private + + # @private + # Starting solution that is worse than any other real solution. + NullSolution = Class.new do + def self.worse_than?(_other) + true + end + end + + def categorize_indexes(indexes_to_categorize, other_indexes) + unmatched = [] + indeterminate = [] + + indexes_to_categorize.each_pair do |index, matches| + if matches.empty? + unmatched << index + elsif !reciprocal_single_match?(matches, index, other_indexes) + indeterminate << index + end + end + + return unmatched, indeterminate + end + + def reciprocal_single_match?(matches, index, other_list) + return false unless matches.one? + other_list[matches.first] == [index] + end + + def best_solution_for_pairing(expected_index, actual_index) + modified_expecteds = apply_pairing_to( + solution.indeterminate_expected_indexes, + expected_to_actual_matched_indexes, actual_index) + + modified_expecteds.delete(expected_index) + + modified_actuals = apply_pairing_to( + solution.indeterminate_actual_indexes, + actual_to_expected_matched_indexes, expected_index) + + modified_actuals.delete(actual_index) + + solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution + end + + def apply_pairing_to(indeterminates, original_matches, other_list_index) + indeterminates.inject({}) do |accum, index| + accum[index] = original_matches[index] - [other_list_index] + accum + end + end + end + end + # rubocop:enable ClassLength + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/count_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/count_expectation.rb new file mode 100644 index 0000000000..32272abf6e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/count_expectation.rb @@ -0,0 +1,169 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Asbtract class to implement `once`, `at_least` and other + # count constraints. + module CountExpectation + # @api public + # Specifies that the method is expected to match once. + def once + exactly(1) + end + + # @api public + # Specifies that the method is expected to match twice. + def twice + exactly(2) + end + + # @api public + # Specifies that the method is expected to match thrice. + def thrice + exactly(3) + end + + # @api public + # Specifies that the method is expected to match the given number of times. + def exactly(number) + set_expected_count(:==, number) + self + end + + # @api public + # Specifies the maximum number of times the method is expected to match + def at_most(number) + set_expected_count(:<=, number) + self + end + + # @api public + # Specifies the minimum number of times the method is expected to match + def at_least(number) + set_expected_count(:>=, number) + self + end + + # @api public + # No-op. Provides syntactic sugar. + def times + self + end + + protected + # @api private + attr_reader :count_expectation_type, :expected_count + + private + + if RUBY_VERSION.to_f > 1.8 + def cover?(count, number) + count.cover?(number) + end + else + def cover?(count, number) + number >= count.first && number <= count.last + end + end + + def expected_count_matches?(actual_count) + @actual_count = actual_count + return @actual_count > 0 unless count_expectation_type + return cover?(expected_count, actual_count) if count_expectation_type == :<=> + + @actual_count.__send__(count_expectation_type, expected_count) + end + + def has_expected_count? + !!count_expectation_type + end + + def set_expected_count(relativity, n) + raise_unsupported_count_expectation if unsupported_count_expectation?(relativity) + + count = count_constraint_to_number(n) + + if count_expectation_type == :<= && relativity == :>= + raise_impossible_count_expectation(count) if count > expected_count + @count_expectation_type = :<=> + @expected_count = count..expected_count + elsif count_expectation_type == :>= && relativity == :<= + raise_impossible_count_expectation(count) if count < expected_count + @count_expectation_type = :<=> + @expected_count = expected_count..count + else + @count_expectation_type = relativity + @expected_count = count + end + end + + def raise_impossible_count_expectation(count) + text = + case count_expectation_type + when :<= then "at_least(#{count}).at_most(#{expected_count})" + when :>= then "at_least(#{expected_count}).at_most(#{count})" + end + raise ArgumentError, "The constraint #{text} is not possible" + end + + def raise_unsupported_count_expectation + text = + case count_expectation_type + when :<= then "at_least" + when :>= then "at_most" + when :<=> then "at_least/at_most combination" + else "count" + end + raise ArgumentError, "Multiple #{text} constraints are not supported" + end + + def count_constraint_to_number(n) + case n + when Numeric then n + when :once then 1 + when :twice then 2 + when :thrice then 3 + else + raise ArgumentError, "Expected a number, :once, :twice or :thrice," \ + " but got #{n}" + end + end + + def unsupported_count_expectation?(relativity) + return true if count_expectation_type == :== + return true if count_expectation_type == :<=> + (count_expectation_type == :<= && relativity == :<=) || + (count_expectation_type == :>= && relativity == :>=) + end + + def count_expectation_description + "#{human_readable_expectation_type}#{human_readable_count(expected_count)}" + end + + def count_failure_reason(action) + "#{count_expectation_description}" \ + " but #{action}#{human_readable_count(@actual_count)}" + end + + def human_readable_expectation_type + case count_expectation_type + when :<= then ' at most' + when :>= then ' at least' + when :<=> then ' between' + else '' + end + end + + def human_readable_count(count) + case count + when Range then " #{count.first} and #{count.last} times" + when nil then '' + when 1 then ' once' + when 2 then ' twice' + else " #{count} times" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/cover.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/cover.rb new file mode 100644 index 0000000000..47474a2c8c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/cover.rb @@ -0,0 +1,24 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `cover`. + # Not intended to be instantiated directly. + class Cover < BaseMatcher + def initialize(*expected) + @expected = expected + end + + def matches?(range) + @actual = range + @expected.all? { |e| range.cover?(e) } + end + + def does_not_match?(range) + @actual = range + expected.none? { |e| range.cover?(e) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eq.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eq.rb new file mode 100644 index 0000000000..f0c804a342 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eq.rb @@ -0,0 +1,40 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `eq`. + # Not intended to be instantiated directly. + class Eq < BaseMatcher + # @api private + # @return [String] + def failure_message + "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" + end + + # @api private + # @return [String] + def failure_message_when_negated + "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" + end + + # @api private + # @return [String] + def description + "eq #{expected_formatted}" + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + private + + def match(expected, actual) + actual == expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eql.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eql.rb new file mode 100644 index 0000000000..b1ec6fc4be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/eql.rb @@ -0,0 +1,34 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `eql`. + # Not intended to be instantiated directly. + class Eql < BaseMatcher + # @api private + # @return [String] + def failure_message + "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" + end + + # @api private + # @return [String] + def failure_message_when_negated + "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + private + + def match(expected, actual) + actual.eql? expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/equal.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/equal.rb new file mode 100644 index 0000000000..bbab3ed162 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/equal.rb @@ -0,0 +1,81 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `equal`. + # Not intended to be instantiated directly. + class Equal < BaseMatcher + # @api private + # @return [String] + def failure_message + if expected_is_a_literal_singleton? + simple_failure_message + else + detailed_failure_message + end + end + + # @api private + # @return [String] + def failure_message_when_negated + <<-MESSAGE + +expected not #{inspect_object(actual)} + got #{inspect_object(expected)} + +Compared using equal?, which compares object identity. + +MESSAGE + end + + # @api private + # @return [Boolean] + def diffable? + !expected_is_a_literal_singleton? + end + + private + + def match(expected, actual) + actual.equal? expected + end + + LITERAL_SINGLETONS = [true, false, nil] + + def expected_is_a_literal_singleton? + LITERAL_SINGLETONS.include?(expected) + end + + def actual_inspected + if LITERAL_SINGLETONS.include?(actual) + actual_formatted + else + inspect_object(actual) + end + end + + def simple_failure_message + "\nexpected #{expected_formatted}\n got #{actual_inspected}\n" + end + + def detailed_failure_message + <<-MESSAGE + +expected #{inspect_object(expected)} + got #{inspect_object(actual)} + +Compared using equal?, which compares object identity, +but expected and actual are not the same object. Use +`expect(actual).to eq(expected)` if you don't care about +object identity in this example. + +MESSAGE + end + + def inspect_object(o) + "#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/exist.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/exist.rb new file mode 100644 index 0000000000..b36e16b033 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/exist.rb @@ -0,0 +1,90 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `exist`. + # Not intended to be instantiated directly. + class Exist < BaseMatcher + def initialize(*expected) + @expected = expected + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + @test = ExistenceTest.new @actual, @expected + @test.valid_test? && @test.actual_exists? + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + @actual = actual + @test = ExistenceTest.new @actual, @expected + @test.valid_test? && !@test.actual_exists? + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to exist#{@test.validity_message}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to exist#{@test.validity_message}" + end + + # @api private + # Simple class for memoizing actual/expected for this matcher + # and examining the match + class ExistenceTest < Struct.new(:actual, :expected) + # @api private + # @return [Boolean] + def valid_test? + uniq_truthy_values.size == 1 + end + + # @api private + # @return [Boolean] + def actual_exists? + existence_values.first + end + + # @api private + # @return [String] + def validity_message + case uniq_truthy_values.size + when 0 + " but it does not respond to either `exist?` or `exists?`" + when 2 + " but `exist?` and `exists?` returned different values:\n\n"\ + " exist?: #{existence_values.first}\n"\ + "exists?: #{existence_values.last}" + end + end + + private + + def uniq_truthy_values + @uniq_truthy_values ||= existence_values.map { |v| !!v }.uniq + end + + def existence_values + @existence_values ||= predicates.map { |p| actual.__send__(p, *expected) } + end + + def predicates + @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) } + end + + def deprecated(predicate, actual) + predicate == :exists? && File == actual + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/has.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/has.rb new file mode 100644 index 0000000000..5036a64058 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/has.rb @@ -0,0 +1,167 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for dynamic predicate matchers. + # Not intended to be inherited directly. + class DynamicPredicate < BaseMatcher + include BeHelpers + + def initialize(method_name, *args, &block) + @method_name, @args, @block = method_name, args, block + end + ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true) + + # @private + def matches?(actual, &block) + @actual = actual + @block ||= block + predicate_accessible? && predicate_matches? + end + + # @private + def does_not_match?(actual, &block) + @actual = actual + @block ||= block + predicate_accessible? && predicate_matches?(false) + end + + # @api private + # @return [String] + def failure_message + failure_message_expecting(true) + end + + # @api private + # @return [String] + def failure_message_when_negated + failure_message_expecting(false) + end + + # @api private + # @return [String] + def description + "#{method_description}#{args_to_sentence}" + end + + private + + def predicate_accessible? + @actual.respond_to? predicate + end + + # support 1.8.7, evaluate once at load time for performance + if String === methods.first + # :nocov: + def private_predicate? + @actual.private_methods.include? predicate.to_s + end + # :nocov: + else + def private_predicate? + @actual.private_methods.include? predicate + end + end + + def predicate_result + @predicate_result = actual.__send__(predicate_method_name, *@args, &@block) + end + + def predicate_method_name + predicate + end + + def predicate_matches?(value=true) + if RSpec::Expectations.configuration.strict_predicate_matchers? + value == predicate_result + else + value == !!predicate_result + end + end + + def root + # On 1.9, there appears to be a bug where String#match can return `false` + # rather than the match data object. Changing to Regex#match appears to + # work around this bug. For an example of this bug, see: + # https://travis-ci.org/rspec/rspec-expectations/jobs/27549635 + self.class::REGEX.match(@method_name.to_s).captures.first + end + + def method_description + EnglishPhrasing.split_words(@method_name) + end + + def failure_message_expecting(value) + validity_message || + "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to #{expectation_of value}, got #{description_of @predicate_result}" + end + + def expectation_of(value) + if RSpec::Expectations.configuration.strict_predicate_matchers? + "return #{value}" + elsif value + "be truthy" + else + "be falsey" + end + end + + def validity_message + return nil if predicate_accessible? + + "expected #{actual_formatted} to respond to `#{predicate}`#{failure_to_respond_explanation}" + end + + def failure_to_respond_explanation + if private_predicate? + " but `#{predicate}` is a private method" + end + end + end + + # @api private + # Provides the implementation for `has_`. + # Not intended to be instantiated directly. + class Has < DynamicPredicate + # :nodoc: + REGEX = Matchers::HAS_REGEX + private + def predicate + @predicate ||= :"has_#{root}?" + end + end + + # @api private + # Provides the implementation of `be_`. + # Not intended to be instantiated directly. + class BePredicate < DynamicPredicate + # :nodoc: + REGEX = Matchers::BE_PREDICATE_REGEX + private + def predicate + @predicate ||= :"#{root}?" + end + + def predicate_method_name + actual.respond_to?(predicate) ? predicate : present_tense_predicate + end + + def failure_to_respond_explanation + super || if predicate == :true? + " or perhaps you meant `be true` or `be_truthy`" + elsif predicate == :false? + " or perhaps you meant `be false` or `be_falsey`" + end + end + + def predicate_accessible? + super || actual.respond_to?(present_tense_predicate) + end + + def present_tense_predicate + :"#{root}s?" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/have_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/have_attributes.rb new file mode 100644 index 0000000000..89be3f2e3d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/have_attributes.rb @@ -0,0 +1,114 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `have_attributes`. + # Not intended to be instantiated directly. + class HaveAttributes < BaseMatcher + # @private + attr_reader :respond_to_failed + + def initialize(expected) + @expected = expected + @values = {} + @respond_to_failed = false + @negated = false + end + + # @private + def actual + @values + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + @negated = false + return false unless respond_to_attributes? + perform_match(:all?) + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + @actual = actual + @negated = true + return false unless respond_to_attributes? + perform_match(:none?) + end + + # @api private + # @return [String] + def description + described_items = surface_descriptions_in(expected) + improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}" + end + + # @api private + # @return [Boolean] + def diffable? + !@respond_to_failed && !@negated + end + + # @api private + # @return [String] + def failure_message + respond_to_failure_message_or do + "expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }" + end + end + + # @api private + # @return [String] + def failure_message_when_negated + respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" } + end + + private + + def cache_all_values + @values = {} + expected.each do |attribute_key, _attribute_value| + actual_value = @actual.__send__(attribute_key) + @values[attribute_key] = actual_value + end + end + + def perform_match(predicate) + cache_all_values + expected.__send__(predicate) do |attribute_key, attribute_value| + actual_has_attribute?(attribute_key, attribute_value) + end + end + + def actual_has_attribute?(attribute_key, attribute_value) + values_match?(attribute_value, @values.fetch(attribute_key)) + end + + def respond_to_attributes? + matches = respond_to_matcher.matches?(@actual) + @respond_to_failed = !matches + matches + end + + def respond_to_matcher + @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! } + end + + def respond_to_failure_message_or + if respond_to_failed + respond_to_matcher.failure_message + else + improve_hash_formatting(yield) + end + end + + def formatted_values + values = RSpec::Support::ObjectFormatter.format(@values) + improve_hash_formatting(values) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/include.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/include.rb new file mode 100644 index 0000000000..5a0d697f09 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/include.rb @@ -0,0 +1,206 @@ +require 'rspec/matchers/built_in/count_expectation' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `include`. + # Not intended to be instantiated directly. + class Include < BaseMatcher # rubocop:disable Metrics/ClassLength + include CountExpectation + # @private + attr_reader :expecteds + + # @api private + def initialize(*expecteds) + @expecteds = expecteds + end + + # @api private + # @return [Boolean] + def matches?(actual) + check_actual?(actual) && + if check_expected_count? + expected_count_matches?(count_inclusions) + else + perform_match { |v| v } + end + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + check_actual?(actual) && + if check_expected_count? + !expected_count_matches?(count_inclusions) + else + perform_match { |v| !v } + end + end + + # @api private + # @return [String] + def description + improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}") + end + + # @api private + # @return [String] + def failure_message + format_failure_message("to") { super } + end + + # @api private + # @return [String] + def failure_message_when_negated + format_failure_message("not to") { super } + end + + # @api private + # @return [Boolean] + def diffable? + !diff_would_wrongly_highlight_matched_item? + end + + # @api private + # @return [Array, Hash] + def expected + if expecteds.one? && Hash === expecteds.first + expecteds.first + else + expecteds + end + end + + private + + def check_actual?(actual) + actual = actual.to_hash if convert_to_hash?(actual) + @actual = actual + @actual.respond_to?(:include?) + end + + def check_expected_count? + case + when !has_expected_count? + return false + when expecteds.size != 1 + raise NotImplementedError, 'Count constraint supported only when testing for a single value being included' + when actual.is_a?(Hash) + raise NotImplementedError, 'Count constraint on hash keys not implemented' + end + true + end + + def format_failure_message(preposition) + msg = if actual.respond_to?(:include?) + "expected #{description_of @actual} #{preposition}" \ + " include#{readable_list_of @divergent_items}" \ + "#{count_failure_reason('it is included') if has_expected_count?}" + else + "#{yield}, but it does not respond to `include?`" + end + improve_hash_formatting(msg) + end + + def readable_list_of(items) + described_items = surface_descriptions_in(items) + if described_items.all? { |item| item.is_a?(Hash) } + " #{described_items.inject(:merge).inspect}" + else + EnglishPhrasing.list(described_items) + end + end + + def perform_match(&block) + @divergent_items = excluded_from_actual(&block) + @divergent_items.empty? + end + + def excluded_from_actual + return [] unless @actual.respond_to?(:include?) + + expecteds.inject([]) do |memo, expected_item| + if comparing_hash_to_a_subset?(expected_item) + expected_item.each do |(key, value)| + memo << { key => value } unless yield actual_hash_includes?(key, value) + end + elsif comparing_hash_keys?(expected_item) + memo << expected_item unless yield actual_hash_has_key?(expected_item) + else + memo << expected_item unless yield actual_collection_includes?(expected_item) + end + memo + end + end + + def comparing_hash_to_a_subset?(expected_item) + actual.is_a?(Hash) && expected_item.is_a?(Hash) + end + + def actual_hash_includes?(expected_key, expected_value) + actual_value = + actual.fetch(expected_key) do + actual.find(Proc.new { return false }) { |actual_key, _| values_match?(expected_key, actual_key) }[1] + end + values_match?(expected_value, actual_value) + end + + def comparing_hash_keys?(expected_item) + actual.is_a?(Hash) && !expected_item.is_a?(Hash) + end + + def actual_hash_has_key?(expected_key) + # We check `key?` first for perf: + # `key?` is O(1), but `any?` is O(N). + actual.key?(expected_key) || + actual.keys.any? { |key| values_match?(expected_key, key) } + end + + def actual_collection_includes?(expected_item) + return true if actual.include?(expected_item) + + # String lacks an `any?` method... + return false unless actual.respond_to?(:any?) + + actual.any? { |value| values_match?(expected_item, value) } + end + + if RUBY_VERSION < '1.9' + def count_enumerable(expected_item) + actual.select { |value| values_match?(expected_item, value) }.size + end + else + def count_enumerable(expected_item) + actual.count { |value| values_match?(expected_item, value) } + end + end + + def count_inclusions + @divergent_items = expected + case actual + when String + actual.scan(expected.first).length + when Enumerable + count_enumerable(expected.first) + else + raise NotImplementedError, 'Count constraints are implemented for Enumerable and String values only' + end + end + + def diff_would_wrongly_highlight_matched_item? + return false unless actual.is_a?(String) && expected.is_a?(Array) + + lines = actual.split("\n") + expected.any? do |str| + actual.include?(str) && lines.none? { |line| line == str } + end + end + + def convert_to_hash?(obj) + !obj.respond_to?(:include?) && obj.respond_to?(:to_hash) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/match.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/match.rb new file mode 100644 index 0000000000..9ed4b068cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/match.rb @@ -0,0 +1,106 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `match`. + # Not intended to be instantiated directly. + class Match < BaseMatcher + def initialize(expected) + super(expected) + + @expected_captures = nil + end + # @api private + # @return [String] + def description + if @expected_captures && @expected.match(actual) + "match #{surface_descriptions_in(expected).inspect} with captures #{surface_descriptions_in(@expected_captures).inspect}" + else + "match #{surface_descriptions_in(expected).inspect}" + end + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + # Used to specify the captures we match against + # @return [self] + def with_captures(*captures) + @expected_captures = captures + self + end + + private + + def match(expected, actual) + return match_captures(expected, actual) if @expected_captures + return true if values_match?(expected, actual) + return false unless can_safely_call_match?(expected, actual) + actual.match(expected) + end + + def can_safely_call_match?(expected, actual) + return false unless actual.respond_to?(:match) + + !(RSpec::Matchers.is_a_matcher?(expected) && + (String === actual || Regexp === actual)) + end + + def match_captures(expected, actual) + match = actual.match(expected) + if match + match = ReliableMatchData.new(match) + if match.names.empty? + values_match?(@expected_captures, match.captures) + else + expected_matcher = @expected_captures.last + values_match?(expected_matcher, Hash[match.names.zip(match.captures)]) || + values_match?(expected_matcher, Hash[match.names.map(&:to_sym).zip(match.captures)]) || + values_match?(@expected_captures, match.captures) + end + else + false + end + end + end + + # @api private + # Used to wrap match data and make it reliable for 1.8.7 + class ReliableMatchData + def initialize(match_data) + @match_data = match_data + end + + if RUBY_VERSION == "1.8.7" + # @api private + # Returns match data names for named captures + # @return Array + def names + [] + end + else + # @api private + # Returns match data names for named captures + # @return Array + def names + match_data.names + end + end + + # @api private + # returns an array of captures from the match data + # @return Array + def captures + match_data.captures + end + + protected + + attr_reader :match_data + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/operators.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/operators.rb new file mode 100644 index 0000000000..64f8f3b239 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/operators.rb @@ -0,0 +1,128 @@ +require 'rspec/support' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for operator matchers. + # Not intended to be instantiated directly. + # Only available for use with `should`. + class OperatorMatcher + class << self + # @private + def registry + @registry ||= {} + end + + # @private + def register(klass, operator, matcher) + registry[klass] ||= {} + registry[klass][operator] = matcher + end + + # @private + def unregister(klass, operator) + registry[klass] && registry[klass].delete(operator) + end + + # @private + def get(klass, operator) + klass.ancestors.each do |ancestor| + matcher = registry[ancestor] && registry[ancestor][operator] + return matcher if matcher + end + + nil + end + end + + register Enumerable, '=~', BuiltIn::ContainExactly + + def initialize(actual) + @actual = actual + end + + # @private + def self.use_custom_matcher_or_delegate(operator) + define_method(operator) do |expected| + if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator)) + @actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected)) + else + eval_match(@actual, operator, expected) + end + end + + negative_operator = operator.sub(/^=/, '!') + if negative_operator != operator && respond_to?(negative_operator) + define_method(negative_operator) do |_expected| + opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method + raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \ + "Use `#{opposite_should} #{operator} expected` instead." + end + end + end + + ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator| + use_custom_matcher_or_delegate operator + end + + # @private + def fail_with_message(message) + RSpec::Expectations.fail_with(message, @expected, @actual) + end + + # @api private + # @return [String] + def description + "#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}" + end + + private + + def has_non_generic_implementation_of?(op) + Support.method_handle_for(@actual, op).owner != ::Kernel + rescue NameError + false + end + + def eval_match(actual, operator, expected) + ::RSpec::Matchers.last_matcher = self + @operator, @expected = operator, expected + __delegate_operator(actual, operator, expected) + end + end + + # @private + # Handles operator matcher for `should`. + class PositiveOperatorMatcher < OperatorMatcher + def __delegate_operator(actual, operator, expected) + if actual.__send__(operator, expected) + true + else + expected_formatted = RSpec::Support::ObjectFormatter.format(expected) + actual_formatted = RSpec::Support::ObjectFormatter.format(actual) + + if ['==', '===', '=~'].include?(operator) + fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})") + else + fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") + end + end + end + end + + # @private + # Handles operator matcher for `should_not`. + class NegativeOperatorMatcher < OperatorMatcher + def __delegate_operator(actual, operator, expected) + return false unless actual.__send__(operator, expected) + + expected_formatted = RSpec::Support::ObjectFormatter.format(expected) + actual_formatted = RSpec::Support::ObjectFormatter.format(actual) + + fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/output.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/output.rb new file mode 100644 index 0000000000..be100a26ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/output.rb @@ -0,0 +1,200 @@ +require 'stringio' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `output`. + # Not intended to be instantiated directly. + class Output < BaseMatcher + def initialize(expected) + @expected = expected + @actual = "" + @block = nil + @stream_capturer = NullCapture + end + + def matches?(block) + @block = block + return false unless Proc === block + @actual = @stream_capturer.capture(block) + @expected ? values_match?(@expected, @actual) : captured? + end + + def does_not_match?(block) + !matches?(block) && Proc === block + end + + # @api public + # Tells the matcher to match against stdout. + # Works only when the main Ruby process prints to stdout + def to_stdout + @stream_capturer = CaptureStdout + self + end + + # @api public + # Tells the matcher to match against stderr. + # Works only when the main Ruby process prints to stderr + def to_stderr + @stream_capturer = CaptureStderr + self + end + + # @api public + # Tells the matcher to match against stdout. + # Works when subprocesses print to stdout as well. + # This is significantly (~30x) slower than `to_stdout` + def to_stdout_from_any_process + @stream_capturer = CaptureStreamToTempfile.new("stdout", $stdout) + self + end + + # @api public + # Tells the matcher to match against stderr. + # Works when subprocesses print to stderr as well. + # This is significantly (~30x) slower than `to_stderr` + def to_stderr_from_any_process + @stream_capturer = CaptureStreamToTempfile.new("stderr", $stderr) + self + end + + # @api private + # @return [String] + def failure_message + "expected block to #{description}, but #{positive_failure_reason}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected block to not #{description}, but #{negative_failure_reason}" + end + + # @api private + # @return [String] + def description + if @expected + "output #{description_of @expected} to #{@stream_capturer.name}" + else + "output to #{@stream_capturer.name}" + end + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + # @api private + # Indicates this matcher matches against a block. + # @return [True] + def supports_block_expectations? + true + end + + private + + def captured? + @actual.length > 0 + end + + def positive_failure_reason + return "was not a block" unless Proc === @block + return "output #{actual_output_description}" if @expected + "did not" + end + + def negative_failure_reason + return "was not a block" unless Proc === @block + "output #{actual_output_description}" + end + + def actual_output_description + return "nothing" unless captured? + actual_formatted + end + end + + # @private + module NullCapture + def self.name + "some stream" + end + + def self.capture(_block) + raise "You must chain `to_stdout` or `to_stderr` off of the `output(...)` matcher." + end + end + + # @private + module CaptureStdout + def self.name + 'stdout' + end + + def self.capture(block) + captured_stream = StringIO.new + + original_stream = $stdout + $stdout = captured_stream + + block.call + + captured_stream.string + ensure + $stdout = original_stream + end + end + + # @private + module CaptureStderr + def self.name + 'stderr' + end + + def self.capture(block) + captured_stream = StringIO.new + + original_stream = $stderr + $stderr = captured_stream + + block.call + + captured_stream.string + ensure + $stderr = original_stream + end + end + + # @private + class CaptureStreamToTempfile < Struct.new(:name, :stream) + def capture(block) + # We delay loading tempfile until it is actually needed because + # we want to minimize stdlibs loaded so that users who use a + # portion of the stdlib can't have passing specs while forgetting + # to load it themselves. `CaptureStreamToTempfile` is rarely used + # and `tempfile` pulls in a bunch of things (delegate, tmpdir, + # thread, fileutils, etc), so it's worth delaying it until this point. + require 'tempfile' + + original_stream = stream.clone + captured_stream = Tempfile.new(name) + + begin + captured_stream.sync = true + stream.reopen(captured_stream) + block.call + captured_stream.rewind + captured_stream.read + ensure + stream.reopen(original_stream) + captured_stream.close + captured_stream.unlink + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/raise_error.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/raise_error.rb new file mode 100644 index 0000000000..bb9ffb927a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/raise_error.rb @@ -0,0 +1,259 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `raise_error`. + # Not intended to be instantiated directly. + # rubocop:disable ClassLength + # rubocop:disable RescueException + class RaiseError + include Composable + + # Used as a sentinel value to be able to tell when the user did not pass an + # argument. We can't use `nil` for that because we need to warn when `nil` is + # passed in a different way. It's an Object, not a Module, since Module's `===` + # does not evaluate to true when compared to itself. + UndefinedValue = Object.new.freeze + + def initialize(expected_error_or_message, expected_message, &block) + @block = block + @actual_error = nil + @warn_about_bare_error = UndefinedValue === expected_error_or_message + @warn_about_nil_error = expected_error_or_message.nil? + + case expected_error_or_message + when nil, UndefinedValue + @expected_error = Exception + @expected_message = expected_message + when String + @expected_error = Exception + @expected_message = expected_error_or_message + else + @expected_error = expected_error_or_message + @expected_message = expected_message + end + end + + # @api public + # Specifies the expected error message. + def with_message(expected_message) + raise_message_already_set if @expected_message + @warn_about_bare_error = false + @expected_message = expected_message + self + end + + # rubocop:disable MethodLength + # @private + def matches?(given_proc, negative_expectation=false, &block) + @given_proc = given_proc + @block ||= block + @raised_expected_error = false + @with_expected_message = false + @eval_block = false + @eval_block_passed = false + + return false unless Proc === given_proc + + begin + given_proc.call + rescue Exception => @actual_error + if values_match?(@expected_error, @actual_error) || + values_match?(@expected_error, @actual_error.message) + @raised_expected_error = true + @with_expected_message = verify_message + end + end + + unless negative_expectation + warn_about_bare_error! if warn_about_bare_error? + warn_about_nil_error! if warn_about_nil_error? + eval_block if ready_to_eval_block? + end + + expectation_matched? + end + # rubocop:enable MethodLength + + # @private + def does_not_match?(given_proc) + warn_for_negative_false_positives! + !matches?(given_proc, :negative_expectation) && Proc === given_proc + end + + # @private + def supports_block_expectations? + true + end + + def expects_call_stack_jump? + true + end + + # @api private + # @return [String] + def failure_message + @eval_block ? @actual_error.message : "expected #{expected_error}#{given_error}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected no #{expected_error}#{given_error}" + end + + # @api private + # @return [String] + def description + "raise #{expected_error}" + end + + private + + def expectation_matched? + error_and_message_match? && block_matches? + end + + def error_and_message_match? + @raised_expected_error && @with_expected_message + end + + def block_matches? + @eval_block ? @eval_block_passed : true + end + + def ready_to_eval_block? + @raised_expected_error && @with_expected_message && @block + end + + def eval_block + @eval_block = true + begin + @block[@actual_error] + @eval_block_passed = true + rescue Exception => err + @actual_error = err + end + end + + def verify_message + return true if @expected_message.nil? + values_match?(@expected_message, @actual_error.message.to_s) + end + + def warn_for_negative_false_positives! + expression = if expecting_specific_exception? && @expected_message + "`expect { }.not_to raise_error(SpecificErrorClass, message)`" + elsif expecting_specific_exception? + "`expect { }.not_to raise_error(SpecificErrorClass)`" + elsif @expected_message + "`expect { }.not_to raise_error(message)`" + elsif @warn_about_nil_error + "`expect { }.not_to raise_error(nil)`" + end + + return unless expression + + warn_about_negative_false_positive! expression + end + + def handle_warning(message) + RSpec::Expectations.configuration.false_positives_handler.call(message) + end + + def warn_about_bare_error? + @warn_about_bare_error && @block.nil? + end + + def warn_about_nil_error? + @warn_about_nil_error + end + + def warn_about_bare_error! + handle_warning("Using the `raise_error` matcher without providing a specific " \ + "error or message risks false positives, since `raise_error` " \ + "will match when Ruby raises a `NoMethodError`, `NameError` or " \ + "`ArgumentError`, potentially allowing the expectation to pass " \ + "without even executing the method you are intending to call. " \ + "#{warning}"\ + "Instead consider providing a specific error class or message. " \ + "This message can be suppressed by setting: " \ + "`RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def warn_about_nil_error! + handle_warning("Using the `raise_error` matcher with a `nil` error is probably " \ + "unintentional, it risks false positives, since `raise_error` " \ + "will match when Ruby raises a `NoMethodError`, `NameError` or " \ + "`ArgumentError`, potentially allowing the expectation to pass " \ + "without even executing the method you are intending to call. " \ + "#{warning}"\ + "Instead consider providing a specific error class or message. " \ + "This message can be suppressed by setting: " \ + "`RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def warn_about_negative_false_positive!(expression) + handle_warning("Using #{expression} risks false positives, since literally " \ + "any other error would cause the expectation to pass, " \ + "including those raised by Ruby (e.g. `NoMethodError`, `NameError` " \ + "and `ArgumentError`), meaning the code you are intending to test " \ + "may not even get reached. Instead consider using " \ + "`expect { }.not_to raise_error` or `expect { }.to raise_error" \ + "(DifferentSpecificErrorClass)`. This message can be suppressed by " \ + "setting: `RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def expected_error + case @expected_message + when nil + if RSpec::Support.is_a_matcher?(@expected_error) + "Exception with #{description_of(@expected_error)}" + else + description_of(@expected_error) + end + when Regexp + "#{@expected_error} with message matching #{description_of(@expected_message)}" + else + "#{@expected_error} with #{description_of(@expected_message)}" + end + end + + def format_backtrace(backtrace) + formatter = Matchers.configuration.backtrace_formatter + formatter.format_backtrace(backtrace) + end + + def given_error + return " but was not given a block" unless Proc === @given_proc + return " but nothing was raised" unless @actual_error + + backtrace = format_backtrace(@actual_error.backtrace) + [ + ", got #{description_of(@actual_error)} with backtrace:", + *backtrace + ].join("\n # ") + end + + def expecting_specific_exception? + @expected_error != Exception + end + + def raise_message_already_set + raise "`expect { }.to raise_error(message).with_message(message)` is not valid. " \ + 'The matcher only allows the expected message to be specified once' + end + + def warning + warning = "Actual error raised was #{description_of(@actual_error)}. " + warning if @actual_error + end + end + # rubocop:enable RescueException + # rubocop:enable ClassLength + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/respond_to.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/respond_to.rb new file mode 100644 index 0000000000..9adbe04ea4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/respond_to.rb @@ -0,0 +1,200 @@ +RSpec::Support.require_rspec_support "method_signature_verifier" + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `respond_to`. + # Not intended to be instantiated directly. + class RespondTo < BaseMatcher + def initialize(*names) + @names = names + @expected_arity = nil + @expected_keywords = [] + @ignoring_method_signature_failure = false + @unlimited_arguments = nil + @arbitrary_keywords = nil + end + + # @api public + # Specifies the number of expected arguments. + # + # @example + # expect(obj).to respond_to(:message).with(3).arguments + def with(n) + @expected_arity = n + self + end + + # @api public + # Specifies keyword arguments, if any. + # + # @example + # expect(obj).to respond_to(:message).with_keywords(:color, :shape) + # @example with an expected number of arguments + # expect(obj).to respond_to(:message).with(3).arguments.and_keywords(:color, :shape) + def with_keywords(*keywords) + @expected_keywords = keywords + self + end + alias :and_keywords :with_keywords + + # @api public + # Specifies that the method accepts any keyword, i.e. the method has + # a splatted keyword parameter of the form **kw_args. + # + # @example + # expect(obj).to respond_to(:message).with_any_keywords + def with_any_keywords + @arbitrary_keywords = true + self + end + alias :and_any_keywords :with_any_keywords + + # @api public + # Specifies that the number of arguments has no upper limit, i.e. the + # method has a splatted parameter of the form *args. + # + # @example + # expect(obj).to respond_to(:message).with_unlimited_arguments + def with_unlimited_arguments + @unlimited_arguments = true + self + end + alias :and_unlimited_arguments :with_unlimited_arguments + + # @api public + # No-op. Intended to be used as syntactic sugar when using `with`. + # + # @example + # expect(obj).to respond_to(:message).with(3).arguments + def argument + self + end + alias :arguments :argument + + # @private + def matches?(actual) + find_failing_method_names(actual, :reject).empty? + end + + # @private + def does_not_match?(actual) + find_failing_method_names(actual, :select).empty? + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}" + end + + # @api private + # @return [String] + def failure_message_when_negated + failure_message.sub(/to respond to/, 'not to respond to') + end + + # @api private + # @return [String] + def description + "respond to #{pp_names}#{with_arity}" + end + + # @api private + # Used by other matchers to suppress a check + def ignoring_method_signature_failure! + @ignoring_method_signature_failure = true + end + + private + + def find_failing_method_names(actual, filter_method) + @actual = actual + @failing_method_names = @names.__send__(filter_method) do |name| + @actual.respond_to?(name) && matches_arity?(actual, name) + end + end + + def matches_arity?(actual, name) + ArityCheck.new(@expected_arity, @expected_keywords, @arbitrary_keywords, @unlimited_arguments).matches?(actual, name) + rescue NameError + return true if @ignoring_method_signature_failure + raise ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object define the method(s) in " \ + "order to check arity, but the method " \ + "`#{name}` is not defined. Remove the arity " \ + "check or define the method to continue." + end + + def with_arity + str = ''.dup + str << " with #{with_arity_string}" if @expected_arity + str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0 + str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments + str << " #{str.length == 0 ? 'with' : 'and'} any keywords" if @arbitrary_keywords + str + end + + def with_arity_string + "#{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}" + end + + def with_keywords_string + kw_str = case @expected_keywords.count + when 1 + @expected_keywords.first.inspect + when 2 + @expected_keywords.map(&:inspect).join(' and ') + else + "#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}" + end + + "keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}" + end + + def pp_names + @names.length == 1 ? "##{@names.first}" : description_of(@names) + end + + # @private + class ArityCheck + def initialize(expected_arity, expected_keywords, arbitrary_keywords, unlimited_arguments) + expectation = Support::MethodSignatureExpectation.new + + if expected_arity.is_a?(Range) + expectation.min_count = expected_arity.min + expectation.max_count = expected_arity.max + else + expectation.min_count = expected_arity + end + + expectation.keywords = expected_keywords + expectation.expect_unlimited_arguments = unlimited_arguments + expectation.expect_arbitrary_keywords = arbitrary_keywords + @expectation = expectation + end + + def matches?(actual, name) + return true if @expectation.empty? + verifier_for(actual, name).with_expectation(@expectation).valid? + end + + def verifier_for(actual, name) + Support::StrictSignatureVerifier.new(method_signature_for(actual, name)) + end + + def method_signature_for(actual, name) + method_handle = Support.method_handle_for(actual, name) + + if name == :new && method_handle.owner === ::Class && ::Class === actual + Support::MethodSignature.new(actual.instance_method(:initialize)) + else + Support::MethodSignature.new(method_handle) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/satisfy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/satisfy.rb new file mode 100644 index 0000000000..cdb34d53c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/satisfy.rb @@ -0,0 +1,60 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `satisfy`. + # Not intended to be instantiated directly. + class Satisfy < BaseMatcher + def initialize(description=nil, &block) + @description = description + @block = block + end + + # @private + def matches?(actual, &block) + @block = block if block + @actual = actual + @block.call(actual) + end + + # @private + def description + @description ||= "satisfy #{block_representation}" + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to #{description}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to #{description}" + end + + private + + if RSpec::Support::RubyFeatures.ripper_supported? + def block_representation + if (block_snippet = extract_block_snippet) + "expression `#{block_snippet}`" + else + 'block' + end + end + + def extract_block_snippet + return nil unless @block + Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name) + end + else + def block_representation + 'block' + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/start_or_end_with.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/start_or_end_with.rb new file mode 100644 index 0000000000..81f06c2881 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/start_or_end_with.rb @@ -0,0 +1,94 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Base class for the `end_with` and `start_with` matchers. + # Not intended to be instantiated directly. + class StartOrEndWith < BaseMatcher + def initialize(*expected) + @actual_does_not_have_ordered_elements = false + @expected = expected.length == 1 ? expected.first : expected + end + + # @api private + # @return [String] + def failure_message + super.tap do |msg| + if @actual_does_not_have_ordered_elements + msg << ", but it does not have ordered elements" + elsif !actual.respond_to?(:[]) + msg << ", but it cannot be indexed using #[]" + end + end + end + + # @api private + # @return [String] + def description + return super unless Hash === expected + english_name = EnglishPhrasing.split_words(self.class.matcher_name) + description_of_expected = surface_descriptions_in(expected).inspect + "#{english_name} #{description_of_expected}" + end + + private + + def match(_expected, actual) + return false unless actual.respond_to?(:[]) + + begin + return true if subsets_comparable? && subset_matches? + element_matches? + rescue ArgumentError + @actual_does_not_have_ordered_elements = true + return false + end + end + + def subsets_comparable? + # Structs support the Enumerable interface but don't really have + # the semantics of a subset of a larger set... + return false if Struct === expected + + expected.respond_to?(:length) + end + end + + # For RSpec 3.1, the base class was named `StartAndEndWith`. For SemVer reasons, + # we still provide this constant until 4.0. + # @deprecated Use StartOrEndWith instead. + # @private + StartAndEndWith = StartOrEndWith + + # @api private + # Provides the implementation for `start_with`. + # Not intended to be instantiated directly. + class StartWith < StartOrEndWith + private + + def subset_matches? + values_match?(expected, actual[0, expected.length]) + end + + def element_matches? + values_match?(expected, actual[0]) + end + end + + # @api private + # Provides the implementation for `end_with`. + # Not intended to be instantiated directly. + class EndWith < StartOrEndWith + private + + def subset_matches? + values_match?(expected, actual[-expected.length, expected.length]) + end + + def element_matches? + values_match?(expected, actual[-1]) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/throw_symbol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/throw_symbol.rb new file mode 100644 index 0000000000..1b6b8bcb46 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/throw_symbol.rb @@ -0,0 +1,132 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `throw_symbol`. + # Not intended to be instantiated directly. + class ThrowSymbol + include Composable + + def initialize(expected_symbol=nil, expected_arg=nil) + @expected_symbol = expected_symbol + @expected_arg = expected_arg + @caught_symbol = @caught_arg = nil + end + + # rubocop:disable MethodLength + # @private + def matches?(given_proc) + @block = given_proc + return false unless Proc === given_proc + + begin + if @expected_symbol.nil? + given_proc.call + else + @caught_arg = catch :proc_did_not_throw_anything do + catch @expected_symbol do + given_proc.call + throw :proc_did_not_throw_anything, :nothing_thrown + end + end + + if @caught_arg == :nothing_thrown + @caught_arg = nil + else + @caught_symbol = @expected_symbol + end + end + + # Ruby 1.8 uses NameError with `symbol' + # Ruby 1.9 uses ArgumentError with :symbol + rescue NameError, ArgumentError => e + unless (match_data = e.message.match(/uncaught throw (`|\:)([a-zA-Z0-9_]*)(')?/)) + other_exception = e + raise + end + @caught_symbol = match_data.captures[1].to_sym + rescue => other_exception + raise + ensure + # rubocop:disable EnsureReturn + unless other_exception + if @expected_symbol.nil? + return !!@caught_symbol + else + if @expected_arg.nil? + return @caught_symbol == @expected_symbol + else + return (@caught_symbol == @expected_symbol) && values_match?(@expected_arg, @caught_arg) + end + end + end + # rubocop:enable EnsureReturn + end + end + # rubocop:enable MethodLength + + def does_not_match?(given_proc) + !matches?(given_proc) && Proc === given_proc + end + + # @api private + # @return [String] + def failure_message + "expected #{expected} to be thrown, #{actual_result}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{expected('no Symbol')}#{' not' if @expected_symbol} to be thrown, #{actual_result}" + end + + # @api private + # @return [String] + def description + "throw #{expected}" + end + + # @api private + # Indicates this matcher matches against a block. + # @return [True] + def supports_block_expectations? + true + end + + def expects_call_stack_jump? + true + end + + private + + def actual_result + return "but was not a block" unless Proc === @block + "got #{caught}" + end + + def expected(symbol_desc='a Symbol') + throw_description(@expected_symbol || symbol_desc, @expected_arg) + end + + def caught + throw_description(@caught_symbol || 'nothing', @caught_arg) + end + + def throw_description(symbol, arg) + symbol_description = symbol.is_a?(String) ? symbol : description_of(symbol) + + arg_description = if arg + " with #{description_of arg}" + elsif @expected_arg && @caught_symbol == @expected_symbol + " with no argument" + else + "" + end + + symbol_description + arg_description + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/yield.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/yield.rb new file mode 100644 index 0000000000..9ca6b1b473 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/built_in/yield.rb @@ -0,0 +1,355 @@ +require 'rspec/matchers/built_in/count_expectation' + +RSpec::Support.require_rspec_support 'method_signature_verifier' + +module RSpec + module Matchers + module BuiltIn + # @private + # Object that is yielded to `expect` when one of the + # yield matchers is used. Provides information about + # the yield behavior of the object-under-test. + class YieldProbe + def self.probe(block, &callback) + probe = new(block, &callback) + return probe unless probe.has_block? + probe.probe + end + + attr_accessor :num_yields, :yielded_args + + def initialize(block, &callback) + @block = block + @callback = callback || Proc.new {} + @used = false + self.num_yields = 0 + self.yielded_args = [] + end + + def has_block? + Proc === @block + end + + def probe + assert_valid_expect_block! + @block.call(self) + assert_used! + self + end + + def to_proc + @used = true + + probe = self + callback = @callback + Proc.new do |*args| + probe.num_yields += 1 + probe.yielded_args << args + callback.call(*args) + nil # to indicate the block does not return a meaningful value + end + end + + def single_yield_args + yielded_args.first + end + + def yielded_once?(matcher_name) + case num_yields + when 1 then true + when 0 then false + else + raise "The #{matcher_name} matcher is not designed to be used with a " \ + 'method that yields multiple times. Use the yield_successive_args ' \ + 'matcher for that case.' + end + end + + def assert_used! + return if @used + raise 'You must pass the argument yielded to your expect block on ' \ + 'to the method-under-test as a block. It acts as a probe that ' \ + 'allows the matcher to detect whether or not the method-under-test ' \ + 'yields, and, if so, how many times, and what the yielded arguments ' \ + 'are.' + end + + if RUBY_VERSION.to_f > 1.8 + def assert_valid_expect_block! + block_signature = RSpec::Support::BlockSignature.new(@block) + return if RSpec::Support::StrictSignatureVerifier.new(block_signature, [self]).valid? + raise 'Your expect block must accept an argument to be used with this ' \ + 'matcher. Pass the argument as a block on to the method you are testing.' + end + else + # :nocov: + # On 1.8.7, `lambda { }.arity` and `lambda { |*a| }.arity` both return -1, + # so we can't distinguish between accepting no args and an arg splat. + # It's OK to skip, this, though; it just provides a nice error message + # when the user forgets to accept an arg in their block. They'll still get + # the `assert_used!` error message from above, which is sufficient. + def assert_valid_expect_block! + # nothing to do + end + # :nocov: + end + end + + # @api private + # Provides the implementation for `yield_control`. + # Not intended to be instantiated directly. + class YieldControl < BaseMatcher + include CountExpectation + # @private + def matches?(block) + @probe = YieldProbe.probe(block) + return false unless @probe.has_block? + expected_count_matches?(@probe.num_yields) + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @api private + # @return [String] + def failure_message + 'expected given block to yield control' + failure_reason + end + + # @api private + # @return [String] + def failure_message_when_negated + 'expected given block not to yield control' + failure_reason + end + + # @private + def supports_block_expectations? + true + end + + private + + def failure_reason + return ' but was not a block' unless @probe.has_block? + return "#{count_expectation_description} but did not yield" if @probe.num_yields == 0 + count_failure_reason('yielded') + end + end + + # @api private + # Provides the implementation for `yield_with_no_args`. + # Not intended to be instantiated directly. + class YieldWithNoArgs < BaseMatcher + # @private + def matches?(block) + @probe = YieldProbe.probe(block) + return false unless @probe.has_block? + @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty? + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + "expected given block to yield with no arguments, but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + "expected given block not to yield with no arguments, but #{negative_failure_reason}" + end + + # @private + def supports_block_expectations? + true + end + + private + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + return 'did not yield' if @probe.num_yields.zero? + "yielded with arguments: #{description_of @probe.single_yield_args}" + end + + def negative_failure_reason + return 'was not a block' unless @probe.has_block? + 'did' + end + end + + # @api private + # Provides the implementation for `yield_with_args`. + # Not intended to be instantiated directly. + class YieldWithArgs < BaseMatcher + def initialize(*args) + @expected = args + end + + # @private + def matches?(block) + @args_matched_when_yielded = true + @probe = YieldProbe.new(block) do + @actual = @probe.single_yield_args + @actual_formatted = actual_formatted + @args_matched_when_yielded &&= args_currently_match? + end + return false unless @probe.has_block? + @probe.probe + @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + "expected given block to yield with arguments, but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + "expected given block not to yield with arguments, but #{negative_failure_reason}" + end + + # @private + def description + desc = 'yield with args' + desc = "#{desc}(#{expected_arg_description})" unless @expected.empty? + desc + end + + # @private + def supports_block_expectations? + true + end + + private + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + return 'did not yield' if @probe.num_yields.zero? + @positive_args_failure + end + + def expected_arg_description + @expected.map { |e| description_of e }.join(', ') + end + + def negative_failure_reason + if !@probe.has_block? + 'was not a block' + elsif @args_matched_when_yielded && !@expected.empty? + 'yielded with expected arguments' \ + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: #{@actual_formatted}" + else + 'did' + end + end + + def args_currently_match? + if @expected.empty? # expect {...}.to yield_with_args + @positive_args_failure = 'yielded with no arguments' if @actual.empty? + return !@actual.empty? + end + + unless (match = all_args_match?) + @positive_args_failure = 'yielded with unexpected arguments' \ + "\nexpected: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: #{@actual_formatted}" + end + + match + end + + def all_args_match? + values_match?(@expected, @actual) + end + end + + # @api private + # Provides the implementation for `yield_successive_args`. + # Not intended to be instantiated directly. + class YieldSuccessiveArgs < BaseMatcher + def initialize(*args) + @expected = args + end + + # @private + def matches?(block) + @actual_formatted = [] + @actual = [] + args_matched_when_yielded = true + yield_count = 0 + + @probe = YieldProbe.probe(block) do |*arg_array| + arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array + @actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args) + @actual << arg_or_args + args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args) + yield_count += 1 + end + + return false unless @probe.has_block? + args_matched_when_yielded && yield_count == @expected.length + end + + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + 'expected given block to yield successively with arguments, ' \ + "but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + 'expected given block not to yield successively with arguments, ' \ + "but #{negative_failure_reason}" + end + + # @private + def description + "yield successive args(#{expected_arg_description})" + end + + # @private + def supports_block_expectations? + true + end + + private + + def expected_arg_description + @expected.map { |e| description_of e }.join(', ') + end + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + + 'yielded with unexpected arguments' \ + "\nexpected: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: [#{@actual_formatted.join(", ")}]" + end + + def negative_failure_reason + return 'was not a block' unless @probe.has_block? + + 'yielded with expected arguments' \ + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: [#{@actual_formatted.join(", ")}]" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/composable.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/composable.rb new file mode 100644 index 0000000000..119b15aa67 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/composable.rb @@ -0,0 +1,171 @@ +RSpec::Support.require_rspec_support "fuzzy_matcher" + +module RSpec + module Matchers + # Mixin designed to support the composable matcher features + # of RSpec 3+. Mix it into your custom matcher classes to + # allow them to be used in a composable fashion. + # + # @api public + module Composable + # Creates a compound `and` expectation. The matcher will + # only pass if both sub-matchers pass. + # This can be chained together to form an arbitrarily long + # chain of matchers. + # + # @example + # expect(alphabet).to start_with("a").and end_with("z") + # expect(alphabet).to start_with("a") & end_with("z") + # + # @note The negative form (`expect(...).not_to matcher.and other`) + # is not supported at this time. + def and(matcher) + BuiltIn::Compound::And.new self, matcher + end + alias & and + + # Creates a compound `or` expectation. The matcher will + # pass if either sub-matcher passes. + # This can be chained together to form an arbitrarily long + # chain of matchers. + # + # @example + # expect(stoplight.color).to eq("red").or eq("green").or eq("yellow") + # expect(stoplight.color).to eq("red") | eq("green") | eq("yellow") + # + # @note The negative form (`expect(...).not_to matcher.or other`) + # is not supported at this time. + def or(matcher) + BuiltIn::Compound::Or.new self, matcher + end + alias | or + + # Delegates to `#matches?`. Allows matchers to be used in composable + # fashion and also supports using matchers in case statements. + def ===(value) + matches?(value) + end + + private + + # This provides a generic way to fuzzy-match an expected value against + # an actual value. It understands nested data structures (e.g. hashes + # and arrays) and is able to match against a matcher being used as + # the expected value or within the expected value at any level of + # nesting. + # + # Within a custom matcher you are encouraged to use this whenever your + # matcher needs to match two values, unless it needs more precise semantics. + # For example, the `eq` matcher _does not_ use this as it is meant to + # use `==` (and only `==`) for matching. + # + # @param expected [Object] what is expected + # @param actual [Object] the actual value + # + # @!visibility public + def values_match?(expected, actual) + expected = with_matchers_cloned(expected) + Support::FuzzyMatcher.values_match?(expected, actual) + end + + # Returns the description of the given object in a way that is + # aware of composed matchers. If the object is a matcher with + # a `description` method, returns the description; otherwise + # returns `object.inspect`. + # + # You are encouraged to use this in your custom matcher's + # `description`, `failure_message` or + # `failure_message_when_negated` implementation if you are + # supporting matcher arguments. + # + # @!visibility public + def description_of(object) + RSpec::Support::ObjectFormatter.format(object) + end + + # Transforms the given data structue (typically a hash or array) + # into a new data structure that, when `#inspect` is called on it, + # will provide descriptions of any contained matchers rather than + # the normal `#inspect` output. + # + # You are encouraged to use this in your custom matcher's + # `description`, `failure_message` or + # `failure_message_when_negated` implementation if you are + # supporting any arguments which may be a data structure + # containing matchers. + # + # @!visibility public + def surface_descriptions_in(item) + if Matchers.is_a_describable_matcher?(item) + DescribableItem.new(item) + elsif Hash === item + Hash[surface_descriptions_in(item.to_a)] + elsif Struct === item || unreadable_io?(item) + RSpec::Support::ObjectFormatter.format(item) + elsif should_enumerate?(item) + item.map { |subitem| surface_descriptions_in(subitem) } + else + item + end + end + + # @private + # Historically, a single matcher instance was only checked + # against a single value. Given that the matcher was only + # used once, it's been common to memoize some intermediate + # calculation that is derived from the `actual` value in + # order to reuse that intermediate result in the failure + # message. + # + # This can cause a problem when using such a matcher as an + # argument to another matcher in a composed matcher expression, + # since the matcher instance may be checked against multiple + # values and produce invalid results due to the memoization. + # + # To deal with this, we clone any matchers in `expected` via + # this method when using `values_match?`, so that any memoization + # does not "leak" between checks. + def with_matchers_cloned(object) + if Matchers.is_a_matcher?(object) + object.clone + elsif Hash === object + Hash[with_matchers_cloned(object.to_a)] + elsif should_enumerate?(object) + object.map { |subobject| with_matchers_cloned(subobject) } + else + object + end + end + + # @api private + # We should enumerate arrays as long as they are not recursive. + def should_enumerate?(item) + Array === item && item.none? { |subitem| subitem.equal?(item) } + end + + # @api private + def unreadable_io?(object) + return false unless IO === object + object.each {} # STDOUT is enumerable but raises an error + false + rescue IOError + true + end + module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io? + + # Wraps an item in order to surface its `description` via `inspect`. + # @api private + DescribableItem = Struct.new(:item) do + # Inspectable version of the item description + def inspect + "(#{item.description})" + end + + # A pretty printed version of the item description. + def pretty_print(pp) + pp.text "(#{item.description})" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/dsl.rb new file mode 100644 index 0000000000..1a1f9c50f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/dsl.rb @@ -0,0 +1,540 @@ +RSpec::Support.require_rspec_support "with_keywords_when_needed" + +module RSpec + module Matchers + # Defines the custom matcher DSL. + module DSL + # Defines a matcher alias. The returned matcher's `description` will be overriden + # to reflect the phrasing of the new name, which will be used in failure messages + # when passed as an argument to another matcher in a composed matcher expression. + # + # @example + # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to + # sum_to(3).description # => "sum to 3" + # a_list_that_sums_to(3).description # => "a list that sums to 3" + # + # @example + # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description| + # description.sub("be sorted by", "a list sorted by") + # end + # + # be_sorted_by(:age).description # => "be sorted by age" + # a_list_sorted_by(:age).description # => "a list sorted by age" + # + # @param new_name [Symbol] the new name for the matcher + # @param old_name [Symbol] the original name for the matcher + # @param options [Hash] options for the aliased matcher + # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used). + # @yield [String] optional block that, when given, is used to define the overriden + # logic. The yielded arg is the original description or failure message. If no + # block is provided, a default override is used based on the old and new names. + # @see RSpec::Matchers + def alias_matcher(new_name, old_name, options={}, &description_override) + description_override ||= lambda do |old_desc| + old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name)) + end + klass = options.fetch(:klass) { AliasedMatcher } + + define_method(new_name) do |*args, &block| + matcher = __send__(old_name, *args, &block) + matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=) + klass.new(matcher, description_override) + end + end + + # Defines a negated matcher. The returned matcher's `description` and `failure_message` + # will be overriden to reflect the phrasing of the new name, and the match logic will + # be based on the original matcher but negated. + # + # @example + # RSpec::Matchers.define_negated_matcher :exclude, :include + # include(1, 2).description # => "include 1 and 2" + # exclude(1, 2).description # => "exclude 1 and 2" + # + # @param negated_name [Symbol] the name for the negated matcher + # @param base_name [Symbol] the name of the original matcher that will be negated + # @yield [String] optional block that, when given, is used to define the overriden + # logic. The yielded arg is the original description or failure message. If no + # block is provided, a default override is used based on the old and new names. + # @see RSpec::Matchers + def define_negated_matcher(negated_name, base_name, &description_override) + alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override) + end + + # Defines a custom matcher. + # + # @param name [Symbol] the name for the matcher + # @yield [Object] block that is used to define the matcher. + # The block is evaluated in the context of your custom matcher class. + # When args are passed to your matcher, they will be yielded here, + # usually representing the expected value(s). + # @see RSpec::Matchers + def define(name, &declarations) + warn_about_block_args(name, declarations) + define_method name do |*expected, &block_arg| + RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg) + end + end + alias_method :matcher, :define + + private + + if Proc.method_defined?(:parameters) + def warn_about_block_args(name, declarations) + declarations.parameters.each do |type, arg_name| + next unless type == :block + RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \ + "but due to limitations in ruby, RSpec cannot provide the block. Instead, " \ + "use the `block_arg` method to access the block") + end + end + else + # :nocov: + def warn_about_block_args(*) + # There's no way to detect block params on 1.8 since the method reflection APIs don't expose it + end + # :nocov: + end + + RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure) + + # Contains the methods that are available from within the + # `RSpec::Matchers.define` DSL for creating custom matchers. + module Macros + # Stores the block that is used to determine whether this matcher passes + # or fails. The block should return a boolean value. When the matcher is + # passed to `expect(...).to` and the block returns `true`, then the expectation + # passes. Similarly, when the matcher is passed to `expect(...).not_to` and the + # block returns `false`, then the expectation passes. + # + # @example + # + # RSpec::Matchers.define :be_even do + # match do |actual| + # actual.even? + # end + # end + # + # expect(4).to be_even # passes + # expect(3).not_to be_even # passes + # expect(3).to be_even # fails + # expect(4).not_to be_even # fails + # + # By default the match block will swallow expectation errors (e.g. + # caused by using an expectation such as `expect(1).to eq 2`), if you + # with to allow these to bubble up, pass in the option + # `:notify_expectation_failures => true`. + # + # @param [Hash] options for defining the behavior of the match block. + # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`) + def match(options={}, &match_block) + define_user_override(:matches?, match_block) do |actual| + @actual = actual + RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do + begin + super(*actual_arg_for(match_block)) + rescue RSpec::Expectations::ExpectationNotMetError + raise if options[:notify_expectation_failures] + false + end + end + end + end + + # @private + RAISE_NOTIFIER = Proc.new { |err, _opts| raise err } + + # Use this to define the block for a negative expectation (`expect(...).not_to`) + # when the positive and negative forms require different handling. This + # is rarely necessary, but can be helpful, for example, when specifying + # asynchronous processes that require different timeouts. + # + # By default the match block will swallow expectation errors (e.g. + # caused by using an expectation such as `expect(1).to eq 2`), if you + # with to allow these to bubble up, pass in the option + # `:notify_expectation_failures => true`. + # + # @param [Hash] options for defining the behavior of the match block. + # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`) + def match_when_negated(options={}, &match_block) + define_user_override(:does_not_match?, match_block) do |actual| + begin + @actual = actual + RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do + super(*actual_arg_for(match_block)) + end + rescue RSpec::Expectations::ExpectationNotMetError + raise if options[:notify_expectation_failures] + false + end + end + end + + # Use this instead of `match` when the block will raise an exception + # rather than returning false to indicate a failure. + # + # @example + # + # RSpec::Matchers.define :accept_as_valid do |candidate_address| + # match_unless_raises ValidationException do |validator| + # validator.validate(candidate_address) + # end + # end + # + # expect(email_validator).to accept_as_valid("person@company.com") + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def match_unless_raises(expected_exception=Exception, &match_block) + define_user_override(:matches?, match_block) do |actual| + @actual = actual + begin + super(*actual_arg_for(match_block)) + rescue expected_exception => @rescued_exception + false + else + true + end + end + end + + # Customizes the failure messsage to use when this matcher is + # asked to positively match. Only use this when the message + # generated by default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :have_strength do |expected| + # match { your_match_logic } + # + # failure_message do |actual| + # "Expected strength of #{expected}, but had #{actual.strength}" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def failure_message(&definition) + define_user_override(__method__, definition) + end + + # Customize the failure messsage to use when this matcher is asked + # to negatively match. Only use this when the message generated by + # default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :have_strength do |expected| + # match { your_match_logic } + # + # failure_message_when_negated do |actual| + # "Expected not to have strength of #{expected}, but did" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def failure_message_when_negated(&definition) + define_user_override(__method__, definition) + end + + # Customize the description to use for one-liners. Only use this when + # the description generated by default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :qualify_for do |expected| + # match { your_match_logic } + # + # description do + # "qualify for #{expected}" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def description(&definition) + define_user_override(__method__, definition) + end + + # Tells the matcher to diff the actual and expected values in the failure + # message. + def diffable + define_method(:diffable?) { true } + end + + # Declares that the matcher can be used in a block expectation. + # Users will not be able to use your matcher in a block + # expectation without declaring this. + # (e.g. `expect { do_something }.to matcher`). + def supports_block_expectations + define_method(:supports_block_expectations?) { true } + end + + # Convenience for defining methods on this matcher to create a fluent + # interface. The trick about fluent interfaces is that each method must + # return self in order to chain methods together. `chain` handles that + # for you. If the method is invoked and the + # `include_chain_clauses_in_custom_matcher_descriptions` config option + # hash been enabled, the chained method name and args will be added to the + # default description and failure message. + # + # In the common case where you just want the chained method to store some + # value(s) for later use (e.g. in `match`), you can provide one or more + # attribute names instead of a block; the chained method will store its + # arguments in instance variables with those names, and the values will + # be exposed via getters. + # + # @example + # + # RSpec::Matchers.define :have_errors_on do |key| + # chain :with do |message| + # @message = message + # end + # + # match do |actual| + # actual.errors[key] == @message + # end + # end + # + # expect(minor).to have_errors_on(:age).with("Not old enough to participate") + def chain(method_name, *attr_names, &definition) + unless block_given? ^ attr_names.any? + raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`." + end + + definition = assign_attributes(attr_names) if attr_names.any? + + define_user_override(method_name, definition) do |*args, &block| + super(*args, &block) + @chained_method_clauses.push([method_name, args]) + self + end + end + + def assign_attributes(attr_names) + attr_reader(*attr_names) + private(*attr_names) + + lambda do |*attr_values| + attr_names.zip(attr_values) do |attr_name, attr_value| + instance_variable_set(:"@#{attr_name}", attr_value) + end + end + end + + # assign_attributes isn't defined in the private section below because + # that makes MRI 1.9.2 emit a warning about private attributes. + private :assign_attributes + + private + + # Does the following: + # + # - Defines the named method using a user-provided block + # in @user_method_defs, which is included as an ancestor + # in the singleton class in which we eval the `define` block. + # - Defines an overriden definition for the same method + # usign the provided `our_def` block. + # - Provides a default `our_def` block for the common case + # of needing to call the user's definition with `@actual` + # as an arg, but only if their block's arity can handle it. + # + # This compiles the user block into an actual method, allowing + # them to use normal method constructs like `return` + # (e.g. for an early guard statement), while allowing us to define + # an override that can provide the wrapped handling + # (e.g. assigning `@actual`, rescueing errors, etc) and + # can `super` to the user's definition. + def define_user_override(method_name, user_def, &our_def) + @user_method_defs.__send__(:define_method, method_name, &user_def) + our_def ||= lambda { super(*actual_arg_for(user_def)) } + define_method(method_name, &our_def) + end + + # Defines deprecated macro methods from RSpec 2 for backwards compatibility. + # @deprecated Use the methods from {Macros} instead. + module Deprecated + # @deprecated Use {Macros#match} instead. + def match_for_should(&definition) + RSpec.deprecate("`match_for_should`", :replacement => "`match`") + match(&definition) + end + + # @deprecated Use {Macros#match_when_negated} instead. + def match_for_should_not(&definition) + RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`") + match_when_negated(&definition) + end + + # @deprecated Use {Macros#failure_message} instead. + def failure_message_for_should(&definition) + RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`") + failure_message(&definition) + end + + # @deprecated Use {Macros#failure_message_when_negated} instead. + def failure_message_for_should_not(&definition) + RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`") + failure_message_when_negated(&definition) + end + end + end + + # Defines default implementations of the matcher + # protocol methods for custom matchers. You can + # override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods + # from within an `RSpec::Matchers.define` block. + module DefaultImplementations + include BuiltIn::BaseMatcher::DefaultFailureMessages + + # @api private + # Used internally by objects returns by `should` and `should_not`. + def diffable? + false + end + + # The default description. + def description + english_name = EnglishPhrasing.split_words(name) + expected_list = EnglishPhrasing.list(expected) + "#{english_name}#{expected_list}#{chained_method_clause_sentences}" + end + + # Matchers do not support block expectations by default. You + # must opt-in. + def supports_block_expectations? + false + end + + # Most matchers do not expect call stack jumps. + def expects_call_stack_jump? + false + end + + private + + def chained_method_clause_sentences + return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions? + + @chained_method_clauses.map do |(method_name, method_args)| + english_name = EnglishPhrasing.split_words(method_name) + arg_list = EnglishPhrasing.list(method_args) + " #{english_name}#{arg_list}" + end.join + end + end + + # The class used for custom matchers. The block passed to + # `RSpec::Matchers.define` will be evaluated in the context + # of the singleton class of an instance, and will have the + # {RSpec::Matchers::DSL::Macros Macros} methods available. + class Matcher + # Provides default implementations for the matcher protocol methods. + include DefaultImplementations + + # Allows expectation expressions to be used in the match block. + include RSpec::Matchers + + # Supports the matcher composability features of RSpec 3+. + include Composable + + # Makes the macro methods available to an `RSpec::Matchers.define` block. + extend Macros + extend Macros::Deprecated + + # Exposes the value being matched against -- generally the object + # object wrapped by `expect`. + attr_reader :actual + + # Exposes the exception raised during the matching by `match_unless_raises`. + # Could be useful to extract details for a failure message. + attr_reader :rescued_exception + + # The block parameter used in the expectation + attr_reader :block_arg + + # The name of the matcher. + attr_reader :name + + # @api private + def initialize(name, declarations, matcher_execution_context, *expected, &block_arg) + @name = name + @actual = nil + @expected_as_array = expected + @matcher_execution_context = matcher_execution_context + @chained_method_clauses = [] + @block_arg = block_arg + + klass = class << self + # See `Macros#define_user_override` above, for an explanation. + include(@user_method_defs = Module.new) + self + end + RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *expected, &declarations) + end + + # Provides the expected value. This will return an array if + # multiple arguments were passed to the matcher; otherwise it + # will return a single value. + # @see #expected_as_array + def expected + if expected_as_array.size == 1 + expected_as_array[0] + else + expected_as_array + end + end + + # Returns the expected value as an an array. This exists primarily + # to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected` + # always returned an array. + # @see #expected + attr_reader :expected_as_array + + # Adds the name (rather than a cryptic hex number) + # so we can identify an instance of + # the matcher in error messages (e.g. for `NoMethodError`) + def inspect + "#<#{self.class.name} #{name}>" + end + + if RUBY_VERSION.to_f >= 1.9 + # Indicates that this matcher responds to messages + # from the `@matcher_execution_context` as well. + # Also, supports getting a method object for such methods. + def respond_to_missing?(method, include_private=false) + super || @matcher_execution_context.respond_to?(method, include_private) + end + else # for 1.8.7 + # :nocov: + # Indicates that this matcher responds to messages + # from the `@matcher_execution_context` as well. + def respond_to?(method, include_private=false) + super || @matcher_execution_context.respond_to?(method, include_private) + end + # :nocov: + end + + private + + def actual_arg_for(block) + block.arity.zero? ? [] : [@actual] + end + + # Takes care of forwarding unhandled messages to the + # `@matcher_execution_context` (typically the current + # running `RSpec::Core::Example`). This is needed by + # rspec-rails so that it can define matchers that wrap + # Rails' test helper methods, but it's also a useful + # feature in its own right. + def method_missing(method, *args, &block) + if @matcher_execution_context.respond_to?(method) + @matcher_execution_context.__send__ method, *args, &block + else + super(method, *args, &block) + end + end + # The method_missing method should be refactored to pass kw args in RSpec 4 + # then this can be removed + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/english_phrasing.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/english_phrasing.rb new file mode 100644 index 0000000000..fff803cccc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/english_phrasing.rb @@ -0,0 +1,58 @@ +module RSpec + module Matchers + # Facilitates converting ruby objects to English phrases. + module EnglishPhrasing + # Converts a symbol into an English expression. + # + # split_words(:banana_creme_pie) #=> "banana creme pie" + # + def self.split_words(sym) + sym.to_s.tr('_', ' ') + end + + # @note The returned string has a leading space except + # when given an empty list. + # + # Converts an object (often a collection of objects) + # into an English list. + # + # list(['banana', 'kiwi', 'mango']) + # #=> " \"banana\", \"kiwi\", and \"mango\"" + # + # Given an empty collection, returns the empty string. + # + # list([]) #=> "" + # + def self.list(obj) + return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj || Hash === obj + items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) } + case items.length + when 0 + "" + when 1 + " #{items[0]}" + when 2 + " #{items[0]} and #{items[1]}" + else + " #{items[0...-1].join(', ')}, and #{items[-1]}" + end + end + + if RUBY_VERSION == '1.8.7' + # Not sure why, but on travis on 1.8.7 we have gotten these warnings: + # lib/rspec/matchers/english_phrasing.rb:28: warning: default `to_a' will be obsolete + # So it appears that `Array` can trigger that (e.g. by calling `to_a` on the passed object?) + # So here we replace `Kernel#Array` with our own warning-free implementation for 1.8.7. + # @private + # rubocop:disable Naming/MethodName + def self.Array(obj) + case obj + when Array then obj + else [obj] + end + end + # rubocop:enable Naming/MethodName + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/expecteds_for_multiple_diffs.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/expecteds_for_multiple_diffs.rb new file mode 100644 index 0000000000..b286bb5ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/expecteds_for_multiple_diffs.rb @@ -0,0 +1,82 @@ +module RSpec + module Matchers + # @api private + # Handles list of expected values when there is a need to render + # multiple diffs. Also can handle one value. + class ExpectedsForMultipleDiffs + # @private + # Default diff label when there is only one matcher in diff + # output + DEFAULT_DIFF_LABEL = "Diff:".freeze + + # @private + # Maximum readable matcher description length + DESCRIPTION_MAX_LENGTH = 65 + + def initialize(expected_list) + @expected_list = expected_list + end + + # @api private + # Wraps provided expected value in instance of + # ExpectedForMultipleDiffs. If provided value is already an + # ExpectedForMultipleDiffs then it just returns it. + # @param [Any] expected value to be wrapped + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def self.from(expected) + return expected if self === expected + new([[expected, DEFAULT_DIFF_LABEL]]) + end + + # @api private + # Wraps provided matcher list in instance of + # ExpectedForMultipleDiffs. + # @param [Array] matchers list of matchers to wrap + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def self.for_many_matchers(matchers) + new(matchers.map { |m| [m.expected, diff_label_for(m)] }) + end + + # @api private + # Returns message with diff(s) appended for provided differ + # factory and actual value if there are any + # @param [String] message original failure message + # @param [Proc] differ + # @param [Any] actual value + # @return [String] + def message_with_diff(message, differ, actual) + diff = diffs(differ, actual) + message = "#{message}\n#{diff}" unless diff.empty? + message + end + + private + + class << self + private + + def diff_label_for(matcher) + "Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):" + end + + def truncated(description) + return description if description.length <= DESCRIPTION_MAX_LENGTH + description[0...DESCRIPTION_MAX_LENGTH - 3] << "..." + end + end + + def diffs(differ, actual) + @expected_list.map do |(expected, diff_label)| + diff = differ.diff(actual, expected) + next if diff.strip.empty? + if diff == "\e[0m\n\e[0m" + "#{diff_label}\n" \ + " " + else + "#{diff_label}#{diff}" + end + end.compact.join("\n") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/fail_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/fail_matchers.rb new file mode 100644 index 0000000000..bdd7cda08c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/fail_matchers.rb @@ -0,0 +1,42 @@ +require 'rspec/expectations' + +module RSpec + module Matchers + # Matchers for testing RSpec matchers. Include them with: + # + # require 'rspec/matchers/fail_matchers' + # RSpec.configure do |config| + # config.include RSpec::Matchers::FailMatchers + # end + # + module FailMatchers + # Matches if an expectation fails + # + # @example + # expect { some_expectation }.to fail + def fail(&block) + raise_error(RSpec::Expectations::ExpectationNotMetError, &block) + end + + # Matches if an expectation fails with the provided message + # + # @example + # expect { some_expectation }.to fail_with("some failure message") + # expect { some_expectation }.to fail_with(/some failure message/) + def fail_with(message) + raise_error(RSpec::Expectations::ExpectationNotMetError, message) + end + + # Matches if an expectation fails including the provided message + # + # @example + # expect { some_expectation }.to fail_including("portion of some failure message") + def fail_including(*snippets) + raise_error( + RSpec::Expectations::ExpectationNotMetError, + a_string_including(*snippets) + ) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/generated_descriptions.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/generated_descriptions.rb new file mode 100644 index 0000000000..cbf37519ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/generated_descriptions.rb @@ -0,0 +1,41 @@ +module RSpec + module Matchers + class << self + # @private + attr_accessor :last_matcher, :last_expectation_handler + end + + # @api private + # Used by rspec-core to clear the state used to generate + # descriptions after an example. + def self.clear_generated_description + self.last_matcher = nil + self.last_expectation_handler = nil + end + + # @api private + # Generates an an example description based on the last expectation. + # Used by rspec-core's one-liner syntax. + def self.generated_description + return nil if last_expectation_handler.nil? + "#{last_expectation_handler.verb} #{last_description}" + end + + # @private + def self.last_description + last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE +When you call a matcher in an example without a String, like this: + +specify { expect(object).to matcher } + +or this: + +it { is_expected.to matcher } + +RSpec expects the matcher to have a #description method. You should either +add a String to the example this matcher is being used in, or give it a +description method. Then you won't have to suffer this lengthy warning again. +MESSAGE + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_delegator.rb new file mode 100644 index 0000000000..e17b2ee599 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_delegator.rb @@ -0,0 +1,35 @@ +module RSpec + module Matchers + # Provides the necessary plumbing to wrap a matcher with a decorator. + # @private + class MatcherDelegator + include Composable + attr_reader :base_matcher + + def initialize(base_matcher) + @base_matcher = base_matcher + end + + def method_missing(*args, &block) + base_matcher.__send__(*args, &block) + end + + if ::RUBY_VERSION.to_f > 1.8 + def respond_to_missing?(name, include_all=false) + super || base_matcher.respond_to?(name, include_all) + end + else + # :nocov: + def respond_to?(name, include_all=false) + super || base_matcher.respond_to?(name, include_all) + end + # :nocov: + end + + def initialize_copy(other) + @base_matcher = @base_matcher.clone + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_protocol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_protocol.rb new file mode 100644 index 0000000000..c5bc432e17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.10.1/lib/rspec/matchers/matcher_protocol.rb @@ -0,0 +1,99 @@ +module RSpec + module Matchers + # rspec-expectations can work with any matcher object that implements this protocol. + # + # @note This class is not loaded at runtime by rspec-expectations. It exists + # purely to provide documentation for the matcher protocol. + class MatcherProtocol + # @!group Required Methods + + # @!method matches?(actual) + # @param actual [Object] The object being matched against. + # @yield For an expression like `expect(x).to matcher do...end`, the `do/end` + # block binds to `to`. It passes that block, if there is one, on to this method. + # @return [Boolean] true if this matcher matches the provided object. + + # @!method failure_message + # This will only be called if {#matches?} returns false. + # @return [String] Explanation for the failure. + + # @!endgroup + + # @!group Optional Methods + + # @!method does_not_match?(actual) + # In a negative expectation such as `expect(x).not_to foo`, RSpec will + # call `foo.does_not_match?(x)` if this method is defined. If it's not + # defined it will fall back to using `!foo.matches?(x)`. This allows you + # to provide custom logic for the negative case. + # + # @param actual [Object] The object being matched against. + # @yield For an expression like `expect(x).not_to matcher do...end`, the `do/end` + # block binds to `not_to`. It passes that block, if there is one, on to this method. + # @return [Boolean] true if this matcher does not match the provided object. + + # @!method failure_message_when_negated + # This will only be called when a negative match fails. + # @return [String] Explanation for the failure. + # @note This method is listed as optional because matchers do not have to + # support negation. But if your matcher does support negation, this is a + # required method -- otherwise, you'll get a `NoMethodError`. + + # @!method description + # The description is used for two things: + # + # * When using RSpec's one-liner syntax + # (e.g. `it { is_expected.to matcher }`), the description + # is used to generate the example's doc string since you + # have not provided one. + # * In a composed matcher expression, the description is used + # as part of the failure message (and description) of the outer + # matcher. + # + # @return [String] Description of the matcher. + + # @!method supports_block_expectations? + # Indicates that this matcher can be used in a block expectation expression, + # such as `expect { foo }.to raise_error`. Generally speaking, this is + # only needed for matchers which operate on a side effect of a block, rather + # than on a particular object. + # @return [Boolean] true if this matcher can be used in block expressions. + # @note If not defined, RSpec assumes a value of `false` for this method. + + # @!method expects_call_stack_jump? + # Indicates that when this matcher is used in a block expectation + # expression, it expects the block to use a ruby construct that causes + # a call stack jump (such as raising an error or throwing a symbol). + # + # This is used internally for compound block expressions, as matchers + # which expect call stack jumps must be treated with care to work properly. + # + # @return [Boolean] true if the matcher expects a call stack jump + # + # @note This method is very rarely used or needed. + # @note If not defined, RSpec assumes a value of `false` for this method. + + # @!method diffable? + # @return [Boolean] true if `actual` and `expected` can be diffed. + # Indicates that this matcher provides `actual` and `expected` attributes, + # and that the values returned by these can be usefully diffed, which can + # be included in the output. + + # @!method actual + # @return [String, Object] If an object (rather than a string) is provided, + # RSpec will use the `pp` library to convert it to multi-line output in + # order to diff. + # The actual value for the purposes of a diff. + # @note This method is required if `diffable?` returns true. + + # @!method expected + # @return [String, Object] If an object (rather than a string) is provided, + # RSpec will use the `pp` library to convert it to multi-line output in + # order to diff. + # The expected value for the purposes of a diff. + # @note This method is required if `diffable?` returns true. + + # @!endgroup + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.yardopts new file mode 100644 index 0000000000..9555b8e5c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/.yardopts @@ -0,0 +1,6 @@ +--exclude features +--no-private +--markup markdown +- +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/Changelog.md new file mode 100644 index 0000000000..9f6ce9bc59 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/Changelog.md @@ -0,0 +1,1302 @@ +### Development +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.12.1...3-12-maintenance) + +### 3.12.2 / 2023-01-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.12.1...v3.12.2) + +Bug Fixes: + +* Prevent deprecation warning when using the `exist` matcher with `Dir`. + (Steve Dierker, #1398) + +### 3.12.1 / 2022-12-16 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.12.0...v3.12.1) + +Bug Fixes: + +* Pass keyword arguments through to aliased (and thus negated) matchers. (Jon Rowe, #1394) +* When handling failures in an aggregated_failures block (or example) prevent + the failure list leaking out. (Maciek Rząsa, #1392) + +### 3.12.0 / 2022-10-26 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.11.1...v3.12.0) + +Enhancements: + +* Add `an_array_matching` alias for `match_array` to improve readability as an argument + matcher. (Mark Schneider, #1361) + +### 3.11.1 / 2022-09-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.11.0...v3.11.1) + +Bug Fixes: + +* Allow the `contain_exactly` matcher to be reused by resetting its + internals on `matches?` (@bclayman-sq, #1326) +* Using the exist matcher on `FileTest` no longer produces a deprecation warning. + (Ryo Nakamura, #1383) + +### 3.11.0 / 2022-02-09 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.2...v3.11.0) + +Enhancements: + +* Return `true` from `aggregate_failures` when no exception occurs. (Jon Rowe, #1225) + +Deprecations: + +* Print a deprecation message when using the implicit block expectation syntax. + (Phil Pirozhkov, #1139) + +### 3.10.2 / 2022-01-14 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.1...v3.10.2) + +Bug Fixes: + +* Fix support for dynamic matchers for expectation target checks (Phil Pirozhkov, #1294) +* Fix `expect(array).to include(hash).times`, previously this would fail due to + matching the entire array as a single hash, rather than a member of the hash. + (Slava Kardakov, #1322) +* Ensure `raise_error` matches works with the `error_highlight` option from Ruby 3.1. + (Peter Goldstein, #1339) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Allow JRuby 9.2.x.x to generate backtraces normally rather than via our + backfill workaround. (#1230, Jon Rowe) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.10.0) + +Enhancements: + +* Allow `include` matcher to be chained with `once`, `at_least`, etc. for simple cases. + (Marc-André Lafortune, #1168) +* Add an explicit warning when `nil` is passed to `raise_error`. (Phil Pirozhkov, #1143) +* Improve `include` matcher's composability. (Phil Pirozhkov, #1155) +* Mocks expectations can now set a custom failure message. + (Benoit Tigeot and Nicolas Zermati, #1156) +* `aggregate_failures` now shows the backtrace line for each failure. (Fabricio Bedin, #1163) +* Support multiple combinations of `yield_control` modifiers like `at_least`, `at_most`. + (Jon Rowe, #1169) +* Dynamic `have_` matchers now have output consistent with other dynamic matchers. + (Marc-André Lafortune, #1195) +* New config option `strict_predicate_matchers` allows predicate matcher to be strict + (i.e. match for `true` or `false`) instead of the default (match truthy vs `false` or `nil`). + (Marc-André Lafortune, #1196) + +### 3.9.4 / 2020-10-29 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.9.4) + +Bug Fixes: + +* Fix regression with `be_` and `have_` matchers and arguments implementing `to_hash` + were they would act like keywords and be cast to a hash. (Jon Rowe, #1222) + +### 3.9.3 / 2020-10-23 + +Bug Fixes: + +* Swap the comparison of the delta vs the expected for the `be_within` matcher allowing + more complicated oobjects to be compared providing they provide `abs` and other + comparison methods. (Kelly Stannard, #1182) +* Properly format expected in the description of the `be_within` matcher. (Jon Rowe, #1185) +* Remove warning when using keyword arguments with `be_` and `have_` matchers on 2.7.x + (Jon Rowe, #1187) +* Prevent formatting a single hash as a list of key value pairs in default failure messages + for custom matches (fixes formatting in `EnglishPhrasing#list`). (Robert Eshleman, #1193) +* Prevent errors from causing false positives when using `be ` comparison, e.g. + `expect(1).not_to be < 'a'` will now correctly fail rather than pass. (Jon Rowe, #1208) + + +### 3.9.2 / 2020-05-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Issue a proper `ArgumentError` when invalid arguments are given to `yield_control` + modifiers such as `at_least` et al. (Marc-André Lafortune, #1167) +* Prevent Ruby 2.7 keyword arguments warning from being issued by custom + matcher definitions. (Jon Rowe, #1176) + +### 3.9.1 / 2020-03-13 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Issue an improved warning when using `respond_to(...).with(n).arguments` and ignore + the warning when using with `have_attributes(...)`. (Jon Rowe, #1164) + +### 3.9.0 / 2019-10-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.6...v3.9.0) + +Enhancements: + +* The `respond_to` matcher now uses the signature from `initialize` to validate checks + for `new` (unless `new` is non standard). (Jon Rowe, #1072) +* Generated descriptions for matchers now use `is expected to` rather than `should` in + line with our preferred DSL. (Pete Johns, #1080, rspec/rspec-core#2572) +* Add the ability to re-raise expectation errors when matching + with `match_when_negated` blocks. (Jon Rowe, #1130) +* Add a warning when an empty diff is produce due to identical inspect output. + (Benoit Tigeot, #1126) + +### 3.8.6 / 2019-10-07 + +Bug Fixes: + +* Revert #1125 due to the change being incompatible with our semantic versioning + policy. + +### 3.8.5 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.4...v3.8.5) + +Bug Fixes: + +* Prevent unsupported implicit block expectation syntax from being used. + (Phil Pirozhkov, #1125) + +### 3.8.4 / 2019-06-10 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.3...v3.8.4) + +Bug Fixes: + +* Prevent false negatives when checking objects for the methods required to run the + the `be_an_instance_of` and `be_kind_of` matchers. (Nazar Matus, #1112) + +### 3.8.3 / 2019-04-20 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.2...v3.8.3) + +Bug Fixes: + +* Prevent composed `all` matchers from leaking into their siblings leading to duplicate + failures. (Jamie English, #1086) +* Prevent objects which change their hash on comparison from failing change checks. + (Phil Pirozhkov, #1100) +* Issue an `ArgumentError` rather than a `NoMethodError` when `be_an_instance_of` and + `be_kind_of` matchers encounter objects not supporting those methods. + (Taichi Ishitani, #1107) + +### 3.8.2 / 2018-10-09 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Change `include` matcher to rely on a `respond_to?(:include?)` check rather than a direct + Hash comparison before calling `to_hash` to convert to a hash. (Jordan Owens, #1073) +* Prevent unexpected call stack jumps from causing an obscure error (`IndexError`), and + replace that error with a proper informative message. (Jon Rowe, #1076) + +### 3.8.1 / 2018-08-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Fix regression in `include` matcher so stopped + `expect(hash.with_indifferent_access).to include(:symbol_key)` + from working. (Eito Katagiri, #1069) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.7.0...v3.8.0) + +Enhancements: + +* Improve failure message of `change(receiver, :message)` by including the + receiver as `SomeClass#some_message`. (Tomohiro Hashidate, #1005) +* Improve `change` matcher so that it can correctly detect changes in + deeply nested mutable objects (such as arrays-of-hashes-of-arrays). + The improved logic uses the before/after `hash` value to see if the + object has been mutated, rather than shallow duping the object. + (Myron Marston, #1034) +* Improve `include` matcher so that pseudo-hash objects (e.g. objects + that decorate a hash using a `SimpleDelegator` or similar) are treated + as a hash, as long as they implement `to_hash`. (Pablo Brasero, #1012) +* Add `max_formatted_output_length=` to configuration, allowing changing + the length at which we truncate large output strings. + (Sam Phippen #951, Benoit Tigeot #1056) +* Improve error message when passing a matcher that doesn't support block + expectations to a block based `expect`. (@nicktime, #1066) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #997) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta2...v3.6.0) + +Enhancements: + +* Treat NoMethodError as a failure for comparison matchers. (Jon Rowe, #972) +* Allow for scoped aliased and negated matchers--just call + `alias_matcher` or `define_negated_matcher` from within an example + group. (Markus Reiter, #974) +* Improve failure message of `change` matcher with block and `satisfy` matcher + by including the block snippet instead of just describing it as `result` or + `block` when Ripper is available. (Yuji Nakayama, #987) + +Bug Fixes: + +* Fix `yield_with_args` and `yield_successive_args` matchers so that + they compare expected to actual args at the time the args are yielded + instead of at the end, in case the method that is yielding mutates the + arguments after yielding. (Alyssa Ross, #965) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta1...v3.6.0.beta2) + +Bug Fixes: + +* Using the exist matcher on `File` no longer produces a deprecation warning. + (Jon Rowe, #954) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Fix `contain_exactly` to work correctly with ranges. (Myron Marston, #940) +* Fix `change` to work correctly with sets. (Marcin Gajewski, #939) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Add support for keyword arguments to the `respond_to` matcher. (Rob Smith, #915). + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta3...v3.5.0.beta4) + +Bug Fixes: + +* Fix `include` matcher so that it provides a valid diff for hashes. (Yuji Nakayama, #916) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Make `rspec/expectations/minitest_integration` work on Minitest::Spec + 5.6+. (Myron Marston, #904) +* Add an alias `having_attributes` for `have_attributes` matcher. + (Yuji Nakayama, #905) +* Improve `change` matcher error message when block is mis-used. + (Alex Altair, #908) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Add the ability to raise an error on encountering false positives via + `RSpec::Configuration#on_potential_false_positives = :raise`. (Jon Rowe, #900) +* When using the custom matcher DSL, support new + `notify_expectation_failures: true` option for the `match` method to + allow expectation failures to be raised as normal instead of being + converted into a `false` return value for `matches?`. (Jon Rowe, #892) + +Bug Fixes: + +* Allow `should` deprecation check to work on `BasicObject`s. (James Coleman, #898) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.4.0...v3.5.0.beta1) + +Enhancements: + +* Make `match_when_negated` in custom matcher DSL support use of + expectations within the match logic. (Chris Arcand, #789) + +Bug Fixes: + +* Return `true` as expected from passing negated expectations + (such as `expect("foo").not_to eq "bar"`), so they work + properly when used within a `match` or `match_when_negated` + block. (Chris Arcand, #789) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.3.1...v3.4.0) + +Enhancements: + +* Warn when `RSpec::Matchers` is included in a superclass after it has + already been included in a subclass on MRI 1.9, since that situation + can cause uses of `super` to trigger infinite recursion. (Myron Marston, #816) +* Stop rescuing `NoMemoryError`, `SignalExcepetion`, `Interrupt` and + `SystemExit`. It is dangerous to interfere with these. (Myron Marston, #845) +* Add `#with_captures` to the + [match matcher](https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/match-matcher) + which allows a user to specify expected captures when matching a regex + against a string. (Sam Phippen, #848) +* Always print compound failure messages in the multi-line form. Trying + to print it all on a single line didn't read very well. (Myron Marston, #859) + +Bug Fixes: + +* Fix failure message from dynamic predicate matchers when the object + does not respond to the predicate so that it is inspected rather + than relying upon its `to_s` -- that way for `nil`, `"nil"` is + printed rather than an empty string. (Myron Marston, #841) +* Fix SystemStackError raised when diffing an Enumerable object + whose `#each` includes the object itself. (Yuji Nakayama, #857) + +### 3.3.1 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Fix `be >`, `be <`, etc so that it fails rather than allowing an + argument error to be raised when compared against an object of the + wrong type. This allows it to be used in composed matcher expressions + against heterogeneous objects. (Dennis Günnewig, #809) +* Fix `respond_to` to work properly on target objects + that redefine the `method` method. (unmanbearpig, #821) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.1...v3.3.0) + +Enhancements: + +* Expose `RSpec::Matchers::EnglishPhrasing` to make it easier to write + nice failure messages in custom matchers. (Jared Beck, #736) +* Add `RSpec::Matchers::FailMatchers`, a mixin which provides + `fail`, `fail_with` and `fail_including` matchers for use in + specifying that an expectation fails for use by + extension/plugin authors. (Charlie Rudolph, #729) +* Avoid loading `tempfile` (and its dependencies) unless + it is absolutely needed. (Myron Marston, #735) +* Improve failure output when attempting to use `be_true` or `be_false`. + (Tim Wade, #744) +* Define `RSpec::Matchers#respond_to_missing?` so that + `RSpec::Matchers#respond_to?` and `RSpec::Matchers#method` handle + dynamic predicate matchers. (Andrei Botalov, #751) +* Use custom Time/DateTime/BigDecimal formatting for all matchers + so they are consistently represented in failure messages. + (Gavin Miller, #740) +* Add configuration to turn off warnings about matcher combinations that + may cause false positives. (Jon Rowe, #768) +* Warn when using a bare `raise_error` matcher that you may be subject to + false positives. (Jon Rowe, #768) +* Warn rather than raise when using the`raise_error` matcher in negative + expectations that may be subject to false positives. (Jon Rowe, #775) +* Improve failure message for `include(a, b, c)` so that if `a` and `b` + are included the failure message only mentions `c`. (Chris Arcand, #780) +* Allow `satisfy` matcher to take an optional description argument + that will be used in the `description`, `failure_message` and + `failure_message_when_negated` in place of the undescriptive + "sastify block". (Chris Arcand, #783) +* Add new `aggregate_failures` API that allows multiple independent + expectations to all fail and be listed in the failure output, rather + than the example aborting on the first failure. (Myron Marston, #776) +* Improve `raise_error` matcher so that it can accept a matcher as a single argument + that matches the message. (Time Wade, #782) + +Bug Fixes: + +* Make `contain_exactly` / `match_array` work with strict test doubles + that have not defined `<=>`. (Myron Marston, #758) +* Fix `include` matcher so that it omits the diff when it would + confusingly highlight items that are actually included but are not + an exact match in a line-by-line diff. (Tim Wade, #763) +* Fix `match` matcher so that it does not blow up when matching a string + or regex against another matcher (rather than a string or regex). + (Myron Marston, #772) +* Silence whitespace-only diffs. (Myron Marston, #801) + +### 3.2.1 / 2015-04-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Prevent `Range`s from being enumerated when generating matcher + descriptions. (Jon Rowe, #755) +* Ensure exception messages are compared as strings in the `raise_error` + matcher. (Jon Rowe, #755) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.2...v3.2.0) + +Enhancements: + +* Add `block_arg` method to custom matcher API, which allows you to + access the block passed to a custom matcher, if there is one. + (Mike Dalton, #645) +* Provide more detail in failure message of `yield_control` matcher. + (Jon Rowe, #650) +* Add a shorthand syntax for `chain` in the matcher DSL which assigns values + for use elsewhere, for example `chain :and_smaller_than, :small_value` + creates an `attr_reader` for `small_value` (Tom Stuart, #644) +* Provide a more helpful deprecation message when using the `should` syntax. + (Elia Schito, #663) +* Provide more detail in the `have_attributes` matcher failure message. + (Jon Rowe, #668) +* Make the `have_attributes` matcher diffable. + (Jon Rowe, Alexey Fedorov, #668) +* Add `output(...).to_std(out|err)_from_any_process` as alternatives + to `output(...).to_std(out|err)`. The latter doesn't work when a sub + process writes to the named stream but is much faster. + (Alex Genco, #700) +* Improve compound matchers (created by `and` and `or`) so that diffs + are included in failures when one or more of their matchers + are diffable. (Alexey Fedorov, #713) + +Bug Fixes: + +* Avoid calling `private_methods` from the `be` predicate matcher on + the target object if the object publicly responds to the predicate + method. This avoids a possible error that can occur if the object + raises errors from `private_methods` (which can happen with celluloid + objects). (@chapmajs, #670) +* Make `yield_control` (with no modifier) default to + `at_least(:once)` rather than raising a confusing error + when multiple yields are encountered. + (Myron Marston, #675) +* Fix "instance variable @color not initialized" warning when using + rspec-expectations outside of an rspec-core context. (Myron Marston, #689) +* Fix `start_with` and `end_with` to work properly when checking a + string against an array of strings. (Myron Marston, #690) +* Don't use internally delegated matchers when generating descriptions + for examples without doc strings. (Myron Marston, #692) + +### 3.1.2 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix `define_negated_matcher` so that matchers that support fluent + interfaces continue to be negated after you use the chained method. + (Myron Marston, #656) +* Fix `define_negated_matcher` so that the matchers fail with an + appropriate failure message. (Myron Marston, #659) + +### 3.1.1 / 2014-09-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix regression in `all` matcher in 3.1.0 that prevented it from + working on objects that are not `Enumerable` but do implement + `each_with_index` (such as an ActiveRecord proxy). (Jori Hardman, #647) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Add `have_attributes` matcher, that passes if actual's attribute + values match the expected attributes hash: + `Person = Struct.new(:name, :age)` + `person = Person.new("Bob", 32)` + `expect(person).to have_attributes(:name => "Bob", :age => 32)`. + (Adam Farhi, #571) +* Extended compound matcher support to block matchers, for cases like: + `expect { ... }.to change { x }.to(3).and change { y }.to(4)`. (Myron + Marston, #567) +* Include chained methods in custom matcher description and failure message + when new `include_chain_clauses_in_custom_matcher_descriptions` config + option is enabled. (Dan Oved, #600) +* Add `thrice` modifier to `yield_control` matcher as a synonym for + `exactly(3).times`. (Dennis Taylor, #615) +* Add `RSpec::Matchers.define_negated_matcher`, which defines a negated + version of the named matcher. (Adam Farhi, Myron Marston, #618) +* Document and support negation of `contain_exactly`/`match_array`. + (Jon Rowe, #626). + +Bug Fixes: + +* Rename private `LegacyMacherAdapter` constant to `LegacyMatcherAdapter` + to fix typo. (Abdelkader Boudih, #563) +* Fix `all` matcher so that it fails properly (rather than raising a + `NoMethodError`) when matched against a non-enumerable. (Hao Su, #622) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix `start_with` and `end_with` so that they work properly with + structs. (Myron Marston, #620) +* Fix failure message generation so that structs are printed properly + in failures. Previously failure messages would represent them as + an array. (Myron Marston, #620) +* Fix composable matcher support so that it does not wrongly treat + structs as arrays. (Myron Marston, #620) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Fix issue with detection of generic operator matchers so they work + correctly when undefined. (Myron Marston, #597) +* Don't inadvertently define `BasicObject` in 1.8.7. (Chris Griego, #603) +* Fix `include` matcher so that it fails gracefully when matched against + an object that does not respond to `include?`. (Myron Marston, #607) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix regression in `contain_exactly` (AKA `match_array`) that caused it + to wrongly pass when the expected array was empty. (Myron Marston, #581) +* Provide a better error message when you use the `change(obj, :msg)` + form of the change matcher but forget the message argument. (Alex + Sunderland, #585) +* Make the `contain_exactly` matcher work with arrays that contain hashes in + arbitrary ordering. (Sam Phippen, #578) + +### 3.0.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Add a missing `require` that would cause the `respond_to` matcher to + fail when used in a project where the rest of RSpec (e.g. core and + expecatations) weren't being used. (Myron Marston, #566) +* Structs are no longer treated as arrays when diffed. (Jon Rowe, #576) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.rc1...v3.0.0) + +No code changes. Just taking it out of pre-release. + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Remove `matcher_execution_context` attribute from DSL-defined + custom matchers. (Myron Marston) +* Remove `RSpec::Matchers::Pretty#_pretty_print`. (Myron Marston) +* Remove `RSpec::Matchers::Pretty#expected_to_sentence`. (Myron Marston) +* Rename `RSpec::Matchers::Configuration` constant to + `RSpec::Expectations::Configuration`. (Myron Marston) +* Prevent `have_xyz` predicate matchers using private methods. + (Adrian Gonzalez) +* Block matchers must now implement `supports_block_expectations?`. + (Myron Marston) +* Stop supporting `require 'rspec-expectations'`. + Use `require 'rspec/expectations'` instead. (Myron Marston) + +Bug Fixes: + +* Fix `NoMethodError` triggered by beta2 when `YARD` was loaded in + the test environment. (Myron Marston) +* Fix `be_xyz` matcher to accept a `do...end` block. (Myron Marston) +* Fix composable matcher failure message generation logic + so that it does not blow up when given `$stdout` or `$stderr`. + (Myron Marston) +* Fix `change` matcher to work properly with `IO` objects. + (Myron Marston) +* Fix `exist` matcher so that it can be used in composed matcher + expressions involving objects that do not implement `exist?` or + `exists?`. (Daniel Fone) +* Fix composable matcher match logic so that it clones matchers + before using them in order to work properly with matchers + that use internal memoization based on a given `actual` value. + (Myron Marston) +* Fix `be_xyz` and `has_xyz` predicate matchers so that they can + be used in composed matcher expressions involving objects that + do not implement the predicate method. (Daniel Fone) + +Enhancements: + +* Document the remaining public APIs. rspec-expectations now has 100% of + the public API documented and will remain that way (as new undocumented + methods will fail the build). (Myron Marston) +* Improve the formatting of BigDecimal objects in `eq` matcher failure + messages. (Daniel Fone) +* Improve the failure message for `be_xyz` predicate matchers so + that it includes the `inspect` output of the receiver. + (Erik Michaels-Ober, Sam Phippen) +* Add `all` matcher, to allow you to specify that a given matcher + matches all elements in a collection: + `expect([1, 3, 5]).to all( be_odd )`. (Adam Farhi) +* Add boolean aliases (`&`/`|`) for compound operators (`and`/`or`). (Adam Farhi) +* Give users a clear error when they wrongly use a value matcher + in a block expectation expression (e.g. `expect { 3 }.to eq(3)`) + or vice versa. (Myron Marston) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Remove deprecated support for accessing the `RSpec` constant using + `Rspec` or `Spec`. (Myron Marston) +* Remove deprecated `RSpec::Expectations.differ=`. (Myron Marston) +* Remove support for deprecated `expect(...).should`. (Myron Marston) +* Explicitly disallow `expect { }.not_to change { }` with `by`, + `by_at_least`, `by_at_most` or `to`. These have never been supported + but did not raise explicit errors. (Myron Marston) +* Provide `===` rather than `==` as an alias of `matches?` for + all matchers. The semantics of `===` are closer to an RSpec + matcher than `==`. (Myron Marston) +* Remove deprecated `RSpec::Matchers::OperatorMatcher` constant. + (Myron Marston) +* Make `RSpec::Expectations::ExpectationNotMetError` subclass + `Exception` rather than `StandardError` so they can bypass + a bare `rescue` in end-user code (e.g. when an expectation is + set from within a rspec-mocks stub implementation). (Myron Marston) +* Remove Test::Unit and Minitest 4.x integration. (Myron Marston) + +Enhancements: + +* Simplify the failure message of the `be` matcher when matching against: + `true`, `false` and `nil`. (Sam Phippen) +* Update matcher protocol and custom matcher DSL to better align + with the newer `expect` syntax. If you want your matchers to + maintain compatibility with multiple versions of RSpec, you can + alias the new names to the old. (Myron Marston) + * `failure_message_for_should` => `failure_message` + * `failure_message_for_should_not` => `failure_message_when_negated` + * `match_for_should` => `match` + * `match_for_should_not` => `match_when_negated` +* Improve generated descriptions from `change` matcher. (Myron Marston) +* Add support for compound matcher expressions using `and` and `or`. + Simply chain them off of any existing matcher to create an expression + like `expect(alphabet).to start_with("a").and end_with("z")`. + (Eloy Espinaco) +* Add `contain_exactly` as a less ambiguous version of `match_array`. + Note that it expects the expected array to be splatted as + individual args: `expect(array).to contain_exactly(1, 2)` is + the same as `expect(array).to match_array([1, 2])`. (Myron Marston) +* Update `contain_exactly`/`match_array` so that it can match against + other non-array collections (such as a `Set`). (Myron Marston) +* Update built-in matchers so that they can accept matchers as arguments + to allow you to compose matchers in arbitrary ways. (Myron Marston) +* Add `RSpec::Matchers::Composable` mixin that can be used to make + a custom matcher composable as well. Note that custom matchers + defined via `RSpec::Matchers.define` already have this. (Myron + Marston) +* Define noun-phrase aliases for built-in matchers, which can be + used when creating composed matcher expressions that read better + and provide better failure messages. (Myron Marston) +* Add `RSpec::Matchers.alias_matcher` so users can define their own + matcher aliases. The `description` of the matcher will reflect the + alternate matcher name. (Myron Marston) +* Add explicit `be_between` matcher. `be_between` has worked for a + long time as a dynamic predicate matcher, but the failure message + was suboptimal. The new matcher provides a much better failure + message. (Erik Michaels-Ober) +* Enhance the `be_between` matcher to allow for `inclusive` or `exclusive` + comparison (e.g. inclusive of min/max or exclusive of min/max). + (Pedro Gimenez) +* Make failure message for `not_to be #{operator}` less confusing by + only saying it's confusing when comparison operators are used. + (Prathamesh Sonpatki) +* Improve failure message of `eq` matcher when `Time` or `DateTime` + objects are used so that the full sub-second precision is included. + (Thomas Holmes, Jeff Wallace) +* Add `output` matcher for expecting that a block outputs `to_stdout` + or `to_stderr`. (Luca Pette, Matthias Günther) +* Forward a provided block on to the `has_xyz?` method call when + the `have_xyz` matcher is used. (Damian Galarza) +* Provide integration with Minitest 5.x. Require + `rspec/expectations/minitest_integration` after loading minitest + to use rspec-expectations with minitest. (Myron Marston) + +Bug Fixes: + +* Fix wrong matcher descriptions with falsey expected value (yujinakayama) +* Fix `expect { }.not_to change { }.from(x)` so that the matcher only + passes if the starting value is `x`. (Tyler Rick, Myron Marston) +* Fix hash diffing, so that it colorizes properly and doesn't consider trailing + commas when performing the diff. (Jared Norman) +* Fix built-in matchers to fail normally rather than raising + `ArgumentError` when given an object of the wrong type to match + against, so that they work well in composite matcher expressions like + `expect([1.51, "foo"]).to include(a_string_matching(/foo/), a_value_within(0.1).of(1.5))`. + (Myron Marston) + +Deprecations: + +* Retain support for RSpec 2 matcher protocol (e.g. for matchers + in 3rd party extension gems like `shoulda`), but it will print + a deprecation warning. (Myron Marston) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.2...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Remove explicit support for 1.8.6. (Jon Rowe) +* Remove the deprecated `be_close` matcher, preferring `be_within` instead. + (Sam Phippen) +* Remove the deprecated `have`, `have_at_least` and `have_at_most` matchers. + You can continue using those matchers through https://github.com/rspec/rspec-collection_matchers, + or you can rewrite your expectations with something like + `expect(your_object.size).to eq(num)`. (Hugo Baraúna) +* Rename `be_true` and `be_false` to `be_truthy` and `be_falsey`. (Sam Phippen) +* Make `expect { }.to_not raise_error(SomeSpecificClass, message)`, + `expect { }.to_not raise_error(SomeSpecificClass)` and + `expect { }.to_not raise_error(message)` invalid, since they are prone + to hiding failures. Instead, use `expect { }.to_not raise_error` (with no + args). (Sam Phippen) +* Within `RSpec::Matchers.define` blocks, helper methods made available + either via `def self.helper` or `extend HelperModule` are no longer + available to the `match` block (or any of the others). Instead + `include` your helper module and define the helper method as an + instance method. (Myron Marston) +* Force upgrading Diff::LCS for encoding compatability with diffs. (Jon Rowe) + +Enhancements: + +* Support `do..end` style block with `raise_error` matcher. (Yuji Nakayama) +* Rewrote custom matcher DSL to simplify its implementation and solve a + few issues. (Myron Marston) +* Allow early `return` from within custom matcher DSL blocks. (Myron + Marston) +* The custom matcher DSL's `chain` can now accept a block. (Myron + Marston) +* Support setting an expectation on a `raise_error` matcher via a chained + `with_message` method call. (Sam Phippen) + +Bug Fixes: + +* Allow `include` and `match` matchers to be used from within a + DSL-defined custom matcher's `match` block. (Myron Marston) +* Correct encoding error message on diff failure (Jon Rowe) + +Deprecations: + + * Using the old `:should` syntax without explicitly configuring it is deprecated. + It will continue to work but will emit a deprecation warning in RSpec 3 if + you do not explicitly enable it. (Sam Phippen) + +### 2.99.2 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.1...v2.99.2) + +Bug Fixes: + +* Fix regression in `Expectations#method_handle_for` where proxy objects + with method delegated would wrongly not return a method handle. + (Jon Rowe, #594) +* Fix issue with detection of generic operator matchers so they work + correctly when undefined. (Myron Marston, #597) + +### 2.99.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Fix typo in custom matcher `expected` deprecation warning -- it's + `expected_as_array`, not `expected_array`. (Frederick Cheung, #562) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.rc1...v2.99.0) + +Enhancements: + +* Special case deprecation message for `errors_on` with `rspec-rails` to be more useful. + (Aaron Kromer) + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.beta2...2.99.0.rc1) + +Deprecations: + +* Deprecate `matcher_execution_context` attribute on DSL-defined + custom matchers. (Myron Marston) +* Deprecate `RSpec::Matchers::Pretty#_pretty_print`. (Myron Marston) +* Deprecate `RSpec::Matchers::Pretty#expected_to_sentence`. (Myron Marston) +* Deprecate `RSpec::Matchers::Configuration` in favor of + `RSpec::Expectations::Configuration`. (Myron Marston) +* Deprecate `be_xyz` predicate matcher on an object that doesn't respond to + `xyz?` or `xyzs?`. (Daniel Fone) +* Deprecate `have_xyz` matcher on an object that doesn't respond to `has_xyz?`. + (Daniel Fone) +* Deprecate `have_xyz` matcher on an object that has a private method `has_xyz?`. + (Jon Rowe) +* Issue a deprecation warning when a block expectation expression is + used with a matcher that doesn't explicitly support block expectations + via `supports_block_expectations?`. (Myron Marston) +* Deprecate `require 'rspec-expectations'`. Use + `require 'rspec/expectations'` instead. (Myron Marston) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.99.0.beta1...v2.99.0.beta2) + +Deprecations: + +* Deprecate chaining `by`, `by_at_least`, `by_at_most` or `to` off of + `expect { }.not_to change { }`. The docs have always said these are + not supported for the negative form but now they explicitly raise + errors in RSpec 3. (Myron Marston) +* Change the semantics of `expect { }.not_to change { x }.from(y)`. + In RSpec 2.x, this expectation would only fail if `x` started with + the value of `y` and changed. If it started with a different value + and changed, it would pass. In RSpec 3, it will pass only if the + value starts at `y` and it does not change. (Myron Marston) +* Deprecate `matcher == value` as an alias for `matcher.matches?(value)`, + in favor of `matcher === value`. (Myron Marston) +* Deprecate `RSpec::Matchers::OperatorMatcher` in favor of + `RSpec::Matchers::BuiltIn::OperatorMatcher`. (Myron Marston) +* Deprecate auto-integration with Test::Unit and minitest. + Instead, include `RSpec::Matchers` in the appropriate test case + base class yourself. (Myron Marston) +* Deprecate treating `#expected` on a DSL-generated custom matcher + as an array when only 1 argument is passed to the matcher method. + In RSpec 3 it will be the single value in order to make diffs + work properly. (Jon Rowe) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.4...v2.99.0.beta1) + +Deprecations + +* Deprecate `have`, `have_at_least` and `have_at_most`. You can continue using those + matchers through https://github.com/rspec/rspec-collection_matchers, or + you can rewrite your expectations with something like + `expect(your_object.size).to eq(num)`. (Hugo Baraúna) +* Deprecate `be_xyz` predicate matcher when `xyz?` is a private method. + (Jon Rowe) +* Deprecate `be_true`/`be_false` in favour of `be_truthy`/`be_falsey` + (for Ruby's conditional semantics) or `be true`/`be false` + (for exact equality). (Sam Phippen) +* Deprecate calling helper methods from a custom matcher with the wrong + scope. (Myron Marston) + * `def self.foo` / `extend Helper` can be used to add macro methods + (e.g. methods that call the custom matcher DSL methods), but should + not be used to define helper methods called from within the DSL + blocks. + * `def foo` / `include Helper` is the opposite: it's for helper methods + callable from within a DSL block, but not for defining macros. + * RSpec 2.x allowed helper methods defined either way to be used for + either purpose, but RSpec 3.0 will not. + +### 2.14.5 / 2014-02-01 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.4...v2.14.5) + +Bug fixes + +* Fix wrong matcher descriptions with falsey expected value + (yujinakayama) + +### 2.14.4 / 2013-11-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.3...v2.14.4) + +Bug fixes + +* Make the `match` matcher produce a diff output. (Jon Rowe, Ben Moss) +* Choose encoding for diff's more intelligently, and when all else fails fall + back to default internal encoding with replacing characters. (Jon Rowe) + +### 2.14.3 / 2013-09-22 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.2...v2.14.3) + +Bug fixes + +* Fix operator matchers (`should` syntax) when `method` is redefined on target. + (Brandon Turner) +* Fix diffing of hashes with object based keys. (Jon Rowe) +* Fix operator matchers (`should` syntax) when operator is defined via + `method_missing` (Jon Rowe) + +### 2.14.2 / 2013-08-14 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.1...v2.14.2) + +Bug fixes + +* Fix `be_` matcher to not support operator chaining like the + `be` matcher does (e.g. `be == 5`). This led to some odd behaviors + since `be_ == anything` returned a `BeComparedTo` matcher + and was thus always truthy. This was a consequence of the implementation + (e.g. subclassing the basic `Be` matcher) and was not intended behavior. + (Myron Marston). +* Fix `change` matcher to compare using `==` in addition to `===`. This + is important for an expression like: + `expect {}.to change { a.class }.from(ClassA).to(ClassB)` because + `SomeClass === SomeClass` returns false. (Myron Marston) + +### 2.14.1 / 2013-08-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.0...2.14.1) + +Bug fixes + +* Ensure diff output uses the same encoding as the encoding of + the string being diff'd to prevent `Encoding::UndefinedConversionError` + errors (Jon Rowe). + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.14.0.rc1...v2.14.0) + +Bug fixes + +* Values that are not matchers use `#inspect`, rather than `#description` for + documentation output (Andy Lindeman, Sam Phippen). +* Make `expect(a).to be_within(x).percent_of(y)` work with negative y + (Katsuhiko Nishimra). +* Make the `be_predicate` matcher work as expected used with `expect{...}.to + change...` (Sam Phippen). + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.13.0...v2.14.0.rc1) + +Enhancements + +* Enhance `yield_control` so that you can specify an exact or relative + number of times: `expect { }.to yield_control.exactly(3).times`, + `expect { }.to yield_control.at_least(2).times`, etc (Bartek + Borkowski). +* Make the differ that is used when an expectation fails better handle arrays + by splitting each element of the array onto its own line. (Sam Phippen) +* Accept duck-typed strings that respond to `:to_str` as expectation messages. + (Toby Ovod-Everett) + +Bug fixes + +* Fix differ to not raise errors when dealing with differently-encoded + strings (Jon Rowe). +* Fix `expect(something).to be_within(x).percent_of(y)` where x and y are both + integers (Sam Phippen). +* Fix `have` matcher to handle the fact that on ruby 2.0, + `Enumerator#size` may return nil (Kenta Murata). +* Fix `expect { raise s }.to raise_error(s)` where s is an error instance + on ruby 2.0 (Sam Phippen). +* Fix `expect(object).to raise_error` passing. This now warns the user and + fails the spec (tomykaira). + +Deprecations + +* Deprecate `expect { }.not_to raise_error(SpecificErrorClass)` or + `expect { }.not_to raise_error("some specific message")`. Using + these was prone to hiding failures as they would allow _any other + error_ to pass. (Sam Phippen and David Chelimsky) + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.12.1...v2.13.0) + +Enhancements + +* Add support for percent deltas to `be_within` matcher: + `expect(value).to be_within(10).percent_of(expected)` + (Myron Marston). +* Add support to `include` matcher to allow it to be given a list + of matchers as the expecteds to match against (Luke Redpath). + +Bug fixes + +* Fix `change` matcher so that it dups strings in order to handle + mutated strings (Myron Marston). +* Fix `should be =~ /some regex/` / `expect(...).to be =~ /some regex/`. + Previously, these either failed with a confusing `undefined method + matches?' for false:FalseClass` error or were no-ops that didn't + actually verify anything (Myron Marston). +* Add compatibility for diff-lcs 1.2 and relax the version + constraint (Peter Goldstein). +* Fix DSL-generated matchers to allow multiple instances of the + same matcher in the same example to have different description + and failure messages based on the expected value (Myron Marston). +* Prevent `undefined method #split for Array` error when dumping + the diff of an array of multiline strings (Myron Marston). +* Don't blow up when comparing strings that are in an encoding + that is not ASCII compatible (Myron Marston). +* Remove confusing "Check the implementation of #==" message + printed for empty diffs (Myron Marston). + +### 2.12.1 / 2012-12-15 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Improve the failure message for an expression like + `{}.should =~ {}`. (Myron Marston and Andy Lindeman) +* Provide a `match_regex` alias so that custom matchers + built using the matcher DSL can use it (since `match` + is a different method in that context). + (Steven Harman) + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.3...v2.12.0) + +Enhancements + +* Colorize diffs if the `--color` option is configured. (Alex Coplan) +* Include backtraces in unexpected errors handled by `raise_error` + matcher (Myron Marston) +* Print a warning when users accidentally pass a non-string argument + as an expectation message (Sam Phippen) +* `=~` and `match_array` matchers output a more useful error message when + the actual value is not an array (or an object that responds to `#to_ary`) + (Sam Phippen) + +Bug fixes + +* Fix `include` matcher so that `expect({}).to include(:a => nil)` + fails as it should (Sam Phippen). +* Fix `be_an_instance_of` matcher so that `Class#to_s` is used in the + description rather than `Class#inspect`, since some classes (like + `ActiveRecord::Base`) define a long, verbose `#inspect`. + (Tom Stuart) + +### 2.11.3 / 2012-09-04 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.2...v2.11.3) + +Bug fixes + +* Fix (and deprecate) `expect { }.should` syntax so that it works even + though it was never a documented or intended syntax. It worked as a + consequence of the implementation of `expect` in RSpec 2.10 and + earlier. (Myron Marston) +* Ensure #== is defined on built in matchers so that they can be composed. + For example: + + expect { + user.emailed! + }.to change { user.last_emailed_at }.to be_within(1.second).of(Time.zone.now) + +### 2.11.2 / 2012-07-25 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.1...v2.11.2) + +Bug fixes + +* Define `should` and `should_not` on `Object` rather than `BasicObject` + on MacRuby. On MacRuby, `BasicObject` is defined but is not the root + of the object hierarchy. (Gabriel Gilder) + +### 2.11.1 / 2012-07-08 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Constrain `actual` in `be_within` matcher to values that respond to `-` instead + of requiring a specific type. + * `Time`, for example, is a legit alternative. + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.10.0...v2.11.0) + +Enhancements + +* Expand `expect` syntax so that it supports expections on bare values + in addition to blocks (Myron Marston). +* Add configuration options to control available expectation syntaxes + (Myron Marston): + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :expect }` + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :should }` + * `RSpec.configuration.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }` + * `RSpec.configuration.add_should_and_should_not_to Delegator` + +Bug fixes + +* Allow only `Numeric` values to be the "actual" in the `be_within` matcher. + This prevents confusing error messages. (Su Zhang @zhangsu) +* Define `should` and `should_not` on `BasicObject` rather than `Kernel` + on 1.9. This makes `should` and `should_not` work properly with + `BasicObject`-subclassed proxy objects like `Delegator`. (Myron + Marston) + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.9.1...v2.10.0) + +Enhancements + +* Add new `start_with` and `end_with` matchers (Jeremy Wadsack) +* Add new matchers for specifying yields (Myron Marston): + * `expect {...}.to yield_control` + * `expect {...}.to yield_with_args(1, 2, 3)` + * `expect {...}.to yield_with_no_args` + * `expect {...}.to yield_successive_args(1, 2, 3)` +* `match_unless_raises` takes multiple exception args + +Bug fixes + +* Fix `be_within` matcher to be inclusive of delta. +* Fix message-specific specs to pass on Rubinius (John Firebaugh) + +### 2.9.1 / 2012-04-03 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.9.0...v2.9.1) + +Bug fixes + +* Provide a helpful message if the diff between two objects is empty. +* Fix bug diffing single strings with multiline strings. +* Fix for error with using custom matchers inside other custom matchers + (mirasrael) +* Fix using execution context methods in nested DSL matchers (mirasrael) + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0...v2.9.0) + +Enhancements + +* Move built-in matcher classes to RSpec::Matchers::BuiltIn to reduce pollution + of RSpec::Matchers (which is included in every example). +* Autoload files with matcher classes to improve load time. + +Bug fixes + +* Align `respond_to?` and `method_missing` in DSL-defined matchers. +* Clear out user-defined instance variables between invocations of DSL-defined + matchers. +* Dup the instance of a DSL generated matcher so its state is not changed by + subsequent invocations. +* Treat expected args consistently across positive and negative expectations + (thanks to Ralf Kistner for the heads up) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0.rc2...v2.8.0) + +Enhancements + +* Better diff output for Hash (Philippe Creux) +* Eliminate Ruby warnings (Olek Janiszewski) + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.8.0.rc1...v2.8.0.rc2) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.7.0...v2.8.0.rc1) + +Enhancements + +* Use classes for the built-in matchers (they're faster). +* Eliminate Ruby warnings (Matijs van Zuijlen) + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.6.0...v2.7.0) + +Enhancements + +* `HaveMatcher` converts argument using `to_i` (Alex Bepple & Pat Maddox) +* Improved failure message for the `have_xxx` matcher (Myron Marston) +* `HaveMatcher` supports `count` (Matthew Bellantoni) +* Change matcher dups `Enumerable` before the action, supporting custom + `Enumerable` types like `CollectionProxy` in Rails (David Chelimsky) + +Bug fixes + +* Fix typo in `have(n).xyz` documentation (Jean Boussier) +* fix `safe_sort` for ruby 1.9.2 (`Kernel` now defines `<=>` for Object) (Peter + van Hardenberg) + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.5.0...v2.6.0) + +Enhancements + +* `change` matcher accepts regexps (Robert Davis) +* better descriptions for `have_xxx` matchers (Magnus Bergmark) +* `range.should cover(*values)` (Anders Furseth) + +Bug fixes + +* Removed non-ascii characters that were choking rcov (Geoffrey Byers) +* change matcher dups arrays and hashes so their before/after states can be + compared correctly. +* Fix the order of inclusion of RSpec::Matchers in Test::Unit::TestCase and + MiniTest::Unit::TestCase to prevent a SystemStackError (Myron Marston) + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.4.0...v2.5.0) + +Enhancements + +* `should exist` works with `exist?` or `exists?` (Myron Marston) +* `expect { ... }.not_to do_something` (in addition to `to_not`) + +Documentation + +* improved docs for raise_error matcher (James Almond) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.3.0...v2.4.0) + +No functional changes in this release, which was made to align with the +rspec-core-2.4.0 release. + +Enhancements + +* improved RDoc for change matcher (Jo Liss) + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.2.1...v2.3.0) + +Enhancements + +* diff strings when include matcher fails (Mike Sassak) + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.1.0...v2.2.0) + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.1...v2.1.0) + +Enhancements + +* `be_within(delta).of(expected)` matcher (Myron Marston) +* Lots of new Cucumber features (Myron Marston) +* Raise error if you try `should != expected` on Ruby-1.9 (Myron Marston) +* Improved failure messages from `throw_symbol` (Myron Marston) + +Bug fixes + +* Eliminate hard dependency on `RSpec::Core` (Myron Marston) +* `have_matcher` - use pluralize only when ActiveSupport inflections are indeed + defined (Josep M Bach) +* throw_symbol matcher no longer swallows exceptions (Myron Marston) +* fix matcher chaining to avoid name collisions (Myron Marston) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.rc...v2.0.0) + +Enhancements + +* Add match_for_should_not method to matcher DSL (Myron Marston) + +Bug fixes + +* `respond_to` matcher works correctly with `should_not` with multiple methods + (Myron Marston) +* `include` matcher works correctly with `should_not` with multiple values + (Myron Marston) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* `require 'rspec/expectations'` in a T::U or MiniUnit suite (Josep M. Bach) + +Bug fixes + +* change by 0 passes/fails correctly (Len Smith) +* Add description to satisfy matcher + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Enhancements + +* diffing improvements + * diff multiline strings + * don't diff single line strings + * don't diff numbers (silly) + * diff regexp + multiline string + +Bug fixes + * `should[_not]` change now handles boolean values correctly diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/LICENSE.md new file mode 100644 index 0000000000..dae02d8a7d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 David Chelimsky, Myron Marston +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-expectations-3.12.2/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/README.md new file mode 100644 index 0000000000..605c834e2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/README.md @@ -0,0 +1,320 @@ +# RSpec Expectations [![Build Status](https://github.com/rspec/rspec-expectations/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-expectations/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.svg)](https://codeclimate.com/github/rspec/rspec-expectations) + +RSpec::Expectations lets you express expected outcomes on an object in an +example. + +```ruby +expect(account.balance).to eq(Money.new(37.42, :USD)) +``` + +## Install + +If you want to use rspec-expectations with rspec, just install the rspec gem +and RubyGems will also install rspec-expectations for you (along with +rspec-core and rspec-mocks): + + gem install rspec + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +If you want to use rspec-expectations with another tool, like Test::Unit, +Minitest, or Cucumber, you can install it directly: + + gem install rspec-expectations + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +- [Build details](BUILD_DETAIL.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Detailed contributing guide](CONTRIBUTING.md) +- [Development setup guide](DEVELOPMENT.md) + +## Basic usage + +Here's an example using rspec-core: + +```ruby +RSpec.describe Order do + it "sums the prices of the items in its line items" do + order = Order.new + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(1.11, :USD) + ))) + order.add_entry(LineItem.new(:item => Item.new( + :price => Money.new(2.22, :USD), + :quantity => 2 + ))) + expect(order.total).to eq(Money.new(5.55, :USD)) + end +end +``` + +The `describe` and `it` methods come from rspec-core. The `Order`, `LineItem`, `Item` and `Money` classes would be from _your_ code. The last line of the example +expresses an expected outcome. If `order.total == Money.new(5.55, :USD)`, then +the example passes. If not, it fails with a message like: + + expected: # + got: # + +## Built-in matchers + +### Equivalence + +```ruby +expect(actual).to eq(expected) # passes if actual == expected +expect(actual).to eql(expected) # passes if actual.eql?(expected) +expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected)) +``` + +Note: The new `expect` syntax no longer supports the `==` matcher. + +### Identity + +```ruby +expect(actual).to be(expected) # passes if actual.equal?(expected) +expect(actual).to equal(expected) # passes if actual.equal?(expected) +``` + +### Comparisons + +```ruby +expect(actual).to be > expected +expect(actual).to be >= expected +expect(actual).to be <= expected +expect(actual).to be < expected +expect(actual).to be_within(delta).of(expected) +``` + +### Regular expressions + +```ruby +expect(actual).to match(/expression/) +``` + +Note: The new `expect` syntax no longer supports the `=~` matcher. + +### Types/classes + +```ruby +expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected +expect(actual).to be_a(expected) # passes if actual.kind_of?(expected) +expect(actual).to be_an(expected) # an alias for be_a +expect(actual).to be_a_kind_of(expected) # another alias +``` + +### Truthiness + +```ruby +expect(actual).to be_truthy # passes if actual is truthy (not nil or false) +expect(actual).to be true # passes if actual == true +expect(actual).to be_falsy # passes if actual is falsy (nil or false) +expect(actual).to be false # passes if actual == false +expect(actual).to be_nil # passes if actual is nil +expect(actual).to_not be_nil # passes if actual is not nil +``` + +### Expecting errors + +```ruby +expect { ... }.to raise_error +expect { ... }.to raise_error(ErrorClass) +expect { ... }.to raise_error("message") +expect { ... }.to raise_error(ErrorClass, "message") +``` + +### Expecting throws + +```ruby +expect { ... }.to throw_symbol +expect { ... }.to throw_symbol(:symbol) +expect { ... }.to throw_symbol(:symbol, 'value') +``` + +### Yielding + +```ruby +expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args + +expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded + +expect { |b| 5.tap(&b) }.to yield_with_args(5) +expect { |b| 5.tap(&b) }.to yield_with_args(Integer) +expect { |b| "a string".tap(&b) }.to yield_with_args(/str/) + +expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) +expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) +``` + +### Predicate matchers + +```ruby +expect(actual).to be_xxx # passes if actual.xxx? +expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg) +``` + +### Ranges (Ruby >= 1.9 only) + +```ruby +expect(1..10).to cover(3) +``` + +### Collection membership + +```ruby +# exact order, entire collection +expect(actual).to eq(expected) + +# exact order, partial collection (based on an exact position) +expect(actual).to start_with(expected) +expect(actual).to end_with(expected) + +# any order, entire collection +expect(actual).to match_array(expected) + +# You can also express this by passing the expected elements +# as individual arguments +expect(actual).to contain_exactly(expected_element1, expected_element2) + + # any order, partial collection +expect(actual).to include(expected) +``` + +#### Examples + +```ruby +expect([1, 2, 3]).to eq([1, 2, 3]) # Order dependent equality check +expect([1, 2, 3]).to include(1) # Exact ordering, partial collection matches +expect([1, 2, 3]).to include(2, 3) # +expect([1, 2, 3]).to start_with(1) # As above, but from the start of the collection +expect([1, 2, 3]).to start_with(1, 2) # +expect([1, 2, 3]).to end_with(3) # As above but from the end of the collection +expect([1, 2, 3]).to end_with(2, 3) # +expect({:a => 'b'}).to include(:a => 'b') # Matching within hashes +expect("this string").to include("is str") # Matching within strings +expect("this string").to start_with("this") # +expect("this string").to end_with("ring") # +expect([1, 2, 3]).to contain_exactly(2, 3, 1) # Order independent matches +expect([1, 2, 3]).to match_array([3, 2, 1]) # + +# Order dependent compound matchers +expect( + [{:a => 'hash'},{:a => 'another'}] +).to match([a_hash_including(:a => 'hash'), a_hash_including(:a => 'another')]) +``` + +## `should` syntax + +In addition to the `expect` syntax, rspec-expectations continues to support the +`should` syntax: + +```ruby +actual.should eq expected +actual.should be > 3 +[1, 2, 3].should_not include 4 +``` + +See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/main/Should.md) + +## Compound Matcher Expressions + +You can also create compound matcher expressions using `and` or `or`: + +``` ruby +expect(alphabet).to start_with("a").and end_with("z") +expect(stoplight.color).to eq("red").or eq("green").or eq("yellow") +``` + +## Composing Matchers + +Many of the built-in matchers are designed to take matchers as +arguments, to allow you to flexibly specify only the essential +aspects of an object or data structure. In addition, all of the +built-in matchers have one or more aliases that provide better +phrasing for when they are used as arguments to another matcher. + +### Examples + +```ruby +expect { k += 1.05 }.to change { k }.by( a_value_within(0.1).of(1.0) ) + +expect { s = "barn" }.to change { s } + .from( a_string_matching(/foo/) ) + .to( a_string_matching(/bar/) ) + +expect(["barn", 2.45]).to contain_exactly( + a_value_within(0.1).of(2.5), + a_string_starting_with("bar") +) + +expect(["barn", "food", 2.45]).to end_with( + a_string_matching("foo"), + a_value > 2 +) + +expect(["barn", 2.45]).to include( a_string_starting_with("bar") ) + +expect(:a => "food", :b => "good").to include(:a => a_string_matching(/foo/)) + +hash = { + :a => { + :b => ["foo", 5], + :c => { :d => 2.05 } + } +} + +expect(hash).to match( + :a => { + :b => a_collection_containing_exactly( + a_string_starting_with("f"), + an_instance_of(Integer) + ), + :c => { :d => (a_value < 3) } + } +) + +expect { |probe| + [1, 2, 3].each(&probe) +}.to yield_successive_args( a_value < 2, 2, a_value > 2 ) +``` + +## Usage outside rspec-core + +You always need to load `rspec/expectations` even if you only want to use one part of the library: + +```ruby +require 'rspec/expectations' +``` + +Then simply include `RSpec::Matchers` in any class: + +```ruby +class MyClass + include RSpec::Matchers + + def do_something(arg) + expect(arg).to be > 0 + # do other stuff + end +end +``` + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations.rb new file mode 100644 index 0000000000..9e11ea00c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations.rb @@ -0,0 +1,82 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "caller_filter" +RSpec::Support.require_rspec_support "warnings" +RSpec::Support.require_rspec_support "object_formatter" + +require 'rspec/matchers' + +RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) } + +%w[ + expectation_target + configuration + fail_with + handler + version +].each { |file| RSpec::Support.require_rspec_expectations(file) } + +module RSpec + # RSpec::Expectations provides a simple, readable API to express + # the expected outcomes in a code example. To express an expected + # outcome, wrap an object or block in `expect`, call `to` or `to_not` + # (aliased as `not_to`) and pass it a matcher object: + # + # expect(order.total).to eq(Money.new(5.55, :USD)) + # expect(list).to include(user) + # expect(message).not_to match(/foo/) + # expect { do_something }.to raise_error + # + # The last form (the block form) is needed to match against ruby constructs + # that are not objects, but can only be observed when executing a block + # of code. This includes raising errors, throwing symbols, yielding, + # and changing values. + # + # When `expect(...).to` is invoked with a matcher, it turns around + # and calls `matcher.matches?()`. For example, + # in the expression: + # + # expect(order.total).to eq(Money.new(5.55, :USD)) + # + # ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results + # in the equivalent of `eq.matches?(order.total)`. If `matches?` returns + # `true`, the expectation is met and execution continues. If `false`, then + # the spec fails with the message returned by `eq.failure_message`. + # + # Given the expression: + # + # expect(order.entries).not_to include(entry) + # + # ...the `not_to` method (also available as `to_not`) invokes the equivalent of + # `include.matches?(order.entries)`, but it interprets `false` as success, and + # `true` as a failure, using the message generated by + # `include.failure_message_when_negated`. + # + # rspec-expectations ships with a standard set of useful matchers, and writing + # your own matchers is quite simple. + # + # See [RSpec::Matchers](../RSpec/Matchers) for more information about the + # built-in matchers that ship with rspec-expectations, and how to write your + # own custom matchers. + module Expectations + # Exception raised when an expectation fails. + # + # @note We subclass Exception so that in a stub implementation if + # the user sets an expectation, it can't be caught in their + # code by a bare `rescue`. + # @api public + class ExpectationNotMetError < Exception + end + + # Exception raised from `aggregate_failures` when multiple expectations fail. + # + # @note The constant is defined here but the extensive logic of this class + # is lazily defined when `FailureAggregator` is autoloaded, since we do + # not need to waste time defining that functionality unless + # `aggregate_failures` is used. + class MultipleExpectationsNotMetError < ExpectationNotMetError + end + + autoload :BlockSnippetExtractor, "rspec/expectations/block_snippet_extractor" + autoload :FailureAggregator, "rspec/expectations/failure_aggregator" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/block_snippet_extractor.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/block_snippet_extractor.rb new file mode 100644 index 0000000000..02f1155968 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/block_snippet_extractor.rb @@ -0,0 +1,253 @@ +module RSpec + module Expectations + # @private + class BlockSnippetExtractor # rubocop:disable Metrics/ClassLength + # rubocop should properly handle `Struct.new {}` as an inner class definition. + + attr_reader :proc, :method_name + + def self.try_extracting_single_line_body_of(proc, method_name) + lines = new(proc, method_name).body_content_lines + return nil unless lines.count == 1 + lines.first + rescue Error + nil + end + + def initialize(proc, method_name) + @proc = proc + @method_name = method_name.to_s.freeze + end + + # Ideally we should properly handle indentations of multiline snippet, + # but it's not implemented yet since because we use result of this method only when it's a + # single line and implementing the logic introduces additional complexity. + def body_content_lines + raw_body_lines.map(&:strip).reject(&:empty?) + end + + private + + def raw_body_lines + raw_body_snippet.split("\n") + end + + def raw_body_snippet + block_token_extractor.body_tokens.map(&:string).join + end + + def block_token_extractor + @block_token_extractor ||= BlockTokenExtractor.new(method_name, source, beginning_line_number) + end + + if RSpec.respond_to?(:world) + def source + raise TargetNotFoundError unless File.exist?(file_path) + RSpec.world.source_from_file(file_path) + end + else + RSpec::Support.require_rspec_support 'source' + def source + raise TargetNotFoundError unless File.exist?(file_path) + @source ||= RSpec::Support::Source.from_file(file_path) + end + end + + def file_path + source_location.first + end + + def beginning_line_number + source_location.last + end + + def source_location + proc.source_location || raise(TargetNotFoundError) + end + + Error = Class.new(StandardError) + TargetNotFoundError = Class.new(Error) + AmbiguousTargetError = Class.new(Error) + + # @private + # Performs extraction of block body snippet using tokens, + # which cannot be done with node information. + BlockTokenExtractor = Struct.new(:method_name, :source, :beginning_line_number) do + attr_reader :state, :body_tokens + + def initialize(*) + super + parse! + end + + private + + def parse! + @state = :initial + + catch(:finish) do + source.tokens.each do |token| + invoke_state_handler(token) + end + end + end + + def finish! + throw :finish + end + + def invoke_state_handler(token) + __send__("#{state}_state", token) + end + + def initial_state(token) + @state = :after_method_call if token.location == block_locator.method_call_location + end + + def after_method_call_state(token) + @state = :after_opener if handle_opener_token(token) + end + + def after_opener_state(token) + if handle_closer_token(token) + finish_or_find_next_block_if_incorrect! + elsif pipe_token?(token) + finalize_pending_tokens! + @state = :after_beginning_of_args + else + pending_tokens << token + handle_opener_token(token) + @state = :after_beginning_of_body unless token.type == :on_sp + end + end + + def after_beginning_of_args_state(token) + @state = :after_beginning_of_body if pipe_token?(token) + end + + def after_beginning_of_body_state(token) + if handle_closer_token(token) + finish_or_find_next_block_if_incorrect! + else + pending_tokens << token + handle_opener_token(token) + end + end + + def pending_tokens + @pending_tokens ||= [] + end + + def finalize_pending_tokens! + pending_tokens.freeze.tap do + @pending_tokens = nil + end + end + + def finish_or_find_next_block_if_incorrect! + body_tokens = finalize_pending_tokens! + + if correct_block?(body_tokens) + @body_tokens = body_tokens + finish! + else + @state = :after_method_call + end + end + + def handle_opener_token(token) + opener_token?(token).tap do |boolean| + opener_token_stack.push(token) if boolean + end + end + + def opener_token?(token) + token.type == :on_lbrace || (token.type == :on_kw && token.string == 'do') + end + + def handle_closer_token(token) + if opener_token_stack.last.closed_by?(token) + opener_token_stack.pop + opener_token_stack.empty? + else + false + end + end + + def opener_token_stack + @opener_token_stack ||= [] + end + + def pipe_token?(token) + token.type == :on_op && token.string == '|' + end + + def correct_block?(body_tokens) + return true if block_locator.body_content_locations.empty? + content_location = block_locator.body_content_locations.first + content_location.between?(body_tokens.first.location, body_tokens.last.location) + end + + def block_locator + @block_locator ||= BlockLocator.new(method_name, source, beginning_line_number) + end + end + + # @private + # Locates target block with node information (semantics), which tokens don't have. + BlockLocator = Struct.new(:method_name, :source, :beginning_line_number) do + def method_call_location + @method_call_location ||= method_ident_node.location + end + + def body_content_locations + @body_content_locations ||= block_body_node.map(&:location).compact + end + + private + + def method_ident_node + method_call_node = block_wrapper_node.children.first + method_call_node.find do |node| + method_ident_node?(node) + end + end + + def block_body_node + block_node = block_wrapper_node.children[1] + block_node.children.last + end + + def block_wrapper_node + case candidate_block_wrapper_nodes.size + when 1 + candidate_block_wrapper_nodes.first + when 0 + raise TargetNotFoundError + else + raise AmbiguousTargetError + end + end + + def candidate_block_wrapper_nodes + @candidate_block_wrapper_nodes ||= candidate_method_ident_nodes.map do |method_ident_node| + block_wrapper_node = method_ident_node.each_ancestor.find { |node| node.type == :method_add_block } + next nil unless block_wrapper_node + method_call_node = block_wrapper_node.children.first + method_call_node.include?(method_ident_node) ? block_wrapper_node : nil + end.compact + end + + def candidate_method_ident_nodes + source.nodes_by_line_number[beginning_line_number].select do |node| + method_ident_node?(node) + end + end + + def method_ident_node?(node) + node.type == :@ident && node.args.first == method_name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/configuration.rb new file mode 100644 index 0000000000..974a9cdf22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/configuration.rb @@ -0,0 +1,230 @@ +RSpec::Support.require_rspec_expectations "syntax" + +module RSpec + module Expectations + # Provides configuration options for rspec-expectations. + # If you are using rspec-core, you can access this via a + # block passed to `RSpec::Core::Configuration#expect_with`. + # Otherwise, you can access it via RSpec::Expectations.configuration. + # + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # # c is the config object + # end + # end + # + # # or + # + # RSpec::Expectations.configuration + class Configuration + # @private + FALSE_POSITIVE_BEHAVIOURS = + { + :warn => lambda { |message| RSpec.warning message }, + :raise => lambda { |message| raise ArgumentError, message }, + :nothing => lambda { |_| true }, + } + + def initialize + @on_potential_false_positives = :warn + @strict_predicate_matchers = false + end + + # Configures the supported syntax. + # @param [Array, Symbol] values the syntaxes to enable + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # c.syntax = :should + # # or + # c.syntax = :expect + # # or + # c.syntax = [:should, :expect] + # end + # end + def syntax=(values) + if Array(values).include?(:expect) + Expectations::Syntax.enable_expect + else + Expectations::Syntax.disable_expect + end + + if Array(values).include?(:should) + Expectations::Syntax.enable_should + else + Expectations::Syntax.disable_should + end + end + + # Configures the maximum character length that RSpec will print while + # formatting an object. You can set length to nil to prevent RSpec from + # doing truncation. + # @param [Fixnum] length the number of characters to limit the formatted output to. + # @example + # RSpec.configure do |rspec| + # rspec.expect_with :rspec do |c| + # c.max_formatted_output_length = 200 + # end + # end + def max_formatted_output_length=(length) + RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = length + end + + # The list of configured syntaxes. + # @return [Array] the list of configured syntaxes. + # @example + # unless RSpec::Matchers.configuration.syntax.include?(:expect) + # raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax" + # end + def syntax + syntaxes = [] + syntaxes << :should if Expectations::Syntax.should_enabled? + syntaxes << :expect if Expectations::Syntax.expect_enabled? + syntaxes + end + + if ::RSpec.respond_to?(:configuration) + def color? + ::RSpec.configuration.color_enabled? + end + else + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + attr_writer :color + + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + def color? + defined?(@color) && @color + end + end + + # Adds `should` and `should_not` to the given classes + # or modules. This can be used to ensure `should` works + # properly on things like proxy objects (particular + # `Delegator`-subclassed objects on 1.8). + # + # @param [Array] modules the list of classes or modules + # to add `should` and `should_not` to. + def add_should_and_should_not_to(*modules) + modules.each do |mod| + Expectations::Syntax.enable_should(mod) + end + end + + # Sets or gets the backtrace formatter. The backtrace formatter should + # implement `#format_backtrace(Array)`. This is used + # to format backtraces of errors handled by the `raise_error` + # matcher. + # + # If you are using rspec-core, rspec-core's backtrace formatting + # will be used (including respecting the presence or absence of + # the `--backtrace` option). + # + # @!attribute [rw] backtrace_formatter + attr_writer :backtrace_formatter + def backtrace_formatter + @backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter) + ::RSpec.configuration.backtrace_formatter + else + NullBacktraceFormatter + end + end + + # Sets if custom matcher descriptions and failure messages + # should include clauses from methods defined using `chain`. + # @param value [Boolean] + attr_writer :include_chain_clauses_in_custom_matcher_descriptions + + # Indicates whether or not custom matcher descriptions and failure messages + # should include clauses from methods defined using `chain`. It is + # false by default for backwards compatibility. + def include_chain_clauses_in_custom_matcher_descriptions? + @include_chain_clauses_in_custom_matcher_descriptions ||= false + end + + # @private + def reset_syntaxes_to_default + self.syntax = [:should, :expect] + RSpec::Expectations::Syntax.warn_about_should! + end + + # @api private + # Null implementation of a backtrace formatter used by default + # when rspec-core is not loaded. Does no filtering. + NullBacktraceFormatter = Module.new do + def self.format_backtrace(backtrace) + backtrace + end + end + + # Configures whether RSpec will warn about matcher use which will + # potentially cause false positives in tests. + # + # @param [Boolean] boolean + def warn_about_potential_false_positives=(boolean) + if boolean + self.on_potential_false_positives = :warn + elsif warn_about_potential_false_positives? + self.on_potential_false_positives = :nothing + else + # no-op, handler is something else + end + end + # + # Configures what RSpec will do about matcher use which will + # potentially cause false positives in tests. + # + # @param [Symbol] behavior can be set to :warn, :raise or :nothing + def on_potential_false_positives=(behavior) + unless FALSE_POSITIVE_BEHAVIOURS.key?(behavior) + raise ArgumentError, "Supported values are: #{FALSE_POSITIVE_BEHAVIOURS.keys}" + end + @on_potential_false_positives = behavior + end + + # Configures RSpec to check predicate matchers to `be(true)` / `be(false)` (strict), + # or `be_truthy` / `be_falsey` (not strict). + # Historically, the default was `false`, but `true` is recommended. + def strict_predicate_matchers=(flag) + raise ArgumentError, "Pass `true` or `false`" unless flag == true || flag == false + @strict_predicate_matchers = flag + end + + attr_reader :strict_predicate_matchers + + def strict_predicate_matchers? + @strict_predicate_matchers + end + + # Indicates what RSpec will do about matcher use which will + # potentially cause false positives in tests, generally you want to + # avoid such scenarios so this defaults to `true`. + attr_reader :on_potential_false_positives + + # Indicates whether RSpec will warn about matcher use which will + # potentially cause false positives in tests, generally you want to + # avoid such scenarios so this defaults to `true`. + def warn_about_potential_false_positives? + on_potential_false_positives == :warn + end + + # @private + def false_positives_handler + FALSE_POSITIVE_BEHAVIOURS.fetch(@on_potential_false_positives) + end + end + + # The configuration object. + # @return [RSpec::Expectations::Configuration] the configuration object + def self.configuration + @configuration ||= Configuration.new + end + + # set default syntax + configuration.reset_syntaxes_to_default + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/expectation_target.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/expectation_target.rb new file mode 100644 index 0000000000..7d1e8d40f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/expectation_target.rb @@ -0,0 +1,163 @@ +module RSpec + module Expectations + # Wraps the target of an expectation. + # + # @example + # expect(something) # => ExpectationTarget wrapping something + # expect { do_something } # => ExpectationTarget wrapping the block + # + # # used with `to` + # expect(actual).to eq(3) + # + # # with `not_to` + # expect(actual).not_to eq(3) + # + # @note `ExpectationTarget` is not intended to be instantiated + # directly by users. Use `expect` instead. + class ExpectationTarget + # @private + # Used as a sentinel value to be able to tell when the user + # did not pass an argument. We can't use `nil` for that because + # `nil` is a valid value to pass. + UndefinedValue = Module.new + + # @note this name aligns with `Minitest::Expectation` so that our + # {InstanceMethods} module can be included in that class when + # used in a Minitest context. + # @return [Object] the target of the expectation + attr_reader :target + + # @api private + def initialize(value) + @target = value + end + + # @private + def self.for(value, block) + if UndefinedValue.equal?(value) + unless block + raise ArgumentError, "You must pass either an argument or a block to `expect`." + end + BlockExpectationTarget.new(block) + elsif block + raise ArgumentError, "You cannot pass both an argument and a block to `expect`." + else + ValueExpectationTarget.new(value) + end + end + + # Defines instance {ExpectationTarget} instance methods. These are defined + # in a module so we can include it in `Minitest::Expectation` when + # `rspec/expectations/minitest_integration` is loaded in order to + # support usage with Minitest. + module InstanceMethods + # Runs the given expectation, passing if `matcher` returns true. + # @example + # expect(value).to eq(5) + # expect { perform }.to raise_error + # @param [Matcher] + # matcher + # @param [String, Proc] message optional message to display when the expectation fails + # @return [Boolean] true if the expectation succeeds (else raises) + # @see RSpec::Matchers + def to(matcher=nil, message=nil, &block) + prevent_operator_matchers(:to) unless matcher + RSpec::Expectations::PositiveExpectationHandler.handle_matcher(target, matcher, message, &block) + end + + # Runs the given expectation, passing if `matcher` returns false. + # @example + # expect(value).not_to eq(5) + # @param [Matcher] + # matcher + # @param [String, Proc] message optional message to display when the expectation fails + # @return [Boolean] false if the negative expectation succeeds (else raises) + # @see RSpec::Matchers + def not_to(matcher=nil, message=nil, &block) + prevent_operator_matchers(:not_to) unless matcher + RSpec::Expectations::NegativeExpectationHandler.handle_matcher(target, matcher, message, &block) + end + alias to_not not_to + + private + + def prevent_operator_matchers(verb) + raise ArgumentError, "The expect syntax does not support operator matchers, " \ + "so you must pass a matcher to `##{verb}`." + end + end + + include InstanceMethods + end + + # @private + # Validates the provided matcher to ensure it supports block + # expectations, in order to avoid user confusion when they + # use a block thinking the expectation will be on the return + # value of the block rather than the block itself. + class ValueExpectationTarget < ExpectationTarget + def to(matcher=nil, message=nil, &block) + enforce_value_expectation(matcher) + super + end + + def not_to(matcher=nil, message=nil, &block) + enforce_value_expectation(matcher) + super + end + + private + + def enforce_value_expectation(matcher) + return if supports_value_expectations?(matcher) + + RSpec.deprecate( + "expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}", + :message => + "The implicit block expectation syntax is deprecated, you should pass " \ + "a block rather than an argument to `expect` to use the provided " \ + "block expectation matcher or the matcher must implement " \ + "`supports_value_expectations?`. e.g `expect { value }.to " \ + "#{RSpec::Support::ObjectFormatter.format(matcher)}` not " \ + "`expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}`" + ) + end + + def supports_value_expectations?(matcher) + !matcher.respond_to?(:supports_value_expectations?) || matcher.supports_value_expectations? + end + end + + # @private + # Validates the provided matcher to ensure it supports block + # expectations, in order to avoid user confusion when they + # use a block thinking the expectation will be on the return + # value of the block rather than the block itself. + class BlockExpectationTarget < ExpectationTarget + def to(matcher, message=nil, &block) + enforce_block_expectation(matcher) + super + end + + def not_to(matcher, message=nil, &block) + enforce_block_expectation(matcher) + super + end + alias to_not not_to + + private + + def enforce_block_expectation(matcher) + return if supports_block_expectations?(matcher) + + raise ExpectationNotMetError, "You must pass an argument rather than a block to `expect` to use the provided " \ + "matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \ + "`supports_block_expectations?`." + end + + def supports_block_expectations?(matcher) + matcher.respond_to?(:supports_block_expectations?) && matcher.supports_block_expectations? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/fail_with.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/fail_with.rb new file mode 100644 index 0000000000..212e71c421 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/fail_with.rb @@ -0,0 +1,39 @@ +module RSpec + module Expectations + class << self + # @private + class Differ + # @private + OBJECT_PREPARER = lambda do |object| + RSpec::Matchers::Composable.surface_descriptions_in(object) + end + end + + # @private + def differ + RSpec::Support::Differ.new( + :object_preparer => Differ::OBJECT_PREPARER, + :color => RSpec::Matchers.configuration.color? + ) + end + + # Raises an RSpec::Expectations::ExpectationNotMetError with message. + # @param [String] message + # @param [Object] expected + # @param [Object] actual + # + # Adds a diff to the failure message when `expected` and `actual` are + # both present. + def fail_with(message, expected=nil, actual=nil) + unless message + raise ArgumentError, "Failure message is nil. Does your matcher define the " \ + "appropriate failure_message[_when_negated] method to return a string?" + end + + message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual) + + RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/failure_aggregator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/failure_aggregator.rb new file mode 100644 index 0000000000..59f5971d6e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/failure_aggregator.rb @@ -0,0 +1,229 @@ +module RSpec + module Expectations + # @private + class FailureAggregator + attr_reader :block_label, :metadata + + # @private + class AggregatedFailure + # @private + MESSAGE = + 'AggregatedFailure: This method caused a failure which has been ' \ + 'suppressed to be aggregated into our failure report by returning ' \ + 'this value, further errors can be ignored.' + + def inspect + MESSAGE + end + end + + AGGREGATED_FAILURE = AggregatedFailure.new + + def aggregate + RSpec::Support.with_failure_notifier(self) do + begin + yield + rescue ExpectationNotMetError => e + # Normally, expectation failures will be notified via the `call` method, below, + # but since the failure notifier uses a thread local variable, failing expectations + # in another thread will still raise. We handle that here and categorize it as part + # of `failures` rather than letting it fall through and be categorized as part of + # `other_errors`. + failures << e + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e + # While it is normally a bad practice to rescue `Exception`, it's important we do + # so here. It's low risk (`notify_aggregated_failures` below will re-raise the exception, + # or raise a `MultipleExpectationsNotMetError` that includes the exception), and it's + # essential that the user is notified of expectation failures that may have already + # occurred in the `aggregate_failures` block. Those expectation failures may provide + # important diagnostics for understanding why this exception occurred, and if we simply + # allowed this exception to be raised as-is, it would (wrongly) suggest to the user + # that the expectation passed when it did not, which would be quite confusing. + other_errors << e + end + end + + notify_aggregated_failures + end + + def failures + @failures ||= [] + end + + def other_errors + @other_errors ||= [] + end + + # This method is defined to satisfy the callable interface + # expected by `RSpec::Support.with_failure_notifier`. + def call(failure, options) + source_id = options[:source_id] + return if source_id && @seen_source_ids.key?(source_id) + + @seen_source_ids[source_id] = true + assign_backtrace(failure) unless failure.backtrace + failures << failure + + AGGREGATED_FAILURE + end + + private + + if RSpec::Support::Ruby.jruby? && RSpec::Support::Ruby.jruby_version < '9.2.0.0' + # On JRuby 9.1.x.x and before, `caller` and `raise` produce different backtraces with + # regards to `.java` stack frames. It's important that we use `raise` for JRuby to produce + # a backtrace that has a continuous common section with the raised `MultipleExpectationsNotMetError`, + # so that rspec-core's truncation logic can work properly on it to list the backtrace + # relative to the `aggregate_failures` block. + def assign_backtrace(failure) + raise failure + rescue failure.class => e + failure.set_backtrace(e.backtrace) + end + else + # Using `caller` performs better (and is simpler) than `raise` on most Rubies. + def assign_backtrace(failure) + failure.set_backtrace(caller) + end + end + + def initialize(block_label, metadata) + @block_label = block_label + @metadata = metadata + @seen_source_ids = {} # don't want to load stdlib set + end + + def notify_aggregated_failures + all_errors = failures + other_errors + + case all_errors.size + when 0 then return true + when 1 then RSpec::Support.notify_failure all_errors.first + else RSpec::Support.notify_failure MultipleExpectationsNotMetError.new(self) + end + end + end + + # Exception raised from `aggregate_failures` when multiple expectations fail. + class MultipleExpectationsNotMetError + # @return [String] The fully formatted exception message. + def message + @message ||= (["#{summary}:"] + enumerated_failures + enumerated_errors).join("\n\n") + end + + # @return [Array] The list of expectation failures. + def failures + @failure_aggregator.failures + end + + # @return [Array] The list of other exceptions. + def other_errors + @failure_aggregator.other_errors + end + + # @return [Array] The list of expectation failures and other exceptions, combined. + attr_reader :all_exceptions + + # @return [String] The user-assigned label for the aggregation block. + def aggregation_block_label + @failure_aggregator.block_label + end + + # @return [Hash] The metadata hash passed to `aggregate_failures`. + def aggregation_metadata + @failure_aggregator.metadata + end + + # @return [String] A summary of the failure, including the block label and a count of failures. + def summary + "Got #{exception_count_description} from failure aggregation " \ + "block#{block_description}" + end + + # return [String] A description of the failure/error counts. + def exception_count_description + failure_count = pluralize("failure", failures.size) + return failure_count if other_errors.empty? + error_count = pluralize("other error", other_errors.size) + "#{failure_count} and #{error_count}" + end + + private + + def initialize(failure_aggregator) + @failure_aggregator = failure_aggregator + @all_exceptions = failures + other_errors + end + + def block_description + return "" unless aggregation_block_label + " #{aggregation_block_label.inspect}" + end + + def pluralize(noun, count) + "#{count} #{noun}#{'s' unless count == 1}" + end + + def enumerated(exceptions, index_offset) + exceptions.each_with_index.map do |exception, index| + index += index_offset + formatted_message = "#{yield exception}\n#{format_backtrace(exception.backtrace).first}" + "#{index_label index}#{indented formatted_message, index}" + end + end + + def exclusion_patterns + patterns = %w[/lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle:] + patterns << "org/jruby/" if RSpec::Support::Ruby.jruby? + patterns.map! { |s| Regexp.new(s.gsub('/', File::SEPARATOR)) } + end + + def format_backtrace(backtrace) + backtrace.map { |l| backtrace_line(l) }.compact.tap { |filtered| filtered.concat backtrace if filtered.empty? } + end + + def backtrace_line(line) + return if [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *exclusion_patterns)].any? { |p| line =~ p } + + # It changes the current path that is relative to + # system root to be relative to the project root. + line.sub(/(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/, '\\1.\\2'.freeze).sub(/\A([^:]+:\d+)$/, '\\1'.freeze) + end + + def enumerated_failures + enumerated(failures, 0, &:message) + end + + def enumerated_errors + enumerated(other_errors, failures.size) do |error| + "#{error.class}: #{error.message}" + end + end + + def indented(failure_message, index) + line_1, *rest = failure_message.strip.lines.to_a + first_line_indentation = ' ' * (longest_index_label_width - width_of_label(index)) + + first_line_indentation + line_1 + rest.map do |line| + line =~ /\S/ ? indentation + line : line + end.join + end + + def indentation + @indentation ||= ' ' * longest_index_label_width + end + + def longest_index_label_width + @longest_index_label_width ||= width_of_label(failures.size) + end + + def width_of_label(index) + index_label(index).chars.count + end + + def index_label(index) + " #{index + 1}) " + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/handler.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/handler.rb new file mode 100644 index 0000000000..a8f7c05318 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/handler.rb @@ -0,0 +1,182 @@ +module RSpec + module Expectations + # @private + module ExpectationHelper + def self.check_message(msg) + unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call) + ::Kernel.warn [ + "WARNING: ignoring the provided expectation message argument (", + msg.inspect, + ") since it is not a string or a proc." + ].join + end + end + + # Returns an RSpec-3+ compatible matcher, wrapping a legacy one + # in an adapter if necessary. + # + # @private + def self.modern_matcher_from(matcher) + LegacyMatcherAdapter::RSpec2.wrap(matcher) || + LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher + end + + def self.with_matcher(handler, matcher, message) + check_message(message) + matcher = modern_matcher_from(matcher) + yield matcher + ensure + ::RSpec::Matchers.last_expectation_handler = handler + ::RSpec::Matchers.last_matcher = matcher + end + + def self.handle_failure(matcher, message, failure_message_method) + message = message.call if message.respond_to?(:call) + message ||= matcher.__send__(failure_message_method) + + if matcher.respond_to?(:diffable?) && matcher.diffable? + ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual + else + ::RSpec::Expectations.fail_with message + end + end + end + + # @private + class PositiveExpectationHandler + def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) + ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| + return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher + + match_result = matcher.matches?(actual, &block) + if custom_message && match_result.respond_to?(:error_generator) + match_result.error_generator.opts[:message] = custom_message + end + + match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message) + end + end + + def self.verb + 'is expected to' + end + + def self.should_method + :should + end + + def self.opposite_should_method + :should_not + end + end + + # @private + class NegativeExpectationHandler + def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) + ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| + return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher + + negated_match_result = does_not_match?(matcher, actual, &block) + if custom_message && negated_match_result.respond_to?(:error_generator) + negated_match_result.error_generator.opts[:message] = custom_message + end + + negated_match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message_when_negated) + end + end + + def self.does_not_match?(matcher, actual, &block) + if matcher.respond_to?(:does_not_match?) + matcher.does_not_match?(actual, &block) + else + !matcher.matches?(actual, &block) + end + end + + def self.verb + 'is expected not to' + end + + def self.should_method + :should_not + end + + def self.opposite_should_method + :should + end + end + + # Wraps a matcher written against one of the legacy protocols in + # order to present the current protocol. + # + # @private + class LegacyMatcherAdapter < Matchers::MatcherDelegator + def initialize(matcher) + super + ::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher") + |#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher + |protocol. For the current protocol you should expose the failure messages + |via the `failure_message` and `failure_message_when_negated` methods. + |(Used from #{CallerFilter.first_non_rspec_line}) + EOS + end + + def self.wrap(matcher) + new(matcher) if interface_matches?(matcher) + end + + # Starting in RSpec 1.2 (and continuing through all 2.x releases), + # the failure message protocol was: + # * `failure_message_for_should` + # * `failure_message_for_should_not` + # @private + class RSpec2 < self + def failure_message + base_matcher.failure_message_for_should + end + + def failure_message_when_negated + base_matcher.failure_message_for_should_not + end + + def self.interface_matches?(matcher) + ( + !matcher.respond_to?(:failure_message) && + matcher.respond_to?(:failure_message_for_should) + ) || ( + !matcher.respond_to?(:failure_message_when_negated) && + matcher.respond_to?(:failure_message_for_should_not) + ) + end + end + + # Before RSpec 1.2, the failure message protocol was: + # * `failure_message` + # * `negative_failure_message` + # @private + class RSpec1 < self + def failure_message + base_matcher.failure_message + end + + def failure_message_when_negated + base_matcher.negative_failure_message + end + + # Note: `failure_message` is part of the RSpec 3 protocol + # (paired with `failure_message_when_negated`), so we don't check + # for `failure_message` here. + def self.interface_matches?(matcher) + !matcher.respond_to?(:failure_message_when_negated) && + matcher.respond_to?(:negative_failure_message) + end + end + end + + # RSpec 3.0 was released with the class name misspelled. For SemVer compatibility, + # we will provide this misspelled alias until 4.0. + # @deprecated Use LegacyMatcherAdapter instead. + # @private + LegacyMacherAdapter = LegacyMatcherAdapter + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/minitest_integration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/minitest_integration.rb new file mode 100644 index 0000000000..94cdb5f3ec --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/minitest_integration.rb @@ -0,0 +1,58 @@ +require 'rspec/expectations' + +Minitest::Test.class_eval do + include ::RSpec::Matchers + + # This `expect` will only be called if the user is using Minitest < 5.6 + # or if they are _not_ using Minitest::Spec on 5.6+. Minitest::Spec on 5.6+ + # defines its own `expect` and will have the assertions incremented via our + # definitions of `to`/`not_to`/`to_not` below. + def expect(*a, &b) + self.assertions += 1 + super + end + + # Convert a `MultipleExpectationsNotMetError` to a `Minitest::Assertion` error so + # it gets counted in minitest's summary stats as a failure rather than an error. + # It would be nice to make `MultipleExpectationsNotMetError` subclass + # `Minitest::Assertion`, but Minitest's implementation does not treat subclasses + # the same, so this is the best we can do. + def aggregate_failures(*args, &block) + super + rescue RSpec::Expectations::MultipleExpectationsNotMetError => e + assertion_failed = Minitest::Assertion.new(e.message) + assertion_failed.set_backtrace e.backtrace + raise assertion_failed + end +end + +# Older versions of Minitest (e.g. before 5.6) do not define +# `Minitest::Expectation`. +if defined?(::Minitest::Expectation) + Minitest::Expectation.class_eval do + include RSpec::Expectations::ExpectationTarget::InstanceMethods + + def to(*args) + ctx.assertions += 1 + super + end + + def not_to(*args) + ctx.assertions += 1 + super + end + + def to_not(*args) + ctx.assertions += 1 + super + end + end +end + +module RSpec + module Expectations + remove_const :ExpectationNotMetError + # Exception raised when an expectation fails. + const_set :ExpectationNotMetError, ::Minitest::Assertion + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/syntax.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/syntax.rb new file mode 100644 index 0000000000..b8430346f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/syntax.rb @@ -0,0 +1,132 @@ +module RSpec + module Expectations + # @api private + # Provides methods for enabling and disabling the available + # syntaxes provided by rspec-expectations. + module Syntax + module_function + + # @api private + # Determines where we add `should` and `should_not`. + def default_should_host + @default_should_host ||= ::Object.ancestors.last + end + + # @api private + # Instructs rspec-expectations to warn on first usage of `should` or `should_not`. + # Enabled by default. This is largely here to facilitate testing. + def warn_about_should! + @warn_about_should = true + end + + # @api private + # Generates a deprecation warning for the given method if no warning + # has already been issued. + def warn_about_should_unless_configured(method_name) + return unless @warn_about_should + + RSpec.deprecate( + "Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax", + :replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`" + ) + + @warn_about_should = false + end + + # @api private + # Enables the `should` syntax. + def enable_should(syntax_host=default_should_host) + @warn_about_should = false if syntax_host == default_should_host + return if should_enabled?(syntax_host) + + syntax_host.module_exec do + def should(matcher=nil, message=nil, &block) + ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) + ::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block) + end + + def should_not(matcher=nil, message=nil, &block) + ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) + ::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block) + end + end + end + + # @api private + # Disables the `should` syntax. + def disable_should(syntax_host=default_should_host) + return unless should_enabled?(syntax_host) + + syntax_host.module_exec do + undef should + undef should_not + end + end + + # @api private + # Enables the `expect` syntax. + def enable_expect(syntax_host=::RSpec::Matchers) + return if expect_enabled?(syntax_host) + + syntax_host.module_exec do + def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block) + ::RSpec::Expectations::ExpectationTarget.for(value, block) + end + end + end + + # @api private + # Disables the `expect` syntax. + def disable_expect(syntax_host=::RSpec::Matchers) + return unless expect_enabled?(syntax_host) + + syntax_host.module_exec do + undef expect + end + end + + # @api private + # Indicates whether or not the `should` syntax is enabled. + def should_enabled?(syntax_host=default_should_host) + syntax_host.method_defined?(:should) + end + + # @api private + # Indicates whether or not the `expect` syntax is enabled. + def expect_enabled?(syntax_host=::RSpec::Matchers) + syntax_host.method_defined?(:expect) + end + end + end +end + +if defined?(BasicObject) + # The legacy `:should` syntax adds the following methods directly to + # `BasicObject` so that they are available off of any object. Note, however, + # that this syntax does not always play nice with delegate/proxy objects. + # We recommend you use the non-monkeypatching `:expect` syntax instead. + class BasicObject + # @method should(matcher, message) + # Passes if `matcher` returns true. Available on every `Object`. + # @example + # actual.should eq expected + # actual.should match /expression/ + # @param [Matcher] + # matcher + # @param [String] message optional message to display when the expectation fails + # @return [Boolean] true if the expectation succeeds (else raises) + # @note This is only available when you have enabled the `:should` syntax. + # @see RSpec::Matchers + + # @method should_not(matcher, message) + # Passes if `matcher` returns false. Available on every `Object`. + # @example + # actual.should_not eq expected + # @param [Matcher] + # matcher + # @param [String] message optional message to display when the expectation fails + # @return [Boolean] false if the negative expectation succeeds (else raises) + # @note This is only available when you have enabled the `:should` syntax. + # @see RSpec::Matchers + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/version.rb new file mode 100644 index 0000000000..45d7ac690a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/expectations/version.rb @@ -0,0 +1,8 @@ +module RSpec + module Expectations + # @private + module Version + STRING = '3.12.2' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers.rb new file mode 100644 index 0000000000..6a853ddf12 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers.rb @@ -0,0 +1,1044 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support 'matcher_definition' +RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) } + +%w[ + english_phrasing + composable + built_in + generated_descriptions + dsl + matcher_delegator + aliased_matcher + expecteds_for_multiple_diffs +].each { |file| RSpec::Support.require_rspec_matchers(file) } + +# RSpec's top level namespace. All of rspec-expectations is contained +# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces. +module RSpec + # RSpec::Matchers provides a number of useful matchers we use to define + # expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol) + # can be used as a matcher. + # + # ## Predicates + # + # In addition to matchers that are defined explicitly, RSpec will create + # custom matchers on the fly for any arbitrary predicate, giving your specs a + # much more natural language feel. + # + # A Ruby predicate is a method that ends with a "?" and returns true or false. + # Common examples are `empty?`, `nil?`, and `instance_of?`. + # + # All you need to do is write `expect(..).to be_` followed by the predicate + # without the question mark, and RSpec will figure it out from there. + # For example: + # + # expect([]).to be_empty # => [].empty?() | passes + # expect([]).not_to be_empty # => [].empty?() | fails + # + # In addition to prefixing the predicate matchers with "be_", you can also use "be_a_" + # and "be_an_", making your specs read much more naturally: + # + # expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes + # + # expect(3).to be_a_kind_of(Integer) # => 3.kind_of?(Numeric) | passes + # expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes + # expect(3).to be_an_instance_of(Integer) # => 3.instance_of?(Integer) | passes + # expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails + # + # RSpec will also create custom matchers for predicates like `has_key?`. To + # use this feature, just state that the object should have_key(:key) and RSpec will + # call has_key?(:key) on the target. For example: + # + # expect(:a => "A").to have_key(:a) + # expect(:a => "A").to have_key(:b) # fails + # + # You can use this feature to invoke any predicate that begins with "has_", whether it is + # part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class. + # + # Note that RSpec does not provide composable aliases for these dynamic predicate + # matchers. You can easily define your own aliases, though: + # + # RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin + # expect(user_list).to include(a_user_who_is_an_admin) + # + # ## Alias Matchers + # + # With {RSpec::Matchers.alias_matcher}, you can easily create an + # alternate name for a given matcher. + # + # The description will also change according to the new name: + # + # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to + # sum_to(3).description # => "sum to 3" + # a_list_that_sums_to(3).description # => "a list that sums to 3" + # + # or you can specify a custom description like this: + # + # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description| + # description.sub("be sorted by", "a list sorted by") + # end + # + # be_sorted_by(:age).description # => "be sorted by age" + # a_list_sorted_by(:age).description # => "a list sorted by age" + # + # ## Custom Matchers + # + # When you find that none of the stock matchers provide a natural feeling + # expectation, you can very easily write your own using RSpec's matcher DSL + # or writing one from scratch. + # + # ### Matcher DSL + # + # Imagine that you are writing a game in which players can be in various + # zones on a virtual board. To specify that bob should be in zone 4, you + # could say: + # + # expect(bob.current_zone).to eql(Zone.new("4")) + # + # But you might find it more expressive to say: + # + # expect(bob).to be_in_zone("4") + # + # and/or + # + # expect(bob).not_to be_in_zone("3") + # + # You can create such a matcher like so: + # + # RSpec::Matchers.define :be_in_zone do |zone| + # match do |player| + # player.in_zone?(zone) + # end + # end + # + # This will generate a be_in_zone method that returns a matcher + # with logical default messages for failures. You can override the failure + # messages and the generated description as follows: + # + # RSpec::Matchers.define :be_in_zone do |zone| + # match do |player| + # player.in_zone?(zone) + # end + # + # failure_message do |player| + # # generate and return the appropriate string. + # end + # + # failure_message_when_negated do |player| + # # generate and return the appropriate string. + # end + # + # description do + # # generate and return the appropriate string. + # end + # end + # + # Each of the message-generation methods has access to the block arguments + # passed to the create method (in this case, zone). The + # failure message methods (failure_message and + # failure_message_when_negated) are passed the actual value (the + # receiver of expect(..) or expect(..).not_to). + # + # ### Custom Matcher from scratch + # + # You could also write a custom matcher from scratch, as follows: + # + # class BeInZone + # def initialize(expected) + # @expected = expected + # end + # + # def matches?(target) + # @target = target + # @target.current_zone.eql?(Zone.new(@expected)) + # end + # + # def failure_message + # "expected #{@target.inspect} to be in Zone #{@expected}" + # end + # + # def failure_message_when_negated + # "expected #{@target.inspect} not to be in Zone #{@expected}" + # end + # end + # + # ... and a method like this: + # + # def be_in_zone(expected) + # BeInZone.new(expected) + # end + # + # And then expose the method to your specs. This is normally done + # by including the method and the class in a module, which is then + # included in your spec: + # + # module CustomGameMatchers + # class BeInZone + # # ... + # end + # + # def be_in_zone(expected) + # # ... + # end + # end + # + # describe "Player behaviour" do + # include CustomGameMatchers + # # ... + # end + # + # or you can include in globally in a spec_helper.rb file required + # from your spec file(s): + # + # RSpec::configure do |config| + # config.include(CustomGameMatchers) + # end + # + # ### Making custom matchers composable + # + # RSpec's built-in matchers are designed to be composed, in expressions like: + # + # expect(["barn", 2.45]).to contain_exactly( + # a_value_within(0.1).of(2.5), + # a_string_starting_with("bar") + # ) + # + # Custom matchers can easily participate in composed matcher expressions like these. + # Include {RSpec::Matchers::Composable} in your custom matcher to make it support + # being composed (matchers defined using the DSL have this included automatically). + # Within your matcher's `matches?` method (or the `match` block, if using the DSL), + # use `values_match?(expected, actual)` rather than `expected == actual`. + # Under the covers, `values_match?` is able to match arbitrary + # nested data structures containing a mix of both matchers and non-matcher objects. + # It uses `===` and `==` to perform the matching, considering the values to + # match if either returns `true`. The `Composable` mixin also provides some helper + # methods for surfacing the matcher descriptions within your matcher's description + # or failure messages. + # + # RSpec's built-in matchers each have a number of aliases that rephrase the matcher + # from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`), + # which reads better when the matcher is passed as an argument in a composed matcher + # expressions, and also uses the noun-phrase wording in the matcher's `description`, + # for readable failure messages. You can alias your custom matchers in similar fashion + # using {RSpec::Matchers.alias_matcher}. + # + # ## Negated Matchers + # + # Sometimes if you want to test for the opposite using a more descriptive name + # instead of using `not_to`, you can use {RSpec::Matchers.define_negated_matcher}: + # + # RSpec::Matchers.define_negated_matcher :exclude, :include + # include(1, 2).description # => "include 1 and 2" + # exclude(1, 2).description # => "exclude 1 and 2" + # + # While the most obvious negated form may be to add a `not_` prefix, + # the failure messages you get with that form can be confusing (e.g. + # "expected [actual] to not [verb], but did not"). We've found it works + # best to find a more positive name for the negated form, such as + # `avoid_changing` rather than `not_change`. + # + module Matchers # rubocop:disable Metrics/ModuleLength + extend ::RSpec::Matchers::DSL + + # @!macro [attach] alias_matcher + # @!parse + # alias $1 $2 + # @!visibility private + # We define this override here so we can attach a YARD macro to it. + # It ensures that our docs list all the matcher aliases. + def self.alias_matcher(*args, &block) + super(*args, &block) + end + + # @!method self.alias_matcher(new_name, old_name, options={}, &description_override) + # Extended from {RSpec::Matchers::DSL#alias_matcher}. + + # @!method self.define(name, &declarations) + # Extended from {RSpec::Matchers::DSL#define}. + + # @!method self.define_negated_matcher(negated_name, base_name, &description_override) + # Extended from {RSpec::Matchers::DSL#define_negated_matcher}. + + # @method expect + # Supports `expect(actual).to matcher` syntax by wrapping `actual` in an + # `ExpectationTarget`. + # @example + # expect(actual).to eq(expected) + # expect(actual).not_to eq(expected) + # @return [Expectations::ExpectationTarget] + # @see Expectations::ExpectationTarget#to + # @see Expectations::ExpectationTarget#not_to + + # Allows multiple expectations in the provided block to fail, and then + # aggregates them into a single exception, rather than aborting on the + # first expectation failure like normal. This allows you to see all + # failures from an entire set of expectations without splitting each + # off into its own example (which may slow things down if the example + # setup is expensive). + # + # @param label [String] label for this aggregation block, which will be + # included in the aggregated exception message. + # @param metadata [Hash] additional metadata about this failure aggregation + # block. If multiple expectations fail, it will be exposed from the + # {Expectations::MultipleExpectationsNotMetError} exception. Mostly + # intended for internal RSpec use but you can use it as well. + # @yield Block containing as many expectation as you want. The block is + # simply yielded to, so you can trust that anything that works outside + # the block should work within it. + # @raise [Expectations::MultipleExpectationsNotMetError] raised when + # multiple expectations fail. + # @raise [Expectations::ExpectationNotMetError] raised when a single + # expectation fails. + # @raise [Exception] other sorts of exceptions will be raised as normal. + # + # @example + # aggregate_failures("verifying response") do + # expect(response.status).to eq(200) + # expect(response.headers).to include("Content-Type" => "text/plain") + # expect(response.body).to include("Success") + # end + # + # @note The implementation of this feature uses a thread-local variable, + # which means that if you have an expectation failure in another thread, + # it'll abort like normal. + def aggregate_failures(label=nil, metadata={}, &block) + Expectations::FailureAggregator.new(label, metadata).aggregate(&block) + end + + # Passes if actual is truthy (anything but false or nil) + def be_truthy + BuiltIn::BeTruthy.new + end + alias_matcher :a_truthy_value, :be_truthy + + # Passes if actual is falsey (false or nil) + def be_falsey + BuiltIn::BeFalsey.new + end + alias_matcher :be_falsy, :be_falsey + alias_matcher :a_falsey_value, :be_falsey + alias_matcher :a_falsy_value, :be_falsey + + # Passes if actual is nil + def be_nil + BuiltIn::BeNil.new + end + alias_matcher :a_nil_value, :be_nil + + # @example + # expect(actual).to be_truthy + # expect(actual).to be_falsey + # expect(actual).to be_nil + # expect(actual).to be_[arbitrary_predicate](*args) + # expect(actual).not_to be_nil + # expect(actual).not_to be_[arbitrary_predicate](*args) + # + # Given true, false, or nil, will pass if actual value is true, false or + # nil (respectively). Given no args means the caller should satisfy an if + # condition (to be or not to be). + # + # Predicates are any Ruby method that ends in a "?" and returns true or + # false. Given be_ followed by arbitrary_predicate (without the "?"), + # RSpec will match convert that into a query against the target object. + # + # The arbitrary_predicate feature will handle any predicate prefixed with + # "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_" + # (e.g. be_empty), letting you choose the prefix that best suits the + # predicate. + def be(*args) + args.empty? ? Matchers::BuiltIn::Be.new : equal(*args) + end + alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport + + # passes if target.kind_of?(klass) + def be_a(klass) + be_a_kind_of(klass) + end + alias_method :be_an, :be_a + + # Passes if actual.instance_of?(expected) + # + # @example + # expect(5).to be_an_instance_of(Integer) + # expect(5).not_to be_an_instance_of(Numeric) + # expect(5).not_to be_an_instance_of(Float) + def be_an_instance_of(expected) + BuiltIn::BeAnInstanceOf.new(expected) + end + alias_method :be_instance_of, :be_an_instance_of + alias_matcher :an_instance_of, :be_an_instance_of + + # Passes if actual.kind_of?(expected) + # + # @example + # expect(5).to be_a_kind_of(Integer) + # expect(5).to be_a_kind_of(Numeric) + # expect(5).not_to be_a_kind_of(Float) + def be_a_kind_of(expected) + BuiltIn::BeAKindOf.new(expected) + end + alias_method :be_kind_of, :be_a_kind_of + alias_matcher :a_kind_of, :be_a_kind_of + + # Passes if actual.between?(min, max). Works with any Comparable object, + # including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer, + # Float, Complex, and Rational). + # + # By default, `be_between` is inclusive (i.e. passes when given either the max or min value), + # but you can make it `exclusive` by chaining that off the matcher. + # + # @example + # expect(5).to be_between(1, 10) + # expect(11).not_to be_between(1, 10) + # expect(10).not_to be_between(1, 10).exclusive + def be_between(min, max) + BuiltIn::BeBetween.new(min, max) + end + alias_matcher :a_value_between, :be_between + + # Passes if actual == expected +/- delta + # + # @example + # expect(result).to be_within(0.5).of(3.0) + # expect(result).not_to be_within(0.5).of(3.0) + def be_within(delta) + BuiltIn::BeWithin.new(delta) + end + alias_matcher :a_value_within, :be_within + alias_matcher :within, :be_within + + # Applied to a proc, specifies that its execution will cause some value to + # change. + # + # @param [Object] receiver + # @param [Symbol] message the message to send the receiver + # + # You can either pass receiver and message, or a block, + # but not both. + # + # When passing a block, it must use the `{ ... }` format, not + # do/end, as `{ ... }` binds to the `change` method, whereas do/end + # would errantly bind to the `expect(..).to` or `expect(...).not_to` method. + # + # You can chain any of the following off of the end to specify details + # about the change: + # + # * `from` + # * `to` + # + # or any one of: + # + # * `by` + # * `by_at_least` + # * `by_at_most` + # + # @example + # expect { + # team.add_player(player) + # }.to change(roster, :count) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by(1) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by_at_least(1) + # + # expect { + # team.add_player(player) + # }.to change(roster, :count).by_at_most(1) + # + # string = "string" + # expect { + # string.reverse! + # }.to change { string }.from("string").to("gnirts") + # + # string = "string" + # expect { + # string + # }.not_to change { string }.from("string") + # + # expect { + # person.happy_birthday + # }.to change(person, :birthday).from(32).to(33) + # + # expect { + # employee.develop_great_new_social_networking_app + # }.to change(employee, :title).from("Mail Clerk").to("CEO") + # + # expect { + # doctor.leave_office + # }.to change(doctor, :sign).from(/is in/).to(/is out/) + # + # user = User.new(:type => "admin") + # expect { + # user.symbolize_type + # }.to change(user, :type).from(String).to(Symbol) + # + # == Notes + # + # Evaluates `receiver.message` or `block` before and after it + # evaluates the block passed to `expect`. If the value is the same + # object, its before/after `hash` value is used to see if it has changed. + # Therefore, your object needs to properly implement `hash` to work correctly + # with this matcher. + # + # `expect( ... ).not_to change` supports the form that specifies `from` + # (which specifies what you expect the starting, unchanged value to be) + # but does not support forms with subsequent calls to `by`, `by_at_least`, + # `by_at_most` or `to`. + def change(receiver=nil, message=nil, &block) + BuiltIn::Change.new(receiver, message, &block) + end + alias_matcher :a_block_changing, :change + alias_matcher :changing, :change + + # Passes if actual contains all of the expected regardless of order. + # This works for collections. Pass in multiple args and it will only + # pass if all args are found in collection. + # + # @note This is also available using the `=~` operator with `should`, + # but `=~` is not supported with `expect`. + # + # @example + # expect([1, 2, 3]).to contain_exactly(1, 2, 3) + # expect([1, 2, 3]).to contain_exactly(1, 3, 2) + # + # @see #match_array + def contain_exactly(*items) + BuiltIn::ContainExactly.new(items) + end + alias_matcher :a_collection_containing_exactly, :contain_exactly + alias_matcher :containing_exactly, :contain_exactly + + # Passes if actual covers expected. This works for + # Ranges. You can also pass in multiple args + # and it will only pass if all args are found in Range. + # + # @example + # expect(1..10).to cover(5) + # expect(1..10).to cover(4, 6) + # expect(1..10).to cover(4, 6, 11) # fails + # expect(1..10).not_to cover(11) + # expect(1..10).not_to cover(5) # fails + # + # ### Warning:: Ruby >= 1.9 only + def cover(*values) + BuiltIn::Cover.new(*values) + end + alias_matcher :a_range_covering, :cover + alias_matcher :covering, :cover + + # Matches if the actual value ends with the expected value(s). In the case + # of a string, matches against the last `expected.length` characters of the + # actual string. In the case of an array, matches against the last + # `expected.length` elements of the actual array. + # + # @example + # expect("this string").to end_with "string" + # expect([0, 1, 2, 3, 4]).to end_with 4 + # expect([0, 2, 3, 4, 4]).to end_with 3, 4 + def end_with(*expected) + BuiltIn::EndWith.new(*expected) + end + alias_matcher :a_collection_ending_with, :end_with + alias_matcher :a_string_ending_with, :end_with + alias_matcher :ending_with, :end_with + + # Passes if actual == expected. + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to eq(5) + # expect(5).not_to eq(3) + def eq(expected) + BuiltIn::Eq.new(expected) + end + alias_matcher :an_object_eq_to, :eq + alias_matcher :eq_to, :eq + + # Passes if `actual.eql?(expected)` + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to eql(5) + # expect(5).not_to eql(3) + def eql(expected) + BuiltIn::Eql.new(expected) + end + alias_matcher :an_object_eql_to, :eql + alias_matcher :eql_to, :eql + + # Passes if actual.equal?(expected) (object identity). + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more + # information about equality in Ruby. + # + # @example + # expect(5).to equal(5) # Integers are equal + # expect("5").not_to equal("5") # Strings that look the same are not the same object + def equal(expected) + BuiltIn::Equal.new(expected) + end + alias_matcher :an_object_equal_to, :equal + alias_matcher :equal_to, :equal + + # Passes if `actual.exist?` or `actual.exists?` + # + # @example + # expect(File).to exist("path/to/file") + def exist(*args) + BuiltIn::Exist.new(*args) + end + alias_matcher :an_object_existing, :exist + alias_matcher :existing, :exist + + # Passes if actual's attribute values match the expected attributes hash. + # This works no matter how you define your attribute readers. + # + # @example + # Person = Struct.new(:name, :age) + # person = Person.new("Bob", 32) + # + # expect(person).to have_attributes(:name => "Bob", :age => 32) + # expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) ) + # + # @note It will fail if actual doesn't respond to any of the expected attributes. + # + # @example + # expect(person).to have_attributes(:color => "red") + def have_attributes(expected) + BuiltIn::HaveAttributes.new(expected) + end + alias_matcher :an_object_having_attributes, :have_attributes + alias_matcher :having_attributes, :have_attributes + + # Passes if actual includes expected. This works for + # collections and Strings. You can also pass in multiple args + # and it will only pass if all args are found in collection. + # + # @example + # expect([1,2,3]).to include(3) + # expect([1,2,3]).to include(2,3) + # expect([1,2,3]).to include(2,3,4) # fails + # expect([1,2,3]).not_to include(4) + # expect("spread").to include("read") + # expect("spread").not_to include("red") + # expect(:a => 1, :b => 2).to include(:a) + # expect(:a => 1, :b => 2).to include(:a, :b) + # expect(:a => 1, :b => 2).to include(:a => 1) + # expect(:a => 1, :b => 2).to include(:b => 2, :a => 1) + # expect(:a => 1, :b => 2).to include(:c) # fails + # expect(:a => 1, :b => 2).not_to include(:a => 2) + def include(*expected) + BuiltIn::Include.new(*expected) + end + alias_matcher :a_collection_including, :include + alias_matcher :a_string_including, :include + alias_matcher :a_hash_including, :include + alias_matcher :including, :include + + # Passes if the provided matcher passes when checked against all + # elements of the collection. + # + # @example + # expect([1, 3, 5]).to all be_odd + # expect([1, 3, 6]).to all be_odd # fails + # + # @note The negative form `not_to all` is not supported. Instead + # use `not_to include` or pass a negative form of a matcher + # as the argument (e.g. `all exclude(:foo)`). + # + # @note You can also use this with compound matchers as well. + # + # @example + # expect([1, 3, 5]).to all( be_odd.and be_an(Integer) ) + def all(expected) + BuiltIn::All.new(expected) + end + + # Given a `Regexp` or `String`, passes if `actual.match(pattern)` + # Given an arbitrary nested data structure (e.g. arrays and hashes), + # matches if `expected === actual` || `actual == expected` for each + # pair of elements. + # + # @example + # expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) + # expect(email).to match("@example.com") + # + # @example + # hash = { + # :a => { + # :b => ["foo", 5], + # :c => { :d => 2.05 } + # } + # } + # + # expect(hash).to match( + # :a => { + # :b => a_collection_containing_exactly( + # a_string_starting_with("f"), + # an_instance_of(Integer) + # ), + # :c => { :d => (a_value < 3) } + # } + # ) + # + # @note The `match_regex` alias is deprecated and is not recommended for use. + # It was added in 2.12.1 to facilitate its use from within custom + # matchers (due to how the custom matcher DSL was evaluated in 2.x, + # `match` could not be used there), but is no longer needed in 3.x. + def match(expected) + BuiltIn::Match.new(expected) + end + alias_matcher :match_regex, :match + alias_matcher :an_object_matching, :match + alias_matcher :a_string_matching, :match + alias_matcher :matching, :match + + # An alternate form of `contain_exactly` that accepts + # the expected contents as a single array arg rather + # than splatted out as individual items. + # + # @example + # expect(results).to contain_exactly(1, 2) + # # is identical to: + # expect(results).to match_array([1, 2]) + # + # @see #contain_exactly + def match_array(items) + contain_exactly(*items) + end + alias_matcher :an_array_matching, :match_array do |desc| + desc.sub("contain exactly", "an array containing exactly") + end + + # With no arg, passes if the block outputs `to_stdout` or `to_stderr`. + # With a string, passes if the block outputs that specific string `to_stdout` or `to_stderr`. + # With a regexp or matcher, passes if the block outputs a string `to_stdout` or `to_stderr` that matches. + # + # To capture output from any spawned subprocess as well, use `to_stdout_from_any_process` or + # `to_stderr_from_any_process`. Output from any process that inherits the main process's corresponding + # standard stream will be captured. + # + # @example + # expect { print 'foo' }.to output.to_stdout + # expect { print 'foo' }.to output('foo').to_stdout + # expect { print 'foo' }.to output(/foo/).to_stdout + # + # expect { do_something }.to_not output.to_stdout + # + # expect { warn('foo') }.to output.to_stderr + # expect { warn('foo') }.to output('foo').to_stderr + # expect { warn('foo') }.to output(/foo/).to_stderr + # + # expect { do_something }.to_not output.to_stderr + # + # expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process + # expect { system('echo foo', out: :err) }.to output("foo\n").to_stderr_from_any_process + # + # @note `to_stdout` and `to_stderr` work by temporarily replacing `$stdout` or `$stderr`, + # so they're not able to intercept stream output that explicitly uses `STDOUT`/`STDERR` + # or that uses a reference to `$stdout`/`$stderr` that was stored before the + # matcher was used. + # @note `to_stdout_from_any_process` and `to_stderr_from_any_process` use Tempfiles, and + # are thus significantly (~30x) slower than `to_stdout` and `to_stderr`. + def output(expected=nil) + BuiltIn::Output.new(expected) + end + alias_matcher :a_block_outputting, :output + + # With no args, matches if any error is raised. + # With a named error, matches only if that specific error is raised. + # With a named error and message specified as a String, matches only if both match. + # With a named error and message specified as a Regexp, matches only if both match. + # Pass an optional block to perform extra verifications on the exception matched + # + # @example + # expect { do_something_risky }.to raise_error + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError) + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 } + # expect { do_something_risky }.to raise_error { |error| expect(error.data).to eq 42 } + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky") + # expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/) + # expect { do_something_risky }.to raise_error("that was too risky") + # + # expect { do_something_risky }.not_to raise_error + def raise_error(error=BuiltIn::RaiseError::UndefinedValue, message=nil, &block) + BuiltIn::RaiseError.new(error, message, &block) + end + alias_method :raise_exception, :raise_error + + alias_matcher :a_block_raising, :raise_error do |desc| + desc.sub("raise", "a block raising") + end + + alias_matcher :raising, :raise_error do |desc| + desc.sub("raise", "raising") + end + + # Matches if the target object responds to all of the names + # provided. Names can be Strings or Symbols. + # + # @example + # expect("string").to respond_to(:length) + # + def respond_to(*names) + BuiltIn::RespondTo.new(*names) + end + alias_matcher :an_object_responding_to, :respond_to + alias_matcher :responding_to, :respond_to + + # Passes if the submitted block returns true. Yields target to the + # block. + # + # Generally speaking, this should be thought of as a last resort when + # you can't find any other way to specify the behaviour you wish to + # specify. + # + # If you do find yourself in such a situation, you could always write + # a custom matcher, which would likely make your specs more expressive. + # + # @param description [String] optional description to be used for this matcher. + # + # @example + # expect(5).to satisfy { |n| n > 3 } + # expect(5).to satisfy("be greater than 3") { |n| n > 3 } + def satisfy(description=nil, &block) + BuiltIn::Satisfy.new(description, &block) + end + alias_matcher :an_object_satisfying, :satisfy + alias_matcher :satisfying, :satisfy + + # Matches if the actual value starts with the expected value(s). In the + # case of a string, matches against the first `expected.length` characters + # of the actual string. In the case of an array, matches against the first + # `expected.length` elements of the actual array. + # + # @example + # expect("this string").to start_with "this s" + # expect([0, 1, 2, 3, 4]).to start_with 0 + # expect([0, 2, 3, 4, 4]).to start_with 0, 1 + def start_with(*expected) + BuiltIn::StartWith.new(*expected) + end + alias_matcher :a_collection_starting_with, :start_with + alias_matcher :a_string_starting_with, :start_with + alias_matcher :starting_with, :start_with + + # Given no argument, matches if a proc throws any Symbol. + # + # Given a Symbol, matches if the given proc throws the specified Symbol. + # + # Given a Symbol and an arg, matches if the given proc throws the + # specified Symbol with the specified arg. + # + # @example + # expect { do_something_risky }.to throw_symbol + # expect { do_something_risky }.to throw_symbol(:that_was_risky) + # expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit') + # + # expect { do_something_risky }.not_to throw_symbol + # expect { do_something_risky }.not_to throw_symbol(:that_was_risky) + # expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit') + def throw_symbol(expected_symbol=nil, expected_arg=nil) + BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg) + end + + alias_matcher :a_block_throwing, :throw_symbol do |desc| + desc.sub("throw", "a block throwing") + end + + alias_matcher :throwing, :throw_symbol do |desc| + desc.sub("throw", "throwing") + end + + # Passes if the method called in the expect block yields, regardless + # of whether or not arguments are yielded. + # + # @example + # expect { |b| 5.tap(&b) }.to yield_control + # expect { |b| "a".to_sym(&b) }.not_to yield_control + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + def yield_control + BuiltIn::YieldControl.new + end + alias_matcher :a_block_yielding_control, :yield_control + alias_matcher :yielding_control, :yield_control + + # Passes if the method called in the expect block yields with + # no arguments. Fails if it does not yield, or yields with arguments. + # + # @example + # expect { |b| User.transaction(&b) }.to yield_with_no_args + # expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5` + # expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + # @note This matcher is not designed for use with methods that yield + # multiple times. + def yield_with_no_args + BuiltIn::YieldWithNoArgs.new + end + alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args + alias_matcher :yielding_with_no_args, :yield_with_no_args + + # Given no arguments, matches if the method called in the expect + # block yields with arguments (regardless of what they are or how + # many there are). + # + # Given arguments, matches if the method called in the expect block + # yields with arguments that match the given arguments. + # + # Argument matching is done using `===` (the case match operator) + # and `==`. If the expected and actual arguments match with either + # operator, the matcher will pass. + # + # @example + # expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg + # expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5 + # expect { |b| 5.tap(&b) }.to yield_with_args(Integer) # because Integer === 5 + # expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt" + # + # expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args + # expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3) + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + # @note This matcher is not designed for use with methods that yield + # multiple times. + def yield_with_args(*args) + BuiltIn::YieldWithArgs.new(*args) + end + alias_matcher :a_block_yielding_with_args, :yield_with_args + alias_matcher :yielding_with_args, :yield_with_args + + # Designed for use with methods that repeatedly yield (such as + # iterators). Passes if the method called in the expect block yields + # multiple times with arguments matching those given. + # + # Argument matching is done using `===` (the case match operator) + # and `==`. If the expected and actual arguments match with either + # operator, the matcher will pass. + # + # @example + # expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) + # expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) + # expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2) + # + # @note Your expect block must accept a parameter and pass it on to + # the method-under-test as a block. + def yield_successive_args(*args) + BuiltIn::YieldSuccessiveArgs.new(*args) + end + alias_matcher :a_block_yielding_successive_args, :yield_successive_args + alias_matcher :yielding_successive_args, :yield_successive_args + + # Delegates to {RSpec::Expectations.configuration}. + # This is here because rspec-core's `expect_with` option + # looks for a `configuration` method on the mixin + # (`RSpec::Matchers`) to yield to a block. + # @return [RSpec::Expectations::Configuration] the configuration object + def self.configuration + Expectations.configuration + end + + private + + BE_PREDICATE_REGEX = /^(?:be_(?:an?_)?)(.*)/ + HAS_REGEX = /^(?:have_)(.*)/ + DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX) + + def method_missing(method, *args, &block) + case method.to_s + when BE_PREDICATE_REGEX + BuiltIn::BePredicate.new(method, *args, &block) + when HAS_REGEX + BuiltIn::Has.new(method, *args, &block) + else + super + end + end + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + + if RUBY_VERSION.to_f >= 1.9 + def respond_to_missing?(method, *) + method =~ DYNAMIC_MATCHER_REGEX || super + end + else # for 1.8.7 + # :nocov: + def respond_to?(method, *) + method = method.to_s + method =~ DYNAMIC_MATCHER_REGEX || super + end + public :respond_to? + # :nocov: + end + + # @api private + def self.is_a_matcher?(obj) + return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj + begin + return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher) + rescue NoMethodError + # Some objects, like BasicObject, don't implemented standard + # reflection methods. + return false + end + return false unless obj.respond_to?(:matches?) + + obj.respond_to?(:failure_message) || + obj.respond_to?(:failure_message_for_should) # support legacy matchers + end + + ::RSpec::Support.register_matcher_definition do |obj| + is_a_matcher?(obj) + end + + # @api private + def self.is_a_describable_matcher?(obj) + is_a_matcher?(obj) && obj.respond_to?(:description) + end + + class << self + private + + if RSpec::Support::Ruby.mri? && RUBY_VERSION[0, 3] == '1.9' + # Note that `included` doesn't work for this because it is triggered + # _after_ `RSpec::Matchers` is an ancestor of the inclusion host, rather + # than _before_, like `append_features`. It's important we check this before + # in order to find the cases where it was already previously included. + # @api private + def append_features(mod) + return super if mod < self # `mod < self` indicates a re-inclusion. + + subclasses = ObjectSpace.each_object(Class).select { |c| c < mod && c < self } + return super unless subclasses.any? + + subclasses.reject! { |s| subclasses.any? { |s2| s < s2 } } # Filter to the root ancestor. + subclasses = subclasses.map { |s| "`#{s}`" }.join(", ") + + RSpec.warning "`#{self}` has been included in a superclass (`#{mod}`) " \ + "after previously being included in subclasses (#{subclasses}), " \ + "which can trigger infinite recursion from `super` due to an MRI 1.9 bug " \ + "(https://redmine.ruby-lang.org/issues/3351). To work around this, " \ + "either upgrade to MRI 2.0+, include a dup of the module (e.g. " \ + "`include #{self}.dup`), or find a way to include `#{self}` in `#{mod}` " \ + "before it is included in subclasses (#{subclasses}). See " \ + "https://github.com/rspec/rspec-expectations/issues/814 for more info" + + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/aliased_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/aliased_matcher.rb new file mode 100644 index 0000000000..c52c4c4c76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/aliased_matcher.rb @@ -0,0 +1,116 @@ +module RSpec + module Matchers + # Decorator that wraps a matcher and overrides `description` + # using the provided block in order to support an alias + # of a matcher. This is intended for use when composing + # matchers, so that you can use an expression like + # `include( a_value_within(0.1).of(3) )` rather than + # `include( be_within(0.1).of(3) )`, and have the corresponding + # description read naturally. + # + # @api private + class AliasedMatcher < MatcherDelegator + def initialize(base_matcher, description_block) + @description_block = description_block + super(base_matcher) + end + + # Forward messages on to the wrapped matcher. + # Since many matchers provide a fluent interface + # (e.g. `a_value_within(0.1).of(3)`), we need to wrap + # the returned value if it responds to `description`, + # so that our override can be applied when it is eventually + # used. + def method_missing(*) + return_val = super + return return_val unless RSpec::Matchers.is_a_matcher?(return_val) + self.class.new(return_val, @description_block) + end + + # Provides the description of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The description is different + # to reflect the aliased name. + # + # @api private + def description + @description_block.call(super) + end + + # Provides the failure_message of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The failure_message is different + # to reflect the aliased name. + # + # @api private + def failure_message + @description_block.call(super) + end + + # Provides the failure_message_when_negated of the aliased matcher. Aliased matchers + # are designed to behave identically to the original matcher except + # for the description and failure messages. The failure_message_when_negated is different + # to reflect the aliased name. + # + # @api private + def failure_message_when_negated + @description_block.call(super) + end + end + + # Decorator used for matchers that have special implementations of + # operators like `==` and `===`. + # @private + class AliasedMatcherWithOperatorSupport < AliasedMatcher + # We undef these so that they get delegated via `method_missing`. + undef == + undef === + end + + # @private + class AliasedNegatedMatcher < AliasedMatcher + def matches?(*args, &block) + if @base_matcher.respond_to?(:does_not_match?) + @base_matcher.does_not_match?(*args, &block) + else + !super + end + end + + def does_not_match?(*args, &block) + @base_matcher.matches?(*args, &block) + end + + def failure_message + optimal_failure_message(__method__, :failure_message_when_negated) + end + + def failure_message_when_negated + optimal_failure_message(__method__, :failure_message) + end + + private + + DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages + + # For a matcher that uses the default failure messages, we prefer to + # use the override provided by the `description_block`, because it + # includes the phrasing that the user has expressed a preference for + # by going through the effort of defining a negated matcher. + # + # However, if the override didn't actually change anything, then we + # should return the opposite failure message instead -- the overridden + # message is going to be confusing if we return it as-is, as it represents + # the non-negated failure message for a negated match (or vice versa). + def optimal_failure_message(same, inverted) + if DefaultFailureMessages.has_default_failure_messages?(@base_matcher) + base_message = @base_matcher.__send__(same) + overridden = @description_block.call(base_message) + return overridden if overridden != base_message + end + + @base_matcher.__send__(inverted) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in.rb new file mode 100644 index 0000000000..e6237ff08b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in.rb @@ -0,0 +1,53 @@ +RSpec::Support.require_rspec_matchers "built_in/base_matcher" + +module RSpec + module Matchers + # Container module for all built-in matchers. The matcher classes are here + # (rather than directly under `RSpec::Matchers`) in order to prevent name + # collisions, since `RSpec::Matchers` gets included into the user's namespace. + # + # Autoloading is used to delay when the matcher classes get loaded, allowing + # rspec-matchers to boot faster, and avoiding loading matchers the user is + # not using. + module BuiltIn + autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of' + autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of' + autoload :BeBetween, 'rspec/matchers/built_in/be_between' + autoload :Be, 'rspec/matchers/built_in/be' + autoload :BeComparedTo, 'rspec/matchers/built_in/be' + autoload :BeFalsey, 'rspec/matchers/built_in/be' + autoload :BeHelpers, 'rspec/matchers/built_in/be' + autoload :BeNil, 'rspec/matchers/built_in/be' + autoload :BePredicate, 'rspec/matchers/built_in/has' + autoload :BeTruthy, 'rspec/matchers/built_in/be' + autoload :BeWithin, 'rspec/matchers/built_in/be_within' + autoload :Change, 'rspec/matchers/built_in/change' + autoload :Compound, 'rspec/matchers/built_in/compound' + autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly' + autoload :Cover, 'rspec/matchers/built_in/cover' + autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with' + autoload :Eq, 'rspec/matchers/built_in/eq' + autoload :Eql, 'rspec/matchers/built_in/eql' + autoload :Equal, 'rspec/matchers/built_in/equal' + autoload :Exist, 'rspec/matchers/built_in/exist' + autoload :Has, 'rspec/matchers/built_in/has' + autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes' + autoload :Include, 'rspec/matchers/built_in/include' + autoload :All, 'rspec/matchers/built_in/all' + autoload :Match, 'rspec/matchers/built_in/match' + autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :OperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :Output, 'rspec/matchers/built_in/output' + autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators' + autoload :RaiseError, 'rspec/matchers/built_in/raise_error' + autoload :RespondTo, 'rspec/matchers/built_in/respond_to' + autoload :Satisfy, 'rspec/matchers/built_in/satisfy' + autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with' + autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol' + autoload :YieldControl, 'rspec/matchers/built_in/yield' + autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield' + autoload :YieldWithArgs, 'rspec/matchers/built_in/yield' + autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/all.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/all.rb new file mode 100644 index 0000000000..27cce20d17 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/all.rb @@ -0,0 +1,86 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `all`. + # Not intended to be instantiated directly. + class All < BaseMatcher + # @private + attr_reader :matcher, :failed_objects + + def initialize(matcher) + @matcher = matcher + @failed_objects = {} + end + + # @private + def does_not_match?(_actual) + raise NotImplementedError, '`expect().not_to all( matcher )` is not supported.' + end + + # @api private + # @return [String] + def failure_message + unless iterable? + return "#{improve_hash_formatting(super)}, but was not iterable" + end + + all_messages = [improve_hash_formatting(super)] + failed_objects.each do |index, matcher_failure_message| + all_messages << failure_message_for_item(index, matcher_failure_message) + end + all_messages.join("\n\n") + end + + # @api private + # @return [String] + def description + improve_hash_formatting "all #{description_of matcher}" + end + + private + + def match(_expected, _actual) + return false unless iterable? + + index_failed_objects + failed_objects.empty? + end + + def index_failed_objects + actual.each_with_index do |actual_item, index| + cloned_matcher = matcher.clone + matches = cloned_matcher.matches?(actual_item) + failed_objects[index] = cloned_matcher.failure_message unless matches + end + end + + def failure_message_for_item(index, failure_message) + failure_message = indent_multiline_message(add_new_line_if_needed(failure_message)) + indent_multiline_message("object at index #{index} failed to match:#{failure_message}") + end + + def add_new_line_if_needed(message) + message.start_with?("\n") ? message : "\n#{message}" + end + + def indent_multiline_message(message) + message = message.sub(/\n+\z/, '') + message.lines.map do |line| + line =~ /\S/ ? ' ' + line : line + end.join + end + + def initialize_copy(other) + @matcher = @matcher.clone + @failed_objects = @failed_objects.clone + super + end + + def iterable? + @actual.respond_to?(:each_with_index) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/base_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/base_matcher.rb new file mode 100644 index 0000000000..ec957be9db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/base_matcher.rb @@ -0,0 +1,198 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # + # Used _internally_ as a base class for matchers that ship with + # rspec-expectations and rspec-rails. + # + # ### Warning: + # + # This class is for internal use, and subject to change without notice. + # We strongly recommend that you do not base your custom matchers on this + # class. If/when this changes, we will announce it and remove this warning. + class BaseMatcher + include RSpec::Matchers::Composable + + # @api private + # Used to detect when no arg is passed to `initialize`. + # `nil` cannot be used because it's a valid value to pass. + UNDEFINED = Object.new.freeze + + # @private + attr_reader :actual, :expected, :rescued_exception + + # @private + attr_writer :matcher_name + + def initialize(expected=UNDEFINED) + @expected = expected unless UNDEFINED.equal?(expected) + end + + # @api private + # Indicates if the match is successful. Delegates to `match`, which + # should be defined on a subclass. Takes care of consistently + # initializing the `actual` attribute. + def matches?(actual) + @actual = actual + match(expected, actual) + end + + # @api private + # Used to wrap a block of code that will indicate failure by + # raising one of the named exceptions. + # + # This is used by rspec-rails for some of its matchers that + # wrap rails' assertions. + def match_unless_raises(*exceptions) + exceptions.unshift Exception if exceptions.empty? + begin + yield + true + rescue *exceptions => @rescued_exception + false + end + end + + # @api private + # Generates a description using {EnglishPhrasing}. + # @return [String] + def description + desc = EnglishPhrasing.split_words(self.class.matcher_name) + desc << EnglishPhrasing.list(@expected) if defined?(@expected) + desc + end + + # @api private + # Matchers are not diffable by default. Override this to make your + # subclass diffable. + def diffable? + false + end + + # @api private + # Most matchers are value matchers (i.e. meant to work with `expect(value)`) + # rather than block matchers (i.e. meant to work with `expect { }`), so + # this defaults to false. Block matchers must override this to return true. + def supports_block_expectations? + false + end + + # @private + def supports_value_expectations? + true + end + + # @api private + def expects_call_stack_jump? + false + end + + # @private + def expected_formatted + RSpec::Support::ObjectFormatter.format(@expected) + end + + # @private + def actual_formatted + RSpec::Support::ObjectFormatter.format(@actual) + end + + # @private + def self.matcher_name + @matcher_name ||= underscore(name.split('::').last) + end + + # @private + def matcher_name + if defined?(@matcher_name) + @matcher_name + else + self.class.matcher_name + end + end + + # @private + # Borrowed from ActiveSupport. + def self.underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + 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 + private_class_method :underscore + + private + + def assert_ivars(*expected_ivars) + return unless (expected_ivars - present_ivars).any? + ivar_list = EnglishPhrasing.list(expected_ivars) + raise "#{self.class.name} needs to supply#{ivar_list}" + end + + if RUBY_VERSION.to_f < 1.9 + # :nocov: + def present_ivars + instance_variables.map(&:to_sym) + end + # :nocov: + else + alias present_ivars instance_variables + end + + # @private + module HashFormatting + # `{ :a => 5, :b => 2 }.inspect` produces: + # + # {:a=>5, :b=>2} + # + # ...but it looks much better as: + # + # {:a => 5, :b => 2} + # + # This is idempotent and safe to run on a string multiple times. + def improve_hash_formatting(inspect_string) + inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2') + end + module_function :improve_hash_formatting + end + + include HashFormatting + + # @api private + # Provides default implementations of failure messages, based on the `description`. + module DefaultFailureMessages + # @api private + # Provides a good generic failure message. Based on `description`. + # When subclassing, if you are not satisfied with this failure message + # you often only need to override `description`. + # @return [String] + def failure_message + "expected #{description_of @actual} to #{description}".dup + end + + # @api private + # Provides a good generic negative failure message. Based on `description`. + # When subclassing, if you are not satisfied with this failure message + # you often only need to override `description`. + # @return [String] + def failure_message_when_negated + "expected #{description_of @actual} not to #{description}".dup + end + + # @private + def self.has_default_failure_messages?(matcher) + matcher.method(:failure_message).owner == self && + matcher.method(:failure_message_when_negated).owner == self + rescue NameError + false + end + end + + include DefaultFailureMessages + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be.rb new file mode 100644 index 0000000000..40d4017181 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be.rb @@ -0,0 +1,191 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_truthy`. + # Not intended to be instantiated directly. + class BeTruthy < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: truthy value\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: falsey value\n got: #{actual_formatted}" + end + + private + + def match(_, actual) + !!actual + end + end + + # @api private + # Provides the implementation for `be_falsey`. + # Not intended to be instantiated directly. + class BeFalsey < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: falsey value\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: truthy value\n got: #{actual_formatted}" + end + + private + + def match(_, actual) + !actual + end + end + + # @api private + # Provides the implementation for `be_nil`. + # Not intended to be instantiated directly. + class BeNil < BaseMatcher + # @api private + # @return [String] + def failure_message + "expected: nil\n got: #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected: not nil\n got: nil" + end + + private + + def match(_, actual) + actual.nil? + end + end + + # @private + module BeHelpers + private + + def args_to_s + @args.empty? ? "" : parenthesize(inspected_args.join(', ')) + end + + def parenthesize(string) + "(#{string})" + end + + def inspected_args + @args.map { |a| RSpec::Support::ObjectFormatter.format(a) } + end + + def expected_to_sentence + EnglishPhrasing.split_words(@expected) + end + + def args_to_sentence + EnglishPhrasing.list(@args) + end + end + + # @api private + # Provides the implementation for `be`. + # Not intended to be instantiated directly. + class Be < BaseMatcher + include BeHelpers + + def initialize(*args) + @args = args + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to evaluate to true" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} to evaluate to false" + end + + [:==, :<, :<=, :>=, :>, :===, :=~].each do |operator| + define_method operator do |operand| + BeComparedTo.new(operand, operator) + end + end + + private + + def match(_, actual) + !!actual + end + end + + # @api private + # Provides the implementation of `be value`. + # Not intended to be instantiated directly. + class BeComparedTo < BaseMatcher + include BeHelpers + + def initialize(operand, operator) + @expected = operand + @operator = operator + @args = [] + end + + def matches?(actual) + perform_match(actual) + rescue ArgumentError, NoMethodError + false + end + + def does_not_match?(actual) + !perform_match(actual) + rescue ArgumentError, NoMethodError + false + end + + # @api private + # @return [String] + def failure_message + "expected: #{@operator} #{expected_formatted}\n" \ + " got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}" + end + + # @api private + # @return [String] + def failure_message_when_negated + message = "`expect(#{actual_formatted}).not_to " \ + "be #{@operator} #{expected_formatted}`" + if [:<, :>, :<=, :>=].include?(@operator) + message + " not only FAILED, it is a bit confusing." + else + message + end + end + + # @api private + # @return [String] + def description + "be #{@operator} #{expected_to_sentence}#{args_to_sentence}" + end + + private + + def perform_match(actual) + @actual = actual + @actual.__send__ @operator, @expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_between.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_between.rb new file mode 100644 index 0000000000..55f084e4b7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_between.rb @@ -0,0 +1,77 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_between`. + # Not intended to be instantiated directly. + class BeBetween < BaseMatcher + def initialize(min, max) + @min, @max = min, max + inclusive + end + + # @api public + # Makes the between comparison inclusive. + # + # @example + # expect(3).to be_between(2, 3).inclusive + # + # @note The matcher is inclusive by default; this simply provides + # a way to be more explicit about it. + def inclusive + @less_than_operator = :<= + @greater_than_operator = :>= + @mode = :inclusive + self + end + + # @api public + # Makes the between comparison exclusive. + # + # @example + # expect(3).to be_between(2, 4).exclusive + def exclusive + @less_than_operator = :< + @greater_than_operator = :> + @mode = :exclusive + self + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + comparable? && compare + rescue ArgumentError + false + end + + # @api private + # @return [String] + def failure_message + "#{super}#{not_comparable_clause}" + end + + # @api private + # @return [String] + def description + "be between #{description_of @min} and #{description_of @max} (#{@mode})" + end + + private + + def comparable? + @actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator) + end + + def not_comparable_clause + ", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable? + end + + def compare + @actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_instance_of.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_instance_of.rb new file mode 100644 index 0000000000..e71d380a03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_instance_of.rb @@ -0,0 +1,26 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_an_instance_of`. + # Not intended to be instantiated directly. + class BeAnInstanceOf < BaseMatcher + # @api private + # @return [String] + def description + "be an instance of #{expected}" + end + + private + + def match(expected, actual) + actual.instance_of?(expected) + rescue NoMethodError + raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object responds to #instance_of? method " \ + "but a `NoMethodError` was encountered instead." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_kind_of.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_kind_of.rb new file mode 100644 index 0000000000..4fe23bd92e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_kind_of.rb @@ -0,0 +1,20 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_a_kind_of`. + # Not intended to be instantiated directly. + class BeAKindOf < BaseMatcher + private + + def match(expected, actual) + actual.kind_of?(expected) + rescue NoMethodError + raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object responds to #kind_of? method " \ + "but a `NoMethodError` was encountered instead." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_within.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_within.rb new file mode 100644 index 0000000000..7a2b5b5bef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/be_within.rb @@ -0,0 +1,72 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `be_within`. + # Not intended to be instantiated directly. + class BeWithin < BaseMatcher + def initialize(delta) + @delta = delta + end + + # @api public + # Sets the expected value. + def of(expected) + @expected = expected + @tolerance = @delta + @unit = '' + self + end + + # @api public + # Sets the expected value, and makes the matcher do + # a percent comparison. + def percent_of(expected) + @expected = expected + @tolerance = @expected.abs * @delta / 100.0 + @unit = '%' + self + end + + # @private + def matches?(actual) + @actual = actual + raise needs_expected unless defined? @expected + numeric? && (@actual - @expected).abs <= @tolerance + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to #{description}#{not_numeric_clause}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to #{description}" + end + + # @api private + # @return [String] + def description + "be within #{@delta}#{@unit} of #{expected_formatted}" + end + + private + + def numeric? + @actual.respond_to?(:-) + end + + def needs_expected + ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)" + end + + def not_numeric_clause + ", but it could not be treated as a numeric value" unless numeric? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/change.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/change.rb new file mode 100644 index 0000000000..c43666a486 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/change.rb @@ -0,0 +1,450 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `change`. + # Not intended to be instantiated directly. + class Change < BaseMatcher + # @api public + # Specifies the delta of the expected change. + def by(expected_delta) + ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta| + values_match?(expected_delta, actual_delta) + end + end + + # @api public + # Specifies a minimum delta of the expected change. + def by_at_least(minimum) + ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta| + actual_delta >= minimum + end + end + + # @api public + # Specifies a maximum delta of the expected change. + def by_at_most(maximum) + ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta| + actual_delta <= maximum + end + end + + # @api public + # Specifies the new value you expect. + def to(value) + ChangeToValue.new(change_details, value) + end + + # @api public + # Specifies the original value. + def from(value) + ChangeFromValue.new(change_details, value) + end + + # @private + def matches?(event_proc) + raise_block_syntax_error if block_given? + perform_change(event_proc) && change_details.changed? + end + + def does_not_match?(event_proc) + raise_block_syntax_error if block_given? + perform_change(event_proc) && !change_details.changed? + end + + # @api private + # @return [String] + def failure_message + "expected #{change_details.value_representation} to have changed, " \ + "but #{positive_failure_reason}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{change_details.value_representation} not to have changed, " \ + "but #{negative_failure_reason}" + end + + # @api private + # @return [String] + def description + "change #{change_details.value_representation}" + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def initialize(receiver=nil, message=nil, &block) + @receiver = receiver + @message = message + @block = block + end + + def change_details + @change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block) + end + + def perform_change(event_proc) + @event_proc = event_proc + change_details.perform_change(event_proc) do |actual_before| + # pre-compute values derived from the `before` value before the + # mutation is applied, in case the specified mutation is mutation + # of a single object (rather than a changing what object a method + # returns). We need to cache these values before the `before` value + # they are based on potentially gets mutated. + @actual_before_description = description_of(actual_before) + end + end + + def raise_block_syntax_error + raise SyntaxError, "Block not received by the `change` matcher. " \ + "Perhaps you want to use `{ ... }` instead of do/end?" + end + + def positive_failure_reason + return "was not given a block" unless Proc === @event_proc + "is still #{@actual_before_description}" + end + + def negative_failure_reason + return "was not given a block" unless Proc === @event_proc + "did change from #{@actual_before_description} " \ + "to #{description_of change_details.actual_after}" + end + end + + # Used to specify a relative change. + # @api private + class ChangeRelatively < BaseMatcher + def initialize(change_details, expected_delta, relativity, &comparer) + @change_details = change_details + @expected_delta = expected_delta + @relativity = relativity + @comparer = comparer + end + + # @private + def failure_message + "expected #{@change_details.value_representation} to have changed " \ + "#{@relativity.to_s.tr('_', ' ')} " \ + "#{description_of @expected_delta}, but #{failure_reason}" + end + + # @private + def matches?(event_proc) + @event_proc = event_proc + @change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta) + end + + # @private + def does_not_match?(_event_proc) + raise NotImplementedError, "`expect { }.not_to change " \ + "{ }.#{@relativity}()` is not supported" + end + + # @private + def description + "change #{@change_details.value_representation} " \ + "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}" + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def failure_reason + return "was not given a block" unless Proc === @event_proc + "was changed by #{description_of @change_details.actual_delta}" + end + end + + # @api private + # Base class for specifying a change from and/or to specific values. + class SpecificValuesChange < BaseMatcher + # @private + MATCH_ANYTHING = ::Object.ancestors.last + + def initialize(change_details, from, to) + @change_details = change_details + @expected_before = from + @expected_after = to + end + + # @private + def matches?(event_proc) + perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after? + end + + # @private + def description + "change #{@change_details.value_representation} #{change_description}" + end + + # @private + def failure_message + return not_given_a_block_failure unless Proc === @event_proc + return before_value_failure unless @matches_before + return did_not_change_failure unless @change_details.changed? + after_value_failure + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def perform_change(event_proc) + @event_proc = event_proc + @change_details.perform_change(event_proc) do |actual_before| + # pre-compute values derived from the `before` value before the + # mutation is applied, in case the specified mutation is mutation + # of a single object (rather than a changing what object a method + # returns). We need to cache these values before the `before` value + # they are based on potentially gets mutated. + @matches_before = values_match?(@expected_before, actual_before) + @actual_before_description = description_of(actual_before) + end + end + + def matches_after? + values_match?(@expected_after, @change_details.actual_after) + end + + def before_value_failure + "expected #{@change_details.value_representation} " \ + "to have initially been #{description_of @expected_before}, " \ + "but was #{@actual_before_description}" + end + + def after_value_failure + "expected #{@change_details.value_representation} " \ + "to have changed to #{description_of @expected_after}, " \ + "but is now #{description_of @change_details.actual_after}" + end + + def did_not_change_failure + "expected #{@change_details.value_representation} " \ + "to have changed #{change_description}, but did not change" + end + + def did_change_failure + "expected #{@change_details.value_representation} not to have changed, but " \ + "did change from #{@actual_before_description} " \ + "to #{description_of @change_details.actual_after}" + end + + def not_given_a_block_failure + "expected #{@change_details.value_representation} to have changed " \ + "#{change_description}, but was not given a block" + end + end + + # @api private + # Used to specify a change from a specific value + # (and, optionally, to a specific value). + class ChangeFromValue < SpecificValuesChange + def initialize(change_details, expected_before) + @description_suffix = nil + super(change_details, expected_before, MATCH_ANYTHING) + end + + # @api public + # Specifies the new value you expect. + def to(value) + @expected_after = value + @description_suffix = " to #{description_of value}" + self + end + + # @private + def does_not_match?(event_proc) + if @description_suffix + raise NotImplementedError, "`expect { }.not_to change { }.to()` " \ + "is not supported" + end + + perform_change(event_proc) && !@change_details.changed? && @matches_before + end + + # @private + def failure_message_when_negated + return not_given_a_block_failure unless Proc === @event_proc + return before_value_failure unless @matches_before + did_change_failure + end + + private + + def change_description + "from #{description_of @expected_before}#{@description_suffix}" + end + end + + # @api private + # Used to specify a change to a specific value + # (and, optionally, from a specific value). + class ChangeToValue < SpecificValuesChange + def initialize(change_details, expected_after) + @description_suffix = nil + super(change_details, MATCH_ANYTHING, expected_after) + end + + # @api public + # Specifies the original value. + def from(value) + @expected_before = value + @description_suffix = " from #{description_of value}" + self + end + + # @private + def does_not_match?(_event_proc) + raise NotImplementedError, "`expect { }.not_to change { }.to()` " \ + "is not supported" + end + + private + + def change_description + "to #{description_of @expected_after}#{@description_suffix}" + end + end + + # @private + # Encapsulates the details of the before/after values. + # + # Note that this class exposes the `actual_after` value, to allow the + # matchers above to derive failure messages, etc from the value on demand + # as needed, but it intentionally does _not_ expose the `actual_before` + # value. Some usages of the `change` matcher mutate a specific object + # returned by the value proc, which means that failure message snippets, + # etc, which are derived from the `before` value may not be accurate if + # they are lazily computed as needed. We must pre-compute them before + # applying the change in the `expect` block. To ensure that all `change` + # matchers do that properly, we do not expose the `actual_before` value. + # Instead, matchers must pass a block to `perform_change`, which yields + # the `actual_before` value before applying the change. + class ChangeDetails + attr_reader :actual_after + + UNDEFINED = Module.new.freeze + + def initialize(matcher_name, receiver=nil, message=nil, &block) + if receiver && !message + raise( + ArgumentError, + "`change` requires either an object and message " \ + "(`change(obj, :msg)`) or a block (`change { }`). " \ + "You passed an object but no message." + ) + end + + @matcher_name = matcher_name + @receiver = receiver + @message = message + @value_proc = block + # TODO: temporary measure to mute warning of access to an initialized + # instance variable when a deprecated implicit block expectation + # syntax is used. This may be removed once `fail` is used, and the + # matcher never issues this warning. + @actual_after = UNDEFINED + end + + def value_representation + @value_representation ||= + if @message + "`#{message_notation(@receiver, @message)}`" + elsif (value_block_snippet = extract_value_block_snippet) + "`#{value_block_snippet}`" + else + 'result' + end + end + + def perform_change(event_proc) + @actual_before = evaluate_value_proc + @before_hash = @actual_before.hash + yield @actual_before if block_given? + + return false unless Proc === event_proc + event_proc.call + + @actual_after = evaluate_value_proc + @actual_hash = @actual_after.hash + true + end + + def changed? + # Consider it changed if either: + # + # - The before/after values are unequal + # - The before/after values have different hash values + # + # The latter case specifically handles the case when the value proc + # returns the exact same object, but it has been mutated. + # + # Note that it is not sufficient to only check the hashes; it is + # possible for two values to be unequal (and of different classes) + # but to return the same hash value. Also, some objects may change + # their hash after being compared with `==`/`!=`. + @actual_before != @actual_after || @before_hash != @actual_hash + end + + def actual_delta + @actual_after - @actual_before + end + + private + + def evaluate_value_proc + @value_proc ? @value_proc.call : @receiver.__send__(@message) + end + + def message_notation(receiver, message) + case receiver + when Module + "#{receiver}.#{message}" + else + "#{Support.class_of(receiver)}##{message}" + end + end + + if RSpec::Support::RubyFeatures.ripper_supported? + def extract_value_block_snippet + return nil unless @value_proc + Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name) + end + else + def extract_value_block_snippet + nil + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/compound.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/compound.rb new file mode 100644 index 0000000000..74f8ccd2ed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/compound.rb @@ -0,0 +1,290 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Base class for `and` and `or` compound matchers. + class Compound < BaseMatcher + # @private + attr_reader :matcher_1, :matcher_2, :evaluator + + def initialize(matcher_1, matcher_2) + @matcher_1 = matcher_1 + @matcher_2 = matcher_2 + end + + # @private + def does_not_match?(_actual) + raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \ + "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \ + "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \ + "use `expect(...).to matcher.#{conjunction} matcher`." + end + + # @api private + # @return [String] + def description + "#{matcher_1.description} #{conjunction} #{matcher_2.description}" + end + + # @api private + def supports_block_expectations? + matcher_supports_block_expectations?(matcher_1) && + matcher_supports_block_expectations?(matcher_2) + end + + # @api private + def supports_value_expectations? + matcher_supports_value_expectations?(matcher_1) && + matcher_supports_value_expectations?(matcher_2) + end + + # @api private + def expects_call_stack_jump? + NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) || + NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2) + end + + # @api private + # @return [Boolean] + def diffable? + matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2) + end + + # @api private + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def expected + return nil unless evaluator + ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list) + end + + protected + + def diffable_matcher_list + list = [] + list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches? + list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches? + list + end + + private + + def initialize_copy(other) + @matcher_1 = @matcher_1.clone + @matcher_2 = @matcher_2.clone + super + end + + def match(_expected, actual) + evaluator_klass = if supports_block_expectations? && Proc === actual + NestedEvaluator + else + SequentialEvaluator + end + + @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2) + end + + def indent_multiline_message(message) + message.lines.map do |line| + line =~ /\S/ ? ' ' + line : line + end.join + end + + def compound_failure_message + "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \ + "\n\n...#{conjunction}:" \ + "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}" + end + + def matcher_1_matches? + evaluator.matcher_matches?(matcher_1) + end + + def matcher_2_matches? + evaluator.matcher_matches?(matcher_2) + end + + def matcher_supports_block_expectations?(matcher) + matcher.supports_block_expectations? + rescue NoMethodError + false + end + + def matcher_supports_value_expectations?(matcher) + matcher.supports_value_expectations? + rescue NoMethodError + true + end + + def matcher_is_diffable?(matcher) + matcher.diffable? + rescue NoMethodError + false + end + + def diffable_matcher_list_for(matcher) + return [] unless matcher_is_diffable?(matcher) + return matcher.diffable_matcher_list if Compound === matcher + [matcher] + end + + # For value expectations, we can evaluate the matchers sequentially. + class SequentialEvaluator + def initialize(actual, *) + @actual = actual + end + + def matcher_matches?(matcher) + matcher.matches?(@actual) + end + end + + # Normally, we evaluate the matching sequentially. For an expression like + # `expect(x).to foo.and bar`, this becomes: + # + # expect(x).to foo + # expect(x).to bar + # + # For block expectations, we need to nest them instead, so that + # `expect { x }.to foo.and bar` becomes: + # + # expect { + # expect { x }.to foo + # }.to bar + # + # This is necessary so that the `expect` block is only executed once. + class NestedEvaluator + def initialize(actual, matcher_1, matcher_2) + @actual = actual + @matcher_1 = matcher_1 + @matcher_2 = matcher_2 + @match_results = {} + + inner, outer = order_block_matchers + + @match_results[outer] = outer.matches?(Proc.new do |*args| + @match_results[inner] = inner.matches?(inner_matcher_block(args)) + end) + end + + def matcher_matches?(matcher) + @match_results.fetch(matcher) do + raise ArgumentError, "Your #{matcher.description} has no match " \ + "results, this can occur when an unexpected call stack or " \ + "local jump occurs. Perhaps one of your matchers needs to " \ + "declare `expects_call_stack_jump?` as `true`?" + end + end + + private + + # Some block matchers (such as `yield_xyz`) pass args to the `expect` block. + # When such a matcher is used as the outer matcher, we need to forward the + # the args on to the `expect` block. + def inner_matcher_block(outer_args) + return @actual if outer_args.empty? + + Proc.new do |*inner_args| + unless inner_args.empty? + raise ArgumentError, "(#{@matcher_1.description}) and " \ + "(#{@matcher_2.description}) cannot be combined in a compound expectation " \ + "since both matchers pass arguments to the block." + end + + @actual.call(*outer_args) + end + end + + # For a matcher like `raise_error` or `throw_symbol`, where the block will jump + # up the call stack, we need to order things so that it is the inner matcher. + # For example, we need it to be this: + # + # expect { + # expect { + # x += 1 + # raise "boom" + # }.to raise_error("boom") + # }.to change { x }.by(1) + # + # ...rather than: + # + # expect { + # expect { + # x += 1 + # raise "boom" + # }.to change { x }.by(1) + # }.to raise_error("boom") + # + # In the latter case, the after-block logic in the `change` matcher would never + # get executed because the `raise "boom"` line would jump to the `rescue` in the + # `raise_error` logic, so only the former case will work properly. + # + # This method figures out which matcher should be the inner matcher and which + # should be the outer matcher. + def order_block_matchers + return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2) + return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1) + + raise ArgumentError, "(#{@matcher_1.description}) and " \ + "(#{@matcher_2.description}) cannot be combined in a compound expectation " \ + "because they both expect a call stack jump." + end + + def self.matcher_expects_call_stack_jump?(matcher) + matcher.expects_call_stack_jump? + rescue NoMethodError + false + end + end + + # @api public + # Matcher used to represent a compound `and` expectation. + class And < self + # @api private + # @return [String] + def failure_message + if matcher_1_matches? + matcher_2.failure_message + elsif matcher_2_matches? + matcher_1.failure_message + else + compound_failure_message + end + end + + private + + def match(*) + super + matcher_1_matches? && matcher_2_matches? + end + + def conjunction + "and" + end + end + + # @api public + # Matcher used to represent a compound `or` expectation. + class Or < self + # @api private + # @return [String] + def failure_message + compound_failure_message + end + + private + + def match(*) + super + matcher_1_matches? || matcher_2_matches? + end + + def conjunction + "or" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/contain_exactly.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/contain_exactly.rb new file mode 100644 index 0000000000..0fff2d79f8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/contain_exactly.rb @@ -0,0 +1,310 @@ +module RSpec + module Matchers + module BuiltIn + # rubocop:disable Metrics/ClassLength + # @api private + # Provides the implementation for `contain_exactly` and `match_array`. + # Not intended to be instantiated directly. + class ContainExactly < BaseMatcher + # @api private + # @return [String] + def failure_message + if Array === actual + generate_failure_message + else + "expected a collection that can be converted to an array with " \ + "`#to_ary` or `#to_a`, but got #{actual_formatted}" + end + end + + # @api private + # @return [String] + def failure_message_when_negated + list = EnglishPhrasing.list(surface_descriptions_in(expected)) + "expected #{actual_formatted} not to contain exactly#{list}" + end + + # @api private + # @return [String] + def description + list = EnglishPhrasing.list(surface_descriptions_in(expected)) + "contain exactly#{list}" + end + + def matches?(actual) + @pairings_maximizer = nil + @best_solution = nil + @extra_items = nil + @missing_items = nil + super(actual) + end + + private + + def generate_failure_message + message = expected_collection_line + message += actual_collection_line + message += missing_elements_line unless missing_items.empty? + message += extra_elements_line unless extra_items.empty? + message + end + + def expected_collection_line + message_line('expected collection contained', expected, true) + end + + def actual_collection_line + message_line('actual collection contained', actual) + end + + def missing_elements_line + message_line('the missing elements were', missing_items, true) + end + + def extra_elements_line + message_line('the extra elements were', extra_items) + end + + def describe_collection(collection, surface_descriptions=false) + if surface_descriptions + "#{description_of(safe_sort(surface_descriptions_in collection))}\n" + else + "#{description_of(safe_sort(collection))}\n" + end + end + + def message_line(prefix, collection, surface_descriptions=false) + "%-32s%s" % [prefix + ':', + describe_collection(collection, surface_descriptions)] + end + + def match(_expected, _actual) + return false unless convert_actual_to_an_array + match_when_sorted? || (extra_items.empty? && missing_items.empty?) + end + + # This cannot always work (e.g. when dealing with unsortable items, + # or matchers as expected items), but it's practically free compared to + # the slowness of the full matching algorithm, and in common cases this + # works, so it's worth a try. + def match_when_sorted? + values_match?(safe_sort(expected), safe_sort(actual)) + end + + def convert_actual_to_an_array + if actual.respond_to?(:to_ary) + @actual = actual.to_ary + elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual) + @actual = actual.to_a + else + false + end + end + + def safe_sort(array) + array.sort + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + array + end + + if RUBY_VERSION == "1.8.7" + def to_a_disallowed?(object) + case object + when NilClass, String then true + else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner + end + end + else + def to_a_disallowed?(object) + NilClass === object + end + end + + def missing_items + @missing_items ||= best_solution.unmatched_expected_indexes.map do |index| + expected[index] + end + end + + def extra_items + @extra_items ||= best_solution.unmatched_actual_indexes.map do |index| + actual[index] + end + end + + def best_solution + @best_solution ||= pairings_maximizer.find_best_solution + end + + def pairings_maximizer + @pairings_maximizer ||= begin + expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }] + actual_matches = Hash[Array.new(actual.size) { |i| [i, []] }] + + expected.each_with_index do |e, ei| + actual.each_with_index do |a, ai| + next unless values_match?(e, a) + + expected_matches[ei] << ai + actual_matches[ai] << ei + end + end + + PairingsMaximizer.new(expected_matches, actual_matches) + end + end + + # Once we started supporting composing matchers, the algorithm for this matcher got + # much more complicated. Consider this expression: + # + # expect(["fool", "food"]).to contain_exactly(/foo/, /fool/) + # + # This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but + # the original algorithm used by this matcher would pair the first elements it could + # (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have + # an expected element which is a matcher that matches a superset of actual items + # compared to another expected element matcher, we need to consider every possible pairing. + # + # This class is designed to maximize the number of actual/expected pairings -- or, + # conversely, to minimize the number of unpaired items. It's essentially a brute + # force solution, but with a few heuristics applied to reduce the size of the + # problem space: + # + # * Any items which match none of the items in the other list are immediately + # placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array. + # The extra items and missing items in the matcher failure message are derived + # from these arrays. + # * Any items which reciprocally match only each other are paired up and not + # considered further. + # + # What's left is only the items which match multiple items from the other list + # (or vice versa). From here, it performs a brute-force depth-first search, + # looking for a solution which pairs all elements in both lists, or, barring that, + # that produces the fewest unmatched items. + # + # @private + class PairingsMaximizer + # @private + Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes, + :indeterminate_expected_indexes, :indeterminate_actual_indexes) do + def worse_than?(other) + unmatched_item_count > other.unmatched_item_count + end + + def candidate? + indeterminate_expected_indexes.empty? && + indeterminate_actual_indexes.empty? + end + + def ideal? + candidate? && ( + unmatched_expected_indexes.empty? || + unmatched_actual_indexes.empty? + ) + end + + def unmatched_item_count + unmatched_expected_indexes.count + unmatched_actual_indexes.count + end + + def +(derived_candidate_solution) + self.class.new( + unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes, + unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes, + # Ignore the indeterminate indexes: by the time we get here, + # we've dealt with all indeterminates. + [], [] + ) + end + end + + attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution + + def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes) + @expected_to_actual_matched_indexes = expected_to_actual_matched_indexes + @actual_to_expected_matched_indexes = actual_to_expected_matched_indexes + + unmatched_expected_indexes, indeterminate_expected_indexes = + categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes) + + unmatched_actual_indexes, indeterminate_actual_indexes = + categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes) + + @solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes, + indeterminate_expected_indexes, indeterminate_actual_indexes) + end + + def find_best_solution + return solution if solution.candidate? + best_solution_so_far = NullSolution + + expected_index = solution.indeterminate_expected_indexes.first + actuals = expected_to_actual_matched_indexes[expected_index] + + actuals.each do |actual_index| + solution = best_solution_for_pairing(expected_index, actual_index) + return solution if solution.ideal? + best_solution_so_far = solution if best_solution_so_far.worse_than?(solution) + end + + best_solution_so_far + end + + private + + # @private + # Starting solution that is worse than any other real solution. + NullSolution = Class.new do + def self.worse_than?(_other) + true + end + end + + def categorize_indexes(indexes_to_categorize, other_indexes) + unmatched = [] + indeterminate = [] + + indexes_to_categorize.each_pair do |index, matches| + if matches.empty? + unmatched << index + elsif !reciprocal_single_match?(matches, index, other_indexes) + indeterminate << index + end + end + + return unmatched, indeterminate + end + + def reciprocal_single_match?(matches, index, other_list) + return false unless matches.one? + other_list[matches.first] == [index] + end + + def best_solution_for_pairing(expected_index, actual_index) + modified_expecteds = apply_pairing_to( + solution.indeterminate_expected_indexes, + expected_to_actual_matched_indexes, actual_index) + + modified_expecteds.delete(expected_index) + + modified_actuals = apply_pairing_to( + solution.indeterminate_actual_indexes, + actual_to_expected_matched_indexes, expected_index) + + modified_actuals.delete(actual_index) + + solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution + end + + def apply_pairing_to(indeterminates, original_matches, other_list_index) + indeterminates.inject({}) do |accum, index| + accum[index] = original_matches[index] - [other_list_index] + accum + end + end + end + end + # rubocop:enable Metrics/ClassLength + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/count_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/count_expectation.rb new file mode 100644 index 0000000000..29a9c82ba3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/count_expectation.rb @@ -0,0 +1,169 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Abstract class to implement `once`, `at_least` and other + # count constraints. + module CountExpectation + # @api public + # Specifies that the method is expected to match once. + def once + exactly(1) + end + + # @api public + # Specifies that the method is expected to match twice. + def twice + exactly(2) + end + + # @api public + # Specifies that the method is expected to match thrice. + def thrice + exactly(3) + end + + # @api public + # Specifies that the method is expected to match the given number of times. + def exactly(number) + set_expected_count(:==, number) + self + end + + # @api public + # Specifies the maximum number of times the method is expected to match + def at_most(number) + set_expected_count(:<=, number) + self + end + + # @api public + # Specifies the minimum number of times the method is expected to match + def at_least(number) + set_expected_count(:>=, number) + self + end + + # @api public + # No-op. Provides syntactic sugar. + def times + self + end + + protected + # @api private + attr_reader :count_expectation_type, :expected_count + + private + + if RUBY_VERSION.to_f > 1.8 + def cover?(count, number) + count.cover?(number) + end + else + def cover?(count, number) + number >= count.first && number <= count.last + end + end + + def expected_count_matches?(actual_count) + @actual_count = actual_count + return @actual_count > 0 unless count_expectation_type + return cover?(expected_count, actual_count) if count_expectation_type == :<=> + + @actual_count.__send__(count_expectation_type, expected_count) + end + + def has_expected_count? + !!count_expectation_type + end + + def set_expected_count(relativity, n) + raise_unsupported_count_expectation if unsupported_count_expectation?(relativity) + + count = count_constraint_to_number(n) + + if count_expectation_type == :<= && relativity == :>= + raise_impossible_count_expectation(count) if count > expected_count + @count_expectation_type = :<=> + @expected_count = count..expected_count + elsif count_expectation_type == :>= && relativity == :<= + raise_impossible_count_expectation(count) if count < expected_count + @count_expectation_type = :<=> + @expected_count = expected_count..count + else + @count_expectation_type = relativity + @expected_count = count + end + end + + def raise_impossible_count_expectation(count) + text = + case count_expectation_type + when :<= then "at_least(#{count}).at_most(#{expected_count})" + when :>= then "at_least(#{expected_count}).at_most(#{count})" + end + raise ArgumentError, "The constraint #{text} is not possible" + end + + def raise_unsupported_count_expectation + text = + case count_expectation_type + when :<= then "at_least" + when :>= then "at_most" + when :<=> then "at_least/at_most combination" + else "count" + end + raise ArgumentError, "Multiple #{text} constraints are not supported" + end + + def count_constraint_to_number(n) + case n + when Numeric then n + when :once then 1 + when :twice then 2 + when :thrice then 3 + else + raise ArgumentError, "Expected a number, :once, :twice or :thrice," \ + " but got #{n}" + end + end + + def unsupported_count_expectation?(relativity) + return true if count_expectation_type == :== + return true if count_expectation_type == :<=> + (count_expectation_type == :<= && relativity == :<=) || + (count_expectation_type == :>= && relativity == :>=) + end + + def count_expectation_description + "#{human_readable_expectation_type}#{human_readable_count(expected_count)}" + end + + def count_failure_reason(action) + "#{count_expectation_description}" \ + " but #{action}#{human_readable_count(@actual_count)}" + end + + def human_readable_expectation_type + case count_expectation_type + when :<= then ' at most' + when :>= then ' at least' + when :<=> then ' between' + else '' + end + end + + def human_readable_count(count) + case count + when Range then " #{count.first} and #{count.last} times" + when nil then '' + when 1 then ' once' + when 2 then ' twice' + else " #{count} times" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/cover.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/cover.rb new file mode 100644 index 0000000000..47474a2c8c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/cover.rb @@ -0,0 +1,24 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `cover`. + # Not intended to be instantiated directly. + class Cover < BaseMatcher + def initialize(*expected) + @expected = expected + end + + def matches?(range) + @actual = range + @expected.all? { |e| range.cover?(e) } + end + + def does_not_match?(range) + @actual = range + expected.none? { |e| range.cover?(e) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eq.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eq.rb new file mode 100644 index 0000000000..f0c804a342 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eq.rb @@ -0,0 +1,40 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `eq`. + # Not intended to be instantiated directly. + class Eq < BaseMatcher + # @api private + # @return [String] + def failure_message + "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" + end + + # @api private + # @return [String] + def failure_message_when_negated + "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" + end + + # @api private + # @return [String] + def description + "eq #{expected_formatted}" + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + private + + def match(expected, actual) + actual == expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eql.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eql.rb new file mode 100644 index 0000000000..b1ec6fc4be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/eql.rb @@ -0,0 +1,34 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `eql`. + # Not intended to be instantiated directly. + class Eql < BaseMatcher + # @api private + # @return [String] + def failure_message + "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" + end + + # @api private + # @return [String] + def failure_message_when_negated + "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + private + + def match(expected, actual) + actual.eql? expected + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/equal.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/equal.rb new file mode 100644 index 0000000000..bbab3ed162 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/equal.rb @@ -0,0 +1,81 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `equal`. + # Not intended to be instantiated directly. + class Equal < BaseMatcher + # @api private + # @return [String] + def failure_message + if expected_is_a_literal_singleton? + simple_failure_message + else + detailed_failure_message + end + end + + # @api private + # @return [String] + def failure_message_when_negated + <<-MESSAGE + +expected not #{inspect_object(actual)} + got #{inspect_object(expected)} + +Compared using equal?, which compares object identity. + +MESSAGE + end + + # @api private + # @return [Boolean] + def diffable? + !expected_is_a_literal_singleton? + end + + private + + def match(expected, actual) + actual.equal? expected + end + + LITERAL_SINGLETONS = [true, false, nil] + + def expected_is_a_literal_singleton? + LITERAL_SINGLETONS.include?(expected) + end + + def actual_inspected + if LITERAL_SINGLETONS.include?(actual) + actual_formatted + else + inspect_object(actual) + end + end + + def simple_failure_message + "\nexpected #{expected_formatted}\n got #{actual_inspected}\n" + end + + def detailed_failure_message + <<-MESSAGE + +expected #{inspect_object(expected)} + got #{inspect_object(actual)} + +Compared using equal?, which compares object identity, +but expected and actual are not the same object. Use +`expect(actual).to eq(expected)` if you don't care about +object identity in this example. + +MESSAGE + end + + def inspect_object(o) + "#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/exist.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/exist.rb new file mode 100644 index 0000000000..438625d7ea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/exist.rb @@ -0,0 +1,90 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `exist`. + # Not intended to be instantiated directly. + class Exist < BaseMatcher + def initialize(*expected) + @expected = expected + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + @test = ExistenceTest.new @actual, @expected + @test.valid_test? && @test.actual_exists? + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + @actual = actual + @test = ExistenceTest.new @actual, @expected + @test.valid_test? && !@test.actual_exists? + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to exist#{@test.validity_message}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to exist#{@test.validity_message}" + end + + # @api private + # Simple class for memoizing actual/expected for this matcher + # and examining the match + class ExistenceTest < Struct.new(:actual, :expected) + # @api private + # @return [Boolean] + def valid_test? + uniq_truthy_values.size == 1 + end + + # @api private + # @return [Boolean] + def actual_exists? + existence_values.first + end + + # @api private + # @return [String] + def validity_message + case uniq_truthy_values.size + when 0 + " but it does not respond to either `exist?` or `exists?`" + when 2 + " but `exist?` and `exists?` returned different values:\n\n"\ + " exist?: #{existence_values.first}\n"\ + "exists?: #{existence_values.last}" + end + end + + private + + def uniq_truthy_values + @uniq_truthy_values ||= existence_values.map { |v| !!v }.uniq + end + + def existence_values + @existence_values ||= predicates.map { |p| actual.__send__(p, *expected) } + end + + def predicates + @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) } + end + + def deprecated(predicate, actual) + predicate == :exists? && (File == actual || FileTest == actual || Dir == actual) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/has.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/has.rb new file mode 100644 index 0000000000..5036a64058 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/has.rb @@ -0,0 +1,167 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for dynamic predicate matchers. + # Not intended to be inherited directly. + class DynamicPredicate < BaseMatcher + include BeHelpers + + def initialize(method_name, *args, &block) + @method_name, @args, @block = method_name, args, block + end + ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true) + + # @private + def matches?(actual, &block) + @actual = actual + @block ||= block + predicate_accessible? && predicate_matches? + end + + # @private + def does_not_match?(actual, &block) + @actual = actual + @block ||= block + predicate_accessible? && predicate_matches?(false) + end + + # @api private + # @return [String] + def failure_message + failure_message_expecting(true) + end + + # @api private + # @return [String] + def failure_message_when_negated + failure_message_expecting(false) + end + + # @api private + # @return [String] + def description + "#{method_description}#{args_to_sentence}" + end + + private + + def predicate_accessible? + @actual.respond_to? predicate + end + + # support 1.8.7, evaluate once at load time for performance + if String === methods.first + # :nocov: + def private_predicate? + @actual.private_methods.include? predicate.to_s + end + # :nocov: + else + def private_predicate? + @actual.private_methods.include? predicate + end + end + + def predicate_result + @predicate_result = actual.__send__(predicate_method_name, *@args, &@block) + end + + def predicate_method_name + predicate + end + + def predicate_matches?(value=true) + if RSpec::Expectations.configuration.strict_predicate_matchers? + value == predicate_result + else + value == !!predicate_result + end + end + + def root + # On 1.9, there appears to be a bug where String#match can return `false` + # rather than the match data object. Changing to Regex#match appears to + # work around this bug. For an example of this bug, see: + # https://travis-ci.org/rspec/rspec-expectations/jobs/27549635 + self.class::REGEX.match(@method_name.to_s).captures.first + end + + def method_description + EnglishPhrasing.split_words(@method_name) + end + + def failure_message_expecting(value) + validity_message || + "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to #{expectation_of value}, got #{description_of @predicate_result}" + end + + def expectation_of(value) + if RSpec::Expectations.configuration.strict_predicate_matchers? + "return #{value}" + elsif value + "be truthy" + else + "be falsey" + end + end + + def validity_message + return nil if predicate_accessible? + + "expected #{actual_formatted} to respond to `#{predicate}`#{failure_to_respond_explanation}" + end + + def failure_to_respond_explanation + if private_predicate? + " but `#{predicate}` is a private method" + end + end + end + + # @api private + # Provides the implementation for `has_`. + # Not intended to be instantiated directly. + class Has < DynamicPredicate + # :nodoc: + REGEX = Matchers::HAS_REGEX + private + def predicate + @predicate ||= :"has_#{root}?" + end + end + + # @api private + # Provides the implementation of `be_`. + # Not intended to be instantiated directly. + class BePredicate < DynamicPredicate + # :nodoc: + REGEX = Matchers::BE_PREDICATE_REGEX + private + def predicate + @predicate ||= :"#{root}?" + end + + def predicate_method_name + actual.respond_to?(predicate) ? predicate : present_tense_predicate + end + + def failure_to_respond_explanation + super || if predicate == :true? + " or perhaps you meant `be true` or `be_truthy`" + elsif predicate == :false? + " or perhaps you meant `be false` or `be_falsey`" + end + end + + def predicate_accessible? + super || actual.respond_to?(present_tense_predicate) + end + + def present_tense_predicate + :"#{root}s?" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/have_attributes.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/have_attributes.rb new file mode 100644 index 0000000000..89be3f2e3d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/have_attributes.rb @@ -0,0 +1,114 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `have_attributes`. + # Not intended to be instantiated directly. + class HaveAttributes < BaseMatcher + # @private + attr_reader :respond_to_failed + + def initialize(expected) + @expected = expected + @values = {} + @respond_to_failed = false + @negated = false + end + + # @private + def actual + @values + end + + # @api private + # @return [Boolean] + def matches?(actual) + @actual = actual + @negated = false + return false unless respond_to_attributes? + perform_match(:all?) + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + @actual = actual + @negated = true + return false unless respond_to_attributes? + perform_match(:none?) + end + + # @api private + # @return [String] + def description + described_items = surface_descriptions_in(expected) + improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}" + end + + # @api private + # @return [Boolean] + def diffable? + !@respond_to_failed && !@negated + end + + # @api private + # @return [String] + def failure_message + respond_to_failure_message_or do + "expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }" + end + end + + # @api private + # @return [String] + def failure_message_when_negated + respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" } + end + + private + + def cache_all_values + @values = {} + expected.each do |attribute_key, _attribute_value| + actual_value = @actual.__send__(attribute_key) + @values[attribute_key] = actual_value + end + end + + def perform_match(predicate) + cache_all_values + expected.__send__(predicate) do |attribute_key, attribute_value| + actual_has_attribute?(attribute_key, attribute_value) + end + end + + def actual_has_attribute?(attribute_key, attribute_value) + values_match?(attribute_value, @values.fetch(attribute_key)) + end + + def respond_to_attributes? + matches = respond_to_matcher.matches?(@actual) + @respond_to_failed = !matches + matches + end + + def respond_to_matcher + @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! } + end + + def respond_to_failure_message_or + if respond_to_failed + respond_to_matcher.failure_message + else + improve_hash_formatting(yield) + end + end + + def formatted_values + values = RSpec::Support::ObjectFormatter.format(@values) + improve_hash_formatting(values) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/include.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/include.rb new file mode 100644 index 0000000000..424755af8c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/include.rb @@ -0,0 +1,206 @@ +require 'rspec/matchers/built_in/count_expectation' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `include`. + # Not intended to be instantiated directly. + class Include < BaseMatcher # rubocop:disable Metrics/ClassLength + include CountExpectation + # @private + attr_reader :expecteds + + # @api private + def initialize(*expecteds) + @expecteds = expecteds + end + + # @api private + # @return [Boolean] + def matches?(actual) + check_actual?(actual) && + if check_expected_count? + expected_count_matches?(count_inclusions) + else + perform_match { |v| v } + end + end + + # @api private + # @return [Boolean] + def does_not_match?(actual) + check_actual?(actual) && + if check_expected_count? + !expected_count_matches?(count_inclusions) + else + perform_match { |v| !v } + end + end + + # @api private + # @return [String] + def description + improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}") + end + + # @api private + # @return [String] + def failure_message + format_failure_message("to") { super } + end + + # @api private + # @return [String] + def failure_message_when_negated + format_failure_message("not to") { super } + end + + # @api private + # @return [Boolean] + def diffable? + !diff_would_wrongly_highlight_matched_item? + end + + # @api private + # @return [Array, Hash] + def expected + if expecteds.one? && Hash === expecteds.first + expecteds.first + else + expecteds + end + end + + private + + def check_actual?(actual) + actual = actual.to_hash if convert_to_hash?(actual) + @actual = actual + @actual.respond_to?(:include?) + end + + def check_expected_count? + case + when !has_expected_count? + return false + when expecteds.size != 1 + raise NotImplementedError, 'Count constraint supported only when testing for a single value being included' + when actual.is_a?(Hash) + raise NotImplementedError, 'Count constraint on hash keys not implemented' + end + true + end + + def format_failure_message(preposition) + msg = if actual.respond_to?(:include?) + "expected #{description_of @actual} #{preposition}" \ + " include#{readable_list_of @divergent_items}" \ + "#{count_failure_reason('it is included') if has_expected_count?}" + else + "#{yield}, but it does not respond to `include?`" + end + improve_hash_formatting(msg) + end + + def readable_list_of(items) + described_items = surface_descriptions_in(items) + if described_items.all? { |item| item.is_a?(Hash) } + " #{described_items.inject(:merge).inspect}" + else + EnglishPhrasing.list(described_items) + end + end + + def perform_match(&block) + @divergent_items = excluded_from_actual(&block) + @divergent_items.empty? + end + + def excluded_from_actual + return [] unless @actual.respond_to?(:include?) + + expecteds.inject([]) do |memo, expected_item| + if comparing_hash_to_a_subset?(expected_item) + expected_item.each do |(key, value)| + memo << { key => value } unless yield actual_hash_includes?(key, value) + end + elsif comparing_hash_keys?(expected_item) + memo << expected_item unless yield actual_hash_has_key?(expected_item) + else + memo << expected_item unless yield actual_collection_includes?(expected_item) + end + memo + end + end + + def comparing_hash_to_a_subset?(expected_item) + actual.is_a?(Hash) && expected_item.is_a?(Hash) + end + + def actual_hash_includes?(expected_key, expected_value) + actual_value = + actual.fetch(expected_key) do + actual.find(Proc.new { return false }) { |actual_key, _| values_match?(expected_key, actual_key) }[1] + end + values_match?(expected_value, actual_value) + end + + def comparing_hash_keys?(expected_item) + actual.is_a?(Hash) && !expected_item.is_a?(Hash) + end + + def actual_hash_has_key?(expected_key) + # We check `key?` first for perf: + # `key?` is O(1), but `any?` is O(N). + actual.key?(expected_key) || + actual.keys.any? { |key| values_match?(expected_key, key) } + end + + def actual_collection_includes?(expected_item) + return true if actual.include?(expected_item) + + # String lacks an `any?` method... + return false unless actual.respond_to?(:any?) + + actual.any? { |value| values_match?(expected_item, value) } + end + + if RUBY_VERSION < '1.9' + def count_enumerable(expected_item) + actual.select { |value| values_match?(expected_item, value) }.size + end + else + def count_enumerable(expected_item) + actual.count { |value| values_match?(expected_item, value) } + end + end + + def count_inclusions + @divergent_items = expected + case actual + when String + actual.scan(expected.first).length + when Enumerable + count_enumerable(Hash === expected ? expected : expected.first) + else + raise NotImplementedError, 'Count constraints are implemented for Enumerable and String values only' + end + end + + def diff_would_wrongly_highlight_matched_item? + return false unless actual.is_a?(String) && expected.is_a?(Array) + + lines = actual.split("\n") + expected.any? do |str| + actual.include?(str) && lines.none? { |line| line == str } + end + end + + def convert_to_hash?(obj) + !obj.respond_to?(:include?) && obj.respond_to?(:to_hash) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/match.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/match.rb new file mode 100644 index 0000000000..9ed4b068cc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/match.rb @@ -0,0 +1,106 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `match`. + # Not intended to be instantiated directly. + class Match < BaseMatcher + def initialize(expected) + super(expected) + + @expected_captures = nil + end + # @api private + # @return [String] + def description + if @expected_captures && @expected.match(actual) + "match #{surface_descriptions_in(expected).inspect} with captures #{surface_descriptions_in(@expected_captures).inspect}" + else + "match #{surface_descriptions_in(expected).inspect}" + end + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + # Used to specify the captures we match against + # @return [self] + def with_captures(*captures) + @expected_captures = captures + self + end + + private + + def match(expected, actual) + return match_captures(expected, actual) if @expected_captures + return true if values_match?(expected, actual) + return false unless can_safely_call_match?(expected, actual) + actual.match(expected) + end + + def can_safely_call_match?(expected, actual) + return false unless actual.respond_to?(:match) + + !(RSpec::Matchers.is_a_matcher?(expected) && + (String === actual || Regexp === actual)) + end + + def match_captures(expected, actual) + match = actual.match(expected) + if match + match = ReliableMatchData.new(match) + if match.names.empty? + values_match?(@expected_captures, match.captures) + else + expected_matcher = @expected_captures.last + values_match?(expected_matcher, Hash[match.names.zip(match.captures)]) || + values_match?(expected_matcher, Hash[match.names.map(&:to_sym).zip(match.captures)]) || + values_match?(@expected_captures, match.captures) + end + else + false + end + end + end + + # @api private + # Used to wrap match data and make it reliable for 1.8.7 + class ReliableMatchData + def initialize(match_data) + @match_data = match_data + end + + if RUBY_VERSION == "1.8.7" + # @api private + # Returns match data names for named captures + # @return Array + def names + [] + end + else + # @api private + # Returns match data names for named captures + # @return Array + def names + match_data.names + end + end + + # @api private + # returns an array of captures from the match data + # @return Array + def captures + match_data.captures + end + + protected + + attr_reader :match_data + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/operators.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/operators.rb new file mode 100644 index 0000000000..64f8f3b239 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/operators.rb @@ -0,0 +1,128 @@ +require 'rspec/support' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for operator matchers. + # Not intended to be instantiated directly. + # Only available for use with `should`. + class OperatorMatcher + class << self + # @private + def registry + @registry ||= {} + end + + # @private + def register(klass, operator, matcher) + registry[klass] ||= {} + registry[klass][operator] = matcher + end + + # @private + def unregister(klass, operator) + registry[klass] && registry[klass].delete(operator) + end + + # @private + def get(klass, operator) + klass.ancestors.each do |ancestor| + matcher = registry[ancestor] && registry[ancestor][operator] + return matcher if matcher + end + + nil + end + end + + register Enumerable, '=~', BuiltIn::ContainExactly + + def initialize(actual) + @actual = actual + end + + # @private + def self.use_custom_matcher_or_delegate(operator) + define_method(operator) do |expected| + if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator)) + @actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected)) + else + eval_match(@actual, operator, expected) + end + end + + negative_operator = operator.sub(/^=/, '!') + if negative_operator != operator && respond_to?(negative_operator) + define_method(negative_operator) do |_expected| + opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method + raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \ + "Use `#{opposite_should} #{operator} expected` instead." + end + end + end + + ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator| + use_custom_matcher_or_delegate operator + end + + # @private + def fail_with_message(message) + RSpec::Expectations.fail_with(message, @expected, @actual) + end + + # @api private + # @return [String] + def description + "#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}" + end + + private + + def has_non_generic_implementation_of?(op) + Support.method_handle_for(@actual, op).owner != ::Kernel + rescue NameError + false + end + + def eval_match(actual, operator, expected) + ::RSpec::Matchers.last_matcher = self + @operator, @expected = operator, expected + __delegate_operator(actual, operator, expected) + end + end + + # @private + # Handles operator matcher for `should`. + class PositiveOperatorMatcher < OperatorMatcher + def __delegate_operator(actual, operator, expected) + if actual.__send__(operator, expected) + true + else + expected_formatted = RSpec::Support::ObjectFormatter.format(expected) + actual_formatted = RSpec::Support::ObjectFormatter.format(actual) + + if ['==', '===', '=~'].include?(operator) + fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})") + else + fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") + end + end + end + end + + # @private + # Handles operator matcher for `should_not`. + class NegativeOperatorMatcher < OperatorMatcher + def __delegate_operator(actual, operator, expected) + return false unless actual.__send__(operator, expected) + + expected_formatted = RSpec::Support::ObjectFormatter.format(expected) + actual_formatted = RSpec::Support::ObjectFormatter.format(actual) + + fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/output.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/output.rb new file mode 100644 index 0000000000..8c3cceda1d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/output.rb @@ -0,0 +1,207 @@ +require 'stringio' + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `output`. + # Not intended to be instantiated directly. + class Output < BaseMatcher + def initialize(expected) + @expected = expected + @actual = "" + @block = nil + @stream_capturer = NullCapture + end + + def matches?(block) + @block = block + return false unless Proc === block + @actual = @stream_capturer.capture(block) + @expected ? values_match?(@expected, @actual) : captured? + end + + def does_not_match?(block) + !matches?(block) && Proc === block + end + + # @api public + # Tells the matcher to match against stdout. + # Works only when the main Ruby process prints to stdout + def to_stdout + @stream_capturer = CaptureStdout + self + end + + # @api public + # Tells the matcher to match against stderr. + # Works only when the main Ruby process prints to stderr + def to_stderr + @stream_capturer = CaptureStderr + self + end + + # @api public + # Tells the matcher to match against stdout. + # Works when subprocesses print to stdout as well. + # This is significantly (~30x) slower than `to_stdout` + def to_stdout_from_any_process + @stream_capturer = CaptureStreamToTempfile.new("stdout", $stdout) + self + end + + # @api public + # Tells the matcher to match against stderr. + # Works when subprocesses print to stderr as well. + # This is significantly (~30x) slower than `to_stderr` + def to_stderr_from_any_process + @stream_capturer = CaptureStreamToTempfile.new("stderr", $stderr) + self + end + + # @api private + # @return [String] + def failure_message + "expected block to #{description}, but #{positive_failure_reason}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected block to not #{description}, but #{negative_failure_reason}" + end + + # @api private + # @return [String] + def description + if @expected + "output #{description_of @expected} to #{@stream_capturer.name}" + else + "output to #{@stream_capturer.name}" + end + end + + # @api private + # @return [Boolean] + def diffable? + true + end + + # @api private + # Indicates this matcher matches against a block. + # @return [True] + def supports_block_expectations? + true + end + + # @api private + # Indicates this matcher matches against a block only. + # @return [False] + def supports_value_expectations? + false + end + + private + + def captured? + @actual.length > 0 + end + + def positive_failure_reason + return "was not a block" unless Proc === @block + return "output #{actual_output_description}" if @expected + "did not" + end + + def negative_failure_reason + return "was not a block" unless Proc === @block + "output #{actual_output_description}" + end + + def actual_output_description + return "nothing" unless captured? + actual_formatted + end + end + + # @private + module NullCapture + def self.name + "some stream" + end + + def self.capture(_block) + raise "You must chain `to_stdout` or `to_stderr` off of the `output(...)` matcher." + end + end + + # @private + module CaptureStdout + def self.name + 'stdout' + end + + def self.capture(block) + captured_stream = StringIO.new + + original_stream = $stdout + $stdout = captured_stream + + block.call + + captured_stream.string + ensure + $stdout = original_stream + end + end + + # @private + module CaptureStderr + def self.name + 'stderr' + end + + def self.capture(block) + captured_stream = StringIO.new + + original_stream = $stderr + $stderr = captured_stream + + block.call + + captured_stream.string + ensure + $stderr = original_stream + end + end + + # @private + class CaptureStreamToTempfile < Struct.new(:name, :stream) + def capture(block) + # We delay loading tempfile until it is actually needed because + # we want to minimize stdlibs loaded so that users who use a + # portion of the stdlib can't have passing specs while forgetting + # to load it themselves. `CaptureStreamToTempfile` is rarely used + # and `tempfile` pulls in a bunch of things (delegate, tmpdir, + # thread, fileutils, etc), so it's worth delaying it until this point. + require 'tempfile' + + original_stream = stream.clone + captured_stream = Tempfile.new(name) + + begin + captured_stream.sync = true + stream.reopen(captured_stream) + block.call + captured_stream.rewind + captured_stream.read + ensure + stream.reopen(original_stream) + captured_stream.close + captured_stream.unlink + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/raise_error.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/raise_error.rb new file mode 100644 index 0000000000..162fe056d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/raise_error.rb @@ -0,0 +1,271 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `raise_error`. + # Not intended to be instantiated directly. + # rubocop:disable Metrics/ClassLength + # rubocop:disable Lint/RescueException + class RaiseError + include Composable + + # Used as a sentinel value to be able to tell when the user did not pass an + # argument. We can't use `nil` for that because we need to warn when `nil` is + # passed in a different way. It's an Object, not a Module, since Module's `===` + # does not evaluate to true when compared to itself. + UndefinedValue = Object.new.freeze + + def initialize(expected_error_or_message, expected_message, &block) + @block = block + @actual_error = nil + @warn_about_bare_error = UndefinedValue === expected_error_or_message + @warn_about_nil_error = expected_error_or_message.nil? + + case expected_error_or_message + when nil, UndefinedValue + @expected_error = Exception + @expected_message = expected_message + when String + @expected_error = Exception + @expected_message = expected_error_or_message + else + @expected_error = expected_error_or_message + @expected_message = expected_message + end + end + + # @api public + # Specifies the expected error message. + def with_message(expected_message) + raise_message_already_set if @expected_message + @warn_about_bare_error = false + @expected_message = expected_message + self + end + + # rubocop:disable Metrics/MethodLength + # @private + def matches?(given_proc, negative_expectation=false, &block) + @given_proc = given_proc + @block ||= block + @raised_expected_error = false + @with_expected_message = false + @eval_block = false + @eval_block_passed = false + + return false unless Proc === given_proc + + begin + given_proc.call + rescue Exception => @actual_error + if values_match?(@expected_error, @actual_error) || + values_match?(@expected_error, actual_error_message) + @raised_expected_error = true + @with_expected_message = verify_message + end + end + + unless negative_expectation + warn_about_bare_error! if warn_about_bare_error? + warn_about_nil_error! if warn_about_nil_error? + eval_block if ready_to_eval_block? + end + + expectation_matched? + end + # rubocop:enable Metrics/MethodLength + + # @private + def does_not_match?(given_proc) + warn_for_negative_false_positives! + !matches?(given_proc, :negative_expectation) && Proc === given_proc + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + # @private + def expects_call_stack_jump? + true + end + + # @api private + # @return [String] + def failure_message + @eval_block ? actual_error_message : "expected #{expected_error}#{given_error}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected no #{expected_error}#{given_error}" + end + + # @api private + # @return [String] + def description + "raise #{expected_error}" + end + + private + + def actual_error_message + return nil unless @actual_error + + @actual_error.respond_to?(:original_message) ? @actual_error.original_message : @actual_error.message + end + + def expectation_matched? + error_and_message_match? && block_matches? + end + + def error_and_message_match? + @raised_expected_error && @with_expected_message + end + + def block_matches? + @eval_block ? @eval_block_passed : true + end + + def ready_to_eval_block? + @raised_expected_error && @with_expected_message && @block + end + + def eval_block + @eval_block = true + begin + @block[@actual_error] + @eval_block_passed = true + rescue Exception => err + @actual_error = err + end + end + + def verify_message + return true if @expected_message.nil? + values_match?(@expected_message, actual_error_message.to_s) + end + + def warn_for_negative_false_positives! + expression = if expecting_specific_exception? && @expected_message + "`expect { }.not_to raise_error(SpecificErrorClass, message)`" + elsif expecting_specific_exception? + "`expect { }.not_to raise_error(SpecificErrorClass)`" + elsif @expected_message + "`expect { }.not_to raise_error(message)`" + elsif @warn_about_nil_error + "`expect { }.not_to raise_error(nil)`" + end + + return unless expression + + warn_about_negative_false_positive! expression + end + + def handle_warning(message) + RSpec::Expectations.configuration.false_positives_handler.call(message) + end + + def warn_about_bare_error? + @warn_about_bare_error && @block.nil? + end + + def warn_about_nil_error? + @warn_about_nil_error + end + + def warn_about_bare_error! + handle_warning("Using the `raise_error` matcher without providing a specific " \ + "error or message risks false positives, since `raise_error` " \ + "will match when Ruby raises a `NoMethodError`, `NameError` or " \ + "`ArgumentError`, potentially allowing the expectation to pass " \ + "without even executing the method you are intending to call. " \ + "#{warning}"\ + "Instead consider providing a specific error class or message. " \ + "This message can be suppressed by setting: " \ + "`RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def warn_about_nil_error! + handle_warning("Using the `raise_error` matcher with a `nil` error is probably " \ + "unintentional, it risks false positives, since `raise_error` " \ + "will match when Ruby raises a `NoMethodError`, `NameError` or " \ + "`ArgumentError`, potentially allowing the expectation to pass " \ + "without even executing the method you are intending to call. " \ + "#{warning}"\ + "Instead consider providing a specific error class or message. " \ + "This message can be suppressed by setting: " \ + "`RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def warn_about_negative_false_positive!(expression) + handle_warning("Using #{expression} risks false positives, since literally " \ + "any other error would cause the expectation to pass, " \ + "including those raised by Ruby (e.g. `NoMethodError`, `NameError` " \ + "and `ArgumentError`), meaning the code you are intending to test " \ + "may not even get reached. Instead consider using " \ + "`expect { }.not_to raise_error` or `expect { }.to raise_error" \ + "(DifferentSpecificErrorClass)`. This message can be suppressed by " \ + "setting: `RSpec::Expectations.configuration.on_potential_false" \ + "_positives = :nothing`") + end + + def expected_error + case @expected_message + when nil + if RSpec::Support.is_a_matcher?(@expected_error) + "Exception with #{description_of(@expected_error)}" + else + description_of(@expected_error) + end + when Regexp + "#{@expected_error} with message matching #{description_of(@expected_message)}" + else + "#{@expected_error} with #{description_of(@expected_message)}" + end + end + + def format_backtrace(backtrace) + formatter = Matchers.configuration.backtrace_formatter + formatter.format_backtrace(backtrace) + end + + def given_error + return " but was not given a block" unless Proc === @given_proc + return " but nothing was raised" unless @actual_error + + backtrace = format_backtrace(@actual_error.backtrace) + [ + ", got #{description_of(@actual_error)} with backtrace:", + *backtrace + ].join("\n # ") + end + + def expecting_specific_exception? + @expected_error != Exception + end + + def raise_message_already_set + raise "`expect { }.to raise_error(message).with_message(message)` is not valid. " \ + 'The matcher only allows the expected message to be specified once' + end + + def warning + warning = "Actual error raised was #{description_of(@actual_error)}. " + warning if @actual_error + end + end + # rubocop:enable Lint/RescueException + # rubocop:enable Metrics/ClassLength + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/respond_to.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/respond_to.rb new file mode 100644 index 0000000000..9adbe04ea4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/respond_to.rb @@ -0,0 +1,200 @@ +RSpec::Support.require_rspec_support "method_signature_verifier" + +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `respond_to`. + # Not intended to be instantiated directly. + class RespondTo < BaseMatcher + def initialize(*names) + @names = names + @expected_arity = nil + @expected_keywords = [] + @ignoring_method_signature_failure = false + @unlimited_arguments = nil + @arbitrary_keywords = nil + end + + # @api public + # Specifies the number of expected arguments. + # + # @example + # expect(obj).to respond_to(:message).with(3).arguments + def with(n) + @expected_arity = n + self + end + + # @api public + # Specifies keyword arguments, if any. + # + # @example + # expect(obj).to respond_to(:message).with_keywords(:color, :shape) + # @example with an expected number of arguments + # expect(obj).to respond_to(:message).with(3).arguments.and_keywords(:color, :shape) + def with_keywords(*keywords) + @expected_keywords = keywords + self + end + alias :and_keywords :with_keywords + + # @api public + # Specifies that the method accepts any keyword, i.e. the method has + # a splatted keyword parameter of the form **kw_args. + # + # @example + # expect(obj).to respond_to(:message).with_any_keywords + def with_any_keywords + @arbitrary_keywords = true + self + end + alias :and_any_keywords :with_any_keywords + + # @api public + # Specifies that the number of arguments has no upper limit, i.e. the + # method has a splatted parameter of the form *args. + # + # @example + # expect(obj).to respond_to(:message).with_unlimited_arguments + def with_unlimited_arguments + @unlimited_arguments = true + self + end + alias :and_unlimited_arguments :with_unlimited_arguments + + # @api public + # No-op. Intended to be used as syntactic sugar when using `with`. + # + # @example + # expect(obj).to respond_to(:message).with(3).arguments + def argument + self + end + alias :arguments :argument + + # @private + def matches?(actual) + find_failing_method_names(actual, :reject).empty? + end + + # @private + def does_not_match?(actual) + find_failing_method_names(actual, :select).empty? + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}" + end + + # @api private + # @return [String] + def failure_message_when_negated + failure_message.sub(/to respond to/, 'not to respond to') + end + + # @api private + # @return [String] + def description + "respond to #{pp_names}#{with_arity}" + end + + # @api private + # Used by other matchers to suppress a check + def ignoring_method_signature_failure! + @ignoring_method_signature_failure = true + end + + private + + def find_failing_method_names(actual, filter_method) + @actual = actual + @failing_method_names = @names.__send__(filter_method) do |name| + @actual.respond_to?(name) && matches_arity?(actual, name) + end + end + + def matches_arity?(actual, name) + ArityCheck.new(@expected_arity, @expected_keywords, @arbitrary_keywords, @unlimited_arguments).matches?(actual, name) + rescue NameError + return true if @ignoring_method_signature_failure + raise ArgumentError, "The #{matcher_name} matcher requires that " \ + "the actual object define the method(s) in " \ + "order to check arity, but the method " \ + "`#{name}` is not defined. Remove the arity " \ + "check or define the method to continue." + end + + def with_arity + str = ''.dup + str << " with #{with_arity_string}" if @expected_arity + str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0 + str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments + str << " #{str.length == 0 ? 'with' : 'and'} any keywords" if @arbitrary_keywords + str + end + + def with_arity_string + "#{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}" + end + + def with_keywords_string + kw_str = case @expected_keywords.count + when 1 + @expected_keywords.first.inspect + when 2 + @expected_keywords.map(&:inspect).join(' and ') + else + "#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}" + end + + "keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}" + end + + def pp_names + @names.length == 1 ? "##{@names.first}" : description_of(@names) + end + + # @private + class ArityCheck + def initialize(expected_arity, expected_keywords, arbitrary_keywords, unlimited_arguments) + expectation = Support::MethodSignatureExpectation.new + + if expected_arity.is_a?(Range) + expectation.min_count = expected_arity.min + expectation.max_count = expected_arity.max + else + expectation.min_count = expected_arity + end + + expectation.keywords = expected_keywords + expectation.expect_unlimited_arguments = unlimited_arguments + expectation.expect_arbitrary_keywords = arbitrary_keywords + @expectation = expectation + end + + def matches?(actual, name) + return true if @expectation.empty? + verifier_for(actual, name).with_expectation(@expectation).valid? + end + + def verifier_for(actual, name) + Support::StrictSignatureVerifier.new(method_signature_for(actual, name)) + end + + def method_signature_for(actual, name) + method_handle = Support.method_handle_for(actual, name) + + if name == :new && method_handle.owner === ::Class && ::Class === actual + Support::MethodSignature.new(actual.instance_method(:initialize)) + else + Support::MethodSignature.new(method_handle) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/satisfy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/satisfy.rb new file mode 100644 index 0000000000..cdb34d53c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/satisfy.rb @@ -0,0 +1,60 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `satisfy`. + # Not intended to be instantiated directly. + class Satisfy < BaseMatcher + def initialize(description=nil, &block) + @description = description + @block = block + end + + # @private + def matches?(actual, &block) + @block = block if block + @actual = actual + @block.call(actual) + end + + # @private + def description + @description ||= "satisfy #{block_representation}" + end + + # @api private + # @return [String] + def failure_message + "expected #{actual_formatted} to #{description}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{actual_formatted} not to #{description}" + end + + private + + if RSpec::Support::RubyFeatures.ripper_supported? + def block_representation + if (block_snippet = extract_block_snippet) + "expression `#{block_snippet}`" + else + 'block' + end + end + + def extract_block_snippet + return nil unless @block + Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name) + end + else + def block_representation + 'block' + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/start_or_end_with.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/start_or_end_with.rb new file mode 100644 index 0000000000..81f06c2881 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/start_or_end_with.rb @@ -0,0 +1,94 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Base class for the `end_with` and `start_with` matchers. + # Not intended to be instantiated directly. + class StartOrEndWith < BaseMatcher + def initialize(*expected) + @actual_does_not_have_ordered_elements = false + @expected = expected.length == 1 ? expected.first : expected + end + + # @api private + # @return [String] + def failure_message + super.tap do |msg| + if @actual_does_not_have_ordered_elements + msg << ", but it does not have ordered elements" + elsif !actual.respond_to?(:[]) + msg << ", but it cannot be indexed using #[]" + end + end + end + + # @api private + # @return [String] + def description + return super unless Hash === expected + english_name = EnglishPhrasing.split_words(self.class.matcher_name) + description_of_expected = surface_descriptions_in(expected).inspect + "#{english_name} #{description_of_expected}" + end + + private + + def match(_expected, actual) + return false unless actual.respond_to?(:[]) + + begin + return true if subsets_comparable? && subset_matches? + element_matches? + rescue ArgumentError + @actual_does_not_have_ordered_elements = true + return false + end + end + + def subsets_comparable? + # Structs support the Enumerable interface but don't really have + # the semantics of a subset of a larger set... + return false if Struct === expected + + expected.respond_to?(:length) + end + end + + # For RSpec 3.1, the base class was named `StartAndEndWith`. For SemVer reasons, + # we still provide this constant until 4.0. + # @deprecated Use StartOrEndWith instead. + # @private + StartAndEndWith = StartOrEndWith + + # @api private + # Provides the implementation for `start_with`. + # Not intended to be instantiated directly. + class StartWith < StartOrEndWith + private + + def subset_matches? + values_match?(expected, actual[0, expected.length]) + end + + def element_matches? + values_match?(expected, actual[0]) + end + end + + # @api private + # Provides the implementation for `end_with`. + # Not intended to be instantiated directly. + class EndWith < StartOrEndWith + private + + def subset_matches? + values_match?(expected, actual[-expected.length, expected.length]) + end + + def element_matches? + values_match?(expected, actual[-1]) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/throw_symbol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/throw_symbol.rb new file mode 100644 index 0000000000..e1bb4c5576 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/throw_symbol.rb @@ -0,0 +1,138 @@ +module RSpec + module Matchers + module BuiltIn + # @api private + # Provides the implementation for `throw_symbol`. + # Not intended to be instantiated directly. + class ThrowSymbol + include Composable + + def initialize(expected_symbol=nil, expected_arg=nil) + @expected_symbol = expected_symbol + @expected_arg = expected_arg + @caught_symbol = @caught_arg = nil + end + + # rubocop:disable Metrics/MethodLength + # @private + def matches?(given_proc) + @block = given_proc + return false unless Proc === given_proc + + begin + if @expected_symbol.nil? + given_proc.call + else + @caught_arg = catch :proc_did_not_throw_anything do + catch @expected_symbol do + given_proc.call + throw :proc_did_not_throw_anything, :nothing_thrown + end + end + + if @caught_arg == :nothing_thrown + @caught_arg = nil + else + @caught_symbol = @expected_symbol + end + end + + # Ruby 1.8 uses NameError with `symbol' + # Ruby 1.9 uses ArgumentError with :symbol + rescue NameError, ArgumentError => e + unless (match_data = e.message.match(/uncaught throw (`|\:)([a-zA-Z0-9_]*)(')?/)) + other_exception = e + raise + end + @caught_symbol = match_data.captures[1].to_sym + rescue => other_exception + raise + ensure + # rubocop:disable Lint/EnsureReturn + unless other_exception + if @expected_symbol.nil? + return !!@caught_symbol + else + if @expected_arg.nil? + return @caught_symbol == @expected_symbol + else + return (@caught_symbol == @expected_symbol) && values_match?(@expected_arg, @caught_arg) + end + end + end + # rubocop:enable Lint/EnsureReturn + end + end + # rubocop:enable Metrics/MethodLength + + def does_not_match?(given_proc) + !matches?(given_proc) && Proc === given_proc + end + + # @api private + # @return [String] + def failure_message + "expected #{expected} to be thrown, #{actual_result}" + end + + # @api private + # @return [String] + def failure_message_when_negated + "expected #{expected('no Symbol')}#{' not' if @expected_symbol} to be thrown, #{actual_result}" + end + + # @api private + # @return [String] + def description + "throw #{expected}" + end + + # @api private + # Indicates this matcher matches against a block. + # @return [True] + def supports_block_expectations? + true + end + + # @api private + def supports_value_expectations? + false + end + + # @api private + def expects_call_stack_jump? + true + end + + private + + def actual_result + return "but was not a block" unless Proc === @block + "got #{caught}" + end + + def expected(symbol_desc='a Symbol') + throw_description(@expected_symbol || symbol_desc, @expected_arg) + end + + def caught + throw_description(@caught_symbol || 'nothing', @caught_arg) + end + + def throw_description(symbol, arg) + symbol_description = symbol.is_a?(String) ? symbol : description_of(symbol) + + arg_description = if arg + " with #{description_of arg}" + elsif @expected_arg && @caught_symbol == @expected_symbol + " with no argument" + else + "" + end + + symbol_description + arg_description + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/yield.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/yield.rb new file mode 100644 index 0000000000..c443dc0309 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/built_in/yield.rb @@ -0,0 +1,375 @@ +require 'rspec/matchers/built_in/count_expectation' + +RSpec::Support.require_rspec_support 'method_signature_verifier' + +module RSpec + module Matchers + module BuiltIn + # @private + # Object that is yielded to `expect` when one of the + # yield matchers is used. Provides information about + # the yield behavior of the object-under-test. + class YieldProbe + def self.probe(block, &callback) + probe = new(block, &callback) + return probe unless probe.has_block? + probe.probe + end + + attr_accessor :num_yields, :yielded_args + + def initialize(block, &callback) + @block = block + @callback = callback || Proc.new {} + @used = false + self.num_yields = 0 + self.yielded_args = [] + end + + def has_block? + Proc === @block + end + + def probe + assert_valid_expect_block! + @block.call(self) + assert_used! + self + end + + def to_proc + @used = true + + probe = self + callback = @callback + Proc.new do |*args| + probe.num_yields += 1 + probe.yielded_args << args + callback.call(*args) + nil # to indicate the block does not return a meaningful value + end + end + + def single_yield_args + yielded_args.first + end + + def yielded_once?(matcher_name) + case num_yields + when 1 then true + when 0 then false + else + raise "The #{matcher_name} matcher is not designed to be used with a " \ + 'method that yields multiple times. Use the yield_successive_args ' \ + 'matcher for that case.' + end + end + + def assert_used! + return if @used + raise 'You must pass the argument yielded to your expect block on ' \ + 'to the method-under-test as a block. It acts as a probe that ' \ + 'allows the matcher to detect whether or not the method-under-test ' \ + 'yields, and, if so, how many times, and what the yielded arguments ' \ + 'are.' + end + + if RUBY_VERSION.to_f > 1.8 + def assert_valid_expect_block! + block_signature = RSpec::Support::BlockSignature.new(@block) + return if RSpec::Support::StrictSignatureVerifier.new(block_signature, [self]).valid? + raise 'Your expect block must accept an argument to be used with this ' \ + 'matcher. Pass the argument as a block on to the method you are testing.' + end + else + # :nocov: + # On 1.8.7, `lambda { }.arity` and `lambda { |*a| }.arity` both return -1, + # so we can't distinguish between accepting no args and an arg splat. + # It's OK to skip, this, though; it just provides a nice error message + # when the user forgets to accept an arg in their block. They'll still get + # the `assert_used!` error message from above, which is sufficient. + def assert_valid_expect_block! + # nothing to do + end + # :nocov: + end + end + + # @api private + # Provides the implementation for `yield_control`. + # Not intended to be instantiated directly. + class YieldControl < BaseMatcher + include CountExpectation + # @private + def matches?(block) + @probe = YieldProbe.probe(block) + return false unless @probe.has_block? + expected_count_matches?(@probe.num_yields) + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @api private + # @return [String] + def failure_message + 'expected given block to yield control' + failure_reason + end + + # @api private + # @return [String] + def failure_message_when_negated + 'expected given block not to yield control' + failure_reason + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def failure_reason + return ' but was not a block' unless @probe.has_block? + return "#{count_expectation_description} but did not yield" if @probe.num_yields == 0 + count_failure_reason('yielded') + end + end + + # @api private + # Provides the implementation for `yield_with_no_args`. + # Not intended to be instantiated directly. + class YieldWithNoArgs < BaseMatcher + # @private + def matches?(block) + @probe = YieldProbe.probe(block) + return false unless @probe.has_block? + @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty? + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + "expected given block to yield with no arguments, but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + "expected given block not to yield with no arguments, but #{negative_failure_reason}" + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + return 'did not yield' if @probe.num_yields.zero? + "yielded with arguments: #{description_of @probe.single_yield_args}" + end + + def negative_failure_reason + return 'was not a block' unless @probe.has_block? + 'did' + end + end + + # @api private + # Provides the implementation for `yield_with_args`. + # Not intended to be instantiated directly. + class YieldWithArgs < BaseMatcher + def initialize(*args) + @expected = args + end + + # @private + def matches?(block) + @args_matched_when_yielded = true + @probe = YieldProbe.new(block) do + @actual = @probe.single_yield_args + @actual_formatted = actual_formatted + @args_matched_when_yielded &&= args_currently_match? + end + return false unless @probe.has_block? + @probe.probe + @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded + end + + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + "expected given block to yield with arguments, but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + "expected given block not to yield with arguments, but #{negative_failure_reason}" + end + + # @private + def description + desc = 'yield with args' + desc = "#{desc}(#{expected_arg_description})" unless @expected.empty? + desc + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + return 'did not yield' if @probe.num_yields.zero? + @positive_args_failure + end + + def expected_arg_description + @expected.map { |e| description_of e }.join(', ') + end + + def negative_failure_reason + if !@probe.has_block? + 'was not a block' + elsif @args_matched_when_yielded && !@expected.empty? + 'yielded with expected arguments' \ + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: #{@actual_formatted}" + else + 'did' + end + end + + def args_currently_match? + if @expected.empty? # expect {...}.to yield_with_args + @positive_args_failure = 'yielded with no arguments' if @actual.empty? + return !@actual.empty? + end + + unless (match = all_args_match?) + @positive_args_failure = 'yielded with unexpected arguments' \ + "\nexpected: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: #{@actual_formatted}" + end + + match + end + + def all_args_match? + values_match?(@expected, @actual) + end + end + + # @api private + # Provides the implementation for `yield_successive_args`. + # Not intended to be instantiated directly. + class YieldSuccessiveArgs < BaseMatcher + def initialize(*args) + @expected = args + end + + # @private + def matches?(block) + @actual_formatted = [] + @actual = [] + args_matched_when_yielded = true + yield_count = 0 + + @probe = YieldProbe.probe(block) do |*arg_array| + arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array + @actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args) + @actual << arg_or_args + args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args) + yield_count += 1 + end + + return false unless @probe.has_block? + args_matched_when_yielded && yield_count == @expected.length + end + + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private + def failure_message + 'expected given block to yield successively with arguments, ' \ + "but #{positive_failure_reason}" + end + + # @private + def failure_message_when_negated + 'expected given block not to yield successively with arguments, ' \ + "but #{negative_failure_reason}" + end + + # @private + def description + "yield successive args(#{expected_arg_description})" + end + + # @private + def supports_block_expectations? + true + end + + # @private + def supports_value_expectations? + false + end + + private + + def expected_arg_description + @expected.map { |e| description_of e }.join(', ') + end + + def positive_failure_reason + return 'was not a block' unless @probe.has_block? + + 'yielded with unexpected arguments' \ + "\nexpected: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: [#{@actual_formatted.join(", ")}]" + end + + def negative_failure_reason + return 'was not a block' unless @probe.has_block? + + 'yielded with expected arguments' \ + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \ + "\n got: [#{@actual_formatted.join(", ")}]" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/composable.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/composable.rb new file mode 100644 index 0000000000..e4816e99bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/composable.rb @@ -0,0 +1,171 @@ +RSpec::Support.require_rspec_support "fuzzy_matcher" + +module RSpec + module Matchers + # Mixin designed to support the composable matcher features + # of RSpec 3+. Mix it into your custom matcher classes to + # allow them to be used in a composable fashion. + # + # @api public + module Composable + # Creates a compound `and` expectation. The matcher will + # only pass if both sub-matchers pass. + # This can be chained together to form an arbitrarily long + # chain of matchers. + # + # @example + # expect(alphabet).to start_with("a").and end_with("z") + # expect(alphabet).to start_with("a") & end_with("z") + # + # @note The negative form (`expect(...).not_to matcher.and other`) + # is not supported at this time. + def and(matcher) + BuiltIn::Compound::And.new self, matcher + end + alias & and + + # Creates a compound `or` expectation. The matcher will + # pass if either sub-matcher passes. + # This can be chained together to form an arbitrarily long + # chain of matchers. + # + # @example + # expect(stoplight.color).to eq("red").or eq("green").or eq("yellow") + # expect(stoplight.color).to eq("red") | eq("green") | eq("yellow") + # + # @note The negative form (`expect(...).not_to matcher.or other`) + # is not supported at this time. + def or(matcher) + BuiltIn::Compound::Or.new self, matcher + end + alias | or + + # Delegates to `#matches?`. Allows matchers to be used in composable + # fashion and also supports using matchers in case statements. + def ===(value) + matches?(value) + end + + private + + # This provides a generic way to fuzzy-match an expected value against + # an actual value. It understands nested data structures (e.g. hashes + # and arrays) and is able to match against a matcher being used as + # the expected value or within the expected value at any level of + # nesting. + # + # Within a custom matcher you are encouraged to use this whenever your + # matcher needs to match two values, unless it needs more precise semantics. + # For example, the `eq` matcher _does not_ use this as it is meant to + # use `==` (and only `==`) for matching. + # + # @param expected [Object] what is expected + # @param actual [Object] the actual value + # + # @!visibility public + def values_match?(expected, actual) + expected = with_matchers_cloned(expected) + Support::FuzzyMatcher.values_match?(expected, actual) + end + + # Returns the description of the given object in a way that is + # aware of composed matchers. If the object is a matcher with + # a `description` method, returns the description; otherwise + # returns `object.inspect`. + # + # You are encouraged to use this in your custom matcher's + # `description`, `failure_message` or + # `failure_message_when_negated` implementation if you are + # supporting matcher arguments. + # + # @!visibility public + def description_of(object) + RSpec::Support::ObjectFormatter.format(object) + end + + # Transforms the given data structure (typically a hash or array) + # into a new data structure that, when `#inspect` is called on it, + # will provide descriptions of any contained matchers rather than + # the normal `#inspect` output. + # + # You are encouraged to use this in your custom matcher's + # `description`, `failure_message` or + # `failure_message_when_negated` implementation if you are + # supporting any arguments which may be a data structure + # containing matchers. + # + # @!visibility public + def surface_descriptions_in(item) + if Matchers.is_a_describable_matcher?(item) + DescribableItem.new(item) + elsif Hash === item + Hash[surface_descriptions_in(item.to_a)] + elsif Struct === item || unreadable_io?(item) + RSpec::Support::ObjectFormatter.format(item) + elsif should_enumerate?(item) + item.map { |subitem| surface_descriptions_in(subitem) } + else + item + end + end + + # @private + # Historically, a single matcher instance was only checked + # against a single value. Given that the matcher was only + # used once, it's been common to memoize some intermediate + # calculation that is derived from the `actual` value in + # order to reuse that intermediate result in the failure + # message. + # + # This can cause a problem when using such a matcher as an + # argument to another matcher in a composed matcher expression, + # since the matcher instance may be checked against multiple + # values and produce invalid results due to the memoization. + # + # To deal with this, we clone any matchers in `expected` via + # this method when using `values_match?`, so that any memoization + # does not "leak" between checks. + def with_matchers_cloned(object) + if Matchers.is_a_matcher?(object) + object.clone + elsif Hash === object + Hash[with_matchers_cloned(object.to_a)] + elsif should_enumerate?(object) + object.map { |subobject| with_matchers_cloned(subobject) } + else + object + end + end + + # @api private + # We should enumerate arrays as long as they are not recursive. + def should_enumerate?(item) + Array === item && item.none? { |subitem| subitem.equal?(item) } + end + + # @api private + def unreadable_io?(object) + return false unless IO === object + object.each {} # STDOUT is enumerable but raises an error + false + rescue IOError + true + end + module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io? + + # Wraps an item in order to surface its `description` via `inspect`. + # @api private + DescribableItem = Struct.new(:item) do + # Inspectable version of the item description + def inspect + "(#{item.description})" + end + + # A pretty printed version of the item description. + def pretty_print(pp) + pp.text "(#{item.description})" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/dsl.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/dsl.rb new file mode 100644 index 0000000000..8e8d30a6e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/dsl.rb @@ -0,0 +1,545 @@ +RSpec::Support.require_rspec_support "with_keywords_when_needed" + +module RSpec + module Matchers + # Defines the custom matcher DSL. + module DSL + # Defines a matcher alias. The returned matcher's `description` will be overridden + # to reflect the phrasing of the new name, which will be used in failure messages + # when passed as an argument to another matcher in a composed matcher expression. + # + # @example + # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to + # sum_to(3).description # => "sum to 3" + # a_list_that_sums_to(3).description # => "a list that sums to 3" + # + # @example + # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description| + # description.sub("be sorted by", "a list sorted by") + # end + # + # be_sorted_by(:age).description # => "be sorted by age" + # a_list_sorted_by(:age).description # => "a list sorted by age" + # + # @param new_name [Symbol] the new name for the matcher + # @param old_name [Symbol] the original name for the matcher + # @param options [Hash] options for the aliased matcher + # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used). + # @yield [String] optional block that, when given, is used to define the overridden + # logic. The yielded arg is the original description or failure message. If no + # block is provided, a default override is used based on the old and new names. + # @see RSpec::Matchers + def alias_matcher(new_name, old_name, options={}, &description_override) + description_override ||= lambda do |old_desc| + old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name)) + end + klass = options.fetch(:klass) { AliasedMatcher } + + define_method(new_name) do |*args, &block| + matcher = __send__(old_name, *args, &block) + matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=) + klass.new(matcher, description_override) + end + ruby2_keywords new_name if respond_to?(:ruby2_keywords, true) + end + + # Defines a negated matcher. The returned matcher's `description` and `failure_message` + # will be overridden to reflect the phrasing of the new name, and the match logic will + # be based on the original matcher but negated. + # + # @example + # RSpec::Matchers.define_negated_matcher :exclude, :include + # include(1, 2).description # => "include 1 and 2" + # exclude(1, 2).description # => "exclude 1 and 2" + # + # @param negated_name [Symbol] the name for the negated matcher + # @param base_name [Symbol] the name of the original matcher that will be negated + # @yield [String] optional block that, when given, is used to define the overridden + # logic. The yielded arg is the original description or failure message. If no + # block is provided, a default override is used based on the old and new names. + # @see RSpec::Matchers + def define_negated_matcher(negated_name, base_name, &description_override) + alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override) + end + + # Defines a custom matcher. + # + # @param name [Symbol] the name for the matcher + # @yield [Object] block that is used to define the matcher. + # The block is evaluated in the context of your custom matcher class. + # When args are passed to your matcher, they will be yielded here, + # usually representing the expected value(s). + # @see RSpec::Matchers + def define(name, &declarations) + warn_about_block_args(name, declarations) + define_method name do |*expected, &block_arg| + RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg) + end + end + alias_method :matcher, :define + + private + + if Proc.method_defined?(:parameters) + def warn_about_block_args(name, declarations) + declarations.parameters.each do |type, arg_name| + next unless type == :block + RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \ + "but due to limitations in ruby, RSpec cannot provide the block. Instead, " \ + "use the `block_arg` method to access the block") + end + end + else + # :nocov: + def warn_about_block_args(*) + # There's no way to detect block params on 1.8 since the method reflection APIs don't expose it + end + # :nocov: + end + + RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure) + + # Contains the methods that are available from within the + # `RSpec::Matchers.define` DSL for creating custom matchers. + module Macros + # Stores the block that is used to determine whether this matcher passes + # or fails. The block should return a boolean value. When the matcher is + # passed to `expect(...).to` and the block returns `true`, then the expectation + # passes. Similarly, when the matcher is passed to `expect(...).not_to` and the + # block returns `false`, then the expectation passes. + # + # @example + # + # RSpec::Matchers.define :be_even do + # match do |actual| + # actual.even? + # end + # end + # + # expect(4).to be_even # passes + # expect(3).not_to be_even # passes + # expect(3).to be_even # fails + # expect(4).not_to be_even # fails + # + # By default the match block will swallow expectation errors (e.g. + # caused by using an expectation such as `expect(1).to eq 2`), if you + # wish to allow these to bubble up, pass in the option + # `:notify_expectation_failures => true`. + # + # @param [Hash] options for defining the behavior of the match block. + # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`) + def match(options={}, &match_block) + define_user_override(:matches?, match_block) do |actual| + @actual = actual + RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do + begin + super(*actual_arg_for(match_block)) + rescue RSpec::Expectations::ExpectationNotMetError + raise if options[:notify_expectation_failures] + false + end + end + end + end + + # @private + RAISE_NOTIFIER = Proc.new { |err, _opts| raise err } + + # Use this to define the block for a negative expectation (`expect(...).not_to`) + # when the positive and negative forms require different handling. This + # is rarely necessary, but can be helpful, for example, when specifying + # asynchronous processes that require different timeouts. + # + # By default the match block will swallow expectation errors (e.g. + # caused by using an expectation such as `expect(1).to eq 2`), if you + # wish to allow these to bubble up, pass in the option + # `:notify_expectation_failures => true`. + # + # @param [Hash] options for defining the behavior of the match block. + # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`) + def match_when_negated(options={}, &match_block) + define_user_override(:does_not_match?, match_block) do |actual| + begin + @actual = actual + RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do + super(*actual_arg_for(match_block)) + end + rescue RSpec::Expectations::ExpectationNotMetError + raise if options[:notify_expectation_failures] + false + end + end + end + + # Use this instead of `match` when the block will raise an exception + # rather than returning false to indicate a failure. + # + # @example + # + # RSpec::Matchers.define :accept_as_valid do |candidate_address| + # match_unless_raises ValidationException do |validator| + # validator.validate(candidate_address) + # end + # end + # + # expect(email_validator).to accept_as_valid("person@company.com") + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def match_unless_raises(expected_exception=Exception, &match_block) + define_user_override(:matches?, match_block) do |actual| + @actual = actual + begin + super(*actual_arg_for(match_block)) + rescue expected_exception => @rescued_exception + false + else + true + end + end + end + + # Customizes the failure message to use when this matcher is + # asked to positively match. Only use this when the message + # generated by default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :have_strength do |expected| + # match { your_match_logic } + # + # failure_message do |actual| + # "Expected strength of #{expected}, but had #{actual.strength}" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def failure_message(&definition) + define_user_override(__method__, definition) + end + + # Customize the failure message to use when this matcher is asked + # to negatively match. Only use this when the message generated by + # default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :have_strength do |expected| + # match { your_match_logic } + # + # failure_message_when_negated do |actual| + # "Expected not to have strength of #{expected}, but did" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def failure_message_when_negated(&definition) + define_user_override(__method__, definition) + end + + # Customize the description to use for one-liners. Only use this when + # the description generated by default doesn't suit your needs. + # + # @example + # + # RSpec::Matchers.define :qualify_for do |expected| + # match { your_match_logic } + # + # description do + # "qualify for #{expected}" + # end + # end + # + # @yield [Object] actual the actual object (i.e. the value wrapped by `expect`) + def description(&definition) + define_user_override(__method__, definition) + end + + # Tells the matcher to diff the actual and expected values in the failure + # message. + def diffable + define_method(:diffable?) { true } + end + + # Declares that the matcher can be used in a block expectation. + # Users will not be able to use your matcher in a block + # expectation without declaring this. + # (e.g. `expect { do_something }.to matcher`). + def supports_block_expectations + define_method(:supports_block_expectations?) { true } + end + + # Convenience for defining methods on this matcher to create a fluent + # interface. The trick about fluent interfaces is that each method must + # return self in order to chain methods together. `chain` handles that + # for you. If the method is invoked and the + # `include_chain_clauses_in_custom_matcher_descriptions` config option + # hash been enabled, the chained method name and args will be added to the + # default description and failure message. + # + # In the common case where you just want the chained method to store some + # value(s) for later use (e.g. in `match`), you can provide one or more + # attribute names instead of a block; the chained method will store its + # arguments in instance variables with those names, and the values will + # be exposed via getters. + # + # @example + # + # RSpec::Matchers.define :have_errors_on do |key| + # chain :with do |message| + # @message = message + # end + # + # match do |actual| + # actual.errors[key] == @message + # end + # end + # + # expect(minor).to have_errors_on(:age).with("Not old enough to participate") + def chain(method_name, *attr_names, &definition) + unless block_given? ^ attr_names.any? + raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`." + end + + definition = assign_attributes(attr_names) if attr_names.any? + + define_user_override(method_name, definition) do |*args, &block| + super(*args, &block) + @chained_method_clauses.push([method_name, args]) + self + end + end + + def assign_attributes(attr_names) + attr_reader(*attr_names) + private(*attr_names) + + lambda do |*attr_values| + attr_names.zip(attr_values) do |attr_name, attr_value| + instance_variable_set(:"@#{attr_name}", attr_value) + end + end + end + + # assign_attributes isn't defined in the private section below because + # that makes MRI 1.9.2 emit a warning about private attributes. + private :assign_attributes + + private + + # Does the following: + # + # - Defines the named method using a user-provided block + # in @user_method_defs, which is included as an ancestor + # in the singleton class in which we eval the `define` block. + # - Defines an overridden definition for the same method + # usign the provided `our_def` block. + # - Provides a default `our_def` block for the common case + # of needing to call the user's definition with `@actual` + # as an arg, but only if their block's arity can handle it. + # + # This compiles the user block into an actual method, allowing + # them to use normal method constructs like `return` + # (e.g. for an early guard statement), while allowing us to define + # an override that can provide the wrapped handling + # (e.g. assigning `@actual`, rescueing errors, etc) and + # can `super` to the user's definition. + def define_user_override(method_name, user_def, &our_def) + @user_method_defs.__send__(:define_method, method_name, &user_def) + our_def ||= lambda { super(*actual_arg_for(user_def)) } + define_method(method_name, &our_def) + end + + # Defines deprecated macro methods from RSpec 2 for backwards compatibility. + # @deprecated Use the methods from {Macros} instead. + module Deprecated + # @deprecated Use {Macros#match} instead. + def match_for_should(&definition) + RSpec.deprecate("`match_for_should`", :replacement => "`match`") + match(&definition) + end + + # @deprecated Use {Macros#match_when_negated} instead. + def match_for_should_not(&definition) + RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`") + match_when_negated(&definition) + end + + # @deprecated Use {Macros#failure_message} instead. + def failure_message_for_should(&definition) + RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`") + failure_message(&definition) + end + + # @deprecated Use {Macros#failure_message_when_negated} instead. + def failure_message_for_should_not(&definition) + RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`") + failure_message_when_negated(&definition) + end + end + end + + # Defines default implementations of the matcher + # protocol methods for custom matchers. You can + # override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods + # from within an `RSpec::Matchers.define` block. + module DefaultImplementations + include BuiltIn::BaseMatcher::DefaultFailureMessages + + # @api private + # Used internally by objects returns by `should` and `should_not`. + def diffable? + false + end + + # The default description. + def description + english_name = EnglishPhrasing.split_words(name) + expected_list = EnglishPhrasing.list(expected) + "#{english_name}#{expected_list}#{chained_method_clause_sentences}" + end + + # Matchers do not support block expectations by default. You + # must opt-in. + def supports_block_expectations? + false + end + + def supports_value_expectations? + true + end + + # Most matchers do not expect call stack jumps. + def expects_call_stack_jump? + false + end + + private + + def chained_method_clause_sentences + return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions? + + @chained_method_clauses.map do |(method_name, method_args)| + english_name = EnglishPhrasing.split_words(method_name) + arg_list = EnglishPhrasing.list(method_args) + " #{english_name}#{arg_list}" + end.join + end + end + + # The class used for custom matchers. The block passed to + # `RSpec::Matchers.define` will be evaluated in the context + # of the singleton class of an instance, and will have the + # {RSpec::Matchers::DSL::Macros Macros} methods available. + class Matcher + # Provides default implementations for the matcher protocol methods. + include DefaultImplementations + + # Allows expectation expressions to be used in the match block. + include RSpec::Matchers + + # Supports the matcher composability features of RSpec 3+. + include Composable + + # Makes the macro methods available to an `RSpec::Matchers.define` block. + extend Macros + extend Macros::Deprecated + + # Exposes the value being matched against -- generally the object + # object wrapped by `expect`. + attr_reader :actual + + # Exposes the exception raised during the matching by `match_unless_raises`. + # Could be useful to extract details for a failure message. + attr_reader :rescued_exception + + # The block parameter used in the expectation + attr_reader :block_arg + + # The name of the matcher. + attr_reader :name + + # @api private + def initialize(name, declarations, matcher_execution_context, *expected, &block_arg) + @name = name + @actual = nil + @expected_as_array = expected + @matcher_execution_context = matcher_execution_context + @chained_method_clauses = [] + @block_arg = block_arg + + klass = class << self + # See `Macros#define_user_override` above, for an explanation. + include(@user_method_defs = Module.new) + self + end + RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *expected, &declarations) + end + + # Provides the expected value. This will return an array if + # multiple arguments were passed to the matcher; otherwise it + # will return a single value. + # @see #expected_as_array + def expected + if expected_as_array.size == 1 + expected_as_array[0] + else + expected_as_array + end + end + + # Returns the expected value as an an array. This exists primarily + # to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected` + # always returned an array. + # @see #expected + attr_reader :expected_as_array + + # Adds the name (rather than a cryptic hex number) + # so we can identify an instance of + # the matcher in error messages (e.g. for `NoMethodError`) + def inspect + "#<#{self.class.name} #{name}>" + end + + if RUBY_VERSION.to_f >= 1.9 + # Indicates that this matcher responds to messages + # from the `@matcher_execution_context` as well. + # Also, supports getting a method object for such methods. + def respond_to_missing?(method, include_private=false) + super || @matcher_execution_context.respond_to?(method, include_private) + end + else # for 1.8.7 + # :nocov: + # Indicates that this matcher responds to messages + # from the `@matcher_execution_context` as well. + def respond_to?(method, include_private=false) + super || @matcher_execution_context.respond_to?(method, include_private) + end + # :nocov: + end + + private + + def actual_arg_for(block) + block.arity.zero? ? [] : [@actual] + end + + # Takes care of forwarding unhandled messages to the + # `@matcher_execution_context` (typically the current + # running `RSpec::Core::Example`). This is needed by + # rspec-rails so that it can define matchers that wrap + # Rails' test helper methods, but it's also a useful + # feature in its own right. + def method_missing(method, *args, &block) + if @matcher_execution_context.respond_to?(method) + @matcher_execution_context.__send__ method, *args, &block + else + super(method, *args, &block) + end + end + # The method_missing method should be refactored to pass kw args in RSpec 4 + # then this can be removed + ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/english_phrasing.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/english_phrasing.rb new file mode 100644 index 0000000000..fff803cccc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/english_phrasing.rb @@ -0,0 +1,58 @@ +module RSpec + module Matchers + # Facilitates converting ruby objects to English phrases. + module EnglishPhrasing + # Converts a symbol into an English expression. + # + # split_words(:banana_creme_pie) #=> "banana creme pie" + # + def self.split_words(sym) + sym.to_s.tr('_', ' ') + end + + # @note The returned string has a leading space except + # when given an empty list. + # + # Converts an object (often a collection of objects) + # into an English list. + # + # list(['banana', 'kiwi', 'mango']) + # #=> " \"banana\", \"kiwi\", and \"mango\"" + # + # Given an empty collection, returns the empty string. + # + # list([]) #=> "" + # + def self.list(obj) + return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj || Hash === obj + items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) } + case items.length + when 0 + "" + when 1 + " #{items[0]}" + when 2 + " #{items[0]} and #{items[1]}" + else + " #{items[0...-1].join(', ')}, and #{items[-1]}" + end + end + + if RUBY_VERSION == '1.8.7' + # Not sure why, but on travis on 1.8.7 we have gotten these warnings: + # lib/rspec/matchers/english_phrasing.rb:28: warning: default `to_a' will be obsolete + # So it appears that `Array` can trigger that (e.g. by calling `to_a` on the passed object?) + # So here we replace `Kernel#Array` with our own warning-free implementation for 1.8.7. + # @private + # rubocop:disable Naming/MethodName + def self.Array(obj) + case obj + when Array then obj + else [obj] + end + end + # rubocop:enable Naming/MethodName + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/expecteds_for_multiple_diffs.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/expecteds_for_multiple_diffs.rb new file mode 100644 index 0000000000..b286bb5ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/expecteds_for_multiple_diffs.rb @@ -0,0 +1,82 @@ +module RSpec + module Matchers + # @api private + # Handles list of expected values when there is a need to render + # multiple diffs. Also can handle one value. + class ExpectedsForMultipleDiffs + # @private + # Default diff label when there is only one matcher in diff + # output + DEFAULT_DIFF_LABEL = "Diff:".freeze + + # @private + # Maximum readable matcher description length + DESCRIPTION_MAX_LENGTH = 65 + + def initialize(expected_list) + @expected_list = expected_list + end + + # @api private + # Wraps provided expected value in instance of + # ExpectedForMultipleDiffs. If provided value is already an + # ExpectedForMultipleDiffs then it just returns it. + # @param [Any] expected value to be wrapped + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def self.from(expected) + return expected if self === expected + new([[expected, DEFAULT_DIFF_LABEL]]) + end + + # @api private + # Wraps provided matcher list in instance of + # ExpectedForMultipleDiffs. + # @param [Array] matchers list of matchers to wrap + # @return [RSpec::Matchers::ExpectedsForMultipleDiffs] + def self.for_many_matchers(matchers) + new(matchers.map { |m| [m.expected, diff_label_for(m)] }) + end + + # @api private + # Returns message with diff(s) appended for provided differ + # factory and actual value if there are any + # @param [String] message original failure message + # @param [Proc] differ + # @param [Any] actual value + # @return [String] + def message_with_diff(message, differ, actual) + diff = diffs(differ, actual) + message = "#{message}\n#{diff}" unless diff.empty? + message + end + + private + + class << self + private + + def diff_label_for(matcher) + "Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):" + end + + def truncated(description) + return description if description.length <= DESCRIPTION_MAX_LENGTH + description[0...DESCRIPTION_MAX_LENGTH - 3] << "..." + end + end + + def diffs(differ, actual) + @expected_list.map do |(expected, diff_label)| + diff = differ.diff(actual, expected) + next if diff.strip.empty? + if diff == "\e[0m\n\e[0m" + "#{diff_label}\n" \ + " " + else + "#{diff_label}#{diff}" + end + end.compact.join("\n") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/fail_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/fail_matchers.rb new file mode 100644 index 0000000000..bdd7cda08c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/fail_matchers.rb @@ -0,0 +1,42 @@ +require 'rspec/expectations' + +module RSpec + module Matchers + # Matchers for testing RSpec matchers. Include them with: + # + # require 'rspec/matchers/fail_matchers' + # RSpec.configure do |config| + # config.include RSpec::Matchers::FailMatchers + # end + # + module FailMatchers + # Matches if an expectation fails + # + # @example + # expect { some_expectation }.to fail + def fail(&block) + raise_error(RSpec::Expectations::ExpectationNotMetError, &block) + end + + # Matches if an expectation fails with the provided message + # + # @example + # expect { some_expectation }.to fail_with("some failure message") + # expect { some_expectation }.to fail_with(/some failure message/) + def fail_with(message) + raise_error(RSpec::Expectations::ExpectationNotMetError, message) + end + + # Matches if an expectation fails including the provided message + # + # @example + # expect { some_expectation }.to fail_including("portion of some failure message") + def fail_including(*snippets) + raise_error( + RSpec::Expectations::ExpectationNotMetError, + a_string_including(*snippets) + ) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/generated_descriptions.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/generated_descriptions.rb new file mode 100644 index 0000000000..cbf37519ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/generated_descriptions.rb @@ -0,0 +1,41 @@ +module RSpec + module Matchers + class << self + # @private + attr_accessor :last_matcher, :last_expectation_handler + end + + # @api private + # Used by rspec-core to clear the state used to generate + # descriptions after an example. + def self.clear_generated_description + self.last_matcher = nil + self.last_expectation_handler = nil + end + + # @api private + # Generates an an example description based on the last expectation. + # Used by rspec-core's one-liner syntax. + def self.generated_description + return nil if last_expectation_handler.nil? + "#{last_expectation_handler.verb} #{last_description}" + end + + # @private + def self.last_description + last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE +When you call a matcher in an example without a String, like this: + +specify { expect(object).to matcher } + +or this: + +it { is_expected.to matcher } + +RSpec expects the matcher to have a #description method. You should either +add a String to the example this matcher is being used in, or give it a +description method. Then you won't have to suffer this lengthy warning again. +MESSAGE + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_delegator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_delegator.rb new file mode 100644 index 0000000000..e17b2ee599 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_delegator.rb @@ -0,0 +1,35 @@ +module RSpec + module Matchers + # Provides the necessary plumbing to wrap a matcher with a decorator. + # @private + class MatcherDelegator + include Composable + attr_reader :base_matcher + + def initialize(base_matcher) + @base_matcher = base_matcher + end + + def method_missing(*args, &block) + base_matcher.__send__(*args, &block) + end + + if ::RUBY_VERSION.to_f > 1.8 + def respond_to_missing?(name, include_all=false) + super || base_matcher.respond_to?(name, include_all) + end + else + # :nocov: + def respond_to?(name, include_all=false) + super || base_matcher.respond_to?(name, include_all) + end + # :nocov: + end + + def initialize_copy(other) + @base_matcher = @base_matcher.clone + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_protocol.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_protocol.rb new file mode 100644 index 0000000000..4a87f6481c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-expectations-3.12.2/lib/rspec/matchers/matcher_protocol.rb @@ -0,0 +1,105 @@ +module RSpec + module Matchers + # rspec-expectations can work with any matcher object that implements this protocol. + # + # @note This class is not loaded at runtime by rspec-expectations. It exists + # purely to provide documentation for the matcher protocol. + class MatcherProtocol + # @!group Required Methods + + # @!method matches?(actual) + # @param actual [Object] The object being matched against. + # @yield For an expression like `expect(x).to matcher do...end`, the `do/end` + # block binds to `to`. It passes that block, if there is one, on to this method. + # @return [Boolean] true if this matcher matches the provided object. + + # @!method failure_message + # This will only be called if {#matches?} returns false. + # @return [String] Explanation for the failure. + + # @!endgroup + + # @!group Optional Methods + + # @!method does_not_match?(actual) + # In a negative expectation such as `expect(x).not_to foo`, RSpec will + # call `foo.does_not_match?(x)` if this method is defined. If it's not + # defined it will fall back to using `!foo.matches?(x)`. This allows you + # to provide custom logic for the negative case. + # + # @param actual [Object] The object being matched against. + # @yield For an expression like `expect(x).not_to matcher do...end`, the `do/end` + # block binds to `not_to`. It passes that block, if there is one, on to this method. + # @return [Boolean] true if this matcher does not match the provided object. + + # @!method failure_message_when_negated + # This will only be called when a negative match fails. + # @return [String] Explanation for the failure. + # @note This method is listed as optional because matchers do not have to + # support negation. But if your matcher does support negation, this is a + # required method -- otherwise, you'll get a `NoMethodError`. + + # @!method description + # The description is used for two things: + # + # * When using RSpec's one-liner syntax + # (e.g. `it { is_expected.to matcher }`), the description + # is used to generate the example's doc string since you + # have not provided one. + # * In a composed matcher expression, the description is used + # as part of the failure message (and description) of the outer + # matcher. + # + # @return [String] Description of the matcher. + + # @!method supports_block_expectations? + # Indicates that this matcher can be used in a block expectation expression, + # such as `expect { foo }.to raise_error`. Generally speaking, this is + # only needed for matchers which operate on a side effect of a block, rather + # than on a particular object. + # @return [Boolean] true if this matcher can be used in block expressions. + # @note If not defined, RSpec assumes a value of `false` for this method. + + # @!method supports_value_expectations? + # Indicates that this matcher can be used in a value expectation expression, + # such as `expect(foo).to eq(bar)`. + # @return [Boolean] true if this matcher can be used in value expressions. + # @note If not defined, RSpec assumes a value of `true` for this method. + + # @!method expects_call_stack_jump? + # Indicates that when this matcher is used in a block expectation + # expression, it expects the block to use a ruby construct that causes + # a call stack jump (such as raising an error or throwing a symbol). + # + # This is used internally for compound block expressions, as matchers + # which expect call stack jumps must be treated with care to work properly. + # + # @return [Boolean] true if the matcher expects a call stack jump + # + # @note This method is very rarely used or needed. + # @note If not defined, RSpec assumes a value of `false` for this method. + + # @!method diffable? + # @return [Boolean] true if `actual` and `expected` can be diffed. + # Indicates that this matcher provides `actual` and `expected` attributes, + # and that the values returned by these can be usefully diffed, which can + # be included in the output. + + # @!method actual + # @return [String, Object] If an object (rather than a string) is provided, + # RSpec will use the `pp` library to convert it to multi-line output in + # order to diff. + # The actual value for the purposes of a diff. + # @note This method is required if `diffable?` returns true. + + # @!method expected + # @return [String, Object] If an object (rather than a string) is provided, + # RSpec will use the `pp` library to convert it to multi-line output in + # order to diff. + # The expected value for the purposes of a diff. + # @note This method is required if `diffable?` returns true. + + # @!endgroup + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.gitignore b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.gitignore new file mode 100644 index 0000000000..13182b2f16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.gitignore @@ -0,0 +1,24 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +gemfiles/*.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +*.bundle +*.so +*.o +*.a +mkmf.log +/dummy diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.travis.yml b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.travis.yml new file mode 100644 index 0000000000..679e5c482c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/.travis.yml @@ -0,0 +1,16 @@ +dist: trusty +language: ruby + +rvm: + - 2.1 + - 2.2 + - 2.3 + - 2.4 + +gemfile: + - gemfiles/rspec-3 + +before_install: + - gem install bundler -v "~> 1.0" + +script: bundle exec cucumber diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/CODE_OF_CONDUCT.md b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..4df92de772 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/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 waterlink000@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.0.0/gems/rspec-json_expectations-2.2.0/Gemfile b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/Gemfile new file mode 100644 index 0000000000..b4e2a20bb6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/LICENSE.txt b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/LICENSE.txt new file mode 100644 index 0000000000..d12659df90 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2014 Alexey Fedorov + +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.0.0/gems/rspec-json_expectations-2.2.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/README.md new file mode 100644 index 0000000000..d8f7c0cb4e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/README.md @@ -0,0 +1,110 @@ +# RSpec::JsonExpectations + +[![Build Status](https://travis-ci.org/waterlink/rspec-json_expectations.svg?branch=master)](https://travis-ci.org/waterlink/rspec-json_expectations) + +Set of matchers and helpers for RSpec 3 to allow you test your JSON API responses like a pro. + +## Installation + +Add this line to your application's Gemfile: + + gem 'rspec-json_expectations' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install rspec-json_expectations + +## Setup + +Simply add this line at the top of your spec helper: + +```ruby +require "rspec/json_expectations" +``` + +## Usage + +Simple example: + +```ruby +require "spec_helper" + +RSpec.describe "User API" do + subject { api_get :user } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John" + ) + end + + it "has some additional info about user" do + expect(subject).to include_json( + premium: "gold", + gamification_score: 79 + ) + end +end +``` + +And the output when I run it is: + +``` +FF + +Failures: + + 1) User API has basic info about user + Failure/Error: expect(subject).to include_json( + + json atom at path "id" is not equal to expected value: + + expected: 25 + got: 37 + + json atom at path "name" is not equal to expected value: + + expected: "John" + got: "Smith J." + + # ./spec/user_api_spec.rb:18:in `block (2 levels) in ' + + 2) User API has some additional info about user + Failure/Error: expect(subject).to include_json( + + json atom at path "premium" is not equal to expected value: + + expected: "gold" + got: "silver" + + # ./spec/user_api_spec.rb:26:in `block (2 levels) in ' + +Finished in 0.00102 seconds (files took 0.0853 seconds to load) +2 examples, 2 failures + +Failed examples: + +rspec ./spec/user_api_spec.rb:17 # User API has basic info about user +rspec ./spec/user_api_spec.rb:25 # User API has some additional info about user +``` + +For other features look into documentation: https://www.relishapp.com/waterlink/rspec-json-expectations/docs/json-expectations + +## Development + +- `bundle install` to install all dependencies. +- `bin/build` to run the test suite + +## Contributing + +1. Fork it ( https://github.com/waterlink/rspec-json_expectations/fork ) +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 a new Pull Request diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/Rakefile new file mode 100644 index 0000000000..809eb5616a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/Rakefile @@ -0,0 +1,2 @@ +require "bundler/gem_tasks" + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/build b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/build new file mode 100755 index 0000000000..a1377784fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/build @@ -0,0 +1,3 @@ +#!/bin/bash + +bin/with-rspec-3 install && bin/with-rspec-3 exec cucumber && echo "SUCCESS" || echo "FAILED" diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/with-rspec-3 b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/with-rspec-3 new file mode 100755 index 0000000000..b58b211c78 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/bin/with-rspec-3 @@ -0,0 +1,3 @@ +#!/bin/bash + +BUNDLE_GEMFILE=gemfiles/rspec-3 bundle $* diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/array_support.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/array_support.feature new file mode 100644 index 0000000000..41c3a5a2fb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/array_support.feature @@ -0,0 +1,210 @@ +Feature: Array matching support for include_json matcher + + As a developer extensively testing my APIs + I want to be able to match json parts with array + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "JSON_WITH_ARRAY" with: + """json + { + "per_page": 3, + "count": 17, + "page": 2, + "page_count": 6, + "results": [ + { + "id": 25, + "email": "john.smith@example.com", + "badges": ["first flight", "day & night"], + "name": "John" + }, + { + "id": 26, + "email": "john.smith@example.com", + "badges": ["first flight"], + "name": "John" + }, + { + "id": 27, + "email": "john.smith@example.com", + "badges": ["day & night"], + "name": "John" + } + ] + } + """ + And a local "JSON_WITH_ROOT_ARRAY" with: + """json + [ + "first flight", + "day & night" + ] + """ + + Scenario: Expecting json string to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: [ + { id: 25, badges: ["first flight", "day & night"] }, + { id: 26, badges: ["first flight"] }, + { id: 27, badges: ["day & night"] } + ] + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: [ + { id: 24, badges: [] }, + { id: 25, badges: ["first flight", "day & night"] }, + { id: 26, badges: ["first flight"] }, + { id: 27, badges: ["day & night"] } + ] + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "results/0/id" is not equal to expected value: + """ + And I see: + """ + json atom at path "results/1/id" is not equal to expected value: + """ + And I see: + """ + json atom at path "results/1/badges/1" is missing + """ + And I see: + """ + json atom at path "results/2/id" is not equal to expected value: + """ + And I see: + """ + json atom at path "results/2/badges/0" is not equal to expected value: + """ + And I see: + """ + expected: "first flight" + got: "day & night" + """ + And I see: + """ + json atom at path "results/3" is missing + """ + + Scenario: Expecting json string to partially include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: { + 2 => { id: 27, badges: ["day & night"] } + } + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to partially include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: { + 2 => { id: 28, badges: ["day & night"] } + } + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "results/2/id" is not equal to expected value: + """ + + Scenario: Expecting json string with array at root to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + [ "first flight", "day & night" ] + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string with array at root to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + [ "first flight", "day & night", "super hero" ] + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "2" is missing + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/include_json_matcher.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/include_json_matcher.feature new file mode 100644 index 0000000000..cb9dd53ab3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/include_json_matcher.feature @@ -0,0 +1,116 @@ +Feature: include_json matcher + + As a developer extensively testing my APIs with RSpec + I want to have a suitable tool to test my API responses + And I want to use simple ruby hashes to describe the parts of response + For that I need a custom matcher + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "SIMPLE_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John" + } + """ + And a local "BIG_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "password_hash": "super_md5_hash_that_is_unbreakable", + "name": "John", + "profile_id": 39, + "role": "admin" + } + """ + + Scenario: Expecting json string to include simple json + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John" + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to include simple json + Given a file "spec/simple_with_fail_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 37, + email: "john.smith@example.com", + name: "Smith" + ) + end + end + """ + When I run "rspec spec/simple_with_fail_spec.rb" + Then I see: + """ + 1 example, 1 failure + """ + And I see: + """ + expected: 37 + got: 25 + """ + And I see: + """ + expected: "Smith" + got: "John" + """ + And I see: + """ruby + # ./spec/simple_with_fail_spec.rb + """ + + Scenario: Expecting json response with excessive fields to include 'smaller' json + Given a file "spec/excessive_fields_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{BIG_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + name: "John", + profile_id: 39, + role: "admin" + ) + end + end + """ + When I run "rspec spec/excessive_fields_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/match_unordered_json_matcher.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/match_unordered_json_matcher.feature new file mode 100644 index 0000000000..ebf707697e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/match_unordered_json_matcher.feature @@ -0,0 +1,145 @@ +Feature: match_unordered_json matcher + + As a developer extensively testing my APIs with RSpec + I want to have a suitable tool to test my API responses + And I want to use simple ruby hashes to describe the parts of response + For that I need a custom matcher + + Scenario: Expecting json array to match expected array + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2, 3]' } + + it "matches when the order is equal and the size is equal" do + expect(subject).to match_unordered_json([1, 2, 3]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json array to match expected array with different order + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2, 3]' } + + it "matches when the order is different but the size is equal" do + expect(subject).to match_unordered_json([2, 3, 1]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json array to not match a subcollection + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2, 3]' } + + it "doesn't match if the size is different" do + expect(subject).to match_unordered_json([1, 2]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + json atom at path "" does not match the expected size: + """ + And I see: + """ + expected collection contained: [1, 2] + """ + And I see: + """ + actual collection contained: [1, 2, 3] + """ + And I see: + """ + the extra elements were: [3] + """ + + + Scenario: Expecting json array to not match a subcollection + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2, 3]' } + + it "doesn't match if the order is different and size is different" do + expect(subject).to match_unordered_json([3, 1]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + json atom at path "" does not match the expected size: + """ + And I see: + """ + expected collection contained: [3, 1] + """ + And I see: + """ + actual collection contained: [1, 2, 3] + """ + And I see: + """ + the extra elements were: [2] + """ + + Scenario: Expecting json array to successfully not match when the arrays do not match + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2]' } + + it "successfully does not match when the the size is unequal" do + expect(subject).to_not match_unordered_json([1, 2, 3]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json array to fail to not match when the arrays do match + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '[1, 2, 3]' } + + it "fails to not match when the arrays do match" do + expect(subject).to_not match_unordered_json([1, 2, 3]) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 1 failure + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/negation_matching.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/negation_matching.feature new file mode 100644 index 0000000000..70eaac24f5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/negation_matching.feature @@ -0,0 +1,78 @@ +Feature: negation matching for include_json matcher + + As a developer extensively testing my APIs + I want to be able to check if JSON response does not include some json part + For that I need appropriate negation matching mechanism + Where all json paths specified by include_json matcher should fail for + expectation to succeed + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "SIMPLE_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John" + } + """ + + Scenario: Expecting json string not to incldue simple json + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).not_to include_json( + id: 26, + email: "sarah@example.org", + name: "Sarah C.", + missing: "field" + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string not to incldue simple json, when it is partially included + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).not_to include_json( + id: 26, + email: /john.*@example.com/, + name: "John", + missing: "field" + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 1 failure + """ + And I see: + """ + json atom at path "email" should not match expected regex: + """ + And I see: + """ + json atom at path "name" should not equal to expected value: + """ + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/nested_json_support.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/nested_json_support.feature new file mode 100644 index 0000000000..1da53c4f6e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/nested_json_support.feature @@ -0,0 +1,63 @@ +Feature: nested json support with include_json matcher + + As a developer extensively testing my APIs + I want to be able to easily test nested JSON responses + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "NESTED_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "gamification": { + "rating": 93, + "score": 397 + }, + "name": "John" + } + """ + + Scenario: Expecting json string to include nested json + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{NESTED_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John" + ) + end + + it "has gamification info for user" do + expect(subject).to include_json( + gamification: { + rating: 93, + score: 355 + } + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 2 examples, 1 failure + """ + And I see: + """ + json atom at path "gamification/score" is not equal to expected value: + """ + And I see: + """ + expected: 355 + got: 397 + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/regular_expressions_support.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/regular_expressions_support.feature new file mode 100644 index 0000000000..5673210dfd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/regular_expressions_support.feature @@ -0,0 +1,125 @@ +Feature: regular expressions support with include_json matcher + + As a developer extensively testing my APIs + I want to be able to match string values by regex + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "SIMPLE_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John", + "code": "5wmsx6ae7p", + "url": "https://test.example.org/api/v5/users/5wmsx6ae7p.json" + } + """ + And a local "WRONG_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John", + "code": "5wmsx6ae7psome-trash", + "url": "https://test.example.org/api/v6/users/5wmsx6ae7p.json" + } + """ + And a local "COMPLEX_JSON" with: + """json + { + "balance": 25.0, + "status": true, + "avatar_url": null + } + """ + + Scenario: Expecting json string to include typed json with regex + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{COMPLEX_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + balance: /\d/, + status: /true|false/, + avatar_url: /^?/ + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string to include simple json with regex + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John", + code: /^[a-z0-9]{10}$/, + url: %%r{api/v5/users/[a-z0-9]{10}.json} + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to include simple json with regex + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{WRONG_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John", + code: /^[a-z0-9]{10}$/, + url: %%r{api/v5/users/[a-z0-9]{10}.json} + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + json atom at path "code" does not match expected regex: + """ + And I see: + """ + expected: /^[a-z0-9]{10}$/ + got: "5wmsx6ae7psome-trash" + """ + And I see: + """ + json atom at path "url" does not match expected regex: + """ + And I see: + """ + expected: /api\/v5\/users\/[a-z0-9]{10}.json/ + got: "https://test.example.org/api/v6/users/5wmsx6ae7p.json" + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/rspec_matchers_support.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/rspec_matchers_support.feature new file mode 100644 index 0000000000..ce4f0c6c20 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/rspec_matchers_support.feature @@ -0,0 +1,100 @@ +Feature: RSpec matcher support for include_json matcher + + As a developer extensively testing my APIs + I want to utilize the power of available RSpec matchers + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec" + require "rspec/expectations" + require "rspec/json_expectations" + """ + And a local "SIMPLE_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John", + "score": 55 + } + """ + + Scenario: Expecting json string to include simple json using an rspec matcher + Given a file "spec/matcher_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John", + score: (be > 30) + ) + end + end + """ + When I run "rspec spec/matcher_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string to include simple json using an rspec alias matcher + Given a file "spec/matcher_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to include_json( + id: a_kind_of(Numeric), + email: "john.smith@example.com", + name: "John", + score: (be > 30) + ) + end + end + """ + When I run "rspec spec/matcher_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string to include simple json using an rspec matcher with failure + Given a file "spec/matcher_example_fail_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + matcher = be < 30 + puts "\'matcher.inspect\'" + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John", + score: (be < 30) + ) + end + end + """ + When I run "rspec spec/matcher_example_fail_spec.rb" + Then I see: + """ + expected: "be < 30" + got: 55 + """ + And I see: + """ + 1 example, 1 failure + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/simple_hash_match.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/simple_hash_match.feature new file mode 100644 index 0000000000..bbe34902eb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/simple_hash_match.feature @@ -0,0 +1,150 @@ +Feature: include_json matcher with hash + + As a developer I want to be able to test not only JSON responses + But I want to test my hashes to be correct and see nice error output on failures + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + + And a local "SIMPLE_HASH" with: + """ruby + { + id: 25, + email: "john.smith@example.com", + name: "John" + } + """ + + And a local "BIG_HASH" with: + """ruby + { + id: 25, + email: "john.smith@example.com", + password_hash: "super_md5_hash_that_is_unbreakable", + name: "John", + profile_id: 39, + role: "admin" + } + """ + + And a local "HASH_WITH_SIMPLE_TYPES" with: + """ruby + { + phone: nil, + name: "A guy without phone", + without_phone: true, + communicative: false + } + """ + + Scenario: Expecting json string to include simple json + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { %{SIMPLE_HASH} } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + email: "john.smith@example.com", + name: "John" + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to include simple json + Given a file "spec/simple_with_fail_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { %{SIMPLE_HASH} } + + it "has basic info about user" do + expect(subject).to include_json( + id: 37, + email: "john.smith@example.com", + name: "Smith" + ) + end + end + """ + When I run "rspec spec/simple_with_fail_spec.rb" + Then I see: + """ + 1 example, 1 failure + """ + And I see: + """ + expected: 37 + got: 25 + """ + And I see: + """ + expected: "Smith" + got: "John" + """ + And I see: + """ruby + # ./spec/simple_with_fail_spec.rb + """ + + Scenario: Expecting json response with excessive fields to include 'smaller' json + Given a file "spec/excessive_fields_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { %{BIG_HASH} } + + it "has basic info about user" do + expect(subject).to include_json( + id: 25, + name: "John", + profile_id: 39, + role: "admin" + ) + end + end + """ + When I run "rspec spec/excessive_fields_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json response to contain null(s), booleans, etc + Given a file "spec/nil_values_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { %{HASH_WITH_SIMPLE_TYPES} } + + it "has no phone" do + expect(subject).to include_json( + name: "A guy without phone", + phone: nil, + communicative: false, + without_phone: true + ) + end + end + """ + When I run "rspec spec/nil_values_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/unordered_array_support.feature b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/unordered_array_support.feature new file mode 100644 index 0000000000..51df860304 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/rspec/json_expectations/unordered_array_support.feature @@ -0,0 +1,262 @@ +Feature: Unordered array matching support for include_json matcher + + As a developer extensively testing my APIs + I want to be able to match json parts with array + And I don't want to be explicit about the order of the elements + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + + RSpec.configure do |c| + c.include RSpec::JsonExpectations::Matchers + end + """ + And a local "JSON_WITH_ARRAY" with: + """json + { + "per_page": 3, + "count": 17, + "page": 2, + "page_count": 6, + "results": [ + { + "id": 25, + "email": "john.smith@example.com", + "badges": ["first flight", "day & night"], + "name": "John" + }, + { + "id": 26, + "email": "john.smith@example.com", + "badges": ["first flight"], + "name": "John" + }, + { + "id": 27, + "email": "john.smith@example.com", + "badges": ["day & night"], + "name": "John" + } + ] + } + """ + And a local "JSON_WITH_ROOT_ARRAY" with: + """json + [ + "first flight", + "day & night" + ] + """ + + Scenario: Expecting json string to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: UnorderedArray( + { id: 25, badges: ["first flight", "day & night"] }, + { id: 26, badges: ["first flight"] }, + { id: 27, badges: ["day & night"] } + ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string to fully include json with arrays with different order + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: UnorderedArray( + { id: 26, badges: ["first flight"] }, + { id: 27, badges: ["day & night"] }, + { id: 25, badges: ["first flight", "day & night"] } + ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string to fully include json with wrong values + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + results: UnorderedArray( + { id: 26, badges: ["first flight"] }, + { id: 35, badges: ["unknown", "badge"] }, + { id: 27, badges: ["day & night"] }, + { id: 25, badges: ["first flight", "day & night"] } + ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "results/1" is missing + """ + And I see: + """ + expected: {:id=>35, :badges=>["unknown", "badge"]} + got: nil + """ + + Scenario: Expecting json string with array at root to fully include json with arrays + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + UnorderedArray( "first flight", "day & night" ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string with array at root to fully include json with arrays using alternative syntax + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_unordered_json ["first flight", "day & night"] + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string with array at root to fully include json with arrays with different order + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + UnorderedArray( "day & night", "first flight" ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string with array at root to fully include json with arrays with different order using alternative syntax + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_unordered_json ["day & night", "first flight"] + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting json string with array at root to fully include json with wrong values + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_json( + UnorderedArray( "day & night", "unknown", "first flight" ) + ) + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "1" is missing + """ + And I see: + """ + expected: "unknown" + got: nil + """ + + Scenario: Expecting json string with array at root to fully include json with wrong values using alternative syntax + Given a file "spec/nested_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{JSON_WITH_ROOT_ARRAY}' } + + it "has basic info about user" do + expect(subject).to include_unordered_json ["day & night", "unknown", "first flight"] + end + end + """ + When I run "rspec spec/nested_example_spec.rb" + Then I see: + """ + json atom at path "1" is missing + """ + And I see: + """ + expected: "unknown" + got: nil + """ diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/step_definitions/steps.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/step_definitions/steps.rb new file mode 100644 index 0000000000..ecff98113a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/step_definitions/steps.rb @@ -0,0 +1,32 @@ +DUMMY_FOLDER = "dummy" + +def init + `mkdir -p #{DUMMY_FOLDER}` + `cp -r gemfiles #{DUMMY_FOLDER}` + `cp *.gemspec #{DUMMY_FOLDER}` + `cp -r lib #{DUMMY_FOLDER}` +end + +init + +Given(/^a file "(.*?)" with:$/) do |filename, contents| + @locals ||= {} + full_path = File.join(DUMMY_FOLDER, filename) + `mkdir -p #{File.dirname(full_path)}` + File.open(full_path, 'w') { |f| f.write(contents % @locals) } +end + +Given(/^a local "(.*?)" with:$/) do |key, value| + @locals ||= {} + @locals[key.to_sym] = value +end + +When(/^I run "(.*?)"$/) do |command| + @output = `cd #{DUMMY_FOLDER}; #{command}` + puts @output +end + +Then(/^I see:$/) do |what| + @output ||= "" + expect(@output).to include(what) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/support/env.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/support/env.rb new file mode 100644 index 0000000000..fbea76c7c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/features/support/env.rb @@ -0,0 +1,3 @@ +require 'rspec/expectations' + +World(RSpec::Expectations) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/gemfiles/rspec-3 b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/gemfiles/rspec-3 new file mode 100644 index 0000000000..7730d38904 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/gemfiles/rspec-3 @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in rspec-json_expectations.gemspec +gemspec path: '..' + +group :test do + gem 'cucumber' + gem 'rspec', '~> 3.0' +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations.rb new file mode 100644 index 0000000000..ebcf46fa94 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations.rb @@ -0,0 +1,13 @@ +require "rspec/core" +require "rspec/expectations" +require "rspec/json_expectations/matcher_factory" +require "rspec/json_expectations/version" +require "rspec/json_expectations/json_traverser" +require "rspec/json_expectations/failure_presenter" +require "rspec/json_expectations/matchers" + +module RSpec + module JsonExpectations + # Your code goes here... + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/failure_presenter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/failure_presenter.rb new file mode 100644 index 0000000000..b039f07f9d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/failure_presenter.rb @@ -0,0 +1,91 @@ +module RSpec + module JsonExpectations + # Allows to present failures in a nice way for each json path + class FailurePresenter + class << self + def render(errors) + negate = errors[:_negate] + errors.map { |path, error| render_error(path, error, negate) }.join + end + + private + + def render_error(path, error, negate=false) + [ + render_size_mismatch(path, error, negate), + render_no_key(path, error, negate), + render_not_eq(path, error, negate), + render_not_match(path, error, negate), + render_missing(path, error, negate) + ].select { |e| e }.first + end + + def render_size_mismatch(path, error, negate=false) + %{ + json atom at path "#{path}" does not match the expected size: + + expected collection contained: #{error[:expected].inspect} + actual collection contained: #{error[:actual].inspect} + the extra elements were: #{error[:extra_elements].inspect} + + } if error_is_size_mismatch?(error) + end + + def render_no_key(path, error, negate=false) + %{ + json atom at path "#{path}" is missing + } if error == :no_key + end + + def render_not_eq(path, error, negate=false) + %{ + json atom at path "#{path}" #{negate ? "should" : "is"} not equal to expected value: + + expected: #{error[:expected].inspect} + got: #{error[:actual].inspect} + } if error_is_not_eq?(error) + end + + def render_not_match(path, error, negate=false) + %{ + json atom at path "#{path}" #{negate ? "should" : "does"} not match expected regex: + + expected: #{error[:expected].inspect} + got: #{error[:actual].inspect} + } if error_is_not_match?(error) + end + + def render_missing(path, error, negate=false) + return unless error_is_missing?(error) + + prefix = "#{path}/" if path && !path.empty? + + items = error[:missing].map do |item| + %{ + json atom at path "#{prefix}#{item[:index]}" is missing + + expected: #{item[:item].inspect} + got: nil + } + end.join("\n") + end + + def error_is_size_mismatch?(error) + error.is_a?(Hash) && error.has_key?(:_size_mismatch_error) + end + + def error_is_not_eq?(error) + error.is_a?(Hash) && error.has_key?(:expected) && !error[:expected].is_a?(Regexp) + end + + def error_is_not_match?(error) + error.is_a?(Hash) && error.has_key?(:expected) && error[:expected].is_a?(Regexp) + end + + def error_is_missing?(error) + error.is_a?(Hash) && error.has_key?(:missing) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/json_traverser.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/json_traverser.rb new file mode 100644 index 0000000000..563c8fb724 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/json_traverser.rb @@ -0,0 +1,165 @@ +require "json" +require "rspec/json_expectations/matchers" + +module RSpec + module JsonExpectations + # This class allows to traverse a json actual value along + # with json expected value for inclusion and check if they + # match. Errors are accumulated in errors hash for each + # json atom paths. + class JsonTraverser + HANDLED_BY_SIMPLE_VALUE_HANDLER = [String, Numeric, FalseClass, TrueClass, NilClass] + RSPECMATCHERS = [RSpec::Matchers::BuiltIn::BaseMatcher, RSpec::Matchers::AliasedMatcher] + SUPPORTED_VALUES = [Hash, Regexp, Array, Matchers::UnorderedArrayMatcher] + + HANDLED_BY_SIMPLE_VALUE_HANDLER + RSPECMATCHERS + + class << self + def traverse(errors, expected, actual, negate=false, prefix=[], options={}) + [ + handle_hash(errors, expected, actual, negate, prefix), + handle_array(errors, expected, actual, negate, prefix), + handle_unordered(errors, expected, actual, negate, prefix, options), + handle_value(errors, expected, actual, negate, prefix), + handle_regex(errors, expected, actual, negate, prefix), + handle_rspec_matcher(errors, expected, actual, negate, prefix), + handle_unsupported(expected) + ].any? + end + + private + + def handle_keyvalue(errors, expected, actual, negate=false, prefix=[]) + expected.map do |key, value| + new_prefix = prefix + [key] + if has_key?(actual, key) + traverse(errors, value, fetch(actual, key), negate, new_prefix) + else + errors[new_prefix.join("/")] = :no_key unless negate + conditionally_negate(false, negate) + end + end.all? || false + end + + def handle_hash(errors, expected, actual, negate=false, prefix=[]) + return nil unless expected.is_a?(Hash) + + handle_keyvalue(errors, expected, actual, negate, prefix) + end + + def handle_array(errors, expected, actual, negate=false, prefix=[]) + return nil unless expected.is_a?(Array) + + transformed_expected = expected.each_with_index.map { |v, k| [k, v] } + handle_keyvalue(errors, transformed_expected, actual, negate, prefix) + end + + def handle_unordered(errors, expected, actual, negate=false, prefix=[], options={}) + return nil unless expected.is_a?(Matchers::UnorderedArrayMatcher) + + match_size_assertion_result = \ + match_size_of_collection(errors, expected, actual, prefix, options) + + if match_size_assertion_result == false + conditionally_negate(false, negate) + else + conditionally_negate(expected.match(errors, actual, prefix), negate) + end + end + + def match_size_of_collection(errors, expected, actual, prefix, options) + match_size = options[:match_size] + return nil unless match_size + return nil if expected.size == actual.size + + errors[prefix.join("/")] = { + _size_mismatch_error: true, + expected: expected.unwrap_array, + actual: actual, + extra_elements: actual - expected.unwrap_array, + } + false + end + + def handle_value(errors, expected, actual, negate=false, prefix=[]) + return nil unless handled_by_simple_value?(expected) + + if conditionally_negate(actual == expected, negate) + true + else + errors[prefix.join("/")] = { + actual: actual, + expected: expected + } + false + end + end + + def handled_by_simple_value?(expected) + HANDLED_BY_SIMPLE_VALUE_HANDLER.any? { |type| type === expected } + end + + def handle_regex(errors, expected, actual, negate=false, prefix=[]) + return nil unless expected.is_a?(Regexp) + + if conditionally_negate(!!expected.match(actual.to_s), negate) + true + else + errors[prefix.join("/")] = { + actual: actual, + expected: expected + } + false + end + end + + def handle_rspec_matcher(errors, expected, actual, negate=false, prefix=[]) + return nil unless defined?(RSpec::Matchers) + return nil unless expected.is_a?(RSpec::Matchers::BuiltIn::BaseMatcher) || + expected.is_a?(RSpec::Matchers::AliasedMatcher) + + if conditionally_negate(!!expected.matches?(actual), negate) + true + else + errors[prefix.join("/")] = { + actual: actual, + expected: expected.description + } + false + end + end + + def handle_unsupported(expected) + unless SUPPORTED_VALUES.any? { |type| expected.is_a?(type) } + raise NotImplementedError, + "#{expected} expectation is not supported" + end + end + + def has_key?(actual, key) + if actual.is_a?(Hash) + actual.has_key?(key) || actual.has_key?(key.to_s) + elsif actual.is_a?(Array) + actual.count > key + else + false + end + end + + def fetch(actual, key, default=nil) + if actual.is_a?(Hash) + actual.has_key?(key) ? actual[key] : actual[key.to_s] + elsif actual.is_a?(Array) + actual[key] + else + default + end + end + + def conditionally_negate(value, negate=false) + value ^ negate + end + + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matcher_factory.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matcher_factory.rb new file mode 100644 index 0000000000..bd0da9e1a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matcher_factory.rb @@ -0,0 +1,31 @@ +module RSpec + module JsonExpectations + class MatcherFactory + def initialize(matcher_name) + @matcher_name = matcher_name + end + + def define_matcher(&block) + RSpec::Matchers.define(@matcher_name) do |expected| + yield + + match do |actual| + traverse(expected, actual, false) + end + + match_when_negated do |actual| + traverse(expected, actual, true) + end + + failure_message do |actual| + RSpec::JsonExpectations::FailurePresenter.render(@include_json_errors) + end + + failure_message_when_negated do |actual| + RSpec::JsonExpectations::FailurePresenter.render(@include_json_errors) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matchers.rb new file mode 100644 index 0000000000..4adbd155b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/matchers.rb @@ -0,0 +1,120 @@ +require "forwardable" + +RSpec::JsonExpectations::MatcherFactory.new(:include_json).define_matcher do + def traverse(expected, actual, negate=false) + unless expected.is_a?(Hash) || + expected.is_a?(Array) || + expected.is_a?(::RSpec::JsonExpectations::Matchers::UnorderedArrayMatcher) + raise ArgumentError, + "Expected value must be a json for include_json matcher" + end + + representation = actual + representation = JSON.parse(actual) if String === actual + + RSpec::JsonExpectations::JsonTraverser.traverse( + @include_json_errors = { _negate: negate }, + expected, + representation, + negate + ) + end +end + +RSpec::JsonExpectations::MatcherFactory.new(:include_unordered_json).define_matcher do + def traverse(expected, actual, negate=false) + unless expected.is_a?(Array) + raise ArgumentError, + "Expected value must be a array for include_unordered_json matcher" + end + + actual_json = actual + actual_json = JSON.parse(actual) if String === actual + + expected_wrapped_in_unordered_array = \ + RSpec::JsonExpectations::Matchers::UnorderedArrayMatcher.new(expected) + + RSpec::JsonExpectations::JsonTraverser.traverse( + @include_json_errors = { _negate: negate }, + expected_wrapped_in_unordered_array, + actual_json, + negate + ) + end +end + +RSpec::JsonExpectations::MatcherFactory.new(:match_unordered_json).define_matcher do + def traverse(expected, actual, negate=false) + unless expected.is_a?(Array) + raise ArgumentError, + "Expected value must be a array for match_unordered_json matcher" + end + + actual_json = actual + actual_json = JSON.parse(actual) if String === actual + + expected_wrapped_in_unordered_array = \ + RSpec::JsonExpectations::Matchers::UnorderedArrayMatcher.new(expected) + + RSpec::JsonExpectations::JsonTraverser.traverse( + @include_json_errors = { _negate: negate }, + expected_wrapped_in_unordered_array, + actual_json, + negate, + [], + { match_size: true } + ) + end +end + +module RSpec + module JsonExpectations + module Matchers + class UnorderedArrayMatcher + extend Forwardable + + attr_reader :array + + def_delegators :array, :size + + def initialize(array) + @array = array + end + + def match(errors, actual, prefix) + missing_items = [] + errors[prefix.join("/")] = { missing: missing_items } + + all? do |expected_item, index| + match_one(missing_items, expected_item, index, actual) + end + end + + def match_one(missing, item, index, actual) + check_for_missing(missing, item, index, + actual.any? do |actual_item| + JsonTraverser.traverse({}, item, actual_item, false) + end + ) + end + + def check_for_missing(missing, item, index, ok) + missing << { item: item, index: index } unless ok + ok + end + + def all?(&blk) + array.each_with_index.all?(&blk) + end + + def unwrap_array + array + end + end + + def UnorderedArray(*array) + UnorderedArrayMatcher.new(array) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/version.rb new file mode 100644 index 0000000000..b25296e13a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/lib/rspec/json_expectations/version.rb @@ -0,0 +1,5 @@ +module RSpec + module JsonExpectations + VERSION = "2.2.0" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/local_env.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/local_env.rb new file mode 100644 index 0000000000..73241c2780 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/local_env.rb @@ -0,0 +1,3 @@ +After do |s| + Cucumber.wants_to_quit = true if s.failed? +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/rspec-json_expectations.gemspec b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/rspec-json_expectations.gemspec new file mode 100644 index 0000000000..21b98669e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-json_expectations-2.2.0/rspec-json_expectations.gemspec @@ -0,0 +1,23 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rspec/json_expectations/version' + +Gem::Specification.new do |spec| + spec.name = "rspec-json_expectations" + spec.version = RSpec::JsonExpectations::VERSION + spec.authors = ["Oleksii Fedorov"] + spec.email = ["waterlink000@gmail.com"] + spec.summary = %q{Set of matchers and helpers to allow you test your APIs responses like a pro.} + spec.description = "" + spec.homepage = "https://github.com/waterlink/rspec-json_expectations" + spec.license = "MIT" + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.6" + spec.add_development_dependency "rake" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.yardopts new file mode 100644 index 0000000000..9555b8e5c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/.yardopts @@ -0,0 +1,6 @@ +--exclude features +--no-private +--markup markdown +- +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/Changelog.md new file mode 100644 index 0000000000..44684e08e1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/Changelog.md @@ -0,0 +1,1167 @@ +### Development +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.2...main) + +### 3.10.2 / 2021-01-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.1...v3.10.2) + +Bug Fixes: + +* Support keyword arguments with `and_call_original` on Ruby 3.0. + (Bryan Powell, #1385) +* `RSpec::Mocks::Constant#previously_defined?` is now always a boolean. + (Phil Pirozhkov, #1397) +* Support keyword arguments on Ruby 3.0 when used with `expect_any_instance_of` + or `allow_any_instance_of` with `and_call_original`. + (Jess Hottenstein, #1407) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Issue `ArgumentError` rather than `TypeError` when unsupported methods on + unsupported objects are attempted to be stubbed. (@zhisme, #1357) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.9.1...v3.10.0) + +Enhancements: +* Add the ability to set a custom error generator in `MessageExpectation`. + This will allow rspec-expectations to inject a custom failure message. + (Benoit Tigeot and Nicolas Zermati, #1312) +* Return the result of the block passed to `RSpec::Mocks.with_temporary_scope` + when block run. (@expeehaa, #1329) + +### 3.9.1 / 2019-12-31 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Trigger `RSpec::Mocks.configuration.verifying_double_callbacks` when using + `allow_any_instance_of` or `expect_any_instance_of` (Daniel Orner, #1309) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.2...v3.9.0) + +Enhancements: + +* Improve thread safety of message expectations by using Mutex to prevent + deadlocking errors. (Ry Biesemeyer, #1236) +* Add the ability to use `time` as an alias for `times`. For example: + `expect(Class).to receive(:method).exactly(1).time`. + (Pistos, Benoit Tigeot, #1271) + +### 3.8.2 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.1...v3.8.2) + +* Allow `array_including` argument matchers to be nested. + (Emmanuel Delmas, #1291) + +### 3.8.1 / 2019-06-13 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Ensure stubbing methods does not change their visibility. + (Kevin Boschert, #1277) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.7.0...v3.8.0) + +Bug Fixes: + +* Issue error when encountering invalid "counted" negative message expectations. + (Sergiy Yarinovskiy, #1212) +* Ensure `allow_any_instance_of` and `expect_any_instance_of` can be temporarily + supressed. (Jon Rowe, #1228) +* Ensure `expect_any_instance_of(double).to_not have_received(:some_method)` + fails gracefully (as its not supported) rather than issuing a `NoMethodError`. + (Maxim Krizhanovsky, #1231) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #1165) + +Bug Fixes: + +* Fix `hash_including` and `hash_excluding` so that they work against + subclasses of `Hash`. (Aaron Rosenberg, #1167) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0.beta2...v3.6.0) + +Bug Fixes: + +* Fix "instance variable @color not initialized" warning when using + rspec-mocks without rspec-core. (Myron Marston, #1142) +* Restore aliased module methods properly when stubbing on 1.8.7. + (Samuel Giddins, #1144) +* Allow a message chain expectation to be constrained by argument(s). + (Jon Rowe, #1156) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0.beta1...v3.6.0.beta2) + +Enhancements: + +* Add new `without_partial_double_verification { }` API that lets you + temporarily turn off partial double verification for an example. + (Jon Rowe, #1104) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Return the test double instance form `#freeze` (Alessandro Berardi, #1109) +* Allow the special logic for stubbing `new` to work when `.method` has + been redefined. (Proby, #1119) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Provides a nice string representation of + `RSpec::Mocks::MessageExpectation` (Myron Marston, #1095) + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: + +* Add `and_throw` to any instance handling. (Tobias Bühlmann, #1068) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Issue warning when attempting to use unsupported + `allow(...).to receive(...).ordered`. (Jon Rowe, #1000) +* Add `rspec/mocks/minitest_integration`, to properly integrate rspec-mocks + with minitest. (Myron Marston, #1065) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Improve error message displayed when using `and_wrap_original` on pure test + doubles. (betesh, #1063) + +Bug Fixes: + +* Fix issue that prevented `receive_message_chain(...).with(...)` working + correctly on "any instance" mocks. (Jon Rowe, #1061) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.4.1...v3.5.0.beta1) + +Bug Fixes: + +* Allow `any_instance_of(...).to receive(...)` to use `and_yield` multiple + times. (Kilian Cirera Sant, #1054) +* Allow matchers which inherit from `rspec-mocks` matchers to be used for + `allow`. (Andrew Kozin, #1056) +* Prevent stubbing `respond_to?` on partial doubles from causing infinite + recursion. (Jon Rowe, #1013) +* Prevent aliased methods from disapearing after being mocked with + `any_instance` (regression from #1043). (Joe Rafaniello, #1060) + +### 3.4.1 / 2016-01-10 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix `any_instance` to work properly on Ruby 2.3. (Joe Rafaniello, #1043) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.2...v3.4.0) + +Enhancements: + +* Make `expect(...).to have_received` work without relying upon + rspec-expectations. (Myron Marston, #978) +* Add option for failing tests when expectations are set on `nil`. + (Liz Rush, #983) + +Bug Fixes: + +* Fix `have_received { ... }` so that any block passed when the message + was received is forwarded to the `have_received` block. (Myron Marston, #1006) +* Fix infinite loop in error generator when stubbing `respond_to?`. + (Alex Dowad, #1022) +* Fix issue with using `receive` on subclasses (at a class level) with 1.8.7. + (Alex Dowad, #1026) + +### 3.3.2 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.1...v3.3.2) + +Bug Fixes: + +* Prevent thread deadlock errors during proxy creation (e.g. when using + `before_verifying_doubles` callbacks). (Jon Rowe, #980, #979) + +### 3.3.1 / 2015-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Fix bug in `before_verifying_double` callback logic that caused it to be called + once for each class in the ancestor list when mocking or stubbing a class. Now + it is only called for the mocked or stubbed class, as you would expect. (Sam + Phippen, #974) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.2.1...v3.3.0) + +Enhancements: + +* When stubbing `new` on `MyClass` or `class_double(MyClass)`, use the + method signature from `MyClass#initialize` to verify arguments. + (Myron Marston, #886) +* Use matcher descriptions when generating description of received arguments + for mock expectation failures. (Tim Wade, #891) +* Avoid loading `stringio` unnecessarily. (Myron Marston, #894) +* Verifying doubles failure messages now distinguish between class and instance + level methods. (Tim Wade, #896, #908) +* Improve mock expectation failure messages so that it combines both + number of times and the received arguments in the output. (John Ceh, #918) +* Improve how test doubles are represented in failure messages. + (Siva Gollapalli, Myron Marston, #932) +* Rename `RSpec::Mocks::Configuration#when_declaring_verifying_double` to + `RSpec::Mocks::Configuration#before_verifying_doubles` and utilise when + verifying partial doubles. (Jon Rowe, #940) +* Use rspec-support's `ObjectFormatter` for improved formatting of + arguments in failure messages so that, for example, full time + precisions is displayed for time objects. (Gavin Miller, Myron Marston, #955) + +Bug Fixes: + +* Ensure expectations that raise eagerly also raise during RSpec verification. + This means that if exceptions are caught inside test execution the test will + still fail. (Sam Phippen, #884) +* Fix `have_received(msg).with(args).exactly(n).times` and + `receive(msg).with(args).exactly(n).times` failure messages + for when the message was received the wrong number of times with + the specified args, and also received additional times with other + arguments. Previously it confusingly listed the arguments as being + mis-matched (even when the double was allowed to receive with any + args) rather than listing the count. (John Ceh, #918) +* Fix `any_args`/`anything` support so that we avoid calling `obj == anything` + on user objects that may have improperly implemented `==` in a way that + raises errors. (Myron Marston, #924) +* Fix edge case involving stubbing the same method on a class and a subclass + which previously hit a `NoMethodError` internally in RSpec. (Myron Marston #954) +* Fix edge case where the message received count would be incremented multiple + times for one failure. (Myron Marston, #957) +* Fix failure messages for when spies received the expected message with + different arguments and also received another message. (Maurício Linhares, #960) +* Silence whitespace-only diffs. (Myron Marston, #969) + +### 3.2.1 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Add missing `rspec/support/differ` require so that rspec-mocks can be + used w/o rspec-expectations (which also loads the differ and hided the + fact we forgot to require it). (Myron Marston, #893) +* Revert tracking of received arg mutation (added in 3.2.0 to provide an + error in a situation we can't support) as our implementation has side + effects on non-standard objects and there's no solution we could come + up with that always works. (Myron Marston, #900) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.3...v3.2.0) + +Enhancements: + +* Treat `any_args` as an arg splat, allowing it to match an arbitrary + number of args at any point in an arg list. (Myron Marston, #786) +* Print diffs when arguments in mock expectations are mismatched. + (Sam Phippen, #751) +* Support names for verified doubles (`instance_double`, `instance_spy`, + `class_double`, `class_spy`, `object_double`, `object_spy`). (Cezary + Baginski, #826) +* Make `array_including` and `hash_including` argument matchers composable. + (Sam Phippen, #819) +* Make `allow_any_instance_of(...).to receive(...).and_wrap_original` + work. (Ryan Fitzgerald, #869) + +Bug Fixes: + +* Provide a clear error when users wrongly combine `no_args` with + additional arguments (e.g. `expect().to receive().with(no_args, 1)`). + (Myron Marston, #786) +* Provide a clear error when users wrongly use `any_args` multiple times in the + same argument list (e.g. `expect().to receive().with(any_args, 1, any_args)`. + (Myron Marston, #786) +* Prevent the error generator from using user object #description methods. + See [#685](https://github.com/rspec/rspec-mocks/issues/685). + (Sam Phippen, #751) +* Make verified doubles declared as `(instance|class)_double(SomeConst)` + work properly when `SomeConst` has previously been stubbed. + `(instance|class)_double("SomeClass")` already worked properly. + (Myron Marston, #824) +* Add a matcher description for `receive`, `receive_messages` and + `receive_message_chain`. (Myron Marston, #828) +* Validate invocation args for null object verified doubles. + (Myron Marston, #829) +* Fix `RSpec::Mocks::Constant.original` when called with an invalid + constant to return an object indicating the constant name is invalid, + rather than blowing up. (Myron Marston, #833) +* Make `extend RSpec::Mocks::ExampleMethods` on any object work properly + to add the rspec-mocks API to that object. Previously, `expect` would + be undefined. (Myron Marston, #846) +* Fix `require 'rspec/mocks/standalone'` so that it only affects `main` + and not every object. It's really only intended to be used in a REPL + like IRB, but some gems have loaded it, thinking it needs to be loaded + when using rspec-mocks outside the context of rspec-core. + (Myron Marston, #846) +* Prevent message expectations from being modified by customization methods + (e.g. `with`) after they have been invoked. (Sam Phippen and Melanie Gilman, #837) +* Handle cases where a method stub cannot be removed due to something + external to RSpec monkeying with the method definition. This can + happen, for example, when you `file.reopen(io)` after previously + stubbing a method on the `file` object. (Myron Marston, #853) +* Provide a clear error when received message args are mutated before + a `have_received(...).with(...)` expectation. (Myron Marston, #868) + +### 3.1.3 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.2...v3.1.3) + +Bug Fixes: + +* Correct received messages count when used with `have_received` matcher. + (Jon Rowe, #793) +* Provide a clear error message when you use `allow_any_instance_of(...)` or + `expect_any_instance_of(...)` with the `have_received` matcher (they are + not intended to be used together and previously caused an odd internal + failure in rspec-mocks). (Jon Rowe, #799). +* Fix verified double `with` verification so that it applies to method + stubs. (Myron Marston, #790) + +### 3.1.2 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Provide a clear error message when you use `allow(...)` with the + `have_received` matcher (they are not intended to be used together + and previously caused an odd internal failure in rspec-mocks). (Jon Rowe, #788). + +### 3.1.1 / 2014-09-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Prevent included modules being detected as prepended modules on Ruby 2.0 + when using `any_instance_of(...)`. (Tony Novak, #781) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Add spying methods (`spy`, `ìnstance_spy`, `class_spy` and `object_spy`) + which create doubles as null objects for use with spying in testing. (Sam + Phippen, #671) +* `have_received` matcher will raise "does not implement" errors correctly when + used with verifying doubles and partial doubles. (Xavier Shay, #722) +* Allow matchers to be used in place of keyword arguments in `with` + expectations. (Xavier Shay, #726) +* Add `thrice` modifier to message expectation interface as a synonym + for `exactly(3).times`. (Dennis Taylor, #753) +* Add more `thrice` synonyms e.g. `.at_least(:thrice)`, `.at_most(:thrice)`, + `receive(...).thrice` and `have_received(...).thrice`. (Jon Rowe, #754) +* Add `and_wrap_original` modifier for partial doubles to mutate the + response from a method. (Jon Rowe, #762) + +Bug Fixes: + +* Remove `any_number_of_times` from `any_instance` recorders that were + erroneously causing mention of the method in documentation. (Jon Rowe, #760) +* Prevent included modules being detected as prepended modules on Ruby 2.0. + (Eugene Kenny, #771) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Restore `kind_of(x)` to match using `arg.kind_of?(x)` (like RSpec 2) + rather than `x === arg`. (Jon Rowe, #750) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* `have_received` matcher will raise "does not implement" errors correctly when + used with verifying doubles and partial doubles. (Xavier Shay, #722) +* Make `double.as_null_object.dup` and `double.as_null_object.clone` + make the copies be null objects. (Myron Marston, #732) +* Don't inadvertently define `BasicObject` in 1.8.7. (Chris Griego, #739) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix edge case that triggered "can't add a new key into hash during + iteration" during mock verification. (Sam Phippen, Myron Marston, #711) +* Fix verifying doubles so that when they accidentally leak into another + example, they provide the same clear error message that normal doubles + do. (Myron Marston, #718) +* Make `ordered` work with exact receive counts. (Sam Phippen, #713) + +### 3.0.1 / 2014-06-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Fix `receive_message_chain(...)` so that it supports `with` just like + `stub_chain` did. (Jon Rowe, #697) +* Fix regression in `expect_any_instance_of` so that it expects the + message on _any_ instance rather than on _every_ instance. + (Myron Marston, #699) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.rc1...v3.0.0) + +Bug Fixes: + +* Fix module prepend detection to work properly on ruby 2.0 for a case + where a module is extended onto itself. (Myron Marston) +* Fix `transfer_nested_constants` option so that transferred constants + get properly reset at the end of the example. (Myron Marston) +* Fix `config.transfer_nested_constants = true` so that you don't + erroneously get errors when stubbing a constant that is not a module + or a class. (Myron Marston) +* Fix regression that caused `double(:class => SomeClass)` to later + trigger infinite recursion. (Myron Marston) +* Fix bug in `have_received(...).with(...).ordered` where it was not + taking the args into account when checking the order. (Myron Marston) +* Fix bug in `have_received(...).ordered` where it was wrongly + considering stubs when checking the order. (Myron Marston) +* Message expectation matchers now show descriptions from argument + matchers when their expectations aren't met. (Jon Rowe) +* Display warning when encountering `TypeError` during instance method + staging on 2.0.0-p195, suffers from https://bugs.ruby-lang.org/issues/8686 + too. (Cezar Halmagean). + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Remove `RSpec::Mocks::TestDouble.extend_onto`. (Myron Marston) +* Remove `RSpec::Mocks::ConstantStubber`. (Jon Rowe) +* Make monkey-patch of Marshal to support dumping of stubbed objects opt-in. + (Xavier Shay) + +Enhancements: + +* Instead of crashing when cleaning up stub methods on a frozen object, it now + issues a warning explaining that it's impossible to clean up the stubs. + (Justin Coyne and Sam Phippen) +* Add meaningful descriptions to `anything`, `duck_type` and `instance_of` argument + matchers. (Jon Rowe) + +Bug Fixes: + +* Fix regression introduced in 3.0.0.beta2 that caused + `double.as_null_object.to_str` to return the double rather + than a string. (Myron Marston) +* Fix bug in `expect(dbl).to receive_message_chain(:foo, :bar)` where it was + not setting an expectation for the last message in the chain. + (Jonathan del Strother) +* Allow verifying partial doubles to have private methods stubbed. (Xavier Shay) +* Fix bug with allowing/expecting messages on Class objects which have had + their singleton class prepended to. (Jon Rowe) +* Fix an issue with 1.8.7 not running implementation blocks on partial doubles. + (Maurício Linhares) +* Prevent `StackLevelTooDeep` errors when stubbing an `any_instance` method that's + accessed in `inspect` by providing our own inspect output. (Jon Rowe) +* Fix bug in `any_instance` logic that did not allow you to mock or stub + private methods if `verify_partial_doubles` was configured. (Oren Dobzinski) +* Include useful error message when trying to observe an unimplemented method + on an any instance. (Xavier Shay) +* Fix `and_call_original` to work properly when multiple classes in an + inheritance hierarchy have been stubbed with the same method. (Myron Marston) +* Fix `any_instance` so that it updates existing instances that have + already been stubbed. (Myron Marston) +* Fix verified doubles so that their class name is included in failure + messages. (Myron Marston) +* Fix `expect_any_instance_of` so that when the message is received + on an individual instance that has been directly stubbed, it still + satisfies the expectation. (Sam Phippen, Myron Marston) +* Explicitly disallow using `any_instance` to mock or stub a method + that is defined on a module prepended onto the class. This triggered + `SystemStackError` before and is very hard to support so we are not + supporting it at this time. (Myron Marston) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Rename `RSpec::Mocks::Mock` to `RSpec::Mocks::Double`. (Myron Marston) +* Change how to integrate rspec-mocks in other test frameworks. You now + need to include `RSpec::Mocks::ExampleMethods` in your test context. + (Myron Marston) +* Prevent RSpec mocks' doubles and partial doubles from being used outside of + the per-test lifecycle (e.g. from a `before(:all)` hook). (Sam Phippen) +* Remove the `host` argument of `RSpec::Mocks.setup`. Instead + `RSpec::Mocks::ExampleMethods` should be included directly in the scope where + RSpec's mocking capabilities are used. (Sam Phippen) +* Make test doubles raise errors if you attempt to use them after they + get reset, to help surface issues when you accidentally retain + references to test doubles and attempt to reuse them in another + example. (Myron Marston) +* Remove support for `and_return { value }` and `and_return` without arguments. (Yuji Nakayama) + +Enhancements: + +* Add `receive_message_chain` which provides the functionality of the old + `stub_chain` for the new allow/expect syntax. Use it like so: `allow(...).to + receive_message_chain(:foo, :bar, :bazz)`. (Sam Phippen). +* Change argument matchers to use `===` as their primary matching + protocol, since their semantics mirror that of a case or rescue statement + (which uses `===` for matching). (Myron Marston) +* Add `RSpec::Mocks.with_temporary_scope`, which allows you to create + temporary rspec-mocks scopes in arbitrary places (such as a + `before(:all)` hook). (Myron Marston) +* Support keyword arguments when checking arity with verifying doubles. + (Xavier Shay) + +Bug Fixes: + +* Fix regression in 3.0.0.beta1 that caused `double("string_name" => :value)` + to stop working. (Xavier Shay) +* Fix the way rspec-mocks and rspec-core interact so that if users + define a `let` with the same name as one of the methods + from `RSpec::Mocks::ArgumentMatchers`, the user's `let` takes + precedence. (Michi Huber, Myron Marston) +* Fix verified doubles so that their methods match the visibility + (public, protected or private) of the interface they verify + against. (Myron Marston) +* Fix verified null object doubles so that they do not wrongly + report that they respond to anything. They only respond to methods + available on the interface they verify against. (Myron Marston) +* Fix deprecation warning for use of old `:should` syntax w/o explicit + config so that it no longer is silenced by an extension gem such + as rspec-rails when it calls `config.add_stub_and_should_receive_to`. + (Sam Phippen) +* Fix `expect` syntax so that it does not wrongly emit a "You're + overriding a previous implementation for this stub" warning when + you are not actually doing that. (Myron Marston) +* Fix `any_instance.unstub` when used on sub classes for whom the super + class has had `any_instance.stub` invoked on. (Jon Rowe) +* Fix regression in `stub_chain`/`receive_message_chain` that caused + it to raise an `ArgumentError` when passing args to the stubbed + methods. (Sam Phippen) +* Correct stub of undefined parent modules all the way down when stubbing a + nested constant. (Xavier Shay) +* Raise `VerifyingDoubleNotDefinedError` when a constant is not defined for + a verifying class double. (Maurício Linhares) +* Remove `Double#to_str`, which caused confusing `raise some_double` + behavior. (Maurício Linhares) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.4...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Raise an explicit error if `should_not_receive(...).and_return` is used. (Sam + Phippen) +* Remove 1.8.6 workarounds. (Jon Rowe) +* Remove `stub!` and `unstub!`. (Sam Phippen) +* Remove `mock(name, methods)` and `stub(name, methods)`, leaving + `double(name, methods)` for creating test doubles. (Sam Phippen, Michi Huber) +* Remove `any_number_of_times` since `should_receive(:msg).any_number_of_times` + is really a stub in a mock's clothing. (Sam Phippen) +* Remove support for re-using the same null-object test double in multiple + examples. Test doubles are designed to only live for one example. + (Myron Marston) +* Make `at_least(0)` raise an error. (Sam Phippen) +* Remove support for `require 'spec/mocks'` which had been kept + in place for backwards compatibility with RSpec 1. (Myron Marston) +* Blocks provided to `with` are always used as implementation. (Xavier Shay) +* The config option (added in 2.99) to yield the receiver to + `any_instance` implementation blocks now defaults to "on". (Sam Phippen) + +Enhancements: + +* Allow the `have_received` matcher to use a block to set further expectations + on arguments. (Tim Cowlishaw) +* Provide `instance_double` and `class_double` to create verifying doubles, + ported from `rspec-fire`. (Xavier Shay) +* `as_null_object` on a verifying double only responds to defined methods. + (Xavier Shay) +* Provide `object_double` to create verified doubles of specific object + instances. (Xavier Shay) +* Provide `verify_partial_doubles` configuration that provides `object_double` + like verification behaviour on partial doubles. (Xavier Shay) +* Improved performance of double creation, particularly those with many + attributes. (Xavier Shay) +* Default value of `transfer_nested_constants` option for constant stubbing can + be configured. (Xavier Shay) +* Messages can be allowed or expected on in bulk via + `receive_messages(:message => :value)`. (Jon Rowe) +* `allow(Klass.any_instance)` and `expect(Klass.any_instance)` now print a + warning. This is usually a mistake, and users usually want + `allow_any_instance_of` or `expect_any_instance_of` instead. (Sam Phippen) +* `instance_double` and `class_double` raise `ArgumentError` if the underlying + module is loaded and the arity of the method being invoked does not match the + arity of the method as it is actually implemented. (Andy Lindeman) +* Spies can now check their invocation ordering is correct. (Jon Rowe) + +Deprecations: + +* Using the old `:should` syntax without explicitly configuring it + is deprecated. It will continue to work but will emit a deprecation + warning in RSpec 3 if you do not explicitly enable it. (Sam Phippen) + +Bug Fixes: + +* Fix `and_call_original` to handle a complex edge case involving + singleton class ancestors. (Marc-André Lafortune, Myron Marston) +* When generating an error message for unexpected arguments, + use `#inspect` rather than `#description` if `#description` + returns `nil` or `''` so that you still get a useful message. + (Nick DeLuca) + +### 2.99.4 / 2015-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.3...v2.99.4) + +Bug Fixes: + +* Add missing deprecation for using `with` with no arguments e.g. `with()`. (Yousuke, #970) + +### 2.99.3 / 2015-01-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.2...v2.99.3) + +Bug Fixes: + +* Fix regression that caused an error when a test double was deserialized from YAML. (Yuji Nakayama, #777) + +### 2.99.2 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.1...v2.99.2) + +Enhancements: + +* Warn about upcoming change to `#===` matching and `DateTime#===` behaviour. + (Jon Rowe, #735) + +### 2.99.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Fix bug that caused errors at the end of each example + when a `double.as_null_object` had been frozen. (Yuji Nakayama, #698) + +Deprecations: + +* Deprecate freezing a test double. (Yuji Nakayama, #698) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.rc1...v2.99.0) + +No changes. Just taking it out of pre-release. + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.beta2...v2.99.0.rc1) + +Deprecations: + +* Deprecate `RSpec::Mocks::TestDouble.extend_onto`. (Myron Marston) +* Deprecate `RSpec::Mocks::ConstantStubber`. (Jon Rowe) +* Deprecate `Marshal.dump` monkey-patch without opt-in. (Xavier Shay) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.beta1...v2.99.0.beta2) + +Deprecations: + +* Deprecate `RSpec::Mocks::Mock` in favor of `RSpec::Mocks::Double`. + (Myron Marston) +* Deprecate the `host` argument of `RSpec::Mocks.setup`. Instead + `RSpec::Mocks::ExampleMethods` should be included directly in the scope where + RSpec's mocking capabilities are used. (Sam Phippen) +* Deprecate using any of rspec-mocks' features outside the per-test + lifecycle (e.g. from a `before(:all)` hook). (Myron Marston) +* Deprecate re-using a test double in another example. (Myron Marston) +* Deprecate `and_return { value }` and `and_return` without arguments. (Yuji Nakayama) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.4...v2.99.0.beta1) + +Deprecations + +* Expecting to use lambdas or other strong arity implementations for stub + methods with mis-matched arity is deprecated and support for them will be + removed in 3.0. Either provide the right amount of arguments or use a weak + arity implementation (methods with splats or procs). (Jon Rowe) +* Using the same test double instance in multiple examples is deprecated. Test + doubles are only meant to live for one example. The mocks and stubs have + always been reset between examples; however, in 2.x the `as_null_object` + state was not reset and some users relied on this to have a null object + double that is used for many examples. This behavior will be removed in 3.0. + (Myron Marston) +* Print a detailed warning when an `any_instance` implementation block is used + when the new `yield_receiver_to_any_instance_implementation_blocks` config + option is not explicitly set, as RSpec 3.0 will default to enabling this new + feature. (Sam Phippen) + +Enhancements: + +* Add a config option to yield the receiver to `any_instance` implementation + blocks. (Sam Phippen) + +### 2.14.6 / 2014-02-20 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.5...v2.14.6) + +Bug Fixes: + +* Ensure `any_instance` method stubs and expectations are torn down regardless of + expectation failures. (Sam Phippen) + +### 2.14.5 / 2014-02-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.4...v2.14.5) + +Bug Fixes: + +* Fix regression that caused block implementations to not receive all + args on 1.8.7 if the block also receives a block, due to Proc#arity + reporting `1` no matter how many args the block receives if it + receives a block, too. (Myron Marston) + +### 2.14.4 / 2013-10-15 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.3...v2.14.4) + +Bug Fixes: + +* Fix issue where unstubing methods on "any instances" would not + remove stubs on existing instances (Jon Rowe) +* Fix issue with receive(:message) do ... end precedence preventing + the usage of modifications (`and_return` etc) (Jon Rowe) + +### 2.14.3 / 2013-08-08 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.2...v2.14.3) + +Bug Fixes: + +* Fix stubbing some instance methods for classes whose hierarchy includes + a prepended Module (Bradley Schaefer) + +### 2.14.2 / 2013-07-30 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.1...v2.14.2) + +Bug Fixes: + +* Fix `as_null_object` doubles so that they return `nil` from `to_ary` + (Jon Rowe). +* Fix regression in 2.14 that made `stub!` (with an implicit receiver) + return a test double rather than stub a method (Myron Marston). + +### 2.14.1 / 2013-07-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.0...v2.14.1) + +Bug Fixes: + +* Restore `double.as_null_object` behavior from 2.13 and earlier: a + double's nullness persisted between examples in earlier examples. + While this is not an intended use case (test doubles are meant to live + for only one example), we don't want to break behavior users rely + on in a minor relase. This will be deprecated in 2.99 and removed + in 3.0. (Myron Marston) + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.0.rc1...v2.14.0) + +Enhancements: + +* Document test spies in the readme. (Adarsh Pandit) +* Add an `array_including` matcher. (Sam Phippen) +* Add a syntax-agnostic API for mocking or stubbing a method. This is + intended for use by libraries such as rspec-rails that need to mock + or stub a method, and work regardless of the syntax the user has + configured (Paul Annesley, Myron Marston and Sam Phippen). + +Bug Fixes: + +* Fix `double` so that it sets up passed stubs correctly regardless of + the configured syntax (Paul Annesley). +* Allow a block implementation to be used in combination with + `and_yield`, `and_raise`, `and_return` or `and_throw`. This got fixed + in 2.13.1 but failed to get merged into master for the 2.14.0.rc1 + release (Myron Marston). +* `Marshal.dump` does not unnecessarily duplicate objects when rspec-mocks has + not been fully initialized. This could cause errors when using `spork` or + similar preloading gems (Andy Lindeman). + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.14.0.rc1) + +Enhancements: + +* Refactor internals so that the mock proxy methods and state are held + outside of the mocked object rather than inside it. This paves the way + for future syntax enhancements and removes the need for some hacky + work arounds for `any_instance` dup'ing and `YAML` serialization, + among other things. Note that the code now relies upon `__id__` + returning a unique, consistent value for any object you want to + mock or stub (Myron Marston). +* Add support for test spies. This allows you to verify a message + was received afterwards using the `have_received` matcher. + Note that you must first stub the method or use a null double. + (Joe Ferris and Joël Quenneville) +* Make `at_least` and `at_most` style receive expectations print that they were + expecting at least or at most some number of calls, rather than just the + number of calls given in the expectation (Sam Phippen) +* Make `with` style receive expectations print the args they were expecting, and + the args that they got (Sam Phippen) +* Fix some warnings seen under ruby 2.0.0p0 (Sam Phippen). +* Add a new `:expect` syntax for message expectations + (Myron Marston and Sam Phippen). + +Bug fixes + +* Fix `any_instance` so that a frozen object can be `dup`'d when methods + have been stubbed on that type using `any_instance` (Jon Rowe). +* Fix `and_call_original` so that it properly raises an `ArgumentError` + when the wrong number of args are passed (Jon Rowe). +* Fix `double` on 1.9.2 so you can wrap them in an Array + using `Array(my_double)` (Jon Rowe). +* Fix `stub_const` and `hide_const` to handle constants that redefine `send` + (Sam Phippen). +* Fix `Marshal.dump` extension so that it correctly handles nil. + (Luke Imhoff, Jon Rowe) +* Fix isolation of `allow_message_expectations_on_nil` (Jon Rowe) +* Use inspect to format actual arguments on expectations in failure messages (#280, Ben Langfeld) +* Protect against improperly initialised test doubles (#293) (Joseph Shraibman and Jon Rowe) + +Deprecations + +* Deprecate `stub` and `mock` as aliases for `double`. `double` is the + best term for creating a test double, and it reduces confusion to + have only one term (Michi Huber). +* Deprecate `stub!` and `unstub!` in favor of `stub` and `unstub` + (Jon Rowe). +* Deprecate `at_least(0).times` and `any_number_of_times` (Michi Huber). + +### 2.13.1 / 2013-04-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.13.1) + +Bug fixes + +* Allow a block implementation to be used in combination with + `and_yield`, `and_raise`, `and_return` or `and_throw` (Myron Marston). + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.2...v2.13.0) + +Bug fixes + +* Fix bug that caused weird behavior when a method that had + previously been stubbed with multiple return values (e.g. + `obj.stub(:foo).and_return(1, 2)`) was later mocked with a + single return value (e.g. `obj.should_receive(:foo).once.and_return(1)`). + (Myron Marston) +* Fix bug related to a mock expectation for a method that already had + multiple stubs with different `with` constraints. Previously, the + first stub was used, even though it may not have matched the passed + args. The fix defers this decision until the message is received so + that the proper stub response can be chosen based on the passed + arguments (Myron Marston). +* Do not call `nil?` extra times on a mocked object, in case `nil?` + itself is expected a set number of times (Myron Marston). +* Fix `missing_default_stub_error` message so array args are handled + properly (Myron Marston). +* Explicitly disallow `any_instance.unstub!` (Ryan Jones). +* Fix `any_instance` stubbing so that it works with `Delegator` + subclasses (Myron Marston). +* Fix `and_call_original` so that it works with `Delegator` subclasses + (Myron Marston). +* Fix `any_instance.should_not_receive` when `any_instance.should_receive` + is used on the same class in the same example. Previously it would + wrongly report a failure even when the message was not received + (Myron Marston). + +### 2.12.2 / 2013-01-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.1...v.2.12.2) + +Bug fixes + +* Fix `and_call_original` to work properly for methods defined + on a module extended onto an object instance (Myron Marston). +* Fix `stub_const` with an undefined constnat name to work properly + with constant strings that are prefixed with `::` -- and edge case + I missed in the bug fix in the 2.12.1 release (Myron Marston). +* Ensure method visibility on a partial mock is restored after reseting + method stubs, even on a singleton module (created via `extend self`) + when the method visibility differs between the instance and singleton + versions (Andy Lindeman). + +### 2.12.1 / 2012-12-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Fix `any_instance` to support `and_call_original`. + (Myron Marston) +* Properly restore stubbed aliased methods on rubies + that report the incorrect owner (Myron Marston and Andy Lindeman). +* Fix `hide_const` and `stub_const` with a defined constnat name to + work properly with constant strings that are prefixed with `::` (Myron Marston). + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.3...v2.12.0) + +Enhancements + +* `and_raise` can accept an exception class and message, more closely + matching `Kernel#raise` (e.g., `foo.stub(:bar).and_raise(RuntimeError, "message")`) + (Bas Vodde) +* Add `and_call_original`, which will delegate the message to the + original method (Myron Marston). + +Deprecations: + +* Add deprecation warning when using `and_return` with `should_not_receive` + (Neha Kumari) + +### 2.11.3 / 2012-09-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.2...v2.11.3) + +Bug fixes + +* Fix `:transfer_nested_constants` option of `stub_const` so that it + doesn't blow up when there are inherited constants. (Myron Marston) +* `any_instance` stubs can be used on classes that override `Object#method`. + (Andy Lindeman) +* Methods stubbed with `any_instance` are unstubbed after the test finishes. + (Andy Lindeman) +* Fix confusing error message when calling a mocked class method an + extra time with the wrong arguments (Myron Marston). + +### 2.11.2 / 2012-08-11 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2) + +Bug fixes + +* Don't modify `dup` on classes that don't support `dup` (David Chelimsky) +* Fix `any_instance` so that it works properly with methods defined on + a superclass. (Daniel Eguzkiza) +* Fix `stub_const` so that it works properly for nested constants that + share a name with a top-level constant (e.g. "MyGem::Hash"). (Myron + Marston) + +### 2.11.1 / 2012-07-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Fix `should_receive` so that when it is called on an `as_null_object` + double with no implementation, and there is a previous explicit stub + for the same method, the explicit stub remains (rather than being + overridden with the null object implementation--`return self`). (Myron + Marston) + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.10.1...v2.11.0) + +Enhancements + +* Expose ArgumentListMatcher as a formal API + * supports use by 3rd party mock frameworks like Surrogate +* Add `stub_const` API to stub constants for the duration of an + example (Myron Marston). + +Bug fixes + +* Fix regression of edge case behavior. `double.should_receive(:foo) { a }` + was causing a NoMethodError when `double.stub(:foo).and_return(a, b)` + had been setup before (Myron Marston). +* Infinite loop generated by using `any_instance` and `dup`. (Sidu Ponnappa @kaiwren) +* `double.should_receive(:foo).at_least(:once).and_return(a)` always returns a + even if `:foo` is already stubbed. +* Prevent infinite loop when interpolating a null double into a string + as an integer (`"%i" % double.as_null_object`). (Myron Marston) +* Fix `should_receive` so that null object behavior (e.g. returning + self) is preserved if no implementation is given (Myron Marston). +* Fix `and_raise` so that it raises `RuntimeError` rather than + `Exception` by default, just like ruby does. (Andrew Marshall) + +### 2.10.1 / 2012-05-05 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.10.0...v2.10.1) + +Bug fixes + +* fix regression of edge case behavior + (https://github.com/rspec/rspec-mocks/issues/132) + * fixed failure of `object.should_receive(:message).at_least(0).times.and_return value` + * fixed failure of `object.should_not_receive(:message).and_return value` + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.9.0...v2.10.0) + +Bug fixes + +* fail fast when an `exactly` or `at_most` expectation is exceeded + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0...v2.9.0) + +Enhancements + +* Support order constraints across objects (preethiramdev) + +Bug fixes + +* Allow a `as_null_object` to be passed to `with` +* Pass proc to block passed to stub (Aubrey Rhodes) +* Initialize child message expectation args to match any args (#109 - + preethiramdev) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0.rc2...v2.8.0) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0.rc1...v2.8.0.rc2) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.7.0...v2.8.0.rc1) + +Enhancements + +* Eliminate Ruby warnings (Matijs van Zuijlen) + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.6.0...v2.7.0) + +Enhancements + +* Use `__send__` rather than `send` (alextk) +* Add support for `any_instance.stub_chain` (Sidu Ponnappa) +* Add support for `any_instance` argument matching based on `with` (Sidu + Ponnappa and Andy Lindeman) + +Changes + +* Check for `failure_message_for_should` or `failure_message` instead of + `description` to detect a matcher (Tibor Claassen) + +Bug fixes + +* pass a hash to `any_instance.stub`. (Justin Ko) +* allow `to_ary` to be called without raising `NoMethodError` (Mikhail + Dieterle) +* `any_instance` properly restores private methods (Sidu Ponnappa) + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.5.0...v2.6.0) + +Enhancements + +* Add support for `any_instance.stub` and `any_instance.should_receive` (Sidu + Ponnappa and Andy Lindeman) + +Bug fixes + +* fix bug in which multiple chains with shared messages ending in hashes failed + to return the correct value + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.4.0...v2.5.0) + +Bug fixes + +* message expectation counts now work in combination with a stub (Damian + Nurzynski) +* fix failure message when message received with incorrect args (Josep M. + Bach) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.3.0...v2.4.0) + +No functional changes in this release, which was made to align with the +rspec-core-2.4.0 release. + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.2.0...v2.3.0) + +Bug fixes + +* Fix our Marshal extension so that it does not interfere with objects that + have their own `@mock_proxy` instance variable. (Myron Marston) + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.1.0...v2.2.0) + +Enhancements + +* Added "rspec/mocks/standalone" for exploring the rspec-mocks in irb. + +Bug fix + +* Eliminate warning on splat args without parens (Gioele Barabucci) +* Fix bug where `obj.should_receive(:foo).with(stub.as_null_object)` would pass + with a false positive. + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.1...v2.1.0) + +Bug fixes + +* Fix serialization of stubbed object (Josep M Bach) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.22...v2.0.0) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* support passing a block to an expectation block (Nicolas Braem) + * `obj.should_receive(:msg) {|&block| ... }` + +Bug fixes + +* Fix YAML serialization of stub (Myron Marston) +* Fix rdoc rake task (Hans de Graaff) + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Bug fixes + +* fixed regression that broke `obj.stub_chain(:a, :b => :c)` +* fixed regression that broke `obj.stub_chain(:a, :b) { :c }` +* `respond_to?` always returns true when using `as_null_object` diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/LICENSE.md new file mode 100644 index 0000000000..dae02d8a7d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 David Chelimsky, Myron Marston +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-mocks-3.10.2/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/README.md new file mode 100644 index 0000000000..b63b789058 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/README.md @@ -0,0 +1,463 @@ +# RSpec Mocks [![Build Status](https://github.com/rspec/rspec-mocks/workflows/RSpec%20CI/badge.svg?branch=3-10-maintenance)](https://github.com/rspec/rspec-mocks/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-mocks.svg)](https://codeclimate.com/github/rspec/rspec-mocks) +rspec-mocks is a test-double framework for rspec with support for method stubs, +fakes, and message expectations on generated test-doubles and real objects +alike. + +## Install + + gem install rspec # for rspec-core, rspec-expectations, rspec-mocks + gem install rspec-mocks # for rspec-mocks only + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +For information about contributing to RSpec, please refer to the following markdown files: +* [Build details](BUILD_DETAIL.md) +* [Code of Conduct](CODE_OF_CONDUCT.md) +* [Detailed contributing guide](CONTRIBUTING.md) +* [Development setup guide](DEVELOPMENT.md) + +## Test Doubles + +A test double is an object that stands in for another object in your system +during a code example. Use the `double` method, passing in an optional identifier, to create one: + +```ruby +book = double("book") +``` + +Most of the time you will want some confidence that your doubles resemble an +existing object in your system. Verifying doubles are provided for this +purpose. If the existing object is available, they will prevent you from adding +stubs and expectations for methods that do not exist or that have an invalid +number of parameters. + +```ruby +book = instance_double("Book", :pages => 250) +``` + +Verifying doubles have some clever tricks to enable you to both test in +isolation without your dependencies loaded while still being able to validate +them against real objects. More detail is available in [their +documentation](https://github.com/rspec/rspec-mocks/blob/main/features/verifying_doubles). + +Verifying doubles can also accept custom identifiers, just like double(), e.g.: + +```ruby +books = [] +books << instance_double("Book", :rspec_book, :pages => 250) +books << instance_double("Book", "(Untitled)", :pages => 5000) + +puts books.inspect # with names, it's clearer which were actually added +``` + +## Method Stubs + +A method stub is an implementation that returns a pre-determined value. Method +stubs can be declared on test doubles or real objects using the same syntax. +rspec-mocks supports 3 forms for declaring method stubs: + +```ruby +allow(book).to receive(:title) { "The RSpec Book" } +allow(book).to receive(:title).and_return("The RSpec Book") +allow(book).to receive_messages( + :title => "The RSpec Book", + :subtitle => "Behaviour-Driven Development with RSpec, Cucumber, and Friends") +``` + +You can also use this shortcut, which creates a test double and declares a +method stub in one statement: + +```ruby +book = double("book", :title => "The RSpec Book") +``` + +The first argument is a name, which is used for documentation and appears in +failure messages. If you don't care about the name, you can leave it out, +making the combined instantiation/stub declaration very terse: + +```ruby +double(:foo => 'bar') +``` + +This is particularly nice when providing a list of test doubles to a method +that iterates through them: + +```ruby +order.calculate_total_price(double(:price => 1.99), double(:price => 2.99)) +``` + +### Stubbing a chain of methods + +You can use `receive_message_chain` in place of `receive` to stub a chain of messages: + +```ruby +allow(double).to receive_message_chain("foo.bar") { :baz } +allow(double).to receive_message_chain(:foo, :bar => :baz) +allow(double).to receive_message_chain(:foo, :bar) { :baz } + +# Given any of the above forms: +double.foo.bar # => :baz +``` + +Chains can be arbitrarily long, which makes it quite painless to violate the Law of Demeter in violent ways, so you should consider any use of `receive_message_chain` a code smell. Even though not all code smells indicate real problems (think fluent interfaces), `receive_message_chain` still results in brittle examples. For example, if you write `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the implementation calls `foo.baz.bar`, the stub will not work. + +## Consecutive return values + +When a stub might be invoked more than once, you can provide additional +arguments to `and_return`. The invocations cycle through the list. The last +value is returned for any subsequent invocations: + +```ruby +allow(die).to receive(:roll).and_return(1, 2, 3) +die.roll # => 1 +die.roll # => 2 +die.roll # => 3 +die.roll # => 3 +die.roll # => 3 +``` + +To return an array in a single invocation, declare an array: + +```ruby +allow(team).to receive(:players).and_return([double(:name => "David")]) +``` + +## Message Expectations + +A message expectation is an expectation that the test double will receive a +message some time before the example ends. If the message is received, the +expectation is satisfied. If not, the example fails. + +```ruby +validator = double("validator") +expect(validator).to receive(:validate) { "02134" } +zipcode = Zipcode.new("02134", validator) +zipcode.valid? +``` + +## Test Spies + +Verifies the given object received the expected message during the course of +the test. For a message to be verified, the given object must be setup to spy +on it, either by having it explicitly stubbed or by being a null object double +(e.g. `double(...).as_null_object`). Convenience methods are provided to easily +create null object doubles for this purpose: + +```ruby +spy("invitation") # => same as `double("invitation").as_null_object` +instance_spy("Invitation") # => same as `instance_double("Invitation").as_null_object` +class_spy("Invitation") # => same as `class_double("Invitation").as_null_object` +object_spy("Invitation") # => same as `object_double("Invitation").as_null_object` +``` + +Verifying messages received in this way implements the Test Spy pattern. + +```ruby +invitation = spy('invitation') + +user.accept_invitation(invitation) + +expect(invitation).to have_received(:accept) + +# You can also use other common message expectations. For example: +expect(invitation).to have_received(:accept).with(mailer) +expect(invitation).to have_received(:accept).twice +expect(invitation).to_not have_received(:accept).with(mailer) + +# One can specify a return value on the spy the same way one would a double. +invitation = spy('invitation', :accept => true) +expect(invitation).to have_received(:accept).with(mailer) +expect(invitation.accept).to eq(true) +``` + +Note that `have_received(...).with(...)` is unable to work properly when +passed arguments are mutated after the spy records the received message. +For example, this does not work properly: + +```ruby +greeter = spy("greeter") + +message = "Hello" +greeter.greet_with(message) +message << ", World" + +expect(greeter).to have_received(:greet_with).with("Hello") +``` + +## Nomenclature + +### Mock Objects and Test Stubs + +The names Mock Object and Test Stub suggest specialized Test Doubles. i.e. +a Test Stub is a Test Double that only supports method stubs, and a Mock +Object is a Test Double that supports message expectations and method +stubs. + +There is a lot of overlapping nomenclature here, and there are many +variations of these patterns (fakes, spies, etc). Keep in mind that most of +the time we're talking about method-level concepts that are variations of +method stubs and message expectations, and we're applying to them to _one_ +generic kind of object: a Test Double. + +### Test-Specific Extension + +a.k.a. Partial Double, a Test-Specific Extension is an extension of a +real object in a system that is instrumented with test-double like +behaviour in the context of a test. This technique is very common in Ruby +because we often see class objects acting as global namespaces for methods. +For example, in Rails: + +```ruby +person = double("person") +allow(Person).to receive(:find) { person } +``` + +In this case we're instrumenting Person to return the person object we've +defined whenever it receives the `find` message. We can also set a message +expectation so that the example fails if `find` is not called: + +```ruby +person = double("person") +expect(Person).to receive(:find) { person } +``` + +RSpec replaces the method we're stubbing or mocking with its own +test-double-like method. At the end of the example, RSpec verifies any message +expectations, and then restores the original methods. + +## Expecting Arguments + +```ruby +expect(double).to receive(:msg).with(*args) +expect(double).to_not receive(:msg).with(*args) +``` + +You can set multiple expectations for the same message if you need to: + +```ruby +expect(double).to receive(:msg).with("A", 1, 3) +expect(double).to receive(:msg).with("B", 2, 4) +``` + +## Argument Matchers + +Arguments that are passed to `with` are compared with actual arguments +received using ===. In cases in which you want to specify things about the +arguments rather than the arguments themselves, you can use any of the +matchers that ship with rspec-expectations. They don't all make syntactic +sense (they were primarily designed for use with RSpec::Expectations), but +you are free to create your own custom RSpec::Matchers. + +rspec-mocks also adds some keyword Symbols that you can use to +specify certain kinds of arguments: + +```ruby +expect(double).to receive(:msg).with(no_args) +expect(double).to receive(:msg).with(any_args) +expect(double).to receive(:msg).with(1, any_args) # any args acts like an arg splat and can go anywhere +expect(double).to receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can be any kind of Numeric +expect(double).to receive(:msg).with(1, boolean(), "b") #2nd argument can be true or false +expect(double).to receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp +expect(double).to receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all +expect(double).to receive(:msg).with(1, duck_type(:abs, :div), "b") #2nd argument can be object that responds to #abs and #div +expect(double).to receive(:msg).with(hash_including(:a => 5)) # first arg is a hash with a: 5 as one of the key-values +expect(double).to receive(:msg).with(array_including(5)) # first arg is an array with 5 as one of the key-values +expect(double).to receive(:msg).with(hash_excluding(:a => 5)) # first arg is a hash without a: 5 as one of the key-values +``` + +## Receive Counts + +```ruby +expect(double).to receive(:msg).once +expect(double).to receive(:msg).twice +expect(double).to receive(:msg).exactly(n).time +expect(double).to receive(:msg).exactly(n).times +expect(double).to receive(:msg).at_least(:once) +expect(double).to receive(:msg).at_least(:twice) +expect(double).to receive(:msg).at_least(n).time +expect(double).to receive(:msg).at_least(n).times +expect(double).to receive(:msg).at_most(:once) +expect(double).to receive(:msg).at_most(:twice) +expect(double).to receive(:msg).at_most(n).time +expect(double).to receive(:msg).at_most(n).times +``` + +## Ordering + +```ruby +expect(double).to receive(:msg).ordered +expect(double).to receive(:other_msg).ordered + # This will fail if the messages are received out of order +``` + +This can include the same message with different arguments: + +```ruby +expect(double).to receive(:msg).with("A", 1, 3).ordered +expect(double).to receive(:msg).with("B", 2, 4).ordered +``` + +## Setting Responses + +Whether you are setting a message expectation or a method stub, you can +tell the object precisely how to respond. The most generic way is to pass +a block to `receive`: + +```ruby +expect(double).to receive(:msg) { value } +``` + +When the double receives the `msg` message, it evaluates the block and returns +the result. + +```ruby +expect(double).to receive(:msg).and_return(value) +expect(double).to receive(:msg).exactly(3).times.and_return(value1, value2, value3) + # returns value1 the first time, value2 the second, etc +expect(double).to receive(:msg).and_raise(error) + # `error` can be an instantiated object (e.g. `StandardError.new(some_arg)`) or a class (e.g. `StandardError`) + # if it is a class, it must be instantiable with no args +expect(double).to receive(:msg).and_throw(:msg) +expect(double).to receive(:msg).and_yield(values, to, yield) +expect(double).to receive(:msg).and_yield(values, to, yield).and_yield(some, other, values, this, time) + # for methods that yield to a block multiple times +``` + +Any of these responses can be applied to a stub as well + +```ruby +allow(double).to receive(:msg).and_return(value) +allow(double).to receive(:msg).and_return(value1, value2, value3) +allow(double).to receive(:msg).and_raise(error) +allow(double).to receive(:msg).and_throw(:msg) +allow(double).to receive(:msg).and_yield(values, to, yield) +allow(double).to receive(:msg).and_yield(values, to, yield).and_yield(some, other, values, this, time) +``` + +## Arbitrary Handling + +Once in a while you'll find that the available expectations don't solve the +particular problem you are trying to solve. Imagine that you expect the message +to come with an Array argument that has a specific length, but you don't care +what is in it. You could do this: + +```ruby +expect(double).to receive(:msg) do |arg| + expect(arg.size).to eq 7 +end +``` + +If the method being stubbed itself takes a block, and you need to yield to it +in some special way, you can use this: + +```ruby +expect(double).to receive(:msg) do |&arg| + begin + arg.call + ensure + # cleanup + end +end +``` + +## Delegating to the Original Implementation + +When working with a partial mock object, you may occasionally +want to set a message expectation without interfering with how +the object responds to the message. You can use `and_call_original` +to achieve this: + +```ruby +expect(Person).to receive(:find).and_call_original +Person.find # => executes the original find method and returns the result +``` + +## Combining Expectation Details + +Combining the message name with specific arguments, receive counts and responses +you can get quite a bit of detail in your expectations: + +```ruby +expect(double).to receive(:<<).with("illegal value").once.and_raise(ArgumentError) +``` + +While this is a good thing when you really need it, you probably don't really +need it! Take care to specify only the things that matter to the behavior of +your code. + +## Stubbing and Hiding Constants + +See the [mutating constants +README](https://github.com/rspec/rspec-mocks/blob/main/features/mutating_constants/README.md) +for info on this feature. + +## Use `before(:example)`, not `before(:context)` + +Stubs in `before(:context)` are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in `before(:context)` would work in the first example that happens to run in that group, but not for any others. + +Instead of `before(:context)`, use `before(:example)`. + +## Settings mocks or stubs on any instance of a class + +rspec-mocks provides two methods, `allow_any_instance_of` and +`expect_any_instance_of`, that will allow you to stub or mock any instance +of a class. They are used in place of `allow` or `expect`: + +```ruby +allow_any_instance_of(Widget).to receive(:name).and_return("Wibble") +expect_any_instance_of(Widget).to receive(:name).and_return("Wobble") +``` + +These methods add the appropriate stub or expectation to all instances of +`Widget`. + +This feature is sometimes useful when working with legacy code, though in +general we discourage its use for a number of reasons: + +* The `rspec-mocks` API is designed for individual object instances, but this + feature operates on entire classes of objects. As a result there are some + semantically confusing edge cases. For example in + `expect_any_instance_of(Widget).to receive(:name).twice` it isn't clear + whether each specific instance is expected to receive `name` twice, or if two + receives total are expected. (It's the former.) +* Using this feature is often a design smell. It may be + that your test is trying to do too much or that the object under test is too + complex. +* It is the most complicated feature of `rspec-mocks`, and has historically + received the most bug reports. (None of the core team actively use it, + which doesn't help.) + + +## Further Reading + +There are many different viewpoints about the meaning of mocks and stubs. If +you are interested in learning more, here is some recommended reading: + +* Mock Objects: http://www.mockobjects.com/ +* Endo-Testing: http://www.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF +* Mock Roles, Not Objects: http://www.jmock.org/oopsla2004.pdf +* Test Double: http://www.martinfowler.com/bliki/TestDouble.html +* Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html +* Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks.rb new file mode 100644 index 0000000000..297779e5a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks.rb @@ -0,0 +1,133 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support 'caller_filter' +RSpec::Support.require_rspec_support 'warnings' +RSpec::Support.require_rspec_support 'ruby_features' + +RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f } + +%w[ + instance_method_stasher + method_double + argument_matchers + example_methods + proxy + test_double + argument_list_matcher + message_expectation + order_group + error_generator + space + mutate_const + targets + syntax + configuration + verifying_double + version +].each { |name| RSpec::Support.require_rspec_mocks name } + +# Share the top-level RSpec namespace, because we are a core supported +# extension. +module RSpec + # Contains top-level utility methods. While this contains a few + # public methods, these are not generally meant to be called from + # a test or example. They exist primarily for integration with + # test frameworks (such as rspec-core). + module Mocks + # Performs per-test/example setup. This should be called before + # an test or example begins. + def self.setup + @space_stack << (@space = space.new_scope) + end + + # Verifies any message expectations that were set during the + # test or example. This should be called at the end of an example. + def self.verify + space.verify_all + end + + # Cleans up all test double state (including any methods that were + # redefined on partial doubles). This _must_ be called after + # each example, even if an error was raised during the example. + def self.teardown + space.reset_all + @space_stack.pop + @space = @space_stack.last || @root_space + end + + # Adds an allowance (stub) on `subject` + # + # @param subject the subject to which the message will be added + # @param message a symbol, representing the message that will be + # added. + # @param opts a hash of options, :expected_from is used to set the + # original call site + # @yield an optional implementation for the allowance + # + # @example Defines the implementation of `foo` on `bar`, using the passed block + # x = 0 + # RSpec::Mocks.allow_message(bar, :foo) { x += 1 } + def self.allow_message(subject, message, opts={}, &block) + space.proxy_for(subject).add_stub(message, opts, &block) + end + + # Sets a message expectation on `subject`. + # @param subject the subject on which the message will be expected + # @param message a symbol, representing the message that will be + # expected. + # @param opts a hash of options, :expected_from is used to set the + # original call site + # @yield an optional implementation for the expectation + # + # @example Expect the message `foo` to receive `bar`, then call it + # RSpec::Mocks.expect_message(bar, :foo) + # bar.foo + def self.expect_message(subject, message, opts={}, &block) + space.proxy_for(subject).add_message_expectation(message, opts, &block) + end + + # Call the passed block and verify mocks after it has executed. This allows + # mock usage in arbitrary places, such as a `before(:all)` hook. + # + # @return [Object] the return value from the block + def self.with_temporary_scope + setup + + begin + result = yield + verify + result + ensure + teardown + end + end + + class << self + # @private + attr_reader :space + end + @space_stack = [] + @root_space = @space = RSpec::Mocks::RootSpace.new + + # @private + IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored' + + # To speed up boot time a bit, delay loading optional or rarely + # used features until their first use. + autoload :AnyInstance, "rspec/mocks/any_instance" + autoload :ExpectChain, "rspec/mocks/message_chain" + autoload :StubChain, "rspec/mocks/message_chain" + autoload :MarshalExtension, "rspec/mocks/marshal_extension" + + # Namespace for mock-related matchers. + module Matchers + # @private + # just a "tag" for rspec-mock matchers detection + module Matcher; end + + autoload :HaveReceived, "rspec/mocks/matchers/have_received" + autoload :Receive, "rspec/mocks/matchers/receive" + autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain" + autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance.rb new file mode 100644 index 0000000000..41eae81461 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance.rb @@ -0,0 +1,11 @@ +%w[ + any_instance/chain + any_instance/error_generator + any_instance/stub_chain + any_instance/stub_chain_chain + any_instance/expect_chain_chain + any_instance/expectation_chain + any_instance/message_chains + any_instance/recorder + any_instance/proxy +].each { |f| RSpec::Support.require_rspec_mocks(f) } diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/chain.rb new file mode 100644 index 0000000000..74d864b3a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/chain.rb @@ -0,0 +1,111 @@ +module RSpec + module Mocks + # @private + module AnyInstance + # @private + class Chain + def initialize(recorder, *args, &block) + @recorder = recorder + @expectation_args = args + @expectation_block = block + @argument_list_matcher = ArgumentListMatcher::MATCH_ALL + end + + # @private + # + # Provides convenience methods for recording customizations on message + # expectations. + module Customizations + # @macro [attach] record + # @method $1(*args, &block) + # Records the `$1` message for playback against an instance that + # invokes a method stubbed or mocked using `any_instance`. + # + # @see RSpec::Mocks::MessageExpectation#$1 + # + def self.record(method_name) + define_method(method_name) do |*args, &block| + record(method_name, *args, &block) + end + end + + record :and_return + record :and_raise + record :and_throw + record :and_yield + record :and_call_original + record :and_wrap_original + record :with + record :once + record :twice + record :thrice + record :exactly + record :times + record :time + record :never + record :at_least + record :at_most + end + + include Customizations + + # @private + def playback!(instance) + message_expectation = create_message_expectation_on(instance) + messages.inject(message_expectation) do |object, message| + object.__send__(*message.first, &message.last) + end + end + + # @private + def constrained_to_any_of?(*constraints) + constraints.any? do |constraint| + messages.any? do |message| + message.first.first == constraint + end + end + end + + # @private + def matches_args?(*args) + @argument_list_matcher.args_match?(*args) + end + + # @private + def expectation_fulfilled! + @expectation_fulfilled = true + end + + def never + AnyInstance.error_generator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated? + super + end + + def with(*args, &block) + @argument_list_matcher = ArgumentListMatcher.new(*args) + super + end + + private + + def negated? + messages.any? { |(message, *_), _| message == :never } + end + + def messages + @messages ||= [] + end + + def last_message + messages.last.first.first unless messages.empty? + end + + def record(rspec_method_name, *args, &block) + verify_invocation_order(rspec_method_name, *args, &block) + messages << [args.unshift(rspec_method_name), block] + self + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/error_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/error_generator.rb new file mode 100644 index 0000000000..d1046cb0bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/error_generator.rb @@ -0,0 +1,31 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ErrorGenerator < ::RSpec::Mocks::ErrorGenerator + def raise_second_instance_received_message_error(unfulfilled_expectations) + __raise "Exactly one instance should have received the following " \ + "message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}" + end + + def raise_does_not_implement_error(klass, method_name) + __raise "#{klass} does not implement ##{method_name}" + end + + def raise_message_already_received_by_other_instance_error(method_name, object_inspect, invoked_instance) + __raise "The message '#{method_name}' was received by #{object_inspect} " \ + "but has already been received by #{invoked_instance}" + end + + def raise_not_supported_with_prepend_error(method_name, problem_mod) + __raise "Using `any_instance` to stub a method (#{method_name}) that has been " \ + "defined on a prepended module (#{problem_mod}) is not supported." + end + end + + def self.error_generator + @error_generator ||= ErrorGenerator.new + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expect_chain_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expect_chain_chain.rb new file mode 100644 index 0000000000..c467ba93a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expect_chain_chain.rb @@ -0,0 +1,31 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ExpectChainChain < StubChain + def initialize(*args) + super + @expectation_fulfilled = false + end + + def expectation_fulfilled? + @expectation_fulfilled + end + + def playback!(instance) + super.tap { @expectation_fulfilled = true } + end + + private + + def create_message_expectation_on(instance) + ::RSpec::Mocks::ExpectChain.expect_chain_on(instance, *@expectation_args, &@expectation_block) + end + + def invocation_order + EmptyInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expectation_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expectation_chain.rb new file mode 100644 index 0000000000..edf854826c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/expectation_chain.rb @@ -0,0 +1,50 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ExpectationChain < Chain + def expectation_fulfilled? + @expectation_fulfilled || constrained_to_any_of?(:never) + end + + def initialize(*args, &block) + @expectation_fulfilled = false + super + end + + private + + def verify_invocation_order(_rspec_method_name, *_args, &_block) + end + end + + # @private + class PositiveExpectationChain < ExpectationChain + private + + def create_message_expectation_on(instance) + proxy = ::RSpec::Mocks.space.proxy_for(instance) + method_name, opts = @expectation_args + opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) + + me = proxy.add_message_expectation(method_name, opts, &@expectation_block) + if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? + me.and_yield_receiver_to_implementation + end + + me + end + + ExpectationInvocationOrder = + { + :and_return => [:with, nil], + :and_raise => [:with, nil], + }.freeze + + def invocation_order + ExpectationInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/message_chains.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/message_chains.rb new file mode 100644 index 0000000000..7298643d5c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/message_chains.rb @@ -0,0 +1,83 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class MessageChains + def initialize + @chains_by_method_name = Hash.new { |h, k| h[k] = [] } + end + + # @private + def [](method_name) + @chains_by_method_name[method_name] + end + + # @private + def add(method_name, chain) + @chains_by_method_name[method_name] << chain + chain + end + + # @private + def remove_stub_chains_for!(method_name) + @chains_by_method_name[method_name].reject! do |chain| + StubChain === chain + end + end + + # @private + def has_expectation?(method_name) + @chains_by_method_name[method_name].find do |chain| + ExpectationChain === chain + end + end + + # @private + def each_unfulfilled_expectation_matching(method_name, *args) + @chains_by_method_name[method_name].each do |chain| + yield chain if !chain.expectation_fulfilled? && chain.matches_args?(*args) + end + end + + # @private + def all_expectations_fulfilled? + @chains_by_method_name.all? do |_method_name, chains| + chains.all? { |chain| chain.expectation_fulfilled? } + end + end + + # @private + def unfulfilled_expectations + @chains_by_method_name.map do |method_name, chains| + method_name.to_s if ExpectationChain === chains.last unless chains.last.expectation_fulfilled? + end.compact + end + + # @private + def received_expected_message!(method_name) + @chains_by_method_name[method_name].each do |chain| + chain.expectation_fulfilled! + end + end + + # @private + def playback!(instance, method_name) + raise_if_second_instance_to_receive_message(instance) + @chains_by_method_name[method_name].each do |chain| + chain.playback!(instance) + end + end + + private + + def raise_if_second_instance_to_receive_message(instance) + @instance_with_expectation ||= instance if ExpectationChain === instance + return unless ExpectationChain === instance + return if @instance_with_expectation.equal?(instance) + + AnyInstance.error_generator.raise_second_instance_received_message_error(unfulfilled_expectations) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/proxy.rb new file mode 100644 index 0000000000..fc66d392ae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/proxy.rb @@ -0,0 +1,116 @@ +module RSpec + module Mocks + module AnyInstance + # @private + # The `AnyInstance::Recorder` is responsible for redefining the klass's + # instance method in order to add any stubs/expectations the first time + # the method is called. It's not capable of updating a stub on an instance + # that's already been previously stubbed (either directly, or via + # `any_instance`). + # + # This proxy sits in front of the recorder and delegates both to it + # and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed + # instance of the class, in order to propogates changes to the instances. + # + # Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless + # and is not persisted in `RSpec::Mocks.space`. + # + # Proxying for the message expectation fluent interface (typically chained + # off of the return value of one of these methods) is provided by the + # `FluentInterfaceProxy` class below. + class Proxy + def initialize(recorder, target_proxies) + @recorder = recorder + @target_proxies = target_proxies + end + + def klass + @recorder.klass + end + + def stub(method_name_or_method_map, &block) + if Hash === method_name_or_method_map + method_name_or_method_map.each do |method_name, return_value| + stub(method_name).and_return(return_value) + end + else + perform_proxying(__method__, [method_name_or_method_map], block) do |proxy| + proxy.add_stub(method_name_or_method_map, &block) + end + end + end + + def unstub(method_name) + perform_proxying(__method__, [method_name], nil) do |proxy| + proxy.remove_stub_if_present(method_name) + end + end + + def stub_chain(*chain, &block) + perform_proxying(__method__, chain, block) do |proxy| + Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block) + end + end + + def expect_chain(*chain, &block) + perform_proxying(__method__, chain, block) do |proxy| + Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block) + end + end + + def should_receive(method_name, &block) + perform_proxying(__method__, [method_name], block) do |proxy| + # Yeah, this is a bit odd...but if we used `add_message_expectation` + # then it would act like `expect_every_instance_of(klass).to receive`. + # The any_instance recorder takes care of validating that an instance + # received the message. + proxy.add_stub(method_name, &block) + end + end + + def should_not_receive(method_name, &block) + perform_proxying(__method__, [method_name], block) do |proxy| + proxy.add_message_expectation(method_name, &block).never + end + end + + private + + def perform_proxying(method_name, args, block, &target_proxy_block) + recorder_value = @recorder.__send__(method_name, *args, &block) + proxy_values = @target_proxies.map(&target_proxy_block) + FluentInterfaceProxy.new([recorder_value] + proxy_values) + end + end + + # @private + # Delegates messages to each of the given targets in order to + # provide the fluent interface that is available off of message + # expectations when dealing with `any_instance`. + # + # `targets` will typically contain 1 of the `AnyInstance::Recorder` + # return values and N `MessageExpectation` instances (one per instance + # of the `any_instance` klass). + class FluentInterfaceProxy + def initialize(targets) + @targets = targets + end + + if RUBY_VERSION.to_f > 1.8 + def respond_to_missing?(method_name, include_private=false) + super || @targets.first.respond_to?(method_name, include_private) + end + else + def respond_to?(method_name, include_private=false) + super || @targets.first.respond_to?(method_name, include_private) + end + end + + def method_missing(*args, &block) + return_values = @targets.map { |t| t.__send__(*args, &block) } + FluentInterfaceProxy.new(return_values) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/recorder.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/recorder.rb new file mode 100644 index 0000000000..aad6ed4c8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/recorder.rb @@ -0,0 +1,295 @@ +module RSpec + module Mocks + module AnyInstance + # Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`, + # which records stubs and message expectations for later playback on + # instances of `TheClass`. + # + # Further constraints are stored in instances of [Chain](Chain). + # + # @see AnyInstance + # @see Chain + class Recorder + # @private + attr_reader :message_chains, :stubs, :klass + + def initialize(klass) + @message_chains = MessageChains.new + @stubs = Hash.new { |hash, key| hash[key] = [] } + @observed_methods = [] + @played_methods = {} + @backed_up_method_owner = {} + @klass = klass + @expectation_set = false + + return unless RSpec::Mocks.configuration.verify_partial_doubles? + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call(ObjectReference.for(klass)) + end + end + + # Initializes the recording a stub to be played back against any + # instance of this object that invokes the submitted method. + # + # @see Methods#stub + def stub(method_name, &block) + observe!(method_name) + message_chains.add(method_name, StubChain.new(self, method_name, &block)) + end + + # Initializes the recording a stub chain to be played back against any + # instance of this object that invokes the method matching the first + # argument. + # + # @see Methods#stub_chain + def stub_chain(*method_names_and_optional_return_values, &block) + normalize_chain(*method_names_and_optional_return_values) do |method_name, args| + observe!(method_name) + message_chains.add(method_name, StubChainChain.new(self, *args, &block)) + end + end + + # @private + def expect_chain(*method_names_and_optional_return_values, &block) + @expectation_set = true + normalize_chain(*method_names_and_optional_return_values) do |method_name, args| + observe!(method_name) + message_chains.add(method_name, ExpectChainChain.new(self, *args, &block)) + end + end + + # Initializes the recording a message expectation to be played back + # against any instance of this object that invokes the submitted + # method. + # + # @see Methods#should_receive + def should_receive(method_name, &block) + @expectation_set = true + observe!(method_name) + message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block)) + end + + # The opposite of `should_receive` + # + # @see Methods#should_not_receive + def should_not_receive(method_name, &block) + should_receive(method_name, &block).never + end + + # Removes any previously recorded stubs, stub_chains or message + # expectations that use `method_name`. + # + # @see Methods#unstub + def unstub(method_name) + unless @observed_methods.include?(method_name.to_sym) + AnyInstance.error_generator.raise_method_not_stubbed_error(method_name) + end + message_chains.remove_stub_chains_for!(method_name) + stubs[method_name].clear + stop_observing!(method_name) unless message_chains.has_expectation?(method_name) + end + + # @api private + # + # Used internally to verify that message expectations have been + # fulfilled. + def verify + return unless @expectation_set + return if message_chains.all_expectations_fulfilled? + + AnyInstance.error_generator.raise_second_instance_received_message_error(message_chains.unfulfilled_expectations) + end + + # @private + def stop_all_observation! + @observed_methods.each { |method_name| restore_method!(method_name) } + end + + # @private + def playback!(instance, method_name) + RSpec::Mocks.space.ensure_registered(instance) + message_chains.playback!(instance, method_name) + @played_methods[method_name] = instance + received_expected_message!(method_name) if message_chains.has_expectation?(method_name) + end + + # @private + def instance_that_received(method_name) + @played_methods[method_name] + end + + # @private + def build_alias_method_name(method_name) + "__#{method_name}_without_any_instance__" + end + + # @private + def already_observing?(method_name) + @observed_methods.include?(method_name) || super_class_observing?(method_name) + end + + # @private + def notify_received_message(_object, message, args, _blk) + has_expectation = false + + message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation| + has_expectation = true + expectation.expectation_fulfilled! + end + + return unless has_expectation + + restore_method!(message) + mark_invoked!(message) + end + + protected + + def stop_observing!(method_name) + restore_method!(method_name) + @observed_methods.delete(method_name) + super_class_observers_for(method_name).each do |ancestor| + ::RSpec::Mocks.space. + any_instance_recorder_for(ancestor).stop_observing!(method_name) + end + end + + private + + def ancestor_is_an_observer?(method_name) + lambda do |ancestor| + unless ancestor == @klass + ::RSpec::Mocks.space. + any_instance_recorder_for(ancestor).already_observing?(method_name) + end + end + end + + def super_class_observers_for(method_name) + @klass.ancestors.select(&ancestor_is_an_observer?(method_name)) + end + + def super_class_observing?(method_name) + @klass.ancestors.any?(&ancestor_is_an_observer?(method_name)) + end + + def normalize_chain(*args) + args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a } + yield args.first, args + end + + def received_expected_message!(method_name) + message_chains.received_expected_message!(method_name) + restore_method!(method_name) + mark_invoked!(method_name) + end + + def restore_method!(method_name) + if public_protected_or_private_method_defined?(build_alias_method_name(method_name)) + restore_original_method!(method_name) + else + remove_dummy_method!(method_name) + end + end + + def restore_original_method!(method_name) + return unless @klass.instance_method(method_name).owner == @klass + + alias_method_name = build_alias_method_name(method_name) + @klass.class_exec(@backed_up_method_owner) do |backed_up_method_owner| + remove_method method_name + + # A @klass can have methods implemented (see Method#owner) in @klass + # or inherited from a superclass. In ruby 2.2 and earlier, we can copy + # a method regardless of the 'owner' and restore it to @klass after + # because a call to 'super' from @klass's copied method would end up + # calling the original class's superclass's method. + # + # With the commit below, available starting in 2.3.0, ruby changed + # this behavior and a call to 'super' from the method copied to @klass + # will call @klass's superclass method, which is the original + # implementer of this method! This leads to very strange errors + # if @klass's copied method calls 'super', since it would end up + # calling itself, the original method implemented in @klass's + # superclass. + # + # For ruby 2.3 and above, we need to only restore methods that + # @klass originally owned. + # + # https://github.com/ruby/ruby/commit/c8854d2ca4be9ee6946e6d17b0e17d9ef130ee81 + if RUBY_VERSION < "2.3" || backed_up_method_owner[method_name.to_sym] == self + alias_method method_name, alias_method_name + end + remove_method alias_method_name + end + end + + def remove_dummy_method!(method_name) + @klass.class_exec do + remove_method method_name + end + end + + def backup_method!(method_name) + return unless public_protected_or_private_method_defined?(method_name) + + alias_method_name = build_alias_method_name(method_name) + @backed_up_method_owner[method_name.to_sym] ||= @klass.instance_method(method_name).owner + @klass.class_exec do + alias_method alias_method_name, method_name + end + end + + def public_protected_or_private_method_defined?(method_name) + MethodReference.method_defined_at_any_visibility?(@klass, method_name) + end + + def observe!(method_name) + allow_no_prepended_module_definition_of(method_name) + + if RSpec::Mocks.configuration.verify_partial_doubles? && !Mocks.configuration.temporarily_suppress_partial_double_verification + unless public_protected_or_private_method_defined?(method_name) + AnyInstance.error_generator.raise_does_not_implement_error(@klass, method_name) + end + end + + stop_observing!(method_name) if already_observing?(method_name) + @observed_methods << method_name + backup_method!(method_name) + recorder = self + @klass.__send__(:define_method, method_name) do |*args, &blk| + recorder.playback!(self, method_name) + __send__(method_name, *args, &blk) + end + @klass.__send__(:ruby2_keywords, method_name) if @klass.respond_to?(:ruby2_keywords, true) + end + + def mark_invoked!(method_name) + backup_method!(method_name) + recorder = self + @klass.__send__(:define_method, method_name) do |*_args, &_blk| + invoked_instance = recorder.instance_that_received(method_name) + inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>" + AnyInstance.error_generator.raise_message_already_received_by_other_instance_error( + method_name, inspect, invoked_instance + ) + end + end + + if Support::RubyFeatures.module_prepends_supported? + def allow_no_prepended_module_definition_of(method_name) + prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass) + problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) } + return unless problem_mod + + AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod) + end + else + def allow_no_prepended_module_definition_of(_method_name) + # nothing to do; prepends aren't supported on this version of ruby + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain.rb new file mode 100644 index 0000000000..c4c0ab748f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain.rb @@ -0,0 +1,51 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class StubChain < Chain + # @private + def expectation_fulfilled? + true + end + + private + + def create_message_expectation_on(instance) + proxy = ::RSpec::Mocks.space.proxy_for(instance) + method_name, opts = @expectation_args + opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) + + stub = proxy.add_stub(method_name, opts, &@expectation_block) + @recorder.stubs[stub.message] << stub + + if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? + stub.and_yield_receiver_to_implementation + end + + stub + end + + InvocationOrder = + { + :and_return => [:with, nil], + :and_raise => [:with, nil], + :and_yield => [:with, :and_yield, nil], + :and_throw => [:with, nil], + :and_call_original => [:with, nil], + :and_wrap_original => [:with, nil] + }.freeze + + EmptyInvocationOrder = {}.freeze + + def invocation_order + InvocationOrder + end + + def verify_invocation_order(rspec_method_name, *_args, &_block) + return if invocation_order.fetch(rspec_method_name, [nil]).include?(last_message) + raise NoMethodError, "Undefined method #{rspec_method_name}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain_chain.rb new file mode 100644 index 0000000000..495511c096 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/any_instance/stub_chain_chain.rb @@ -0,0 +1,23 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class StubChainChain < StubChain + def initialize(*args) + super + @expectation_fulfilled = false + end + + private + + def create_message_expectation_on(instance) + ::RSpec::Mocks::StubChain.stub_chain_on(instance, *@expectation_args, &@expectation_block) + end + + def invocation_order + EmptyInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_list_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_list_matcher.rb new file mode 100644 index 0000000000..1ee647bb65 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_list_matcher.rb @@ -0,0 +1,100 @@ +# We intentionally do not use the `RSpec::Support.require...` methods +# here so that this file can be loaded individually, as documented +# below. +require 'rspec/mocks/argument_matchers' +require 'rspec/support/fuzzy_matcher' + +module RSpec + module Mocks + # Wrapper for matching arguments against a list of expected values. Used by + # the `with` method on a `MessageExpectation`: + # + # expect(object).to receive(:message).with(:a, 'b', 3) + # object.message(:a, 'b', 3) + # + # Values passed to `with` can be literal values or argument matchers that + # match against the real objects .e.g. + # + # expect(object).to receive(:message).with(hash_including(:a => 'b')) + # + # Can also be used directly to match the contents of any `Array`. This + # enables 3rd party mocking libs to take advantage of rspec's argument + # matching without using the rest of rspec-mocks. + # + # require 'rspec/mocks/argument_list_matcher' + # include RSpec::Mocks::ArgumentMatchers + # + # arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b')) + # arg_list_matcher.args_match?(123, :a => 'b') + # + # This class is immutable. + # + # @see ArgumentMatchers + class ArgumentListMatcher + # @private + attr_reader :expected_args + + # @api public + # @param [Array] expected_args a list of expected literals and/or argument matchers + # + # Initializes an `ArgumentListMatcher` with a collection of literal + # values and/or argument matchers. + # + # @see ArgumentMatchers + # @see #args_match? + def initialize(*expected_args) + @expected_args = expected_args + ensure_expected_args_valid! + end + + # @api public + # @param [Array] args + # + # Matches each element in the `expected_args` against the element in the same + # position of the arguments passed to `new`. + # + # @see #initialize + def args_match?(*args) + Support::FuzzyMatcher.values_match?(resolve_expected_args_based_on(args), args) + end + + # @private + # Resolves abstract arg placeholders like `no_args` and `any_args` into + # a more concrete arg list based on the provided `actual_args`. + def resolve_expected_args_based_on(actual_args) + return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args + + any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } + return expected_args unless any_args_index + + replace_any_args_with_splat_of_anything(any_args_index, actual_args.count) + end + + private + + def replace_any_args_with_splat_of_anything(before_count, actual_args_count) + any_args_count = actual_args_count - expected_args.count + 1 + after_count = expected_args.count - before_count - 1 + + any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE } + expected_args.first(before_count) + any_args + expected_args.last(after_count) + end + + def ensure_expected_args_valid! + if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1 + raise ArgumentError, "`any_args` can only be passed to " \ + "`with` once but you have passed it multiple times." + elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a } + raise ArgumentError, "`no_args` can only be passed as a " \ + "singleton argument to `with` (i.e. `with(no_args)`), " \ + "but you have passed additional arguments." + end + end + + # Value that will match all argument lists. + # + # @private + MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher::INSTANCE) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_matchers.rb new file mode 100644 index 0000000000..fe41744b6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/argument_matchers.rb @@ -0,0 +1,322 @@ +# This cannot take advantage of our relative requires, since this file is a +# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for +# details. +require 'rspec/support/matcher_definition' + +module RSpec + module Mocks + # ArgumentMatchers are placeholders that you can include in message + # expectations to match arguments against a broader check than simple + # equality. + # + # With the exception of `any_args` and `no_args`, they all match against + # the arg in same position in the argument list. + # + # @see ArgumentListMatcher + module ArgumentMatchers + # Acts like an arg splat, matching any number of args at any point in an arg list. + # + # @example + # expect(object).to receive(:message).with(1, 2, any_args) + # + # # matches any of these: + # object.message(1, 2) + # object.message(1, 2, 3) + # object.message(1, 2, 3, 4) + def any_args + AnyArgsMatcher::INSTANCE + end + + # Matches any argument at all. + # + # @example + # expect(object).to receive(:message).with(anything) + def anything + AnyArgMatcher::INSTANCE + end + + # Matches no arguments. + # + # @example + # expect(object).to receive(:message).with(no_args) + def no_args + NoArgsMatcher::INSTANCE + end + + # Matches if the actual argument responds to the specified messages. + # + # @example + # expect(object).to receive(:message).with(duck_type(:hello)) + # expect(object).to receive(:message).with(duck_type(:hello, :goodbye)) + def duck_type(*args) + DuckTypeMatcher.new(*args) + end + + # Matches a boolean value. + # + # @example + # expect(object).to receive(:message).with(boolean()) + def boolean + BooleanMatcher::INSTANCE + end + + # Matches a hash that includes the specified key(s) or key/value pairs. + # Ignores any additional keys. + # + # @example + # expect(object).to receive(:message).with(hash_including(:key => val)) + # expect(object).to receive(:message).with(hash_including(:key)) + # expect(object).to receive(:message).with(hash_including(:key, :key2 => val2)) + def hash_including(*args) + HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args)) + end + + # Matches an array that includes the specified items at least once. + # Ignores duplicates and additional values + # + # @example + # expect(object).to receive(:message).with(array_including(1,2,3)) + # expect(object).to receive(:message).with(array_including([1,2,3])) + def array_including(*args) + actually_an_array = Array === args.first && args.count == 1 ? args.first : args + ArrayIncludingMatcher.new(actually_an_array) + end + + # Matches a hash that doesn't include the specified key(s) or key/value. + # + # @example + # expect(object).to receive(:message).with(hash_excluding(:key => val)) + # expect(object).to receive(:message).with(hash_excluding(:key)) + # expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2)) + def hash_excluding(*args) + HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args)) + end + + alias_method :hash_not_including, :hash_excluding + + # Matches if `arg.instance_of?(klass)` + # + # @example + # expect(object).to receive(:message).with(instance_of(Thing)) + def instance_of(klass) + InstanceOf.new(klass) + end + + alias_method :an_instance_of, :instance_of + + # Matches if `arg.kind_of?(klass)` + # + # @example + # expect(object).to receive(:message).with(kind_of(Thing)) + def kind_of(klass) + KindOf.new(klass) + end + + alias_method :a_kind_of, :kind_of + + # @private + def self.anythingize_lonely_keys(*args) + hash = Hash === args.last ? args.delete_at(-1) : {} + args.each { | arg | hash[arg] = AnyArgMatcher::INSTANCE } + hash + end + + # Intended to be subclassed by stateless, immutable argument matchers. + # Provides a `::INSTANCE` constant for accessing a global + # singleton instance of the matcher. There is no need to construct + # multiple instance since there is no state. It also facilities the + # special case logic we need for some of these matchers, by making it + # easy to do comparisons like: `[klass::INSTANCE] == args` rather than + # `args.count == 1 && klass === args.first`. + # + # @private + class SingletonMatcher + private_class_method :new + + def self.inherited(subklass) + subklass.const_set(:INSTANCE, subklass.send(:new)) + end + end + + # @private + class AnyArgsMatcher < SingletonMatcher + def description + "*(any args)" + end + end + + # @private + class AnyArgMatcher < SingletonMatcher + def ===(_other) + true + end + + def description + "anything" + end + end + + # @private + class NoArgsMatcher < SingletonMatcher + def description + "no args" + end + end + + # @private + class BooleanMatcher < SingletonMatcher + def ===(value) + true == value || false == value + end + + def description + "boolean" + end + end + + # @private + class BaseHashMatcher + def initialize(expected) + @expected = expected + end + + def ===(predicate, actual) + @expected.__send__(predicate) do |k, v| + actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k]) + end + rescue NoMethodError + false + end + + def description(name) + "#{name}(#{formatted_expected_hash.inspect.sub(/^\{/, "").sub(/\}$/, "")})" + end + + private + + def formatted_expected_hash + Hash[ + @expected.map do |k, v| + k = RSpec::Support.rspec_description_for_object(k) + v = RSpec::Support.rspec_description_for_object(v) + + [k, v] + end + ] + end + end + + # @private + class HashIncludingMatcher < BaseHashMatcher + def ===(actual) + super(:all?, actual) + end + + def description + super("hash_including") + end + end + + # @private + class HashExcludingMatcher < BaseHashMatcher + def ===(actual) + super(:none?, actual) + end + + def description + super("hash_not_including") + end + end + + # @private + class ArrayIncludingMatcher + def initialize(expected) + @expected = expected + end + + def ===(actual) + actual = actual.uniq + @expected.uniq.all? do |expected_element| + actual.any? do |actual_element| + RSpec::Support::FuzzyMatcher.values_match?(expected_element, actual_element) + end + end + rescue NoMethodError + false + end + + def description + "array_including(#{formatted_expected_values})" + end + + private + + def formatted_expected_values + @expected.map do |x| + RSpec::Support.rspec_description_for_object(x) + end.join(", ") + end + end + + # @private + class DuckTypeMatcher + def initialize(*methods_to_respond_to) + @methods_to_respond_to = methods_to_respond_to + end + + def ===(value) + @methods_to_respond_to.all? { |message| value.respond_to?(message) } + end + + def description + "duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})" + end + end + + # @private + class InstanceOf + def initialize(klass) + @klass = klass + end + + def ===(actual) + actual.instance_of?(@klass) + end + + def description + "an_instance_of(#{@klass.name})" + end + end + + # @private + class KindOf + def initialize(klass) + @klass = klass + end + + def ===(actual) + actual.kind_of?(@klass) + end + + def description + "kind of #{@klass.name}" + end + end + + matcher_namespace = name + '::' + ::RSpec::Support.register_matcher_definition do |object| + # This is the best we have for now. We should tag all of our matchers + # with a module or something so we can test for it directly. + # + # (Note Module#parent in ActiveSupport is defined in a similar way.) + begin + object.class.name.include?(matcher_namespace) + rescue NoMethodError + # Some objects, like BasicObject, don't implemented standard + # reflection methods. + false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/configuration.rb new file mode 100644 index 0000000000..5962215f80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/configuration.rb @@ -0,0 +1,212 @@ +module RSpec + module Mocks + # Provides configuration options for rspec-mocks. + class Configuration + def initialize + @allow_message_expectations_on_nil = nil + @yield_receiver_to_any_instance_implementation_blocks = true + @verify_doubled_constant_names = false + @transfer_nested_constants = false + @verify_partial_doubles = false + @temporarily_suppress_partial_double_verification = false + @color = false + end + + # Sets whether RSpec will warn, ignore, or fail a test when + # expectations are set on nil. + # By default, when this flag is not set, warning messages are issued when + # expectations are set on nil. This is to prevent false-positives and to + # catch potential bugs early on. + # When set to `true`, warning messages are suppressed. + # When set to `false`, it will raise an error. + # + # @example + # RSpec.configure do |config| + # config.mock_with :rspec do |mocks| + # mocks.allow_message_expectations_on_nil = false + # end + # end + attr_accessor :allow_message_expectations_on_nil + + def yield_receiver_to_any_instance_implementation_blocks? + @yield_receiver_to_any_instance_implementation_blocks + end + + # Sets whether or not RSpec will yield the receiving instance of a + # message to blocks that are used for any_instance stub implementations. + # When set, the first yielded argument will be the receiving instance. + # Defaults to `true`. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.yield_receiver_to_any_instance_implementation_blocks = false + # end + # end + attr_writer :yield_receiver_to_any_instance_implementation_blocks + + # Adds `stub` and `should_receive` to the given + # modules or classes. This is usually only necessary + # if you application uses some proxy classes that + # "strip themselves down" to a bare minimum set of + # methods and remove `stub` and `should_receive` in + # the process. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.add_stub_and_should_receive_to Delegator + # end + # end + # + def add_stub_and_should_receive_to(*modules) + modules.each do |mod| + Syntax.enable_should(mod) + end + end + + # Provides the ability to set either `expect`, + # `should` or both syntaxes. RSpec uses `expect` + # syntax by default. This is needed if you want to + # explicitly enable `should` syntax and/or explicitly + # disable `expect` syntax. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.syntax = [:expect, :should] + # end + # end + # + def syntax=(*values) + syntaxes = values.flatten + if syntaxes.include?(:expect) + Syntax.enable_expect + else + Syntax.disable_expect + end + + if syntaxes.include?(:should) + Syntax.enable_should + else + Syntax.disable_should + end + end + + # Returns an array with a list of syntaxes + # that are enabled. + # + # @example + # unless RSpec::Mocks.configuration.syntax.include?(:expect) + # raise "this RSpec extension gem requires the rspec-mocks `:expect` syntax" + # end + # + def syntax + syntaxes = [] + syntaxes << :should if Syntax.should_enabled? + syntaxes << :expect if Syntax.expect_enabled? + syntaxes + end + + def verify_doubled_constant_names? + !!@verify_doubled_constant_names + end + + # When this is set to true, an error will be raised when + # `instance_double` or `class_double` is given the name of an undefined + # constant. You probably only want to set this when running your entire + # test suite, with all production code loaded. Setting this for an + # isolated unit test will prevent you from being able to isolate it! + attr_writer :verify_doubled_constant_names + + # Provides a way to perform customisations when verifying doubles. + # + # @example + # RSpec::Mocks.configuration.before_verifying_doubles do |ref| + # ref.some_method! + # end + def before_verifying_doubles(&block) + verifying_double_callbacks << block + end + alias :when_declaring_verifying_double :before_verifying_doubles + + # @api private + # Returns an array of blocks to call when verifying doubles + def verifying_double_callbacks + @verifying_double_callbacks ||= [] + end + + def transfer_nested_constants? + !!@transfer_nested_constants + end + + # Sets the default for the `transfer_nested_constants` option when + # stubbing constants. + attr_writer :transfer_nested_constants + + # When set to true, partial mocks will be verified the same as object + # doubles. Any stubs will have their arguments checked against the original + # method, and methods that do not exist cannot be stubbed. + def verify_partial_doubles=(val) + @verify_partial_doubles = !!val + end + + def verify_partial_doubles? + @verify_partial_doubles + end + + # @private + # Used to track wether we are temporarily suppressing verifying partial + # doubles with `without_partial_double_verification { ... }` + attr_accessor :temporarily_suppress_partial_double_verification + + if ::RSpec.respond_to?(:configuration) + def color? + ::RSpec.configuration.color_enabled? + end + else + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + attr_writer :color + + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + def color? + @color + end + end + + # Monkey-patch `Marshal.dump` to enable dumping of mocked or stubbed + # objects. By default this will not work since RSpec mocks works by + # adding singleton methods that cannot be serialized. This patch removes + # these singleton methods before serialization. Setting to falsey removes + # the patch. + # + # This method is idempotent. + def patch_marshal_to_support_partial_doubles=(val) + if val + RSpec::Mocks::MarshalExtension.patch! + else + RSpec::Mocks::MarshalExtension.unpatch! + end + end + + # @api private + # Resets the configured syntax to the default. + def reset_syntaxes_to_default + self.syntax = [:should, :expect] + RSpec::Mocks::Syntax.warn_about_should! + end + end + + # Mocks specific configuration, as distinct from `RSpec.configuration` + # which is core RSpec configuration. + def self.configuration + @configuration ||= Configuration.new + end + + configuration.reset_syntaxes_to_default + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/error_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/error_generator.rb new file mode 100644 index 0000000000..9bf0984f31 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/error_generator.rb @@ -0,0 +1,369 @@ +RSpec::Support.require_rspec_support "object_formatter" + +module RSpec + module Mocks + # Raised when a message expectation is not satisfied. + MockExpectationError = Class.new(Exception) + + # Raised when a test double is used after it has been torn + # down (typically at the end of an rspec-core example). + ExpiredTestDoubleError = Class.new(MockExpectationError) + + # Raised when doubles or partial doubles are used outside of the per-test lifecycle. + OutsideOfExampleError = Class.new(StandardError) + + # Raised when an expectation customization method (e.g. `with`, + # `and_return`) is called on a message expectation which has already been + # invoked. + MockExpectationAlreadyInvokedError = Class.new(Exception) + + # Raised for situations that RSpec cannot support due to mutations made + # externally on arguments that RSpec is holding onto to use for later + # comparisons. + # + # @deprecated We no longer raise this error but the constant remains until + # RSpec 4 for SemVer reasons. + CannotSupportArgMutationsError = Class.new(StandardError) + + # @private + UnsupportedMatcherError = Class.new(StandardError) + # @private + NegationUnsupportedError = Class.new(StandardError) + # @private + VerifyingDoubleNotDefinedError = Class.new(StandardError) + + # @private + class ErrorGenerator + attr_writer :opts + + def initialize(target=nil) + @target = target + end + + # @private + def opts + @opts ||= {} + end + + # @private + def raise_unexpected_message_error(message, args) + __raise "#{intro} received unexpected message :#{message} with #{format_args(args)}" + end + + # @private + def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil) + __raise error_message(expectation, args_for_multiple_calls), nil, source_id + end + + # @private + def raise_missing_default_stub_error(expectation, args_for_multiple_calls) + __raise( + error_message(expectation, args_for_multiple_calls) + + "\n Please stub a default value first if message might be received with other args as well. \n" + ) + end + + # @private + def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil) + __raise error_message(expectation, args_for_multiple_calls), backtrace_line + end + + def default_error_message(expectation, expected_args, actual_args) + "#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}".dup + end + + # rubocop:disable Metrics/ParameterLists + # @private + def raise_expectation_error(message, expected_received_count, argument_list_matcher, + actual_received_count, expectation_count_type, args, + backtrace_line=nil, source_id=nil) + expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher) + received_part = received_part_of_expectation_error(actual_received_count, args) + __raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id + end + # rubocop:enable Metrics/ParameterLists + + # @private + def raise_unimplemented_error(doubled_module, method_name, object) + message = case object + when InstanceVerifyingDouble + "the %s class does not implement the instance method: %s".dup << + if ObjectMethodReference.for(doubled_module, method_name).implemented? + ". Perhaps you meant to use `class_double` instead?" + else + "" + end + when ClassVerifyingDouble + "the %s class does not implement the class method: %s".dup << + if InstanceMethodReference.for(doubled_module, method_name).implemented? + ". Perhaps you meant to use `instance_double` instead?" + else + "" + end + else + "%s does not implement: %s" + end + + __raise message % [doubled_module.description, method_name] + end + + # @private + def raise_non_public_error(method_name, visibility) + raise NoMethodError, "%s method `%s' called on %s" % [ + visibility, method_name, intro + ] + end + + # @private + def raise_invalid_arguments_error(verifier) + __raise verifier.error_message + end + + # @private + def raise_expired_test_double_error + raise ExpiredTestDoubleError, + "#{intro} was originally created in one example but has leaked into " \ + "another example and can no longer be used. rspec-mocks' doubles are " \ + "designed to only last for one example, and you need to create a new " \ + "one in each example you wish to use it for." + end + + # @private + def describe_expectation(verb, message, expected_received_count, _actual_received_count, args) + "#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}" + end + + # @private + def raise_out_of_order_error(message) + __raise "#{intro} received :#{message} out of order" + end + + # @private + def raise_missing_block_error(args_to_yield) + __raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed" + end + + # @private + def raise_wrong_arity_error(args_to_yield, signature) + __raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}" + end + + # @private + def raise_only_valid_on_a_partial_double(method) + __raise "#{intro} is a pure test double. `#{method}` is only " \ + "available on a partial double." + end + + # @private + def raise_expectation_on_unstubbed_method(method) + __raise "#{intro} expected to have received #{method}, but that " \ + "object is not a spy or method has not been stubbed." + end + + # @private + def raise_expectation_on_mocked_method(method) + __raise "#{intro} expected to have received #{method}, but that " \ + "method has been mocked instead of stubbed or spied." + end + + # @private + def raise_double_negation_error(wrapped_expression) + __raise "Isn't life confusing enough? You've already set a " \ + "negative message expectation and now you are trying to " \ + "negate it again with `never`. What does an expression like " \ + "`#{wrapped_expression}.not_to receive(:msg).never` even mean?" + end + + # @private + def raise_verifying_double_not_defined_error(ref) + notify(VerifyingDoubleNotDefinedError.new( + "#{ref.description.inspect} is not a defined constant. " \ + "Perhaps you misspelt it? " \ + "Disable check with `verify_doubled_constant_names` configuration option." + )) + end + + # @private + def raise_have_received_disallowed(type, reason) + __raise "Using #{type}(...) with the `have_received` " \ + "matcher is not supported#{reason}." + end + + # @private + def raise_cant_constrain_count_for_negated_have_received_error(count_constraint) + __raise "can't use #{count_constraint} when negative" + end + + # @private + def raise_method_not_stubbed_error(method_name) + __raise "The method `#{method_name}` was not stubbed or was already unstubbed" + end + + # @private + def raise_already_invoked_error(message, calling_customization) + error_message = "The message expectation for #{intro}.#{message} has already been invoked " \ + "and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \ + "customizations must be applied before it is used for the first time." + + notify MockExpectationAlreadyInvokedError.new(error_message) + end + + def raise_expectation_on_nil_error(method_name) + __raise expectation_on_nil_message(method_name) + end + + def expectation_on_nil_message(method_name) + "An expectation of `:#{method_name}` was set on `nil`. " \ + "To allow expectations on `nil` and suppress this message, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `true`. " \ + "To disallow expectations on `nil`, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `false`" + end + + # @private + def intro(unwrapped=false) + case @target + when TestDouble then TestDoubleFormatter.format(@target, unwrapped) + when Class then + formatted = "#{@target.inspect} (class)" + return formatted if unwrapped + "#<#{formatted}>" + when NilClass then "nil" + else @target.inspect + end + end + + # @private + def method_call_args_description(args, generic_prefix=" with arguments: ", matcher_prefix=" with ") + case args.first + when ArgumentMatchers::AnyArgsMatcher then "#{matcher_prefix}any arguments" + when ArgumentMatchers::NoArgsMatcher then "#{matcher_prefix}no arguments" + else + if yield + "#{generic_prefix}#{format_args(args)}" + else + "" + end + end + end + + private + + def received_part_of_expectation_error(actual_received_count, args) + "received: #{count_message(actual_received_count)}" + + method_call_args_description(args) do + actual_received_count > 0 && args.length > 0 + end + end + + def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher) + "expected: #{count_message(expected_received_count, expectation_count_type)}" + + method_call_args_description(argument_list_matcher.expected_args) do + argument_list_matcher.expected_args.length > 0 + end + end + + def unexpected_arguments_message(expected_args_string, actual_args_string) + "with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}" + end + + def error_message(expectation, args_for_multiple_calls) + expected_args = format_args(expectation.expected_args) + actual_args = format_received_args(args_for_multiple_calls) + message = default_error_message(expectation, expected_args, actual_args) + + if args_for_multiple_calls.one? + diff = diff_message(expectation.expected_args, args_for_multiple_calls.first) + message << "\nDiff:#{diff}" unless diff.strip.empty? + end + + message + end + + def diff_message(expected_args, actual_args) + formatted_expected_args = expected_args.map do |x| + RSpec::Support.rspec_description_for_object(x) + end + + formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args) + + differ.diff(actual_args, formatted_expected_args) + end + + def unpack_string_args(formatted_expected_args, actual_args) + if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) } + [formatted_expected_args.first, actual_args.first] + else + [formatted_expected_args, actual_args] + end + end + + def list_of_exactly_one_string?(args) + Array === args && args.count == 1 && String === args.first + end + + def differ + RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?) + end + + def __raise(message, backtrace_line=nil, source_id=nil) + message = opts[:message] unless opts[:message].nil? + exception = RSpec::Mocks::MockExpectationError.new(message) + prepend_to_backtrace(exception, backtrace_line) if backtrace_line + notify exception, :source_id => source_id + end + + if RSpec::Support::Ruby.jruby? + def prepend_to_backtrace(exception, line) + raise exception + rescue RSpec::Mocks::MockExpectationError => with_backtrace + with_backtrace.backtrace.unshift(line) + end + else + def prepend_to_backtrace(exception, line) + exception.set_backtrace(caller.unshift line) + end + end + + def notify(*args) + RSpec::Support.notify_failure(*args) + end + + def format_args(args) + return "(no args)" if args.empty? + "(#{arg_list(args)})" + end + + def arg_list(args) + args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ") + end + + def format_received_args(args_for_multiple_calls) + grouped_args(args_for_multiple_calls).map do |args_for_one_call, index| + "#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}" + end.join("\n ") + end + + def count_message(count, expectation_count_type=nil) + return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least + return "at most #{times(count)}" if expectation_count_type == :at_most + times(count) + end + + def times(count) + "#{count} time#{count == 1 ? '' : 's'}" + end + + def grouped_args(args) + Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }] + end + + def group_count(index, args) + " (#{times(index)})" if args.size > 1 || index > 1 + end + end + + # @private + def self.error_generator + @error_generator ||= ErrorGenerator.new + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/example_methods.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/example_methods.rb new file mode 100644 index 0000000000..5531b28bf0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/example_methods.rb @@ -0,0 +1,434 @@ +RSpec::Support.require_rspec_mocks 'object_reference' + +module RSpec + module Mocks + # Contains methods intended to be used from within code examples. + # Mix this in to your test context (such as a test framework base class) + # to use rspec-mocks with your test framework. If you're using rspec-core, + # it'll take care of doing this for you. + module ExampleMethods + include RSpec::Mocks::ArgumentMatchers + + # @overload double() + # @overload double(name) + # @param name [String/Symbol] name or description to be used in failure messages + # @overload double(stubs) + # @param stubs (Hash) hash of message/return-value pairs + # @overload double(name, stubs) + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs (Hash) hash of message/return-value pairs + # @return (Double) + # + # Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured + # with an optional name, used for reporting in failure messages, and an optional + # hash of message/return-value pairs. + # + # @example + # book = double("book", :title => "The RSpec Book") + # book.title #=> "The RSpec Book" + # + # card = double("card", :suit => "Spades", :rank => "A") + # card.suit #=> "Spades" + # card.rank #=> "A" + # + def double(*args) + ExampleMethods.declare_double(Double, *args) + end + + # @overload instance_double(doubled_class) + # @param doubled_class [String, Class] + # @overload instance_double(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload instance_double(doubled_class, stubs) + # @param doubled_class [String, Class] + # @param stubs [Hash] hash of message/return-value pairs + # @overload instance_double(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return InstanceVerifyingDouble + # + # Constructs a test double against a specific class. If the given class + # name has been loaded, only instance methods defined on the class are + # allowed to be stubbed. In all other ways it behaves like a + # [double](double). + def instance_double(doubled_class, *args) + ref = ObjectReference.for(doubled_class) + ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args) + end + + # @overload class_double(doubled_class) + # @param doubled_class [String, Module] + # @overload class_double(doubled_class, name) + # @param doubled_class [String, Module] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload class_double(doubled_class, stubs) + # @param doubled_class [String, Module] + # @param stubs [Hash] hash of message/return-value pairs + # @overload class_double(doubled_class, name, stubs) + # @param doubled_class [String, Module] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ClassVerifyingDouble + # + # Constructs a test double against a specific class. If the given class + # name has been loaded, only class methods defined on the class are + # allowed to be stubbed. In all other ways it behaves like a + # [double](double). + def class_double(doubled_class, *args) + ref = ObjectReference.for(doubled_class) + ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args) + end + + # @overload object_double(object_or_name) + # @param object_or_name [String, Object] + # @overload object_double(object_or_name, name) + # @param object_or_name [String, Object] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload object_double(object_or_name, stubs) + # @param object_or_name [String, Object] + # @param stubs [Hash] hash of message/return-value pairs + # @overload object_double(object_or_name, name, stubs) + # @param object_or_name [String, Object] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ObjectVerifyingDouble + # + # Constructs a test double against a specific object. Only the methods + # the object responds to are allowed to be stubbed. If a String argument + # is provided, it is assumed to reference a constant object which is used + # for verification. In all other ways it behaves like a [double](double). + def object_double(object_or_name, *args) + ref = ObjectReference.for(object_or_name, :allow_direct_object_refs) + ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args) + end + + # @overload spy() + # @overload spy(name) + # @param name [String/Symbol] name or description to be used in failure messages + # @overload spy(stubs) + # @param stubs (Hash) hash of message/return-value pairs + # @overload spy(name, stubs) + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs (Hash) hash of message/return-value pairs + # @return (Double) + # + # Constructs a test double that is optimized for use with + # `have_received`. With a normal double one has to stub methods in order + # to be able to spy them. A spy automatically spies on all methods. + def spy(*args) + double(*args).as_null_object + end + + # @overload instance_spy(doubled_class) + # @param doubled_class [String, Class] + # @overload instance_spy(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload instance_spy(doubled_class, stubs) + # @param doubled_class [String, Class] + # @param stubs [Hash] hash of message/return-value pairs + # @overload instance_spy(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return InstanceVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific class. If the given class name has been loaded, only + # instance methods defined on the class are allowed to be stubbed. With + # a normal double one has to stub methods in order to be able to spy + # them. An instance_spy automatically spies on all instance methods to + # which the class responds. + def instance_spy(*args) + instance_double(*args).as_null_object + end + + # @overload object_spy(object_or_name) + # @param object_or_name [String, Object] + # @overload object_spy(object_or_name, name) + # @param object_or_name [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload object_spy(object_or_name, stubs) + # @param object_or_name [String, Object] + # @param stubs [Hash] hash of message/return-value pairs + # @overload object_spy(object_or_name, name, stubs) + # @param object_or_name [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ObjectVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific object. Only instance methods defined on the object + # are allowed to be stubbed. With a normal double one has to stub + # methods in order to be able to spy them. An object_spy automatically + # spies on all methods to which the object responds. + def object_spy(*args) + object_double(*args).as_null_object + end + + # @overload class_spy(doubled_class) + # @param doubled_class [String, Module] + # @overload class_spy(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload class_spy(doubled_class, stubs) + # @param doubled_class [String, Module] + # @param stubs [Hash] hash of message/return-value pairs + # @overload class_spy(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ClassVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific class. If the given class name has been loaded, + # only class methods defined on the class are allowed to be stubbed. + # With a normal double one has to stub methods in order to be able to spy + # them. An class_spy automatically spies on all class methods to which the + # class responds. + def class_spy(*args) + class_double(*args).as_null_object + end + + # Disables warning messages about expectations being set on nil. + # + # By default warning messages are issued when expectations are set on + # nil. This is to prevent false-positives and to catch potential bugs + # early on. + # @deprecated Use {RSpec::Mocks::Configuration#allow_message_expectations_on_nil} instead. + def allow_message_expectations_on_nil + RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false + end + + # Stubs the named constant with the given value. + # Like method stubs, the constant will be restored + # to its original value (or lack of one, if it was + # undefined) when the example completes. + # + # @param constant_name [String] The fully qualified name of the constant. The current + # constant scoping at the point of call is not considered. + # @param value [Object] The value to make the constant refer to. When the + # example completes, the constant will be restored to its prior state. + # @param options [Hash] Stubbing options. + # @option options :transfer_nested_constants [Boolean, Array] Determines + # what nested constants, if any, will be transferred from the original value + # of the constant to the new value of the constant. This only works if both + # the original and new values are modules (or classes). + # @return [Object] the stubbed value of the constant + # + # @example + # stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object. + # stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5. + # + # class CardDeck + # SUITS = [:Spades, :Diamonds, :Clubs, :Hearts] + # NUM_CARDS = 52 + # end + # + # stub_const("CardDeck", Class.new) + # CardDeck::SUITS # => uninitialized constant error + # CardDeck::NUM_CARDS # => uninitialized constant error + # + # stub_const("CardDeck", Class.new, :transfer_nested_constants => true) + # CardDeck::SUITS # => our suits array + # CardDeck::NUM_CARDS # => 52 + # + # stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS]) + # CardDeck::SUITS # => our suits array + # CardDeck::NUM_CARDS # => uninitialized constant error + def stub_const(constant_name, value, options={}) + ConstantMutator.stub(constant_name, value, options) + end + + # Hides the named constant with the given value. The constant will be + # undefined for the duration of the test. + # + # Like method stubs, the constant will be restored to its original value + # when the example completes. + # + # @param constant_name [String] The fully qualified name of the constant. + # The current constant scoping at the point of call is not considered. + # + # @example + # hide_const("MyClass") # => MyClass is now an undefined constant + def hide_const(constant_name) + ConstantMutator.hide(constant_name) + end + + # Verifies that the given object received the expected message during the + # course of the test. On a spy objects or as null object doubles this + # works for any method, on other objects the method must have + # been stubbed beforehand in order for messages to be verified. + # + # Stubbing and verifying messages received in this way implements the + # Test Spy pattern. + # + # @param method_name [Symbol] name of the method expected to have been + # called. + # + # @example + # invitation = double('invitation', accept: true) + # user.accept_invitation(invitation) + # expect(invitation).to have_received(:accept) + # + # # You can also use most message expectations: + # expect(invitation).to have_received(:accept).with(mailer).once + # + # @note `have_received(...).with(...)` is unable to work properly when + # passed arguments are mutated after the spy records the received message. + def have_received(method_name, &block) + Matchers::HaveReceived.new(method_name, &block) + end + + # Turns off the verifying of partial doubles for the duration of the + # block, this is useful in situations where methods are defined at run + # time and you wish to define stubs for them but not turn off partial + # doubles for the entire run suite. (e.g. view specs in rspec-rails). + def without_partial_double_verification + original_state = Mocks.configuration.temporarily_suppress_partial_double_verification + Mocks.configuration.temporarily_suppress_partial_double_verification = true + yield + ensure + Mocks.configuration.temporarily_suppress_partial_double_verification = original_state + end + + # @method expect + # Used to wrap an object in preparation for setting a mock expectation + # on it. + # + # @example + # expect(obj).to receive(:foo).with(5).and_return(:return_value) + # + # @note This method is usually provided by rspec-expectations. However, + # if you use rspec-mocks without rspec-expectations, there's a definition + # of it that is made available here. If you disable the `:expect` syntax + # this method will be undefined. + + # @method allow + # Used to wrap an object in preparation for stubbing a method + # on it. + # + # @example + # allow(dbl).to receive(:foo).with(5).and_return(:return_value) + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method expect_any_instance_of + # Used to wrap a class in preparation for setting a mock expectation + # on instances of it. + # + # @example + # expect_any_instance_of(MyClass).to receive(:foo) + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method allow_any_instance_of + # Used to wrap a class in preparation for stubbing a method + # on instances of it. + # + # @example + # allow_any_instance_of(MyClass).to receive(:foo) + # + # @note This is only available when you have enabled the `expect` syntax. + + # @method receive + # Used to specify a message that you expect or allow an object + # to receive. The object returned by `receive` supports the same + # fluent interface that `should_receive` and `stub` have always + # supported, allowing you to constrain the arguments or number of + # times, and configure how the object should respond to the message. + # + # @example + # expect(obj).to receive(:hello).with("world").exactly(3).times + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method receive_messages + # Shorthand syntax used to setup message(s), and their return value(s), + # that you expect or allow an object to receive. The method takes a hash + # of messages and their respective return values. Unlike with `receive`, + # you cannot apply further customizations using a block or the fluent + # interface. + # + # @example + # allow(obj).to receive_messages(:speak => "Hello World") + # allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow") + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method receive_message_chain + # @overload receive_message_chain(method1, method2) + # @overload receive_message_chain("method1.method2") + # @overload receive_message_chain(method1, method_to_value_hash) + # + # stubs/mocks a chain of messages on an object or test double. + # + # ## Warning: + # + # Chains can be arbitrarily long, which makes it quite painless to + # violate the Law of Demeter in violent ways, so you should consider any + # use of `receive_message_chain` a code smell. Even though not all code smells + # indicate real problems (think fluent interfaces), `receive_message_chain` still + # results in brittle examples. For example, if you write + # `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the + # implementation calls `foo.baz.bar`, the stub will not work. + # + # @example + # allow(double).to receive_message_chain("foo.bar") { :baz } + # allow(double).to receive_message_chain(:foo, :bar => :baz) + # allow(double).to receive_message_chain(:foo, :bar) { :baz } + # + # # Given any of ^^ these three forms ^^: + # double.foo.bar # => :baz + # + # # Common use in Rails/ActiveRecord: + # allow(Article).to receive_message_chain("recent.published") { [Article.new] } + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @private + def self.included(klass) + klass.class_exec do + # This gets mixed in so that if `RSpec::Matchers` is included in + # `klass` later, its definition of `expect` will take precedence. + include ExpectHost unless method_defined?(:expect) + end + end + + # @private + def self.extended(object) + # This gets extended in so that if `RSpec::Matchers` is included in + # `klass` later, its definition of `expect` will take precedence. + object.extend ExpectHost unless object.respond_to?(:expect) + end + + # @private + def self.declare_verifying_double(type, ref, *args) + if RSpec::Mocks.configuration.verify_doubled_constant_names? && + !ref.defined? + + RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref) + end + + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call(ref) + end + + declare_double(type, ref, *args) + end + + # @private + def self.declare_double(type, *args) + args << {} unless Hash === args.last + type.new(*args) + end + + # This module exists to host the `expect` method for cases where + # rspec-mocks is used w/o rspec-expectations. + module ExpectHost + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/instance_method_stasher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/instance_method_stasher.rb new file mode 100644 index 0000000000..12edec2fae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/instance_method_stasher.rb @@ -0,0 +1,146 @@ +module RSpec + module Mocks + # @private + class InstanceMethodStasher + def initialize(object, method) + @object = object + @method = method + @klass = (class << object; self; end) + + @original_method = nil + @method_is_stashed = false + end + + attr_reader :original_method + + if RUBY_VERSION.to_f < 1.9 + # @private + def method_is_stashed? + @method_is_stashed + end + + # @private + def stash + return if !method_defined_directly_on_klass? || @method_is_stashed + + @klass.__send__(:alias_method, stashed_method_name, @method) + @method_is_stashed = true + end + + # @private + def stashed_method_name + "obfuscated_by_rspec_mocks__#{@method}" + end + + # @private + def restore + return unless @method_is_stashed + + if @klass.__send__(:method_defined?, @method) + @klass.__send__(:undef_method, @method) + end + @klass.__send__(:alias_method, @method, stashed_method_name) + @klass.__send__(:remove_method, stashed_method_name) + @method_is_stashed = false + end + else + + # @private + def method_is_stashed? + !!@original_method + end + + # @private + def stash + return unless method_defined_directly_on_klass? + @original_method ||= ::RSpec::Support.method_handle_for(@object, @method) + @klass.__send__(:undef_method, @method) + end + + # @private + def restore + return unless @original_method + + if @klass.__send__(:method_defined?, @method) + @klass.__send__(:undef_method, @method) + end + + handle_restoration_failures do + @klass.__send__(:define_method, @method, @original_method) + end + + @original_method = nil + end + end + + if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195') + # ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(. + # https://bugs.ruby-lang.org/issues/8686 + def handle_restoration_failures + yield + rescue TypeError + RSpec.warn_with( + "RSpec failed to properly restore a partial double (#{@object.inspect}) " \ + "to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \ + "(https://bugs.ruby-lang.org/issues/8686). This object may remain " \ + "screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.", + :call_site => nil, :use_spec_location_as_call_site => true + ) + end + else + def handle_restoration_failures + # No known reasons for restoration to fail on other rubies. + yield + end + end + + private + + # @private + def method_defined_directly_on_klass? + method_defined_on_klass? && method_owned_by_klass? + end + + # @private + def method_defined_on_klass?(klass=@klass) + MethodReference.method_defined_at_any_visibility?(klass, @method) + end + + def method_owned_by_klass? + owner = @klass.instance_method(@method).owner + + # On Ruby 2.0.0+ the owner of a method on a class which has been + # `prepend`ed may actually be an instance, e.g. + # `#`, rather than the expected `MyClass`. + owner = owner.class unless Module === owner + + # On some 1.9s (e.g. rubinius) aliased methods + # can report the wrong owner. Example: + # class MyClass + # class << self + # alias alternate_new new + # end + # end + # + # MyClass.owner(:alternate_new) returns `Class` when incorrect, + # but we need to consider the owner to be `MyClass` because + # it is not actually available on `Class` but is on `MyClass`. + # Hence, we verify that the owner actually has the method defined. + # If the given owner does not have the method defined, we assume + # that the method is actually owned by @klass. + # + # On 1.8, aliased methods can also report the wrong owner. Example: + # module M + # def a; end + # module_function :a + # alias b a + # module_function :b + # end + # The owner of M.b is the raw Module object, instead of the expected + # singleton class of the module + return true if RUBY_VERSION < '1.9' && owner == @object + owner == @klass || !(method_defined_on_klass?(owner)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/marshal_extension.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/marshal_extension.rb new file mode 100644 index 0000000000..cfa9c1a7b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/marshal_extension.rb @@ -0,0 +1,41 @@ +module RSpec + module Mocks + # Support for `patch_marshal_to_support_partial_doubles` configuration. + # + # @private + class MarshalExtension + def self.patch! + return if Marshal.respond_to?(:dump_with_rspec_mocks) + + Marshal.instance_eval do + class << self + def dump_with_rspec_mocks(object, *rest) + if !::RSpec::Mocks.space.registered?(object) || NilClass === object + dump_without_rspec_mocks(object, *rest) + else + dump_without_rspec_mocks(object.dup, *rest) + end + end + + alias_method :dump_without_rspec_mocks, :dump + undef_method :dump + alias_method :dump, :dump_with_rspec_mocks + end + end + end + + def self.unpatch! + return unless Marshal.respond_to?(:dump_with_rspec_mocks) + + Marshal.instance_eval do + class << self + undef_method :dump_with_rspec_mocks + undef_method :dump + alias_method :dump, :dump_without_rspec_mocks + undef_method :dump_without_rspec_mocks + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/expectation_customization.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/expectation_customization.rb new file mode 100644 index 0000000000..81e6427977 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/expectation_customization.rb @@ -0,0 +1,20 @@ +module RSpec + module Mocks + module Matchers + # @private + class ExpectationCustomization + attr_accessor :block + + def initialize(method_name, args, block) + @method_name = method_name + @args = args + @block = block + end + + def playback_onto(expectation) + expectation.__send__(@method_name, *@args, &@block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/have_received.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/have_received.rb new file mode 100644 index 0000000000..dce6974f9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/have_received.rb @@ -0,0 +1,134 @@ +module RSpec + module Mocks + module Matchers + # @private + class HaveReceived + include Matcher + + COUNT_CONSTRAINTS = %w[exactly at_least at_most times time once twice thrice] + ARGS_CONSTRAINTS = %w[with] + CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w[ordered] + + def initialize(method_name, &block) + @method_name = method_name + @block = block + @constraints = [] + @subject = nil + end + + def name + "have_received" + end + + def matches?(subject, &block) + @block ||= block + @subject = subject + @expectation = expect + mock_proxy.ensure_implemented(@method_name) + + expected_messages_received_in_order? + end + + def does_not_match?(subject) + @subject = subject + ensure_count_unconstrained + @expectation = expect.never + mock_proxy.ensure_implemented(@method_name) + expected_messages_received_in_order? + end + + def failure_message + capture_failure_message + end + + def failure_message_when_negated + capture_failure_message + end + + def description + (@expectation ||= expect).description_for("have received") + end + + CONSTRAINTS.each do |expectation| + define_method expectation do |*args| + @constraints << [expectation, *args] + self + end + end + + def setup_expectation(subject, &block) + notify_failure_message unless matches?(subject, &block) + end + + def setup_negative_expectation(subject, &block) + notify_failure_message unless does_not_match?(subject, &block) + end + + def setup_allowance(_subject, &_block) + disallow("allow", " as it would have no effect") + end + + def setup_any_instance_allowance(_subject, &_block) + disallow("allow_any_instance_of") + end + + def setup_any_instance_expectation(_subject, &_block) + disallow("expect_any_instance_of") + end + + def setup_any_instance_negative_expectation(_subject, &_block) + disallow("expect_any_instance_of") + end + + private + + def disallow(type, reason="") + RSpec::Mocks.error_generator.raise_have_received_disallowed(type, reason) + end + + def expect + expectation = mock_proxy.build_expectation(@method_name) + apply_constraints_to expectation + expectation + end + + def apply_constraints_to(expectation) + @constraints.each do |constraint| + expectation.send(*constraint) + end + end + + def ensure_count_unconstrained + return unless count_constraint + RSpec::Mocks.error_generator.raise_cant_constrain_count_for_negated_have_received_error(count_constraint) + end + + def count_constraint + @constraints.map(&:first).find do |constraint| + COUNT_CONSTRAINTS.include?(constraint) + end + end + + def capture_failure_message + RSpec::Support.with_failure_notifier(Proc.new { |err, _opt| return err.message }) do + notify_failure_message + end + end + + def notify_failure_message + mock_proxy.check_for_unexpected_arguments(@expectation) + @expectation.generate_error + end + + def expected_messages_received_in_order? + mock_proxy.replay_received_message_on @expectation, &@block + @expectation.expected_messages_received? && @expectation.ensure_expected_ordering_received! + end + + def mock_proxy + RSpec::Mocks.space.proxy_for(@subject) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive.rb new file mode 100644 index 0000000000..980a01dde7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive.rb @@ -0,0 +1,132 @@ +RSpec::Support.require_rspec_mocks 'matchers/expectation_customization' + +module RSpec + module Mocks + module Matchers + # @private + class Receive + include Matcher + + def initialize(message, block) + @message = message + @block = block + @recorded_customizations = [] + end + + def name + "receive" + end + + def description + describable.description_for("receive") + end + + def setup_expectation(subject, &block) + warn_if_any_instance("expect", subject) + @describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block) + end + alias matches? setup_expectation + + def setup_negative_expectation(subject, &block) + # ensure `never` goes first for cases like `never.and_return(5)`, + # where `and_return` is meant to raise an error + @recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil) + + warn_if_any_instance("expect", subject) + + setup_expectation(subject, &block) + end + alias does_not_match? setup_negative_expectation + + def setup_allowance(subject, &block) + warn_if_any_instance("allow", subject) + setup_mock_proxy_method_substitute(subject, :add_stub, block) + end + + def setup_any_instance_expectation(subject, &block) + setup_any_instance_method_substitute(subject, :should_receive, block) + end + + def setup_any_instance_negative_expectation(subject, &block) + setup_any_instance_method_substitute(subject, :should_not_receive, block) + end + + def setup_any_instance_allowance(subject, &block) + setup_any_instance_method_substitute(subject, :stub, block) + end + + MessageExpectation.public_instance_methods(false).each do |method| + next if method_defined?(method) + + define_method(method) do |*args, &block| + @recorded_customizations << ExpectationCustomization.new(method, args, block) + self + end + end + + private + + def describable + @describable ||= DefaultDescribable.new(@message) + end + + def warn_if_any_instance(expression, subject) + return unless AnyInstance::Proxy === subject + + RSpec.warning( + "`#{expression}(#{subject.klass}.any_instance).to` " \ + "is probably not what you meant, it does not operate on " \ + "any instance of `#{subject.klass}`. " \ + "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead." + ) + end + + def setup_mock_proxy_method_substitute(subject, method, block) + proxy = ::RSpec::Mocks.space.proxy_for(subject) + setup_method_substitute(proxy, method, block) + end + + def setup_any_instance_method_substitute(subject, method, block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + setup_method_substitute(proxy, method, block) + end + + def setup_method_substitute(host, method, block, *args) + args << @message.to_sym + block = move_block_to_last_customization(block) + + expectation = host.__send__(method, *args, &(@block || block)) + + @recorded_customizations.each do |customization| + customization.playback_onto(expectation) + end + expectation + end + + def move_block_to_last_customization(block) + last = @recorded_customizations.last + return block unless last + + last.block ||= block + nil + end + + # MessageExpectation objects are able to describe themselves in detail. + # We use this as a fall back when a MessageExpectation is not available. + # @private + class DefaultDescribable + def initialize(message) + @message = message + end + + # This is much simpler for the `any_instance` case than what the + # user may want, but I'm not up for putting a bunch of effort + # into full descriptions for `any_instance` expectations at this point :(. + def description_for(verb) + "#{verb} #{@message}" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_message_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_message_chain.rb new file mode 100644 index 0000000000..583ecf70a6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_message_chain.rb @@ -0,0 +1,82 @@ +RSpec::Support.require_rspec_mocks 'matchers/expectation_customization' + +module RSpec + module Mocks + module Matchers + # @private + class ReceiveMessageChain + include Matcher + + def initialize(chain, &block) + @chain = chain + @block = block + @recorded_customizations = [] + end + + [:with, :and_return, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg| + define_method(msg) do |*args, &block| + @recorded_customizations << ExpectationCustomization.new(msg, args, block) + self + end + end + + def name + "receive_message_chain" + end + + def description + "receive message chain #{formatted_chain}" + end + + def setup_allowance(subject, &block) + chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_any_instance_allowance(subject, &block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + chain = proxy.stub_chain(*@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_any_instance_expectation(subject, &block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + chain = proxy.expect_chain(*@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_expectation(subject, &block) + chain = ExpectChain.expect_chain_on(subject, *@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_negative_expectation(*_args) + raise NegationUnsupportedError, + "`expect(...).not_to receive_message_chain` is not supported " \ + "since it doesn't really make sense. What would it even mean?" + end + + alias matches? setup_expectation + alias does_not_match? setup_negative_expectation + + private + + def replay_customizations(chain) + @recorded_customizations.each do |customization| + customization.playback_onto(chain) + end + end + + def formatted_chain + @formatted_chain ||= @chain.map do |part| + if Hash === part + part.keys.first.to_s + else + part.to_s + end + end.join(".") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_messages.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_messages.rb new file mode 100644 index 0000000000..218d238e34 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/matchers/receive_messages.rb @@ -0,0 +1,77 @@ +module RSpec + module Mocks + module Matchers + # @private + class ReceiveMessages + include Matcher + + def initialize(message_return_value_hash) + @message_return_value_hash = message_return_value_hash + @backtrace_line = CallerFilter.first_non_rspec_line + end + + def name + "receive_messages" + end + + def description + "receive messages: #{@message_return_value_hash.inspect}" + end + + def setup_expectation(subject) + warn_about_block if block_given? + each_message_on(proxy_on(subject)) do |host, message, return_value| + host.add_simple_expectation(message, return_value, @backtrace_line) + end + end + alias matches? setup_expectation + + def setup_negative_expectation(_subject) + raise NegationUnsupportedError, + "`expect(...).to_not receive_messages` is not supported since it " \ + "doesn't really make sense. What would it even mean?" + end + alias does_not_match? setup_negative_expectation + + def setup_allowance(subject) + warn_about_block if block_given? + each_message_on(proxy_on(subject)) do |host, message, return_value| + host.add_simple_stub(message, return_value) + end + end + + def setup_any_instance_expectation(subject) + warn_about_block if block_given? + each_message_on(any_instance_of(subject)) do |host, message, return_value| + host.should_receive(message).and_return(return_value) + end + end + + def setup_any_instance_allowance(subject) + warn_about_block if block_given? + any_instance_of(subject).stub(@message_return_value_hash) + end + + def warn_about_block + raise "Implementation blocks aren't supported with `receive_messages`" + end + + private + + def proxy_on(subject) + ::RSpec::Mocks.space.proxy_for(subject) + end + + def any_instance_of(subject) + ::RSpec::Mocks.space.any_instance_proxy_for(subject) + end + + def each_message_on(host) + @message_return_value_hash.each do |message, value| + yield host, message, value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_chain.rb new file mode 100644 index 0000000000..907d14b0e8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_chain.rb @@ -0,0 +1,87 @@ +module RSpec + module Mocks + # @private + class MessageChain + attr_reader :object, :chain, :block + + def initialize(object, *chain, &blk) + @object = object + @chain, @block = format_chain(*chain, &blk) + end + + # @api private + def setup_chain + if chain.length > 1 + if (matching_stub = find_matching_stub) + chain.shift + chain_on(matching_stub.invoke(nil), *chain, &@block) + elsif (matching_expectation = find_matching_expectation) + chain.shift + chain_on(matching_expectation.invoke_without_incrementing_received_count(nil), *chain, &@block) + else + next_in_chain = Double.new + expectation(object, chain.shift) { next_in_chain } + chain_on(next_in_chain, *chain, &@block) + end + else + expectation(object, chain.shift, &@block) + end + end + + private + + def chain_on(object, *chain, &block) + initialize(object, *chain, &block) + setup_chain + end + + def format_chain(*chain, &blk) + if Hash === chain.last + hash = chain.pop + hash.each do |k, v| + chain << k + blk = Proc.new { v } + end + end + return chain.join('.').split('.'), blk + end + + def find_matching_stub + ::RSpec::Mocks.space.proxy_for(object). + __send__(:find_matching_method_stub, chain.first.to_sym) + end + + def find_matching_expectation + ::RSpec::Mocks.space.proxy_for(object). + __send__(:find_matching_expectation, chain.first.to_sym) + end + end + + # @private + class ExpectChain < MessageChain + # @api private + def self.expect_chain_on(object, *chain, &blk) + new(object, *chain, &blk).setup_chain + end + + private + + def expectation(object, message, &return_block) + ::RSpec::Mocks.expect_message(object, message, {}, &return_block) + end + end + + # @private + class StubChain < MessageChain + def self.stub_chain_on(object, *chain, &blk) + new(object, *chain, &blk).setup_chain + end + + private + + def expectation(object, message, &return_block) + ::RSpec::Mocks.allow_message(object, message, {}, &return_block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_expectation.rb new file mode 100644 index 0000000000..cb5a39922e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/message_expectation.rb @@ -0,0 +1,751 @@ +RSpec::Support.require_rspec_support 'mutex' + +module RSpec + module Mocks + # A message expectation that only allows concrete return values to be set + # for a message. While this same effect can be achieved using a standard + # MessageExpectation, this version is much faster and so can be used as an + # optimization. + # + # @private + class SimpleMessageExpectation + def initialize(message, response, error_generator, backtrace_line=nil) + @message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line + @received = false + end + + def invoke(*_) + @received = true + @response + end + + def matches?(message, *_) + @message == message.to_sym + end + + def called_max_times? + false + end + + def verify_messages_received + return if @received + @error_generator.raise_expectation_error( + @message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line + ) + end + + def unadvise(_) + end + end + + # Represents an individual method stub or message expectation. The methods + # defined here can be used to configure how it behaves. The methods return + # `self` so that they can be chained together to form a fluent interface. + class MessageExpectation + # @!group Configuring Responses + + # @overload and_return(value) + # @overload and_return(first_value, second_value) + # + # Tells the object to return a value when it receives the message. Given + # more than one value, the first value is returned the first time the + # message is received, the second value is returned the next time, etc, + # etc. + # + # If the message is received more times than there are values, the last + # value is received for every subsequent call. + # + # @return [nil] No further chaining is supported after this. + # @example + # allow(counter).to receive(:count).and_return(1) + # counter.count # => 1 + # counter.count # => 1 + # + # allow(counter).to receive(:count).and_return(1,2,3) + # counter.count # => 1 + # counter.count # => 2 + # counter.count # => 3 + # counter.count # => 3 + # counter.count # => 3 + # # etc + def and_return(first_value, *values) + raise_already_invoked_error_if_necessary(__method__) + if negative? + raise "`and_return` is not supported with negative message expectations" + end + + if block_given? + raise ArgumentError, "Implementation blocks aren't supported with `and_return`" + end + + values.unshift(first_value) + @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least) + self.terminal_implementation_action = AndReturnImplementation.new(values) + + nil + end + + # Tells the object to delegate to the original unmodified method + # when it receives the message. + # + # @note This is only available on partial doubles. + # + # @return [nil] No further chaining is supported after this. + # @example + # expect(counter).to receive(:increment).and_call_original + # original_count = counter.count + # counter.increment + # expect(counter.count).to eq(original_count + 1) + def and_call_original + wrap_original(__method__) do |original, *args, &block| + original.call(*args, &block) + end + end + + # Decorates the stubbed method with the supplied block. The original + # unmodified method is passed to the block along with any method call + # arguments so you can delegate to it, whilst still being able to + # change what args are passed to it and/or change the return value. + # + # @note This is only available on partial doubles. + # + # @return [nil] No further chaining is supported after this. + # @example + # expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block| + # original_method.call(*args, &block).first(10) + # end + def and_wrap_original(&block) + wrap_original(__method__, &block) + end + + # @overload and_raise + # @overload and_raise(ExceptionClass) + # @overload and_raise(ExceptionClass, message) + # @overload and_raise(exception_instance) + # + # Tells the object to raise an exception when the message is received. + # + # @return [nil] No further chaining is supported after this. + # @note + # When you pass an exception class, the MessageExpectation will raise + # an instance of it, creating it with `exception` and passing `message` + # if specified. If the exception class initializer requires more than + # one parameters, you must pass in an instance and not the class, + # otherwise this method will raise an ArgumentError exception. + # + # @example + # allow(car).to receive(:go).and_raise + # allow(car).to receive(:go).and_raise(OutOfGas) + # allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive") + # allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz)) + def and_raise(*args) + raise_already_invoked_error_if_necessary(__method__) + self.terminal_implementation_action = Proc.new { raise(*args) } + nil + end + + # @overload and_throw(symbol) + # @overload and_throw(symbol, object) + # + # Tells the object to throw a symbol (with the object if that form is + # used) when the message is received. + # + # @return [nil] No further chaining is supported after this. + # @example + # allow(car).to receive(:go).and_throw(:out_of_gas) + # allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1) + def and_throw(*args) + raise_already_invoked_error_if_necessary(__method__) + self.terminal_implementation_action = Proc.new { throw(*args) } + nil + end + + # Tells the object to yield one or more args to a block when the message + # is received. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # stream.stub(:open).and_yield(StringIO.new) + def and_yield(*args, &block) + raise_already_invoked_error_if_necessary(__method__) + yield @eval_context = Object.new if block + + # Initialize args to yield now that it's being used, see also: comment + # in constructor. + @args_to_yield ||= [] + + @args_to_yield << args + self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator) + self + end + # @!endgroup + + # @!group Constraining Receive Counts + + # Constrain a message expectation to be received a specific number of + # times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).exactly(10).times + def exactly(n, &block) + raise_already_invoked_error_if_necessary(__method__) + self.inner_implementation_action = block + set_expected_received_count :exactly, n + self + end + + # Constrain a message expectation to be received at least a specific + # number of times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).at_least(9).times + def at_least(n, &block) + raise_already_invoked_error_if_necessary(__method__) + set_expected_received_count :at_least, n + + if n == 0 + raise "at_least(0) has been removed, use allow(...).to receive(:message) instead" + end + + self.inner_implementation_action = block + + self + end + + # Constrain a message expectation to be received at most a specific + # number of times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).at_most(10).times + def at_most(n, &block) + raise_already_invoked_error_if_necessary(__method__) + self.inner_implementation_action = block + set_expected_received_count :at_most, n + self + end + + # Syntactic sugar for `exactly`, `at_least` and `at_most` + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).exactly(10).times + # expect(dealer).to receive(:deal_card).at_least(10).times + # expect(dealer).to receive(:deal_card).at_most(10).times + def times(&block) + self.inner_implementation_action = block + self + end + alias time times + + # Expect a message not to be received at all. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:stop).never + def never + error_generator.raise_double_negation_error("expect(obj)") if negative? + @expected_received_count = 0 + self + end + + # Expect a message to be received exactly one time. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).once + def once(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 1 + self + end + + # Expect a message to be received exactly two times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).twice + def twice(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 2 + self + end + + # Expect a message to be received exactly three times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).thrice + def thrice(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 3 + self + end + # @!endgroup + + # @!group Other Constraints + + # Constrains a stub or message expectation to invocations with specific + # arguments. + # + # With a stub, if the message might be received with other args as well, + # you should stub a default value first, and then stub or mock the same + # message using `with` to constrain to specific arguments. + # + # A message expectation will fail if the message is received with different + # arguments. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # allow(cart).to receive(:add) { :failure } + # allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success } + # cart.add(Book.new(:isbn => 1234567890)) + # # => :failure + # cart.add(Book.new(:isbn => 1934356379)) + # # => :success + # + # expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success } + # cart.add(Book.new(:isbn => 1234567890)) + # # => failed expectation + # cart.add(Book.new(:isbn => 1934356379)) + # # => passes + def with(*args, &block) + raise_already_invoked_error_if_necessary(__method__) + if args.empty? + raise ArgumentError, + "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments." + end + + self.inner_implementation_action = block + @argument_list_matcher = ArgumentListMatcher.new(*args) + self + end + + # Expect messages to be received in a specific order. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(api).to receive(:prepare).ordered + # expect(api).to receive(:run).ordered + # expect(api).to receive(:finish).ordered + def ordered(&block) + if type == :stub + RSpec.warning( + "`allow(...).to receive(..).ordered` is not supported and will " \ + "have no effect, use `and_return(*ordered_values)` instead." + ) + end + + self.inner_implementation_action = block + additional_expected_calls.times do + @order_group.register(self) + end + @ordered = true + self + end + + # @return [String] a nice representation of the message expectation + def to_s + args_description = error_generator.method_call_args_description(@argument_list_matcher.expected_args, "", "") { true } + args_description = "(#{args_description})" unless args_description.start_with?("(") + "#<#{self.class} #{error_generator.intro}.#{message}#{args_description}>" + end + alias inspect to_s + + # @private + # Contains the parts of `MessageExpectation` that aren't part of + # rspec-mocks' public API. The class is very big and could really use + # some collaborators it delegates to for this stuff but for now this was + # the simplest way to split the public from private stuff to make it + # easier to publish the docs for the APIs we want published. + # rubocop:disable Metrics/ModuleLength + module ImplementationDetails + attr_accessor :error_generator, :implementation + attr_reader :message + attr_reader :orig_object + attr_writer :expected_received_count, :expected_from, :argument_list_matcher + protected :expected_received_count=, :expected_from=, :error_generator=, :implementation= + + # @private + attr_reader :type + + # rubocop:disable Metrics/ParameterLists + def initialize(error_generator, expectation_ordering, expected_from, method_double, + type=:expectation, opts={}, &implementation_block) + @type = type + @error_generator = error_generator + @error_generator.opts = error_generator.opts.merge(opts) + @expected_from = expected_from + @method_double = method_double + @orig_object = @method_double.object + @message = @method_double.method_name + @actual_received_count = 0 + @actual_received_count_write_mutex = Support::Mutex.new + @expected_received_count = type == :expectation ? 1 : :any + @argument_list_matcher = ArgumentListMatcher::MATCH_ALL + @order_group = expectation_ordering + @order_group.register(self) unless type == :stub + @expectation_type = type + @ordered = false + @at_least = @at_most = @exactly = nil + + # Initialized to nil so that we don't allocate an array for every + # mock or stub. See also comment in `and_yield`. + @args_to_yield = nil + @eval_context = nil + @yield_receiver_to_implementation_block = false + + @implementation = Implementation.new + self.inner_implementation_action = implementation_block + end + # rubocop:enable Metrics/ParameterLists + + def expected_args + @argument_list_matcher.expected_args + end + + def and_yield_receiver_to_implementation + @yield_receiver_to_implementation_block = true + self + end + + def yield_receiver_to_implementation_block? + @yield_receiver_to_implementation_block + end + + def matches?(message, *args) + @message == message && @argument_list_matcher.args_match?(*args) + end + + def safe_invoke(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block) + end + + def invoke(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block) + end + + def invoke_without_incrementing_received_count(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block) + end + + def negative? + @expected_received_count == 0 && !@at_least + end + + def called_max_times? + @expected_received_count != :any && + !@at_least && + @expected_received_count > 0 && + @actual_received_count >= @expected_received_count + end + + def matches_name_but_not_args(message, *args) + @message == message && !@argument_list_matcher.args_match?(*args) + end + + def verify_messages_received + return if expected_messages_received? + generate_error + end + + def expected_messages_received? + ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count? + end + + def ensure_expected_ordering_received! + @order_group.verify_invocation_order(self) if @ordered + true + end + + def ignoring_args? + @expected_received_count == :any + end + + def matches_at_least_count? + @at_least && @actual_received_count >= @expected_received_count + end + + def matches_at_most_count? + @at_most && @actual_received_count <= @expected_received_count + end + + def matches_exact_count? + @expected_received_count == @actual_received_count + end + + def similar_messages + @similar_messages ||= [] + end + + def advise(*args) + similar_messages << args + end + + def unadvise(args) + similar_messages.delete_if { |message| args.include?(message) } + end + + def generate_error + if similar_messages.empty? + @error_generator.raise_expectation_error( + @message, @expected_received_count, @argument_list_matcher, + @actual_received_count, expectation_count_type, expected_args, + @expected_from, exception_source_id + ) + else + @error_generator.raise_similar_message_args_error( + self, @similar_messages, @expected_from + ) + end + end + + def raise_unexpected_message_args_error(args_for_multiple_calls) + @error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id) + end + + def expectation_count_type + return :at_least if @at_least + return :at_most if @at_most + nil + end + + def description_for(verb) + @error_generator.describe_expectation( + verb, @message, @expected_received_count, + @actual_received_count, expected_args + ) + end + + def raise_out_of_order_error + @error_generator.raise_out_of_order_error @message + end + + def additional_expected_calls + return 0 if @expectation_type == :stub || !@exactly + @expected_received_count - 1 + end + + def ordered? + @ordered + end + + def negative_expectation_for?(message) + @message == message && negative? + end + + def actual_received_count_matters? + @at_least || @at_most || @exactly + end + + def increase_actual_received_count! + @actual_received_count_write_mutex.synchronize do + @actual_received_count += 1 + end + end + + private + + def exception_source_id + @exception_source_id ||= "#{self.class.name} #{__id__}" + end + + def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block) + args.unshift(orig_object) if yield_receiver_to_implementation_block? + + if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count)) + # args are the args we actually received, @argument_list_matcher is the + # list of args we were expecting + @error_generator.raise_expectation_error( + @message, @expected_received_count, + @argument_list_matcher, + @actual_received_count + increment, + expectation_count_type, args, nil, exception_source_id + ) + end + + @order_group.handle_order_constraint self + + if implementation.present? + implementation.call(*args, &block) + elsif parent_stub + parent_stub.invoke(nil, *args, &block) + end + ensure + @actual_received_count_write_mutex.synchronize do + @actual_received_count += increment + end + end + + def has_been_invoked? + @actual_received_count > 0 + end + + def raise_already_invoked_error_if_necessary(calling_customization) + return unless has_been_invoked? + + error_generator.raise_already_invoked_error(message, calling_customization) + end + + def set_expected_received_count(relativity, n) + raise "`count` is not supported with negative message expectations" if negative? + @at_least = (relativity == :at_least) + @at_most = (relativity == :at_most) + @exactly = (relativity == :exactly) + @expected_received_count = case n + when Numeric then n + when :once then 1 + when :twice then 2 + when :thrice then 3 + end + end + + def initial_implementation_action=(action) + implementation.initial_action = action + end + + def inner_implementation_action=(action) + return unless action + warn_about_stub_override if implementation.inner_action + implementation.inner_action = action + end + + def terminal_implementation_action=(action) + implementation.terminal_action = action + end + + def warn_about_stub_override + RSpec.warning( + "You're overriding a previous stub implementation of `#{@message}`. " \ + "Called from #{CallerFilter.first_non_rspec_line}." + ) + end + + def wrap_original(method_name, &block) + if RSpec::Mocks::TestDouble === @method_double.object + @error_generator.raise_only_valid_on_a_partial_double(method_name) + else + warn_about_stub_override if implementation.inner_action + @implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block) + @yield_receiver_to_implementation_block = false + end + + nil + end + end + # rubocop:enable Metrics/ModuleLength + + include ImplementationDetails + end + + # Handles the implementation of an `and_yield` declaration. + # @private + class AndYieldImplementation + def initialize(args_to_yield, eval_context, error_generator) + @args_to_yield = args_to_yield + @eval_context = eval_context + @error_generator = error_generator + end + + def call(*_args_to_ignore, &block) + return if @args_to_yield.empty? && @eval_context.nil? + + @error_generator.raise_missing_block_error @args_to_yield unless block + value = nil + block_signature = Support::BlockSignature.new(block) + + @args_to_yield.each do |args| + unless Support::StrictSignatureVerifier.new(block_signature, args).valid? + @error_generator.raise_wrong_arity_error(args, block_signature) + end + + value = @eval_context ? @eval_context.instance_exec(*args, &block) : yield(*args) + end + value + end + end + + # Handles the implementation of an `and_return` implementation. + # @private + class AndReturnImplementation + def initialize(values_to_return) + @values_to_return = values_to_return + end + + def call(*_args_to_ignore, &_block) + if @values_to_return.size > 1 + @values_to_return.shift + else + @values_to_return.first + end + end + end + + # Represents a configured implementation. Takes into account + # any number of sub-implementations. + # @private + class Implementation + attr_accessor :initial_action, :inner_action, :terminal_action + + def call(*args, &block) + actions.map do |action| + action.call(*args, &block) + end.last + end + + def present? + actions.any? + end + + private + + def actions + [initial_action, inner_action, terminal_action].compact + end + end + + # Represents an `and_call_original` implementation. + # @private + class AndWrapOriginalImplementation + def initialize(method, block) + @method = method + @block = block + end + + CannotModifyFurtherError = Class.new(StandardError) + + def initial_action=(_value) + raise cannot_modify_further_error + end + + def inner_action=(_value) + raise cannot_modify_further_error + end + + def terminal_action=(_value) + raise cannot_modify_further_error + end + + def present? + true + end + + def inner_action + true + end + + def call(*args, &block) + @block.call(@method, *args, &block) + end + + private + + def cannot_modify_further_error + CannotModifyFurtherError.new "This method has already been configured " \ + "to call the original implementation, and cannot be modified further." + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_double.rb new file mode 100644 index 0000000000..0598a35d41 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_double.rb @@ -0,0 +1,288 @@ +module RSpec + module Mocks + # @private + class MethodDouble + # @private + attr_reader :method_name, :object, :expectations, :stubs, :method_stasher + + # @private + def initialize(object, method_name, proxy) + @method_name = method_name + @object = object + @proxy = proxy + + @original_visibility = nil + @method_stasher = InstanceMethodStasher.new(object, method_name) + @method_is_proxied = false + @expectations = [] + @stubs = [] + end + + def original_implementation_callable + # If original method is not present, uses the `method_missing` + # handler of the object. This accounts for cases where the user has not + # correctly defined `respond_to?`, and also 1.8 which does not provide + # method handles for missing methods even if `respond_to?` is correct. + @original_implementation_callable ||= original_method || + Proc.new do |*args, &block| + @object.__send__(:method_missing, @method_name, *args, &block) + end + end + + alias_method :save_original_implementation_callable!, :original_implementation_callable + + def original_method + @original_method ||= + @method_stasher.original_method || + @proxy.original_method_handle_for(method_name) + end + + # @private + def visibility + @proxy.visibility_for(@method_name) + end + + # @private + def object_singleton_class + class << @object; self; end + end + + # @private + def configure_method + @original_visibility = visibility + @method_stasher.stash unless @method_is_proxied + define_proxy_method + end + + # @private + def define_proxy_method + return if @method_is_proxied + + save_original_implementation_callable! + definition_target.class_exec(self, method_name, @original_visibility || visibility) do |method_double, method_name, visibility| + define_method(method_name) do |*args, &block| + method_double.proxy_method_invoked(self, *args, &block) + end + ruby2_keywords(method_name) if Module.private_method_defined?(:ruby2_keywords) + __send__(visibility, method_name) + end + + @method_is_proxied = true + end + + # The implementation of the proxied method. Subclasses may override this + # method to perform additional operations. + # + # @private + def proxy_method_invoked(_obj, *args, &block) + @proxy.message_received method_name, *args, &block + end + + # @private + def restore_original_method + return show_frozen_warning if object_singleton_class.frozen? + return unless @method_is_proxied + + remove_method_from_definition_target + @method_stasher.restore if @method_stasher.method_is_stashed? + restore_original_visibility + + @method_is_proxied = false + end + + # @private + def show_frozen_warning + RSpec.warn_with( + "WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \ + "method on #{@object.inspect} because it has been frozen. If you reuse this " \ + "object, `#{@method_name}` will continue to respond with its stub implementation.", + :call_site => nil, + :use_spec_location_as_call_site => true + ) + end + + # @private + def restore_original_visibility + return unless @original_visibility && + MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name) + + object_singleton_class.__send__(@original_visibility, method_name) + end + + # @private + def verify + expectations.each { |e| e.verify_messages_received } + end + + # @private + def reset + restore_original_method + clear + end + + # @private + def clear + expectations.clear + stubs.clear + end + + # The type of message expectation to create has been extracted to its own + # method so that subclasses can override it. + # + # @private + def message_expectation_class + MessageExpectation + end + + # @private + def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation) + configure_method + expectation = message_expectation_class.new(error_generator, expectation_ordering, + expected_from, self, :expectation, opts, &implementation) + expectations << expectation + expectation + end + + # @private + def build_expectation(error_generator, expectation_ordering) + expected_from = IGNORED_BACKTRACE_LINE + message_expectation_class.new(error_generator, expectation_ordering, expected_from, self) + end + + # @private + def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation) + configure_method + stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from, + self, :stub, opts, &implementation) + stubs.unshift stub + stub + end + + # A simple stub can only return a concrete value for a message, and + # cannot match on arguments. It is used as an optimization over + # `add_stub` / `add_expectation` where it is known in advance that this + # is all that will be required of a stub, such as when passing attributes + # to the `double` example method. They do not stash or restore existing method + # definitions. + # + # @private + def add_simple_stub(method_name, response) + setup_simple_method_double method_name, response, stubs + end + + # @private + def add_simple_expectation(method_name, response, error_generator, backtrace_line) + setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line + end + + # @private + def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil) + define_proxy_method + + me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line) + collection.unshift me + me + end + + # @private + def add_default_stub(*args, &implementation) + return if stubs.any? + add_stub(*args, &implementation) + end + + # @private + def remove_stub + raise_method_not_stubbed_error if stubs.empty? + remove_stub_if_present + end + + # @private + def remove_stub_if_present + expectations.empty? ? reset : stubs.clear + end + + # @private + def raise_method_not_stubbed_error + RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name) + end + + # In Ruby 2.0.0 and above prepend will alter the method lookup chain. + # We use an object's singleton class to define method doubles upon, + # however if the object has had its singleton class (as opposed to + # its actual class) prepended too then the the method lookup chain + # will look in the prepended module first, **before** the singleton + # class. + # + # This code works around that by providing a mock definition target + # that is either the singleton class, or if necessary, a prepended module + # of our own. + # + if Support::RubyFeatures.module_prepends_supported? + + private + + # We subclass `Module` in order to be able to easily detect our prepended module. + RSpecPrependedModule = Class.new(Module) + + def definition_target + @definition_target ||= usable_rspec_prepended_module || object_singleton_class + end + + def usable_rspec_prepended_module + @proxy.prepended_modules_of_singleton_class.each do |mod| + # If we have one of our modules prepended before one of the user's + # modules that defines the method, use that, since our module's + # definition will take precedence. + return mod if RSpecPrependedModule === mod + + # If we hit a user module with the method defined first, + # we must create a new prepend module, even if one exists later, + # because ours will only take precedence if it comes first. + return new_rspec_prepended_module if mod.method_defined?(method_name) + end + + nil + end + + def new_rspec_prepended_module + RSpecPrependedModule.new.tap do |mod| + object_singleton_class.__send__ :prepend, mod + end + end + + else + + private + + def definition_target + object_singleton_class + end + + end + + private + + def remove_method_from_definition_target + definition_target.__send__(:remove_method, @method_name) + rescue NameError + # This can happen when the method has been monkeyed with by + # something outside RSpec. This happens, for example, when + # `file.write` has been stubbed, and then `file.reopen(other_io)` + # is later called, as `File#reopen` appears to redefine `write`. + # + # Note: we could avoid rescuing this by checking + # `definition_target.instance_method(@method_name).owner == definition_target`, + # saving us from the cost of the expensive exception, but this error is + # extremely rare (it was discovered on 2014-12-30, only happens on + # RUBY_VERSION < 2.0 and our spec suite only hits this condition once), + # so we'd rather avoid the cost of that check for every method double, + # and risk the rare situation where this exception will get raised. + RSpec.warn_with( + "WARNING: RSpec could not fully restore #{@object.inspect}." \ + "#{@method_name}, possibly because the method has been redefined " \ + "by something outside of RSpec." + ) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_reference.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_reference.rb new file mode 100644 index 0000000000..026c2c07d1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/method_reference.rb @@ -0,0 +1,202 @@ +RSpec::Support.require_rspec_support 'comparable_version' + +module RSpec + module Mocks + # Represents a method on an object that may or may not be defined. + # The method may be an instance method on a module or a method on + # any object. + # + # @private + class MethodReference + def self.for(object_reference, method_name) + new(object_reference, method_name) + end + + def initialize(object_reference, method_name) + @object_reference = object_reference + @method_name = method_name + end + + # A method is implemented if sending the message does not result in + # a `NoMethodError`. It might be dynamically implemented by + # `method_missing`. + def implemented? + @object_reference.when_loaded do |m| + method_implemented?(m) + end + end + + # Returns true if we definitively know that sending the method + # will result in a `NoMethodError`. + # + # This is not simply the inverse of `implemented?`: there are + # cases when we don't know if a method is implemented and + # both `implemented?` and `unimplemented?` will return false. + def unimplemented? + @object_reference.when_loaded do |_m| + return !implemented? + end + + # If it's not loaded, then it may be implemented but we can't check. + false + end + + # A method is defined if we are able to get a `Method` object for it. + # In that case, we can assert against metadata like the arity. + def defined? + @object_reference.when_loaded do |m| + method_defined?(m) + end + end + + def with_signature + return unless (original = original_method) + yield Support::MethodSignature.new(original) + end + + def visibility + @object_reference.when_loaded do |m| + return visibility_from(m) + end + + # When it's not loaded, assume it's public. We don't want to + # wrongly treat the method as private. + :public + end + + def self.instance_method_visibility_for(klass, method_name) + if klass.public_method_defined?(method_name) + :public + elsif klass.private_method_defined?(method_name) + :private + elsif klass.protected_method_defined?(method_name) + :protected + end + end + + class << self + alias method_defined_at_any_visibility? instance_method_visibility_for + end + + def self.method_visibility_for(object, method_name) + vis = instance_method_visibility_for(class << object; self; end, method_name) + + # If the method is not defined on the class, `instance_method_visibility_for` + # returns `nil`. However, it may be handled dynamically by `method_missing`, + # so here we check `respond_to` (passing false to not check private methods). + # + # This only considers the public case, but I don't think it's possible to + # write `method_missing` in such a way that it handles a dynamic message + # with private or protected visibility. Ruby doesn't provide you with + # the caller info. + return vis unless vis.nil? + + proxy = RSpec::Mocks.space.proxy_for(object) + respond_to = proxy.method_double_if_exists_for_message(:respond_to?) + + visible = respond_to && respond_to.original_method.call(method_name) || + object.respond_to?(method_name) + + return :public if visible + end + + private + + def original_method + @object_reference.when_loaded do |m| + self.defined? && find_method(m) + end + end + end + + # @private + class InstanceMethodReference < MethodReference + private + + def method_implemented?(mod) + MethodReference.method_defined_at_any_visibility?(mod, @method_name) + end + + # Ideally, we'd use `respond_to?` for `method_implemented?` but we need a + # reference to an instance to do that and we don't have one. Note that + # we may get false negatives: if the method is implemented via + # `method_missing`, we'll return `false` even though it meets our + # definition of "implemented". However, it's the best we can do. + alias method_defined? method_implemented? + + # works around the fact that repeated calls for method parameters will + # falsely return empty arrays on JRuby in certain circumstances, this + # is necessary here because we can't dup/clone UnboundMethods. + # + # This is necessary due to a bug in JRuby prior to 1.7.5 fixed in: + # https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27 + if RUBY_PLATFORM == 'java' && RSpec::Support::ComparableVersion.new(JRUBY_VERSION) < '1.7.5' + def find_method(mod) + mod.dup.instance_method(@method_name) + end + else + def find_method(mod) + mod.instance_method(@method_name) + end + end + + def visibility_from(mod) + MethodReference.instance_method_visibility_for(mod, @method_name) + end + end + + # @private + class ObjectMethodReference < MethodReference + def self.for(object_reference, method_name) + if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } } + ClassNewMethodReference.new(object_reference, method_name) + else + super + end + end + + private + + def method_implemented?(object) + object.respond_to?(@method_name, true) + end + + def method_defined?(object) + (class << object; self; end).method_defined?(@method_name) + end + + def find_method(object) + object.method(@method_name) + end + + def visibility_from(object) + MethodReference.method_visibility_for(object, @method_name) + end + end + + # When a class's `.new` method is stubbed, we want to use the method + # signature from `#initialize` because `.new`'s signature is a generic + # `def new(*args)` and it simply delegates to `#initialize` and forwards + # all args...so the method with the actually used signature is `#initialize`. + # + # This method reference implementation handles that specific case. + # @private + class ClassNewMethodReference < ObjectMethodReference + def self.applies_to?(method_name) + return false unless method_name == :new + klass = yield + return false unless klass.respond_to?(:new, true) + + # We only want to apply our special logic to normal `new` methods. + # Methods that the user has monkeyed with should be left as-is. + ::RSpec::Support.method_handle_for(klass, :new).owner == ::Class + end + + def with_signature + @object_reference.when_loaded do |klass| + yield Support::MethodSignature.new(klass.instance_method(:initialize)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/minitest_integration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/minitest_integration.rb new file mode 100644 index 0000000000..c7a7bab45c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/minitest_integration.rb @@ -0,0 +1,68 @@ +require 'rspec/mocks' + +module RSpec + module Mocks + # @private + module MinitestIntegration + include ::RSpec::Mocks::ExampleMethods + + def before_setup + ::RSpec::Mocks.setup + super + end + + def after_teardown + super + + # Only verify if there's not already an error. Otherwise + # we risk getting the same failure twice, since negative + # expectation violations raise both when the message is + # unexpectedly received, and also during `verify` (in case + # the first failure was caught by user code via a + # `rescue Exception`). + ::RSpec::Mocks.verify unless failures.any? + ensure + ::RSpec::Mocks.teardown + end + end + end +end + +Minitest::Test.send(:include, RSpec::Mocks::MinitestIntegration) + +if defined?(::Minitest::Expectation) + if defined?(::RSpec::Expectations) && ::Minitest::Expectation.method_defined?(:to) + # rspec/expectations/minitest_integration has already been loaded and + # has defined `to`/`not_to`/`to_not` on `Minitest::Expectation` so we do + # not want to here (or else we would interfere with rspec-expectations' definition). + else + # ...otherwise, define those methods now. If `rspec/expectations/minitest_integration` + # is loaded after this file, it'll overide the defintion here. + Minitest::Expectation.class_eval do + include RSpec::Mocks::ExpectationTargetMethods + + def to(*args) + ctx.assertions += 1 + super + end + + def not_to(*args) + ctx.assertions += 1 + super + end + + def to_not(*args) + ctx.assertions += 1 + super + end + end + end +end + +module RSpec + module Mocks + remove_const :MockExpectationError + # Raised when a message expectation is not satisfied. + MockExpectationError = ::Minitest::Assertion + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/mutate_const.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/mutate_const.rb new file mode 100644 index 0000000000..c5b0b79217 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/mutate_const.rb @@ -0,0 +1,339 @@ +RSpec::Support.require_rspec_support 'recursive_const_methods' + +module RSpec + module Mocks + # Provides information about constants that may (or may not) + # have been mutated by rspec-mocks. + class Constant + extend Support::RecursiveConstMethods + + # @api private + def initialize(name) + @name = name + @previously_defined = false + @stubbed = false + @hidden = false + @valid_name = true + yield self if block_given? + end + + # @return [String] The fully qualified name of the constant. + attr_reader :name + + # @return [Object, nil] The original value (e.g. before it + # was mutated by rspec-mocks) of the constant, or + # nil if the constant was not previously defined. + attr_accessor :original_value + + # @private + attr_writer :previously_defined, :stubbed, :hidden, :valid_name + + # @return [Boolean] Whether or not the constant was defined + # before the current example. + def previously_defined? + @previously_defined + end + + # @return [Boolean] Whether or not rspec-mocks has mutated + # (stubbed or hidden) this constant. + def mutated? + @stubbed || @hidden + end + + # @return [Boolean] Whether or not rspec-mocks has stubbed + # this constant. + def stubbed? + @stubbed + end + + # @return [Boolean] Whether or not rspec-mocks has hidden + # this constant. + def hidden? + @hidden + end + + # @return [Boolean] Whether or not the provided constant name + # is a valid Ruby constant name. + def valid_name? + @valid_name + end + + # The default `to_s` isn't very useful, so a custom version is provided. + def to_s + "#<#{self.class.name} #{name}>" + end + alias inspect to_s + + # @private + def self.unmutated(name) + previously_defined = !!recursive_const_defined?(name) + rescue NameError + new(name) do |c| + c.valid_name = false + end + else + new(name) do |const| + const.previously_defined = previously_defined + const.original_value = recursive_const_get(name) if previously_defined + end + end + + # Queries rspec-mocks to find out information about the named constant. + # + # @param [String] name the name of the constant + # @return [Constant] an object contaning information about the named + # constant. + def self.original(name) + mutator = ::RSpec::Mocks.space.constant_mutator_for(name) + mutator ? mutator.to_constant : unmutated(name) + end + end + + # Provides a means to stub constants. + class ConstantMutator + extend Support::RecursiveConstMethods + + # Stubs a constant. + # + # @param (see ExampleMethods#stub_const) + # @option (see ExampleMethods#stub_const) + # @return (see ExampleMethods#stub_const) + # + # @see ExampleMethods#stub_const + # @note It's recommended that you use `stub_const` in your + # examples. This is an alternate public API that is provided + # so you can stub constants in other contexts (e.g. helper + # classes). + def self.stub(constant_name, value, options={}) + unless String === constant_name + raise ArgumentError, "`stub_const` requires a String, but you provided a #{constant_name.class.name}" + end + + mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const) + DefinedConstantReplacer + else + UndefinedConstantSetter + end + + mutate(mutator.new(constant_name, value, options[:transfer_nested_constants])) + value + end + + # Hides a constant. + # + # @param (see ExampleMethods#hide_const) + # + # @see ExampleMethods#hide_const + # @note It's recommended that you use `hide_const` in your + # examples. This is an alternate public API that is provided + # so you can hide constants in other contexts (e.g. helper + # classes). + def self.hide(constant_name) + mutate(ConstantHider.new(constant_name, nil, {})) + nil + end + + # Contains common functionality used by all of the constant mutators. + # + # @private + class BaseMutator + include Support::RecursiveConstMethods + + attr_reader :original_value, :full_constant_name + + def initialize(full_constant_name, mutated_value, transfer_nested_constants) + @full_constant_name = normalize_const_name(full_constant_name) + @mutated_value = mutated_value + @transfer_nested_constants = transfer_nested_constants + @context_parts = @full_constant_name.split('::') + @const_name = @context_parts.pop + @reset_performed = false + end + + def to_constant + const = Constant.new(full_constant_name) + const.original_value = original_value + + const + end + + def idempotently_reset + reset unless @reset_performed + @reset_performed = true + end + end + + # Hides a defined constant for the duration of an example. + # + # @private + class ConstantHider < BaseMutator + def mutate + return unless (@defined = recursive_const_defined?(full_constant_name)) + @context = recursive_const_get(@context_parts.join('::')) + @original_value = get_const_defined_on(@context, @const_name) + + @context.__send__(:remove_const, @const_name) + end + + def to_constant + return Constant.unmutated(full_constant_name) unless @defined + + const = super + const.hidden = true + const.previously_defined = true + + const + end + + def reset + return unless @defined + @context.const_set(@const_name, @original_value) + end + end + + # Replaces a defined constant for the duration of an example. + # + # @private + class DefinedConstantReplacer < BaseMutator + def initialize(*args) + super + @constants_to_transfer = [] + end + + def mutate + @context = recursive_const_get(@context_parts.join('::')) + @original_value = get_const_defined_on(@context, @const_name) + + @constants_to_transfer = verify_constants_to_transfer! + + @context.__send__(:remove_const, @const_name) + @context.const_set(@const_name, @mutated_value) + + transfer_nested_constants + end + + def to_constant + const = super + const.stubbed = true + const.previously_defined = true + + const + end + + def reset + @constants_to_transfer.each do |const| + @mutated_value.__send__(:remove_const, const) + end + + @context.__send__(:remove_const, @const_name) + @context.const_set(@const_name, @original_value) + end + + def transfer_nested_constants + @constants_to_transfer.each do |const| + @mutated_value.const_set(const, get_const_defined_on(original_value, const)) + end + end + + def verify_constants_to_transfer! + return [] unless should_transfer_nested_constants? + + { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description| + next if value.respond_to?(:constants) + + raise ArgumentError, + "Cannot transfer nested constants for #{@full_constant_name} " \ + "since #{description} is not a class or module and only classes " \ + "and modules support nested constants." + end + + if Array === @transfer_nested_constants + @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7' + undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value) + + if undefined_constants.any? + available_constants = constants_defined_on(@original_value) - @transfer_nested_constants + raise ArgumentError, + "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \ + "for #{@full_constant_name} since they are not defined. Did you mean " \ + "#{available_constants.join(' or ')}?" + end + + @transfer_nested_constants + else + constants_defined_on(@original_value) + end + end + + def should_transfer_nested_constants? + return true if @transfer_nested_constants + return false unless RSpec::Mocks.configuration.transfer_nested_constants? + @original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants) + end + end + + # Sets an undefined constant for the duration of an example. + # + # @private + class UndefinedConstantSetter < BaseMutator + def mutate + @parent = @context_parts.inject(Object) do |klass, name| + if const_defined_on?(klass, name) + get_const_defined_on(klass, name) + else + ConstantMutator.stub(name_for(klass, name), Module.new) + end + end + + @parent.const_set(@const_name, @mutated_value) + end + + def to_constant + const = super + const.stubbed = true + const.previously_defined = false + + const + end + + def reset + @parent.__send__(:remove_const, @const_name) + end + + private + + def name_for(parent, name) + root = if parent == Object + '' + else + parent.name + end + root + '::' + name + end + end + + # Uses the mutator to mutate (stub or hide) a constant. Ensures that + # the mutator is correctly registered so it can be backed out at the end + # of the test. + # + # @private + def self.mutate(mutator) + ::RSpec::Mocks.space.register_constant_mutator(mutator) + mutator.mutate + end + + # Used internally by the constant stubbing to raise a helpful + # error when a constant like "A::B::C" is stubbed and A::B is + # not a module (and thus, it's impossible to define "A::B::C" + # since only modules can have nested constants). + # + # @api private + def self.raise_on_invalid_const + lambda do |const_name, failed_name| + raise "Cannot stub constant #{failed_name} on #{const_name} " \ + "since #{const_name} is not a module." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/object_reference.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/object_reference.rb new file mode 100644 index 0000000000..cce2c3313e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/object_reference.rb @@ -0,0 +1,149 @@ +module RSpec + module Mocks + # @private + class ObjectReference + # Returns an appropriate Object or Module reference based + # on the given argument. + def self.for(object_module_or_name, allow_direct_object_refs=false) + case object_module_or_name + when Module + if anonymous_module?(object_module_or_name) + DirectObjectReference.new(object_module_or_name) + else + # Use a `NamedObjectReference` if it has a name because this + # will use the original value of the constant in case it has + # been stubbed. + NamedObjectReference.new(name_of(object_module_or_name)) + end + when String + NamedObjectReference.new(object_module_or_name) + else + if allow_direct_object_refs + DirectObjectReference.new(object_module_or_name) + else + raise ArgumentError, + "Module or String expected, got #{object_module_or_name.inspect}" + end + end + end + + if Module.new.name.nil? + def self.anonymous_module?(mod) + !name_of(mod) + end + else # 1.8.7 + def self.anonymous_module?(mod) + name_of(mod) == "" + end + end + private_class_method :anonymous_module? + + def self.name_of(mod) + MODULE_NAME_METHOD.bind(mod).call + end + private_class_method :name_of + + # @private + MODULE_NAME_METHOD = Module.instance_method(:name) + end + + # An implementation of rspec-mocks' reference interface. + # Used when an object is passed to {ExampleMethods#object_double}, or + # an anonymous class or module is passed to {ExampleMethods#instance_double} + # or {ExampleMethods#class_double}. + # Represents a reference to that object. + # @see NamedObjectReference + class DirectObjectReference + # @param object [Object] the object to which this refers + def initialize(object) + @object = object + end + + # @return [String] the object's description (via `#inspect`). + def description + @object.inspect + end + + # Defined for interface parity with the other object reference + # implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const` + # is invalid when passing an object argument to `object_double`. + def const_to_replace + raise ArgumentError, + "Can not perform constant replacement with an anonymous object." + end + + # The target of the verifying double (the object itself). + # + # @return [Object] + def target + @object + end + + # Always returns true for an object as the class is defined. + # + # @return [true] + def defined? + true + end + + # Yields if the reference target is loaded, providing a generic mechanism + # to optionally run a bit of code only when a reference's target is + # loaded. + # + # This specific implementation always yields because direct references + # are always loaded. + # + # @yield [Object] the target of this reference. + def when_loaded + yield @object + end + end + + # An implementation of rspec-mocks' reference interface. + # Used when a string is passed to {ExampleMethods#object_double}, + # and when a string, named class or named module is passed to + # {ExampleMethods#instance_double}, or {ExampleMethods#class_double}. + # Represents a reference to the object named (via a constant lookup) + # by the string. + # @see DirectObjectReference + class NamedObjectReference + # @param const_name [String] constant name + def initialize(const_name) + @const_name = const_name + end + + # @return [Boolean] true if the named constant is defined, false otherwise. + def defined? + !!object + end + + # @return [String] the constant name to replace with a double. + def const_to_replace + @const_name + end + alias description const_to_replace + + # @return [Object, nil] the target of the verifying double (the named object), or + # nil if it is not defined. + def target + object + end + + # Yields if the reference target is loaded, providing a generic mechanism + # to optionally run a bit of code only when a reference's target is + # loaded. + # + # @yield [Object] the target object + def when_loaded + yield object if object + end + + private + + def object + return @object if defined?(@object) + @object = Constant.original(@const_name).original_value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/order_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/order_group.rb new file mode 100644 index 0000000000..a9947995c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/order_group.rb @@ -0,0 +1,81 @@ +module RSpec + module Mocks + # @private + class OrderGroup + def initialize + @expectations = [] + @invocation_order = [] + @index = 0 + end + + # @private + def register(expectation) + @expectations << expectation + end + + def invoked(message) + @invocation_order << message + end + + # @private + def ready_for?(expectation) + remaining_expectations.find(&:ordered?) == expectation + end + + # @private + def consume + remaining_expectations.each_with_index do |expectation, index| + next unless expectation.ordered? + + @index += index + 1 + return expectation + end + nil + end + + # @private + def handle_order_constraint(expectation) + return unless expectation.ordered? && remaining_expectations.include?(expectation) + return consume if ready_for?(expectation) + expectation.raise_out_of_order_error + end + + def verify_invocation_order(expectation) + expectation.raise_out_of_order_error unless expectations_invoked_in_order? + true + end + + def clear + @index = 0 + @invocation_order.clear + @expectations.clear + end + + def empty? + @expectations.empty? + end + + private + + def remaining_expectations + @expectations[@index..-1] || [] + end + + def expectations_invoked_in_order? + invoked_expectations == expected_invocations + end + + def invoked_expectations + @expectations.select { |e| e.ordered? && @invocation_order.include?(e) } + end + + def expected_invocations + @invocation_order.map { |invocation| expectation_for(invocation) }.compact + end + + def expectation_for(message) + @expectations.find { |e| message == e } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/proxy.rb new file mode 100644 index 0000000000..d8f092d8c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/proxy.rb @@ -0,0 +1,519 @@ +module RSpec + module Mocks + # @private + class Proxy + # @private + SpecificMessage = Struct.new(:object, :message, :args) do + def ==(expectation) + expectation.orig_object == object && expectation.matches?(message, *args) + end + end + + unless defined?(Mutex) + Support.require_rspec_support 'mutex' + Mutex = Support::Mutex + end + + # @private + def ensure_implemented(*_args) + # noop for basic proxies, see VerifyingProxy for behaviour. + end + + # @private + def initialize(object, order_group, options={}) + ensure_can_be_proxied!(object) + + @object = object + @order_group = order_group + @error_generator = ErrorGenerator.new(object) + @messages_received = [] + @messages_received_mutex = Mutex.new + @options = options + @null_object = false + @method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) } + end + + # @private + def ensure_can_be_proxied!(object) + return unless object.is_a?(Symbol) || object.frozen? + return if object.nil? + + msg = "Cannot proxy frozen objects" + if Symbol === object + msg << ". Symbols such as #{object} cannot be mocked or stubbed." + else + msg << ", rspec-mocks relies on proxies for method stubbing and expectations." + end + raise ArgumentError, msg + end + + # @private + attr_reader :object + + # @private + def null_object? + @null_object + end + + # @private + # Tells the object to ignore any messages that aren't explicitly set as + # stubs or message expectations. + def as_null_object + @null_object = true + @object + end + + # @private + def original_method_handle_for(_message) + nil + end + + DEFAULT_MESSAGE_EXPECTATION_OPTS = {}.freeze + + # @private + def add_message_expectation(method_name, opts=DEFAULT_MESSAGE_EXPECTATION_OPTS, &block) + location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line } + meth_double = method_double_for(method_name) + + if null_object? && !block + meth_double.add_default_stub(@error_generator, @order_group, location, opts) do + @object + end + end + + meth_double.add_expectation @error_generator, @order_group, location, opts, &block + end + + # @private + def add_simple_expectation(method_name, response, location) + method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location + end + + # @private + def build_expectation(method_name) + meth_double = method_double_for(method_name) + + meth_double.build_expectation( + @error_generator, + @order_group + ) + end + + # @private + def replay_received_message_on(expectation, &block) + expected_method_name = expectation.message + meth_double = method_double_for(expected_method_name) + + if meth_double.expectations.any? + @error_generator.raise_expectation_on_mocked_method(expected_method_name) + end + + unless null_object? || meth_double.stubs.any? + @error_generator.raise_expectation_on_unstubbed_method(expected_method_name) + end + + @messages_received_mutex.synchronize do + @messages_received.each do |(actual_method_name, args, received_block)| + next unless expectation.matches?(actual_method_name, *args) + + expectation.safe_invoke(nil) + block.call(*args, &received_block) if block + end + end + end + + # @private + def check_for_unexpected_arguments(expectation) + @messages_received_mutex.synchronize do + return if @messages_received.empty? + + return if @messages_received.any? { |method_name, args, _| expectation.matches?(method_name, *args) } + + name_but_not_args, others = @messages_received.partition do |(method_name, args, _)| + expectation.matches_name_but_not_args(method_name, *args) + end + + return if name_but_not_args.empty? && !others.empty? + + expectation.raise_unexpected_message_args_error(name_but_not_args.map { |args| args[1] }) + end + end + + # @private + def add_stub(method_name, opts={}, &implementation) + location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line } + method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation + end + + # @private + def add_simple_stub(method_name, response) + method_double_for(method_name).add_simple_stub method_name, response + end + + # @private + def remove_stub(method_name) + method_double_for(method_name).remove_stub + end + + # @private + def remove_stub_if_present(method_name) + method_double_for(method_name).remove_stub_if_present + end + + # @private + def verify + @method_doubles.each_value { |d| d.verify } + end + + # @private + def reset + @messages_received_mutex.synchronize do + @messages_received.clear + end + end + + # @private + def received_message?(method_name, *args, &block) + @messages_received_mutex.synchronize do + @messages_received.any? { |array| array == [method_name, args, block] } + end + end + + # @private + def messages_arg_list + @messages_received_mutex.synchronize do + @messages_received.map { |_, args, _| args } + end + end + + # @private + def has_negative_expectation?(message) + method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) } + end + + # @private + def record_message_received(message, *args, &block) + @order_group.invoked SpecificMessage.new(object, message, args) + @messages_received_mutex.synchronize do + @messages_received << [message, args, block] + end + end + + # @private + def message_received(message, *args, &block) + record_message_received message, *args, &block + + expectation = find_matching_expectation(message, *args) + stub = find_matching_method_stub(message, *args) + + if (stub && expectation && expectation.called_max_times?) || (stub && !expectation) + expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters? + if (expectation = find_almost_matching_expectation(message, *args)) + expectation.advise(*args) unless expectation.expected_messages_received? + end + stub.invoke(nil, *args, &block) + elsif expectation + expectation.unadvise(messages_arg_list) + expectation.invoke(stub, *args, &block) + elsif (expectation = find_almost_matching_expectation(message, *args)) + expectation.advise(*args) if null_object? unless expectation.expected_messages_received? + + if null_object? || !has_negative_expectation?(message) + expectation.raise_unexpected_message_args_error([args]) + end + elsif (stub = find_almost_matching_stub(message, *args)) + stub.advise(*args) + raise_missing_default_stub_error(stub, [args]) + elsif Class === @object + @object.superclass.__send__(message, *args, &block) + else + @object.__send__(:method_missing, message, *args, &block) + end + end + + # @private + def raise_unexpected_message_error(method_name, args) + @error_generator.raise_unexpected_message_error method_name, args + end + + # @private + def raise_missing_default_stub_error(expectation, args_for_multiple_calls) + @error_generator.raise_missing_default_stub_error(expectation, args_for_multiple_calls) + end + + # @private + def visibility_for(_method_name) + # This is the default (for test doubles). Subclasses override this. + :public + end + + if Support::RubyFeatures.module_prepends_supported? + def self.prepended_modules_of(klass) + ancestors = klass.ancestors + + # `|| 0` is necessary for Ruby 2.0, where the singleton class + # is only in the ancestor list when there are prepended modules. + singleton_index = ancestors.index(klass) || 0 + + ancestors[0, singleton_index] + end + + def prepended_modules_of_singleton_class + @prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class) + end + end + + # @private + def method_double_if_exists_for_message(message) + method_double_for(message) if @method_doubles.key?(message.to_sym) + end + + private + + def method_double_for(message) + @method_doubles[message.to_sym] + end + + def find_matching_expectation(method_name, *args) + find_best_matching_expectation_for(method_name) do |expectation| + expectation.matches?(method_name, *args) + end + end + + def find_almost_matching_expectation(method_name, *args) + find_best_matching_expectation_for(method_name) do |expectation| + expectation.matches_name_but_not_args(method_name, *args) + end + end + + def find_best_matching_expectation_for(method_name) + first_match = nil + + method_double_for(method_name).expectations.each do |expectation| + next unless yield expectation + return expectation unless expectation.called_max_times? + first_match ||= expectation + end + + first_match + end + + def find_matching_method_stub(method_name, *args) + method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) } + end + + def find_almost_matching_stub(method_name, *args) + method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) } + end + end + + # @private + class TestDoubleProxy < Proxy + def reset + @method_doubles.clear + object.__disallow_further_usage! + super + end + end + + # @private + class PartialDoubleProxy < Proxy + def original_method_handle_for(message) + if any_instance_class_recorder_observing_method?(@object.class, message) + message = ::RSpec::Mocks.space. + any_instance_recorder_for(@object.class). + build_alias_method_name(message) + end + + ::RSpec::Support.method_handle_for(@object, message) + rescue NameError + nil + end + + # @private + def add_simple_expectation(method_name, response, location) + method_double_for(method_name).configure_method + super + end + + # @private + def add_simple_stub(method_name, response) + method_double_for(method_name).configure_method + super + end + + # @private + def visibility_for(method_name) + # We fall back to :public because by default we allow undefined methods + # to be stubbed, and when we do so, we make them public. + MethodReference.method_visibility_for(@object, method_name) || :public + end + + def reset + @method_doubles.each_value { |d| d.reset } + super + end + + def message_received(message, *args, &block) + RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber| + subscriber.notify_received_message(object, message, args, block) + end + super + end + + private + + def any_instance_class_recorder_observing_method?(klass, method_name) + only_return_existing = true + recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing) + return true if recorder && recorder.already_observing?(method_name) + + superklass = klass.superclass + return false if superklass.nil? + any_instance_class_recorder_observing_method?(superklass, method_name) + end + end + + # @private + # When we mock or stub a method on a class, we have to treat it a bit different, + # because normally singleton method definitions only affect the object on which + # they are defined, but on classes they affect subclasses, too. As a result, + # we need some special handling to get the original method. + module PartialClassDoubleProxyMethods + def initialize(source_space, *args) + @source_space = source_space + super(*args) + end + + # Consider this situation: + # + # class A; end + # class B < A; end + # + # allow(A).to receive(:new) + # expect(B).to receive(:new).and_call_original + # + # When getting the original definition for `B.new`, we cannot rely purely on + # using `B.method(:new)` before our redefinition is defined on `B`, because + # `B.method(:new)` will return a method that will execute the stubbed version + # of the method on `A` since singleton methods on classes are in the lookup + # hierarchy. + # + # To do it properly, we need to find the original definition of `new` from `A` + # from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will + # run with the proper `self`. + # + # That's what this method (together with `original_unbound_method_handle_from_ancestor_for`) + # does. + def original_method_handle_for(message) + unbound_method = superclass_proxy && + superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym) + + return super unless unbound_method + unbound_method.bind(object) + # :nocov: + rescue TypeError + if RUBY_VERSION == '1.8.7' + # In MRI 1.8.7, a singleton method on a class cannot be rebound to its subclass + if unbound_method && unbound_method.owner.ancestors.first != unbound_method.owner + # This is a singleton method; we can't do anything with it + # But we can work around this using a different implementation + double = method_double_from_ancestor_for(message) + return object.method(double.method_stasher.stashed_method_name) + end + end + raise + # :nocov: + end + + protected + + def original_unbound_method_handle_from_ancestor_for(message) + double = method_double_from_ancestor_for(message) + double && double.original_method.unbind + end + + def method_double_from_ancestor_for(message) + @method_doubles.fetch(message) do + # The fact that there is no method double for this message indicates + # that it has not been redefined by rspec-mocks. We need to continue + # looking up the ancestor chain. + return superclass_proxy && + superclass_proxy.method_double_from_ancestor_for(message) + end + end + + def superclass_proxy + return @superclass_proxy if defined?(@superclass_proxy) + + if (superclass = object.superclass) + @superclass_proxy = @source_space.superclass_proxy_for(superclass) + else + @superclass_proxy = nil + end + end + end + + # @private + class PartialClassDoubleProxy < PartialDoubleProxy + include PartialClassDoubleProxyMethods + end + + # @private + class ProxyForNil < PartialDoubleProxy + def initialize(order_group) + set_expectation_behavior + super(nil, order_group) + end + + attr_accessor :disallow_expectations + attr_accessor :warn_about_expectations + + def add_message_expectation(method_name, opts={}, &block) + warn_or_raise!(method_name) + super + end + + def add_stub(method_name, opts={}, &implementation) + warn_or_raise!(method_name) + super + end + + private + + def set_expectation_behavior + case RSpec::Mocks.configuration.allow_message_expectations_on_nil + when false + @warn_about_expectations = false + @disallow_expectations = true + when true + @warn_about_expectations = false + @disallow_expectations = false + else + @warn_about_expectations = true + @disallow_expectations = false + end + end + + def warn_or_raise!(method_name) + # This method intentionally swallows the message when + # neither disallow_expectations nor warn_about_expectations + # are set to true. + if disallow_expectations + raise_error(method_name) + elsif warn_about_expectations + warn(method_name) + end + end + + def warn(method_name) + warning_msg = @error_generator.expectation_on_nil_message(method_name) + RSpec.warning(warning_msg) + end + + def raise_error(method_name) + @error_generator.raise_expectation_on_nil_error(method_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/space.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/space.rb new file mode 100644 index 0000000000..f5a4c24061 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/space.rb @@ -0,0 +1,238 @@ +RSpec::Support.require_rspec_support 'reentrant_mutex' + +module RSpec + module Mocks + # @private + # Provides a default space implementation for outside + # the scope of an example. Called "root" because it serves + # as the root of the space stack. + class RootSpace + def proxy_for(*_args) + raise_lifecycle_message + end + + def any_instance_recorder_for(*_args) + raise_lifecycle_message + end + + def any_instance_proxy_for(*_args) + raise_lifecycle_message + end + + def register_constant_mutator(_mutator) + raise_lifecycle_message + end + + def any_instance_recorders_from_ancestry_of(_object) + raise_lifecycle_message + end + + def reset_all + end + + def verify_all + end + + def registered?(_object) + false + end + + def superclass_proxy_for(*_args) + raise_lifecycle_message + end + + def new_scope + Space.new + end + + private + + def raise_lifecycle_message + raise OutsideOfExampleError, + "The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported." + end + end + + # @private + class Space + attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex + + def initialize + @proxies = {} + @any_instance_recorders = {} + @constant_mutators = [] + @expectation_ordering = OrderGroup.new + @proxy_mutex = new_mutex + @any_instance_mutex = new_mutex + end + + def new_scope + NestedSpace.new(self) + end + + def verify_all + proxies.values.each { |proxy| proxy.verify } + any_instance_recorders.each_value { |recorder| recorder.verify } + end + + def reset_all + proxies.each_value { |proxy| proxy.reset } + @constant_mutators.reverse.each { |mut| mut.idempotently_reset } + any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! } + any_instance_recorders.clear + end + + def register_constant_mutator(mutator) + @constant_mutators << mutator + end + + def constant_mutator_for(name) + @constant_mutators.find { |m| m.full_constant_name == name } + end + + def any_instance_recorder_for(klass, only_return_existing=false) + any_instance_mutex.synchronize do + id = klass.__id__ + any_instance_recorders.fetch(id) do + return nil if only_return_existing + any_instance_recorder_not_found_for(id, klass) + end + end + end + + def any_instance_proxy_for(klass) + AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass)) + end + + def proxies_of(klass) + proxies.values.select { |proxy| klass === proxy.object } + end + + def proxy_for(object) + proxy_mutex.synchronize do + id = id_for(object) + proxies.fetch(id) { proxy_not_found_for(id, object) } + end + end + + def superclass_proxy_for(klass) + proxy_mutex.synchronize do + id = id_for(klass) + proxies.fetch(id) { superclass_proxy_not_found_for(id, klass) } + end + end + + alias ensure_registered proxy_for + + def registered?(object) + proxies.key?(id_for object) + end + + def any_instance_recorders_from_ancestry_of(object) + # Optimization: `any_instance` is a feature we generally + # recommend not using, so we can often early exit here + # without doing an O(N) linear search over the number of + # ancestors in the object's class hierarchy. + return [] if any_instance_recorders.empty? + + # We access the ancestors through the singleton class, to avoid calling + # `class` in case `class` has been stubbed. + (class << object; ancestors; end).map do |klass| + any_instance_recorders[klass.__id__] + end.compact + end + + private + + def new_mutex + Support::ReentrantMutex.new + end + + def proxy_not_found_for(id, object) + proxies[id] = case object + when NilClass then ProxyForNil.new(@expectation_ordering) + when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering) + when Class + class_proxy_with_callback_verification_strategy(object, CallbackInvocationStrategy.new) + else + if RSpec::Mocks.configuration.verify_partial_doubles? + VerifyingPartialDoubleProxy.new(object, @expectation_ordering) + else + PartialDoubleProxy.new(object, @expectation_ordering) + end + end + end + + def superclass_proxy_not_found_for(id, object) + raise "superclass_proxy_not_found_for called with something that is not a class" unless Class === object + proxies[id] = class_proxy_with_callback_verification_strategy(object, NoCallbackInvocationStrategy.new) + end + + def class_proxy_with_callback_verification_strategy(object, strategy) + if RSpec::Mocks.configuration.verify_partial_doubles? + VerifyingPartialClassDoubleProxy.new( + self, + object, + @expectation_ordering, + strategy + ) + else + PartialClassDoubleProxy.new(self, object, @expectation_ordering) + end + end + + def any_instance_recorder_not_found_for(id, klass) + any_instance_recorders[id] = AnyInstance::Recorder.new(klass) + end + + if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2 + require 'securerandom' + + def id_for(object) + id = object.__id__ + + return id if object.equal?(::ObjectSpace._id2ref(id)) + # this suggests that object.__id__ is proxying through to some wrapped object + + object.instance_exec do + @__id_for_rspec_mocks_space ||= ::SecureRandom.uuid + end + end + else + def id_for(object) + object.__id__ + end + end + end + + # @private + class NestedSpace < Space + def initialize(parent) + @parent = parent + super() + end + + def proxies_of(klass) + super + @parent.proxies_of(klass) + end + + def constant_mutator_for(name) + super || @parent.constant_mutator_for(name) + end + + def registered?(object) + super || @parent.registered?(object) + end + + private + + def proxy_not_found_for(id, object) + @parent.proxies[id] || super + end + + def any_instance_recorder_not_found_for(id, klass) + @parent.any_instance_recorders[id] || super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/standalone.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/standalone.rb new file mode 100644 index 0000000000..74317b01bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/standalone.rb @@ -0,0 +1,3 @@ +require 'rspec/mocks' +extend RSpec::Mocks::ExampleMethods +RSpec::Mocks.setup diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/syntax.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/syntax.rb new file mode 100644 index 0000000000..6305ab9619 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/syntax.rb @@ -0,0 +1,325 @@ +module RSpec + module Mocks + # @api private + # Provides methods for enabling and disabling the available syntaxes + # provided by rspec-mocks. + module Syntax + # @private + def self.warn_about_should! + @warn_about_should = true + end + + # @private + def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`") + if @warn_about_should + RSpec.deprecate( + "Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax", + :replacement => replacement + ) + + @warn_about_should = false + end + end + + # @api private + # Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). + def self.enable_should(syntax_host=default_should_syntax_host) + @warn_about_should = false if syntax_host == default_should_syntax_host + return if should_enabled?(syntax_host) + + syntax_host.class_exec do + def should_receive(message, opts={}, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.expect_message(self, message, opts, &block) + end + + def should_not_receive(message, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.expect_message(self, message, {}, &block).never + end + + def stub(message_or_hash, opts={}, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + if ::Hash === message_or_hash + message_or_hash.each { |message, value| stub(message).and_return value } + else + ::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block) + end + end + + def unstub(message) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to receive(...).and_call_original` or explicitly enable `:should`") + ::RSpec::Mocks.space.proxy_for(self).remove_stub(message) + end + + def stub_chain(*chain, &blk) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk) + end + + def as_null_object + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + @_null_object = true + ::RSpec::Mocks.space.proxy_for(self).as_null_object + end + + def null_object? + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + defined?(@_null_object) + end + + def received_message?(message, *args, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block) + end + + unless Class.respond_to? :any_instance + Class.class_exec do + def any_instance + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.space.any_instance_proxy_for(self) + end + end + end + end + end + + # @api private + # Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). + def self.disable_should(syntax_host=default_should_syntax_host) + return unless should_enabled?(syntax_host) + + syntax_host.class_exec do + undef should_receive + undef should_not_receive + undef stub + undef unstub + undef stub_chain + undef as_null_object + undef null_object? + undef received_message? + end + + Class.class_exec do + undef any_instance + end + end + + # @api private + # Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). + def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods) + return if expect_enabled?(syntax_host) + + syntax_host.class_exec do + def receive(method_name, &block) + Matchers::Receive.new(method_name, block) + end + + def receive_messages(message_return_value_hash) + matcher = Matchers::ReceiveMessages.new(message_return_value_hash) + matcher.warn_about_block if block_given? + matcher + end + + def receive_message_chain(*messages, &block) + Matchers::ReceiveMessageChain.new(messages, &block) + end + + def allow(target) + AllowanceTarget.new(target) + end + + def expect_any_instance_of(klass) + AnyInstanceExpectationTarget.new(klass) + end + + def allow_any_instance_of(klass) + AnyInstanceAllowanceTarget.new(klass) + end + end + + RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do + def expect(target) + ExpectationTarget.new(target) + end + end + end + + # @api private + # Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). + def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods) + return unless expect_enabled?(syntax_host) + + syntax_host.class_exec do + undef receive + undef receive_messages + undef receive_message_chain + undef allow + undef expect_any_instance_of + undef allow_any_instance_of + end + + RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do + undef expect + end + end + + # @api private + # Indicates whether or not the should syntax is enabled. + def self.should_enabled?(syntax_host=default_should_syntax_host) + syntax_host.method_defined?(:should_receive) + end + + # @api private + # Indicates whether or not the expect syntax is enabled. + def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods) + syntax_host.method_defined?(:allow) + end + + # @api private + # Determines where the methods like `should_receive`, and `stub` are added. + def self.default_should_syntax_host + # JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil` + # yet `BasicObject` still exists and patching onto ::Object breaks things + # e.g. SimpleDelegator expectations won't work + # + # See: https://github.com/jruby/jruby/issues/814 + if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8 + return ::BasicObject + end + + # On 1.8.7, Object.ancestors.last == Kernel but + # things blow up if we include `RSpec::Mocks::Methods` + # into Kernel...not sure why. + return Object unless defined?(::BasicObject) + + # MacRuby has BasicObject but it's not the root class. + return Object unless Object.ancestors.last == ::BasicObject + + ::BasicObject + end + end + end +end + +if defined?(BasicObject) + # The legacy `:should` syntax adds the following methods directly to + # `BasicObject` so that they are available off of any object. Note, however, + # that this syntax does not always play nice with delegate/proxy objects. + # We recommend you use the non-monkeypatching `:expect` syntax instead. + # @see Class + class BasicObject + # @method should_receive + # Sets an expectation that this object should receive a message before + # the end of the example. + # + # @example + # logger = double('logger') + # thing_that_logs = ThingThatLogs.new(logger) + # logger.should_receive(:log) + # thing_that_logs.do_something_that_logs_a_message + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#expect + + # @method should_not_receive + # Sets and expectation that this object should _not_ receive a message + # during this example. + # @see RSpec::Mocks::ExampleMethods#expect + + # @method stub + # Tells the object to respond to the message with the specified value. + # + # @example + # counter.stub(:count).and_return(37) + # counter.stub(:count => 37) + # counter.stub(:count) { 37 } + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#allow + + # @method unstub + # Removes a stub. On a double, the object will no longer respond to + # `message`. On a real object, the original method (if it exists) is + # restored. + # + # This is rarely used, but can be useful when a stub is set up during a + # shared `before` hook for the common case, but you want to replace it + # for a special case. + # + # @note This is only available when you have enabled the `should` syntax. + + # @method stub_chain + # @overload stub_chain(method1, method2) + # @overload stub_chain("method1.method2") + # @overload stub_chain(method1, method_to_value_hash) + # + # Stubs a chain of methods. + # + # ## Warning: + # + # Chains can be arbitrarily long, which makes it quite painless to + # violate the Law of Demeter in violent ways, so you should consider any + # use of `stub_chain` a code smell. Even though not all code smells + # indicate real problems (think fluent interfaces), `stub_chain` still + # results in brittle examples. For example, if you write + # `foo.stub_chain(:bar, :baz => 37)` in a spec and then the + # implementation calls `foo.baz.bar`, the stub will not work. + # + # @example + # double.stub_chain("foo.bar") { :baz } + # double.stub_chain(:foo, :bar => :baz) + # double.stub_chain(:foo, :bar) { :baz } + # + # # Given any of ^^ these three forms ^^: + # double.foo.bar # => :baz + # + # # Common use in Rails/ActiveRecord: + # Article.stub_chain("recent.published") { [Article.new] } + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#receive_message_chain + + # @method as_null_object + # Tells the object to respond to all messages. If specific stub values + # are declared, they'll work as expected. If not, the receiver is + # returned. + # + # @note This is only available when you have enabled the `should` syntax. + + # @method null_object? + # Returns true if this object has received `as_null_object` + # + # @note This is only available when you have enabled the `should` syntax. + end +end + +# The legacy `:should` syntax adds the `any_instance` to `Class`. +# We generally recommend you use the newer `:expect` syntax instead, +# which allows you to stub any instance of a class using +# `allow_any_instance_of(klass)` or mock any instance using +# `expect_any_instance_of(klass)`. +# @see BasicObject +class Class + # @method any_instance + # Used to set stubs and message expectations on any instance of a given + # class. Returns a [Recorder](Recorder), which records messages like + # `stub` and `should_receive` for later playback on instances of the + # class. + # + # @example + # Car.any_instance.should_receive(:go) + # race = Race.new + # race.cars << Car.new + # race.go # assuming this delegates to all of its cars + # # this example would pass + # + # Account.any_instance.stub(:balance) { Money.new(:USD, 25) } + # Account.new.balance # => Money.new(:USD, 25)) + # + # @return [Recorder] + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#expect_any_instance_of + # @see RSpec::Mocks::ExampleMethods#allow_any_instance_of +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/targets.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/targets.rb new file mode 100644 index 0000000000..7130e895a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/targets.rb @@ -0,0 +1,124 @@ +module RSpec + module Mocks + # @private + module TargetDelegationClassMethods + def delegate_to(matcher_method) + define_method(:to) do |matcher, &block| + unless matcher_allowed?(matcher) + raise_unsupported_matcher(:to, matcher) + end + define_matcher(matcher, matcher_method, &block) + end + end + + def delegate_not_to(matcher_method, options={}) + method_name = options.fetch(:from) + define_method(method_name) do |matcher, &block| + case matcher + when Matchers::Receive, Matchers::HaveReceived + define_matcher(matcher, matcher_method, &block) + when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain + raise_negation_unsupported(method_name, matcher) + else + raise_unsupported_matcher(method_name, matcher) + end + end + end + + def disallow_negation(method_name) + define_method(method_name) do |matcher, *_args| + raise_negation_unsupported(method_name, matcher) + end + end + end + + # @private + module TargetDelegationInstanceMethods + attr_reader :target + + private + + def matcher_allowed?(matcher) + Matchers::Matcher === matcher + end + + def define_matcher(matcher, name, &block) + matcher.__send__(name, target, &block) + end + + def raise_unsupported_matcher(method_name, matcher) + raise UnsupportedMatcherError, + "only the `receive`, `have_received` and `receive_messages` matchers are supported " \ + "with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}" + end + + def raise_negation_unsupported(method_name, matcher) + raise NegationUnsupportedError, + "`#{expression}(...).#{method_name} #{matcher.name}` is not supported since it " \ + "doesn't really make sense. What would it even mean?" + end + end + + # @private + class TargetBase + def initialize(target) + @target = target + end + + extend TargetDelegationClassMethods + include TargetDelegationInstanceMethods + end + + # @private + module ExpectationTargetMethods + extend TargetDelegationClassMethods + include TargetDelegationInstanceMethods + + delegate_to :setup_expectation + delegate_not_to :setup_negative_expectation, :from => :not_to + delegate_not_to :setup_negative_expectation, :from => :to_not + + def expression + :expect + end + end + + # @private + class ExpectationTarget < TargetBase + include ExpectationTargetMethods + end + + # @private + class AllowanceTarget < TargetBase + def expression + :allow + end + + delegate_to :setup_allowance + disallow_negation :not_to + disallow_negation :to_not + end + + # @private + class AnyInstanceAllowanceTarget < TargetBase + def expression + :allow_any_instance_of + end + + delegate_to :setup_any_instance_allowance + disallow_negation :not_to + disallow_negation :to_not + end + + # @private + class AnyInstanceExpectationTarget < TargetBase + def expression + :expect_any_instance_of + end + + delegate_to :setup_any_instance_expectation + delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to + delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/test_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/test_double.rb new file mode 100644 index 0000000000..e8bfbeb59d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/test_double.rb @@ -0,0 +1,171 @@ +module RSpec + module Mocks + # Implements the methods needed for a pure test double. RSpec::Mocks::Double + # includes this module, and it is provided for cases where you want a + # pure test double without subclassing RSpec::Mocks::Double. + module TestDouble + # Creates a new test double with a `name` (that will be used in error + # messages only) + def initialize(name=nil, stubs={}) + @__expired = false + if Hash === name && stubs.empty? + stubs = name + @name = nil + else + @name = name + end + assign_stubs(stubs) + end + + # Tells the object to respond to all messages. If specific stub values + # are declared, they'll work as expected. If not, the receiver is + # returned. + def as_null_object + __mock_proxy.as_null_object + end + + # Returns true if this object has received `as_null_object` + def null_object? + __mock_proxy.null_object? + end + + # This allows for comparing the mock to other objects that proxy such as + # ActiveRecords belongs_to proxy objects. By making the other object run + # the comparison, we're sure the call gets delegated to the proxy + # target. + def ==(other) + other == __mock_proxy + end + + # @private + def inspect + TestDoubleFormatter.format(self) + end + + # @private + def to_s + inspect.tr('<', '[').tr('>', ']') + end + + # @private + def respond_to?(message, incl_private=false) + __mock_proxy.null_object? ? true : super + end + + # @private + def __build_mock_proxy_unless_expired(order_group) + __raise_expired_error || __build_mock_proxy(order_group) + end + + # @private + def __disallow_further_usage! + @__expired = true + end + + # Override for default freeze implementation to prevent freezing of test + # doubles. + def freeze + RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.") + self + end + + private + + def method_missing(message, *args, &block) + proxy = __mock_proxy + proxy.record_message_received(message, *args, &block) + + if proxy.null_object? + case message + when :to_int then return 0 + when :to_a, :to_ary then return nil + when :to_str then return to_s + else return self + end + end + + # Defined private and protected methods will still trigger `method_missing` + # when called publicly. We want ruby's method visibility error to get raised, + # so we simply delegate to `super` in that case. + # ...well, we would delegate to `super`, but there's a JRuby + # bug, so we raise our own visibility error instead: + # https://github.com/jruby/jruby/issues/1398 + visibility = proxy.visibility_for(message) + if visibility == :private || visibility == :protected + ErrorGenerator.new(self).raise_non_public_error( + message, visibility + ) + end + + # Required wrapping doubles in an Array on Ruby 1.9.2 + raise NoMethodError if [:to_a, :to_ary].include? message + proxy.raise_unexpected_message_error(message, args) + end + + def assign_stubs(stubs) + stubs.each_pair do |message, response| + __mock_proxy.add_simple_stub(message, response) + end + end + + def __mock_proxy + ::RSpec::Mocks.space.proxy_for(self) + end + + def __build_mock_proxy(order_group) + TestDoubleProxy.new(self, order_group) + end + + def __raise_expired_error + return false unless @__expired + ErrorGenerator.new(self).raise_expired_test_double_error + end + + def initialize_copy(other) + as_null_object if other.null_object? + super + end + end + + # A generic test double object. `double`, `instance_double` and friends + # return an instance of this. + class Double + include TestDouble + end + + # @private + module TestDoubleFormatter + def self.format(dbl, unwrap=false) + format = "#{type_desc(dbl)}#{verified_module_desc(dbl)} #{name_desc(dbl)}" + return format if unwrap + "#<#{format}>" + end + + class << self + private + + def type_desc(dbl) + case dbl + when InstanceVerifyingDouble then "InstanceDouble" + when ClassVerifyingDouble then "ClassDouble" + when ObjectVerifyingDouble then "ObjectDouble" + else "Double" + end + end + + # @private + IVAR_GET = Object.instance_method(:instance_variable_get) + + def verified_module_desc(dbl) + return nil unless VerifyingDouble === dbl + "(#{IVAR_GET.bind(dbl).call(:@doubled_module).description})" + end + + def name_desc(dbl) + return "(anonymous)" unless (name = IVAR_GET.bind(dbl).call(:@name)) + name.inspect + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_double.rb new file mode 100644 index 0000000000..39723b9fc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_double.rb @@ -0,0 +1,129 @@ +RSpec::Support.require_rspec_mocks 'verifying_proxy' + +module RSpec + module Mocks + # @private + module VerifyingDouble + def respond_to?(message, include_private=false) + return super unless null_object? + + method_ref = __mock_proxy.method_reference[message] + + case method_ref.visibility + when :public then true + when :private then include_private + when :protected then include_private || RUBY_VERSION.to_f < 2.0 + else !method_ref.unimplemented? + end + end + + def method_missing(message, *args, &block) + # Null object conditional is an optimization. If not a null object, + # validity of method expectations will have been checked at definition + # time. + if null_object? + if @__sending_message == message + __mock_proxy.ensure_implemented(message) + else + __mock_proxy.ensure_publicly_implemented(message, self) + end + + __mock_proxy.validate_arguments!(message, args) + end + + super + end + + # @private + module SilentIO + def self.method_missing(*); end + def self.respond_to?(*) + true + end + end + + # Redefining `__send__` causes ruby to issue a warning. + old, $stderr = $stderr, SilentIO + def __send__(name, *args, &block) + @__sending_message = name + super + ensure + @__sending_message = nil + end + $stderr = old + + def send(name, *args, &block) + __send__(name, *args, &block) + end + + def initialize(doubled_module, *args) + @doubled_module = doubled_module + + possible_name = args.first + name = if String === possible_name || Symbol === possible_name + args.shift + end + + super(name, *args) + @__sending_message = nil + end + end + + # A mock providing a custom proxy that can verify the validity of any + # method stubs or expectations against the public instance methods of the + # given class. + # + # @private + class InstanceVerifyingDouble + include TestDouble + include VerifyingDouble + + def __build_mock_proxy(order_group) + VerifyingProxy.new(self, order_group, + @doubled_module, + InstanceMethodReference + ) + end + end + + # An awkward module necessary because we cannot otherwise have + # ClassVerifyingDouble inherit from Module and still share these methods. + # + # @private + module ObjectVerifyingDoubleMethods + include TestDouble + include VerifyingDouble + + def as_stubbed_const(options={}) + ConstantMutator.stub(@doubled_module.const_to_replace, self, options) + self + end + + private + + def __build_mock_proxy(order_group) + VerifyingProxy.new(self, order_group, + @doubled_module, + ObjectMethodReference + ) + end + end + + # Similar to an InstanceVerifyingDouble, except that it verifies against + # public methods of the given object. + # + # @private + class ObjectVerifyingDouble + include ObjectVerifyingDoubleMethods + end + + # Effectively the same as an ObjectVerifyingDouble (since a class is a type + # of object), except with Module in the inheritance chain so that + # transferring nested constants to work. + # + # @private + class ClassVerifyingDouble < Module + include ObjectVerifyingDoubleMethods + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_message_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_message_expectation.rb new file mode 100644 index 0000000000..c1faa5d3c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_message_expectation.rb @@ -0,0 +1,54 @@ +RSpec::Support.require_rspec_support 'method_signature_verifier' + +module RSpec + module Mocks + # A message expectation that knows about the real implementation of the + # message being expected, so that it can verify that any expectations + # have the valid arguments. + # @api private + class VerifyingMessageExpectation < MessageExpectation + # A level of indirection is used here rather than just passing in the + # method itself, since method look up is expensive and we only want to + # do it if actually needed. + # + # Conceptually the method reference makes more sense as a constructor + # argument since it should be immutable, but it is significantly more + # straight forward to build the object in pieces so for now it stays as + # an accessor. + attr_accessor :method_reference + + def initialize(*args) + super + end + + # @private + def with(*args, &block) + super(*args, &block).tap do + validate_expected_arguments! do |signature| + example_call_site_args = [:an_arg] * signature.min_non_kw_args + example_call_site_args << :kw_args_hash if signature.required_kw_args.any? + @argument_list_matcher.resolve_expected_args_based_on(example_call_site_args) + end + end + end + + private + + def validate_expected_arguments! + return if method_reference.nil? + + method_reference.with_signature do |signature| + args = yield signature + verifier = Support::LooseSignatureVerifier.new(signature, args) + + unless verifier.valid? + # Fail fast is required, otherwise the message expectation will fail + # as well ("expected method not called") and clobber this one. + @failed_fast = true + @error_generator.raise_invalid_arguments_error(verifier) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_proxy.rb new file mode 100644 index 0000000000..b39871c878 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/verifying_proxy.rb @@ -0,0 +1,220 @@ +RSpec::Support.require_rspec_mocks 'verifying_message_expectation' +RSpec::Support.require_rspec_mocks 'method_reference' + +module RSpec + module Mocks + # @private + class CallbackInvocationStrategy + def call(doubled_module) + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call doubled_module + end + end + end + + # @private + class NoCallbackInvocationStrategy + def call(_doubled_module) + end + end + + # @private + module VerifyingProxyMethods + def add_stub(method_name, opts={}, &implementation) + ensure_implemented(method_name) + super + end + + def add_simple_stub(method_name, *args) + ensure_implemented(method_name) + super + end + + def add_message_expectation(method_name, opts={}, &block) + ensure_implemented(method_name) + super + end + + def ensure_implemented(method_name) + return unless method_reference[method_name].unimplemented? + + @error_generator.raise_unimplemented_error( + @doubled_module, + method_name, + @object + ) + end + + def ensure_publicly_implemented(method_name, _object) + ensure_implemented(method_name) + visibility = method_reference[method_name].visibility + + return if visibility == :public + @error_generator.raise_non_public_error(method_name, visibility) + end + end + + # A verifying proxy mostly acts like a normal proxy, except that it + # contains extra logic to try and determine the validity of any expectation + # set on it. This includes whether or not methods have been defined and the + # validatiy of arguments on method calls. + # + # In all other ways this behaves like a normal proxy. It only adds the + # verification behaviour to specific methods then delegates to the parent + # implementation. + # + # These checks are only activated if the doubled class has already been + # loaded, otherwise they are disabled. This allows for testing in + # isolation. + # + # @private + class VerifyingProxy < TestDoubleProxy + include VerifyingProxyMethods + + def initialize(object, order_group, doubled_module, method_reference_class) + super(object, order_group) + @object = object + @doubled_module = doubled_module + @method_reference_class = method_reference_class + + # A custom method double is required to pass through a way to lookup + # methods to determine their parameters. This is only relevant if the doubled + # class is loaded. + @method_doubles = Hash.new do |h, k| + h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k]) + end + end + + def method_reference + @method_reference ||= Hash.new do |h, k| + h[k] = @method_reference_class.for(@doubled_module, k) + end + end + + def visibility_for(method_name) + method_reference[method_name].visibility + end + + def validate_arguments!(method_name, args) + @method_doubles[method_name].validate_arguments!(args) + end + end + + # @private + DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new + + # @private + class VerifyingPartialDoubleProxy < PartialDoubleProxy + include VerifyingProxyMethods + + def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY) + super(object, expectation_ordering) + @doubled_module = DirectObjectReference.new(object) + + # A custom method double is required to pass through a way to lookup + # methods to determine their parameters. + @method_doubles = Hash.new do |h, k| + h[k] = VerifyingExistingMethodDouble.for(object, k, self) + end + + optional_callback_invocation_strategy.call(@doubled_module) + end + + def ensure_implemented(_method_name) + return if Mocks.configuration.temporarily_suppress_partial_double_verification + super + end + + def method_reference + @method_doubles + end + end + + # @private + class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy + include PartialClassDoubleProxyMethods + end + + # @private + class VerifyingMethodDouble < MethodDouble + def initialize(object, method_name, proxy, method_reference) + super(object, method_name, proxy) + @method_reference = method_reference + end + + def message_expectation_class + VerifyingMessageExpectation + end + + def add_expectation(*args, &block) + # explict params necessary for 1.8.7 see #626 + super(*args, &block).tap { |x| x.method_reference = @method_reference } + end + + def add_stub(*args, &block) + # explict params necessary for 1.8.7 see #626 + super(*args, &block).tap { |x| x.method_reference = @method_reference } + end + + def proxy_method_invoked(obj, *args, &block) + validate_arguments!(args) + super + end + + def validate_arguments!(actual_args) + @method_reference.with_signature do |signature| + verifier = Support::StrictSignatureVerifier.new(signature, actual_args) + raise ArgumentError, verifier.error_message unless verifier.valid? + end + end + end + + # A VerifyingMethodDouble fetches the method to verify against from the + # original object, using a MethodReference. This works for pure doubles, + # but when the original object is itself the one being modified we need to + # collapse the reference and the method double into a single object so that + # we can access the original pristine method definition. + # + # @private + class VerifyingExistingMethodDouble < VerifyingMethodDouble + def initialize(object, method_name, proxy) + super(object, method_name, proxy, self) + + @valid_method = object.respond_to?(method_name, true) + + # Trigger an eager find of the original method since if we find it any + # later we end up getting a stubbed method with incorrect arity. + save_original_implementation_callable! + end + + def with_signature + yield Support::MethodSignature.new(original_implementation_callable) + end + + def unimplemented? + !@valid_method + end + + def self.for(object, method_name, proxy) + if ClassNewMethodReference.applies_to?(method_name) { object } + VerifyingExistingClassNewMethodDouble + elsif Mocks.configuration.temporarily_suppress_partial_double_verification + MethodDouble + else + self + end.new(object, method_name, proxy) + end + end + + # Used in place of a `VerifyingExistingMethodDouble` for the specific case + # of mocking or stubbing a `new` method on a class. In this case, we substitute + # the method signature from `#initialize` since new's signature is just `*args`. + # + # @private + class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble + def with_signature + yield Support::MethodSignature.new(object.instance_method(:initialize)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/version.rb new file mode 100644 index 0000000000..98b74bd1fc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.10.2/lib/rspec/mocks/version.rb @@ -0,0 +1,9 @@ +module RSpec + module Mocks + # Version information for RSpec mocks. + module Version + # Version of RSpec mocks currently in use in SemVer format. + STRING = '3.10.2' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.document b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.document new file mode 100644 index 0000000000..52a564f65f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.document @@ -0,0 +1,5 @@ +lib/**/*.rb +- +README.md +LICENSE.md +Changelog.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.yardopts b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.yardopts new file mode 100644 index 0000000000..9555b8e5c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/.yardopts @@ -0,0 +1,6 @@ +--exclude features +--no-private +--markup markdown +- +Changelog.md +LICENSE.md diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/Changelog.md new file mode 100644 index 0000000000..add395016b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/Changelog.md @@ -0,0 +1,1245 @@ +### Development +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.12.3...3-12-maintenance) + +### 3.12.3 / 2023-01-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.12.2...v3.12.3) + +Bug Fixes: + +* Fix keyword delegation in `send` for verifying doubles on Ruby 3. + (Charlie Honig, #1485) + +### 3.12.2 / 2023-01-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.12.1...v3.12.2) + +Bug Fixes: + +* Fix implementation blocks for mocks using keyword arguments on Ruby 3.2.0. + (Adam Steel, #1508) +* Fix keyword argument assertions when mocking using `with` on Ruby 3.2.0. + (Slava Kardakov, Benoit Tigeot, Phil Pirozhkov, Benoit Daloze, #1514) + +### 3.12.1 / 2022-12-10 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.12.0...v3.12.1) + +Bug Fixes: + +* Remove empty diff marker when a diff only contains console codes. (Jon Rowe, #1506) +* Show keyword vs hash diff marker when arguments are not `==` (Jon Rowe, #1506) +* Change check to detect frozen objects to rescue errors rather than + pre-empting by checking `frozen?` due to some objects mis-behaving. + (Keegan Roth, #1401) +* Prevent unfulfilled expectations using `expect_any_instance_of` across a class + inheritance boundary from raising rather than failing. (Jon Rowe, #1496) +* Prevent a misleading error message when using `allow(...).not_to` with + unsupported matchers. (Phil Pirozhkov, #1503) + +### 3.12.0 / 2022-10-26 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.11.2...v3.12.0) + +Enhancements: + +* Improve diff output when diffing keyword arguments against hashes. + (Jean Boussier, #1461) + +### 3.11.2 / 2022-10-25 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.11.1...v3.11.2) + +Bug Fixes: + +* Use the original implementation of `Class.new` to detect overridden definitions + of `new` rather than the owner, fixing detection of "double aliased" methods + in Ruby 3 and above. (Benoit Daloze, #1470, #1476) +* Support keyword argument semantics when constraining argument expectations using + `with` on Ruby 3.0+ with `instance_double` (Andrii Malyshko, #1473) + +### 3.11.1 / 2022-03-31 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.11.0...v3.11.1) + +Bug Fixes: + +* Add extra `ruby2_keywords` calls to properly designate methods using + `*args` to pass keyword around, fixes an issue with TruffleRuby. + (Benoit Daloze, #1464) + +### 3.11.0 / 2022-02-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.3...v3.11.0) + +Enhancements: + +* Add `and_invoke` implementation for configuring responses to `receive` + (and `receive_messages`) with multiple callable objects. (Kyle Smith, #1411) + +### 3.10.3 / 2022-01-28 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.2...v3.10.3) + +Bug Fixes: + +* Suppress warning by setting `$VERBOSE` to nil. (Nobuyoshi Nakada, #1414) +* Support keyword argument semantics when constraining argument expectations using + `with` on Ruby 3.0+ (Yusuke Endoh, #1394) + +### 3.10.2 / 2021-01-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.1...v3.10.2) + +Bug Fixes: + +* Support keyword arguments with `and_call_original` on Ruby 3.0. + (Bryan Powell, #1385) +* `RSpec::Mocks::Constant#previously_defined?` is now always a boolean. + (Phil Pirozhkov, #1397) +* Support keyword arguments on Ruby 3.0 when used with `expect_any_instance_of` + or `allow_any_instance_of` with `and_call_original`. + (Jess Hottenstein, #1407) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Issue `ArgumentError` rather than `TypeError` when unsupported methods on + unsupported objects are attempted to be stubbed. (@zhisme, #1357) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.9.1...v3.10.0) + +Enhancements: +* Add the ability to set a custom error generator in `MessageExpectation`. + This will allow rspec-expectations to inject a custom failure message. + (Benoit Tigeot and Nicolas Zermati, #1312) +* Return the result of the block passed to `RSpec::Mocks.with_temporary_scope` + when block run. (@expeehaa, #1329) + +### 3.9.1 / 2019-12-31 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Trigger `RSpec::Mocks.configuration.verifying_double_callbacks` when using + `allow_any_instance_of` or `expect_any_instance_of` (Daniel Orner, #1309) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.2...v3.9.0) + +Enhancements: + +* Improve thread safety of message expectations by using Mutex to prevent + deadlocking errors. (Ry Biesemeyer, #1236) +* Add the ability to use `time` as an alias for `times`. For example: + `expect(Class).to receive(:method).exactly(1).time`. + (Pistos, Benoit Tigeot, #1271) + +### 3.8.2 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.1...v3.8.2) + +* Allow `array_including` argument matchers to be nested. + (Emmanuel Delmas, #1291) + +### 3.8.1 / 2019-06-13 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Ensure stubbing methods does not change their visibility. + (Kevin Boschert, #1277) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.7.0...v3.8.0) + +Bug Fixes: + +* Issue error when encountering invalid "counted" negative message expectations. + (Sergiy Yarinovskiy, #1212) +* Ensure `allow_any_instance_of` and `expect_any_instance_of` can be temporarily + supressed. (Jon Rowe, #1228) +* Ensure `expect_any_instance_of(double).to_not have_received(:some_method)` + fails gracefully (as its not supported) rather than issuing a `NoMethodError`. + (Maxim Krizhanovsky, #1231) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #1165) + +Bug Fixes: + +* Fix `hash_including` and `hash_excluding` so that they work against + subclasses of `Hash`. (Aaron Rosenberg, #1167) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0.beta2...v3.6.0) + +Bug Fixes: + +* Fix "instance variable @color not initialized" warning when using + rspec-mocks without rspec-core. (Myron Marston, #1142) +* Restore aliased module methods properly when stubbing on 1.8.7. + (Samuel Giddins, #1144) +* Allow a message chain expectation to be constrained by argument(s). + (Jon Rowe, #1156) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.6.0.beta1...v3.6.0.beta2) + +Enhancements: + +* Add new `without_partial_double_verification { }` API that lets you + temporarily turn off partial double verification for an example. + (Jon Rowe, #1104) + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Return the test double instance form `#freeze` (Alessandro Berardi, #1109) +* Allow the special logic for stubbing `new` to work when `.method` has + been redefined. (Proby, #1119) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Provides a nice string representation of + `RSpec::Mocks::MessageExpectation` (Myron Marston, #1095) + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: + +* Add `and_throw` to any instance handling. (Tobias Bühlmann, #1068) + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta2...v3.5.0.beta3) + +Enhancements: + +* Issue warning when attempting to use unsupported + `allow(...).to receive(...).ordered`. (Jon Rowe, #1000) +* Add `rspec/mocks/minitest_integration`, to properly integrate rspec-mocks + with minitest. (Myron Marston, #1065) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.5.0.beta1...v3.5.0.beta2) + +Enhancements: + +* Improve error message displayed when using `and_wrap_original` on pure test + doubles. (betesh, #1063) + +Bug Fixes: + +* Fix issue that prevented `receive_message_chain(...).with(...)` working + correctly on "any instance" mocks. (Jon Rowe, #1061) + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.4.1...v3.5.0.beta1) + +Bug Fixes: + +* Allow `any_instance_of(...).to receive(...)` to use `and_yield` multiple + times. (Kilian Cirera Sant, #1054) +* Allow matchers which inherit from `rspec-mocks` matchers to be used for + `allow`. (Andrew Kozin, #1056) +* Prevent stubbing `respond_to?` on partial doubles from causing infinite + recursion. (Jon Rowe, #1013) +* Prevent aliased methods from disapearing after being mocked with + `any_instance` (regression from #1043). (Joe Rafaniello, #1060) + +### 3.4.1 / 2016-01-10 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix `any_instance` to work properly on Ruby 2.3. (Joe Rafaniello, #1043) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.2...v3.4.0) + +Enhancements: + +* Make `expect(...).to have_received` work without relying upon + rspec-expectations. (Myron Marston, #978) +* Add option for failing tests when expectations are set on `nil`. + (Liz Rush, #983) + +Bug Fixes: + +* Fix `have_received { ... }` so that any block passed when the message + was received is forwarded to the `have_received` block. (Myron Marston, #1006) +* Fix infinite loop in error generator when stubbing `respond_to?`. + (Alex Dowad, #1022) +* Fix issue with using `receive` on subclasses (at a class level) with 1.8.7. + (Alex Dowad, #1026) + +### 3.3.2 / 2015-07-15 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.1...v3.3.2) + +Bug Fixes: + +* Prevent thread deadlock errors during proxy creation (e.g. when using + `before_verifying_doubles` callbacks). (Jon Rowe, #980, #979) + +### 3.3.1 / 2015-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.3.0...v3.3.1) + +Bug Fixes: + +* Fix bug in `before_verifying_double` callback logic that caused it to be called + once for each class in the ancestor list when mocking or stubbing a class. Now + it is only called for the mocked or stubbed class, as you would expect. (Sam + Phippen, #974) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.2.1...v3.3.0) + +Enhancements: + +* When stubbing `new` on `MyClass` or `class_double(MyClass)`, use the + method signature from `MyClass#initialize` to verify arguments. + (Myron Marston, #886) +* Use matcher descriptions when generating description of received arguments + for mock expectation failures. (Tim Wade, #891) +* Avoid loading `stringio` unnecessarily. (Myron Marston, #894) +* Verifying doubles failure messages now distinguish between class and instance + level methods. (Tim Wade, #896, #908) +* Improve mock expectation failure messages so that it combines both + number of times and the received arguments in the output. (John Ceh, #918) +* Improve how test doubles are represented in failure messages. + (Siva Gollapalli, Myron Marston, #932) +* Rename `RSpec::Mocks::Configuration#when_declaring_verifying_double` to + `RSpec::Mocks::Configuration#before_verifying_doubles` and utilise when + verifying partial doubles. (Jon Rowe, #940) +* Use rspec-support's `ObjectFormatter` for improved formatting of + arguments in failure messages so that, for example, full time + precisions is displayed for time objects. (Gavin Miller, Myron Marston, #955) + +Bug Fixes: + +* Ensure expectations that raise eagerly also raise during RSpec verification. + This means that if exceptions are caught inside test execution the test will + still fail. (Sam Phippen, #884) +* Fix `have_received(msg).with(args).exactly(n).times` and + `receive(msg).with(args).exactly(n).times` failure messages + for when the message was received the wrong number of times with + the specified args, and also received additional times with other + arguments. Previously it confusingly listed the arguments as being + mis-matched (even when the double was allowed to receive with any + args) rather than listing the count. (John Ceh, #918) +* Fix `any_args`/`anything` support so that we avoid calling `obj == anything` + on user objects that may have improperly implemented `==` in a way that + raises errors. (Myron Marston, #924) +* Fix edge case involving stubbing the same method on a class and a subclass + which previously hit a `NoMethodError` internally in RSpec. (Myron Marston #954) +* Fix edge case where the message received count would be incremented multiple + times for one failure. (Myron Marston, #957) +* Fix failure messages for when spies received the expected message with + different arguments and also received another message. (Maurício Linhares, #960) +* Silence whitespace-only diffs. (Myron Marston, #969) + +### 3.2.1 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Add missing `rspec/support/differ` require so that rspec-mocks can be + used w/o rspec-expectations (which also loads the differ and hided the + fact we forgot to require it). (Myron Marston, #893) +* Revert tracking of received arg mutation (added in 3.2.0 to provide an + error in a situation we can't support) as our implementation has side + effects on non-standard objects and there's no solution we could come + up with that always works. (Myron Marston, #900) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.3...v3.2.0) + +Enhancements: + +* Treat `any_args` as an arg splat, allowing it to match an arbitrary + number of args at any point in an arg list. (Myron Marston, #786) +* Print diffs when arguments in mock expectations are mismatched. + (Sam Phippen, #751) +* Support names for verified doubles (`instance_double`, `instance_spy`, + `class_double`, `class_spy`, `object_double`, `object_spy`). (Cezary + Baginski, #826) +* Make `array_including` and `hash_including` argument matchers composable. + (Sam Phippen, #819) +* Make `allow_any_instance_of(...).to receive(...).and_wrap_original` + work. (Ryan Fitzgerald, #869) + +Bug Fixes: + +* Provide a clear error when users wrongly combine `no_args` with + additional arguments (e.g. `expect().to receive().with(no_args, 1)`). + (Myron Marston, #786) +* Provide a clear error when users wrongly use `any_args` multiple times in the + same argument list (e.g. `expect().to receive().with(any_args, 1, any_args)`. + (Myron Marston, #786) +* Prevent the error generator from using user object #description methods. + See [#685](https://github.com/rspec/rspec-mocks/issues/685). + (Sam Phippen, #751) +* Make verified doubles declared as `(instance|class)_double(SomeConst)` + work properly when `SomeConst` has previously been stubbed. + `(instance|class)_double("SomeClass")` already worked properly. + (Myron Marston, #824) +* Add a matcher description for `receive`, `receive_messages` and + `receive_message_chain`. (Myron Marston, #828) +* Validate invocation args for null object verified doubles. + (Myron Marston, #829) +* Fix `RSpec::Mocks::Constant.original` when called with an invalid + constant to return an object indicating the constant name is invalid, + rather than blowing up. (Myron Marston, #833) +* Make `extend RSpec::Mocks::ExampleMethods` on any object work properly + to add the rspec-mocks API to that object. Previously, `expect` would + be undefined. (Myron Marston, #846) +* Fix `require 'rspec/mocks/standalone'` so that it only affects `main` + and not every object. It's really only intended to be used in a REPL + like IRB, but some gems have loaded it, thinking it needs to be loaded + when using rspec-mocks outside the context of rspec-core. + (Myron Marston, #846) +* Prevent message expectations from being modified by customization methods + (e.g. `with`) after they have been invoked. (Sam Phippen and Melanie Gilman, #837) +* Handle cases where a method stub cannot be removed due to something + external to RSpec monkeying with the method definition. This can + happen, for example, when you `file.reopen(io)` after previously + stubbing a method on the `file` object. (Myron Marston, #853) +* Provide a clear error when received message args are mutated before + a `have_received(...).with(...)` expectation. (Myron Marston, #868) + +### 3.1.3 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.2...v3.1.3) + +Bug Fixes: + +* Correct received messages count when used with `have_received` matcher. + (Jon Rowe, #793) +* Provide a clear error message when you use `allow_any_instance_of(...)` or + `expect_any_instance_of(...)` with the `have_received` matcher (they are + not intended to be used together and previously caused an odd internal + failure in rspec-mocks). (Jon Rowe, #799). +* Fix verified double `with` verification so that it applies to method + stubs. (Myron Marston, #790) + +### 3.1.2 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Provide a clear error message when you use `allow(...)` with the + `have_received` matcher (they are not intended to be used together + and previously caused an odd internal failure in rspec-mocks). (Jon Rowe, #788). + +### 3.1.1 / 2014-09-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Prevent included modules being detected as prepended modules on Ruby 2.0 + when using `any_instance_of(...)`. (Tony Novak, #781) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.4...v3.1.0) + +Enhancements: + +* Add spying methods (`spy`, `ìnstance_spy`, `class_spy` and `object_spy`) + which create doubles as null objects for use with spying in testing. (Sam + Phippen, #671) +* `have_received` matcher will raise "does not implement" errors correctly when + used with verifying doubles and partial doubles. (Xavier Shay, #722) +* Allow matchers to be used in place of keyword arguments in `with` + expectations. (Xavier Shay, #726) +* Add `thrice` modifier to message expectation interface as a synonym + for `exactly(3).times`. (Dennis Taylor, #753) +* Add more `thrice` synonyms e.g. `.at_least(:thrice)`, `.at_most(:thrice)`, + `receive(...).thrice` and `have_received(...).thrice`. (Jon Rowe, #754) +* Add `and_wrap_original` modifier for partial doubles to mutate the + response from a method. (Jon Rowe, #762) + +Bug Fixes: + +* Remove `any_number_of_times` from `any_instance` recorders that were + erroneously causing mention of the method in documentation. (Jon Rowe, #760) +* Prevent included modules being detected as prepended modules on Ruby 2.0. + (Eugene Kenny, #771) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Restore `kind_of(x)` to match using `arg.kind_of?(x)` (like RSpec 2) + rather than `x === arg`. (Jon Rowe, #750) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* `have_received` matcher will raise "does not implement" errors correctly when + used with verifying doubles and partial doubles. (Xavier Shay, #722) +* Make `double.as_null_object.dup` and `double.as_null_object.clone` + make the copies be null objects. (Myron Marston, #732) +* Don't inadvertently define `BasicObject` in 1.8.7. (Chris Griego, #739) + +### 3.0.2 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.1...v3.0.2) + +Bug Fixes: + +* Fix edge case that triggered "can't add a new key into hash during + iteration" during mock verification. (Sam Phippen, Myron Marston, #711) +* Fix verifying doubles so that when they accidentally leak into another + example, they provide the same clear error message that normal doubles + do. (Myron Marston, #718) +* Make `ordered` work with exact receive counts. (Sam Phippen, #713) + +### 3.0.1 / 2014-06-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0...v3.0.1) + +Bug Fixes: + +* Fix `receive_message_chain(...)` so that it supports `with` just like + `stub_chain` did. (Jon Rowe, #697) +* Fix regression in `expect_any_instance_of` so that it expects the + message on _any_ instance rather than on _every_ instance. + (Myron Marston, #699) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.rc1...v3.0.0) + +Bug Fixes: + +* Fix module prepend detection to work properly on ruby 2.0 for a case + where a module is extended onto itself. (Myron Marston) +* Fix `transfer_nested_constants` option so that transferred constants + get properly reset at the end of the example. (Myron Marston) +* Fix `config.transfer_nested_constants = true` so that you don't + erroneously get errors when stubbing a constant that is not a module + or a class. (Myron Marston) +* Fix regression that caused `double(:class => SomeClass)` to later + trigger infinite recursion. (Myron Marston) +* Fix bug in `have_received(...).with(...).ordered` where it was not + taking the args into account when checking the order. (Myron Marston) +* Fix bug in `have_received(...).ordered` where it was wrongly + considering stubs when checking the order. (Myron Marston) +* Message expectation matchers now show descriptions from argument + matchers when their expectations aren't met. (Jon Rowe) +* Display warning when encountering `TypeError` during instance method + staging on 2.0.0-p195, suffers from https://bugs.ruby-lang.org/issues/8686 + too. (Cezar Halmagean). + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.beta2...v3.0.0.rc1) + +Breaking Changes for 3.0.0: + +* Remove `RSpec::Mocks::TestDouble.extend_onto`. (Myron Marston) +* Remove `RSpec::Mocks::ConstantStubber`. (Jon Rowe) +* Make monkey-patch of Marshal to support dumping of stubbed objects opt-in. + (Xavier Shay) + +Enhancements: + +* Instead of crashing when cleaning up stub methods on a frozen object, it now + issues a warning explaining that it's impossible to clean up the stubs. + (Justin Coyne and Sam Phippen) +* Add meaningful descriptions to `anything`, `duck_type` and `instance_of` argument + matchers. (Jon Rowe) + +Bug Fixes: + +* Fix regression introduced in 3.0.0.beta2 that caused + `double.as_null_object.to_str` to return the double rather + than a string. (Myron Marston) +* Fix bug in `expect(dbl).to receive_message_chain(:foo, :bar)` where it was + not setting an expectation for the last message in the chain. + (Jonathan del Strother) +* Allow verifying partial doubles to have private methods stubbed. (Xavier Shay) +* Fix bug with allowing/expecting messages on Class objects which have had + their singleton class prepended to. (Jon Rowe) +* Fix an issue with 1.8.7 not running implementation blocks on partial doubles. + (Maurício Linhares) +* Prevent `StackLevelTooDeep` errors when stubbing an `any_instance` method that's + accessed in `inspect` by providing our own inspect output. (Jon Rowe) +* Fix bug in `any_instance` logic that did not allow you to mock or stub + private methods if `verify_partial_doubles` was configured. (Oren Dobzinski) +* Include useful error message when trying to observe an unimplemented method + on an any instance. (Xavier Shay) +* Fix `and_call_original` to work properly when multiple classes in an + inheritance hierarchy have been stubbed with the same method. (Myron Marston) +* Fix `any_instance` so that it updates existing instances that have + already been stubbed. (Myron Marston) +* Fix verified doubles so that their class name is included in failure + messages. (Myron Marston) +* Fix `expect_any_instance_of` so that when the message is received + on an individual instance that has been directly stubbed, it still + satisfies the expectation. (Sam Phippen, Myron Marston) +* Explicitly disallow using `any_instance` to mock or stub a method + that is defined on a module prepended onto the class. This triggered + `SystemStackError` before and is very hard to support so we are not + supporting it at this time. (Myron Marston) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.0.0.beta1...v3.0.0.beta2) + +Breaking Changes for 3.0.0: + +* Rename `RSpec::Mocks::Mock` to `RSpec::Mocks::Double`. (Myron Marston) +* Change how to integrate rspec-mocks in other test frameworks. You now + need to include `RSpec::Mocks::ExampleMethods` in your test context. + (Myron Marston) +* Prevent RSpec mocks' doubles and partial doubles from being used outside of + the per-test lifecycle (e.g. from a `before(:all)` hook). (Sam Phippen) +* Remove the `host` argument of `RSpec::Mocks.setup`. Instead + `RSpec::Mocks::ExampleMethods` should be included directly in the scope where + RSpec's mocking capabilities are used. (Sam Phippen) +* Make test doubles raise errors if you attempt to use them after they + get reset, to help surface issues when you accidentally retain + references to test doubles and attempt to reuse them in another + example. (Myron Marston) +* Remove support for `and_return { value }` and `and_return` without arguments. (Yuji Nakayama) + +Enhancements: + +* Add `receive_message_chain` which provides the functionality of the old + `stub_chain` for the new allow/expect syntax. Use it like so: `allow(...).to + receive_message_chain(:foo, :bar, :bazz)`. (Sam Phippen). +* Change argument matchers to use `===` as their primary matching + protocol, since their semantics mirror that of a case or rescue statement + (which uses `===` for matching). (Myron Marston) +* Add `RSpec::Mocks.with_temporary_scope`, which allows you to create + temporary rspec-mocks scopes in arbitrary places (such as a + `before(:all)` hook). (Myron Marston) +* Support keyword arguments when checking arity with verifying doubles. + (Xavier Shay) + +Bug Fixes: + +* Fix regression in 3.0.0.beta1 that caused `double("string_name" => :value)` + to stop working. (Xavier Shay) +* Fix the way rspec-mocks and rspec-core interact so that if users + define a `let` with the same name as one of the methods + from `RSpec::Mocks::ArgumentMatchers`, the user's `let` takes + precedence. (Michi Huber, Myron Marston) +* Fix verified doubles so that their methods match the visibility + (public, protected or private) of the interface they verify + against. (Myron Marston) +* Fix verified null object doubles so that they do not wrongly + report that they respond to anything. They only respond to methods + available on the interface they verify against. (Myron Marston) +* Fix deprecation warning for use of old `:should` syntax w/o explicit + config so that it no longer is silenced by an extension gem such + as rspec-rails when it calls `config.add_stub_and_should_receive_to`. + (Sam Phippen) +* Fix `expect` syntax so that it does not wrongly emit a "You're + overriding a previous implementation for this stub" warning when + you are not actually doing that. (Myron Marston) +* Fix `any_instance.unstub` when used on sub classes for whom the super + class has had `any_instance.stub` invoked on. (Jon Rowe) +* Fix regression in `stub_chain`/`receive_message_chain` that caused + it to raise an `ArgumentError` when passing args to the stubbed + methods. (Sam Phippen) +* Correct stub of undefined parent modules all the way down when stubbing a + nested constant. (Xavier Shay) +* Raise `VerifyingDoubleNotDefinedError` when a constant is not defined for + a verifying class double. (Maurício Linhares) +* Remove `Double#to_str`, which caused confusing `raise some_double` + behavior. (Maurício Linhares) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.4...v3.0.0.beta1) + +Breaking Changes for 3.0.0: + +* Raise an explicit error if `should_not_receive(...).and_return` is used. (Sam + Phippen) +* Remove 1.8.6 workarounds. (Jon Rowe) +* Remove `stub!` and `unstub!`. (Sam Phippen) +* Remove `mock(name, methods)` and `stub(name, methods)`, leaving + `double(name, methods)` for creating test doubles. (Sam Phippen, Michi Huber) +* Remove `any_number_of_times` since `should_receive(:msg).any_number_of_times` + is really a stub in a mock's clothing. (Sam Phippen) +* Remove support for re-using the same null-object test double in multiple + examples. Test doubles are designed to only live for one example. + (Myron Marston) +* Make `at_least(0)` raise an error. (Sam Phippen) +* Remove support for `require 'spec/mocks'` which had been kept + in place for backwards compatibility with RSpec 1. (Myron Marston) +* Blocks provided to `with` are always used as implementation. (Xavier Shay) +* The config option (added in 2.99) to yield the receiver to + `any_instance` implementation blocks now defaults to "on". (Sam Phippen) + +Enhancements: + +* Allow the `have_received` matcher to use a block to set further expectations + on arguments. (Tim Cowlishaw) +* Provide `instance_double` and `class_double` to create verifying doubles, + ported from `rspec-fire`. (Xavier Shay) +* `as_null_object` on a verifying double only responds to defined methods. + (Xavier Shay) +* Provide `object_double` to create verified doubles of specific object + instances. (Xavier Shay) +* Provide `verify_partial_doubles` configuration that provides `object_double` + like verification behaviour on partial doubles. (Xavier Shay) +* Improved performance of double creation, particularly those with many + attributes. (Xavier Shay) +* Default value of `transfer_nested_constants` option for constant stubbing can + be configured. (Xavier Shay) +* Messages can be allowed or expected on in bulk via + `receive_messages(:message => :value)`. (Jon Rowe) +* `allow(Klass.any_instance)` and `expect(Klass.any_instance)` now print a + warning. This is usually a mistake, and users usually want + `allow_any_instance_of` or `expect_any_instance_of` instead. (Sam Phippen) +* `instance_double` and `class_double` raise `ArgumentError` if the underlying + module is loaded and the arity of the method being invoked does not match the + arity of the method as it is actually implemented. (Andy Lindeman) +* Spies can now check their invocation ordering is correct. (Jon Rowe) + +Deprecations: + +* Using the old `:should` syntax without explicitly configuring it + is deprecated. It will continue to work but will emit a deprecation + warning in RSpec 3 if you do not explicitly enable it. (Sam Phippen) + +Bug Fixes: + +* Fix `and_call_original` to handle a complex edge case involving + singleton class ancestors. (Marc-André Lafortune, Myron Marston) +* When generating an error message for unexpected arguments, + use `#inspect` rather than `#description` if `#description` + returns `nil` or `''` so that you still get a useful message. + (Nick DeLuca) + +### 2.99.4 / 2015-06-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.3...v2.99.4) + +Bug Fixes: + +* Add missing deprecation for using `with` with no arguments e.g. `with()`. (Yousuke, #970) + +### 2.99.3 / 2015-01-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.2...v2.99.3) + +Bug Fixes: + +* Fix regression that caused an error when a test double was deserialized from YAML. (Yuji Nakayama, #777) + +### 2.99.2 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.1...v2.99.2) + +Enhancements: + +* Warn about upcoming change to `#===` matching and `DateTime#===` behaviour. + (Jon Rowe, #735) + +### 2.99.1 / 2014-06-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0...v2.99.1) + +Bug Fixes: + +* Fix bug that caused errors at the end of each example + when a `double.as_null_object` had been frozen. (Yuji Nakayama, #698) + +Deprecations: + +* Deprecate freezing a test double. (Yuji Nakayama, #698) + +### 2.99.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.rc1...v2.99.0) + +No changes. Just taking it out of pre-release. + +### 2.99.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.beta2...v2.99.0.rc1) + +Deprecations: + +* Deprecate `RSpec::Mocks::TestDouble.extend_onto`. (Myron Marston) +* Deprecate `RSpec::Mocks::ConstantStubber`. (Jon Rowe) +* Deprecate `Marshal.dump` monkey-patch without opt-in. (Xavier Shay) + +### 2.99.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.99.0.beta1...v2.99.0.beta2) + +Deprecations: + +* Deprecate `RSpec::Mocks::Mock` in favor of `RSpec::Mocks::Double`. + (Myron Marston) +* Deprecate the `host` argument of `RSpec::Mocks.setup`. Instead + `RSpec::Mocks::ExampleMethods` should be included directly in the scope where + RSpec's mocking capabilities are used. (Sam Phippen) +* Deprecate using any of rspec-mocks' features outside the per-test + lifecycle (e.g. from a `before(:all)` hook). (Myron Marston) +* Deprecate re-using a test double in another example. (Myron Marston) +* Deprecate `and_return { value }` and `and_return` without arguments. (Yuji Nakayama) + +### 2.99.0.beta1 / 2013-11-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.4...v2.99.0.beta1) + +Deprecations + +* Expecting to use lambdas or other strong arity implementations for stub + methods with mis-matched arity is deprecated and support for them will be + removed in 3.0. Either provide the right amount of arguments or use a weak + arity implementation (methods with splats or procs). (Jon Rowe) +* Using the same test double instance in multiple examples is deprecated. Test + doubles are only meant to live for one example. The mocks and stubs have + always been reset between examples; however, in 2.x the `as_null_object` + state was not reset and some users relied on this to have a null object + double that is used for many examples. This behavior will be removed in 3.0. + (Myron Marston) +* Print a detailed warning when an `any_instance` implementation block is used + when the new `yield_receiver_to_any_instance_implementation_blocks` config + option is not explicitly set, as RSpec 3.0 will default to enabling this new + feature. (Sam Phippen) + +Enhancements: + +* Add a config option to yield the receiver to `any_instance` implementation + blocks. (Sam Phippen) + +### 2.14.6 / 2014-02-20 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.5...v2.14.6) + +Bug Fixes: + +* Ensure `any_instance` method stubs and expectations are torn down regardless of + expectation failures. (Sam Phippen) + +### 2.14.5 / 2014-02-01 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.4...v2.14.5) + +Bug Fixes: + +* Fix regression that caused block implementations to not receive all + args on 1.8.7 if the block also receives a block, due to Proc#arity + reporting `1` no matter how many args the block receives if it + receives a block, too. (Myron Marston) + +### 2.14.4 / 2013-10-15 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.3...v2.14.4) + +Bug Fixes: + +* Fix issue where unstubing methods on "any instances" would not + remove stubs on existing instances (Jon Rowe) +* Fix issue with receive(:message) do ... end precedence preventing + the usage of modifications (`and_return` etc) (Jon Rowe) + +### 2.14.3 / 2013-08-08 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.2...v2.14.3) + +Bug Fixes: + +* Fix stubbing some instance methods for classes whose hierarchy includes + a prepended Module (Bradley Schaefer) + +### 2.14.2 / 2013-07-30 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.1...v2.14.2) + +Bug Fixes: + +* Fix `as_null_object` doubles so that they return `nil` from `to_ary` + (Jon Rowe). +* Fix regression in 2.14 that made `stub!` (with an implicit receiver) + return a test double rather than stub a method (Myron Marston). + +### 2.14.1 / 2013-07-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.0...v2.14.1) + +Bug Fixes: + +* Restore `double.as_null_object` behavior from 2.13 and earlier: a + double's nullness persisted between examples in earlier examples. + While this is not an intended use case (test doubles are meant to live + for only one example), we don't want to break behavior users rely + on in a minor relase. This will be deprecated in 2.99 and removed + in 3.0. (Myron Marston) + +### 2.14.0 / 2013-07-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.0.rc1...v2.14.0) + +Enhancements: + +* Document test spies in the readme. (Adarsh Pandit) +* Add an `array_including` matcher. (Sam Phippen) +* Add a syntax-agnostic API for mocking or stubbing a method. This is + intended for use by libraries such as rspec-rails that need to mock + or stub a method, and work regardless of the syntax the user has + configured (Paul Annesley, Myron Marston and Sam Phippen). + +Bug Fixes: + +* Fix `double` so that it sets up passed stubs correctly regardless of + the configured syntax (Paul Annesley). +* Allow a block implementation to be used in combination with + `and_yield`, `and_raise`, `and_return` or `and_throw`. This got fixed + in 2.13.1 but failed to get merged into master for the 2.14.0.rc1 + release (Myron Marston). +* `Marshal.dump` does not unnecessarily duplicate objects when rspec-mocks has + not been fully initialized. This could cause errors when using `spork` or + similar preloading gems (Andy Lindeman). + +### 2.14.0.rc1 / 2013-05-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.14.0.rc1) + +Enhancements: + +* Refactor internals so that the mock proxy methods and state are held + outside of the mocked object rather than inside it. This paves the way + for future syntax enhancements and removes the need for some hacky + work arounds for `any_instance` dup'ing and `YAML` serialization, + among other things. Note that the code now relies upon `__id__` + returning a unique, consistent value for any object you want to + mock or stub (Myron Marston). +* Add support for test spies. This allows you to verify a message + was received afterwards using the `have_received` matcher. + Note that you must first stub the method or use a null double. + (Joe Ferris and Joël Quenneville) +* Make `at_least` and `at_most` style receive expectations print that they were + expecting at least or at most some number of calls, rather than just the + number of calls given in the expectation (Sam Phippen) +* Make `with` style receive expectations print the args they were expecting, and + the args that they got (Sam Phippen) +* Fix some warnings seen under ruby 2.0.0p0 (Sam Phippen). +* Add a new `:expect` syntax for message expectations + (Myron Marston and Sam Phippen). + +Bug fixes + +* Fix `any_instance` so that a frozen object can be `dup`'d when methods + have been stubbed on that type using `any_instance` (Jon Rowe). +* Fix `and_call_original` so that it properly raises an `ArgumentError` + when the wrong number of args are passed (Jon Rowe). +* Fix `double` on 1.9.2 so you can wrap them in an Array + using `Array(my_double)` (Jon Rowe). +* Fix `stub_const` and `hide_const` to handle constants that redefine `send` + (Sam Phippen). +* Fix `Marshal.dump` extension so that it correctly handles nil. + (Luke Imhoff, Jon Rowe) +* Fix isolation of `allow_message_expectations_on_nil` (Jon Rowe) +* Use inspect to format actual arguments on expectations in failure messages (#280, Ben Langfeld) +* Protect against improperly initialised test doubles (#293) (Joseph Shraibman and Jon Rowe) + +Deprecations + +* Deprecate `stub` and `mock` as aliases for `double`. `double` is the + best term for creating a test double, and it reduces confusion to + have only one term (Michi Huber). +* Deprecate `stub!` and `unstub!` in favor of `stub` and `unstub` + (Jon Rowe). +* Deprecate `at_least(0).times` and `any_number_of_times` (Michi Huber). + +### 2.13.1 / 2013-04-06 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.13.1) + +Bug fixes + +* Allow a block implementation to be used in combination with + `and_yield`, `and_raise`, `and_return` or `and_throw` (Myron Marston). + +### 2.13.0 / 2013-02-23 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.2...v2.13.0) + +Bug fixes + +* Fix bug that caused weird behavior when a method that had + previously been stubbed with multiple return values (e.g. + `obj.stub(:foo).and_return(1, 2)`) was later mocked with a + single return value (e.g. `obj.should_receive(:foo).once.and_return(1)`). + (Myron Marston) +* Fix bug related to a mock expectation for a method that already had + multiple stubs with different `with` constraints. Previously, the + first stub was used, even though it may not have matched the passed + args. The fix defers this decision until the message is received so + that the proper stub response can be chosen based on the passed + arguments (Myron Marston). +* Do not call `nil?` extra times on a mocked object, in case `nil?` + itself is expected a set number of times (Myron Marston). +* Fix `missing_default_stub_error` message so array args are handled + properly (Myron Marston). +* Explicitly disallow `any_instance.unstub!` (Ryan Jones). +* Fix `any_instance` stubbing so that it works with `Delegator` + subclasses (Myron Marston). +* Fix `and_call_original` so that it works with `Delegator` subclasses + (Myron Marston). +* Fix `any_instance.should_not_receive` when `any_instance.should_receive` + is used on the same class in the same example. Previously it would + wrongly report a failure even when the message was not received + (Myron Marston). + +### 2.12.2 / 2013-01-27 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.1...v.2.12.2) + +Bug fixes + +* Fix `and_call_original` to work properly for methods defined + on a module extended onto an object instance (Myron Marston). +* Fix `stub_const` with an undefined constnat name to work properly + with constant strings that are prefixed with `::` -- and edge case + I missed in the bug fix in the 2.12.1 release (Myron Marston). +* Ensure method visibility on a partial mock is restored after reseting + method stubs, even on a singleton module (created via `extend self`) + when the method visibility differs between the instance and singleton + versions (Andy Lindeman). + +### 2.12.1 / 2012-12-21 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.12.0...v2.12.1) + +Bug fixes + +* Fix `any_instance` to support `and_call_original`. + (Myron Marston) +* Properly restore stubbed aliased methods on rubies + that report the incorrect owner (Myron Marston and Andy Lindeman). +* Fix `hide_const` and `stub_const` with a defined constnat name to + work properly with constant strings that are prefixed with `::` (Myron Marston). + +### 2.12.0 / 2012-11-12 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.3...v2.12.0) + +Enhancements + +* `and_raise` can accept an exception class and message, more closely + matching `Kernel#raise` (e.g., `foo.stub(:bar).and_raise(RuntimeError, "message")`) + (Bas Vodde) +* Add `and_call_original`, which will delegate the message to the + original method (Myron Marston). + +Deprecations: + +* Add deprecation warning when using `and_return` with `should_not_receive` + (Neha Kumari) + +### 2.11.3 / 2012-09-19 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.2...v2.11.3) + +Bug fixes + +* Fix `:transfer_nested_constants` option of `stub_const` so that it + doesn't blow up when there are inherited constants. (Myron Marston) +* `any_instance` stubs can be used on classes that override `Object#method`. + (Andy Lindeman) +* Methods stubbed with `any_instance` are unstubbed after the test finishes. + (Andy Lindeman) +* Fix confusing error message when calling a mocked class method an + extra time with the wrong arguments (Myron Marston). + +### 2.11.2 / 2012-08-11 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2) + +Bug fixes + +* Don't modify `dup` on classes that don't support `dup` (David Chelimsky) +* Fix `any_instance` so that it works properly with methods defined on + a superclass. (Daniel Eguzkiza) +* Fix `stub_const` so that it works properly for nested constants that + share a name with a top-level constant (e.g. "MyGem::Hash"). (Myron + Marston) + +### 2.11.1 / 2012-07-09 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.0...v2.11.1) + +Bug fixes + +* Fix `should_receive` so that when it is called on an `as_null_object` + double with no implementation, and there is a previous explicit stub + for the same method, the explicit stub remains (rather than being + overridden with the null object implementation--`return self`). (Myron + Marston) + +### 2.11.0 / 2012-07-07 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.10.1...v2.11.0) + +Enhancements + +* Expose ArgumentListMatcher as a formal API + * supports use by 3rd party mock frameworks like Surrogate +* Add `stub_const` API to stub constants for the duration of an + example (Myron Marston). + +Bug fixes + +* Fix regression of edge case behavior. `double.should_receive(:foo) { a }` + was causing a NoMethodError when `double.stub(:foo).and_return(a, b)` + had been setup before (Myron Marston). +* Infinite loop generated by using `any_instance` and `dup`. (Sidu Ponnappa @kaiwren) +* `double.should_receive(:foo).at_least(:once).and_return(a)` always returns a + even if `:foo` is already stubbed. +* Prevent infinite loop when interpolating a null double into a string + as an integer (`"%i" % double.as_null_object`). (Myron Marston) +* Fix `should_receive` so that null object behavior (e.g. returning + self) is preserved if no implementation is given (Myron Marston). +* Fix `and_raise` so that it raises `RuntimeError` rather than + `Exception` by default, just like ruby does. (Andrew Marshall) + +### 2.10.1 / 2012-05-05 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.10.0...v2.10.1) + +Bug fixes + +* fix regression of edge case behavior + (https://github.com/rspec/rspec-mocks/issues/132) + * fixed failure of `object.should_receive(:message).at_least(0).times.and_return value` + * fixed failure of `object.should_not_receive(:message).and_return value` + +### 2.10.0 / 2012-05-03 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.9.0...v2.10.0) + +Bug fixes + +* fail fast when an `exactly` or `at_most` expectation is exceeded + +### 2.9.0 / 2012-03-17 +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0...v2.9.0) + +Enhancements + +* Support order constraints across objects (preethiramdev) + +Bug fixes + +* Allow a `as_null_object` to be passed to `with` +* Pass proc to block passed to stub (Aubrey Rhodes) +* Initialize child message expectation args to match any args (#109 - + preethiramdev) + +### 2.8.0 / 2012-01-04 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0.rc2...v2.8.0) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc2 / 2011-12-19 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.8.0.rc1...v2.8.0.rc2) + +No changes for this release. Just releasing with the other rspec gems. + +### 2.8.0.rc1 / 2011-11-06 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.7.0...v2.8.0.rc1) + +Enhancements + +* Eliminate Ruby warnings (Matijs van Zuijlen) + +### 2.7.0 / 2011-10-16 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.6.0...v2.7.0) + +Enhancements + +* Use `__send__` rather than `send` (alextk) +* Add support for `any_instance.stub_chain` (Sidu Ponnappa) +* Add support for `any_instance` argument matching based on `with` (Sidu + Ponnappa and Andy Lindeman) + +Changes + +* Check for `failure_message_for_should` or `failure_message` instead of + `description` to detect a matcher (Tibor Claassen) + +Bug fixes + +* pass a hash to `any_instance.stub`. (Justin Ko) +* allow `to_ary` to be called without raising `NoMethodError` (Mikhail + Dieterle) +* `any_instance` properly restores private methods (Sidu Ponnappa) + +### 2.6.0 / 2011-05-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.5.0...v2.6.0) + +Enhancements + +* Add support for `any_instance.stub` and `any_instance.should_receive` (Sidu + Ponnappa and Andy Lindeman) + +Bug fixes + +* fix bug in which multiple chains with shared messages ending in hashes failed + to return the correct value + +### 2.5.0 / 2011-02-05 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.4.0...v2.5.0) + +Bug fixes + +* message expectation counts now work in combination with a stub (Damian + Nurzynski) +* fix failure message when message received with incorrect args (Josep M. + Bach) + +### 2.4.0 / 2011-01-02 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.3.0...v2.4.0) + +No functional changes in this release, which was made to align with the +rspec-core-2.4.0 release. + +### 2.3.0 / 2010-12-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.2.0...v2.3.0) + +Bug fixes + +* Fix our Marshal extension so that it does not interfere with objects that + have their own `@mock_proxy` instance variable. (Myron Marston) + +### 2.2.0 / 2010-11-28 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.1.0...v2.2.0) + +Enhancements + +* Added "rspec/mocks/standalone" for exploring the rspec-mocks in irb. + +Bug fix + +* Eliminate warning on splat args without parens (Gioele Barabucci) +* Fix bug where `obj.should_receive(:foo).with(stub.as_null_object)` would pass + with a false positive. + +### 2.1.0 / 2010-11-07 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.1...v2.1.0) + +Bug fixes + +* Fix serialization of stubbed object (Josep M Bach) + +### 2.0.0 / 2010-10-10 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.22...v2.0.0) + +### 2.0.0.rc / 2010-10-05 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.22...v2.0.0.rc) + +Enhancements + +* support passing a block to an expectation block (Nicolas Braem) + * `obj.should_receive(:msg) {|&block| ... }` + +Bug fixes + +* Fix YAML serialization of stub (Myron Marston) +* Fix rdoc rake task (Hans de Graaff) + +### 2.0.0.beta.22 / 2010-09-12 + +[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v2.0.0.beta.20...v2.0.0.beta.22) + +Bug fixes + +* fixed regression that broke `obj.stub_chain(:a, :b => :c)` +* fixed regression that broke `obj.stub_chain(:a, :b) { :c }` +* `respond_to?` always returns true when using `as_null_object` diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/LICENSE.md new file mode 100644 index 0000000000..dae02d8a7d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +* Copyright © 2012 David Chelimsky, Myron Marston +* Copyright © 2006 David Chelimsky, The RSpec Development Team +* Copyright © 2005 Steven Baker + +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.0.0/gems/rspec-mocks-3.12.3/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/README.md new file mode 100644 index 0000000000..c1cd5909d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/README.md @@ -0,0 +1,463 @@ +# RSpec Mocks [![Build Status](https://github.com/rspec/rspec-mocks/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-mocks/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-mocks.svg)](https://codeclimate.com/github/rspec/rspec-mocks) +rspec-mocks is a test-double framework for rspec with support for method stubs, +fakes, and message expectations on generated test-doubles and real objects +alike. + +## Install + + gem install rspec # for rspec-core, rspec-expectations, rspec-mocks + gem install rspec-mocks # for rspec-mocks only + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +For information about contributing to RSpec, please refer to the following markdown files: +* [Build details](BUILD_DETAIL.md) +* [Code of Conduct](CODE_OF_CONDUCT.md) +* [Detailed contributing guide](CONTRIBUTING.md) +* [Development setup guide](DEVELOPMENT.md) + +## Test Doubles + +A test double is an object that stands in for another object in your system +during a code example. Use the `double` method, passing in an optional identifier, to create one: + +```ruby +book = double("book") +``` + +Most of the time you will want some confidence that your doubles resemble an +existing object in your system. Verifying doubles are provided for this +purpose. If the existing object is available, they will prevent you from adding +stubs and expectations for methods that do not exist or that have an invalid +number of parameters. + +```ruby +book = instance_double("Book", :pages => 250) +``` + +Verifying doubles have some clever tricks to enable you to both test in +isolation without your dependencies loaded while still being able to validate +them against real objects. More detail is available in [their +documentation](https://github.com/rspec/rspec-mocks/blob/main/features/verifying_doubles). + +Verifying doubles can also accept custom identifiers, just like double(), e.g.: + +```ruby +books = [] +books << instance_double("Book", :rspec_book, :pages => 250) +books << instance_double("Book", "(Untitled)", :pages => 5000) + +puts books.inspect # with names, it's clearer which were actually added +``` + +## Method Stubs + +A method stub is an implementation that returns a pre-determined value. Method +stubs can be declared on test doubles or real objects using the same syntax. +rspec-mocks supports 3 forms for declaring method stubs: + +```ruby +allow(book).to receive(:title) { "The RSpec Book" } +allow(book).to receive(:title).and_return("The RSpec Book") +allow(book).to receive_messages( + :title => "The RSpec Book", + :subtitle => "Behaviour-Driven Development with RSpec, Cucumber, and Friends") +``` + +You can also use this shortcut, which creates a test double and declares a +method stub in one statement: + +```ruby +book = double("book", :title => "The RSpec Book") +``` + +The first argument is a name, which is used for documentation and appears in +failure messages. If you don't care about the name, you can leave it out, +making the combined instantiation/stub declaration very terse: + +```ruby +double(:foo => 'bar') +``` + +This is particularly nice when providing a list of test doubles to a method +that iterates through them: + +```ruby +order.calculate_total_price(double(:price => 1.99), double(:price => 2.99)) +``` + +### Stubbing a chain of methods + +You can use `receive_message_chain` in place of `receive` to stub a chain of messages: + +```ruby +allow(double).to receive_message_chain("foo.bar") { :baz } +allow(double).to receive_message_chain(:foo, :bar => :baz) +allow(double).to receive_message_chain(:foo, :bar) { :baz } + +# Given any of the above forms: +double.foo.bar # => :baz +``` + +Chains can be arbitrarily long, which makes it quite painless to violate the Law of Demeter in violent ways, so you should consider any use of `receive_message_chain` a code smell. Even though not all code smells indicate real problems (think fluent interfaces), `receive_message_chain` still results in brittle examples. For example, if you write `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the implementation calls `foo.baz.bar`, the stub will not work. + +## Consecutive return values + +When a stub might be invoked more than once, you can provide additional +arguments to `and_return`. The invocations cycle through the list. The last +value is returned for any subsequent invocations: + +```ruby +allow(die).to receive(:roll).and_return(1, 2, 3) +die.roll # => 1 +die.roll # => 2 +die.roll # => 3 +die.roll # => 3 +die.roll # => 3 +``` + +To return an array in a single invocation, declare an array: + +```ruby +allow(team).to receive(:players).and_return([double(:name => "David")]) +``` + +## Message Expectations + +A message expectation is an expectation that the test double will receive a +message some time before the example ends. If the message is received, the +expectation is satisfied. If not, the example fails. + +```ruby +validator = double("validator") +expect(validator).to receive(:validate) { "02134" } +zipcode = Zipcode.new("02134", validator) +zipcode.valid? +``` + +## Test Spies + +Verifies the given object received the expected message during the course of +the test. For a message to be verified, the given object must be setup to spy +on it, either by having it explicitly stubbed or by being a null object double +(e.g. `double(...).as_null_object`). Convenience methods are provided to easily +create null object doubles for this purpose: + +```ruby +spy("invitation") # => same as `double("invitation").as_null_object` +instance_spy("Invitation") # => same as `instance_double("Invitation").as_null_object` +class_spy("Invitation") # => same as `class_double("Invitation").as_null_object` +object_spy("Invitation") # => same as `object_double("Invitation").as_null_object` +``` + +Verifying messages received in this way implements the Test Spy pattern. + +```ruby +invitation = spy('invitation') + +user.accept_invitation(invitation) + +expect(invitation).to have_received(:accept) + +# You can also use other common message expectations. For example: +expect(invitation).to have_received(:accept).with(mailer) +expect(invitation).to have_received(:accept).twice +expect(invitation).to_not have_received(:accept).with(mailer) + +# One can specify a return value on the spy the same way one would a double. +invitation = spy('invitation', :accept => true) +expect(invitation).to have_received(:accept).with(mailer) +expect(invitation.accept).to eq(true) +``` + +Note that `have_received(...).with(...)` is unable to work properly when +passed arguments are mutated after the spy records the received message. +For example, this does not work properly: + +```ruby +greeter = spy("greeter") + +message = "Hello" +greeter.greet_with(message) +message << ", World" + +expect(greeter).to have_received(:greet_with).with("Hello") +``` + +## Nomenclature + +### Mock Objects and Test Stubs + +The names Mock Object and Test Stub suggest specialized Test Doubles. i.e. +a Test Stub is a Test Double that only supports method stubs, and a Mock +Object is a Test Double that supports message expectations and method +stubs. + +There is a lot of overlapping nomenclature here, and there are many +variations of these patterns (fakes, spies, etc). Keep in mind that most of +the time we're talking about method-level concepts that are variations of +method stubs and message expectations, and we're applying to them to _one_ +generic kind of object: a Test Double. + +### Test-Specific Extension + +a.k.a. Partial Double, a Test-Specific Extension is an extension of a +real object in a system that is instrumented with test-double like +behaviour in the context of a test. This technique is very common in Ruby +because we often see class objects acting as global namespaces for methods. +For example, in Rails: + +```ruby +person = double("person") +allow(Person).to receive(:find) { person } +``` + +In this case we're instrumenting Person to return the person object we've +defined whenever it receives the `find` message. We can also set a message +expectation so that the example fails if `find` is not called: + +```ruby +person = double("person") +expect(Person).to receive(:find) { person } +``` + +RSpec replaces the method we're stubbing or mocking with its own +test-double-like method. At the end of the example, RSpec verifies any message +expectations, and then restores the original methods. + +## Expecting Arguments + +```ruby +expect(double).to receive(:msg).with(*args) +expect(double).to_not receive(:msg).with(*args) +``` + +You can set multiple expectations for the same message if you need to: + +```ruby +expect(double).to receive(:msg).with("A", 1, 3) +expect(double).to receive(:msg).with("B", 2, 4) +``` + +## Argument Matchers + +Arguments that are passed to `with` are compared with actual arguments +received using ===. In cases in which you want to specify things about the +arguments rather than the arguments themselves, you can use any of the +matchers that ship with rspec-expectations. They don't all make syntactic +sense (they were primarily designed for use with RSpec::Expectations), but +you are free to create your own custom RSpec::Matchers. + +rspec-mocks also adds some keyword Symbols that you can use to +specify certain kinds of arguments: + +```ruby +expect(double).to receive(:msg).with(no_args) +expect(double).to receive(:msg).with(any_args) +expect(double).to receive(:msg).with(1, any_args) # any args acts like an arg splat and can go anywhere +expect(double).to receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can be any kind of Numeric +expect(double).to receive(:msg).with(1, boolean(), "b") #2nd argument can be true or false +expect(double).to receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp +expect(double).to receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all +expect(double).to receive(:msg).with(1, duck_type(:abs, :div), "b") #2nd argument can be object that responds to #abs and #div +expect(double).to receive(:msg).with(hash_including(:a => 5)) # first arg is a hash with a: 5 as one of the key-values +expect(double).to receive(:msg).with(array_including(5)) # first arg is an array with 5 as one of the key-values +expect(double).to receive(:msg).with(hash_excluding(:a => 5)) # first arg is a hash without a: 5 as one of the key-values +``` + +## Receive Counts + +```ruby +expect(double).to receive(:msg).once +expect(double).to receive(:msg).twice +expect(double).to receive(:msg).exactly(n).time +expect(double).to receive(:msg).exactly(n).times +expect(double).to receive(:msg).at_least(:once) +expect(double).to receive(:msg).at_least(:twice) +expect(double).to receive(:msg).at_least(n).time +expect(double).to receive(:msg).at_least(n).times +expect(double).to receive(:msg).at_most(:once) +expect(double).to receive(:msg).at_most(:twice) +expect(double).to receive(:msg).at_most(n).time +expect(double).to receive(:msg).at_most(n).times +``` + +## Ordering + +```ruby +expect(double).to receive(:msg).ordered +expect(double).to receive(:other_msg).ordered + # This will fail if the messages are received out of order +``` + +This can include the same message with different arguments: + +```ruby +expect(double).to receive(:msg).with("A", 1, 3).ordered +expect(double).to receive(:msg).with("B", 2, 4).ordered +``` + +## Setting Responses + +Whether you are setting a message expectation or a method stub, you can +tell the object precisely how to respond. The most generic way is to pass +a block to `receive`: + +```ruby +expect(double).to receive(:msg) { value } +``` + +When the double receives the `msg` message, it evaluates the block and returns +the result. + +```ruby +expect(double).to receive(:msg).and_return(value) +expect(double).to receive(:msg).exactly(3).times.and_return(value1, value2, value3) + # returns value1 the first time, value2 the second, etc +expect(double).to receive(:msg).and_raise(error) + # `error` can be an instantiated object (e.g. `StandardError.new(some_arg)`) or a class (e.g. `StandardError`) + # if it is a class, it must be instantiable with no args +expect(double).to receive(:msg).and_throw(:msg) +expect(double).to receive(:msg).and_yield(values, to, yield) +expect(double).to receive(:msg).and_yield(values, to, yield).and_yield(some, other, values, this, time) + # for methods that yield to a block multiple times +``` + +Any of these responses can be applied to a stub as well + +```ruby +allow(double).to receive(:msg).and_return(value) +allow(double).to receive(:msg).and_return(value1, value2, value3) +allow(double).to receive(:msg).and_raise(error) +allow(double).to receive(:msg).and_throw(:msg) +allow(double).to receive(:msg).and_yield(values, to, yield) +allow(double).to receive(:msg).and_yield(values, to, yield).and_yield(some, other, values, this, time) +``` + +## Arbitrary Handling + +Once in a while you'll find that the available expectations don't solve the +particular problem you are trying to solve. Imagine that you expect the message +to come with an Array argument that has a specific length, but you don't care +what is in it. You could do this: + +```ruby +expect(double).to receive(:msg) do |arg| + expect(arg.size).to eq 7 +end +``` + +If the method being stubbed itself takes a block, and you need to yield to it +in some special way, you can use this: + +```ruby +expect(double).to receive(:msg) do |&arg| + begin + arg.call + ensure + # cleanup + end +end +``` + +## Delegating to the Original Implementation + +When working with a partial mock object, you may occasionally +want to set a message expectation without interfering with how +the object responds to the message. You can use `and_call_original` +to achieve this: + +```ruby +expect(Person).to receive(:find).and_call_original +Person.find # => executes the original find method and returns the result +``` + +## Combining Expectation Details + +Combining the message name with specific arguments, receive counts and responses +you can get quite a bit of detail in your expectations: + +```ruby +expect(double).to receive(:<<).with("illegal value").once.and_raise(ArgumentError) +``` + +While this is a good thing when you really need it, you probably don't really +need it! Take care to specify only the things that matter to the behavior of +your code. + +## Stubbing and Hiding Constants + +See the [mutating constants +README](https://github.com/rspec/rspec-mocks/blob/main/features/mutating_constants/README.md) +for info on this feature. + +## Use `before(:example)`, not `before(:context)` + +Stubs in `before(:context)` are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in `before(:context)` would work in the first example that happens to run in that group, but not for any others. + +Instead of `before(:context)`, use `before(:example)`. + +## Settings mocks or stubs on any instance of a class + +rspec-mocks provides two methods, `allow_any_instance_of` and +`expect_any_instance_of`, that will allow you to stub or mock any instance +of a class. They are used in place of `allow` or `expect`: + +```ruby +allow_any_instance_of(Widget).to receive(:name).and_return("Wibble") +expect_any_instance_of(Widget).to receive(:name).and_return("Wobble") +``` + +These methods add the appropriate stub or expectation to all instances of +`Widget`. + +This feature is sometimes useful when working with legacy code, though in +general we discourage its use for a number of reasons: + +* The `rspec-mocks` API is designed for individual object instances, but this + feature operates on entire classes of objects. As a result there are some + semantically confusing edge cases. For example in + `expect_any_instance_of(Widget).to receive(:name).twice` it isn't clear + whether each specific instance is expected to receive `name` twice, or if two + receives total are expected. (It's the former.) +* Using this feature is often a design smell. It may be + that your test is trying to do too much or that the object under test is too + complex. +* It is the most complicated feature of `rspec-mocks`, and has historically + received the most bug reports. (None of the core team actively use it, + which doesn't help.) + + +## Further Reading + +There are many different viewpoints about the meaning of mocks and stubs. If +you are interested in learning more, here is some recommended reading: + +* Mock Objects: http://www.mockobjects.com/ +* Endo-Testing: http://www.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF +* Mock Roles, Not Objects: http://www.jmock.org/oopsla2004.pdf +* Test Double: http://www.martinfowler.com/bliki/TestDouble.html +* Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html +* Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html + +## Also see + +* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) +* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) +* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) +* [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails) diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks.rb new file mode 100644 index 0000000000..297779e5a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks.rb @@ -0,0 +1,133 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support 'caller_filter' +RSpec::Support.require_rspec_support 'warnings' +RSpec::Support.require_rspec_support 'ruby_features' + +RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f } + +%w[ + instance_method_stasher + method_double + argument_matchers + example_methods + proxy + test_double + argument_list_matcher + message_expectation + order_group + error_generator + space + mutate_const + targets + syntax + configuration + verifying_double + version +].each { |name| RSpec::Support.require_rspec_mocks name } + +# Share the top-level RSpec namespace, because we are a core supported +# extension. +module RSpec + # Contains top-level utility methods. While this contains a few + # public methods, these are not generally meant to be called from + # a test or example. They exist primarily for integration with + # test frameworks (such as rspec-core). + module Mocks + # Performs per-test/example setup. This should be called before + # an test or example begins. + def self.setup + @space_stack << (@space = space.new_scope) + end + + # Verifies any message expectations that were set during the + # test or example. This should be called at the end of an example. + def self.verify + space.verify_all + end + + # Cleans up all test double state (including any methods that were + # redefined on partial doubles). This _must_ be called after + # each example, even if an error was raised during the example. + def self.teardown + space.reset_all + @space_stack.pop + @space = @space_stack.last || @root_space + end + + # Adds an allowance (stub) on `subject` + # + # @param subject the subject to which the message will be added + # @param message a symbol, representing the message that will be + # added. + # @param opts a hash of options, :expected_from is used to set the + # original call site + # @yield an optional implementation for the allowance + # + # @example Defines the implementation of `foo` on `bar`, using the passed block + # x = 0 + # RSpec::Mocks.allow_message(bar, :foo) { x += 1 } + def self.allow_message(subject, message, opts={}, &block) + space.proxy_for(subject).add_stub(message, opts, &block) + end + + # Sets a message expectation on `subject`. + # @param subject the subject on which the message will be expected + # @param message a symbol, representing the message that will be + # expected. + # @param opts a hash of options, :expected_from is used to set the + # original call site + # @yield an optional implementation for the expectation + # + # @example Expect the message `foo` to receive `bar`, then call it + # RSpec::Mocks.expect_message(bar, :foo) + # bar.foo + def self.expect_message(subject, message, opts={}, &block) + space.proxy_for(subject).add_message_expectation(message, opts, &block) + end + + # Call the passed block and verify mocks after it has executed. This allows + # mock usage in arbitrary places, such as a `before(:all)` hook. + # + # @return [Object] the return value from the block + def self.with_temporary_scope + setup + + begin + result = yield + verify + result + ensure + teardown + end + end + + class << self + # @private + attr_reader :space + end + @space_stack = [] + @root_space = @space = RSpec::Mocks::RootSpace.new + + # @private + IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored' + + # To speed up boot time a bit, delay loading optional or rarely + # used features until their first use. + autoload :AnyInstance, "rspec/mocks/any_instance" + autoload :ExpectChain, "rspec/mocks/message_chain" + autoload :StubChain, "rspec/mocks/message_chain" + autoload :MarshalExtension, "rspec/mocks/marshal_extension" + + # Namespace for mock-related matchers. + module Matchers + # @private + # just a "tag" for rspec-mock matchers detection + module Matcher; end + + autoload :HaveReceived, "rspec/mocks/matchers/have_received" + autoload :Receive, "rspec/mocks/matchers/receive" + autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain" + autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages" + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance.rb new file mode 100644 index 0000000000..41eae81461 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance.rb @@ -0,0 +1,11 @@ +%w[ + any_instance/chain + any_instance/error_generator + any_instance/stub_chain + any_instance/stub_chain_chain + any_instance/expect_chain_chain + any_instance/expectation_chain + any_instance/message_chains + any_instance/recorder + any_instance/proxy +].each { |f| RSpec::Support.require_rspec_mocks(f) } diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/chain.rb new file mode 100644 index 0000000000..74d864b3a0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/chain.rb @@ -0,0 +1,111 @@ +module RSpec + module Mocks + # @private + module AnyInstance + # @private + class Chain + def initialize(recorder, *args, &block) + @recorder = recorder + @expectation_args = args + @expectation_block = block + @argument_list_matcher = ArgumentListMatcher::MATCH_ALL + end + + # @private + # + # Provides convenience methods for recording customizations on message + # expectations. + module Customizations + # @macro [attach] record + # @method $1(*args, &block) + # Records the `$1` message for playback against an instance that + # invokes a method stubbed or mocked using `any_instance`. + # + # @see RSpec::Mocks::MessageExpectation#$1 + # + def self.record(method_name) + define_method(method_name) do |*args, &block| + record(method_name, *args, &block) + end + end + + record :and_return + record :and_raise + record :and_throw + record :and_yield + record :and_call_original + record :and_wrap_original + record :with + record :once + record :twice + record :thrice + record :exactly + record :times + record :time + record :never + record :at_least + record :at_most + end + + include Customizations + + # @private + def playback!(instance) + message_expectation = create_message_expectation_on(instance) + messages.inject(message_expectation) do |object, message| + object.__send__(*message.first, &message.last) + end + end + + # @private + def constrained_to_any_of?(*constraints) + constraints.any? do |constraint| + messages.any? do |message| + message.first.first == constraint + end + end + end + + # @private + def matches_args?(*args) + @argument_list_matcher.args_match?(*args) + end + + # @private + def expectation_fulfilled! + @expectation_fulfilled = true + end + + def never + AnyInstance.error_generator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated? + super + end + + def with(*args, &block) + @argument_list_matcher = ArgumentListMatcher.new(*args) + super + end + + private + + def negated? + messages.any? { |(message, *_), _| message == :never } + end + + def messages + @messages ||= [] + end + + def last_message + messages.last.first.first unless messages.empty? + end + + def record(rspec_method_name, *args, &block) + verify_invocation_order(rspec_method_name, *args, &block) + messages << [args.unshift(rspec_method_name), block] + self + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/error_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/error_generator.rb new file mode 100644 index 0000000000..d1046cb0bc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/error_generator.rb @@ -0,0 +1,31 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ErrorGenerator < ::RSpec::Mocks::ErrorGenerator + def raise_second_instance_received_message_error(unfulfilled_expectations) + __raise "Exactly one instance should have received the following " \ + "message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}" + end + + def raise_does_not_implement_error(klass, method_name) + __raise "#{klass} does not implement ##{method_name}" + end + + def raise_message_already_received_by_other_instance_error(method_name, object_inspect, invoked_instance) + __raise "The message '#{method_name}' was received by #{object_inspect} " \ + "but has already been received by #{invoked_instance}" + end + + def raise_not_supported_with_prepend_error(method_name, problem_mod) + __raise "Using `any_instance` to stub a method (#{method_name}) that has been " \ + "defined on a prepended module (#{problem_mod}) is not supported." + end + end + + def self.error_generator + @error_generator ||= ErrorGenerator.new + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expect_chain_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expect_chain_chain.rb new file mode 100644 index 0000000000..c467ba93a9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expect_chain_chain.rb @@ -0,0 +1,31 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ExpectChainChain < StubChain + def initialize(*args) + super + @expectation_fulfilled = false + end + + def expectation_fulfilled? + @expectation_fulfilled + end + + def playback!(instance) + super.tap { @expectation_fulfilled = true } + end + + private + + def create_message_expectation_on(instance) + ::RSpec::Mocks::ExpectChain.expect_chain_on(instance, *@expectation_args, &@expectation_block) + end + + def invocation_order + EmptyInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expectation_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expectation_chain.rb new file mode 100644 index 0000000000..edf854826c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/expectation_chain.rb @@ -0,0 +1,50 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class ExpectationChain < Chain + def expectation_fulfilled? + @expectation_fulfilled || constrained_to_any_of?(:never) + end + + def initialize(*args, &block) + @expectation_fulfilled = false + super + end + + private + + def verify_invocation_order(_rspec_method_name, *_args, &_block) + end + end + + # @private + class PositiveExpectationChain < ExpectationChain + private + + def create_message_expectation_on(instance) + proxy = ::RSpec::Mocks.space.proxy_for(instance) + method_name, opts = @expectation_args + opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) + + me = proxy.add_message_expectation(method_name, opts, &@expectation_block) + if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? + me.and_yield_receiver_to_implementation + end + + me + end + + ExpectationInvocationOrder = + { + :and_return => [:with, nil], + :and_raise => [:with, nil], + }.freeze + + def invocation_order + ExpectationInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/message_chains.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/message_chains.rb new file mode 100644 index 0000000000..7f4d770a28 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/message_chains.rb @@ -0,0 +1,83 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class MessageChains + def initialize + @chains_by_method_name = Hash.new { |h, k| h[k] = [] } + end + + # @private + def [](method_name) + @chains_by_method_name[method_name] + end + + # @private + def add(method_name, chain) + @chains_by_method_name[method_name] << chain + chain + end + + # @private + def remove_stub_chains_for!(method_name) + @chains_by_method_name[method_name].reject! do |chain| + StubChain === chain + end + end + + # @private + def has_expectation?(method_name) + @chains_by_method_name[method_name].find do |chain| + ExpectationChain === chain + end + end + + # @private + def each_unfulfilled_expectation_matching(method_name, *args) + @chains_by_method_name[method_name].each do |chain| + yield chain if !chain.expectation_fulfilled? && chain.matches_args?(*args) + end + end + + # @private + def all_expectations_fulfilled? + @chains_by_method_name.all? do |_method_name, chains| + chains.all? { |chain| chain.expectation_fulfilled? } + end + end + + # @private + def unfulfilled_expectations + @chains_by_method_name.map do |method_name, chains| + method_name.to_s if ExpectationChain === chains.last && !chains.last.expectation_fulfilled? + end.compact + end + + # @private + def received_expected_message!(method_name) + @chains_by_method_name[method_name].each do |chain| + chain.expectation_fulfilled! + end + end + + # @private + def playback!(instance, method_name) + raise_if_second_instance_to_receive_message(instance) + @chains_by_method_name[method_name].each do |chain| + chain.playback!(instance) + end + end + + private + + def raise_if_second_instance_to_receive_message(instance) + @instance_with_expectation ||= instance if ExpectationChain === instance + return unless ExpectationChain === instance + return if @instance_with_expectation.equal?(instance) + + AnyInstance.error_generator.raise_second_instance_received_message_error(unfulfilled_expectations) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/proxy.rb new file mode 100644 index 0000000000..8e108bdfa3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/proxy.rb @@ -0,0 +1,116 @@ +module RSpec + module Mocks + module AnyInstance + # @private + # The `AnyInstance::Recorder` is responsible for redefining the klass's + # instance method in order to add any stubs/expectations the first time + # the method is called. It's not capable of updating a stub on an instance + # that's already been previously stubbed (either directly, or via + # `any_instance`). + # + # This proxy sits in front of the recorder and delegates both to it + # and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed + # instance of the class, in order to propagates changes to the instances. + # + # Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless + # and is not persisted in `RSpec::Mocks.space`. + # + # Proxying for the message expectation fluent interface (typically chained + # off of the return value of one of these methods) is provided by the + # `FluentInterfaceProxy` class below. + class Proxy + def initialize(recorder, target_proxies) + @recorder = recorder + @target_proxies = target_proxies + end + + def klass + @recorder.klass + end + + def stub(method_name_or_method_map, &block) + if Hash === method_name_or_method_map + method_name_or_method_map.each do |method_name, return_value| + stub(method_name).and_return(return_value) + end + else + perform_proxying(__method__, [method_name_or_method_map], block) do |proxy| + proxy.add_stub(method_name_or_method_map, &block) + end + end + end + + def unstub(method_name) + perform_proxying(__method__, [method_name], nil) do |proxy| + proxy.remove_stub_if_present(method_name) + end + end + + def stub_chain(*chain, &block) + perform_proxying(__method__, chain, block) do |proxy| + Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block) + end + end + + def expect_chain(*chain, &block) + perform_proxying(__method__, chain, block) do |proxy| + Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block) + end + end + + def should_receive(method_name, &block) + perform_proxying(__method__, [method_name], block) do |proxy| + # Yeah, this is a bit odd...but if we used `add_message_expectation` + # then it would act like `expect_every_instance_of(klass).to receive`. + # The any_instance recorder takes care of validating that an instance + # received the message. + proxy.add_stub(method_name, &block) + end + end + + def should_not_receive(method_name, &block) + perform_proxying(__method__, [method_name], block) do |proxy| + proxy.add_message_expectation(method_name, &block).never + end + end + + private + + def perform_proxying(method_name, args, block, &target_proxy_block) + recorder_value = @recorder.__send__(method_name, *args, &block) + proxy_values = @target_proxies.map(&target_proxy_block) + FluentInterfaceProxy.new([recorder_value] + proxy_values) + end + end + + # @private + # Delegates messages to each of the given targets in order to + # provide the fluent interface that is available off of message + # expectations when dealing with `any_instance`. + # + # `targets` will typically contain 1 of the `AnyInstance::Recorder` + # return values and N `MessageExpectation` instances (one per instance + # of the `any_instance` klass). + class FluentInterfaceProxy + def initialize(targets) + @targets = targets + end + + if RUBY_VERSION.to_f > 1.8 + def respond_to_missing?(method_name, include_private=false) + super || @targets.first.respond_to?(method_name, include_private) + end + else + def respond_to?(method_name, include_private=false) + super || @targets.first.respond_to?(method_name, include_private) + end + end + + def method_missing(*args, &block) + return_values = @targets.map { |t| t.__send__(*args, &block) } + FluentInterfaceProxy.new(return_values) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/recorder.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/recorder.rb new file mode 100644 index 0000000000..aad6ed4c8b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/recorder.rb @@ -0,0 +1,295 @@ +module RSpec + module Mocks + module AnyInstance + # Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`, + # which records stubs and message expectations for later playback on + # instances of `TheClass`. + # + # Further constraints are stored in instances of [Chain](Chain). + # + # @see AnyInstance + # @see Chain + class Recorder + # @private + attr_reader :message_chains, :stubs, :klass + + def initialize(klass) + @message_chains = MessageChains.new + @stubs = Hash.new { |hash, key| hash[key] = [] } + @observed_methods = [] + @played_methods = {} + @backed_up_method_owner = {} + @klass = klass + @expectation_set = false + + return unless RSpec::Mocks.configuration.verify_partial_doubles? + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call(ObjectReference.for(klass)) + end + end + + # Initializes the recording a stub to be played back against any + # instance of this object that invokes the submitted method. + # + # @see Methods#stub + def stub(method_name, &block) + observe!(method_name) + message_chains.add(method_name, StubChain.new(self, method_name, &block)) + end + + # Initializes the recording a stub chain to be played back against any + # instance of this object that invokes the method matching the first + # argument. + # + # @see Methods#stub_chain + def stub_chain(*method_names_and_optional_return_values, &block) + normalize_chain(*method_names_and_optional_return_values) do |method_name, args| + observe!(method_name) + message_chains.add(method_name, StubChainChain.new(self, *args, &block)) + end + end + + # @private + def expect_chain(*method_names_and_optional_return_values, &block) + @expectation_set = true + normalize_chain(*method_names_and_optional_return_values) do |method_name, args| + observe!(method_name) + message_chains.add(method_name, ExpectChainChain.new(self, *args, &block)) + end + end + + # Initializes the recording a message expectation to be played back + # against any instance of this object that invokes the submitted + # method. + # + # @see Methods#should_receive + def should_receive(method_name, &block) + @expectation_set = true + observe!(method_name) + message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block)) + end + + # The opposite of `should_receive` + # + # @see Methods#should_not_receive + def should_not_receive(method_name, &block) + should_receive(method_name, &block).never + end + + # Removes any previously recorded stubs, stub_chains or message + # expectations that use `method_name`. + # + # @see Methods#unstub + def unstub(method_name) + unless @observed_methods.include?(method_name.to_sym) + AnyInstance.error_generator.raise_method_not_stubbed_error(method_name) + end + message_chains.remove_stub_chains_for!(method_name) + stubs[method_name].clear + stop_observing!(method_name) unless message_chains.has_expectation?(method_name) + end + + # @api private + # + # Used internally to verify that message expectations have been + # fulfilled. + def verify + return unless @expectation_set + return if message_chains.all_expectations_fulfilled? + + AnyInstance.error_generator.raise_second_instance_received_message_error(message_chains.unfulfilled_expectations) + end + + # @private + def stop_all_observation! + @observed_methods.each { |method_name| restore_method!(method_name) } + end + + # @private + def playback!(instance, method_name) + RSpec::Mocks.space.ensure_registered(instance) + message_chains.playback!(instance, method_name) + @played_methods[method_name] = instance + received_expected_message!(method_name) if message_chains.has_expectation?(method_name) + end + + # @private + def instance_that_received(method_name) + @played_methods[method_name] + end + + # @private + def build_alias_method_name(method_name) + "__#{method_name}_without_any_instance__" + end + + # @private + def already_observing?(method_name) + @observed_methods.include?(method_name) || super_class_observing?(method_name) + end + + # @private + def notify_received_message(_object, message, args, _blk) + has_expectation = false + + message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation| + has_expectation = true + expectation.expectation_fulfilled! + end + + return unless has_expectation + + restore_method!(message) + mark_invoked!(message) + end + + protected + + def stop_observing!(method_name) + restore_method!(method_name) + @observed_methods.delete(method_name) + super_class_observers_for(method_name).each do |ancestor| + ::RSpec::Mocks.space. + any_instance_recorder_for(ancestor).stop_observing!(method_name) + end + end + + private + + def ancestor_is_an_observer?(method_name) + lambda do |ancestor| + unless ancestor == @klass + ::RSpec::Mocks.space. + any_instance_recorder_for(ancestor).already_observing?(method_name) + end + end + end + + def super_class_observers_for(method_name) + @klass.ancestors.select(&ancestor_is_an_observer?(method_name)) + end + + def super_class_observing?(method_name) + @klass.ancestors.any?(&ancestor_is_an_observer?(method_name)) + end + + def normalize_chain(*args) + args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a } + yield args.first, args + end + + def received_expected_message!(method_name) + message_chains.received_expected_message!(method_name) + restore_method!(method_name) + mark_invoked!(method_name) + end + + def restore_method!(method_name) + if public_protected_or_private_method_defined?(build_alias_method_name(method_name)) + restore_original_method!(method_name) + else + remove_dummy_method!(method_name) + end + end + + def restore_original_method!(method_name) + return unless @klass.instance_method(method_name).owner == @klass + + alias_method_name = build_alias_method_name(method_name) + @klass.class_exec(@backed_up_method_owner) do |backed_up_method_owner| + remove_method method_name + + # A @klass can have methods implemented (see Method#owner) in @klass + # or inherited from a superclass. In ruby 2.2 and earlier, we can copy + # a method regardless of the 'owner' and restore it to @klass after + # because a call to 'super' from @klass's copied method would end up + # calling the original class's superclass's method. + # + # With the commit below, available starting in 2.3.0, ruby changed + # this behavior and a call to 'super' from the method copied to @klass + # will call @klass's superclass method, which is the original + # implementer of this method! This leads to very strange errors + # if @klass's copied method calls 'super', since it would end up + # calling itself, the original method implemented in @klass's + # superclass. + # + # For ruby 2.3 and above, we need to only restore methods that + # @klass originally owned. + # + # https://github.com/ruby/ruby/commit/c8854d2ca4be9ee6946e6d17b0e17d9ef130ee81 + if RUBY_VERSION < "2.3" || backed_up_method_owner[method_name.to_sym] == self + alias_method method_name, alias_method_name + end + remove_method alias_method_name + end + end + + def remove_dummy_method!(method_name) + @klass.class_exec do + remove_method method_name + end + end + + def backup_method!(method_name) + return unless public_protected_or_private_method_defined?(method_name) + + alias_method_name = build_alias_method_name(method_name) + @backed_up_method_owner[method_name.to_sym] ||= @klass.instance_method(method_name).owner + @klass.class_exec do + alias_method alias_method_name, method_name + end + end + + def public_protected_or_private_method_defined?(method_name) + MethodReference.method_defined_at_any_visibility?(@klass, method_name) + end + + def observe!(method_name) + allow_no_prepended_module_definition_of(method_name) + + if RSpec::Mocks.configuration.verify_partial_doubles? && !Mocks.configuration.temporarily_suppress_partial_double_verification + unless public_protected_or_private_method_defined?(method_name) + AnyInstance.error_generator.raise_does_not_implement_error(@klass, method_name) + end + end + + stop_observing!(method_name) if already_observing?(method_name) + @observed_methods << method_name + backup_method!(method_name) + recorder = self + @klass.__send__(:define_method, method_name) do |*args, &blk| + recorder.playback!(self, method_name) + __send__(method_name, *args, &blk) + end + @klass.__send__(:ruby2_keywords, method_name) if @klass.respond_to?(:ruby2_keywords, true) + end + + def mark_invoked!(method_name) + backup_method!(method_name) + recorder = self + @klass.__send__(:define_method, method_name) do |*_args, &_blk| + invoked_instance = recorder.instance_that_received(method_name) + inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>" + AnyInstance.error_generator.raise_message_already_received_by_other_instance_error( + method_name, inspect, invoked_instance + ) + end + end + + if Support::RubyFeatures.module_prepends_supported? + def allow_no_prepended_module_definition_of(method_name) + prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass) + problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) } + return unless problem_mod + + AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod) + end + else + def allow_no_prepended_module_definition_of(_method_name) + # nothing to do; prepends aren't supported on this version of ruby + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain.rb new file mode 100644 index 0000000000..c4c0ab748f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain.rb @@ -0,0 +1,51 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class StubChain < Chain + # @private + def expectation_fulfilled? + true + end + + private + + def create_message_expectation_on(instance) + proxy = ::RSpec::Mocks.space.proxy_for(instance) + method_name, opts = @expectation_args + opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) + + stub = proxy.add_stub(method_name, opts, &@expectation_block) + @recorder.stubs[stub.message] << stub + + if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? + stub.and_yield_receiver_to_implementation + end + + stub + end + + InvocationOrder = + { + :and_return => [:with, nil], + :and_raise => [:with, nil], + :and_yield => [:with, :and_yield, nil], + :and_throw => [:with, nil], + :and_call_original => [:with, nil], + :and_wrap_original => [:with, nil] + }.freeze + + EmptyInvocationOrder = {}.freeze + + def invocation_order + InvocationOrder + end + + def verify_invocation_order(rspec_method_name, *_args, &_block) + return if invocation_order.fetch(rspec_method_name, [nil]).include?(last_message) + raise NoMethodError, "Undefined method #{rspec_method_name}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain_chain.rb new file mode 100644 index 0000000000..495511c096 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/any_instance/stub_chain_chain.rb @@ -0,0 +1,23 @@ +module RSpec + module Mocks + module AnyInstance + # @private + class StubChainChain < StubChain + def initialize(*args) + super + @expectation_fulfilled = false + end + + private + + def create_message_expectation_on(instance) + ::RSpec::Mocks::StubChain.stub_chain_on(instance, *@expectation_args, &@expectation_block) + end + + def invocation_order + EmptyInvocationOrder + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_list_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_list_matcher.rb new file mode 100644 index 0000000000..f8adcd9313 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_list_matcher.rb @@ -0,0 +1,117 @@ +# We intentionally do not use the `RSpec::Support.require...` methods +# here so that this file can be loaded individually, as documented +# below. +require 'rspec/mocks/argument_matchers' +require 'rspec/support/fuzzy_matcher' + +module RSpec + module Mocks + # Wrapper for matching arguments against a list of expected values. Used by + # the `with` method on a `MessageExpectation`: + # + # expect(object).to receive(:message).with(:a, 'b', 3) + # object.message(:a, 'b', 3) + # + # Values passed to `with` can be literal values or argument matchers that + # match against the real objects .e.g. + # + # expect(object).to receive(:message).with(hash_including(:a => 'b')) + # + # Can also be used directly to match the contents of any `Array`. This + # enables 3rd party mocking libs to take advantage of rspec's argument + # matching without using the rest of rspec-mocks. + # + # require 'rspec/mocks/argument_list_matcher' + # include RSpec::Mocks::ArgumentMatchers + # + # arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b')) + # arg_list_matcher.args_match?(123, :a => 'b') + # + # This class is immutable. + # + # @see ArgumentMatchers + class ArgumentListMatcher + # @private + attr_reader :expected_args + + # @api public + # @param [Array] expected_args a list of expected literals and/or argument matchers + # + # Initializes an `ArgumentListMatcher` with a collection of literal + # values and/or argument matchers. + # + # @see ArgumentMatchers + # @see #args_match? + def initialize(*expected_args) + @expected_args = expected_args + ensure_expected_args_valid! + end + ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true) + + # @api public + # @param [Array] actual_args + # + # Matches each element in the `expected_args` against the element in the same + # position of the arguments passed to `new`. + # + # @see #initialize + def args_match?(*actual_args) + expected_args = resolve_expected_args_based_on(actual_args) + + return false if expected_args.size != actual_args.size + + if RUBY_VERSION >= "3" + # If the expectation was set with keywords, while the actual method was called with a positional hash argument, they don't match. + # If the expectation was set without keywords, e.g., with({a: 1}), then it fine to call it with either foo(a: 1) or foo({a: 1}). + # This corresponds to Ruby semantics, as if the method was def foo(options). + if Hash === expected_args.last && Hash === actual_args.last + if !Hash.ruby2_keywords_hash?(actual_args.last) && Hash.ruby2_keywords_hash?(expected_args.last) + return false + end + end + end + + Support::FuzzyMatcher.values_match?(expected_args, actual_args) + end + ruby2_keywords :args_match? if respond_to?(:ruby2_keywords, true) + + # @private + # Resolves abstract arg placeholders like `no_args` and `any_args` into + # a more concrete arg list based on the provided `actual_args`. + def resolve_expected_args_based_on(actual_args) + return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args + + any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } + return expected_args unless any_args_index + + replace_any_args_with_splat_of_anything(any_args_index, actual_args.count) + end + + private + + def replace_any_args_with_splat_of_anything(before_count, actual_args_count) + any_args_count = actual_args_count - expected_args.count + 1 + after_count = expected_args.count - before_count - 1 + + any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE } + expected_args.first(before_count) + any_args + expected_args.last(after_count) + end + + def ensure_expected_args_valid! + if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1 + raise ArgumentError, "`any_args` can only be passed to " \ + "`with` once but you have passed it multiple times." + elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a } + raise ArgumentError, "`no_args` can only be passed as a " \ + "singleton argument to `with` (i.e. `with(no_args)`), " \ + "but you have passed additional arguments." + end + end + + # Value that will match all argument lists. + # + # @private + MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher::INSTANCE) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_matchers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_matchers.rb new file mode 100644 index 0000000000..fe41744b6b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/argument_matchers.rb @@ -0,0 +1,322 @@ +# This cannot take advantage of our relative requires, since this file is a +# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for +# details. +require 'rspec/support/matcher_definition' + +module RSpec + module Mocks + # ArgumentMatchers are placeholders that you can include in message + # expectations to match arguments against a broader check than simple + # equality. + # + # With the exception of `any_args` and `no_args`, they all match against + # the arg in same position in the argument list. + # + # @see ArgumentListMatcher + module ArgumentMatchers + # Acts like an arg splat, matching any number of args at any point in an arg list. + # + # @example + # expect(object).to receive(:message).with(1, 2, any_args) + # + # # matches any of these: + # object.message(1, 2) + # object.message(1, 2, 3) + # object.message(1, 2, 3, 4) + def any_args + AnyArgsMatcher::INSTANCE + end + + # Matches any argument at all. + # + # @example + # expect(object).to receive(:message).with(anything) + def anything + AnyArgMatcher::INSTANCE + end + + # Matches no arguments. + # + # @example + # expect(object).to receive(:message).with(no_args) + def no_args + NoArgsMatcher::INSTANCE + end + + # Matches if the actual argument responds to the specified messages. + # + # @example + # expect(object).to receive(:message).with(duck_type(:hello)) + # expect(object).to receive(:message).with(duck_type(:hello, :goodbye)) + def duck_type(*args) + DuckTypeMatcher.new(*args) + end + + # Matches a boolean value. + # + # @example + # expect(object).to receive(:message).with(boolean()) + def boolean + BooleanMatcher::INSTANCE + end + + # Matches a hash that includes the specified key(s) or key/value pairs. + # Ignores any additional keys. + # + # @example + # expect(object).to receive(:message).with(hash_including(:key => val)) + # expect(object).to receive(:message).with(hash_including(:key)) + # expect(object).to receive(:message).with(hash_including(:key, :key2 => val2)) + def hash_including(*args) + HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args)) + end + + # Matches an array that includes the specified items at least once. + # Ignores duplicates and additional values + # + # @example + # expect(object).to receive(:message).with(array_including(1,2,3)) + # expect(object).to receive(:message).with(array_including([1,2,3])) + def array_including(*args) + actually_an_array = Array === args.first && args.count == 1 ? args.first : args + ArrayIncludingMatcher.new(actually_an_array) + end + + # Matches a hash that doesn't include the specified key(s) or key/value. + # + # @example + # expect(object).to receive(:message).with(hash_excluding(:key => val)) + # expect(object).to receive(:message).with(hash_excluding(:key)) + # expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2)) + def hash_excluding(*args) + HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args)) + end + + alias_method :hash_not_including, :hash_excluding + + # Matches if `arg.instance_of?(klass)` + # + # @example + # expect(object).to receive(:message).with(instance_of(Thing)) + def instance_of(klass) + InstanceOf.new(klass) + end + + alias_method :an_instance_of, :instance_of + + # Matches if `arg.kind_of?(klass)` + # + # @example + # expect(object).to receive(:message).with(kind_of(Thing)) + def kind_of(klass) + KindOf.new(klass) + end + + alias_method :a_kind_of, :kind_of + + # @private + def self.anythingize_lonely_keys(*args) + hash = Hash === args.last ? args.delete_at(-1) : {} + args.each { | arg | hash[arg] = AnyArgMatcher::INSTANCE } + hash + end + + # Intended to be subclassed by stateless, immutable argument matchers. + # Provides a `::INSTANCE` constant for accessing a global + # singleton instance of the matcher. There is no need to construct + # multiple instance since there is no state. It also facilities the + # special case logic we need for some of these matchers, by making it + # easy to do comparisons like: `[klass::INSTANCE] == args` rather than + # `args.count == 1 && klass === args.first`. + # + # @private + class SingletonMatcher + private_class_method :new + + def self.inherited(subklass) + subklass.const_set(:INSTANCE, subklass.send(:new)) + end + end + + # @private + class AnyArgsMatcher < SingletonMatcher + def description + "*(any args)" + end + end + + # @private + class AnyArgMatcher < SingletonMatcher + def ===(_other) + true + end + + def description + "anything" + end + end + + # @private + class NoArgsMatcher < SingletonMatcher + def description + "no args" + end + end + + # @private + class BooleanMatcher < SingletonMatcher + def ===(value) + true == value || false == value + end + + def description + "boolean" + end + end + + # @private + class BaseHashMatcher + def initialize(expected) + @expected = expected + end + + def ===(predicate, actual) + @expected.__send__(predicate) do |k, v| + actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k]) + end + rescue NoMethodError + false + end + + def description(name) + "#{name}(#{formatted_expected_hash.inspect.sub(/^\{/, "").sub(/\}$/, "")})" + end + + private + + def formatted_expected_hash + Hash[ + @expected.map do |k, v| + k = RSpec::Support.rspec_description_for_object(k) + v = RSpec::Support.rspec_description_for_object(v) + + [k, v] + end + ] + end + end + + # @private + class HashIncludingMatcher < BaseHashMatcher + def ===(actual) + super(:all?, actual) + end + + def description + super("hash_including") + end + end + + # @private + class HashExcludingMatcher < BaseHashMatcher + def ===(actual) + super(:none?, actual) + end + + def description + super("hash_not_including") + end + end + + # @private + class ArrayIncludingMatcher + def initialize(expected) + @expected = expected + end + + def ===(actual) + actual = actual.uniq + @expected.uniq.all? do |expected_element| + actual.any? do |actual_element| + RSpec::Support::FuzzyMatcher.values_match?(expected_element, actual_element) + end + end + rescue NoMethodError + false + end + + def description + "array_including(#{formatted_expected_values})" + end + + private + + def formatted_expected_values + @expected.map do |x| + RSpec::Support.rspec_description_for_object(x) + end.join(", ") + end + end + + # @private + class DuckTypeMatcher + def initialize(*methods_to_respond_to) + @methods_to_respond_to = methods_to_respond_to + end + + def ===(value) + @methods_to_respond_to.all? { |message| value.respond_to?(message) } + end + + def description + "duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})" + end + end + + # @private + class InstanceOf + def initialize(klass) + @klass = klass + end + + def ===(actual) + actual.instance_of?(@klass) + end + + def description + "an_instance_of(#{@klass.name})" + end + end + + # @private + class KindOf + def initialize(klass) + @klass = klass + end + + def ===(actual) + actual.kind_of?(@klass) + end + + def description + "kind of #{@klass.name}" + end + end + + matcher_namespace = name + '::' + ::RSpec::Support.register_matcher_definition do |object| + # This is the best we have for now. We should tag all of our matchers + # with a module or something so we can test for it directly. + # + # (Note Module#parent in ActiveSupport is defined in a similar way.) + begin + object.class.name.include?(matcher_namespace) + rescue NoMethodError + # Some objects, like BasicObject, don't implemented standard + # reflection methods. + false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/configuration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/configuration.rb new file mode 100644 index 0000000000..5962215f80 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/configuration.rb @@ -0,0 +1,212 @@ +module RSpec + module Mocks + # Provides configuration options for rspec-mocks. + class Configuration + def initialize + @allow_message_expectations_on_nil = nil + @yield_receiver_to_any_instance_implementation_blocks = true + @verify_doubled_constant_names = false + @transfer_nested_constants = false + @verify_partial_doubles = false + @temporarily_suppress_partial_double_verification = false + @color = false + end + + # Sets whether RSpec will warn, ignore, or fail a test when + # expectations are set on nil. + # By default, when this flag is not set, warning messages are issued when + # expectations are set on nil. This is to prevent false-positives and to + # catch potential bugs early on. + # When set to `true`, warning messages are suppressed. + # When set to `false`, it will raise an error. + # + # @example + # RSpec.configure do |config| + # config.mock_with :rspec do |mocks| + # mocks.allow_message_expectations_on_nil = false + # end + # end + attr_accessor :allow_message_expectations_on_nil + + def yield_receiver_to_any_instance_implementation_blocks? + @yield_receiver_to_any_instance_implementation_blocks + end + + # Sets whether or not RSpec will yield the receiving instance of a + # message to blocks that are used for any_instance stub implementations. + # When set, the first yielded argument will be the receiving instance. + # Defaults to `true`. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.yield_receiver_to_any_instance_implementation_blocks = false + # end + # end + attr_writer :yield_receiver_to_any_instance_implementation_blocks + + # Adds `stub` and `should_receive` to the given + # modules or classes. This is usually only necessary + # if you application uses some proxy classes that + # "strip themselves down" to a bare minimum set of + # methods and remove `stub` and `should_receive` in + # the process. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.add_stub_and_should_receive_to Delegator + # end + # end + # + def add_stub_and_should_receive_to(*modules) + modules.each do |mod| + Syntax.enable_should(mod) + end + end + + # Provides the ability to set either `expect`, + # `should` or both syntaxes. RSpec uses `expect` + # syntax by default. This is needed if you want to + # explicitly enable `should` syntax and/or explicitly + # disable `expect` syntax. + # + # @example + # RSpec.configure do |rspec| + # rspec.mock_with :rspec do |mocks| + # mocks.syntax = [:expect, :should] + # end + # end + # + def syntax=(*values) + syntaxes = values.flatten + if syntaxes.include?(:expect) + Syntax.enable_expect + else + Syntax.disable_expect + end + + if syntaxes.include?(:should) + Syntax.enable_should + else + Syntax.disable_should + end + end + + # Returns an array with a list of syntaxes + # that are enabled. + # + # @example + # unless RSpec::Mocks.configuration.syntax.include?(:expect) + # raise "this RSpec extension gem requires the rspec-mocks `:expect` syntax" + # end + # + def syntax + syntaxes = [] + syntaxes << :should if Syntax.should_enabled? + syntaxes << :expect if Syntax.expect_enabled? + syntaxes + end + + def verify_doubled_constant_names? + !!@verify_doubled_constant_names + end + + # When this is set to true, an error will be raised when + # `instance_double` or `class_double` is given the name of an undefined + # constant. You probably only want to set this when running your entire + # test suite, with all production code loaded. Setting this for an + # isolated unit test will prevent you from being able to isolate it! + attr_writer :verify_doubled_constant_names + + # Provides a way to perform customisations when verifying doubles. + # + # @example + # RSpec::Mocks.configuration.before_verifying_doubles do |ref| + # ref.some_method! + # end + def before_verifying_doubles(&block) + verifying_double_callbacks << block + end + alias :when_declaring_verifying_double :before_verifying_doubles + + # @api private + # Returns an array of blocks to call when verifying doubles + def verifying_double_callbacks + @verifying_double_callbacks ||= [] + end + + def transfer_nested_constants? + !!@transfer_nested_constants + end + + # Sets the default for the `transfer_nested_constants` option when + # stubbing constants. + attr_writer :transfer_nested_constants + + # When set to true, partial mocks will be verified the same as object + # doubles. Any stubs will have their arguments checked against the original + # method, and methods that do not exist cannot be stubbed. + def verify_partial_doubles=(val) + @verify_partial_doubles = !!val + end + + def verify_partial_doubles? + @verify_partial_doubles + end + + # @private + # Used to track wether we are temporarily suppressing verifying partial + # doubles with `without_partial_double_verification { ... }` + attr_accessor :temporarily_suppress_partial_double_verification + + if ::RSpec.respond_to?(:configuration) + def color? + ::RSpec.configuration.color_enabled? + end + else + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + attr_writer :color + + # Indicates whether or not diffs should be colored. + # Delegates to rspec-core's color option if rspec-core + # is loaded; otherwise you can set it here. + def color? + @color + end + end + + # Monkey-patch `Marshal.dump` to enable dumping of mocked or stubbed + # objects. By default this will not work since RSpec mocks works by + # adding singleton methods that cannot be serialized. This patch removes + # these singleton methods before serialization. Setting to falsey removes + # the patch. + # + # This method is idempotent. + def patch_marshal_to_support_partial_doubles=(val) + if val + RSpec::Mocks::MarshalExtension.patch! + else + RSpec::Mocks::MarshalExtension.unpatch! + end + end + + # @api private + # Resets the configured syntax to the default. + def reset_syntaxes_to_default + self.syntax = [:should, :expect] + RSpec::Mocks::Syntax.warn_about_should! + end + end + + # Mocks specific configuration, as distinct from `RSpec.configuration` + # which is core RSpec configuration. + def self.configuration + @configuration ||= Configuration.new + end + + configuration.reset_syntaxes_to_default + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/error_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/error_generator.rb new file mode 100644 index 0000000000..8e7e2c1fc4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/error_generator.rb @@ -0,0 +1,390 @@ +RSpec::Support.require_rspec_support "object_formatter" + +module RSpec + module Mocks + # Raised when a message expectation is not satisfied. + MockExpectationError = Class.new(Exception) + + # Raised when a test double is used after it has been torn + # down (typically at the end of an rspec-core example). + ExpiredTestDoubleError = Class.new(MockExpectationError) + + # Raised when doubles or partial doubles are used outside of the per-test lifecycle. + OutsideOfExampleError = Class.new(StandardError) + + # Raised when an expectation customization method (e.g. `with`, + # `and_return`) is called on a message expectation which has already been + # invoked. + MockExpectationAlreadyInvokedError = Class.new(Exception) + + # Raised for situations that RSpec cannot support due to mutations made + # externally on arguments that RSpec is holding onto to use for later + # comparisons. + # + # @deprecated We no longer raise this error but the constant remains until + # RSpec 4 for SemVer reasons. + CannotSupportArgMutationsError = Class.new(StandardError) + + # @private + UnsupportedMatcherError = Class.new(StandardError) + # @private + NegationUnsupportedError = Class.new(StandardError) + # @private + VerifyingDoubleNotDefinedError = Class.new(StandardError) + + # @private + class ErrorGenerator + attr_writer :opts + + def initialize(target=nil) + @target = target + end + + # @private + def opts + @opts ||= {} + end + + # @private + def raise_unexpected_message_error(message, args) + __raise "#{intro} received unexpected message :#{message} with #{format_args(args)}" + end + + # @private + def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil) + __raise error_message(expectation, args_for_multiple_calls), nil, source_id + end + + # @private + def raise_missing_default_stub_error(expectation, args_for_multiple_calls) + __raise( + error_message(expectation, args_for_multiple_calls) + + "\n Please stub a default value first if message might be received with other args as well. \n" + ) + end + + # @private + def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil) + __raise error_message(expectation, args_for_multiple_calls), backtrace_line + end + + def default_error_message(expectation, expected_args, actual_args) + "#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}".dup + end + + # rubocop:disable Metrics/ParameterLists + # @private + def raise_expectation_error(message, expected_received_count, argument_list_matcher, + actual_received_count, expectation_count_type, args, + backtrace_line=nil, source_id=nil) + expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher) + received_part = received_part_of_expectation_error(actual_received_count, args) + __raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id + end + # rubocop:enable Metrics/ParameterLists + + # @private + def raise_unimplemented_error(doubled_module, method_name, object) + message = case object + when InstanceVerifyingDouble + "the %s class does not implement the instance method: %s".dup << + if ObjectMethodReference.for(doubled_module, method_name).implemented? + ". Perhaps you meant to use `class_double` instead?" + else + "" + end + when ClassVerifyingDouble + "the %s class does not implement the class method: %s".dup << + if InstanceMethodReference.for(doubled_module, method_name).implemented? + ". Perhaps you meant to use `instance_double` instead?" + else + "" + end + else + "%s does not implement: %s" + end + + __raise message % [doubled_module.description, method_name] + end + + # @private + def raise_non_public_error(method_name, visibility) + raise NoMethodError, "%s method `%s' called on %s" % [ + visibility, method_name, intro + ] + end + + # @private + def raise_invalid_arguments_error(verifier) + __raise verifier.error_message + end + + # @private + def raise_expired_test_double_error + raise ExpiredTestDoubleError, + "#{intro} was originally created in one example but has leaked into " \ + "another example and can no longer be used. rspec-mocks' doubles are " \ + "designed to only last for one example, and you need to create a new " \ + "one in each example you wish to use it for." + end + + # @private + def describe_expectation(verb, message, expected_received_count, _actual_received_count, args) + "#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}" + end + + # @private + def raise_out_of_order_error(message) + __raise "#{intro} received :#{message} out of order" + end + + # @private + def raise_missing_block_error(args_to_yield) + __raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed" + end + + # @private + def raise_wrong_arity_error(args_to_yield, signature) + __raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}" + end + + # @private + def raise_only_valid_on_a_partial_double(method) + __raise "#{intro} is a pure test double. `#{method}` is only " \ + "available on a partial double." + end + + # @private + def raise_expectation_on_unstubbed_method(method) + __raise "#{intro} expected to have received #{method}, but that " \ + "object is not a spy or method has not been stubbed." + end + + # @private + def raise_expectation_on_mocked_method(method) + __raise "#{intro} expected to have received #{method}, but that " \ + "method has been mocked instead of stubbed or spied." + end + + # @private + def raise_double_negation_error(wrapped_expression) + __raise "Isn't life confusing enough? You've already set a " \ + "negative message expectation and now you are trying to " \ + "negate it again with `never`. What does an expression like " \ + "`#{wrapped_expression}.not_to receive(:msg).never` even mean?" + end + + # @private + def raise_verifying_double_not_defined_error(ref) + notify(VerifyingDoubleNotDefinedError.new( + "#{ref.description.inspect} is not a defined constant. " \ + "Perhaps you misspelt it? " \ + "Disable check with `verify_doubled_constant_names` configuration option." + )) + end + + # @private + def raise_have_received_disallowed(type, reason) + __raise "Using #{type}(...) with the `have_received` " \ + "matcher is not supported#{reason}." + end + + # @private + def raise_cant_constrain_count_for_negated_have_received_error(count_constraint) + __raise "can't use #{count_constraint} when negative" + end + + # @private + def raise_method_not_stubbed_error(method_name) + __raise "The method `#{method_name}` was not stubbed or was already unstubbed" + end + + # @private + def raise_already_invoked_error(message, calling_customization) + error_message = "The message expectation for #{intro}.#{message} has already been invoked " \ + "and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \ + "customizations must be applied before it is used for the first time." + + notify MockExpectationAlreadyInvokedError.new(error_message) + end + + def raise_expectation_on_nil_error(method_name) + __raise expectation_on_nil_message(method_name) + end + + def expectation_on_nil_message(method_name) + "An expectation of `:#{method_name}` was set on `nil`. " \ + "To allow expectations on `nil` and suppress this message, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `true`. " \ + "To disallow expectations on `nil`, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `false`" + end + + # @private + def intro(unwrapped=false) + case @target + when TestDouble then TestDoubleFormatter.format(@target, unwrapped) + when Class then + formatted = "#{@target.inspect} (class)" + return formatted if unwrapped + "#<#{formatted}>" + when NilClass then "nil" + else @target.inspect + end + end + + # @private + def method_call_args_description(args, generic_prefix=" with arguments: ", matcher_prefix=" with ") + case args.first + when ArgumentMatchers::AnyArgsMatcher then "#{matcher_prefix}any arguments" + when ArgumentMatchers::NoArgsMatcher then "#{matcher_prefix}no arguments" + else + if yield + "#{generic_prefix}#{format_args(args)}" + else + "" + end + end + end + + private + + def received_part_of_expectation_error(actual_received_count, args) + "received: #{count_message(actual_received_count)}" + + method_call_args_description(args) do + actual_received_count > 0 && args.length > 0 + end + end + + def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher) + "expected: #{count_message(expected_received_count, expectation_count_type)}" + + method_call_args_description(argument_list_matcher.expected_args) do + argument_list_matcher.expected_args.length > 0 + end + end + + def unexpected_arguments_message(expected_args_string, actual_args_string) + "with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}" + end + + def error_message(expectation, args_for_multiple_calls) + expected_args = format_args(expectation.expected_args) + actual_args = format_received_args(args_for_multiple_calls) + + if RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash? + expected_hash = expectation.expected_args.last + actual_hash = args_for_multiple_calls.last.last + if Hash === expected_hash && Hash === actual_hash && + (Hash.ruby2_keywords_hash?(expected_hash) != Hash.ruby2_keywords_hash?(actual_hash)) + + actual_description = Hash.ruby2_keywords_hash?(actual_hash) ? " (keyword arguments)" : " (options hash)" + expected_description = Hash.ruby2_keywords_hash?(expected_hash) ? " (keyword arguments)" : " (options hash)" + + if actual_description != expected_description + actual_args += actual_description + expected_args += expected_description + end + end + end + + message = default_error_message(expectation, expected_args, actual_args) + + if args_for_multiple_calls.one? + diff = diff_message(expectation.expected_args, args_for_multiple_calls.first) + if RSpec::Mocks.configuration.color? + message << "\nDiff:#{diff}" unless diff.gsub(/\e\[\d+m/, '').strip.empty? + else + message << "\nDiff:#{diff}" unless diff.strip.empty? + end + end + + message + end + + def diff_message(expected_args, actual_args) + formatted_expected_args = expected_args.map do |x| + RSpec::Support.rspec_description_for_object(x) + end + + formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args) + + differ.diff(actual_args, formatted_expected_args) + end + + def unpack_string_args(formatted_expected_args, actual_args) + if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) } + [formatted_expected_args.first, actual_args.first] + else + [formatted_expected_args, actual_args] + end + end + + def list_of_exactly_one_string?(args) + Array === args && args.count == 1 && String === args.first + end + + def differ + RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?) + end + + def __raise(message, backtrace_line=nil, source_id=nil) + message = opts[:message] unless opts[:message].nil? + exception = RSpec::Mocks::MockExpectationError.new(message) + prepend_to_backtrace(exception, backtrace_line) if backtrace_line + notify exception, :source_id => source_id + end + + if RSpec::Support::Ruby.jruby? + def prepend_to_backtrace(exception, line) + raise exception + rescue RSpec::Mocks::MockExpectationError => with_backtrace + with_backtrace.backtrace.unshift(line) + end + else + def prepend_to_backtrace(exception, line) + exception.set_backtrace(caller.unshift line) + end + end + + def notify(*args) + RSpec::Support.notify_failure(*args) + end + + def format_args(args) + return "(no args)" if args.empty? + "(#{arg_list(args)})" + end + + def arg_list(args) + args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ") + end + + def format_received_args(args_for_multiple_calls) + grouped_args(args_for_multiple_calls).map do |args_for_one_call, index| + "#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}" + end.join("\n ") + end + + def count_message(count, expectation_count_type=nil) + return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least + return "at most #{times(count)}" if expectation_count_type == :at_most + times(count) + end + + def times(count) + "#{count} time#{count == 1 ? '' : 's'}" + end + + def grouped_args(args) + Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }] + end + + def group_count(index, args) + " (#{times(index)})" if args.size > 1 || index > 1 + end + end + + # @private + def self.error_generator + @error_generator ||= ErrorGenerator.new + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/example_methods.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/example_methods.rb new file mode 100644 index 0000000000..5531b28bf0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/example_methods.rb @@ -0,0 +1,434 @@ +RSpec::Support.require_rspec_mocks 'object_reference' + +module RSpec + module Mocks + # Contains methods intended to be used from within code examples. + # Mix this in to your test context (such as a test framework base class) + # to use rspec-mocks with your test framework. If you're using rspec-core, + # it'll take care of doing this for you. + module ExampleMethods + include RSpec::Mocks::ArgumentMatchers + + # @overload double() + # @overload double(name) + # @param name [String/Symbol] name or description to be used in failure messages + # @overload double(stubs) + # @param stubs (Hash) hash of message/return-value pairs + # @overload double(name, stubs) + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs (Hash) hash of message/return-value pairs + # @return (Double) + # + # Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured + # with an optional name, used for reporting in failure messages, and an optional + # hash of message/return-value pairs. + # + # @example + # book = double("book", :title => "The RSpec Book") + # book.title #=> "The RSpec Book" + # + # card = double("card", :suit => "Spades", :rank => "A") + # card.suit #=> "Spades" + # card.rank #=> "A" + # + def double(*args) + ExampleMethods.declare_double(Double, *args) + end + + # @overload instance_double(doubled_class) + # @param doubled_class [String, Class] + # @overload instance_double(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload instance_double(doubled_class, stubs) + # @param doubled_class [String, Class] + # @param stubs [Hash] hash of message/return-value pairs + # @overload instance_double(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return InstanceVerifyingDouble + # + # Constructs a test double against a specific class. If the given class + # name has been loaded, only instance methods defined on the class are + # allowed to be stubbed. In all other ways it behaves like a + # [double](double). + def instance_double(doubled_class, *args) + ref = ObjectReference.for(doubled_class) + ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args) + end + + # @overload class_double(doubled_class) + # @param doubled_class [String, Module] + # @overload class_double(doubled_class, name) + # @param doubled_class [String, Module] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload class_double(doubled_class, stubs) + # @param doubled_class [String, Module] + # @param stubs [Hash] hash of message/return-value pairs + # @overload class_double(doubled_class, name, stubs) + # @param doubled_class [String, Module] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ClassVerifyingDouble + # + # Constructs a test double against a specific class. If the given class + # name has been loaded, only class methods defined on the class are + # allowed to be stubbed. In all other ways it behaves like a + # [double](double). + def class_double(doubled_class, *args) + ref = ObjectReference.for(doubled_class) + ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args) + end + + # @overload object_double(object_or_name) + # @param object_or_name [String, Object] + # @overload object_double(object_or_name, name) + # @param object_or_name [String, Object] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload object_double(object_or_name, stubs) + # @param object_or_name [String, Object] + # @param stubs [Hash] hash of message/return-value pairs + # @overload object_double(object_or_name, name, stubs) + # @param object_or_name [String, Object] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ObjectVerifyingDouble + # + # Constructs a test double against a specific object. Only the methods + # the object responds to are allowed to be stubbed. If a String argument + # is provided, it is assumed to reference a constant object which is used + # for verification. In all other ways it behaves like a [double](double). + def object_double(object_or_name, *args) + ref = ObjectReference.for(object_or_name, :allow_direct_object_refs) + ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args) + end + + # @overload spy() + # @overload spy(name) + # @param name [String/Symbol] name or description to be used in failure messages + # @overload spy(stubs) + # @param stubs (Hash) hash of message/return-value pairs + # @overload spy(name, stubs) + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs (Hash) hash of message/return-value pairs + # @return (Double) + # + # Constructs a test double that is optimized for use with + # `have_received`. With a normal double one has to stub methods in order + # to be able to spy them. A spy automatically spies on all methods. + def spy(*args) + double(*args).as_null_object + end + + # @overload instance_spy(doubled_class) + # @param doubled_class [String, Class] + # @overload instance_spy(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload instance_spy(doubled_class, stubs) + # @param doubled_class [String, Class] + # @param stubs [Hash] hash of message/return-value pairs + # @overload instance_spy(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return InstanceVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific class. If the given class name has been loaded, only + # instance methods defined on the class are allowed to be stubbed. With + # a normal double one has to stub methods in order to be able to spy + # them. An instance_spy automatically spies on all instance methods to + # which the class responds. + def instance_spy(*args) + instance_double(*args).as_null_object + end + + # @overload object_spy(object_or_name) + # @param object_or_name [String, Object] + # @overload object_spy(object_or_name, name) + # @param object_or_name [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload object_spy(object_or_name, stubs) + # @param object_or_name [String, Object] + # @param stubs [Hash] hash of message/return-value pairs + # @overload object_spy(object_or_name, name, stubs) + # @param object_or_name [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ObjectVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific object. Only instance methods defined on the object + # are allowed to be stubbed. With a normal double one has to stub + # methods in order to be able to spy them. An object_spy automatically + # spies on all methods to which the object responds. + def object_spy(*args) + object_double(*args).as_null_object + end + + # @overload class_spy(doubled_class) + # @param doubled_class [String, Module] + # @overload class_spy(doubled_class, name) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @overload class_spy(doubled_class, stubs) + # @param doubled_class [String, Module] + # @param stubs [Hash] hash of message/return-value pairs + # @overload class_spy(doubled_class, name, stubs) + # @param doubled_class [String, Class] + # @param name [String/Symbol] name or description to be used in failure messages + # @param stubs [Hash] hash of message/return-value pairs + # @return ClassVerifyingDouble + # + # Constructs a test double that is optimized for use with `have_received` + # against a specific class. If the given class name has been loaded, + # only class methods defined on the class are allowed to be stubbed. + # With a normal double one has to stub methods in order to be able to spy + # them. An class_spy automatically spies on all class methods to which the + # class responds. + def class_spy(*args) + class_double(*args).as_null_object + end + + # Disables warning messages about expectations being set on nil. + # + # By default warning messages are issued when expectations are set on + # nil. This is to prevent false-positives and to catch potential bugs + # early on. + # @deprecated Use {RSpec::Mocks::Configuration#allow_message_expectations_on_nil} instead. + def allow_message_expectations_on_nil + RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false + end + + # Stubs the named constant with the given value. + # Like method stubs, the constant will be restored + # to its original value (or lack of one, if it was + # undefined) when the example completes. + # + # @param constant_name [String] The fully qualified name of the constant. The current + # constant scoping at the point of call is not considered. + # @param value [Object] The value to make the constant refer to. When the + # example completes, the constant will be restored to its prior state. + # @param options [Hash] Stubbing options. + # @option options :transfer_nested_constants [Boolean, Array] Determines + # what nested constants, if any, will be transferred from the original value + # of the constant to the new value of the constant. This only works if both + # the original and new values are modules (or classes). + # @return [Object] the stubbed value of the constant + # + # @example + # stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object. + # stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5. + # + # class CardDeck + # SUITS = [:Spades, :Diamonds, :Clubs, :Hearts] + # NUM_CARDS = 52 + # end + # + # stub_const("CardDeck", Class.new) + # CardDeck::SUITS # => uninitialized constant error + # CardDeck::NUM_CARDS # => uninitialized constant error + # + # stub_const("CardDeck", Class.new, :transfer_nested_constants => true) + # CardDeck::SUITS # => our suits array + # CardDeck::NUM_CARDS # => 52 + # + # stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS]) + # CardDeck::SUITS # => our suits array + # CardDeck::NUM_CARDS # => uninitialized constant error + def stub_const(constant_name, value, options={}) + ConstantMutator.stub(constant_name, value, options) + end + + # Hides the named constant with the given value. The constant will be + # undefined for the duration of the test. + # + # Like method stubs, the constant will be restored to its original value + # when the example completes. + # + # @param constant_name [String] The fully qualified name of the constant. + # The current constant scoping at the point of call is not considered. + # + # @example + # hide_const("MyClass") # => MyClass is now an undefined constant + def hide_const(constant_name) + ConstantMutator.hide(constant_name) + end + + # Verifies that the given object received the expected message during the + # course of the test. On a spy objects or as null object doubles this + # works for any method, on other objects the method must have + # been stubbed beforehand in order for messages to be verified. + # + # Stubbing and verifying messages received in this way implements the + # Test Spy pattern. + # + # @param method_name [Symbol] name of the method expected to have been + # called. + # + # @example + # invitation = double('invitation', accept: true) + # user.accept_invitation(invitation) + # expect(invitation).to have_received(:accept) + # + # # You can also use most message expectations: + # expect(invitation).to have_received(:accept).with(mailer).once + # + # @note `have_received(...).with(...)` is unable to work properly when + # passed arguments are mutated after the spy records the received message. + def have_received(method_name, &block) + Matchers::HaveReceived.new(method_name, &block) + end + + # Turns off the verifying of partial doubles for the duration of the + # block, this is useful in situations where methods are defined at run + # time and you wish to define stubs for them but not turn off partial + # doubles for the entire run suite. (e.g. view specs in rspec-rails). + def without_partial_double_verification + original_state = Mocks.configuration.temporarily_suppress_partial_double_verification + Mocks.configuration.temporarily_suppress_partial_double_verification = true + yield + ensure + Mocks.configuration.temporarily_suppress_partial_double_verification = original_state + end + + # @method expect + # Used to wrap an object in preparation for setting a mock expectation + # on it. + # + # @example + # expect(obj).to receive(:foo).with(5).and_return(:return_value) + # + # @note This method is usually provided by rspec-expectations. However, + # if you use rspec-mocks without rspec-expectations, there's a definition + # of it that is made available here. If you disable the `:expect` syntax + # this method will be undefined. + + # @method allow + # Used to wrap an object in preparation for stubbing a method + # on it. + # + # @example + # allow(dbl).to receive(:foo).with(5).and_return(:return_value) + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method expect_any_instance_of + # Used to wrap a class in preparation for setting a mock expectation + # on instances of it. + # + # @example + # expect_any_instance_of(MyClass).to receive(:foo) + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method allow_any_instance_of + # Used to wrap a class in preparation for stubbing a method + # on instances of it. + # + # @example + # allow_any_instance_of(MyClass).to receive(:foo) + # + # @note This is only available when you have enabled the `expect` syntax. + + # @method receive + # Used to specify a message that you expect or allow an object + # to receive. The object returned by `receive` supports the same + # fluent interface that `should_receive` and `stub` have always + # supported, allowing you to constrain the arguments or number of + # times, and configure how the object should respond to the message. + # + # @example + # expect(obj).to receive(:hello).with("world").exactly(3).times + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method receive_messages + # Shorthand syntax used to setup message(s), and their return value(s), + # that you expect or allow an object to receive. The method takes a hash + # of messages and their respective return values. Unlike with `receive`, + # you cannot apply further customizations using a block or the fluent + # interface. + # + # @example + # allow(obj).to receive_messages(:speak => "Hello World") + # allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow") + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @method receive_message_chain + # @overload receive_message_chain(method1, method2) + # @overload receive_message_chain("method1.method2") + # @overload receive_message_chain(method1, method_to_value_hash) + # + # stubs/mocks a chain of messages on an object or test double. + # + # ## Warning: + # + # Chains can be arbitrarily long, which makes it quite painless to + # violate the Law of Demeter in violent ways, so you should consider any + # use of `receive_message_chain` a code smell. Even though not all code smells + # indicate real problems (think fluent interfaces), `receive_message_chain` still + # results in brittle examples. For example, if you write + # `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the + # implementation calls `foo.baz.bar`, the stub will not work. + # + # @example + # allow(double).to receive_message_chain("foo.bar") { :baz } + # allow(double).to receive_message_chain(:foo, :bar => :baz) + # allow(double).to receive_message_chain(:foo, :bar) { :baz } + # + # # Given any of ^^ these three forms ^^: + # double.foo.bar # => :baz + # + # # Common use in Rails/ActiveRecord: + # allow(Article).to receive_message_chain("recent.published") { [Article.new] } + # + # @note If you disable the `:expect` syntax this method will be undefined. + + # @private + def self.included(klass) + klass.class_exec do + # This gets mixed in so that if `RSpec::Matchers` is included in + # `klass` later, its definition of `expect` will take precedence. + include ExpectHost unless method_defined?(:expect) + end + end + + # @private + def self.extended(object) + # This gets extended in so that if `RSpec::Matchers` is included in + # `klass` later, its definition of `expect` will take precedence. + object.extend ExpectHost unless object.respond_to?(:expect) + end + + # @private + def self.declare_verifying_double(type, ref, *args) + if RSpec::Mocks.configuration.verify_doubled_constant_names? && + !ref.defined? + + RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref) + end + + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call(ref) + end + + declare_double(type, ref, *args) + end + + # @private + def self.declare_double(type, *args) + args << {} unless Hash === args.last + type.new(*args) + end + + # This module exists to host the `expect` method for cases where + # rspec-mocks is used w/o rspec-expectations. + module ExpectHost + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/instance_method_stasher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/instance_method_stasher.rb new file mode 100644 index 0000000000..12edec2fae --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/instance_method_stasher.rb @@ -0,0 +1,146 @@ +module RSpec + module Mocks + # @private + class InstanceMethodStasher + def initialize(object, method) + @object = object + @method = method + @klass = (class << object; self; end) + + @original_method = nil + @method_is_stashed = false + end + + attr_reader :original_method + + if RUBY_VERSION.to_f < 1.9 + # @private + def method_is_stashed? + @method_is_stashed + end + + # @private + def stash + return if !method_defined_directly_on_klass? || @method_is_stashed + + @klass.__send__(:alias_method, stashed_method_name, @method) + @method_is_stashed = true + end + + # @private + def stashed_method_name + "obfuscated_by_rspec_mocks__#{@method}" + end + + # @private + def restore + return unless @method_is_stashed + + if @klass.__send__(:method_defined?, @method) + @klass.__send__(:undef_method, @method) + end + @klass.__send__(:alias_method, @method, stashed_method_name) + @klass.__send__(:remove_method, stashed_method_name) + @method_is_stashed = false + end + else + + # @private + def method_is_stashed? + !!@original_method + end + + # @private + def stash + return unless method_defined_directly_on_klass? + @original_method ||= ::RSpec::Support.method_handle_for(@object, @method) + @klass.__send__(:undef_method, @method) + end + + # @private + def restore + return unless @original_method + + if @klass.__send__(:method_defined?, @method) + @klass.__send__(:undef_method, @method) + end + + handle_restoration_failures do + @klass.__send__(:define_method, @method, @original_method) + end + + @original_method = nil + end + end + + if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195') + # ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(. + # https://bugs.ruby-lang.org/issues/8686 + def handle_restoration_failures + yield + rescue TypeError + RSpec.warn_with( + "RSpec failed to properly restore a partial double (#{@object.inspect}) " \ + "to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \ + "(https://bugs.ruby-lang.org/issues/8686). This object may remain " \ + "screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.", + :call_site => nil, :use_spec_location_as_call_site => true + ) + end + else + def handle_restoration_failures + # No known reasons for restoration to fail on other rubies. + yield + end + end + + private + + # @private + def method_defined_directly_on_klass? + method_defined_on_klass? && method_owned_by_klass? + end + + # @private + def method_defined_on_klass?(klass=@klass) + MethodReference.method_defined_at_any_visibility?(klass, @method) + end + + def method_owned_by_klass? + owner = @klass.instance_method(@method).owner + + # On Ruby 2.0.0+ the owner of a method on a class which has been + # `prepend`ed may actually be an instance, e.g. + # `#`, rather than the expected `MyClass`. + owner = owner.class unless Module === owner + + # On some 1.9s (e.g. rubinius) aliased methods + # can report the wrong owner. Example: + # class MyClass + # class << self + # alias alternate_new new + # end + # end + # + # MyClass.owner(:alternate_new) returns `Class` when incorrect, + # but we need to consider the owner to be `MyClass` because + # it is not actually available on `Class` but is on `MyClass`. + # Hence, we verify that the owner actually has the method defined. + # If the given owner does not have the method defined, we assume + # that the method is actually owned by @klass. + # + # On 1.8, aliased methods can also report the wrong owner. Example: + # module M + # def a; end + # module_function :a + # alias b a + # module_function :b + # end + # The owner of M.b is the raw Module object, instead of the expected + # singleton class of the module + return true if RUBY_VERSION < '1.9' && owner == @object + owner == @klass || !(method_defined_on_klass?(owner)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/marshal_extension.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/marshal_extension.rb new file mode 100644 index 0000000000..cfa9c1a7b6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/marshal_extension.rb @@ -0,0 +1,41 @@ +module RSpec + module Mocks + # Support for `patch_marshal_to_support_partial_doubles` configuration. + # + # @private + class MarshalExtension + def self.patch! + return if Marshal.respond_to?(:dump_with_rspec_mocks) + + Marshal.instance_eval do + class << self + def dump_with_rspec_mocks(object, *rest) + if !::RSpec::Mocks.space.registered?(object) || NilClass === object + dump_without_rspec_mocks(object, *rest) + else + dump_without_rspec_mocks(object.dup, *rest) + end + end + + alias_method :dump_without_rspec_mocks, :dump + undef_method :dump + alias_method :dump, :dump_with_rspec_mocks + end + end + end + + def self.unpatch! + return unless Marshal.respond_to?(:dump_with_rspec_mocks) + + Marshal.instance_eval do + class << self + undef_method :dump_with_rspec_mocks + undef_method :dump + alias_method :dump, :dump_without_rspec_mocks + undef_method :dump_without_rspec_mocks + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/expectation_customization.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/expectation_customization.rb new file mode 100644 index 0000000000..81e6427977 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/expectation_customization.rb @@ -0,0 +1,20 @@ +module RSpec + module Mocks + module Matchers + # @private + class ExpectationCustomization + attr_accessor :block + + def initialize(method_name, args, block) + @method_name = method_name + @args = args + @block = block + end + + def playback_onto(expectation) + expectation.__send__(@method_name, *@args, &@block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/have_received.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/have_received.rb new file mode 100644 index 0000000000..cf4852b432 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/have_received.rb @@ -0,0 +1,134 @@ +module RSpec + module Mocks + module Matchers + # @private + class HaveReceived + include Matcher + + COUNT_CONSTRAINTS = %w[exactly at_least at_most times time once twice thrice] + ARGS_CONSTRAINTS = %w[with] + CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w[ordered] + + def initialize(method_name, &block) + @method_name = method_name + @block = block + @constraints = [] + @subject = nil + end + + def matcher_name + "have_received" + end + + def matches?(subject, &block) + @block ||= block + @subject = subject + @expectation = expect + mock_proxy.ensure_implemented(@method_name) + + expected_messages_received_in_order? + end + + def does_not_match?(subject) + @subject = subject + ensure_count_unconstrained + @expectation = expect.never + mock_proxy.ensure_implemented(@method_name) + expected_messages_received_in_order? + end + + def failure_message + capture_failure_message + end + + def failure_message_when_negated + capture_failure_message + end + + def description + (@expectation ||= expect).description_for("have received") + end + + CONSTRAINTS.each do |expectation| + define_method expectation do |*args| + @constraints << [expectation, *args] + self + end + end + + def setup_expectation(subject, &block) + notify_failure_message unless matches?(subject, &block) + end + + def setup_negative_expectation(subject, &block) + notify_failure_message unless does_not_match?(subject, &block) + end + + def setup_allowance(_subject, &_block) + disallow("allow", " as it would have no effect") + end + + def setup_any_instance_allowance(_subject, &_block) + disallow("allow_any_instance_of") + end + + def setup_any_instance_expectation(_subject, &_block) + disallow("expect_any_instance_of") + end + + def setup_any_instance_negative_expectation(_subject, &_block) + disallow("expect_any_instance_of") + end + + private + + def disallow(type, reason="") + RSpec::Mocks.error_generator.raise_have_received_disallowed(type, reason) + end + + def expect + expectation = mock_proxy.build_expectation(@method_name) + apply_constraints_to expectation + expectation + end + + def apply_constraints_to(expectation) + @constraints.each do |constraint| + expectation.send(*constraint) + end + end + + def ensure_count_unconstrained + return unless count_constraint + RSpec::Mocks.error_generator.raise_cant_constrain_count_for_negated_have_received_error(count_constraint) + end + + def count_constraint + @constraints.map(&:first).find do |constraint| + COUNT_CONSTRAINTS.include?(constraint) + end + end + + def capture_failure_message + RSpec::Support.with_failure_notifier(Proc.new { |err, _opt| return err.message }) do + notify_failure_message + end + end + + def notify_failure_message + mock_proxy.check_for_unexpected_arguments(@expectation) + @expectation.generate_error + end + + def expected_messages_received_in_order? + mock_proxy.replay_received_message_on @expectation, &@block + @expectation.expected_messages_received? && @expectation.ensure_expected_ordering_received! + end + + def mock_proxy + RSpec::Mocks.space.proxy_for(@subject) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive.rb new file mode 100644 index 0000000000..f45434b6f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive.rb @@ -0,0 +1,133 @@ +RSpec::Support.require_rspec_mocks 'matchers/expectation_customization' + +module RSpec + module Mocks + module Matchers + # @private + class Receive + include Matcher + + def initialize(message, block) + @message = message + @block = block + @recorded_customizations = [] + end + + def matcher_name + "receive" + end + + def description + describable.description_for("receive") + end + + def setup_expectation(subject, &block) + warn_if_any_instance("expect", subject) + @describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block) + end + alias matches? setup_expectation + + def setup_negative_expectation(subject, &block) + # ensure `never` goes first for cases like `never.and_return(5)`, + # where `and_return` is meant to raise an error + @recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil) + + warn_if_any_instance("expect", subject) + + setup_expectation(subject, &block) + end + alias does_not_match? setup_negative_expectation + + def setup_allowance(subject, &block) + warn_if_any_instance("allow", subject) + setup_mock_proxy_method_substitute(subject, :add_stub, block) + end + + def setup_any_instance_expectation(subject, &block) + setup_any_instance_method_substitute(subject, :should_receive, block) + end + + def setup_any_instance_negative_expectation(subject, &block) + setup_any_instance_method_substitute(subject, :should_not_receive, block) + end + + def setup_any_instance_allowance(subject, &block) + setup_any_instance_method_substitute(subject, :stub, block) + end + + MessageExpectation.public_instance_methods(false).each do |method| + next if method_defined?(method) + + define_method(method) do |*args, &block| + @recorded_customizations << ExpectationCustomization.new(method, args, block) + self + end + ruby2_keywords(method) if respond_to?(:ruby2_keywords, true) + end + + private + + def describable + @describable ||= DefaultDescribable.new(@message) + end + + def warn_if_any_instance(expression, subject) + return unless AnyInstance::Proxy === subject + + RSpec.warning( + "`#{expression}(#{subject.klass}.any_instance).to` " \ + "is probably not what you meant, it does not operate on " \ + "any instance of `#{subject.klass}`. " \ + "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead." + ) + end + + def setup_mock_proxy_method_substitute(subject, method, block) + proxy = ::RSpec::Mocks.space.proxy_for(subject) + setup_method_substitute(proxy, method, block) + end + + def setup_any_instance_method_substitute(subject, method, block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + setup_method_substitute(proxy, method, block) + end + + def setup_method_substitute(host, method, block, *args) + args << @message.to_sym + block = move_block_to_last_customization(block) + + expectation = host.__send__(method, *args, &(@block || block)) + + @recorded_customizations.each do |customization| + customization.playback_onto(expectation) + end + expectation + end + + def move_block_to_last_customization(block) + last = @recorded_customizations.last + return block unless last + + last.block ||= block + nil + end + + # MessageExpectation objects are able to describe themselves in detail. + # We use this as a fall back when a MessageExpectation is not available. + # @private + class DefaultDescribable + def initialize(message) + @message = message + end + + # This is much simpler for the `any_instance` case than what the + # user may want, but I'm not up for putting a bunch of effort + # into full descriptions for `any_instance` expectations at this point :(. + def description_for(verb) + "#{verb} #{@message}" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_message_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_message_chain.rb new file mode 100644 index 0000000000..fdc89f9f7f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_message_chain.rb @@ -0,0 +1,82 @@ +RSpec::Support.require_rspec_mocks 'matchers/expectation_customization' + +module RSpec + module Mocks + module Matchers + # @private + class ReceiveMessageChain + include Matcher + + def initialize(chain, &block) + @chain = chain + @block = block + @recorded_customizations = [] + end + + [:with, :and_return, :and_invoke, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg| + define_method(msg) do |*args, &block| + @recorded_customizations << ExpectationCustomization.new(msg, args, block) + self + end + end + + def matcher_name + "receive_message_chain" + end + + def description + "receive message chain #{formatted_chain}" + end + + def setup_allowance(subject, &block) + chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_any_instance_allowance(subject, &block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + chain = proxy.stub_chain(*@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_any_instance_expectation(subject, &block) + proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) + chain = proxy.expect_chain(*@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_expectation(subject, &block) + chain = ExpectChain.expect_chain_on(subject, *@chain, &(@block || block)) + replay_customizations(chain) + end + + def setup_negative_expectation(*_args) + raise NegationUnsupportedError, + "`expect(...).not_to receive_message_chain` is not supported " \ + "since it doesn't really make sense. What would it even mean?" + end + + alias matches? setup_expectation + alias does_not_match? setup_negative_expectation + + private + + def replay_customizations(chain) + @recorded_customizations.each do |customization| + customization.playback_onto(chain) + end + end + + def formatted_chain + @formatted_chain ||= @chain.map do |part| + if Hash === part + part.keys.first.to_s + else + part.to_s + end + end.join(".") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_messages.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_messages.rb new file mode 100644 index 0000000000..6bf9047816 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/matchers/receive_messages.rb @@ -0,0 +1,77 @@ +module RSpec + module Mocks + module Matchers + # @private + class ReceiveMessages + include Matcher + + def initialize(message_return_value_hash) + @message_return_value_hash = message_return_value_hash + @backtrace_line = CallerFilter.first_non_rspec_line + end + + def matcher_name + "receive_messages" + end + + def description + "receive messages: #{@message_return_value_hash.inspect}" + end + + def setup_expectation(subject) + warn_about_block if block_given? + each_message_on(proxy_on(subject)) do |host, message, return_value| + host.add_simple_expectation(message, return_value, @backtrace_line) + end + end + alias matches? setup_expectation + + def setup_negative_expectation(_subject) + raise NegationUnsupportedError, + "`expect(...).to_not receive_messages` is not supported since it " \ + "doesn't really make sense. What would it even mean?" + end + alias does_not_match? setup_negative_expectation + + def setup_allowance(subject) + warn_about_block if block_given? + each_message_on(proxy_on(subject)) do |host, message, return_value| + host.add_simple_stub(message, return_value) + end + end + + def setup_any_instance_expectation(subject) + warn_about_block if block_given? + each_message_on(any_instance_of(subject)) do |host, message, return_value| + host.should_receive(message).and_return(return_value) + end + end + + def setup_any_instance_allowance(subject) + warn_about_block if block_given? + any_instance_of(subject).stub(@message_return_value_hash) + end + + def warn_about_block + raise "Implementation blocks aren't supported with `receive_messages`" + end + + private + + def proxy_on(subject) + ::RSpec::Mocks.space.proxy_for(subject) + end + + def any_instance_of(subject) + ::RSpec::Mocks.space.any_instance_proxy_for(subject) + end + + def each_message_on(host) + @message_return_value_hash.each do |message, value| + yield host, message, value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_chain.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_chain.rb new file mode 100644 index 0000000000..907d14b0e8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_chain.rb @@ -0,0 +1,87 @@ +module RSpec + module Mocks + # @private + class MessageChain + attr_reader :object, :chain, :block + + def initialize(object, *chain, &blk) + @object = object + @chain, @block = format_chain(*chain, &blk) + end + + # @api private + def setup_chain + if chain.length > 1 + if (matching_stub = find_matching_stub) + chain.shift + chain_on(matching_stub.invoke(nil), *chain, &@block) + elsif (matching_expectation = find_matching_expectation) + chain.shift + chain_on(matching_expectation.invoke_without_incrementing_received_count(nil), *chain, &@block) + else + next_in_chain = Double.new + expectation(object, chain.shift) { next_in_chain } + chain_on(next_in_chain, *chain, &@block) + end + else + expectation(object, chain.shift, &@block) + end + end + + private + + def chain_on(object, *chain, &block) + initialize(object, *chain, &block) + setup_chain + end + + def format_chain(*chain, &blk) + if Hash === chain.last + hash = chain.pop + hash.each do |k, v| + chain << k + blk = Proc.new { v } + end + end + return chain.join('.').split('.'), blk + end + + def find_matching_stub + ::RSpec::Mocks.space.proxy_for(object). + __send__(:find_matching_method_stub, chain.first.to_sym) + end + + def find_matching_expectation + ::RSpec::Mocks.space.proxy_for(object). + __send__(:find_matching_expectation, chain.first.to_sym) + end + end + + # @private + class ExpectChain < MessageChain + # @api private + def self.expect_chain_on(object, *chain, &blk) + new(object, *chain, &blk).setup_chain + end + + private + + def expectation(object, message, &return_block) + ::RSpec::Mocks.expect_message(object, message, {}, &return_block) + end + end + + # @private + class StubChain < MessageChain + def self.stub_chain_on(object, *chain, &blk) + new(object, *chain, &blk).setup_chain + end + + private + + def expectation(object, message, &return_block) + ::RSpec::Mocks.allow_message(object, message, {}, &return_block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_expectation.rb new file mode 100644 index 0000000000..110e1cb076 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/message_expectation.rb @@ -0,0 +1,820 @@ +RSpec::Support.require_rspec_support 'mutex' + +module RSpec + module Mocks + # A message expectation that only allows concrete return values to be set + # for a message. While this same effect can be achieved using a standard + # MessageExpectation, this version is much faster and so can be used as an + # optimization. + # + # @private + class SimpleMessageExpectation + def initialize(message, response, error_generator, backtrace_line=nil) + @message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line + @received = false + end + + def invoke(*_) + @received = true + @response + end + + def matches?(message, *_) + @message == message.to_sym + end + + def called_max_times? + false + end + + def verify_messages_received + return if @received + @error_generator.raise_expectation_error( + @message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line + ) + end + + def unadvise(_) + end + end + + # Represents an individual method stub or message expectation. The methods + # defined here can be used to configure how it behaves. The methods return + # `self` so that they can be chained together to form a fluent interface. + class MessageExpectation + # @!group Configuring Responses + + # @overload and_return(value) + # @overload and_return(first_value, second_value) + # + # Tells the object to return a value when it receives the message. Given + # more than one value, the first value is returned the first time the + # message is received, the second value is returned the next time, etc, + # etc. + # + # If the message is received more times than there are values, the last + # value is returned for every subsequent call. + # + # @return [nil] No further chaining is supported after this. + # @example + # allow(counter).to receive(:count).and_return(1) + # counter.count # => 1 + # counter.count # => 1 + # + # allow(counter).to receive(:count).and_return(1,2,3) + # counter.count # => 1 + # counter.count # => 2 + # counter.count # => 3 + # counter.count # => 3 + # counter.count # => 3 + # # etc + def and_return(first_value, *values) + raise_already_invoked_error_if_necessary(__method__) + if negative? + raise "`and_return` is not supported with negative message expectations" + end + + if block_given? + raise ArgumentError, "Implementation blocks aren't supported with `and_return`" + end + + values.unshift(first_value) + @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least) + self.terminal_implementation_action = AndReturnImplementation.new(values) + + nil + end + + # Tells the object to invoke a Proc when it receives the message. Given + # more than one value, the result of the first Proc is returned the first + # time the message is received, the result of the second Proc is returned + # the next time, etc, etc. + # + # If the message is received more times than there are Procs, the result of + # the last Proc is returned for every subsequent call. + # + # @return [nil] No further chaining is supported after this. + # @example + # allow(api).to receive(:get_foo).and_invoke(-> { raise ApiTimeout }) + # api.get_foo # => raises ApiTimeout + # api.get_foo # => raises ApiTimeout + # + # allow(api).to receive(:get_foo).and_invoke(-> { raise ApiTimeout }, -> { raise ApiTimeout }, -> { :a_foo }) + # api.get_foo # => raises ApiTimeout + # api.get_foo # => rasies ApiTimeout + # api.get_foo # => :a_foo + # api.get_foo # => :a_foo + # api.get_foo # => :a_foo + # # etc + def and_invoke(first_proc, *procs) + raise_already_invoked_error_if_necessary(__method__) + if negative? + raise "`and_invoke` is not supported with negative message expectations" + end + + if block_given? + raise ArgumentError, "Implementation blocks aren't supported with `and_invoke`" + end + + procs.unshift(first_proc) + if procs.any? { |p| !p.respond_to?(:call) } + raise ArgumentError, "Arguments to `and_invoke` must be callable." + end + + @expected_received_count = [@expected_received_count, procs.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least) + self.terminal_implementation_action = AndInvokeImplementation.new(procs) + + nil + end + + # Tells the object to delegate to the original unmodified method + # when it receives the message. + # + # @note This is only available on partial doubles. + # + # @return [nil] No further chaining is supported after this. + # @example + # expect(counter).to receive(:increment).and_call_original + # original_count = counter.count + # counter.increment + # expect(counter.count).to eq(original_count + 1) + def and_call_original + block = lambda do |original, *args, &b| + original.call(*args, &b) + end + block = block.ruby2_keywords if block.respond_to?(:ruby2_keywords) + + wrap_original(__method__, &block) + end + + # Decorates the stubbed method with the supplied block. The original + # unmodified method is passed to the block along with any method call + # arguments so you can delegate to it, whilst still being able to + # change what args are passed to it and/or change the return value. + # + # @note This is only available on partial doubles. + # + # @return [nil] No further chaining is supported after this. + # @example + # expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block| + # original_method.call(*args, &block).first(10) + # end + def and_wrap_original(&block) + wrap_original(__method__, &block) + end + + # @overload and_raise + # @overload and_raise(ExceptionClass) + # @overload and_raise(ExceptionClass, message) + # @overload and_raise(exception_instance) + # + # Tells the object to raise an exception when the message is received. + # + # @return [nil] No further chaining is supported after this. + # @note + # When you pass an exception class, the MessageExpectation will raise + # an instance of it, creating it with `exception` and passing `message` + # if specified. If the exception class initializer requires more than + # one parameters, you must pass in an instance and not the class, + # otherwise this method will raise an ArgumentError exception. + # + # @example + # allow(car).to receive(:go).and_raise + # allow(car).to receive(:go).and_raise(OutOfGas) + # allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive") + # allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz)) + def and_raise(*args) + raise_already_invoked_error_if_necessary(__method__) + self.terminal_implementation_action = Proc.new { raise(*args) } + nil + end + + # @overload and_throw(symbol) + # @overload and_throw(symbol, object) + # + # Tells the object to throw a symbol (with the object if that form is + # used) when the message is received. + # + # @return [nil] No further chaining is supported after this. + # @example + # allow(car).to receive(:go).and_throw(:out_of_gas) + # allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1) + def and_throw(*args) + raise_already_invoked_error_if_necessary(__method__) + self.terminal_implementation_action = Proc.new { throw(*args) } + nil + end + + # Tells the object to yield one or more args to a block when the message + # is received. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # stream.stub(:open).and_yield(StringIO.new) + def and_yield(*args, &block) + raise_already_invoked_error_if_necessary(__method__) + yield @eval_context = Object.new if block + + # Initialize args to yield now that it's being used, see also: comment + # in constructor. + @args_to_yield ||= [] + + @args_to_yield << args + self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator) + self + end + # @!endgroup + + # @!group Constraining Receive Counts + + # Constrain a message expectation to be received a specific number of + # times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).exactly(10).times + def exactly(n, &block) + raise_already_invoked_error_if_necessary(__method__) + self.inner_implementation_action = block + set_expected_received_count :exactly, n + self + end + + # Constrain a message expectation to be received at least a specific + # number of times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).at_least(9).times + def at_least(n, &block) + raise_already_invoked_error_if_necessary(__method__) + set_expected_received_count :at_least, n + + if n == 0 + raise "at_least(0) has been removed, use allow(...).to receive(:message) instead" + end + + self.inner_implementation_action = block + + self + end + + # Constrain a message expectation to be received at most a specific + # number of times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).at_most(10).times + def at_most(n, &block) + raise_already_invoked_error_if_necessary(__method__) + self.inner_implementation_action = block + set_expected_received_count :at_most, n + self + end + + # Syntactic sugar for `exactly`, `at_least` and `at_most` + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(dealer).to receive(:deal_card).exactly(10).times + # expect(dealer).to receive(:deal_card).at_least(10).times + # expect(dealer).to receive(:deal_card).at_most(10).times + def times(&block) + self.inner_implementation_action = block + self + end + alias time times + + # Expect a message not to be received at all. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:stop).never + def never + error_generator.raise_double_negation_error("expect(obj)") if negative? + @expected_received_count = 0 + self + end + + # Expect a message to be received exactly one time. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).once + def once(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 1 + self + end + + # Expect a message to be received exactly two times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).twice + def twice(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 2 + self + end + + # Expect a message to be received exactly three times. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(car).to receive(:go).thrice + def thrice(&block) + self.inner_implementation_action = block + set_expected_received_count :exactly, 3 + self + end + # @!endgroup + + # @!group Other Constraints + + # Constrains a stub or message expectation to invocations with specific + # arguments. + # + # With a stub, if the message might be received with other args as well, + # you should stub a default value first, and then stub or mock the same + # message using `with` to constrain to specific arguments. + # + # A message expectation will fail if the message is received with different + # arguments. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # allow(cart).to receive(:add) { :failure } + # allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success } + # cart.add(Book.new(:isbn => 1234567890)) + # # => :failure + # cart.add(Book.new(:isbn => 1934356379)) + # # => :success + # + # expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success } + # cart.add(Book.new(:isbn => 1234567890)) + # # => failed expectation + # cart.add(Book.new(:isbn => 1934356379)) + # # => passes + def with(*args, &block) + raise_already_invoked_error_if_necessary(__method__) + if args.empty? + raise ArgumentError, + "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments." + end + + self.inner_implementation_action = block + @argument_list_matcher = ArgumentListMatcher.new(*args) + self + end + ruby2_keywords(:with) if respond_to?(:ruby2_keywords, true) + + # Expect messages to be received in a specific order. + # + # @return [MessageExpectation] self, to support further chaining. + # @example + # expect(api).to receive(:prepare).ordered + # expect(api).to receive(:run).ordered + # expect(api).to receive(:finish).ordered + def ordered(&block) + if type == :stub + RSpec.warning( + "`allow(...).to receive(..).ordered` is not supported and will " \ + "have no effect, use `and_return(*ordered_values)` instead." + ) + end + + self.inner_implementation_action = block + additional_expected_calls.times do + @order_group.register(self) + end + @ordered = true + self + end + + # @return [String] a nice representation of the message expectation + def to_s + args_description = error_generator.method_call_args_description(@argument_list_matcher.expected_args, "", "") { true } + args_description = "(#{args_description})" unless args_description.start_with?("(") + "#<#{self.class} #{error_generator.intro}.#{message}#{args_description}>" + end + alias inspect to_s + + # @private + # Contains the parts of `MessageExpectation` that aren't part of + # rspec-mocks' public API. The class is very big and could really use + # some collaborators it delegates to for this stuff but for now this was + # the simplest way to split the public from private stuff to make it + # easier to publish the docs for the APIs we want published. + module ImplementationDetails + attr_accessor :error_generator, :implementation + attr_reader :message + attr_reader :orig_object + attr_writer :expected_received_count, :expected_from, :argument_list_matcher + protected :expected_received_count=, :expected_from=, :error_generator=, :implementation= + + # @private + attr_reader :type + + # rubocop:disable Metrics/ParameterLists + def initialize(error_generator, expectation_ordering, expected_from, method_double, + type=:expectation, opts={}, &implementation_block) + @type = type + @error_generator = error_generator + @error_generator.opts = error_generator.opts.merge(opts) + @expected_from = expected_from + @method_double = method_double + @orig_object = @method_double.object + @message = @method_double.method_name + @actual_received_count = 0 + @actual_received_count_write_mutex = Support::Mutex.new + @expected_received_count = type == :expectation ? 1 : :any + @argument_list_matcher = ArgumentListMatcher::MATCH_ALL + @order_group = expectation_ordering + @order_group.register(self) unless type == :stub + @expectation_type = type + @ordered = false + @at_least = @at_most = @exactly = nil + + # Initialized to nil so that we don't allocate an array for every + # mock or stub. See also comment in `and_yield`. + @args_to_yield = nil + @eval_context = nil + @yield_receiver_to_implementation_block = false + + @implementation = Implementation.new + self.inner_implementation_action = implementation_block + end + # rubocop:enable Metrics/ParameterLists + + def expected_args + @argument_list_matcher.expected_args + end + + def and_yield_receiver_to_implementation + @yield_receiver_to_implementation_block = true + self + end + + def yield_receiver_to_implementation_block? + @yield_receiver_to_implementation_block + end + + def matches?(message, *args) + @message == message && @argument_list_matcher.args_match?(*args) + end + ruby2_keywords :matches? if respond_to?(:ruby2_keywords, true) + + def safe_invoke(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block) + end + ruby2_keywords :safe_invoke if respond_to?(:ruby2_keywords, true) + + def invoke(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block) + end + ruby2_keywords :invoke if respond_to?(:ruby2_keywords, true) + + def invoke_without_incrementing_received_count(parent_stub, *args, &block) + invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block) + end + ruby2_keywords :invoke_without_incrementing_received_count if respond_to?(:ruby2_keywords, true) + + def negative? + @expected_received_count == 0 && !@at_least + end + + def called_max_times? + @expected_received_count != :any && + !@at_least && + @expected_received_count > 0 && + @actual_received_count >= @expected_received_count + end + + def matches_name_but_not_args(message, *args) + @message == message && !@argument_list_matcher.args_match?(*args) + end + + def verify_messages_received + return if expected_messages_received? + generate_error + end + + def expected_messages_received? + ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count? + end + + def ensure_expected_ordering_received! + @order_group.verify_invocation_order(self) if @ordered + true + end + + def ignoring_args? + @expected_received_count == :any + end + + def matches_at_least_count? + @at_least && @actual_received_count >= @expected_received_count + end + + def matches_at_most_count? + @at_most && @actual_received_count <= @expected_received_count + end + + def matches_exact_count? + @expected_received_count == @actual_received_count + end + + def similar_messages + @similar_messages ||= [] + end + + def advise(*args) + similar_messages << args + end + + def unadvise(args) + similar_messages.delete_if { |message| args.include?(message) } + end + + def generate_error + if similar_messages.empty? + @error_generator.raise_expectation_error( + @message, @expected_received_count, @argument_list_matcher, + @actual_received_count, expectation_count_type, expected_args, + @expected_from, exception_source_id + ) + else + @error_generator.raise_similar_message_args_error( + self, @similar_messages, @expected_from + ) + end + end + + def raise_unexpected_message_args_error(args_for_multiple_calls) + @error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id) + end + + def expectation_count_type + return :at_least if @at_least + return :at_most if @at_most + nil + end + + def description_for(verb) + @error_generator.describe_expectation( + verb, @message, @expected_received_count, + @actual_received_count, expected_args + ) + end + + def raise_out_of_order_error + @error_generator.raise_out_of_order_error @message + end + + def additional_expected_calls + return 0 if @expectation_type == :stub || !@exactly + @expected_received_count - 1 + end + + def ordered? + @ordered + end + + def negative_expectation_for?(message) + @message == message && negative? + end + + def actual_received_count_matters? + @at_least || @at_most || @exactly + end + + def increase_actual_received_count! + @actual_received_count_write_mutex.synchronize do + @actual_received_count += 1 + end + end + + private + + def exception_source_id + @exception_source_id ||= "#{self.class.name} #{__id__}" + end + + def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block) + args.unshift(orig_object) if yield_receiver_to_implementation_block? + + if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count)) + # args are the args we actually received, @argument_list_matcher is the + # list of args we were expecting + @error_generator.raise_expectation_error( + @message, @expected_received_count, + @argument_list_matcher, + @actual_received_count + increment, + expectation_count_type, args, nil, exception_source_id + ) + end + + @order_group.handle_order_constraint self + + if implementation.present? + implementation.call(*args, &block) + elsif parent_stub + parent_stub.invoke(nil, *args, &block) + end + ensure + @actual_received_count_write_mutex.synchronize do + @actual_received_count += increment + end + end + ruby2_keywords :invoke_incrementing_actual_calls_by if respond_to?(:ruby2_keywords, true) + + def has_been_invoked? + @actual_received_count > 0 + end + + def raise_already_invoked_error_if_necessary(calling_customization) + return unless has_been_invoked? + + error_generator.raise_already_invoked_error(message, calling_customization) + end + + def set_expected_received_count(relativity, n) + raise "`count` is not supported with negative message expectations" if negative? + @at_least = (relativity == :at_least) + @at_most = (relativity == :at_most) + @exactly = (relativity == :exactly) + @expected_received_count = case n + when Numeric then n + when :once then 1 + when :twice then 2 + when :thrice then 3 + end + end + + def initial_implementation_action=(action) + implementation.initial_action = action + end + + def inner_implementation_action=(action) + return unless action + warn_about_stub_override if implementation.inner_action + implementation.inner_action = action + end + + def terminal_implementation_action=(action) + implementation.terminal_action = action + end + + def warn_about_stub_override + RSpec.warning( + "You're overriding a previous stub implementation of `#{@message}`. " \ + "Called from #{CallerFilter.first_non_rspec_line}." + ) + end + + def wrap_original(method_name, &block) + if RSpec::Mocks::TestDouble === @method_double.object + @error_generator.raise_only_valid_on_a_partial_double(method_name) + else + warn_about_stub_override if implementation.inner_action + @implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block) + @yield_receiver_to_implementation_block = false + end + + nil + end + end + + include ImplementationDetails + end + + # Handles the implementation of an `and_yield` declaration. + # @private + class AndYieldImplementation + def initialize(args_to_yield, eval_context, error_generator) + @args_to_yield = args_to_yield + @eval_context = eval_context + @error_generator = error_generator + end + + def call(*_args_to_ignore, &block) + return if @args_to_yield.empty? && @eval_context.nil? + + @error_generator.raise_missing_block_error @args_to_yield unless block + value = nil + block_signature = Support::BlockSignature.new(block) + + @args_to_yield.each do |args| + unless Support::StrictSignatureVerifier.new(block_signature, args).valid? + @error_generator.raise_wrong_arity_error(args, block_signature) + end + + value = @eval_context ? @eval_context.instance_exec(*args, &block) : yield(*args) + end + value + end + end + + # Handles the implementation of an `and_return` implementation. + # @private + class AndReturnImplementation + def initialize(values_to_return) + @values_to_return = values_to_return + end + + def call(*_args_to_ignore, &_block) + if @values_to_return.size > 1 + @values_to_return.shift + else + @values_to_return.first + end + end + end + + # Handles the implementation of an `and_invoke` implementation. + # @private + class AndInvokeImplementation + def initialize(procs_to_invoke) + @procs_to_invoke = procs_to_invoke + end + + def call(*args, &block) + proc = if @procs_to_invoke.size > 1 + @procs_to_invoke.shift + else + @procs_to_invoke.first + end + + proc.call(*args, &block) + end + end + + # Represents a configured implementation. Takes into account + # any number of sub-implementations. + # @private + class Implementation + attr_accessor :initial_action, :inner_action, :terminal_action + + def call(*args, &block) + actions.map do |action| + action.call(*args, &block) + end.last + end + ruby2_keywords :call if respond_to?(:ruby2_keywords, true) + + def present? + actions.any? + end + + private + + def actions + [initial_action, inner_action, terminal_action].compact + end + end + + # Represents an `and_call_original` implementation. + # @private + class AndWrapOriginalImplementation + def initialize(method, block) + @method = method + @block = block + end + + CannotModifyFurtherError = Class.new(StandardError) + + def initial_action=(_value) + raise cannot_modify_further_error + end + + def inner_action=(_value) + raise cannot_modify_further_error + end + + def terminal_action=(_value) + raise cannot_modify_further_error + end + + def present? + true + end + + def inner_action + true + end + + def call(*args, &block) + @block.call(@method, *args, &block) + end + ruby2_keywords :call if respond_to?(:ruby2_keywords, true) + + private + + def cannot_modify_further_error + CannotModifyFurtherError.new "This method has already been configured " \ + "to call the original implementation, and cannot be modified further." + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_double.rb new file mode 100644 index 0000000000..39494f290e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_double.rb @@ -0,0 +1,309 @@ +module RSpec + module Mocks + # @private + class MethodDouble + # @private TODO: drop in favor of FrozenError in ruby 2.5+ + FROZEN_ERROR_MSG = /can't modify frozen/ + + # @private + attr_reader :method_name, :object, :expectations, :stubs, :method_stasher + + # @private + def initialize(object, method_name, proxy) + @method_name = method_name + @object = object + @proxy = proxy + + @original_visibility = nil + @method_stasher = InstanceMethodStasher.new(object, method_name) + @method_is_proxied = false + @expectations = [] + @stubs = [] + end + + def original_implementation_callable + # If original method is not present, uses the `method_missing` + # handler of the object. This accounts for cases where the user has not + # correctly defined `respond_to?`, and also 1.8 which does not provide + # method handles for missing methods even if `respond_to?` is correct. + @original_implementation_callable ||= original_method || + Proc.new do |*args, &block| + @object.__send__(:method_missing, @method_name, *args, &block) + end + end + + alias_method :save_original_implementation_callable!, :original_implementation_callable + + def original_method + @original_method ||= + @method_stasher.original_method || + @proxy.original_method_handle_for(method_name) + end + + # @private + def visibility + @proxy.visibility_for(@method_name) + end + + # @private + def object_singleton_class + class << @object; self; end + end + + # @private + def configure_method + @original_visibility = visibility + @method_stasher.stash unless @method_is_proxied + define_proxy_method + end + + # @private + def define_proxy_method + return if @method_is_proxied + + save_original_implementation_callable! + definition_target.class_exec(self, method_name, @original_visibility || visibility) do |method_double, method_name, visibility| + define_method(method_name) do |*args, &block| + method_double.proxy_method_invoked(self, *args, &block) + end + # This can't be `if respond_to?(:ruby2_keywords, true)`, + # see https://github.com/rspec/rspec-mocks/pull/1385#issuecomment-755340298 + ruby2_keywords(method_name) if Module.private_method_defined?(:ruby2_keywords) + __send__(visibility, method_name) + end + + @method_is_proxied = true + rescue RuntimeError, TypeError => e + # TODO: drop in favor of FrozenError in ruby 2.5+ + # RuntimeError (and FrozenError) for ruby 2.x + # TypeError for ruby 1.x + if (defined?(FrozenError) && e.is_a?(FrozenError)) || FROZEN_ERROR_MSG === e.message + raise ArgumentError, "Cannot proxy frozen objects, rspec-mocks relies on proxies for method stubbing and expectations." + end + raise + end + + # The implementation of the proxied method. Subclasses may override this + # method to perform additional operations. + # + # @private + def proxy_method_invoked(_obj, *args, &block) + @proxy.message_received method_name, *args, &block + end + ruby2_keywords :proxy_method_invoked if respond_to?(:ruby2_keywords, true) + + # @private + def restore_original_method + return unless @method_is_proxied + + remove_method_from_definition_target + @method_stasher.restore if @method_stasher.method_is_stashed? + restore_original_visibility + + @method_is_proxied = false + rescue RuntimeError, TypeError => e + # TODO: drop in favor of FrozenError in ruby 2.5+ + # RuntimeError (and FrozenError) for ruby 2.x + # TypeError for ruby 1.x + if (defined?(FrozenError) && e.is_a?(FrozenError)) || FROZEN_ERROR_MSG === e.message + return show_frozen_warning + end + raise + end + + # @private + def show_frozen_warning + RSpec.warn_with( + "WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \ + "method on #{@object.inspect} because it has been frozen. If you reuse this " \ + "object, `#{@method_name}` will continue to respond with its stub implementation.", + :call_site => nil, + :use_spec_location_as_call_site => true + ) + end + + # @private + def restore_original_visibility + return unless @original_visibility && + MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name) + + object_singleton_class.__send__(@original_visibility, method_name) + end + + # @private + def verify + expectations.each { |e| e.verify_messages_received } + end + + # @private + def reset + restore_original_method + clear + end + + # @private + def clear + expectations.clear + stubs.clear + end + + # The type of message expectation to create has been extracted to its own + # method so that subclasses can override it. + # + # @private + def message_expectation_class + MessageExpectation + end + + # @private + def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation) + configure_method + expectation = message_expectation_class.new(error_generator, expectation_ordering, + expected_from, self, :expectation, opts, &implementation) + expectations << expectation + expectation + end + + # @private + def build_expectation(error_generator, expectation_ordering) + expected_from = IGNORED_BACKTRACE_LINE + message_expectation_class.new(error_generator, expectation_ordering, expected_from, self) + end + + # @private + def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation) + configure_method + stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from, + self, :stub, opts, &implementation) + stubs.unshift stub + stub + end + + # A simple stub can only return a concrete value for a message, and + # cannot match on arguments. It is used as an optimization over + # `add_stub` / `add_expectation` where it is known in advance that this + # is all that will be required of a stub, such as when passing attributes + # to the `double` example method. They do not stash or restore existing method + # definitions. + # + # @private + def add_simple_stub(method_name, response) + setup_simple_method_double method_name, response, stubs + end + + # @private + def add_simple_expectation(method_name, response, error_generator, backtrace_line) + setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line + end + + # @private + def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil) + define_proxy_method + + me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line) + collection.unshift me + me + end + + # @private + def add_default_stub(*args, &implementation) + return if stubs.any? + add_stub(*args, &implementation) + end + + # @private + def remove_stub + raise_method_not_stubbed_error if stubs.empty? + remove_stub_if_present + end + + # @private + def remove_stub_if_present + expectations.empty? ? reset : stubs.clear + end + + # @private + def raise_method_not_stubbed_error + RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name) + end + + # In Ruby 2.0.0 and above prepend will alter the method lookup chain. + # We use an object's singleton class to define method doubles upon, + # however if the object has had its singleton class (as opposed to + # its actual class) prepended too then the the method lookup chain + # will look in the prepended module first, **before** the singleton + # class. + # + # This code works around that by providing a mock definition target + # that is either the singleton class, or if necessary, a prepended module + # of our own. + # + if Support::RubyFeatures.module_prepends_supported? + + private + + # We subclass `Module` in order to be able to easily detect our prepended module. + RSpecPrependedModule = Class.new(Module) + + def definition_target + @definition_target ||= usable_rspec_prepended_module || object_singleton_class + end + + def usable_rspec_prepended_module + @proxy.prepended_modules_of_singleton_class.each do |mod| + # If we have one of our modules prepended before one of the user's + # modules that defines the method, use that, since our module's + # definition will take precedence. + return mod if RSpecPrependedModule === mod + + # If we hit a user module with the method defined first, + # we must create a new prepend module, even if one exists later, + # because ours will only take precedence if it comes first. + return new_rspec_prepended_module if mod.method_defined?(method_name) + end + + nil + end + + def new_rspec_prepended_module + RSpecPrependedModule.new.tap do |mod| + object_singleton_class.__send__ :prepend, mod + end + end + + else + + private + + def definition_target + object_singleton_class + end + + end + + private + + def remove_method_from_definition_target + definition_target.__send__(:remove_method, @method_name) + rescue NameError + # This can happen when the method has been monkeyed with by + # something outside RSpec. This happens, for example, when + # `file.write` has been stubbed, and then `file.reopen(other_io)` + # is later called, as `File#reopen` appears to redefine `write`. + # + # Note: we could avoid rescuing this by checking + # `definition_target.instance_method(@method_name).owner == definition_target`, + # saving us from the cost of the expensive exception, but this error is + # extremely rare (it was discovered on 2014-12-30, only happens on + # RUBY_VERSION < 2.0 and our spec suite only hits this condition once), + # so we'd rather avoid the cost of that check for every method double, + # and risk the rare situation where this exception will get raised. + RSpec.warn_with( + "WARNING: RSpec could not fully restore #{@object.inspect}." \ + "#{@method_name}, possibly because the method has been redefined " \ + "by something outside of RSpec." + ) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_reference.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_reference.rb new file mode 100644 index 0000000000..50608b01a4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/method_reference.rb @@ -0,0 +1,214 @@ +RSpec::Support.require_rspec_support 'comparable_version' + +module RSpec + module Mocks + # Represents a method on an object that may or may not be defined. + # The method may be an instance method on a module or a method on + # any object. + # + # @private + class MethodReference + def self.for(object_reference, method_name) + new(object_reference, method_name) + end + + def initialize(object_reference, method_name) + @object_reference = object_reference + @method_name = method_name + end + + # A method is implemented if sending the message does not result in + # a `NoMethodError`. It might be dynamically implemented by + # `method_missing`. + def implemented? + @object_reference.when_loaded do |m| + method_implemented?(m) + end + end + + # Returns true if we definitively know that sending the method + # will result in a `NoMethodError`. + # + # This is not simply the inverse of `implemented?`: there are + # cases when we don't know if a method is implemented and + # both `implemented?` and `unimplemented?` will return false. + def unimplemented? + @object_reference.when_loaded do |_m| + return !implemented? + end + + # If it's not loaded, then it may be implemented but we can't check. + false + end + + # A method is defined if we are able to get a `Method` object for it. + # In that case, we can assert against metadata like the arity. + def defined? + @object_reference.when_loaded do |m| + method_defined?(m) + end + end + + def with_signature + return unless (original = original_method) + yield Support::MethodSignature.new(original) + end + + def visibility + @object_reference.when_loaded do |m| + return visibility_from(m) + end + + # When it's not loaded, assume it's public. We don't want to + # wrongly treat the method as private. + :public + end + + def self.instance_method_visibility_for(klass, method_name) + if klass.public_method_defined?(method_name) + :public + elsif klass.private_method_defined?(method_name) + :private + elsif klass.protected_method_defined?(method_name) + :protected + end + end + + class << self + alias method_defined_at_any_visibility? instance_method_visibility_for + end + + def self.method_visibility_for(object, method_name) + vis = instance_method_visibility_for(class << object; self; end, method_name) + + # If the method is not defined on the class, `instance_method_visibility_for` + # returns `nil`. However, it may be handled dynamically by `method_missing`, + # so here we check `respond_to` (passing false to not check private methods). + # + # This only considers the public case, but I don't think it's possible to + # write `method_missing` in such a way that it handles a dynamic message + # with private or protected visibility. Ruby doesn't provide you with + # the caller info. + return vis unless vis.nil? + + proxy = RSpec::Mocks.space.proxy_for(object) + respond_to = proxy.method_double_if_exists_for_message(:respond_to?) + + visible = respond_to && respond_to.original_method.call(method_name) || + object.respond_to?(method_name) + + return :public if visible + end + + private + + def original_method + @object_reference.when_loaded do |m| + self.defined? && find_method(m) + end + end + end + + # @private + class InstanceMethodReference < MethodReference + private + + def method_implemented?(mod) + MethodReference.method_defined_at_any_visibility?(mod, @method_name) + end + + # Ideally, we'd use `respond_to?` for `method_implemented?` but we need a + # reference to an instance to do that and we don't have one. Note that + # we may get false negatives: if the method is implemented via + # `method_missing`, we'll return `false` even though it meets our + # definition of "implemented". However, it's the best we can do. + alias method_defined? method_implemented? + + # works around the fact that repeated calls for method parameters will + # falsely return empty arrays on JRuby in certain circumstances, this + # is necessary here because we can't dup/clone UnboundMethods. + # + # This is necessary due to a bug in JRuby prior to 1.7.5 fixed in: + # https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27 + if RUBY_PLATFORM == 'java' && RSpec::Support::ComparableVersion.new(JRUBY_VERSION) < '1.7.5' + def find_method(mod) + mod.dup.instance_method(@method_name) + end + else + def find_method(mod) + mod.instance_method(@method_name) + end + end + + def visibility_from(mod) + MethodReference.instance_method_visibility_for(mod, @method_name) + end + end + + # @private + class ObjectMethodReference < MethodReference + def self.for(object_reference, method_name) + if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } } + ClassNewMethodReference.new(object_reference, method_name) + else + super + end + end + + private + + def method_implemented?(object) + object.respond_to?(@method_name, true) + end + + def method_defined?(object) + (class << object; self; end).method_defined?(@method_name) + end + + def find_method(object) + object.method(@method_name) + end + + def visibility_from(object) + MethodReference.method_visibility_for(object, @method_name) + end + end + + # When a class's `.new` method is stubbed, we want to use the method + # signature from `#initialize` because `.new`'s signature is a generic + # `def new(*args)` and it simply delegates to `#initialize` and forwards + # all args...so the method with the actually used signature is `#initialize`. + # + # This method reference implementation handles that specific case. + # @private + class ClassNewMethodReference < ObjectMethodReference + def self.applies_to?(method_name) + return false unless method_name == :new + klass = yield + return false unless ::Class === klass && klass.respond_to?(:new, true) + + # We only want to apply our special logic to normal `new` methods. + # Methods that the user has monkeyed with should be left as-is. + uses_class_new?(klass) + end + + if RUBY_VERSION.to_i >= 3 + CLASS_NEW = ::Class.instance_method(:new) + + def self.uses_class_new?(klass) + ::RSpec::Support.method_handle_for(klass, :new) == CLASS_NEW.bind(klass) + end + else # Ruby 2's Method#== is too strict + def self.uses_class_new?(klass) + ::RSpec::Support.method_handle_for(klass, :new).owner == ::Class + end + end + + def with_signature + @object_reference.when_loaded do |klass| + yield Support::MethodSignature.new(klass.instance_method(:initialize)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/minitest_integration.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/minitest_integration.rb new file mode 100644 index 0000000000..a129890405 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/minitest_integration.rb @@ -0,0 +1,68 @@ +require 'rspec/mocks' + +module RSpec + module Mocks + # @private + module MinitestIntegration + include ::RSpec::Mocks::ExampleMethods + + def before_setup + ::RSpec::Mocks.setup + super + end + + def after_teardown + super + + # Only verify if there's not already an error. Otherwise + # we risk getting the same failure twice, since negative + # expectation violations raise both when the message is + # unexpectedly received, and also during `verify` (in case + # the first failure was caught by user code via a + # `rescue Exception`). + ::RSpec::Mocks.verify unless failures.any? + ensure + ::RSpec::Mocks.teardown + end + end + end +end + +Minitest::Test.send(:include, RSpec::Mocks::MinitestIntegration) + +if defined?(::Minitest::Expectation) + if defined?(::RSpec::Expectations) && ::Minitest::Expectation.method_defined?(:to) + # rspec/expectations/minitest_integration has already been loaded and + # has defined `to`/`not_to`/`to_not` on `Minitest::Expectation` so we do + # not want to here (or else we would interfere with rspec-expectations' definition). + else + # ...otherwise, define those methods now. If `rspec/expectations/minitest_integration` + # is loaded after this file, it'll override the definition here. + Minitest::Expectation.class_eval do + include RSpec::Mocks::ExpectationTargetMethods + + def to(*args) + ctx.assertions += 1 + super + end + + def not_to(*args) + ctx.assertions += 1 + super + end + + def to_not(*args) + ctx.assertions += 1 + super + end + end + end +end + +module RSpec + module Mocks + remove_const :MockExpectationError + # Raised when a message expectation is not satisfied. + MockExpectationError = ::Minitest::Assertion + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/mutate_const.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/mutate_const.rb new file mode 100644 index 0000000000..071d7a2186 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/mutate_const.rb @@ -0,0 +1,339 @@ +RSpec::Support.require_rspec_support 'recursive_const_methods' + +module RSpec + module Mocks + # Provides information about constants that may (or may not) + # have been mutated by rspec-mocks. + class Constant + extend Support::RecursiveConstMethods + + # @api private + def initialize(name) + @name = name + @previously_defined = false + @stubbed = false + @hidden = false + @valid_name = true + yield self if block_given? + end + + # @return [String] The fully qualified name of the constant. + attr_reader :name + + # @return [Object, nil] The original value (e.g. before it + # was mutated by rspec-mocks) of the constant, or + # nil if the constant was not previously defined. + attr_accessor :original_value + + # @private + attr_writer :previously_defined, :stubbed, :hidden, :valid_name + + # @return [Boolean] Whether or not the constant was defined + # before the current example. + def previously_defined? + @previously_defined + end + + # @return [Boolean] Whether or not rspec-mocks has mutated + # (stubbed or hidden) this constant. + def mutated? + @stubbed || @hidden + end + + # @return [Boolean] Whether or not rspec-mocks has stubbed + # this constant. + def stubbed? + @stubbed + end + + # @return [Boolean] Whether or not rspec-mocks has hidden + # this constant. + def hidden? + @hidden + end + + # @return [Boolean] Whether or not the provided constant name + # is a valid Ruby constant name. + def valid_name? + @valid_name + end + + # The default `to_s` isn't very useful, so a custom version is provided. + def to_s + "#<#{self.class.name} #{name}>" + end + alias inspect to_s + + # @private + def self.unmutated(name) + previously_defined = !!recursive_const_defined?(name) + rescue NameError + new(name) do |c| + c.valid_name = false + end + else + new(name) do |const| + const.previously_defined = previously_defined + const.original_value = recursive_const_get(name) if previously_defined + end + end + + # Queries rspec-mocks to find out information about the named constant. + # + # @param [String] name the name of the constant + # @return [Constant] an object containing information about the named + # constant. + def self.original(name) + mutator = ::RSpec::Mocks.space.constant_mutator_for(name) + mutator ? mutator.to_constant : unmutated(name) + end + end + + # Provides a means to stub constants. + class ConstantMutator + extend Support::RecursiveConstMethods + + # Stubs a constant. + # + # @param (see ExampleMethods#stub_const) + # @option (see ExampleMethods#stub_const) + # @return (see ExampleMethods#stub_const) + # + # @see ExampleMethods#stub_const + # @note It's recommended that you use `stub_const` in your + # examples. This is an alternate public API that is provided + # so you can stub constants in other contexts (e.g. helper + # classes). + def self.stub(constant_name, value, options={}) + unless String === constant_name + raise ArgumentError, "`stub_const` requires a String, but you provided a #{constant_name.class.name}" + end + + mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const) + DefinedConstantReplacer + else + UndefinedConstantSetter + end + + mutate(mutator.new(constant_name, value, options[:transfer_nested_constants])) + value + end + + # Hides a constant. + # + # @param (see ExampleMethods#hide_const) + # + # @see ExampleMethods#hide_const + # @note It's recommended that you use `hide_const` in your + # examples. This is an alternate public API that is provided + # so you can hide constants in other contexts (e.g. helper + # classes). + def self.hide(constant_name) + mutate(ConstantHider.new(constant_name, nil, {})) + nil + end + + # Contains common functionality used by all of the constant mutators. + # + # @private + class BaseMutator + include Support::RecursiveConstMethods + + attr_reader :original_value, :full_constant_name + + def initialize(full_constant_name, mutated_value, transfer_nested_constants) + @full_constant_name = normalize_const_name(full_constant_name) + @mutated_value = mutated_value + @transfer_nested_constants = transfer_nested_constants + @context_parts = @full_constant_name.split('::') + @const_name = @context_parts.pop + @reset_performed = false + end + + def to_constant + const = Constant.new(full_constant_name) + const.original_value = original_value + + const + end + + def idempotently_reset + reset unless @reset_performed + @reset_performed = true + end + end + + # Hides a defined constant for the duration of an example. + # + # @private + class ConstantHider < BaseMutator + def mutate + return unless (@defined = recursive_const_defined?(full_constant_name)) + @context = recursive_const_get(@context_parts.join('::')) + @original_value = get_const_defined_on(@context, @const_name) + + @context.__send__(:remove_const, @const_name) + end + + def to_constant + return Constant.unmutated(full_constant_name) unless @defined + + const = super + const.hidden = true + const.previously_defined = true + + const + end + + def reset + return unless @defined + @context.const_set(@const_name, @original_value) + end + end + + # Replaces a defined constant for the duration of an example. + # + # @private + class DefinedConstantReplacer < BaseMutator + def initialize(*args) + super + @constants_to_transfer = [] + end + + def mutate + @context = recursive_const_get(@context_parts.join('::')) + @original_value = get_const_defined_on(@context, @const_name) + + @constants_to_transfer = verify_constants_to_transfer! + + @context.__send__(:remove_const, @const_name) + @context.const_set(@const_name, @mutated_value) + + transfer_nested_constants + end + + def to_constant + const = super + const.stubbed = true + const.previously_defined = true + + const + end + + def reset + @constants_to_transfer.each do |const| + @mutated_value.__send__(:remove_const, const) + end + + @context.__send__(:remove_const, @const_name) + @context.const_set(@const_name, @original_value) + end + + def transfer_nested_constants + @constants_to_transfer.each do |const| + @mutated_value.const_set(const, get_const_defined_on(original_value, const)) + end + end + + def verify_constants_to_transfer! + return [] unless should_transfer_nested_constants? + + { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description| + next if value.respond_to?(:constants) + + raise ArgumentError, + "Cannot transfer nested constants for #{@full_constant_name} " \ + "since #{description} is not a class or module and only classes " \ + "and modules support nested constants." + end + + if Array === @transfer_nested_constants + @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7' + undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value) + + if undefined_constants.any? + available_constants = constants_defined_on(@original_value) - @transfer_nested_constants + raise ArgumentError, + "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \ + "for #{@full_constant_name} since they are not defined. Did you mean " \ + "#{available_constants.join(' or ')}?" + end + + @transfer_nested_constants + else + constants_defined_on(@original_value) + end + end + + def should_transfer_nested_constants? + return true if @transfer_nested_constants + return false unless RSpec::Mocks.configuration.transfer_nested_constants? + @original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants) + end + end + + # Sets an undefined constant for the duration of an example. + # + # @private + class UndefinedConstantSetter < BaseMutator + def mutate + @parent = @context_parts.inject(Object) do |klass, name| + if const_defined_on?(klass, name) + get_const_defined_on(klass, name) + else + ConstantMutator.stub(name_for(klass, name), Module.new) + end + end + + @parent.const_set(@const_name, @mutated_value) + end + + def to_constant + const = super + const.stubbed = true + const.previously_defined = false + + const + end + + def reset + @parent.__send__(:remove_const, @const_name) + end + + private + + def name_for(parent, name) + root = if parent == Object + '' + else + parent.name + end + root + '::' + name + end + end + + # Uses the mutator to mutate (stub or hide) a constant. Ensures that + # the mutator is correctly registered so it can be backed out at the end + # of the test. + # + # @private + def self.mutate(mutator) + ::RSpec::Mocks.space.register_constant_mutator(mutator) + mutator.mutate + end + + # Used internally by the constant stubbing to raise a helpful + # error when a constant like "A::B::C" is stubbed and A::B is + # not a module (and thus, it's impossible to define "A::B::C" + # since only modules can have nested constants). + # + # @api private + def self.raise_on_invalid_const + lambda do |const_name, failed_name| + raise "Cannot stub constant #{failed_name} on #{const_name} " \ + "since #{const_name} is not a module." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/object_reference.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/object_reference.rb new file mode 100644 index 0000000000..cce2c3313e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/object_reference.rb @@ -0,0 +1,149 @@ +module RSpec + module Mocks + # @private + class ObjectReference + # Returns an appropriate Object or Module reference based + # on the given argument. + def self.for(object_module_or_name, allow_direct_object_refs=false) + case object_module_or_name + when Module + if anonymous_module?(object_module_or_name) + DirectObjectReference.new(object_module_or_name) + else + # Use a `NamedObjectReference` if it has a name because this + # will use the original value of the constant in case it has + # been stubbed. + NamedObjectReference.new(name_of(object_module_or_name)) + end + when String + NamedObjectReference.new(object_module_or_name) + else + if allow_direct_object_refs + DirectObjectReference.new(object_module_or_name) + else + raise ArgumentError, + "Module or String expected, got #{object_module_or_name.inspect}" + end + end + end + + if Module.new.name.nil? + def self.anonymous_module?(mod) + !name_of(mod) + end + else # 1.8.7 + def self.anonymous_module?(mod) + name_of(mod) == "" + end + end + private_class_method :anonymous_module? + + def self.name_of(mod) + MODULE_NAME_METHOD.bind(mod).call + end + private_class_method :name_of + + # @private + MODULE_NAME_METHOD = Module.instance_method(:name) + end + + # An implementation of rspec-mocks' reference interface. + # Used when an object is passed to {ExampleMethods#object_double}, or + # an anonymous class or module is passed to {ExampleMethods#instance_double} + # or {ExampleMethods#class_double}. + # Represents a reference to that object. + # @see NamedObjectReference + class DirectObjectReference + # @param object [Object] the object to which this refers + def initialize(object) + @object = object + end + + # @return [String] the object's description (via `#inspect`). + def description + @object.inspect + end + + # Defined for interface parity with the other object reference + # implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const` + # is invalid when passing an object argument to `object_double`. + def const_to_replace + raise ArgumentError, + "Can not perform constant replacement with an anonymous object." + end + + # The target of the verifying double (the object itself). + # + # @return [Object] + def target + @object + end + + # Always returns true for an object as the class is defined. + # + # @return [true] + def defined? + true + end + + # Yields if the reference target is loaded, providing a generic mechanism + # to optionally run a bit of code only when a reference's target is + # loaded. + # + # This specific implementation always yields because direct references + # are always loaded. + # + # @yield [Object] the target of this reference. + def when_loaded + yield @object + end + end + + # An implementation of rspec-mocks' reference interface. + # Used when a string is passed to {ExampleMethods#object_double}, + # and when a string, named class or named module is passed to + # {ExampleMethods#instance_double}, or {ExampleMethods#class_double}. + # Represents a reference to the object named (via a constant lookup) + # by the string. + # @see DirectObjectReference + class NamedObjectReference + # @param const_name [String] constant name + def initialize(const_name) + @const_name = const_name + end + + # @return [Boolean] true if the named constant is defined, false otherwise. + def defined? + !!object + end + + # @return [String] the constant name to replace with a double. + def const_to_replace + @const_name + end + alias description const_to_replace + + # @return [Object, nil] the target of the verifying double (the named object), or + # nil if it is not defined. + def target + object + end + + # Yields if the reference target is loaded, providing a generic mechanism + # to optionally run a bit of code only when a reference's target is + # loaded. + # + # @yield [Object] the target object + def when_loaded + yield object if object + end + + private + + def object + return @object if defined?(@object) + @object = Constant.original(@const_name).original_value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/order_group.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/order_group.rb new file mode 100644 index 0000000000..a9947995c2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/order_group.rb @@ -0,0 +1,81 @@ +module RSpec + module Mocks + # @private + class OrderGroup + def initialize + @expectations = [] + @invocation_order = [] + @index = 0 + end + + # @private + def register(expectation) + @expectations << expectation + end + + def invoked(message) + @invocation_order << message + end + + # @private + def ready_for?(expectation) + remaining_expectations.find(&:ordered?) == expectation + end + + # @private + def consume + remaining_expectations.each_with_index do |expectation, index| + next unless expectation.ordered? + + @index += index + 1 + return expectation + end + nil + end + + # @private + def handle_order_constraint(expectation) + return unless expectation.ordered? && remaining_expectations.include?(expectation) + return consume if ready_for?(expectation) + expectation.raise_out_of_order_error + end + + def verify_invocation_order(expectation) + expectation.raise_out_of_order_error unless expectations_invoked_in_order? + true + end + + def clear + @index = 0 + @invocation_order.clear + @expectations.clear + end + + def empty? + @expectations.empty? + end + + private + + def remaining_expectations + @expectations[@index..-1] || [] + end + + def expectations_invoked_in_order? + invoked_expectations == expected_invocations + end + + def invoked_expectations + @expectations.select { |e| e.ordered? && @invocation_order.include?(e) } + end + + def expected_invocations + @invocation_order.map { |invocation| expectation_for(invocation) }.compact + end + + def expectation_for(message) + @expectations.find { |e| message == e } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/proxy.rb new file mode 100644 index 0000000000..6e02681663 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/proxy.rb @@ -0,0 +1,520 @@ +module RSpec + module Mocks + # @private + class Proxy + # @private + SpecificMessage = Struct.new(:object, :message, :args) do + def ==(expectation) + expectation.orig_object == object && expectation.matches?(message, *args) + end + end + + unless defined?(Mutex) + Support.require_rspec_support 'mutex' + Mutex = Support::Mutex + end + + # @private + def ensure_implemented(*_args) + # noop for basic proxies, see VerifyingProxy for behaviour. + end + + # @private + def initialize(object, order_group, options={}) + ensure_can_be_proxied!(object) + + @object = object + @order_group = order_group + @error_generator = ErrorGenerator.new(object) + @messages_received = [] + @messages_received_mutex = Mutex.new + @options = options + @null_object = false + @method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) } + end + + # @private + def ensure_can_be_proxied!(object) + return unless object.is_a?(Symbol) + + msg = "Cannot proxy frozen objects. Symbols such as #{object} cannot be mocked or stubbed." + raise ArgumentError, msg + end + + # @private + attr_reader :object + + # @private + def null_object? + @null_object + end + + # @private + # Tells the object to ignore any messages that aren't explicitly set as + # stubs or message expectations. + def as_null_object + @null_object = true + @object + end + + # @private + def original_method_handle_for(_message) + nil + end + + DEFAULT_MESSAGE_EXPECTATION_OPTS = {}.freeze + + # @private + def add_message_expectation(method_name, opts=DEFAULT_MESSAGE_EXPECTATION_OPTS, &block) + location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line } + meth_double = method_double_for(method_name) + + if null_object? && !block + meth_double.add_default_stub(@error_generator, @order_group, location, opts) do + @object + end + end + + meth_double.add_expectation @error_generator, @order_group, location, opts, &block + end + + # @private + def add_simple_expectation(method_name, response, location) + method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location + end + + # @private + def build_expectation(method_name) + meth_double = method_double_for(method_name) + + meth_double.build_expectation( + @error_generator, + @order_group + ) + end + + # @private + def replay_received_message_on(expectation, &block) + expected_method_name = expectation.message + meth_double = method_double_for(expected_method_name) + + if meth_double.expectations.any? + @error_generator.raise_expectation_on_mocked_method(expected_method_name) + end + + unless null_object? || meth_double.stubs.any? + @error_generator.raise_expectation_on_unstubbed_method(expected_method_name) + end + + @messages_received_mutex.synchronize do + @messages_received.each do |(actual_method_name, args, received_block)| + next unless expectation.matches?(actual_method_name, *args) + + expectation.safe_invoke(nil) + block.call(*args, &received_block) if block + end + end + end + + # @private + def check_for_unexpected_arguments(expectation) + @messages_received_mutex.synchronize do + return if @messages_received.empty? + + return if @messages_received.any? { |method_name, args, _| expectation.matches?(method_name, *args) } + + name_but_not_args, others = @messages_received.partition do |(method_name, args, _)| + expectation.matches_name_but_not_args(method_name, *args) + end + + return if name_but_not_args.empty? && !others.empty? + + expectation.raise_unexpected_message_args_error(name_but_not_args.map { |args| args[1] }) + end + end + + # @private + def add_stub(method_name, opts={}, &implementation) + location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line } + method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation + end + + # @private + def add_simple_stub(method_name, response) + method_double_for(method_name).add_simple_stub method_name, response + end + + # @private + def remove_stub(method_name) + method_double_for(method_name).remove_stub + end + + # @private + def remove_stub_if_present(method_name) + method_double_for(method_name).remove_stub_if_present + end + + # @private + def verify + @method_doubles.each_value { |d| d.verify } + end + + # @private + def reset + @messages_received_mutex.synchronize do + @messages_received.clear + end + end + + # @private + def received_message?(method_name, *args, &block) + @messages_received_mutex.synchronize do + @messages_received.any? { |array| array == [method_name, args, block] } + end + end + + # @private + def messages_arg_list + @messages_received_mutex.synchronize do + @messages_received.map { |_, args, _| args } + end + end + + # @private + def has_negative_expectation?(message) + method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) } + end + + # @private + def record_message_received(message, *args, &block) + @order_group.invoked SpecificMessage.new(object, message, args) + @messages_received_mutex.synchronize do + @messages_received << [message, args, block] + end + end + ruby2_keywords :record_message_received if respond_to?(:ruby2_keywords, true) + + # @private + def message_received(message, *args, &block) + record_message_received message, *args, &block + + expectation = find_matching_expectation(message, *args) + stub = find_matching_method_stub(message, *args) + + if (stub && expectation && expectation.called_max_times?) || (stub && !expectation) + expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters? + if (expectation = find_almost_matching_expectation(message, *args)) + expectation.advise(*args) unless expectation.expected_messages_received? + end + stub.invoke(nil, *args, &block) + elsif expectation + expectation.unadvise(messages_arg_list) + expectation.invoke(stub, *args, &block) + elsif (expectation = find_almost_matching_expectation(message, *args)) + expectation.advise(*args) if null_object? unless expectation.expected_messages_received? + + if null_object? || !has_negative_expectation?(message) + expectation.raise_unexpected_message_args_error([args]) + end + elsif (stub = find_almost_matching_stub(message, *args)) + stub.advise(*args) + raise_missing_default_stub_error(stub, [args]) + elsif Class === @object + @object.superclass.__send__(message, *args, &block) + else + @object.__send__(:method_missing, message, *args, &block) + end + end + ruby2_keywords :message_received if respond_to?(:ruby2_keywords, true) + + # @private + def raise_unexpected_message_error(method_name, args) + @error_generator.raise_unexpected_message_error method_name, args + end + + # @private + def raise_missing_default_stub_error(expectation, args_for_multiple_calls) + @error_generator.raise_missing_default_stub_error(expectation, args_for_multiple_calls) + end + + # @private + def visibility_for(_method_name) + # This is the default (for test doubles). Subclasses override this. + :public + end + + if Support::RubyFeatures.module_prepends_supported? + def self.prepended_modules_of(klass) + ancestors = klass.ancestors + + # `|| 0` is necessary for Ruby 2.0, where the singleton class + # is only in the ancestor list when there are prepended modules. + singleton_index = ancestors.index(klass) || 0 + + ancestors[0, singleton_index] + end + + def prepended_modules_of_singleton_class + @prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class) + end + end + + # @private + def method_double_if_exists_for_message(message) + method_double_for(message) if @method_doubles.key?(message.to_sym) + end + + private + + def method_double_for(message) + @method_doubles[message.to_sym] + end + + def find_matching_expectation(method_name, *args) + find_best_matching_expectation_for(method_name) do |expectation| + expectation.matches?(method_name, *args) + end + end + ruby2_keywords :find_matching_expectation if respond_to?(:ruby2_keywords, true) + + def find_almost_matching_expectation(method_name, *args) + find_best_matching_expectation_for(method_name) do |expectation| + expectation.matches_name_but_not_args(method_name, *args) + end + end + ruby2_keywords :find_almost_matching_expectation if respond_to?(:ruby2_keywords, true) + + def find_best_matching_expectation_for(method_name) + first_match = nil + + method_double_for(method_name).expectations.each do |expectation| + next unless yield expectation + return expectation unless expectation.called_max_times? + first_match ||= expectation + end + + first_match + end + + def find_matching_method_stub(method_name, *args) + method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) } + end + ruby2_keywords :find_matching_method_stub if respond_to?(:ruby2_keywords, true) + + def find_almost_matching_stub(method_name, *args) + method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) } + end + ruby2_keywords :find_almost_matching_stub if respond_to?(:ruby2_keywords, true) + end + + # @private + class TestDoubleProxy < Proxy + def reset + @method_doubles.clear + object.__disallow_further_usage! + super + end + end + + # @private + class PartialDoubleProxy < Proxy + def original_method_handle_for(message) + if any_instance_class_recorder_observing_method?(@object.class, message) + message = ::RSpec::Mocks.space. + any_instance_recorder_for(@object.class). + build_alias_method_name(message) + end + + ::RSpec::Support.method_handle_for(@object, message) + rescue NameError + nil + end + + # @private + def add_simple_expectation(method_name, response, location) + method_double_for(method_name).configure_method + super + end + + # @private + def add_simple_stub(method_name, response) + method_double_for(method_name).configure_method + super + end + + # @private + def visibility_for(method_name) + # We fall back to :public because by default we allow undefined methods + # to be stubbed, and when we do so, we make them public. + MethodReference.method_visibility_for(@object, method_name) || :public + end + + def reset + @method_doubles.each_value { |d| d.reset } + super + end + + def message_received(message, *args, &block) + RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber| + subscriber.notify_received_message(object, message, args, block) + end + super + end + ruby2_keywords :message_received if respond_to?(:ruby2_keywords, true) + + private + + def any_instance_class_recorder_observing_method?(klass, method_name) + only_return_existing = true + recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing) + return true if recorder && recorder.already_observing?(method_name) + + superklass = klass.superclass + return false if superklass.nil? + any_instance_class_recorder_observing_method?(superklass, method_name) + end + end + + # @private + # When we mock or stub a method on a class, we have to treat it a bit different, + # because normally singleton method definitions only affect the object on which + # they are defined, but on classes they affect subclasses, too. As a result, + # we need some special handling to get the original method. + module PartialClassDoubleProxyMethods + def initialize(source_space, *args) + @source_space = source_space + super(*args) + end + + # Consider this situation: + # + # class A; end + # class B < A; end + # + # allow(A).to receive(:new) + # expect(B).to receive(:new).and_call_original + # + # When getting the original definition for `B.new`, we cannot rely purely on + # using `B.method(:new)` before our redefinition is defined on `B`, because + # `B.method(:new)` will return a method that will execute the stubbed version + # of the method on `A` since singleton methods on classes are in the lookup + # hierarchy. + # + # To do it properly, we need to find the original definition of `new` from `A` + # from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will + # run with the proper `self`. + # + # That's what this method (together with `original_unbound_method_handle_from_ancestor_for`) + # does. + def original_method_handle_for(message) + unbound_method = superclass_proxy && + superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym) + + return super unless unbound_method + unbound_method.bind(object) + # :nocov: + rescue TypeError + if RUBY_VERSION == '1.8.7' + # In MRI 1.8.7, a singleton method on a class cannot be rebound to its subclass + if unbound_method && unbound_method.owner.ancestors.first != unbound_method.owner + # This is a singleton method; we can't do anything with it + # But we can work around this using a different implementation + double = method_double_from_ancestor_for(message) + return object.method(double.method_stasher.stashed_method_name) + end + end + raise + # :nocov: + end + + protected + + def original_unbound_method_handle_from_ancestor_for(message) + double = method_double_from_ancestor_for(message) + double && double.original_method.unbind + end + + def method_double_from_ancestor_for(message) + @method_doubles.fetch(message) do + # The fact that there is no method double for this message indicates + # that it has not been redefined by rspec-mocks. We need to continue + # looking up the ancestor chain. + return superclass_proxy && + superclass_proxy.method_double_from_ancestor_for(message) + end + end + + def superclass_proxy + return @superclass_proxy if defined?(@superclass_proxy) + + if (superclass = object.superclass) + @superclass_proxy = @source_space.superclass_proxy_for(superclass) + else + @superclass_proxy = nil + end + end + end + + # @private + class PartialClassDoubleProxy < PartialDoubleProxy + include PartialClassDoubleProxyMethods + end + + # @private + class ProxyForNil < PartialDoubleProxy + def initialize(order_group) + set_expectation_behavior + super(nil, order_group) + end + + attr_accessor :disallow_expectations + attr_accessor :warn_about_expectations + + def add_message_expectation(method_name, opts={}, &block) + warn_or_raise!(method_name) + super + end + + def add_stub(method_name, opts={}, &implementation) + warn_or_raise!(method_name) + super + end + + private + + def set_expectation_behavior + case RSpec::Mocks.configuration.allow_message_expectations_on_nil + when false + @warn_about_expectations = false + @disallow_expectations = true + when true + @warn_about_expectations = false + @disallow_expectations = false + else + @warn_about_expectations = true + @disallow_expectations = false + end + end + + def warn_or_raise!(method_name) + # This method intentionally swallows the message when + # neither disallow_expectations nor warn_about_expectations + # are set to true. + if disallow_expectations + raise_error(method_name) + elsif warn_about_expectations + warn(method_name) + end + end + + def warn(method_name) + warning_msg = @error_generator.expectation_on_nil_message(method_name) + RSpec.warning(warning_msg) + end + + def raise_error(method_name) + @error_generator.raise_expectation_on_nil_error(method_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/space.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/space.rb new file mode 100644 index 0000000000..f5a4c24061 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/space.rb @@ -0,0 +1,238 @@ +RSpec::Support.require_rspec_support 'reentrant_mutex' + +module RSpec + module Mocks + # @private + # Provides a default space implementation for outside + # the scope of an example. Called "root" because it serves + # as the root of the space stack. + class RootSpace + def proxy_for(*_args) + raise_lifecycle_message + end + + def any_instance_recorder_for(*_args) + raise_lifecycle_message + end + + def any_instance_proxy_for(*_args) + raise_lifecycle_message + end + + def register_constant_mutator(_mutator) + raise_lifecycle_message + end + + def any_instance_recorders_from_ancestry_of(_object) + raise_lifecycle_message + end + + def reset_all + end + + def verify_all + end + + def registered?(_object) + false + end + + def superclass_proxy_for(*_args) + raise_lifecycle_message + end + + def new_scope + Space.new + end + + private + + def raise_lifecycle_message + raise OutsideOfExampleError, + "The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported." + end + end + + # @private + class Space + attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex + + def initialize + @proxies = {} + @any_instance_recorders = {} + @constant_mutators = [] + @expectation_ordering = OrderGroup.new + @proxy_mutex = new_mutex + @any_instance_mutex = new_mutex + end + + def new_scope + NestedSpace.new(self) + end + + def verify_all + proxies.values.each { |proxy| proxy.verify } + any_instance_recorders.each_value { |recorder| recorder.verify } + end + + def reset_all + proxies.each_value { |proxy| proxy.reset } + @constant_mutators.reverse.each { |mut| mut.idempotently_reset } + any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! } + any_instance_recorders.clear + end + + def register_constant_mutator(mutator) + @constant_mutators << mutator + end + + def constant_mutator_for(name) + @constant_mutators.find { |m| m.full_constant_name == name } + end + + def any_instance_recorder_for(klass, only_return_existing=false) + any_instance_mutex.synchronize do + id = klass.__id__ + any_instance_recorders.fetch(id) do + return nil if only_return_existing + any_instance_recorder_not_found_for(id, klass) + end + end + end + + def any_instance_proxy_for(klass) + AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass)) + end + + def proxies_of(klass) + proxies.values.select { |proxy| klass === proxy.object } + end + + def proxy_for(object) + proxy_mutex.synchronize do + id = id_for(object) + proxies.fetch(id) { proxy_not_found_for(id, object) } + end + end + + def superclass_proxy_for(klass) + proxy_mutex.synchronize do + id = id_for(klass) + proxies.fetch(id) { superclass_proxy_not_found_for(id, klass) } + end + end + + alias ensure_registered proxy_for + + def registered?(object) + proxies.key?(id_for object) + end + + def any_instance_recorders_from_ancestry_of(object) + # Optimization: `any_instance` is a feature we generally + # recommend not using, so we can often early exit here + # without doing an O(N) linear search over the number of + # ancestors in the object's class hierarchy. + return [] if any_instance_recorders.empty? + + # We access the ancestors through the singleton class, to avoid calling + # `class` in case `class` has been stubbed. + (class << object; ancestors; end).map do |klass| + any_instance_recorders[klass.__id__] + end.compact + end + + private + + def new_mutex + Support::ReentrantMutex.new + end + + def proxy_not_found_for(id, object) + proxies[id] = case object + when NilClass then ProxyForNil.new(@expectation_ordering) + when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering) + when Class + class_proxy_with_callback_verification_strategy(object, CallbackInvocationStrategy.new) + else + if RSpec::Mocks.configuration.verify_partial_doubles? + VerifyingPartialDoubleProxy.new(object, @expectation_ordering) + else + PartialDoubleProxy.new(object, @expectation_ordering) + end + end + end + + def superclass_proxy_not_found_for(id, object) + raise "superclass_proxy_not_found_for called with something that is not a class" unless Class === object + proxies[id] = class_proxy_with_callback_verification_strategy(object, NoCallbackInvocationStrategy.new) + end + + def class_proxy_with_callback_verification_strategy(object, strategy) + if RSpec::Mocks.configuration.verify_partial_doubles? + VerifyingPartialClassDoubleProxy.new( + self, + object, + @expectation_ordering, + strategy + ) + else + PartialClassDoubleProxy.new(self, object, @expectation_ordering) + end + end + + def any_instance_recorder_not_found_for(id, klass) + any_instance_recorders[id] = AnyInstance::Recorder.new(klass) + end + + if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2 + require 'securerandom' + + def id_for(object) + id = object.__id__ + + return id if object.equal?(::ObjectSpace._id2ref(id)) + # this suggests that object.__id__ is proxying through to some wrapped object + + object.instance_exec do + @__id_for_rspec_mocks_space ||= ::SecureRandom.uuid + end + end + else + def id_for(object) + object.__id__ + end + end + end + + # @private + class NestedSpace < Space + def initialize(parent) + @parent = parent + super() + end + + def proxies_of(klass) + super + @parent.proxies_of(klass) + end + + def constant_mutator_for(name) + super || @parent.constant_mutator_for(name) + end + + def registered?(object) + super || @parent.registered?(object) + end + + private + + def proxy_not_found_for(id, object) + @parent.proxies[id] || super + end + + def any_instance_recorder_not_found_for(id, klass) + @parent.any_instance_recorders[id] || super + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/standalone.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/standalone.rb new file mode 100644 index 0000000000..74317b01bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/standalone.rb @@ -0,0 +1,3 @@ +require 'rspec/mocks' +extend RSpec::Mocks::ExampleMethods +RSpec::Mocks.setup diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/syntax.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/syntax.rb new file mode 100644 index 0000000000..6305ab9619 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/syntax.rb @@ -0,0 +1,325 @@ +module RSpec + module Mocks + # @api private + # Provides methods for enabling and disabling the available syntaxes + # provided by rspec-mocks. + module Syntax + # @private + def self.warn_about_should! + @warn_about_should = true + end + + # @private + def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`") + if @warn_about_should + RSpec.deprecate( + "Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax", + :replacement => replacement + ) + + @warn_about_should = false + end + end + + # @api private + # Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). + def self.enable_should(syntax_host=default_should_syntax_host) + @warn_about_should = false if syntax_host == default_should_syntax_host + return if should_enabled?(syntax_host) + + syntax_host.class_exec do + def should_receive(message, opts={}, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.expect_message(self, message, opts, &block) + end + + def should_not_receive(message, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.expect_message(self, message, {}, &block).never + end + + def stub(message_or_hash, opts={}, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + if ::Hash === message_or_hash + message_or_hash.each { |message, value| stub(message).and_return value } + else + ::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block) + end + end + + def unstub(message) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to receive(...).and_call_original` or explicitly enable `:should`") + ::RSpec::Mocks.space.proxy_for(self).remove_stub(message) + end + + def stub_chain(*chain, &blk) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk) + end + + def as_null_object + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + @_null_object = true + ::RSpec::Mocks.space.proxy_for(self).as_null_object + end + + def null_object? + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + defined?(@_null_object) + end + + def received_message?(message, *args, &block) + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block) + end + + unless Class.respond_to? :any_instance + Class.class_exec do + def any_instance + ::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) + ::RSpec::Mocks.space.any_instance_proxy_for(self) + end + end + end + end + end + + # @api private + # Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). + def self.disable_should(syntax_host=default_should_syntax_host) + return unless should_enabled?(syntax_host) + + syntax_host.class_exec do + undef should_receive + undef should_not_receive + undef stub + undef unstub + undef stub_chain + undef as_null_object + undef null_object? + undef received_message? + end + + Class.class_exec do + undef any_instance + end + end + + # @api private + # Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). + def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods) + return if expect_enabled?(syntax_host) + + syntax_host.class_exec do + def receive(method_name, &block) + Matchers::Receive.new(method_name, block) + end + + def receive_messages(message_return_value_hash) + matcher = Matchers::ReceiveMessages.new(message_return_value_hash) + matcher.warn_about_block if block_given? + matcher + end + + def receive_message_chain(*messages, &block) + Matchers::ReceiveMessageChain.new(messages, &block) + end + + def allow(target) + AllowanceTarget.new(target) + end + + def expect_any_instance_of(klass) + AnyInstanceExpectationTarget.new(klass) + end + + def allow_any_instance_of(klass) + AnyInstanceAllowanceTarget.new(klass) + end + end + + RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do + def expect(target) + ExpectationTarget.new(target) + end + end + end + + # @api private + # Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). + def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods) + return unless expect_enabled?(syntax_host) + + syntax_host.class_exec do + undef receive + undef receive_messages + undef receive_message_chain + undef allow + undef expect_any_instance_of + undef allow_any_instance_of + end + + RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do + undef expect + end + end + + # @api private + # Indicates whether or not the should syntax is enabled. + def self.should_enabled?(syntax_host=default_should_syntax_host) + syntax_host.method_defined?(:should_receive) + end + + # @api private + # Indicates whether or not the expect syntax is enabled. + def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods) + syntax_host.method_defined?(:allow) + end + + # @api private + # Determines where the methods like `should_receive`, and `stub` are added. + def self.default_should_syntax_host + # JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil` + # yet `BasicObject` still exists and patching onto ::Object breaks things + # e.g. SimpleDelegator expectations won't work + # + # See: https://github.com/jruby/jruby/issues/814 + if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8 + return ::BasicObject + end + + # On 1.8.7, Object.ancestors.last == Kernel but + # things blow up if we include `RSpec::Mocks::Methods` + # into Kernel...not sure why. + return Object unless defined?(::BasicObject) + + # MacRuby has BasicObject but it's not the root class. + return Object unless Object.ancestors.last == ::BasicObject + + ::BasicObject + end + end + end +end + +if defined?(BasicObject) + # The legacy `:should` syntax adds the following methods directly to + # `BasicObject` so that they are available off of any object. Note, however, + # that this syntax does not always play nice with delegate/proxy objects. + # We recommend you use the non-monkeypatching `:expect` syntax instead. + # @see Class + class BasicObject + # @method should_receive + # Sets an expectation that this object should receive a message before + # the end of the example. + # + # @example + # logger = double('logger') + # thing_that_logs = ThingThatLogs.new(logger) + # logger.should_receive(:log) + # thing_that_logs.do_something_that_logs_a_message + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#expect + + # @method should_not_receive + # Sets and expectation that this object should _not_ receive a message + # during this example. + # @see RSpec::Mocks::ExampleMethods#expect + + # @method stub + # Tells the object to respond to the message with the specified value. + # + # @example + # counter.stub(:count).and_return(37) + # counter.stub(:count => 37) + # counter.stub(:count) { 37 } + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#allow + + # @method unstub + # Removes a stub. On a double, the object will no longer respond to + # `message`. On a real object, the original method (if it exists) is + # restored. + # + # This is rarely used, but can be useful when a stub is set up during a + # shared `before` hook for the common case, but you want to replace it + # for a special case. + # + # @note This is only available when you have enabled the `should` syntax. + + # @method stub_chain + # @overload stub_chain(method1, method2) + # @overload stub_chain("method1.method2") + # @overload stub_chain(method1, method_to_value_hash) + # + # Stubs a chain of methods. + # + # ## Warning: + # + # Chains can be arbitrarily long, which makes it quite painless to + # violate the Law of Demeter in violent ways, so you should consider any + # use of `stub_chain` a code smell. Even though not all code smells + # indicate real problems (think fluent interfaces), `stub_chain` still + # results in brittle examples. For example, if you write + # `foo.stub_chain(:bar, :baz => 37)` in a spec and then the + # implementation calls `foo.baz.bar`, the stub will not work. + # + # @example + # double.stub_chain("foo.bar") { :baz } + # double.stub_chain(:foo, :bar => :baz) + # double.stub_chain(:foo, :bar) { :baz } + # + # # Given any of ^^ these three forms ^^: + # double.foo.bar # => :baz + # + # # Common use in Rails/ActiveRecord: + # Article.stub_chain("recent.published") { [Article.new] } + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#receive_message_chain + + # @method as_null_object + # Tells the object to respond to all messages. If specific stub values + # are declared, they'll work as expected. If not, the receiver is + # returned. + # + # @note This is only available when you have enabled the `should` syntax. + + # @method null_object? + # Returns true if this object has received `as_null_object` + # + # @note This is only available when you have enabled the `should` syntax. + end +end + +# The legacy `:should` syntax adds the `any_instance` to `Class`. +# We generally recommend you use the newer `:expect` syntax instead, +# which allows you to stub any instance of a class using +# `allow_any_instance_of(klass)` or mock any instance using +# `expect_any_instance_of(klass)`. +# @see BasicObject +class Class + # @method any_instance + # Used to set stubs and message expectations on any instance of a given + # class. Returns a [Recorder](Recorder), which records messages like + # `stub` and `should_receive` for later playback on instances of the + # class. + # + # @example + # Car.any_instance.should_receive(:go) + # race = Race.new + # race.cars << Car.new + # race.go # assuming this delegates to all of its cars + # # this example would pass + # + # Account.any_instance.stub(:balance) { Money.new(:USD, 25) } + # Account.new.balance # => Money.new(:USD, 25)) + # + # @return [Recorder] + # + # @note This is only available when you have enabled the `should` syntax. + # @see RSpec::Mocks::ExampleMethods#expect_any_instance_of + # @see RSpec::Mocks::ExampleMethods#allow_any_instance_of +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/targets.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/targets.rb new file mode 100644 index 0000000000..26f728716a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/targets.rb @@ -0,0 +1,124 @@ +module RSpec + module Mocks + # @private + module TargetDelegationClassMethods + def delegate_to(matcher_method) + define_method(:to) do |matcher, &block| + unless matcher_allowed?(matcher) + raise_unsupported_matcher(:to, matcher) + end + define_matcher(matcher, matcher_method, &block) + end + end + + def delegate_not_to(matcher_method, options={}) + method_name = options.fetch(:from) + define_method(method_name) do |matcher, &block| + case matcher + when Matchers::Receive, Matchers::HaveReceived + define_matcher(matcher, matcher_method, &block) + when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain + raise_negation_unsupported(method_name, matcher) + else + raise_unsupported_matcher(method_name, matcher) + end + end + end + + def disallow_negation(method_name) + define_method(method_name) do |matcher, *_args| + raise_negation_unsupported(method_name, matcher) + end + end + end + + # @private + module TargetDelegationInstanceMethods + attr_reader :target + + private + + def matcher_allowed?(matcher) + Matchers::Matcher === matcher + end + + def define_matcher(matcher, name, &block) + matcher.__send__(name, target, &block) + end + + def raise_unsupported_matcher(method_name, matcher) + raise UnsupportedMatcherError, + "only the `receive`, `have_received` and `receive_messages` matchers are supported " \ + "with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}" + end + + def raise_negation_unsupported(method_name, matcher) + raise NegationUnsupportedError, + "`#{expression}(...).#{method_name} #{matcher.matcher_name}` is not supported since it " \ + "doesn't really make sense. What would it even mean?" + end + end + + # @private + class TargetBase + def initialize(target) + @target = target + end + + extend TargetDelegationClassMethods + include TargetDelegationInstanceMethods + end + + # @private + module ExpectationTargetMethods + extend TargetDelegationClassMethods + include TargetDelegationInstanceMethods + + delegate_to :setup_expectation + delegate_not_to :setup_negative_expectation, :from => :not_to + delegate_not_to :setup_negative_expectation, :from => :to_not + + def expression + :expect + end + end + + # @private + class ExpectationTarget < TargetBase + include ExpectationTargetMethods + end + + # @private + class AllowanceTarget < TargetBase + def expression + :allow + end + + delegate_to :setup_allowance + disallow_negation :not_to + disallow_negation :to_not + end + + # @private + class AnyInstanceAllowanceTarget < TargetBase + def expression + :allow_any_instance_of + end + + delegate_to :setup_any_instance_allowance + disallow_negation :not_to + disallow_negation :to_not + end + + # @private + class AnyInstanceExpectationTarget < TargetBase + def expression + :expect_any_instance_of + end + + delegate_to :setup_any_instance_expectation + delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to + delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/test_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/test_double.rb new file mode 100644 index 0000000000..e8bfbeb59d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/test_double.rb @@ -0,0 +1,171 @@ +module RSpec + module Mocks + # Implements the methods needed for a pure test double. RSpec::Mocks::Double + # includes this module, and it is provided for cases where you want a + # pure test double without subclassing RSpec::Mocks::Double. + module TestDouble + # Creates a new test double with a `name` (that will be used in error + # messages only) + def initialize(name=nil, stubs={}) + @__expired = false + if Hash === name && stubs.empty? + stubs = name + @name = nil + else + @name = name + end + assign_stubs(stubs) + end + + # Tells the object to respond to all messages. If specific stub values + # are declared, they'll work as expected. If not, the receiver is + # returned. + def as_null_object + __mock_proxy.as_null_object + end + + # Returns true if this object has received `as_null_object` + def null_object? + __mock_proxy.null_object? + end + + # This allows for comparing the mock to other objects that proxy such as + # ActiveRecords belongs_to proxy objects. By making the other object run + # the comparison, we're sure the call gets delegated to the proxy + # target. + def ==(other) + other == __mock_proxy + end + + # @private + def inspect + TestDoubleFormatter.format(self) + end + + # @private + def to_s + inspect.tr('<', '[').tr('>', ']') + end + + # @private + def respond_to?(message, incl_private=false) + __mock_proxy.null_object? ? true : super + end + + # @private + def __build_mock_proxy_unless_expired(order_group) + __raise_expired_error || __build_mock_proxy(order_group) + end + + # @private + def __disallow_further_usage! + @__expired = true + end + + # Override for default freeze implementation to prevent freezing of test + # doubles. + def freeze + RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.") + self + end + + private + + def method_missing(message, *args, &block) + proxy = __mock_proxy + proxy.record_message_received(message, *args, &block) + + if proxy.null_object? + case message + when :to_int then return 0 + when :to_a, :to_ary then return nil + when :to_str then return to_s + else return self + end + end + + # Defined private and protected methods will still trigger `method_missing` + # when called publicly. We want ruby's method visibility error to get raised, + # so we simply delegate to `super` in that case. + # ...well, we would delegate to `super`, but there's a JRuby + # bug, so we raise our own visibility error instead: + # https://github.com/jruby/jruby/issues/1398 + visibility = proxy.visibility_for(message) + if visibility == :private || visibility == :protected + ErrorGenerator.new(self).raise_non_public_error( + message, visibility + ) + end + + # Required wrapping doubles in an Array on Ruby 1.9.2 + raise NoMethodError if [:to_a, :to_ary].include? message + proxy.raise_unexpected_message_error(message, args) + end + + def assign_stubs(stubs) + stubs.each_pair do |message, response| + __mock_proxy.add_simple_stub(message, response) + end + end + + def __mock_proxy + ::RSpec::Mocks.space.proxy_for(self) + end + + def __build_mock_proxy(order_group) + TestDoubleProxy.new(self, order_group) + end + + def __raise_expired_error + return false unless @__expired + ErrorGenerator.new(self).raise_expired_test_double_error + end + + def initialize_copy(other) + as_null_object if other.null_object? + super + end + end + + # A generic test double object. `double`, `instance_double` and friends + # return an instance of this. + class Double + include TestDouble + end + + # @private + module TestDoubleFormatter + def self.format(dbl, unwrap=false) + format = "#{type_desc(dbl)}#{verified_module_desc(dbl)} #{name_desc(dbl)}" + return format if unwrap + "#<#{format}>" + end + + class << self + private + + def type_desc(dbl) + case dbl + when InstanceVerifyingDouble then "InstanceDouble" + when ClassVerifyingDouble then "ClassDouble" + when ObjectVerifyingDouble then "ObjectDouble" + else "Double" + end + end + + # @private + IVAR_GET = Object.instance_method(:instance_variable_get) + + def verified_module_desc(dbl) + return nil unless VerifyingDouble === dbl + "(#{IVAR_GET.bind(dbl).call(:@doubled_module).description})" + end + + def name_desc(dbl) + return "(anonymous)" unless (name = IVAR_GET.bind(dbl).call(:@name)) + name.inspect + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_double.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_double.rb new file mode 100644 index 0000000000..31f7c6e21d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_double.rb @@ -0,0 +1,123 @@ +RSpec::Support.require_rspec_mocks 'verifying_proxy' + +module RSpec + module Mocks + # @private + module VerifyingDouble + def respond_to?(message, include_private=false) + return super unless null_object? + + method_ref = __mock_proxy.method_reference[message] + + case method_ref.visibility + when :public then true + when :private then include_private + when :protected then include_private || RUBY_VERSION.to_f < 2.0 + else !method_ref.unimplemented? + end + end + + def method_missing(message, *args, &block) + # Null object conditional is an optimization. If not a null object, + # validity of method expectations will have been checked at definition + # time. + if null_object? + if @__sending_message == message + __mock_proxy.ensure_implemented(message) + else + __mock_proxy.ensure_publicly_implemented(message, self) + end + + __mock_proxy.validate_arguments!(message, args) + end + + super + end + + # Redefining `__send__` causes ruby to issue a warning. + old, $VERBOSE = $VERBOSE, nil + def __send__(name, *args, &block) + @__sending_message = name + super + ensure + @__sending_message = nil + end + ruby2_keywords :__send__ if respond_to?(:ruby2_keywords, true) + $VERBOSE = old + + def send(name, *args, &block) + __send__(name, *args, &block) + end + ruby2_keywords :send if respond_to?(:ruby2_keywords, true) + + def initialize(doubled_module, *args) + @doubled_module = doubled_module + + possible_name = args.first + name = if String === possible_name || Symbol === possible_name + args.shift + end + + super(name, *args) + @__sending_message = nil + end + end + + # A mock providing a custom proxy that can verify the validity of any + # method stubs or expectations against the public instance methods of the + # given class. + # + # @private + class InstanceVerifyingDouble + include TestDouble + include VerifyingDouble + + def __build_mock_proxy(order_group) + VerifyingProxy.new(self, order_group, + @doubled_module, + InstanceMethodReference + ) + end + end + + # An awkward module necessary because we cannot otherwise have + # ClassVerifyingDouble inherit from Module and still share these methods. + # + # @private + module ObjectVerifyingDoubleMethods + include TestDouble + include VerifyingDouble + + def as_stubbed_const(options={}) + ConstantMutator.stub(@doubled_module.const_to_replace, self, options) + self + end + + private + + def __build_mock_proxy(order_group) + VerifyingProxy.new(self, order_group, + @doubled_module, + ObjectMethodReference + ) + end + end + + # Similar to an InstanceVerifyingDouble, except that it verifies against + # public methods of the given object. + # + # @private + class ObjectVerifyingDouble + include ObjectVerifyingDoubleMethods + end + + # Effectively the same as an ObjectVerifyingDouble (since a class is a type + # of object), except with Module in the inheritance chain so that + # transferring nested constants to work. + # + # @private + class ClassVerifyingDouble < Module + include ObjectVerifyingDoubleMethods + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_message_expectation.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_message_expectation.rb new file mode 100644 index 0000000000..d6dcb5744d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_message_expectation.rb @@ -0,0 +1,55 @@ +RSpec::Support.require_rspec_support 'method_signature_verifier' + +module RSpec + module Mocks + # A message expectation that knows about the real implementation of the + # message being expected, so that it can verify that any expectations + # have the valid arguments. + # @api private + class VerifyingMessageExpectation < MessageExpectation + # A level of indirection is used here rather than just passing in the + # method itself, since method look up is expensive and we only want to + # do it if actually needed. + # + # Conceptually the method reference makes more sense as a constructor + # argument since it should be immutable, but it is significantly more + # straight forward to build the object in pieces so for now it stays as + # an accessor. + attr_accessor :method_reference + + def initialize(*args) + super + end + + # @private + def with(*args, &block) + super(*args, &block).tap do + validate_expected_arguments! do |signature| + example_call_site_args = [:an_arg] * signature.min_non_kw_args + example_call_site_args << :kw_args_hash if signature.required_kw_args.any? + @argument_list_matcher.resolve_expected_args_based_on(example_call_site_args) + end + end + end + ruby2_keywords(:with) if respond_to?(:ruby2_keywords, true) + + private + + def validate_expected_arguments! + return if method_reference.nil? + + method_reference.with_signature do |signature| + args = yield signature + verifier = Support::LooseSignatureVerifier.new(signature, args) + + unless verifier.valid? + # Fail fast is required, otherwise the message expectation will fail + # as well ("expected method not called") and clobber this one. + @failed_fast = true + @error_generator.raise_invalid_arguments_error(verifier) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_proxy.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_proxy.rb new file mode 100644 index 0000000000..6147b819e6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/verifying_proxy.rb @@ -0,0 +1,221 @@ +RSpec::Support.require_rspec_mocks 'verifying_message_expectation' +RSpec::Support.require_rspec_mocks 'method_reference' + +module RSpec + module Mocks + # @private + class CallbackInvocationStrategy + def call(doubled_module) + RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| + block.call doubled_module + end + end + end + + # @private + class NoCallbackInvocationStrategy + def call(_doubled_module) + end + end + + # @private + module VerifyingProxyMethods + def add_stub(method_name, opts={}, &implementation) + ensure_implemented(method_name) + super + end + + def add_simple_stub(method_name, *args) + ensure_implemented(method_name) + super + end + + def add_message_expectation(method_name, opts={}, &block) + ensure_implemented(method_name) + super + end + + def ensure_implemented(method_name) + return unless method_reference[method_name].unimplemented? + + @error_generator.raise_unimplemented_error( + @doubled_module, + method_name, + @object + ) + end + + def ensure_publicly_implemented(method_name, _object) + ensure_implemented(method_name) + visibility = method_reference[method_name].visibility + + return if visibility == :public + @error_generator.raise_non_public_error(method_name, visibility) + end + end + + # A verifying proxy mostly acts like a normal proxy, except that it + # contains extra logic to try and determine the validity of any expectation + # set on it. This includes whether or not methods have been defined and the + # validity of arguments on method calls. + # + # In all other ways this behaves like a normal proxy. It only adds the + # verification behaviour to specific methods then delegates to the parent + # implementation. + # + # These checks are only activated if the doubled class has already been + # loaded, otherwise they are disabled. This allows for testing in + # isolation. + # + # @private + class VerifyingProxy < TestDoubleProxy + include VerifyingProxyMethods + + def initialize(object, order_group, doubled_module, method_reference_class) + super(object, order_group) + @object = object + @doubled_module = doubled_module + @method_reference_class = method_reference_class + + # A custom method double is required to pass through a way to lookup + # methods to determine their parameters. This is only relevant if the doubled + # class is loaded. + @method_doubles = Hash.new do |h, k| + h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k]) + end + end + + def method_reference + @method_reference ||= Hash.new do |h, k| + h[k] = @method_reference_class.for(@doubled_module, k) + end + end + + def visibility_for(method_name) + method_reference[method_name].visibility + end + + def validate_arguments!(method_name, args) + @method_doubles[method_name].validate_arguments!(args) + end + end + + # @private + DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new + + # @private + class VerifyingPartialDoubleProxy < PartialDoubleProxy + include VerifyingProxyMethods + + def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY) + super(object, expectation_ordering) + @doubled_module = DirectObjectReference.new(object) + + # A custom method double is required to pass through a way to lookup + # methods to determine their parameters. + @method_doubles = Hash.new do |h, k| + h[k] = VerifyingExistingMethodDouble.for(object, k, self) + end + + optional_callback_invocation_strategy.call(@doubled_module) + end + + def ensure_implemented(_method_name) + return if Mocks.configuration.temporarily_suppress_partial_double_verification + super + end + + def method_reference + @method_doubles + end + end + + # @private + class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy + include PartialClassDoubleProxyMethods + end + + # @private + class VerifyingMethodDouble < MethodDouble + def initialize(object, method_name, proxy, method_reference) + super(object, method_name, proxy) + @method_reference = method_reference + end + + def message_expectation_class + VerifyingMessageExpectation + end + + def add_expectation(*args, &block) + # explicit params necessary for 1.8.7 see #626 + super(*args, &block).tap { |x| x.method_reference = @method_reference } + end + + def add_stub(*args, &block) + # explicit params necessary for 1.8.7 see #626 + super(*args, &block).tap { |x| x.method_reference = @method_reference } + end + + def proxy_method_invoked(obj, *args, &block) + validate_arguments!(args) + super + end + ruby2_keywords :proxy_method_invoked if respond_to?(:ruby2_keywords, true) + + def validate_arguments!(actual_args) + @method_reference.with_signature do |signature| + verifier = Support::StrictSignatureVerifier.new(signature, actual_args) + raise ArgumentError, verifier.error_message unless verifier.valid? + end + end + end + + # A VerifyingMethodDouble fetches the method to verify against from the + # original object, using a MethodReference. This works for pure doubles, + # but when the original object is itself the one being modified we need to + # collapse the reference and the method double into a single object so that + # we can access the original pristine method definition. + # + # @private + class VerifyingExistingMethodDouble < VerifyingMethodDouble + def initialize(object, method_name, proxy) + super(object, method_name, proxy, self) + + @valid_method = object.respond_to?(method_name, true) + + # Trigger an eager find of the original method since if we find it any + # later we end up getting a stubbed method with incorrect arity. + save_original_implementation_callable! + end + + def with_signature + yield Support::MethodSignature.new(original_implementation_callable) + end + + def unimplemented? + !@valid_method + end + + def self.for(object, method_name, proxy) + if ClassNewMethodReference.applies_to?(method_name) { object } + VerifyingExistingClassNewMethodDouble + elsif Mocks.configuration.temporarily_suppress_partial_double_verification + MethodDouble + else + self + end.new(object, method_name, proxy) + end + end + + # Used in place of a `VerifyingExistingMethodDouble` for the specific case + # of mocking or stubbing a `new` method on a class. In this case, we substitute + # the method signature from `#initialize` since new's signature is just `*args`. + # + # @private + class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble + def with_signature + yield Support::MethodSignature.new(object.instance_method(:initialize)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/version.rb new file mode 100644 index 0000000000..8bb319500c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-mocks-3.12.3/lib/rspec/mocks/version.rb @@ -0,0 +1,9 @@ +module RSpec + module Mocks + # Version information for RSpec mocks. + module Version + # Version of RSpec mocks currently in use in SemVer format. + STRING = '3.12.3' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/Changelog.md new file mode 100644 index 0000000000..9b56b6b3dc --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/Changelog.md @@ -0,0 +1,332 @@ +### 3.10.2 / 2021-01-28 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.1...v3.10.2) + +Bug Fixes: + +* Fix issue with `RSpec::Support.define_optimized_require_for_rspec` on JRuby + 9.1.17.0 (Jon Rowe, #492) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Fix deprecation expectations to fail correctly when + asserting on messages. (Phil Pirozhkov, #453) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.4...v3.10.0) + +No changes. Released to support other RSpec releases. + +### 3.9.4 / 2020-10-23 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.3...v3.9.4) + +Bug Fixes: + +* Flag ripper as supported on Truffle Ruby. (Brandon Fish, #427) +* Prevent stubbing `File.read` from breaking source extraction. + (Jon Rowe, #431) + +### 3.9.3 / 2020-05-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.2...v3.9.3) + +Bug Fixes: + +* Mark ripper as unsupported on Truffle Ruby. (Brandon Fish, #395) +* Mark ripper as unsupported on JRuby 9.2.0.0. (Brian Hawley, #400) +* Capture `Mutex.new` for our `RSpec::Support:Mutex` in order to + allow stubbing `Mutex.new`. (Jon Rowe, #411) + +### 3.9.2 / 2019-12-30 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Remove unneeded eval. (Matijs van Zuijlen, #394) + +### 3.9.1 / 2019-12-28 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Remove warning caused by keyword arguments on Ruby 2.7.0. + (Jon Rowe, #392) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.3...v3.9.0) + +*NO CHANGES* + +Version 3.9.0 was released to allow other RSpec gems to release 3.9.0. + +### 3.8.3 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.2...v3.8.3) + +Bug Fixes: + +* Escape \r when outputting strings inside arrays. + (Tomita Masahiro, Jon Rowe, #378) +* Ensure that optional hash arguments are recognised correctly vs keyword + arguments. (Evgeni Dzhelyov, #366) + +### 3.8.2 / 2019-06-10 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Ensure that an empty hash is recognised as empty keyword arguments when + applicable. (Thomas Walpole, #375) +* Ensure that diffing truthy values produce diffs consistently. + (Lucas Nestor, #377) + +### 3.8.1 / 2019-03-03 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Ensure that inspecting a `SimpleDelegator` based object works regardless of + visibilty of the `__getobj__` method. (Jon Rowe, #369) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.7.1...v3.8.0) + +Bug Fixes: + +* Order hash keys before diffing to improve diff accuracy when using mocked calls. + (James Crisp, #334) + +### 3.7.1 / 2018-01-29 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.7.0...v3.7.1) + +Bug Fixes: + +* Fix source extraction logic so that it does not trigger a `SystemStackError` + when processing deeply nested example groups. (Craig Bass, #343) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #320) +* Add `Support.class_of` for extracting class of any object. + (Yuji Nakayama, #325) + +Bug Fixes: + +* Fix recursive const support to not blow up when given buggy classes + that raise odd errors from `#to_str`. (Myron Marston, #317) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0.beta2...3.6.0) + +Enhancements: + +* Import `Source` classes from rspec-core. (Yuji Nakayama, #315) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0.beta1...v3.6.0.beta2) + +No user-facing changes. + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Prevent truncated formatted object output from mangling console codes. (#294, Anson Kelly) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta4...v3.5.0) + +**No user facing changes since beta4** + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: +* Improve `MethodSignature` to better support keyword arguments. (#250, Rob Smith). + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta2...v3.5.0.beta3) + +Bug Fixes: + +* Fix `EncodedString` to properly handle the behavior of `String#split` + on JRuby when the string contains invalid bytes. (Jon Rowe, #268) +* Fix `ObjectFormatter` so that formatting objects that don't respond to + `#inspect` (such as `BasicObject`) does not cause `NoMethodError`. + (Yuji Nakayama, #269) +* Fix `ObjectFormatter` so that formatting recursive array or hash does not + cause `SystemStackError`. (Yuji Nakayama, #270, #272) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta1...v3.5.0.beta2) + +No user-facing changes. + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.4.1...v3.5.0.beta1) + +Enhancements: + +* Improve formatting of objects by allowing truncation to a pre-configured length. + (Liam M, #256) + +### 3.4.1 / 2015-11-20 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix `RSpec::Support::RubyFeature.ripper_supported?` so it returns + `false` on Rubinius since the Rubinius team has no plans to support + it. This prevents rspec-core from trying to load and use ripper to + extract failure snippets. (Aaron Stone, #251) + +Changes: + +* Remove `VersionChecker` in favor of `ComparableVersion`. (Yuji Nakayama, #266) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.3.0...v3.4.0) + +Enhancements: + +* Improve formatting of `Delegator` based objects (e.g. `SimpleDelgator`) in + failure messages and diffs. (Andrew Horner, #215) +* Add `ComparableVersion`. (Yuji Nakayama, #245) +* Add `Ripper` support detection. (Yuji Nakayama, #245) + +Bug Fixes: + +* Work around bug in JRuby that reports that `attr_writer` methods + have no parameters, causing RSpec's verifying doubles to wrongly + fail when mocking or stubbing a writer method on JRuby. (Myron Marston, #225) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.2...v3.3.0) + +Enhancements: + +* Improve formatting of arrays and hashes in failure messages so they + use our custom formatting of matchers, time objects, etc. + (Myron Marston, Nicholas Chmielewski, #205) +* Use improved formatting for diffs as well. (Nicholas Chmielewski, #205) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it checks `expected == actual` rather than + `actual == expected`, which avoids errors in situations where the + `actual` object's `==` is improperly implemented to assume that only + objects of the same type will be given. This allows rspec-mocks' + `anything` to match against objects with buggy `==` definitions. + (Myron Marston, #193) + +### 3.2.2 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.1...v3.2.2) + +Bug Fixes: + +* Fix an encoding issue with `EncodedString#split` when encountering an + invalid byte string. (Benjamin Fleischer, #1760) + +### 3.2.1 / 2015-02-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Fix `RSpec::CallerFilter` to work on Rubinius 2.2. + (Myron Marston, #169) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.2...v3.2.0) + +Enhancements: + +* Add extra Ruby type detection. (Jon Rowe, #133) +* Make differ instance re-usable. (Alexey Fedorov, #160) + +Bug Fixes: + +* Do not consider `[]` and `{}` to match when performing fuzzy matching. + (Myron Marston, #157) + +### 3.1.2 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix method signature to not blow up with a `NoMethodError` on 1.8.7 when + verifying against an RSpec matcher. (Myron Marston, #116) + +### 3.1.1 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix `RSpec::Support::DirectoryMaker` (used by `rspec --init` and + `rails generate rspec:install`) so that it detects absolute paths + on Windows properly. (Scott Archer, #107, #108, #109) (Jon Rowe, #110) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.4...v3.1.0) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it does not wrongly match a struct against + an array. (Myron Marston, #97) +* Prevent infinitely recursing `#flatten` methods from causing the differ + to hang. (Jon Rowe, #101) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it does not silence `ArgumentError` raised + from broken implementations of `==`. (Myron Marston, #94) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Fix regression in `Support#method_handle_for` where proxy objects + with method delegated would wrongly not return a method handle. + (Jon Rowe, #90) +* Properly detect Module#prepend support in Ruby 2.1+ (Ben Langfeld, #91) +* Fix `rspec/support/warnings.rb` so it can be loaded and used in + isolation. (Myron Marston, #93) + +### 3.0.2 / 2014-06-20 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.1...v3.0.2) + +* Revert `BlockSignature` change from 3.0.1 because of a ruby bug that + caused it to change the block's behavior (https://bugs.ruby-lang.org/issues/9967). + (Myron Marston, rspec-mocks#721) + +### 3.0.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0...v3.0.1) + +* Fix `BlockSignature` so that it correctly differentiates between + required and optional block args. (Myron Marston, rspec-mocks#714) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.rc1...v3.0.0) + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.beta2...v3.0.0.rc1) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.beta1...v3.0.0.beta2) + +Bug Fixes: + +* Issue message when :replacement is passed to `RSpec.warn_with`. (Jon Rowe) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](https://github.com/rspec/rspec-support/compare/0dc12d1bdbbacc757a9989f8c09cd08ef3a4837e...v3.0.0.beta1) + +Initial release. diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/LICENSE.md new file mode 100644 index 0000000000..08aa3abbb5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/LICENSE.md @@ -0,0 +1,23 @@ +The MIT License (MIT) +==================== + +* Copyright © 2013 David Chelimsky, Myron Marston, Jon Rowe, Sam Phippen, Xavier Shay, Bradley Schaefer + +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.0.0/gems/rspec-support-3.10.2/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/README.md new file mode 100644 index 0000000000..2cc9a2a509 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/README.md @@ -0,0 +1,40 @@ +# RSpec::Support [![Build Status](https://github.com/rspec/rspec-support/workflows/RSpec%20CI/badge.svg?branch=3-10-maintenance)](https://github.com/rspec/rspec-support/actions) + +`RSpec::Support` provides common functionality to `RSpec::Core`, +`RSpec::Expectations` and `RSpec::Mocks`. It is considered +suitable for internal use only at this time. + +## Installation / Usage + +Install one or more of the `RSpec` gems. + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +- [Build details](BUILD_DETAIL.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Detailed contributing guide](CONTRIBUTING.md) +- [Development setup guide](DEVELOPMENT.md) + +## Patches + +Please submit a pull request or a github issue. If you submit an issue, please +include a link to either of: + +* a gist (or equivalent) of the patch +* a branch or commit in your github fork of the repo diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support.rb new file mode 100644 index 0000000000..b85eae3c25 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support.rb @@ -0,0 +1,154 @@ +module RSpec + module Support + # @api private + # + # Defines a helper method that is optimized to require files from the + # named lib. The passed block MUST be `{ |f| require_relative f }` + # because for `require_relative` to work properly from within the named + # lib the line of code must be IN that lib. + # + # `require_relative` is preferred when available because it is always O(1), + # regardless of the number of dirs in $LOAD_PATH. `require`, on the other + # hand, does a linear O(N) search over the dirs in the $LOAD_PATH until + # it can resolve the file relative to one of the dirs. + def self.define_optimized_require_for_rspec(lib, &require_relative) + name = "require_rspec_#{lib}" + + if RUBY_PLATFORM == 'java' && !Kernel.respond_to?(:require) + # JRuby 9.1.17.0 has developed a regression for require + (class << self; self; end).__send__(:define_method, name) do |f| + Kernel.send(:require, "rspec/#{lib}/#{f}") + end + elsif Kernel.respond_to?(:require_relative) + (class << self; self; end).__send__(:define_method, name) do |f| + require_relative.call("#{lib}/#{f}") + end + else + (class << self; self; end).__send__(:define_method, name) do |f| + require "rspec/#{lib}/#{f}" + end + end + end + + define_optimized_require_for_rspec(:support) { |f| require_relative(f) } + require_rspec_support "version" + require_rspec_support "ruby_features" + + # @api private + KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method) + + # @api private + # + # Used internally to get a method handle for a particular object + # and method name. + # + # Includes handling for a few special cases: + # + # - Objects that redefine #method (e.g. an HTTPRequest struct) + # - BasicObject subclasses that mixin a Kernel dup (e.g. SimpleDelegator) + # - Objects that undefine method and delegate everything to another + # object (e.g. Mongoid association objects) + if RubyFeatures.supports_rebinding_module_methods? + def self.method_handle_for(object, method_name) + KERNEL_METHOD_METHOD.bind(object).call(method_name) + rescue NameError => original + begin + handle = object.method(method_name) + raise original unless handle.is_a? Method + handle + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + raise original + end + end + else + def self.method_handle_for(object, method_name) + if ::Kernel === object + KERNEL_METHOD_METHOD.bind(object).call(method_name) + else + object.method(method_name) + end + rescue NameError => original + begin + handle = object.method(method_name) + raise original unless handle.is_a? Method + handle + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + raise original + end + end + end + + # @api private + # + # Used internally to get a class of a given object, even if it does not respond to #class. + def self.class_of(object) + object.class + rescue NoMethodError + singleton_class = class << object; self; end + singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) } + end + + # A single thread local variable so we don't excessively pollute that namespace. + def self.thread_local_data + Thread.current[:__rspec] ||= {} + end + + # @api private + def self.failure_notifier=(callable) + thread_local_data[:failure_notifier] = callable + end + + # @private + DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure } + + # @api private + def self.failure_notifier + thread_local_data[:failure_notifier] || DEFAULT_FAILURE_NOTIFIER + end + + # @api private + def self.notify_failure(failure, options={}) + failure_notifier.call(failure, options) + end + + # @api private + def self.with_failure_notifier(callable) + orig_notifier = failure_notifier + self.failure_notifier = callable + yield + ensure + self.failure_notifier = orig_notifier + end + + class << self + # @api private + attr_writer :warning_notifier + end + + # @private + DEFAULT_WARNING_NOTIFIER = lambda { |warning| ::Kernel.warn warning } + + # @api private + def self.warning_notifier + @warning_notifier ||= DEFAULT_WARNING_NOTIFIER + end + + # @private + module AllExceptionsExceptOnesWeMustNotRescue + # These exceptions are dangerous to rescue as rescuing them + # would interfere with things we should not interfere with. + AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit] + + def self.===(exception) + AVOID_RESCUING.none? { |ar| ar === exception } + end + end + + # The Differ is only needed when a spec fails with a diffable failure. + # In the more common case of all specs passing or the only failures being + # non-diffable, we can avoid the extra cost of loading the differ, diff-lcs, + # pp, etc by avoiding an unnecessary require. Instead, autoload will take + # care of loading the differ on first use. + autoload :Differ, "rspec/support/differ" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/caller_filter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/caller_filter.rb new file mode 100644 index 0000000000..cd59a30f88 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/caller_filter.rb @@ -0,0 +1,83 @@ +RSpec::Support.require_rspec_support "ruby_features" + +module RSpec + # Consistent implementation for "cleaning" the caller method to strip out + # non-rspec lines. This enables errors to be reported at the call site in + # the code using the library, which is far more useful than the particular + # internal method that raised an error. + class CallerFilter + RSPEC_LIBS = %w[ + core + mocks + expectations + support + matchers + rails + ] + + ADDITIONAL_TOP_LEVEL_FILES = %w[ autorun ] + + LIB_REGEX = %r{/lib/rspec/(#{(RSPEC_LIBS + ADDITIONAL_TOP_LEVEL_FILES).join('|')})(\.rb|/)} + + # rubygems/core_ext/kernel_require.rb isn't actually part of rspec (obviously) but we want + # it ignored when we are looking for the first meaningful line of the backtrace outside + # of RSpec. It can show up in the backtrace as the immediate first caller + # when `CallerFilter.first_non_rspec_line` is called from the top level of a required + # file, but it depends on if rubygems is loaded or not. We don't want to have to deal + # with this complexity in our `RSpec.deprecate` calls, so we ignore it here. + IGNORE_REGEX = Regexp.union(LIB_REGEX, "rubygems/core_ext/kernel_require.rb") + + if RSpec::Support::RubyFeatures.caller_locations_supported? + # This supports args because it's more efficient when the caller specifies + # these. It allows us to skip frames the caller knows are part of RSpec, + # and to decrease the increment size if the caller is confident the line will + # be found in a small number of stack frames from `skip_frames`. + # + # Note that there is a risk to passing a `skip_frames` value that is too high: + # If it skippped the first non-rspec line, then this method would return the + # 2nd or 3rd (or whatever) non-rspec line. Thus, you generally shouldn't pass + # values for these parameters, particularly since most places that use this are + # not hot spots (generally it gets used for deprecation warnings). However, + # if you do have a hot spot that calls this, passing `skip_frames` can make + # a significant difference. Just make sure that that particular use is tested + # so that if the provided `skip_frames` changes to no longer be accurate in + # such a way that would return the wrong stack frame, a test will fail to tell you. + # + # See benchmarks/skip_frames_for_caller_filter.rb for measurements. + def self.first_non_rspec_line(skip_frames=3, increment=5) + # Why a default `skip_frames` of 3? + # By the time `caller_locations` is called below, the first 3 frames are: + # lib/rspec/support/caller_filter.rb:63:in `block in first_non_rspec_line' + # lib/rspec/support/caller_filter.rb:62:in `loop' + # lib/rspec/support/caller_filter.rb:62:in `first_non_rspec_line' + + # `caller` is an expensive method that scales linearly with the size of + # the stack. The performance hit for fetching it in chunks is small, + # and since the target line is probably near the top of the stack, the + # overall improvement of a chunked search like this is significant. + # + # See benchmarks/caller.rb for measurements. + + # The default increment of 5 for this method are mostly arbitrary, but + # is chosen to give good performance on the common case of creating a double. + + loop do + stack = caller_locations(skip_frames, increment) + raise "No non-lib lines in stack" unless stack + + line = stack.find { |l| l.path !~ IGNORE_REGEX } + return line.to_s if line + + skip_frames += increment + increment *= 2 # The choice of two here is arbitrary. + end + end + else + # Earlier rubies do not support the two argument form of `caller`. This + # fallback is logically the same, but slower. + def self.first_non_rspec_line(*) + caller.find { |line| line !~ IGNORE_REGEX } + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/comparable_version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/comparable_version.rb new file mode 100644 index 0000000000..2afa0a4f2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/comparable_version.rb @@ -0,0 +1,46 @@ +module RSpec + module Support + # @private + class ComparableVersion + include Comparable + + attr_reader :string + + def initialize(string) + @string = string + end + + def <=>(other) # rubocop:disable Metrics/AbcSize + other = self.class.new(other) unless other.is_a?(self.class) + + return 0 if string == other.string + + longer_segment_count = [self, other].map { |version| version.segments.count }.max + + longer_segment_count.times do |index| + self_segment = segments[index] || 0 + other_segment = other.segments[index] || 0 + + if self_segment.class == other_segment.class + result = self_segment <=> other_segment + return result unless result == 0 + else + return self_segment.is_a?(String) ? -1 : 1 + end + end + + 0 + end + + def segments + @segments ||= string.scan(/[a-z]+|\d+/i).map do |segment| + if segment =~ /\A\d+\z/ + segment.to_i + else + segment + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/differ.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/differ.rb new file mode 100644 index 0000000000..b81184239b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/differ.rb @@ -0,0 +1,215 @@ +RSpec::Support.require_rspec_support 'encoded_string' +RSpec::Support.require_rspec_support 'hunk_generator' +RSpec::Support.require_rspec_support "object_formatter" + +require 'pp' + +module RSpec + module Support + # rubocop:disable ClassLength + class Differ + def diff(actual, expected) + diff = "" + + unless actual.nil? || expected.nil? + if all_strings?(actual, expected) + if any_multiline_strings?(actual, expected) + diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected)) + end + elsif no_procs?(actual, expected) && no_numbers?(actual, expected) + diff = diff_as_object(actual, expected) + end + end + + diff.to_s + end + + # rubocop:disable MethodLength + def diff_as_string(actual, expected) + encoding = EncodedString.pick_encoding(actual, expected) + + actual = EncodedString.new(actual, encoding) + expected = EncodedString.new(expected, encoding) + + output = EncodedString.new("\n", encoding) + hunks = build_hunks(actual, expected) + + hunks.each_cons(2) do |prev_hunk, current_hunk| + begin + if current_hunk.overlaps?(prev_hunk) + add_old_hunk_to_hunk(current_hunk, prev_hunk) + else + add_to_output(output, prev_hunk.diff(format_type).to_s) + end + ensure + add_to_output(output, "\n") + end + end + + finalize_output(output, hunks.last.diff(format_type).to_s) if hunks.last + + color_diff output + rescue Encoding::CompatibilityError + handle_encoding_errors(actual, expected) + end + # rubocop:enable MethodLength + + def diff_as_object(actual, expected) + actual_as_string = object_to_string(actual) + expected_as_string = object_to_string(expected) + diff_as_string(actual_as_string, expected_as_string) + end + + def color? + @color + end + + def initialize(opts={}) + @color = opts.fetch(:color, false) + @object_preparer = opts.fetch(:object_preparer, lambda { |string| string }) + end + + private + + def no_procs?(*args) + safely_flatten(args).none? { |a| Proc === a } + end + + def all_strings?(*args) + safely_flatten(args).all? { |a| String === a } + end + + def any_multiline_strings?(*args) + all_strings?(*args) && safely_flatten(args).any? { |a| multiline?(a) } + end + + def no_numbers?(*args) + safely_flatten(args).none? { |a| Numeric === a } + end + + def coerce_to_string(string_or_array) + return string_or_array unless Array === string_or_array + diffably_stringify(string_or_array).join("\n") + end + + def diffably_stringify(array) + array.map do |entry| + if Array === entry + entry.inspect + else + entry.to_s.gsub("\n", "\\n").gsub("\r", "\\r") + end + end + end + + if String.method_defined?(:encoding) + def multiline?(string) + string.include?("\n".encode(string.encoding)) + end + else + def multiline?(string) + string.include?("\n") + end + end + + def build_hunks(actual, expected) + HunkGenerator.new(actual, expected).hunks + end + + def finalize_output(output, final_line) + add_to_output(output, final_line) + add_to_output(output, "\n") + end + + def add_to_output(output, string) + output << string + end + + def add_old_hunk_to_hunk(hunk, oldhunk) + hunk.merge(oldhunk) + end + + def safely_flatten(array) + array = array.flatten(1) until (array == array.flatten(1)) + array + end + + def format_type + :unified + end + + def color(text, color_code) + "\e[#{color_code}m#{text}\e[0m" + end + + def red(text) + color(text, 31) + end + + def green(text) + color(text, 32) + end + + def blue(text) + color(text, 34) + end + + def normal(text) + color(text, 0) + end + + def color_diff(diff) + return diff unless color? + + diff.lines.map do |line| + case line[0].chr + when "+" + green line + when "-" + red line + when "@" + line[1].chr == "@" ? blue(line) : normal(line) + else + normal(line) + end + end.join + end + + def object_to_string(object) + object = @object_preparer.call(object) + case object + when Hash + hash_to_string(object) + when Array + PP.pp(ObjectFormatter.prepare_for_inspection(object), "".dup) + when String + object =~ /\n/ ? object : object.inspect + else + PP.pp(object, "".dup) + end + end + + def hash_to_string(hash) + formatted_hash = ObjectFormatter.prepare_for_inspection(hash) + formatted_hash.keys.sort_by { |k| k.to_s }.map do |key| + pp_key = PP.singleline_pp(key, "".dup) + pp_value = PP.singleline_pp(formatted_hash[key], "".dup) + + "#{pp_key} => #{pp_value}," + end.join("\n") + end + + def handle_encoding_errors(actual, expected) + if actual.source_encoding != expected.source_encoding + "Could not produce a diff because the encoding of the actual string " \ + "(#{actual.source_encoding}) differs from the encoding of the expected " \ + "string (#{expected.source_encoding})" + else + "Could not produce a diff because of the encoding of the string " \ + "(#{expected.source_encoding})" + end + end + end + # rubocop:enable ClassLength + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/directory_maker.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/directory_maker.rb new file mode 100644 index 0000000000..39a280e3ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/directory_maker.rb @@ -0,0 +1,63 @@ +RSpec::Support.require_rspec_support 'ruby_features' + +module RSpec + module Support + # @api private + # + # Replacement for fileutils#mkdir_p because we don't want to require parts + # of stdlib in RSpec. + class DirectoryMaker + # @api private + # + # Implements nested directory construction + def self.mkdir_p(path) + stack = generate_stack(path) + path.split(File::SEPARATOR).each do |part| + stack = generate_path(stack, part) + begin + Dir.mkdir(stack) unless directory_exists?(stack) + rescue Errno::EEXIST => e + raise e unless directory_exists?(stack) + rescue Errno::ENOTDIR => e + raise Errno::EEXIST, e.message + end + end + end + + if OS.windows_file_path? + def self.generate_stack(path) + if path.start_with?(File::SEPARATOR) + File::SEPARATOR + elsif path[1] == ':' + '' + else + '.' + end + end + def self.generate_path(stack, part) + if stack == '' + part + elsif stack == File::SEPARATOR + File.join('', part) + else + File.join(stack, part) + end + end + else + def self.generate_stack(path) + path.start_with?(File::SEPARATOR) ? File::SEPARATOR : "." + end + def self.generate_path(stack, part) + File.join(stack, part) + end + end + + def self.directory_exists?(dirname) + File.exist?(dirname) && File.directory?(dirname) + end + private_class_method :directory_exists? + private_class_method :generate_stack + private_class_method :generate_path + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/encoded_string.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/encoded_string.rb new file mode 100644 index 0000000000..13c323503d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/encoded_string.rb @@ -0,0 +1,161 @@ +module RSpec + module Support + # @private + class EncodedString + # Reduce allocations by storing constants. + UTF_8 = "UTF-8" + US_ASCII = "US-ASCII" + + # Ruby's default replacement string is: + # U+FFFD ("\xEF\xBF\xBD"), for Unicode encoding forms, else + # ? ("\x3F") + REPLACE = "?" + + def initialize(string, encoding=nil) + @encoding = encoding + @source_encoding = detect_source_encoding(string) + @string = matching_encoding(string) + end + attr_reader :source_encoding + + delegated_methods = String.instance_methods.map(&:to_s) & %w[eql? lines == encoding empty?] + delegated_methods.each do |name| + define_method(name) { |*args, &block| @string.__send__(name, *args, &block) } + end + + def <<(string) + @string << matching_encoding(string) + end + + if Ruby.jruby? + def split(regex_or_string) + @string.split(matching_encoding(regex_or_string)) + rescue ArgumentError + # JRuby raises an ArgumentError when splitting a source string that + # contains invalid bytes. + remove_invalid_bytes(@string).split regex_or_string + end + else + def split(regex_or_string) + @string.split(matching_encoding(regex_or_string)) + end + end + + def to_s + @string + end + alias :to_str :to_s + + if String.method_defined?(:encoding) + + private + + # Encoding Exceptions: + # + # Raised by Encoding and String methods: + # Encoding::UndefinedConversionError: + # when a transcoding operation fails + # if the String contains characters invalid for the target encoding + # e.g. "\x80".encode('UTF-8','ASCII-8BIT') + # vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '') + # # => '' + # Encoding::CompatibilityError + # when Encoding.compatibile?(str1, str2) is nil + # e.g. utf_16le_emoji_string.split("\n") + # e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string + # Encoding::InvalidByteSequenceError: + # when the string being transcoded contains a byte invalid for + # either the source or target encoding + # e.g. "\x80".encode('UTF-8','US-ASCII') + # vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '') + # # => '' + # ArgumentError + # when operating on a string with invalid bytes + # e.g."\x80".split("\n") + # TypeError + # when a symbol is passed as an encoding + # Encoding.find(:"UTF-8") + # when calling force_encoding on an object + # that doesn't respond to #to_str + # + # Raised by transcoding methods: + # Encoding::ConverterNotFoundError: + # when a named encoding does not correspond with a known converter + # e.g. 'abc'.force_encoding('UTF-8').encode('foo') + # or a converter path cannot be found + # e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule') + # + # Raised by byte <-> char conversions + # RangeError: out of char range + # e.g. the UTF-16LE emoji: 128169.chr + def matching_encoding(string) + string = remove_invalid_bytes(string) + string.encode(@encoding) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError + # Originally defined as a constant to avoid uneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 + # + # In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence + # see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176 + # https://www.ruby-forum.com/topic/6861247 + # https://twitter.com/nalsh/status/553413844685438976 + # + # For example, given: + # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a + # + # On MRI 2.1 or above: 63 # '?' + # else : 128 # "\x80" + # + string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) + rescue Encoding::ConverterNotFoundError + # Originally defined as a constant to avoid uneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 + string.dup.force_encoding(@encoding).encode(:invalid => :replace, :replace => REPLACE) + end + + # Prevents raising ArgumentError + if String.method_defined?(:scrub) + # https://github.com/ruby/ruby/blob/eeb05e8c11/doc/NEWS-2.1.0#L120-L123 + # https://github.com/ruby/ruby/blob/v2_1_0/string.c#L8242 + # https://github.com/hsbt/string-scrub + # https://github.com/rubinius/rubinius/blob/v2.5.2/kernel/common/string.rb#L1913-L1972 + def remove_invalid_bytes(string) + string.scrub(REPLACE) + end + else + # http://stackoverflow.com/a/8711118/879854 + # Loop over chars in a string replacing chars + # with invalid encoding, which is a pretty good proxy + # for the invalid byte sequence that causes an ArgumentError + def remove_invalid_bytes(string) + string.chars.map do |char| + char.valid_encoding? ? char : REPLACE + end.join + end + end + + def detect_source_encoding(string) + string.encoding + end + + def self.pick_encoding(source_a, source_b) + Encoding.compatible?(source_a, source_b) || Encoding.default_external + end + else + + def self.pick_encoding(_source_a, _source_b) + end + + private + + def matching_encoding(string) + string + end + + def detect_source_encoding(_string) + US_ASCII + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/fuzzy_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/fuzzy_matcher.rb new file mode 100644 index 0000000000..4151949540 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/fuzzy_matcher.rb @@ -0,0 +1,48 @@ +module RSpec + module Support + # Provides a means to fuzzy-match between two arbitrary objects. + # Understands array/hash nesting. Uses `===` or `==` to + # perform the matching. + module FuzzyMatcher + # @api private + def self.values_match?(expected, actual) + if Hash === actual + return hashes_match?(expected, actual) if Hash === expected + elsif Array === expected && Enumerable === actual && !(Struct === actual) + return arrays_match?(expected, actual.to_a) + end + + return true if expected == actual + + begin + expected === actual + rescue ArgumentError + # Some objects, like 0-arg lambdas on 1.9+, raise + # ArgumentError for `expected === actual`. + false + end + end + + # @private + def self.arrays_match?(expected_list, actual_list) + return false if expected_list.size != actual_list.size + + expected_list.zip(actual_list).all? do |expected, actual| + values_match?(expected, actual) + end + end + + # @private + def self.hashes_match?(expected_hash, actual_hash) + return false if expected_hash.size != actual_hash.size + + expected_hash.all? do |expected_key, expected_value| + actual_value = actual_hash.fetch(expected_key) { return false } + values_match?(expected_value, actual_value) + end + end + + private_class_method :arrays_match?, :hashes_match? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/hunk_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/hunk_generator.rb new file mode 100644 index 0000000000..382579e83a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/hunk_generator.rb @@ -0,0 +1,47 @@ +require 'diff/lcs' +require 'diff/lcs/hunk' + +module RSpec + module Support + # @private + class HunkGenerator + def initialize(actual, expected) + @actual = actual + @expected = expected + end + + def hunks + @file_length_difference = 0 + @hunks ||= diffs.map do |piece| + build_hunk(piece) + end + end + + private + + def diffs + Diff::LCS.diff(expected_lines, actual_lines) + end + + def expected_lines + @expected.split("\n").map! { |e| e.chomp } + end + + def actual_lines + @actual.split("\n").map! { |e| e.chomp } + end + + def build_hunk(piece) + Diff::LCS::Hunk.new( + expected_lines, actual_lines, piece, context_lines, @file_length_difference + ).tap do |h| + @file_length_difference = h.file_length_difference + end + end + + def context_lines + 3 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/matcher_definition.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/matcher_definition.rb new file mode 100644 index 0000000000..d653cc11df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/matcher_definition.rb @@ -0,0 +1,42 @@ +module RSpec + module Support + # @private + def self.matcher_definitions + @matcher_definitions ||= [] + end + + # Used internally to break cyclic dependency between mocks, expectations, + # and support. We don't currently have a consistent implementation of our + # matchers, though we are considering changing that: + # https://github.com/rspec/rspec-mocks/issues/513 + # + # @private + def self.register_matcher_definition(&block) + matcher_definitions << block + end + + # Remove a previously registered matcher. Useful for cleaning up after + # yourself in specs. + # + # @private + def self.deregister_matcher_definition(&block) + matcher_definitions.delete(block) + end + + # @private + def self.is_a_matcher?(object) + matcher_definitions.any? { |md| md.call(object) } + end + + # @api private + # + # gives a string representation of an object for use in RSpec descriptions + def self.rspec_description_for_object(object) + if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description) + object.description + else + object + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/method_signature_verifier.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/method_signature_verifier.rb new file mode 100644 index 0000000000..16736224e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/method_signature_verifier.rb @@ -0,0 +1,438 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "ruby_features" +RSpec::Support.require_rspec_support "matcher_definition" + +module RSpec + module Support + # Extracts info about the number of arguments and allowed/required + # keyword args of a given method. + # + # @private + class MethodSignature # rubocop:disable ClassLength + attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args + + def initialize(method) + @method = method + @optional_kw_args = [] + @required_kw_args = [] + classify_parameters + end + + def non_kw_args_arity_description + case max_non_kw_args + when min_non_kw_args then min_non_kw_args.to_s + when INFINITY then "#{min_non_kw_args} or more" + else "#{min_non_kw_args} to #{max_non_kw_args}" + end + end + + def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count) + return true if positional_arg_count.nil? + + min_non_kw_args <= positional_arg_count && + optional_max_arg_count <= max_non_kw_args + end + + def classify_arity(arity=@method.arity) + if arity < 0 + # `~` inverts the one's complement and gives us the + # number of required args + @min_non_kw_args = ~arity + @max_non_kw_args = INFINITY + else + @min_non_kw_args = arity + @max_non_kw_args = arity + end + end + + if RubyFeatures.optional_and_splat_args_supported? + def description + @description ||= begin + parts = [] + + unless non_kw_args_arity_description == "0" + parts << "arity of #{non_kw_args_arity_description}" + end + + if @optional_kw_args.any? + parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})" + end + + if @required_kw_args.any? + parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})" + end + + parts << "any additional keyword args" if @allows_any_kw_args + + parts.join(" and ") + end + end + + def missing_kw_args_from(given_kw_args) + @required_kw_args - given_kw_args + end + + def invalid_kw_args_from(given_kw_args) + return [] if @allows_any_kw_args + given_kw_args - @allowed_kw_args + end + + # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments + # the rest will be grouped in another Hash and passed as positional argument. + def has_kw_args_in?(args) + Hash === args.last && + could_contain_kw_args?(args) && + (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) }) + end + + # Without considering what the last arg is, could it + # contain keyword arguments? + def could_contain_kw_args?(args) + return false if args.count <= min_non_kw_args + + @allows_any_kw_args || @allowed_kw_args.any? + end + + def arbitrary_kw_args? + @allows_any_kw_args + end + + def unlimited_args? + @max_non_kw_args == INFINITY + end + + def classify_parameters + optional_non_kw_args = @min_non_kw_args = 0 + @allows_any_kw_args = false + + @method.parameters.each do |(type, name)| + case type + # def foo(a:) + when :keyreq then @required_kw_args << name + # def foo(a: 1) + when :key then @optional_kw_args << name + # def foo(**kw_args) + when :keyrest then @allows_any_kw_args = true + # def foo(a) + when :req then @min_non_kw_args += 1 + # def foo(a = 1) + when :opt then optional_non_kw_args += 1 + # def foo(*a) + when :rest then optional_non_kw_args = INFINITY + end + end + + @max_non_kw_args = @min_non_kw_args + optional_non_kw_args + @allowed_kw_args = @required_kw_args + @optional_kw_args + end + else + def description + "arity of #{non_kw_args_arity_description}" + end + + def missing_kw_args_from(_given_kw_args) + [] + end + + def invalid_kw_args_from(_given_kw_args) + [] + end + + def has_kw_args_in?(_args) + false + end + + def could_contain_kw_args?(*) + false + end + + def arbitrary_kw_args? + false + end + + def unlimited_args? + false + end + + alias_method :classify_parameters, :classify_arity + end + + INFINITY = 1 / 0.0 + end + + if RSpec::Support::Ruby.jruby? + # JRuby has only partial support for UnboundMethod#parameters, so we fall back on using #arity + # https://github.com/jruby/jruby/issues/2816 and https://github.com/jruby/jruby/issues/2817 + if RubyFeatures.optional_and_splat_args_supported? && + Java::JavaLang::String.instance_method(:char_at).parameters == [] + + class MethodSignature < remove_const(:MethodSignature) + private + + def classify_parameters + super + if (arity = @method.arity) != 0 && @method.parameters.empty? + classify_arity(arity) + end + end + end + end + + # JRuby used to always report -1 arity for Java proxy methods. + # The workaround essentially makes use of Java's introspection to figure + # out matching methods (which could be more than one partly because Java + # supports multiple overloads, and partly because JRuby introduces + # aliases to make method names look more Rubyesque). If there is only a + # single match, we can use that methods arity directly instead of the + # default -1 arity. + # + # This workaround only works for Java proxy methods, and in order to + # support regular methods and blocks, we need to be careful about calling + # owner and java_class as they might not be available + if Java::JavaLang::String.instance_method(:char_at).arity == -1 + class MethodSignature < remove_const(:MethodSignature) + private + + def classify_parameters + super + return unless @method.arity == -1 + return unless @method.respond_to?(:owner) + return unless @method.owner.respond_to?(:java_class) + java_instance_methods = @method.owner.java_class.java_instance_methods + compatible_overloads = java_instance_methods.select do |java_method| + @method == @method.owner.instance_method(java_method.name) + end + if compatible_overloads.size == 1 + classify_arity(compatible_overloads.first.arity) + end + end + end + end + end + + # Encapsulates expectations about the number of arguments and + # allowed/required keyword args of a given method. + # + # @api private + class MethodSignatureExpectation + def initialize + @min_count = nil + @max_count = nil + @keywords = [] + + @expect_unlimited_arguments = false + @expect_arbitrary_keywords = false + end + + attr_reader :min_count, :max_count, :keywords + + attr_accessor :expect_unlimited_arguments, :expect_arbitrary_keywords + + def max_count=(number) + raise ArgumentError, 'must be a non-negative integer or nil' \ + unless number.nil? || (number.is_a?(Integer) && number >= 0) + + @max_count = number + end + + def min_count=(number) + raise ArgumentError, 'must be a non-negative integer or nil' \ + unless number.nil? || (number.is_a?(Integer) && number >= 0) + + @min_count = number + end + + def empty? + @min_count.nil? && + @keywords.to_a.empty? && + !@expect_arbitrary_keywords && + !@expect_unlimited_arguments + end + + def keywords=(values) + @keywords = values.to_a || [] + end + end + + # Deals with the slightly different semantics of block arguments. + # For methods, arguments are required unless a default value is provided. + # For blocks, arguments are optional, even if no default value is provided. + # + # However, we want to treat block args as required since you virtually + # always want to pass a value for each received argument and our + # `and_yield` has treated block args as required for many years. + # + # @api private + class BlockSignature < MethodSignature + if RubyFeatures.optional_and_splat_args_supported? + def classify_parameters + super + @min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY + end + end + end + + # Abstract base class for signature verifiers. + # + # @api private + class MethodSignatureVerifier + attr_reader :non_kw_args, :kw_args, :min_non_kw_args, :max_non_kw_args + + def initialize(signature, args=[]) + @signature = signature + @non_kw_args, @kw_args = split_args(*args) + @min_non_kw_args = @max_non_kw_args = @non_kw_args + @arbitrary_kw_args = @unlimited_args = false + end + + def with_expectation(expectation) # rubocop:disable MethodLength, Metrics/PerceivedComplexity + return self unless MethodSignatureExpectation === expectation + + if expectation.empty? + @min_non_kw_args = @max_non_kw_args = @non_kw_args = nil + @kw_args = [] + else + @min_non_kw_args = @non_kw_args = expectation.min_count || 0 + @max_non_kw_args = expectation.max_count || @min_non_kw_args + + if RubyFeatures.optional_and_splat_args_supported? + @unlimited_args = expectation.expect_unlimited_arguments + else + @unlimited_args = false + end + + if RubyFeatures.kw_args_supported? + @kw_args = expectation.keywords + @arbitrary_kw_args = expectation.expect_arbitrary_keywords + else + @kw_args = [] + @arbitrary_kw_args = false + end + end + + self + end + + def valid? + missing_kw_args.empty? && + invalid_kw_args.empty? && + valid_non_kw_args? && + arbitrary_kw_args? && + unlimited_args? + end + + def error_message + if missing_kw_args.any? + "Missing required keyword arguments: %s" % [ + missing_kw_args.join(", ") + ] + elsif invalid_kw_args.any? + "Invalid keyword arguments provided: %s" % [ + invalid_kw_args.join(", ") + ] + elsif !valid_non_kw_args? + "Wrong number of arguments. Expected %s, got %s." % [ + @signature.non_kw_args_arity_description, + non_kw_args + ] + end + end + + private + + def valid_non_kw_args? + @signature.valid_non_kw_args?(min_non_kw_args, max_non_kw_args) + end + + def missing_kw_args + @signature.missing_kw_args_from(kw_args) + end + + def invalid_kw_args + @signature.invalid_kw_args_from(kw_args) + end + + def arbitrary_kw_args? + !@arbitrary_kw_args || @signature.arbitrary_kw_args? + end + + def unlimited_args? + !@unlimited_args || @signature.unlimited_args? + end + + def split_args(*args) + kw_args = if @signature.has_kw_args_in?(args) + last = args.pop + non_kw_args = last.reject { |k, _| k.is_a?(Symbol) } + if non_kw_args.empty? + last.keys + else + args << non_kw_args + last.select { |k, _| k.is_a?(Symbol) }.keys + end + else + [] + end + + [args.length, kw_args] + end + end + + # Figures out wether a given method can accept various arguments. + # Surprisingly non-trivial. + # + # @private + StrictSignatureVerifier = MethodSignatureVerifier + + # Allows matchers to be used instead of providing keyword arguments. In + # practice, when this happens only the arity of the method is verified. + # + # @private + class LooseSignatureVerifier < MethodSignatureVerifier + private + + def split_args(*args) + if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args) + args.pop + @signature = SignatureWithKeywordArgumentsMatcher.new(@signature) + end + + super(*args) + end + + # If a matcher is used in a signature in place of keyword arguments, all + # keyword argument validation needs to be skipped since the matcher is + # opaque. + # + # Instead, keyword arguments will be validated when the method is called + # and they are actually known. + # + # @private + class SignatureWithKeywordArgumentsMatcher + def initialize(signature) + @signature = signature + end + + def missing_kw_args_from(_kw_args) + [] + end + + def invalid_kw_args_from(_kw_args) + [] + end + + def non_kw_args_arity_description + @signature.non_kw_args_arity_description + end + + def valid_non_kw_args?(*args) + @signature.valid_non_kw_args?(*args) + end + + def has_kw_args_in?(args) + @signature.has_kw_args_in?(args) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/mutex.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/mutex.rb new file mode 100644 index 0000000000..1bc3ccf698 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/mutex.rb @@ -0,0 +1,73 @@ +module RSpec + module Support + # On 1.8.7, it's in the stdlib. + # We don't want to load the stdlib, b/c this is a test tool, and can affect + # the test environment, causing tests to pass where they should fail. + # + # So we're transcribing/modifying it from + # https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56 + # Some methods we don't need are deleted. Anything I don't + # understand (there's quite a bit, actually) is left in. + # + # Some formating changes are made to appease the robot overlord: + # https://travis-ci.org/rspec/rspec-core/jobs/54410874 + # @private + class Mutex + def initialize + @waiting = [] + @locked = false + @waiting.taint + taint + end + + # @private + def lock + while Thread.critical = true && @locked + @waiting.push Thread.current + Thread.stop + end + @locked = true + Thread.critical = false + self + end + + # @private + def unlock + return unless @locked + Thread.critical = true + @locked = false + wakeup_and_run_waiting_thread + self + end + + # @private + def synchronize + lock + begin + yield + ensure + unlock + end + end + + private + + def wakeup_and_run_waiting_thread + begin + t = @waiting.shift + t.wakeup if t + rescue ThreadError + retry + end + Thread.critical = false + begin + t.run if t + rescue ThreadError + :noop + end + end + + # Avoid warnings for library wide checks spec + end unless defined?(::RSpec::Support::Mutex) || defined?(::Mutex) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/object_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/object_formatter.rb new file mode 100644 index 0000000000..2798a57b7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/object_formatter.rb @@ -0,0 +1,275 @@ +RSpec::Support.require_rspec_support 'matcher_definition' + +module RSpec + module Support + # Provide additional output details beyond what `inspect` provides when + # printing Time, DateTime, or BigDecimal + # @api private + class ObjectFormatter # rubocop:disable Metrics/ClassLength + ELLIPSIS = "..." + + attr_accessor :max_formatted_output_length + + # Methods are deferred to a default instance of the class to maintain the interface + # For example, calling ObjectFormatter.format is still possible + def self.default_instance + @default_instance ||= new + end + + def self.format(object) + default_instance.format(object) + end + + def self.prepare_for_inspection(object) + default_instance.prepare_for_inspection(object) + end + + def initialize(max_formatted_output_length=200) + @max_formatted_output_length = max_formatted_output_length + @current_structure_stack = [] + end + + def format(object) + if max_formatted_output_length.nil? + prepare_for_inspection(object).inspect + else + formatted_object = prepare_for_inspection(object).inspect + if formatted_object.length < max_formatted_output_length + formatted_object + else + beginning = truncate_string formatted_object, 0, max_formatted_output_length / 2 + ending = truncate_string formatted_object, -max_formatted_output_length / 2, -1 + beginning + ELLIPSIS + ending + end + end + end + + # Prepares the provided object to be formatted by wrapping it as needed + # in something that, when `inspect` is called on it, will produce the + # desired output. + # + # This allows us to apply the desired formatting to hash/array data structures + # at any level of nesting, simply by walking that structure and replacing items + # with custom items that have `inspect` defined to return the desired output + # for that item. Then we can just use `Array#inspect` or `Hash#inspect` to + # format the entire thing. + def prepare_for_inspection(object) + case object + when Array + prepare_array(object) + when Hash + prepare_hash(object) + else + inspector_class = INSPECTOR_CLASSES.find { |inspector| inspector.can_inspect?(object) } + inspector_class.new(object, self) + end + end + + def prepare_array(array) + with_entering_structure(array) do + array.map { |element| prepare_element(element) } + end + end + + def prepare_hash(input_hash) + with_entering_structure(input_hash) do + sort_hash_keys(input_hash).inject({}) do |output_hash, key_and_value| + key, value = key_and_value.map { |element| prepare_element(element) } + output_hash[key] = value + output_hash + end + end + end + + def sort_hash_keys(input_hash) + if input_hash.keys.all? { |k| k.is_a?(String) || k.is_a?(Symbol) } + Hash[input_hash.sort_by { |k, _v| k.to_s }] + else + input_hash + end + end + + def prepare_element(element) + if recursive_structure?(element) + case element + when Array then InspectableItem.new('[...]') + when Hash then InspectableItem.new('{...}') + else raise # This won't happen + end + else + prepare_for_inspection(element) + end + end + + def with_entering_structure(structure) + @current_structure_stack.push(structure) + return_value = yield + @current_structure_stack.pop + return_value + end + + def recursive_structure?(object) + @current_structure_stack.any? { |seen_structure| seen_structure.equal?(object) } + end + + InspectableItem = Struct.new(:text) do + def inspect + text + end + + def pretty_print(pp) + pp.text(text) + end + end + + BaseInspector = Struct.new(:object, :formatter) do + def self.can_inspect?(_object) + raise NotImplementedError + end + + def inspect + raise NotImplementedError + end + + def pretty_print(pp) + pp.text(inspect) + end + end + + class TimeInspector < BaseInspector + FORMAT = "%Y-%m-%d %H:%M:%S" + + def self.can_inspect?(object) + Time === object + end + + if Time.method_defined?(:nsec) + def inspect + object.strftime("#{FORMAT}.#{"%09d" % object.nsec} %z") + end + else # for 1.8.7 + def inspect + object.strftime("#{FORMAT}.#{"%06d" % object.usec} %z") + end + end + end + + class DateTimeInspector < BaseInspector + FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z" + + def self.can_inspect?(object) + defined?(DateTime) && DateTime === object + end + + # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is + # defined use a custom format string that includes more time precision. + def inspect + if defined?(ActiveSupport) + object.strftime(FORMAT) + else + object.inspect + end + end + end + + class BigDecimalInspector < BaseInspector + def self.can_inspect?(object) + defined?(BigDecimal) && BigDecimal === object + end + + def inspect + "#{object.to_s('F')} (#{object.inspect})" + end + end + + class DescribableMatcherInspector < BaseInspector + def self.can_inspect?(object) + Support.is_a_matcher?(object) && object.respond_to?(:description) + end + + def inspect + object.description + end + end + + class UninspectableObjectInspector < BaseInspector + OBJECT_ID_FORMAT = '%#016x' + + def self.can_inspect?(object) + object.inspect + false + rescue NoMethodError + true + end + + def inspect + "#<#{klass}:#{native_object_id}>" + end + + def klass + Support.class_of(object) + end + + # http://stackoverflow.com/a/2818916 + def native_object_id + OBJECT_ID_FORMAT % (object.__id__ << 1) + rescue NoMethodError + # In Ruby 1.9.2, BasicObject responds to none of #__id__, #object_id, #id... + '-' + end + end + + class DelegatorInspector < BaseInspector + def self.can_inspect?(object) + defined?(Delegator) && Delegator === object + end + + def inspect + "#<#{object.class}(#{formatter.format(object.send(:__getobj__))})>" + end + end + + class InspectableObjectInspector < BaseInspector + def self.can_inspect?(object) + object.inspect + true + rescue NoMethodError + false + end + + def inspect + object.inspect + end + end + + INSPECTOR_CLASSES = [ + TimeInspector, + DateTimeInspector, + BigDecimalInspector, + UninspectableObjectInspector, + DescribableMatcherInspector, + DelegatorInspector, + InspectableObjectInspector + ].tap do |classes| + # 2.4 has improved BigDecimal formatting so we do not need + # to provide our own. + # https://github.com/ruby/bigdecimal/pull/42 + classes.delete(BigDecimalInspector) if RUBY_VERSION >= '2.4' + end + + private + + # Returns the substring defined by the start_index and end_index + # If the string ends with a partial ANSI code code then that + # will be removed as printing partial ANSI + # codes to the terminal can lead to corruption + def truncate_string(str, start_index, end_index) + cut_str = str[start_index..end_index] + + # ANSI color codes are like: \e[33m so anything with \e[ and a + # number without a 'm' is an incomplete color code + cut_str.sub(/\e\[\d+$/, '') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/recursive_const_methods.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/recursive_const_methods.rb new file mode 100644 index 0000000000..b19ad0666a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/recursive_const_methods.rb @@ -0,0 +1,76 @@ +module RSpec + module Support + # Provides recursive constant lookup methods useful for + # constant stubbing. + module RecursiveConstMethods + # We only want to consider constants that are defined directly on a + # particular module, and not include top-level/inherited constants. + # Unfortunately, the constant API changed between 1.8 and 1.9, so + # we need to conditionally define methods to ignore the top-level/inherited + # constants. + # + # Given: + # class A; B = 1; end + # class C < A; end + # + # On 1.8: + # - C.const_get("Hash") # => ::Hash + # - C.const_defined?("Hash") # => false + # - C.constants # => ["B"] + # - None of these methods accept the extra `inherit` argument + # On 1.9: + # - C.const_get("Hash") # => ::Hash + # - C.const_defined?("Hash") # => true + # - C.const_get("Hash", false) # => raises NameError + # - C.const_defined?("Hash", false) # => false + # - C.constants # => [:B] + # - C.constants(false) #=> [] + if Module.method(:const_defined?).arity == 1 + def const_defined_on?(mod, const_name) + mod.const_defined?(const_name) + end + + def get_const_defined_on(mod, const_name) + return mod.const_get(const_name) if const_defined_on?(mod, const_name) + + raise NameError, "uninitialized constant #{mod.name}::#{const_name}" + end + + def constants_defined_on(mod) + mod.constants.select { |c| const_defined_on?(mod, c) } + end + else + def const_defined_on?(mod, const_name) + mod.const_defined?(const_name, false) + end + + def get_const_defined_on(mod, const_name) + mod.const_get(const_name, false) + end + + def constants_defined_on(mod) + mod.constants(false) + end + end + + def recursive_const_get(const_name) + normalize_const_name(const_name).split('::').inject(Object) do |mod, name| + get_const_defined_on(mod, name) + end + end + + def recursive_const_defined?(const_name) + parts = normalize_const_name(const_name).split('::') + parts.inject([Object, '']) do |(mod, full_name), name| + yield(full_name, name) if block_given? && !(Module === mod) + return false unless const_defined_on?(mod, name) + [get_const_defined_on(mod, name), [mod.name, name].join('::')] + end + end + + def normalize_const_name(const_name) + const_name.sub(/\A::/, '') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/reentrant_mutex.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/reentrant_mutex.rb new file mode 100644 index 0000000000..3611359428 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/reentrant_mutex.rb @@ -0,0 +1,61 @@ +module RSpec + module Support + # Allows a thread to lock out other threads from a critical section of code, + # while allowing the thread with the lock to reenter that section. + # + # Based on Monitor as of 2.2 - + # https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9 + # + # Depends on Mutex, but Mutex is only available as part of core since 1.9.1: + # exists - http://ruby-doc.org/core-1.9.1/Mutex.html + # dne - http://ruby-doc.org/core-1.9.0/Mutex.html + # + # @private + class ReentrantMutex + def initialize + @owner = nil + @count = 0 + @mutex = Mutex.new + end + + def synchronize + enter + yield + ensure + exit + end + + private + + def enter + @mutex.lock if @owner != Thread.current + @owner = Thread.current + @count += 1 + end + + def exit + @count -= 1 + return unless @count == 0 + @owner = nil + @mutex.unlock + end + end + + if defined? ::Mutex + # On 1.9 and up, this is in core, so we just use the real one + class Mutex < ::Mutex + # If you mock Mutex.new you break our usage of Mutex, so + # instead we capture the original method to return Mutexs. + NEW_MUTEX_METHOD = Mutex.method(:new) + + def self.new + NEW_MUTEX_METHOD.call + end + end + else # For 1.8.7 + # :nocov: + RSpec::Support.require_rspec_support "mutex" + # :nocov: + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/ruby_features.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/ruby_features.rb new file mode 100644 index 0000000000..daba00ea52 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/ruby_features.rb @@ -0,0 +1,191 @@ +require 'rbconfig' +RSpec::Support.require_rspec_support "comparable_version" + +module RSpec + module Support + # @api private + # + # Provides query methods for different OS or OS features. + module OS + module_function + + def windows? + !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) + end + + def windows_file_path? + ::File::ALT_SEPARATOR == '\\' + end + end + + # @api private + # + # Provides query methods for different rubies + module Ruby + module_function + + def jruby? + RUBY_PLATFORM == 'java' + end + + def jruby_version + @jruby_version ||= ComparableVersion.new(JRUBY_VERSION) + end + + def jruby_9000? + jruby? && JRUBY_VERSION >= '9.0.0.0' + end + + def rbx? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' + end + + def non_mri? + !mri? + end + + def mri? + !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' + end + + def truffleruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby' + end + end + + # @api private + # + # Provides query methods for ruby features that differ among + # implementations. + module RubyFeatures + module_function + + if Ruby.jruby? && RUBY_VERSION.to_f < 1.9 + # On JRuby 1.7 `--1.8` mode, `Process.respond_to?(:fork)` returns true, + # but when you try to fork, it raises an error: + # NotImplementedError: fork is not available on this platform + # + # When we drop support for JRuby 1.7 and/or Ruby 1.8, we can drop + # this special case. + def fork_supported? + false + end + else + def fork_supported? + Process.respond_to?(:fork) + end + end + + def optional_and_splat_args_supported? + Method.method_defined?(:parameters) + end + + def caller_locations_supported? + respond_to?(:caller_locations, true) + end + + if Exception.method_defined?(:cause) + def supports_exception_cause? + true + end + else + def supports_exception_cause? + false + end + end + + if RUBY_VERSION.to_f >= 2.7 + def supports_taint? + false + end + else + def supports_taint? + true + end + end + ripper_requirements = [ComparableVersion.new(RUBY_VERSION) >= '1.9.2'] + + ripper_requirements.push(false) if Ruby.rbx? + + if Ruby.jruby? + ripper_requirements.push(Ruby.jruby_version >= '1.7.5') + # Ripper on JRuby 9.0.0.0.rc1 - 9.1.8.0 reports wrong line number + # or cannot parse source including `:if`. + # Ripper on JRuby 9.x.x.x < 9.1.17.0 can't handle keyword arguments + # Neither can JRuby 9.2, e.g. < 9.2.1.0 + ripper_requirements.push(!Ruby.jruby_version.between?('9.0.0.0.rc1', '9.2.0.0')) + end + + if ripper_requirements.all? + def ripper_supported? + true + end + else + def ripper_supported? + false + end + end + + if Ruby.mri? + def kw_args_supported? + RUBY_VERSION >= '2.0.0' + end + + def required_kw_args_supported? + RUBY_VERSION >= '2.1.0' + end + + def supports_rebinding_module_methods? + RUBY_VERSION.to_i >= 2 + end + else + # RBX / JRuby et al support is unknown for keyword arguments + begin + eval("o = Object.new; def o.m(a: 1); end;"\ + " raise SyntaxError unless o.method(:m).parameters.include?([:key, :a])") + + def kw_args_supported? + true + end + rescue SyntaxError + def kw_args_supported? + false + end + end + + begin + eval("o = Object.new; def o.m(a: ); end;"\ + "raise SyntaxError unless o.method(:m).parameters.include?([:keyreq, :a])") + + def required_kw_args_supported? + true + end + rescue SyntaxError + def required_kw_args_supported? + false + end + end + + begin + Module.new { def foo; end }.instance_method(:foo).bind(Object.new) + + def supports_rebinding_module_methods? + true + end + rescue TypeError + def supports_rebinding_module_methods? + false + end + end + end + + def module_refinement_supported? + Module.method_defined?(:refine) || Module.private_method_defined?(:refine) + end + + def module_prepends_supported? + Module.method_defined?(:prepend) || Module.private_method_defined?(:prepend) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source.rb new file mode 100644 index 0000000000..ec0e1aeb1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source.rb @@ -0,0 +1,85 @@ +RSpec::Support.require_rspec_support 'encoded_string' +RSpec::Support.require_rspec_support 'ruby_features' + +module RSpec + module Support + # @private + # Represents a Ruby source file and provides access to AST and tokens. + class Source + attr_reader :source, :path + + # This class protects us against having File read and expand_path + # stubbed out within tests. + class File + class << self + [:read, :expand_path].each do |method_name| + define_method(method_name, &::File.method(method_name)) + end + end + end + + def self.from_file(path) + source = File.read(path) + new(source, path) + end + + if String.method_defined?(:encoding) + def initialize(source_string, path=nil) + @source = RSpec::Support::EncodedString.new(source_string, Encoding.default_external) + @path = path ? File.expand_path(path) : '(string)' + end + else # for 1.8.7 + # :nocov: + def initialize(source_string, path=nil) + @source = RSpec::Support::EncodedString.new(source_string) + @path = path ? File.expand_path(path) : '(string)' + end + # :nocov: + end + + def lines + @lines ||= source.split("\n") + end + + def inspect + "#<#{self.class} #{path}>" + end + + if RSpec::Support::RubyFeatures.ripper_supported? + RSpec::Support.require_rspec_support 'source/node' + RSpec::Support.require_rspec_support 'source/token' + + def ast + @ast ||= begin + require 'ripper' + sexp = Ripper.sexp(source) + raise SyntaxError unless sexp + Node.new(sexp) + end + end + + def tokens + @tokens ||= begin + require 'ripper' + tokens = Ripper.lex(source) + Token.tokens_from_ripper_tokens(tokens) + end + end + + def nodes_by_line_number + @nodes_by_line_number ||= begin + nodes_by_line_number = ast.select(&:location).group_by { |node| node.location.line } + Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number) + end + end + + def tokens_by_line_number + @tokens_by_line_number ||= begin + nodes_by_line_number = tokens.group_by { |token| token.location.line } + Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/location.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/location.rb new file mode 100644 index 0000000000..29077c99c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/location.rb @@ -0,0 +1,21 @@ +module RSpec + module Support + class Source + # @private + # Represents a source location of node or token. + Location = Struct.new(:line, :column) do + include Comparable + + def self.location?(array) + array.is_a?(Array) && array.size == 2 && array.all? { |e| e.is_a?(Integer) } + end + + def <=>(other) + line_comparison = (line <=> other.line) + return line_comparison unless line_comparison == 0 + column <=> other.column + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/node.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/node.rb new file mode 100644 index 0000000000..6f3086b660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/node.rb @@ -0,0 +1,110 @@ +RSpec::Support.require_rspec_support 'source/location' + +module RSpec + module Support + class Source + # @private + # A wrapper for Ripper AST node which is generated with `Ripper.sexp`. + class Node + include Enumerable + + attr_reader :sexp, :parent + + def self.sexp?(array) + array.is_a?(Array) && array.first.is_a?(Symbol) + end + + def initialize(ripper_sexp, parent=nil) + @sexp = ripper_sexp.freeze + @parent = parent + end + + def type + sexp[0] + end + + def args + @args ||= raw_args.map do |raw_arg| + if Node.sexp?(raw_arg) + Node.new(raw_arg, self) + elsif Location.location?(raw_arg) + Location.new(*raw_arg) + elsif raw_arg.is_a?(Array) + ExpressionSequenceNode.new(raw_arg, self) + else + raw_arg + end + end.freeze + end + + def children + @children ||= args.select { |arg| arg.is_a?(Node) }.freeze + end + + def location + @location ||= args.find { |arg| arg.is_a?(Location) } + end + + # We use a loop here (instead of recursion) to prevent SystemStackError + def each + return to_enum(__method__) unless block_given? + + node_queue = [] + node_queue << self + + while (current_node = node_queue.shift) + yield current_node + node_queue.concat(current_node.children) + end + end + + def each_ancestor + return to_enum(__method__) unless block_given? + + current_node = self + + while (current_node = current_node.parent) + yield current_node + end + end + + def inspect + "#<#{self.class} #{type}>" + end + + private + + def raw_args + sexp[1..-1] || [] + end + end + + # @private + # Basically `Ripper.sexp` generates arrays whose first element is a symbol (type of sexp), + # but it exceptionally generates typeless arrays for expression sequence: + # + # Ripper.sexp('foo; bar') + # => [ + # :program, + # [ # Typeless array + # [:vcall, [:@ident, "foo", [1, 0]]], + # [:vcall, [:@ident, "bar", [1, 5]]] + # ] + # ] + # + # We wrap typeless arrays in this pseudo type node + # so that it can be handled in the same way as other type node. + class ExpressionSequenceNode < Node + def type + :_expression_sequence + end + + private + + def raw_args + sexp + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/token.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/token.rb new file mode 100644 index 0000000000..6b8d965b41 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/source/token.rb @@ -0,0 +1,87 @@ +RSpec::Support.require_rspec_support 'source/location' + +module RSpec + module Support + class Source + # @private + # A wrapper for Ripper token which is generated with `Ripper.lex`. + class Token + CLOSING_TYPES_BY_OPENING_TYPE = { + :on_lbracket => :on_rbracket, + :on_lparen => :on_rparen, + :on_lbrace => :on_rbrace, + :on_heredoc_beg => :on_heredoc_end + }.freeze + + CLOSING_KEYWORDS_BY_OPENING_KEYWORD = { + 'def' => 'end', + 'do' => 'end', + }.freeze + + attr_reader :token + + def self.tokens_from_ripper_tokens(ripper_tokens) + ripper_tokens.map { |ripper_token| new(ripper_token) }.freeze + end + + def initialize(ripper_token) + @token = ripper_token.freeze + end + + def location + @location ||= Location.new(*token[0]) + end + + def type + token[1] + end + + def string + token[2] + end + + def ==(other) + token == other.token + end + + alias_method :eql?, :== + + def inspect + "#<#{self.class} #{type} #{string.inspect}>" + end + + def keyword? + type == :on_kw + end + + def opening? + opening_delimiter? || opening_keyword? + end + + def closed_by?(other) + closed_by_delimiter?(other) || closed_by_keyword?(other) + end + + private + + def opening_delimiter? + CLOSING_TYPES_BY_OPENING_TYPE.key?(type) + end + + def opening_keyword? + return false unless keyword? + CLOSING_KEYWORDS_BY_OPENING_KEYWORD.key?(string) + end + + def closed_by_delimiter?(other) + other.type == CLOSING_TYPES_BY_OPENING_TYPE[type] + end + + def closed_by_keyword?(other) + return false unless other.keyword? + other.string == CLOSING_KEYWORDS_BY_OPENING_KEYWORD[string] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec.rb new file mode 100644 index 0000000000..b53829a986 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec.rb @@ -0,0 +1,82 @@ +require 'rspec/support' +require 'rspec/support/spec/in_sub_process' + +RSpec::Support.require_rspec_support "spec/deprecation_helpers" +RSpec::Support.require_rspec_support "spec/diff_helpers" +RSpec::Support.require_rspec_support "spec/with_isolated_stderr" +RSpec::Support.require_rspec_support "spec/stderr_splitter" +RSpec::Support.require_rspec_support "spec/formatting_support" +RSpec::Support.require_rspec_support "spec/with_isolated_directory" +RSpec::Support.require_rspec_support "ruby_features" + +warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr) + +RSpec.configure do |c| + c.include RSpecHelpers + c.include RSpec::Support::WithIsolatedStdErr + c.include RSpec::Support::FormattingSupport + c.include RSpec::Support::InSubProcess + + unless defined?(Debugger) # debugger causes warnings when used + c.before do + warning_preventer.reset! + end + + c.after do + warning_preventer.verify_no_warnings! + end + end + + if c.files_to_run.one? + c.full_backtrace = true + c.default_formatter = 'doc' + end + + c.filter_run_when_matching :focus + + c.example_status_persistence_file_path = "./spec/examples.txt" + + c.define_derived_metadata :failing_on_windows_ci do |meta| + meta[:pending] ||= "This spec fails on Windows CI and needs someone to fix it." + end if RSpec::Support::OS.windows? && ENV['CI'] +end + +module RSpec + module Support + module Spec + def self.setup_simplecov(&block) + # Simplecov emits some ruby warnings when loaded, so silence them. + old_verbose, $VERBOSE = $VERBOSE, false + + return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3' + return if RUBY_ENGINE != 'ruby' || RSpec::Support::OS.windows? + + # Don't load it when we're running a single isolated + # test file rather than the whole suite. + return if RSpec.configuration.files_to_run.one? + + require 'simplecov' + start_simplecov(&block) + rescue LoadError + warn "Simplecov could not be loaded" + ensure + $VERBOSE = old_verbose + end + + def self.start_simplecov(&block) + SimpleCov.start do + add_filter "bundle/" + add_filter "tmp/" + add_filter do |source_file| + # Filter out `spec` directory except when it is under `lib` + # (as is the case in rspec-support) + source_file.filename.include?('/spec/') && !source_file.filename.include?('/lib/') + end + + instance_eval(&block) if block + end + end + private_class_method :start_simplecov + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/deprecation_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/deprecation_helpers.rb new file mode 100644 index 0000000000..8413ed60ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/deprecation_helpers.rb @@ -0,0 +1,48 @@ +module RSpecHelpers + def expect_deprecation_with_call_site(file, line, snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:deprecated => match(snippet), :call_site => include([file, line].join(':')))) + end + + def expect_deprecation_without_call_site(snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:deprecated => match(snippet), :call_site => eq(nil))) + end + + def expect_warn_deprecation_with_call_site(file, line, snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:message => match(snippet), :call_site => include([file, line].join(':')))) + end + + def expect_warn_deprecation(snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:message => match(snippet))) + end + + def allow_deprecation + allow(RSpec.configuration.reporter).to receive(:deprecation) + end + + def expect_no_deprecations + expect(RSpec.configuration.reporter).not_to receive(:deprecation) + end + alias expect_no_deprecation expect_no_deprecations + + def expect_warning_without_call_site(expected=//) + expect(::Kernel).to receive(:warn). + with(match(expected).and(satisfy { |message| !(/Called from/ =~ message) })) + end + + def expect_warning_with_call_site(file, line, expected=//) + expect(::Kernel).to receive(:warn). + with(match(expected).and(match(/Called from #{file}:#{line}/))) + end + + def expect_no_warnings + expect(::Kernel).not_to receive(:warn) + end + + def allow_warning + allow(::Kernel).to receive(:warn) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/diff_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/diff_helpers.rb new file mode 100644 index 0000000000..76ef10a67a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/diff_helpers.rb @@ -0,0 +1,31 @@ +require 'diff/lcs' + +module RSpec + module Support + module Spec + module DiffHelpers + # In the updated version of diff-lcs several diff headers change format slightly + # compensate for this and change minimum version in RSpec 4 + if ::Diff::LCS::VERSION.to_f < 1.4 + def one_line_header(line_number=2) + "-1,#{line_number} +1,#{line_number}" + end + else + def one_line_header(_=2) + "-1 +1" + end + end + + if Diff::LCS::VERSION.to_f < 1.4 || Diff::LCS::VERSION >= "1.4.4" + def removing_two_line_header + "-1,3 +1" + end + else + def removing_two_line_header + "-1,3 +1,5" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/formatting_support.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/formatting_support.rb new file mode 100644 index 0000000000..7e61cef495 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/formatting_support.rb @@ -0,0 +1,9 @@ +module RSpec + module Support + module FormattingSupport + def dedent(string) + string.gsub(/^\s+\|/, '').chomp + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/in_sub_process.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/in_sub_process.rb new file mode 100644 index 0000000000..85196997c8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/in_sub_process.rb @@ -0,0 +1,67 @@ +module RSpec + module Support + module InSubProcess + if Process.respond_to?(:fork) && !(Ruby.jruby? && RUBY_VERSION == '1.8.7') + + UnmarshableObject = Struct.new(:error) + + # Useful as a way to isolate a global change to a subprocess. + + def in_sub_process(prevent_warnings=true) # rubocop:disable MethodLength, Metrics/AbcSize + exception_reader, exception_writer = IO.pipe + result_reader, result_writer = IO.pipe + + pid = Process.fork do + warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr) + + begin + result = yield + warning_preventer.verify_no_warnings! if prevent_warnings + # rubocop:disable Lint/HandleExceptions + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception + # rubocop:enable Lint/HandleExceptions + end + + exception_writer.write marshal_dump_with_unmarshable_object_handling(exception) + exception_reader.close + exception_writer.close + + result_writer.write marshal_dump_with_unmarshable_object_handling(result) + result_reader.close + result_writer.close + + exit! # prevent at_exit hooks from running (e.g. minitest) + end + + exception_writer.close + result_writer.close + Process.waitpid(pid) + + exception = Marshal.load(exception_reader.read) + exception_reader.close + raise exception if exception + + result = Marshal.load(result_reader.read) + result_reader.close + result + end + alias :in_sub_process_if_possible :in_sub_process + + def marshal_dump_with_unmarshable_object_handling(object) + Marshal.dump(object) + rescue TypeError => error + Marshal.dump(UnmarshableObject.new(error)) + end + else + def in_sub_process(*) + skip "This spec requires forking to work properly, " \ + "and your platform does not support forking" + end + + def in_sub_process_if_possible(*) + yield + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/library_wide_checks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/library_wide_checks.rb new file mode 100644 index 0000000000..56b0593640 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/library_wide_checks.rb @@ -0,0 +1,150 @@ +require 'rspec/support/spec/shell_out' + +module RSpec + module Support + module WhitespaceChecks + # This malformed whitespace detection logic has been borrowed from bundler: + # https://github.com/bundler/bundler/blob/v1.8.0/spec/quality_spec.rb + def check_for_tab_characters(filename) + failing_lines = [] + File.readlines(filename).each_with_index do |line, number| + failing_lines << number + 1 if line =~ /\t/ + end + + return if failing_lines.empty? + "#{filename} has tab characters on lines #{failing_lines.join(', ')}" + end + + def check_for_extra_spaces(filename) + failing_lines = [] + File.readlines(filename).each_with_index do |line, number| + next if line =~ /^\s+#.*\s+\n$/ + failing_lines << number + 1 if line =~ /\s+\n$/ + end + + return if failing_lines.empty? + "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" + end + end + end +end + +RSpec.shared_examples_for "library wide checks" do |lib, options| + consider_a_test_env_file = options.fetch(:consider_a_test_env_file, /MATCHES NOTHING/) + allowed_loaded_feature_regexps = options.fetch(:allowed_loaded_feature_regexps, []) + preamble_for_lib = options[:preamble_for_lib] + preamble_for_spec = "require 'rspec/core'; require 'spec_helper'" + skip_spec_files = options.fetch(:skip_spec_files, /MATCHES NOTHING/) + + include RSpec::Support::ShellOut + include RSpec::Support::WhitespaceChecks + + define_method :files_to_require_for do |sub_dir| + slash = File::SEPARATOR + lib_path_re = /#{slash + lib}[^#{slash}]*#{slash}lib/ + load_path = $LOAD_PATH.grep(lib_path_re).first + directory = load_path.sub(/lib$/, sub_dir) + files = Dir["#{directory}/**/*.rb"] + extract_regex = /#{Regexp.escape(directory) + File::SEPARATOR}(.+)\.rb$/ + + # We sort to ensure the files are loaded in a consistent order, regardless + # of OS. Otherwise, it could load in a different order on Travis than + # locally, and potentially trigger a "circular require considered harmful" + # warning or similar. + files.sort.map { |file| file[extract_regex, 1] } + end + + def command_from(code_lines) + code_lines.join("\n") + end + + def load_all_files(files, preamble, postamble=nil) + requires = files.map { |f| "require '#{f}'" } + command = command_from(Array(preamble) + requires + Array(postamble)) + + stdout, stderr, status = with_env 'NO_COVERAGE' => '1' do + options = %w[ -w ] + options << "--disable=gem" if RUBY_VERSION.to_f >= 1.9 && RSpec::Support::Ruby.mri? + run_ruby_with_current_load_path(command, *options) + end + + [stdout, strip_known_warnings(stderr), status.exitstatus] + end + + define_method :load_all_lib_files do + files = all_lib_files - lib_test_env_files + preamble = ['orig_loaded_features = $".dup', preamble_for_lib] + postamble = ['puts(($" - orig_loaded_features).join("\n"))'] + + @loaded_feature_lines, stderr, exitstatus = load_all_files(files, preamble, postamble) + ["", stderr, exitstatus] + end + + define_method :load_all_spec_files do + files = files_to_require_for("spec") + lib_test_env_files + files = files.reject { |f| f =~ skip_spec_files } + load_all_files(files, preamble_for_spec) + end + + attr_reader :all_lib_files, :lib_test_env_files, + :lib_file_results, :spec_file_results + + before(:context) do + @all_lib_files = files_to_require_for("lib") + @lib_test_env_files = all_lib_files.grep(consider_a_test_env_file) + + @lib_file_results, @spec_file_results = [ + # Load them in parallel so it's faster... + Thread.new { load_all_lib_files }, + Thread.new { load_all_spec_files } + ].map(&:join).map(&:value) + end + + def have_successful_no_warnings_output + eq ["", "", 0] + end + + it "issues no warnings when loaded", :slow do + expect(lib_file_results).to have_successful_no_warnings_output + end + + it "issues no warnings when the spec files are loaded", :slow do + expect(spec_file_results).to have_successful_no_warnings_output + end + + it 'only loads a known set of stdlibs so gem authors are forced ' \ + 'to load libs they use to have passing specs', :slow do + loaded_features = @loaded_feature_lines.split("\n") + if RUBY_VERSION == '1.8.7' + # On 1.8.7, $" returns the relative require path if that was used + # to require the file. LIB_REGEX will not match the relative version + # since it has a `/lib` prefix. Here we deal with this by expanding + # relative files relative to the $LOAD_PATH dir (lib). + Dir.chdir("lib") { loaded_features.map! { |f| File.expand_path(f) } } + end + + loaded_features.reject! { |feature| RSpec::CallerFilter::LIB_REGEX =~ feature } + loaded_features.reject! { |feature| allowed_loaded_feature_regexps.any? { |r| r =~ feature } } + + expect(loaded_features).to eq([]) + end + + RSpec::Matchers.define :be_well_formed do + match do |actual| + actual.empty? + end + + failure_message do |actual| + actual.join("\n") + end + end + + it "has no malformed whitespace", :slow do + error_messages = [] + `git ls-files -z`.split("\x0").each do |filename| + error_messages << check_for_tab_characters(filename) + error_messages << check_for_extra_spaces(filename) + end + expect(error_messages.compact).to be_well_formed + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/shell_out.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/shell_out.rb new file mode 100644 index 0000000000..864e540b33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/shell_out.rb @@ -0,0 +1,105 @@ +require 'open3' +require 'rake/file_utils' +require 'shellwords' + +module RSpec + module Support + module ShellOut + def with_env(vars) + original = ENV.to_hash + vars.each { |k, v| ENV[k] = v } + + begin + yield + ensure + ENV.replace(original) + end + end + + if Open3.respond_to?(:capture3) # 1.9+ + def shell_out(*command) + stdout, stderr, status = Open3.capture3(*command) + return stdout, filter(stderr), status + end + else # 1.8.7 + # popen3 doesn't provide the exit status so we fake it out. + FakeProcessStatus = Struct.new(:exitstatus) + + def shell_out(*command) + stdout = stderr = nil + + Open3.popen3(*command) do |_in, out, err| + stdout = out.read + stderr = err.read + end + + status = FakeProcessStatus.new(0) + return stdout, filter(stderr), status + end + end + + def run_ruby_with_current_load_path(ruby_command, *flags) + command = [ + FileUtils::RUBY, + "-I#{$LOAD_PATH.map(&:shellescape).join(File::PATH_SEPARATOR)}", + "-e", ruby_command, *flags + ] + + # Unset these env vars because `ruby -w` will issue warnings whenever + # they are set to non-default values. + with_env 'RUBY_GC_HEAP_FREE_SLOTS' => nil, 'RUBY_GC_MALLOC_LIMIT' => nil, + 'RUBY_FREE_MIN' => nil do + shell_out(*command) + end + end + + LINES_TO_IGNORE = + [ + # Ignore bundler warning. + %r{bundler/source/rubygems}, + # Ignore bundler + rubygems warning. + %r{site_ruby/\d\.\d\.\d/rubygems}, + %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/rubygems}, + # This is required for windows for some reason + %r{lib/bundler/rubygems}, + # This is a JRuby file that generates warnings on 9.0.3.0 + %r{lib/ruby/stdlib/jar}, + # This is a JRuby file that generates warnings on 9.1.7.0 + %r{org/jruby/RubyKernel\.java}, + # This is a JRuby gem that generates warnings on 9.1.7.0 + %r{ffi-1\.13\.\d+-java}, + %r{uninitialized constant FFI}, + # These are related to the above, there is a warning about io from FFI + %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/io}, + %r{io/console on JRuby shells out to stty for most operations}, + # This is a JRuby 9.1.17.0 error on Github Actions + %r{io/console not supported; tty will not be manipulated}, + # This is a JRuby 9.2.1.x error + %r{jruby/kernel/gem_prelude}, + %r{lib/jruby\.jar!/jruby/preludes}, + ] + + def strip_known_warnings(input) + input.split("\n").reject do |l| + LINES_TO_IGNORE.any? { |to_ignore| l =~ to_ignore } || + # Remove blank lines + l == "" || l.nil? + end.join("\n") + end + + private + + if Ruby.jruby? + def filter(output) + output.each_line.reject do |line| + line.include?("lib/ruby/shared/rubygems") + end.join($/) + end + else + def filter(output) + output + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/stderr_splitter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/stderr_splitter.rb new file mode 100644 index 0000000000..03b50e9392 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/stderr_splitter.rb @@ -0,0 +1,75 @@ +require 'stringio' + +module RSpec + module Support + class StdErrSplitter + def initialize(original) + @orig_stderr = original + @output_tracker = ::StringIO.new + @last_line = nil + end + + respond_to_name = (::RUBY_VERSION.to_f < 1.9) ? :respond_to? : :respond_to_missing? + define_method respond_to_name do |*args| + @orig_stderr.respond_to?(*args) || super(*args) + end + + def method_missing(name, *args, &block) + @output_tracker.__send__(name, *args, &block) if @output_tracker.respond_to?(name) + @orig_stderr.__send__(name, *args, &block) + end + + def ==(other) + @orig_stderr == other + end + + def reopen(*args) + reset! + @orig_stderr.reopen(*args) + end + + # To work around JRuby error: + # can't convert RSpec::Support::StdErrSplitter into String + def to_io + @orig_stderr.to_io + end + + # To work around JRuby error: + # TypeError: $stderr must have write method, RSpec::StdErrSplitter given + def write(line) + return if line =~ %r{^\S+/gems/\S+:\d+: warning:} # http://rubular.com/r/kqeUIZOfPG + + # Ruby 2.7.0 warnings from keyword argments span multiple lines, extend check above + # to look for the next line. + return if @last_line =~ %r{^\S+/gems/\S+:\d+: warning:} && + line =~ %r{warning: The called method .* is defined here} + + # Ruby 2.7.0 complains about hashes used in place of keyword arguments + # Aruba 0.14.2 uses this internally triggering that here + return if line =~ %r{lib/ruby/2\.7\.0/fileutils\.rb:622: warning:} + + @orig_stderr.write(line) + @output_tracker.write(line) + ensure + @last_line = line + end + + def has_output? + !output.empty? + end + + def reset! + @output_tracker = ::StringIO.new + end + + def verify_no_warnings! + raise "Warnings were generated: #{output}" if has_output? + reset! + end + + def output + @output_tracker.string + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/string_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/string_matcher.rb new file mode 100644 index 0000000000..7df3199188 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/string_matcher.rb @@ -0,0 +1,45 @@ +require 'rspec/matchers' +# Special matcher for comparing encoded strings so that +# we don't run any expectation failures through the Differ, +# which also relies on EncodedString. Instead, confirm the +# strings have the same bytes. +RSpec::Matchers.define :be_identical_string do |expected| + if String.method_defined?(:encoding) + match do + expected_encoding? && + actual.bytes.to_a == expected.bytes.to_a + end + + failure_message do + "expected\n#{actual.inspect} (#{actual.encoding.name}) to be identical to\n"\ + "#{expected.inspect} (#{expected.encoding.name})\n"\ + "The exact bytes are printed below for more detail:\n"\ + "#{actual.bytes.to_a}\n"\ + "#{expected.bytes.to_a}\n"\ + end + + # Depends on chaining :with_same_encoding for it to + # check for string encoding. + def expected_encoding? + if defined?(@expect_same_encoding) && @expect_same_encoding + actual.encoding == expected.encoding + else + true + end + end + else + match do + actual.split(//) == expected.split(//) + end + + failure_message do + "expected\n#{actual.inspect} to be identical to\n#{expected.inspect}\n" + end + end + + chain :with_same_encoding do + @expect_same_encoding ||= true + end +end +RSpec::Matchers.alias_matcher :a_string_identical_to, :be_identical_string +RSpec::Matchers.alias_matcher :be_diffed_as, :be_identical_string diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_directory.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_directory.rb new file mode 100644 index 0000000000..6e38c82ea9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_directory.rb @@ -0,0 +1,13 @@ +require 'tmpdir' + +RSpec.shared_context "isolated directory" do + around do |ex| + Dir.mktmpdir do |tmp_dir| + Dir.chdir(tmp_dir, &ex) + end + end +end + +RSpec.configure do |c| + c.include_context "isolated directory", :isolated_directory => true +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_stderr.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_stderr.rb new file mode 100644 index 0000000000..8884c2fbe2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/spec/with_isolated_stderr.rb @@ -0,0 +1,13 @@ +module RSpec + module Support + module WithIsolatedStdErr + def with_isolated_stderr + original = $stderr + $stderr = StringIO.new + yield + ensure + $stderr = original + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/version.rb new file mode 100644 index 0000000000..8c7cffc7bb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/version.rb @@ -0,0 +1,7 @@ +module RSpec + module Support + module Version + STRING = '3.10.2' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/warnings.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/warnings.rb new file mode 100644 index 0000000000..380150be9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/warnings.rb @@ -0,0 +1,39 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "caller_filter" + +module RSpec + module Support + module Warnings + def deprecate(deprecated, options={}) + warn_with "DEPRECATION: #{deprecated} is deprecated.", options + end + + # @private + # + # Used internally to print deprecation warnings + # when rspec-core isn't loaded + def warn_deprecation(message, options={}) + warn_with "DEPRECATION: \n #{message}", options + end + + # @private + # + # Used internally to print warnings + def warning(text, options={}) + warn_with "WARNING: #{text}.", options + end + + # @private + # + # Used internally to print longer warnings + def warn_with(message, options={}) + call_site = options.fetch(:call_site) { CallerFilter.first_non_rspec_line } + message += " Use #{options[:replacement]} instead." if options[:replacement] + message += " Called from #{call_site}." if call_site + Support.warning_notifier.call message + end + end + end + + extend RSpec::Support::Warnings +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/with_keywords_when_needed.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/with_keywords_when_needed.rb new file mode 100644 index 0000000000..56b67e70b5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.10.2/lib/rspec/support/with_keywords_when_needed.rb @@ -0,0 +1,33 @@ +RSpec::Support.require_rspec_support("method_signature_verifier") + +module RSpec + module Support + module WithKeywordsWhenNeeded + # This module adds keyword sensitive support for core ruby methods + # where we cannot use `ruby2_keywords` directly. + + module_function + + if RSpec::Support::RubyFeatures.kw_args_supported? + # Remove this in RSpec 4 in favour of explictly passed in kwargs where + # this is used. Works around a warning in Ruby 2.7 + + def class_exec(klass, *args, &block) + if MethodSignature.new(block).has_kw_args_in?(args) + binding.eval(<<-CODE, __FILE__, __LINE__) + kwargs = args.pop + klass.class_exec(*args, **kwargs, &block) + CODE + else + klass.class_exec(*args, &block) + end + end + ruby2_keywords :class_exec if respond_to?(:ruby2_keywords, true) + else + def class_exec(klass, *args, &block) + klass.class_exec(*args, &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/Changelog.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/Changelog.md new file mode 100644 index 0000000000..e1f42050e2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/Changelog.md @@ -0,0 +1,364 @@ +### Development +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.12.0...3-12-maintenance) + +### 3.12.0 / 2022-10-26 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.11.1...v3.12.0) +Enhancements: + +* Add `RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash?` + (Jean byroot Boussier, #535) + +### 3.11.1 / 2022-09-12 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.11.0...v3.11.1) + +Bug Fixes: + +* Fix ripper detection on TruffleRuby. (Brandon Fish, #541) + +### 3.11.0 / 2022-02-09 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.3...v3.11.0) + +No changes. Released to support other RSpec releases. + +### 3.10.3 / 2021-11-03 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.2...v3.10.3) + +Bug Fixes: + +* Use `Mutex#owned?` to allow `RSpec::Support::ReentrantMutex` to work in + nested Fibers on Ruby 3.0 and later. (Benoit Daloze, #503, #504) +* Support `end`-less methods in `RSpec::Support::Source::Token` + so that RSpec won't hang when an `end`-less method raises an error. (Yuji Nakayama, #505) + +### 3.10.2 / 2021-01-28 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.1...v3.10.2) + +Bug Fixes: + +* Fix issue with `RSpec::Support.define_optimized_require_for_rspec` on JRuby + 9.1.17.0 (Jon Rowe, #492) + +### 3.10.1 / 2020-12-27 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.10.0...v3.10.1) + +Bug Fixes: + +* Fix deprecation expectations to fail correctly when + asserting on messages. (Phil Pirozhkov, #453) + +### 3.10.0 / 2020-10-30 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.4...v3.10.0) + +No changes. Released to support other RSpec releases. + +### 3.9.4 / 2020-10-23 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.3...v3.9.4) + +Bug Fixes: + +* Flag ripper as supported on Truffle Ruby. (Brandon Fish, #427) +* Prevent stubbing `File.read` from breaking source extraction. + (Jon Rowe, #431) + +### 3.9.3 / 2020-05-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.2...v3.9.3) + +Bug Fixes: + +* Mark ripper as unsupported on Truffle Ruby. (Brandon Fish, #395) +* Mark ripper as unsupported on JRuby 9.2.0.0. (Brian Hawley, #400) +* Capture `Mutex.new` for our `RSpec::Support:Mutex` in order to + allow stubbing `Mutex.new`. (Jon Rowe, #411) + +### 3.9.2 / 2019-12-30 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Remove unneeded eval. (Matijs van Zuijlen, #394) + +### 3.9.1 / 2019-12-28 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.9.0...v3.9.1) + +Bug Fixes: + +* Remove warning caused by keyword arguments on Ruby 2.7.0. + (Jon Rowe, #392) + +### 3.9.0 / 2019-10-07 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.3...v3.9.0) + +*NO CHANGES* + +Version 3.9.0 was released to allow other RSpec gems to release 3.9.0. + +### 3.8.3 / 2019-10-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.2...v3.8.3) + +Bug Fixes: + +* Escape \r when outputting strings inside arrays. + (Tomita Masahiro, Jon Rowe, #378) +* Ensure that optional hash arguments are recognised correctly vs keyword + arguments. (Evgeni Dzhelyov, #366) + +### 3.8.2 / 2019-06-10 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.1...v3.8.2) + +Bug Fixes: + +* Ensure that an empty hash is recognised as empty keyword arguments when + applicable. (Thomas Walpole, #375) +* Ensure that diffing truthy values produce diffs consistently. + (Lucas Nestor, #377) + +### 3.8.1 / 2019-03-03 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.8.0...v3.8.1) + +Bug Fixes: + +* Ensure that inspecting a `SimpleDelegator` based object works regardless of + visibilty of the `__getobj__` method. (Jon Rowe, #369) + +### 3.8.0 / 2018-08-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.7.1...v3.8.0) + +Bug Fixes: + +* Order hash keys before diffing to improve diff accuracy when using mocked calls. + (James Crisp, #334) + +### 3.7.1 / 2018-01-29 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.7.0...v3.7.1) + +Bug Fixes: + +* Fix source extraction logic so that it does not trigger a `SystemStackError` + when processing deeply nested example groups. (Craig Bass, #343) + +### 3.7.0 / 2017-10-17 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0...v3.7.0) + +Enhancements: + +* Improve compatibility with `--enable-frozen-string-literal` option + on Ruby 2.3+. (Pat Allan, #320) +* Add `Support.class_of` for extracting class of any object. + (Yuji Nakayama, #325) + +Bug Fixes: + +* Fix recursive const support to not blow up when given buggy classes + that raise odd errors from `#to_str`. (Myron Marston, #317) + +### 3.6.0 / 2017-05-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0.beta2...3.6.0) + +Enhancements: + +* Import `Source` classes from rspec-core. (Yuji Nakayama, #315) + +### 3.6.0.beta2 / 2016-12-12 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.6.0.beta1...v3.6.0.beta2) + +No user-facing changes. + +### 3.6.0.beta1 / 2016-10-09 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0...v3.6.0.beta1) + +Bug Fixes: + +* Prevent truncated formatted object output from mangling console codes. (#294, Anson Kelly) + +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta4...v3.5.0) + +**No user facing changes since beta4** + +### 3.5.0.beta4 / 2016-06-05 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta3...v3.5.0.beta4) + +Enhancements: +* Improve `MethodSignature` to better support keyword arguments. (#250, Rob Smith). + +### 3.5.0.beta3 / 2016-04-02 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta2...v3.5.0.beta3) + +Bug Fixes: + +* Fix `EncodedString` to properly handle the behavior of `String#split` + on JRuby when the string contains invalid bytes. (Jon Rowe, #268) +* Fix `ObjectFormatter` so that formatting objects that don't respond to + `#inspect` (such as `BasicObject`) does not cause `NoMethodError`. + (Yuji Nakayama, #269) +* Fix `ObjectFormatter` so that formatting recursive array or hash does not + cause `SystemStackError`. (Yuji Nakayama, #270, #272) + +### 3.5.0.beta2 / 2016-03-10 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta1...v3.5.0.beta2) + +No user-facing changes. + +### 3.5.0.beta1 / 2016-02-06 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.4.1...v3.5.0.beta1) + +Enhancements: + +* Improve formatting of objects by allowing truncation to a pre-configured length. + (Liam M, #256) + +### 3.4.1 / 2015-11-20 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.4.0...v3.4.1) + +Bug Fixes: + +* Fix `RSpec::Support::RubyFeature.ripper_supported?` so it returns + `false` on Rubinius since the Rubinius team has no plans to support + it. This prevents rspec-core from trying to load and use ripper to + extract failure snippets. (Aaron Stone, #251) + +Changes: + +* Remove `VersionChecker` in favor of `ComparableVersion`. (Yuji Nakayama, #266) + +### 3.4.0 / 2015-11-11 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.3.0...v3.4.0) + +Enhancements: + +* Improve formatting of `Delegator` based objects (e.g. `SimpleDelgator`) in + failure messages and diffs. (Andrew Horner, #215) +* Add `ComparableVersion`. (Yuji Nakayama, #245) +* Add `Ripper` support detection. (Yuji Nakayama, #245) + +Bug Fixes: + +* Work around bug in JRuby that reports that `attr_writer` methods + have no parameters, causing RSpec's verifying doubles to wrongly + fail when mocking or stubbing a writer method on JRuby. (Myron Marston, #225) + +### 3.3.0 / 2015-06-12 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.2...v3.3.0) + +Enhancements: + +* Improve formatting of arrays and hashes in failure messages so they + use our custom formatting of matchers, time objects, etc. + (Myron Marston, Nicholas Chmielewski, #205) +* Use improved formatting for diffs as well. (Nicholas Chmielewski, #205) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it checks `expected == actual` rather than + `actual == expected`, which avoids errors in situations where the + `actual` object's `==` is improperly implemented to assume that only + objects of the same type will be given. This allows rspec-mocks' + `anything` to match against objects with buggy `==` definitions. + (Myron Marston, #193) + +### 3.2.2 / 2015-02-23 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.1...v3.2.2) + +Bug Fixes: + +* Fix an encoding issue with `EncodedString#split` when encountering an + invalid byte string. (Benjamin Fleischer, #1760) + +### 3.2.1 / 2015-02-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.2.0...v3.2.1) + +Bug Fixes: + +* Fix `RSpec::CallerFilter` to work on Rubinius 2.2. + (Myron Marston, #169) + +### 3.2.0 / 2015-02-03 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.2...v3.2.0) + +Enhancements: + +* Add extra Ruby type detection. (Jon Rowe, #133) +* Make differ instance re-usable. (Alexey Fedorov, #160) + +Bug Fixes: + +* Do not consider `[]` and `{}` to match when performing fuzzy matching. + (Myron Marston, #157) + +### 3.1.2 / 2014-10-08 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.1...v3.1.2) + +Bug Fixes: + +* Fix method signature to not blow up with a `NoMethodError` on 1.8.7 when + verifying against an RSpec matcher. (Myron Marston, #116) + +### 3.1.1 / 2014-09-26 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.1.0...v3.1.1) + +Bug Fixes: + +* Fix `RSpec::Support::DirectoryMaker` (used by `rspec --init` and + `rails generate rspec:install`) so that it detects absolute paths + on Windows properly. (Scott Archer, #107, #108, #109) (Jon Rowe, #110) + +### 3.1.0 / 2014-09-04 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.4...v3.1.0) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it does not wrongly match a struct against + an array. (Myron Marston, #97) +* Prevent infinitely recursing `#flatten` methods from causing the differ + to hang. (Jon Rowe, #101) + +### 3.0.4 / 2014-08-14 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.3...v3.0.4) + +Bug Fixes: + +* Fix `FuzzyMatcher` so that it does not silence `ArgumentError` raised + from broken implementations of `==`. (Myron Marston, #94) + +### 3.0.3 / 2014-07-21 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.2...v3.0.3) + +Bug Fixes: + +* Fix regression in `Support#method_handle_for` where proxy objects + with method delegated would wrongly not return a method handle. + (Jon Rowe, #90) +* Properly detect Module#prepend support in Ruby 2.1+ (Ben Langfeld, #91) +* Fix `rspec/support/warnings.rb` so it can be loaded and used in + isolation. (Myron Marston, #93) + +### 3.0.2 / 2014-06-20 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.1...v3.0.2) + +* Revert `BlockSignature` change from 3.0.1 because of a ruby bug that + caused it to change the block's behavior (https://bugs.ruby-lang.org/issues/9967). + (Myron Marston, rspec-mocks#721) + +### 3.0.1 / 2014-06-19 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0...v3.0.1) + +* Fix `BlockSignature` so that it correctly differentiates between + required and optional block args. (Myron Marston, rspec-mocks#714) + +### 3.0.0 / 2014-06-01 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.rc1...v3.0.0) + +### 3.0.0.rc1 / 2014-05-18 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.beta2...v3.0.0.rc1) + +### 3.0.0.beta2 / 2014-02-17 +[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.0.0.beta1...v3.0.0.beta2) + +Bug Fixes: + +* Issue message when :replacement is passed to `RSpec.warn_with`. (Jon Rowe) + +### 3.0.0.beta1 / 2013-11-07 +[Full Changelog](https://github.com/rspec/rspec-support/compare/0dc12d1bdbbacc757a9989f8c09cd08ef3a4837e...v3.0.0.beta1) + +Initial release. diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/LICENSE.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/LICENSE.md new file mode 100644 index 0000000000..08aa3abbb5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/LICENSE.md @@ -0,0 +1,23 @@ +The MIT License (MIT) +==================== + +* Copyright © 2013 David Chelimsky, Myron Marston, Jon Rowe, Sam Phippen, Xavier Shay, Bradley Schaefer + +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.0.0/gems/rspec-support-3.12.0/README.md b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/README.md new file mode 100644 index 0000000000..bb88209df4 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/README.md @@ -0,0 +1,40 @@ +# RSpec::Support [![Build Status](https://github.com/rspec/rspec-support/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-support/actions) + +`RSpec::Support` provides common functionality to `RSpec::Core`, +`RSpec::Expectations` and `RSpec::Mocks`. It is considered +suitable for internal use only at this time. + +## Installation / Usage + +Install one or more of the `RSpec` gems. + +Want to run against the `main` branch? You'll need to include the dependent +RSpec repos as well. Add the following to your `Gemfile`: + +```ruby +%w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main' +end +``` + +## Contributing + +Once you've set up the environment, you'll need to cd into the working +directory of whichever repo you want to work in. From there you can run the +specs and cucumber features, and make patches. + +NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You +can treat each RSpec repo as an independent project. + +- [Build details](BUILD_DETAIL.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Detailed contributing guide](CONTRIBUTING.md) +- [Development setup guide](DEVELOPMENT.md) + +## Patches + +Please submit a pull request or a github issue. If you submit an issue, please +include a link to either of: + +* a gist (or equivalent) of the patch +* a branch or commit in your github fork of the repo diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support.rb new file mode 100644 index 0000000000..b85eae3c25 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support.rb @@ -0,0 +1,154 @@ +module RSpec + module Support + # @api private + # + # Defines a helper method that is optimized to require files from the + # named lib. The passed block MUST be `{ |f| require_relative f }` + # because for `require_relative` to work properly from within the named + # lib the line of code must be IN that lib. + # + # `require_relative` is preferred when available because it is always O(1), + # regardless of the number of dirs in $LOAD_PATH. `require`, on the other + # hand, does a linear O(N) search over the dirs in the $LOAD_PATH until + # it can resolve the file relative to one of the dirs. + def self.define_optimized_require_for_rspec(lib, &require_relative) + name = "require_rspec_#{lib}" + + if RUBY_PLATFORM == 'java' && !Kernel.respond_to?(:require) + # JRuby 9.1.17.0 has developed a regression for require + (class << self; self; end).__send__(:define_method, name) do |f| + Kernel.send(:require, "rspec/#{lib}/#{f}") + end + elsif Kernel.respond_to?(:require_relative) + (class << self; self; end).__send__(:define_method, name) do |f| + require_relative.call("#{lib}/#{f}") + end + else + (class << self; self; end).__send__(:define_method, name) do |f| + require "rspec/#{lib}/#{f}" + end + end + end + + define_optimized_require_for_rspec(:support) { |f| require_relative(f) } + require_rspec_support "version" + require_rspec_support "ruby_features" + + # @api private + KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method) + + # @api private + # + # Used internally to get a method handle for a particular object + # and method name. + # + # Includes handling for a few special cases: + # + # - Objects that redefine #method (e.g. an HTTPRequest struct) + # - BasicObject subclasses that mixin a Kernel dup (e.g. SimpleDelegator) + # - Objects that undefine method and delegate everything to another + # object (e.g. Mongoid association objects) + if RubyFeatures.supports_rebinding_module_methods? + def self.method_handle_for(object, method_name) + KERNEL_METHOD_METHOD.bind(object).call(method_name) + rescue NameError => original + begin + handle = object.method(method_name) + raise original unless handle.is_a? Method + handle + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + raise original + end + end + else + def self.method_handle_for(object, method_name) + if ::Kernel === object + KERNEL_METHOD_METHOD.bind(object).call(method_name) + else + object.method(method_name) + end + rescue NameError => original + begin + handle = object.method(method_name) + raise original unless handle.is_a? Method + handle + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + raise original + end + end + end + + # @api private + # + # Used internally to get a class of a given object, even if it does not respond to #class. + def self.class_of(object) + object.class + rescue NoMethodError + singleton_class = class << object; self; end + singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) } + end + + # A single thread local variable so we don't excessively pollute that namespace. + def self.thread_local_data + Thread.current[:__rspec] ||= {} + end + + # @api private + def self.failure_notifier=(callable) + thread_local_data[:failure_notifier] = callable + end + + # @private + DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure } + + # @api private + def self.failure_notifier + thread_local_data[:failure_notifier] || DEFAULT_FAILURE_NOTIFIER + end + + # @api private + def self.notify_failure(failure, options={}) + failure_notifier.call(failure, options) + end + + # @api private + def self.with_failure_notifier(callable) + orig_notifier = failure_notifier + self.failure_notifier = callable + yield + ensure + self.failure_notifier = orig_notifier + end + + class << self + # @api private + attr_writer :warning_notifier + end + + # @private + DEFAULT_WARNING_NOTIFIER = lambda { |warning| ::Kernel.warn warning } + + # @api private + def self.warning_notifier + @warning_notifier ||= DEFAULT_WARNING_NOTIFIER + end + + # @private + module AllExceptionsExceptOnesWeMustNotRescue + # These exceptions are dangerous to rescue as rescuing them + # would interfere with things we should not interfere with. + AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit] + + def self.===(exception) + AVOID_RESCUING.none? { |ar| ar === exception } + end + end + + # The Differ is only needed when a spec fails with a diffable failure. + # In the more common case of all specs passing or the only failures being + # non-diffable, we can avoid the extra cost of loading the differ, diff-lcs, + # pp, etc by avoiding an unnecessary require. Instead, autoload will take + # care of loading the differ on first use. + autoload :Differ, "rspec/support/differ" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/caller_filter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/caller_filter.rb new file mode 100644 index 0000000000..fe210f74a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/caller_filter.rb @@ -0,0 +1,83 @@ +RSpec::Support.require_rspec_support "ruby_features" + +module RSpec + # Consistent implementation for "cleaning" the caller method to strip out + # non-rspec lines. This enables errors to be reported at the call site in + # the code using the library, which is far more useful than the particular + # internal method that raised an error. + class CallerFilter + RSPEC_LIBS = %w[ + core + mocks + expectations + support + matchers + rails + ] + + ADDITIONAL_TOP_LEVEL_FILES = %w[ autorun ] + + LIB_REGEX = %r{/lib/rspec/(#{(RSPEC_LIBS + ADDITIONAL_TOP_LEVEL_FILES).join('|')})(\.rb|/)} + + # rubygems/core_ext/kernel_require.rb isn't actually part of rspec (obviously) but we want + # it ignored when we are looking for the first meaningful line of the backtrace outside + # of RSpec. It can show up in the backtrace as the immediate first caller + # when `CallerFilter.first_non_rspec_line` is called from the top level of a required + # file, but it depends on if rubygems is loaded or not. We don't want to have to deal + # with this complexity in our `RSpec.deprecate` calls, so we ignore it here. + IGNORE_REGEX = Regexp.union(LIB_REGEX, "rubygems/core_ext/kernel_require.rb", "(other) + other = self.class.new(other) unless other.is_a?(self.class) + + return 0 if string == other.string + + longer_segment_count = [self, other].map { |version| version.segments.count }.max + + longer_segment_count.times do |index| + self_segment = segments[index] || 0 + other_segment = other.segments[index] || 0 + + if self_segment.class == other_segment.class + result = self_segment <=> other_segment + return result unless result == 0 + else + return self_segment.is_a?(String) ? -1 : 1 + end + end + + 0 + end + + def segments + @segments ||= string.scan(/[a-z]+|\d+/i).map do |segment| + if segment =~ /\A\d+\z/ + segment.to_i + else + segment + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/differ.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/differ.rb new file mode 100644 index 0000000000..71a8694cea --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/differ.rb @@ -0,0 +1,215 @@ +RSpec::Support.require_rspec_support 'encoded_string' +RSpec::Support.require_rspec_support 'hunk_generator' +RSpec::Support.require_rspec_support "object_formatter" + +require 'pp' + +module RSpec + module Support + # rubocop:disable Metrics/ClassLength + class Differ + def diff(actual, expected) + diff = "" + + unless actual.nil? || expected.nil? + if all_strings?(actual, expected) + if any_multiline_strings?(actual, expected) + diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected)) + end + elsif no_procs?(actual, expected) && no_numbers?(actual, expected) + diff = diff_as_object(actual, expected) + end + end + + diff.to_s + end + + # rubocop:disable Metrics/MethodLength + def diff_as_string(actual, expected) + encoding = EncodedString.pick_encoding(actual, expected) + + actual = EncodedString.new(actual, encoding) + expected = EncodedString.new(expected, encoding) + + output = EncodedString.new("\n", encoding) + hunks = build_hunks(actual, expected) + + hunks.each_cons(2) do |prev_hunk, current_hunk| + begin + if current_hunk.overlaps?(prev_hunk) + add_old_hunk_to_hunk(current_hunk, prev_hunk) + else + add_to_output(output, prev_hunk.diff(format_type).to_s) + end + ensure + add_to_output(output, "\n") + end + end + + finalize_output(output, hunks.last.diff(format_type).to_s) if hunks.last + + color_diff output + rescue Encoding::CompatibilityError + handle_encoding_errors(actual, expected) + end + # rubocop:enable Metrics/MethodLength + + def diff_as_object(actual, expected) + actual_as_string = object_to_string(actual) + expected_as_string = object_to_string(expected) + diff_as_string(actual_as_string, expected_as_string) + end + + def color? + @color + end + + def initialize(opts={}) + @color = opts.fetch(:color, false) + @object_preparer = opts.fetch(:object_preparer, lambda { |string| string }) + end + + private + + def no_procs?(*args) + safely_flatten(args).none? { |a| Proc === a } + end + + def all_strings?(*args) + safely_flatten(args).all? { |a| String === a } + end + + def any_multiline_strings?(*args) + all_strings?(*args) && safely_flatten(args).any? { |a| multiline?(a) } + end + + def no_numbers?(*args) + safely_flatten(args).none? { |a| Numeric === a } + end + + def coerce_to_string(string_or_array) + return string_or_array unless Array === string_or_array + diffably_stringify(string_or_array).join("\n") + end + + def diffably_stringify(array) + array.map do |entry| + if Array === entry + entry.inspect + else + entry.to_s.gsub("\n", "\\n").gsub("\r", "\\r") + end + end + end + + if String.method_defined?(:encoding) + def multiline?(string) + string.include?("\n".encode(string.encoding)) + end + else + def multiline?(string) + string.include?("\n") + end + end + + def build_hunks(actual, expected) + HunkGenerator.new(actual, expected).hunks + end + + def finalize_output(output, final_line) + add_to_output(output, final_line) + add_to_output(output, "\n") + end + + def add_to_output(output, string) + output << string + end + + def add_old_hunk_to_hunk(hunk, oldhunk) + hunk.merge(oldhunk) + end + + def safely_flatten(array) + array = array.flatten(1) until (array == array.flatten(1)) + array + end + + def format_type + :unified + end + + def color(text, color_code) + "\e[#{color_code}m#{text}\e[0m" + end + + def red(text) + color(text, 31) + end + + def green(text) + color(text, 32) + end + + def blue(text) + color(text, 34) + end + + def normal(text) + color(text, 0) + end + + def color_diff(diff) + return diff unless color? + + diff.lines.map do |line| + case line[0].chr + when "+" + green line + when "-" + red line + when "@" + line[1].chr == "@" ? blue(line) : normal(line) + else + normal(line) + end + end.join + end + + def object_to_string(object) + object = @object_preparer.call(object) + case object + when Hash + hash_to_string(object) + when Array + PP.pp(ObjectFormatter.prepare_for_inspection(object), "".dup) + when String + object =~ /\n/ ? object : object.inspect + else + PP.pp(object, "".dup) + end + end + + def hash_to_string(hash) + formatted_hash = ObjectFormatter.prepare_for_inspection(hash) + formatted_hash.keys.sort_by { |k| k.to_s }.map do |key| + pp_key = PP.singleline_pp(key, "".dup) + pp_value = PP.singleline_pp(formatted_hash[key], "".dup) + + "#{pp_key} => #{pp_value}," + end.join("\n") + end + + def handle_encoding_errors(actual, expected) + if actual.source_encoding != expected.source_encoding + "Could not produce a diff because the encoding of the actual string " \ + "(#{actual.source_encoding}) differs from the encoding of the expected " \ + "string (#{expected.source_encoding})" + else + "Could not produce a diff because of the encoding of the string " \ + "(#{expected.source_encoding})" + end + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/directory_maker.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/directory_maker.rb new file mode 100644 index 0000000000..39a280e3ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/directory_maker.rb @@ -0,0 +1,63 @@ +RSpec::Support.require_rspec_support 'ruby_features' + +module RSpec + module Support + # @api private + # + # Replacement for fileutils#mkdir_p because we don't want to require parts + # of stdlib in RSpec. + class DirectoryMaker + # @api private + # + # Implements nested directory construction + def self.mkdir_p(path) + stack = generate_stack(path) + path.split(File::SEPARATOR).each do |part| + stack = generate_path(stack, part) + begin + Dir.mkdir(stack) unless directory_exists?(stack) + rescue Errno::EEXIST => e + raise e unless directory_exists?(stack) + rescue Errno::ENOTDIR => e + raise Errno::EEXIST, e.message + end + end + end + + if OS.windows_file_path? + def self.generate_stack(path) + if path.start_with?(File::SEPARATOR) + File::SEPARATOR + elsif path[1] == ':' + '' + else + '.' + end + end + def self.generate_path(stack, part) + if stack == '' + part + elsif stack == File::SEPARATOR + File.join('', part) + else + File.join(stack, part) + end + end + else + def self.generate_stack(path) + path.start_with?(File::SEPARATOR) ? File::SEPARATOR : "." + end + def self.generate_path(stack, part) + File.join(stack, part) + end + end + + def self.directory_exists?(dirname) + File.exist?(dirname) && File.directory?(dirname) + end + private_class_method :directory_exists? + private_class_method :generate_stack + private_class_method :generate_path + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/encoded_string.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/encoded_string.rb new file mode 100644 index 0000000000..80c69a8813 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/encoded_string.rb @@ -0,0 +1,161 @@ +module RSpec + module Support + # @private + class EncodedString + # Reduce allocations by storing constants. + UTF_8 = "UTF-8" + US_ASCII = "US-ASCII" + + # Ruby's default replacement string is: + # U+FFFD ("\xEF\xBF\xBD"), for Unicode encoding forms, else + # ? ("\x3F") + REPLACE = "?" + + def initialize(string, encoding=nil) + @encoding = encoding + @source_encoding = detect_source_encoding(string) + @string = matching_encoding(string) + end + attr_reader :source_encoding + + delegated_methods = String.instance_methods.map(&:to_s) & %w[eql? lines == encoding empty?] + delegated_methods.each do |name| + define_method(name) { |*args, &block| @string.__send__(name, *args, &block) } + end + + def <<(string) + @string << matching_encoding(string) + end + + if Ruby.jruby? + def split(regex_or_string) + @string.split(matching_encoding(regex_or_string)) + rescue ArgumentError + # JRuby raises an ArgumentError when splitting a source string that + # contains invalid bytes. + remove_invalid_bytes(@string).split regex_or_string + end + else + def split(regex_or_string) + @string.split(matching_encoding(regex_or_string)) + end + end + + def to_s + @string + end + alias :to_str :to_s + + if String.method_defined?(:encoding) + + private + + # Encoding Exceptions: + # + # Raised by Encoding and String methods: + # Encoding::UndefinedConversionError: + # when a transcoding operation fails + # if the String contains characters invalid for the target encoding + # e.g. "\x80".encode('UTF-8','ASCII-8BIT') + # vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '') + # # => '' + # Encoding::CompatibilityError + # when Encoding.compatible?(str1, str2) is nil + # e.g. utf_16le_emoji_string.split("\n") + # e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string + # Encoding::InvalidByteSequenceError: + # when the string being transcoded contains a byte invalid for + # either the source or target encoding + # e.g. "\x80".encode('UTF-8','US-ASCII') + # vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '') + # # => '' + # ArgumentError + # when operating on a string with invalid bytes + # e.g."\x80".split("\n") + # TypeError + # when a symbol is passed as an encoding + # Encoding.find(:"UTF-8") + # when calling force_encoding on an object + # that doesn't respond to #to_str + # + # Raised by transcoding methods: + # Encoding::ConverterNotFoundError: + # when a named encoding does not correspond with a known converter + # e.g. 'abc'.force_encoding('UTF-8').encode('foo') + # or a converter path cannot be found + # e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule') + # + # Raised by byte <-> char conversions + # RangeError: out of char range + # e.g. the UTF-16LE emoji: 128169.chr + def matching_encoding(string) + string = remove_invalid_bytes(string) + string.encode(@encoding) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError + # Originally defined as a constant to avoid unneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 + # + # In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence + # see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176 + # https://www.ruby-forum.com/topic/6861247 + # https://twitter.com/nalsh/status/553413844685438976 + # + # For example, given: + # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a + # + # On MRI 2.1 or above: 63 # '?' + # else : 128 # "\x80" + # + string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) + rescue Encoding::ConverterNotFoundError + # Originally defined as a constant to avoid unneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 + string.dup.force_encoding(@encoding).encode(:invalid => :replace, :replace => REPLACE) + end + + # Prevents raising ArgumentError + if String.method_defined?(:scrub) + # https://github.com/ruby/ruby/blob/eeb05e8c11/doc/NEWS-2.1.0#L120-L123 + # https://github.com/ruby/ruby/blob/v2_1_0/string.c#L8242 + # https://github.com/hsbt/string-scrub + # https://github.com/rubinius/rubinius/blob/v2.5.2/kernel/common/string.rb#L1913-L1972 + def remove_invalid_bytes(string) + string.scrub(REPLACE) + end + else + # http://stackoverflow.com/a/8711118/879854 + # Loop over chars in a string replacing chars + # with invalid encoding, which is a pretty good proxy + # for the invalid byte sequence that causes an ArgumentError + def remove_invalid_bytes(string) + string.chars.map do |char| + char.valid_encoding? ? char : REPLACE + end.join + end + end + + def detect_source_encoding(string) + string.encoding + end + + def self.pick_encoding(source_a, source_b) + Encoding.compatible?(source_a, source_b) || Encoding.default_external + end + else + + def self.pick_encoding(_source_a, _source_b) + end + + private + + def matching_encoding(string) + string + end + + def detect_source_encoding(_string) + US_ASCII + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/fuzzy_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/fuzzy_matcher.rb new file mode 100644 index 0000000000..4151949540 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/fuzzy_matcher.rb @@ -0,0 +1,48 @@ +module RSpec + module Support + # Provides a means to fuzzy-match between two arbitrary objects. + # Understands array/hash nesting. Uses `===` or `==` to + # perform the matching. + module FuzzyMatcher + # @api private + def self.values_match?(expected, actual) + if Hash === actual + return hashes_match?(expected, actual) if Hash === expected + elsif Array === expected && Enumerable === actual && !(Struct === actual) + return arrays_match?(expected, actual.to_a) + end + + return true if expected == actual + + begin + expected === actual + rescue ArgumentError + # Some objects, like 0-arg lambdas on 1.9+, raise + # ArgumentError for `expected === actual`. + false + end + end + + # @private + def self.arrays_match?(expected_list, actual_list) + return false if expected_list.size != actual_list.size + + expected_list.zip(actual_list).all? do |expected, actual| + values_match?(expected, actual) + end + end + + # @private + def self.hashes_match?(expected_hash, actual_hash) + return false if expected_hash.size != actual_hash.size + + expected_hash.all? do |expected_key, expected_value| + actual_value = actual_hash.fetch(expected_key) { return false } + values_match?(expected_value, actual_value) + end + end + + private_class_method :arrays_match?, :hashes_match? + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/hunk_generator.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/hunk_generator.rb new file mode 100644 index 0000000000..382579e83a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/hunk_generator.rb @@ -0,0 +1,47 @@ +require 'diff/lcs' +require 'diff/lcs/hunk' + +module RSpec + module Support + # @private + class HunkGenerator + def initialize(actual, expected) + @actual = actual + @expected = expected + end + + def hunks + @file_length_difference = 0 + @hunks ||= diffs.map do |piece| + build_hunk(piece) + end + end + + private + + def diffs + Diff::LCS.diff(expected_lines, actual_lines) + end + + def expected_lines + @expected.split("\n").map! { |e| e.chomp } + end + + def actual_lines + @actual.split("\n").map! { |e| e.chomp } + end + + def build_hunk(piece) + Diff::LCS::Hunk.new( + expected_lines, actual_lines, piece, context_lines, @file_length_difference + ).tap do |h| + @file_length_difference = h.file_length_difference + end + end + + def context_lines + 3 + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/matcher_definition.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/matcher_definition.rb new file mode 100644 index 0000000000..d653cc11df --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/matcher_definition.rb @@ -0,0 +1,42 @@ +module RSpec + module Support + # @private + def self.matcher_definitions + @matcher_definitions ||= [] + end + + # Used internally to break cyclic dependency between mocks, expectations, + # and support. We don't currently have a consistent implementation of our + # matchers, though we are considering changing that: + # https://github.com/rspec/rspec-mocks/issues/513 + # + # @private + def self.register_matcher_definition(&block) + matcher_definitions << block + end + + # Remove a previously registered matcher. Useful for cleaning up after + # yourself in specs. + # + # @private + def self.deregister_matcher_definition(&block) + matcher_definitions.delete(block) + end + + # @private + def self.is_a_matcher?(object) + matcher_definitions.any? { |md| md.call(object) } + end + + # @api private + # + # gives a string representation of an object for use in RSpec descriptions + def self.rspec_description_for_object(object) + if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description) + object.description + else + object + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/method_signature_verifier.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/method_signature_verifier.rb new file mode 100644 index 0000000000..c4eb432ad6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/method_signature_verifier.rb @@ -0,0 +1,438 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "ruby_features" +RSpec::Support.require_rspec_support "matcher_definition" + +module RSpec + module Support + # Extracts info about the number of arguments and allowed/required + # keyword args of a given method. + # + # @private + class MethodSignature # rubocop:disable Metrics/ClassLength + attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args + + def initialize(method) + @method = method + @optional_kw_args = [] + @required_kw_args = [] + classify_parameters + end + + def non_kw_args_arity_description + case max_non_kw_args + when min_non_kw_args then min_non_kw_args.to_s + when INFINITY then "#{min_non_kw_args} or more" + else "#{min_non_kw_args} to #{max_non_kw_args}" + end + end + + def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count) + return true if positional_arg_count.nil? + + min_non_kw_args <= positional_arg_count && + optional_max_arg_count <= max_non_kw_args + end + + def classify_arity(arity=@method.arity) + if arity < 0 + # `~` inverts the one's complement and gives us the + # number of required args + @min_non_kw_args = ~arity + @max_non_kw_args = INFINITY + else + @min_non_kw_args = arity + @max_non_kw_args = arity + end + end + + if RubyFeatures.optional_and_splat_args_supported? + def description + @description ||= begin + parts = [] + + unless non_kw_args_arity_description == "0" + parts << "arity of #{non_kw_args_arity_description}" + end + + if @optional_kw_args.any? + parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})" + end + + if @required_kw_args.any? + parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})" + end + + parts << "any additional keyword args" if @allows_any_kw_args + + parts.join(" and ") + end + end + + def missing_kw_args_from(given_kw_args) + @required_kw_args - given_kw_args + end + + def invalid_kw_args_from(given_kw_args) + return [] if @allows_any_kw_args + given_kw_args - @allowed_kw_args + end + + # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments + # the rest will be grouped in another Hash and passed as positional argument. + def has_kw_args_in?(args) + Hash === args.last && + could_contain_kw_args?(args) && + (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) }) + end + + # Without considering what the last arg is, could it + # contain keyword arguments? + def could_contain_kw_args?(args) + return false if args.count <= min_non_kw_args + + @allows_any_kw_args || @allowed_kw_args.any? + end + + def arbitrary_kw_args? + @allows_any_kw_args + end + + def unlimited_args? + @max_non_kw_args == INFINITY + end + + def classify_parameters + optional_non_kw_args = @min_non_kw_args = 0 + @allows_any_kw_args = false + + @method.parameters.each do |(type, name)| + case type + # def foo(a:) + when :keyreq then @required_kw_args << name + # def foo(a: 1) + when :key then @optional_kw_args << name + # def foo(**kw_args) + when :keyrest then @allows_any_kw_args = true + # def foo(a) + when :req then @min_non_kw_args += 1 + # def foo(a = 1) + when :opt then optional_non_kw_args += 1 + # def foo(*a) + when :rest then optional_non_kw_args = INFINITY + end + end + + @max_non_kw_args = @min_non_kw_args + optional_non_kw_args + @allowed_kw_args = @required_kw_args + @optional_kw_args + end + else + def description + "arity of #{non_kw_args_arity_description}" + end + + def missing_kw_args_from(_given_kw_args) + [] + end + + def invalid_kw_args_from(_given_kw_args) + [] + end + + def has_kw_args_in?(_args) + false + end + + def could_contain_kw_args?(*) + false + end + + def arbitrary_kw_args? + false + end + + def unlimited_args? + false + end + + alias_method :classify_parameters, :classify_arity + end + + INFINITY = 1 / 0.0 + end + + if RSpec::Support::Ruby.jruby? + # JRuby has only partial support for UnboundMethod#parameters, so we fall back on using #arity + # https://github.com/jruby/jruby/issues/2816 and https://github.com/jruby/jruby/issues/2817 + if RubyFeatures.optional_and_splat_args_supported? && + Java::JavaLang::String.instance_method(:char_at).parameters == [] + + class MethodSignature < remove_const(:MethodSignature) + private + + def classify_parameters + super + if (arity = @method.arity) != 0 && @method.parameters.empty? + classify_arity(arity) + end + end + end + end + + # JRuby used to always report -1 arity for Java proxy methods. + # The workaround essentially makes use of Java's introspection to figure + # out matching methods (which could be more than one partly because Java + # supports multiple overloads, and partly because JRuby introduces + # aliases to make method names look more Rubyesque). If there is only a + # single match, we can use that methods arity directly instead of the + # default -1 arity. + # + # This workaround only works for Java proxy methods, and in order to + # support regular methods and blocks, we need to be careful about calling + # owner and java_class as they might not be available + if Java::JavaLang::String.instance_method(:char_at).arity == -1 + class MethodSignature < remove_const(:MethodSignature) + private + + def classify_parameters + super + return unless @method.arity == -1 + return unless @method.respond_to?(:owner) + return unless @method.owner.respond_to?(:java_class) + java_instance_methods = @method.owner.java_class.java_instance_methods + compatible_overloads = java_instance_methods.select do |java_method| + @method == @method.owner.instance_method(java_method.name) + end + if compatible_overloads.size == 1 + classify_arity(compatible_overloads.first.arity) + end + end + end + end + end + + # Encapsulates expectations about the number of arguments and + # allowed/required keyword args of a given method. + # + # @api private + class MethodSignatureExpectation + def initialize + @min_count = nil + @max_count = nil + @keywords = [] + + @expect_unlimited_arguments = false + @expect_arbitrary_keywords = false + end + + attr_reader :min_count, :max_count, :keywords + + attr_accessor :expect_unlimited_arguments, :expect_arbitrary_keywords + + def max_count=(number) + raise ArgumentError, 'must be a non-negative integer or nil' \ + unless number.nil? || (number.is_a?(Integer) && number >= 0) + + @max_count = number + end + + def min_count=(number) + raise ArgumentError, 'must be a non-negative integer or nil' \ + unless number.nil? || (number.is_a?(Integer) && number >= 0) + + @min_count = number + end + + def empty? + @min_count.nil? && + @keywords.to_a.empty? && + !@expect_arbitrary_keywords && + !@expect_unlimited_arguments + end + + def keywords=(values) + @keywords = values.to_a || [] + end + end + + # Deals with the slightly different semantics of block arguments. + # For methods, arguments are required unless a default value is provided. + # For blocks, arguments are optional, even if no default value is provided. + # + # However, we want to treat block args as required since you virtually + # always want to pass a value for each received argument and our + # `and_yield` has treated block args as required for many years. + # + # @api private + class BlockSignature < MethodSignature + if RubyFeatures.optional_and_splat_args_supported? + def classify_parameters + super + @min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY + end + end + end + + # Abstract base class for signature verifiers. + # + # @api private + class MethodSignatureVerifier + attr_reader :non_kw_args, :kw_args, :min_non_kw_args, :max_non_kw_args + + def initialize(signature, args=[]) + @signature = signature + @non_kw_args, @kw_args = split_args(*args) + @min_non_kw_args = @max_non_kw_args = @non_kw_args + @arbitrary_kw_args = @unlimited_args = false + end + + def with_expectation(expectation) # rubocop:disable Metrics/MethodLength + return self unless MethodSignatureExpectation === expectation + + if expectation.empty? + @min_non_kw_args = @max_non_kw_args = @non_kw_args = nil + @kw_args = [] + else + @min_non_kw_args = @non_kw_args = expectation.min_count || 0 + @max_non_kw_args = expectation.max_count || @min_non_kw_args + + if RubyFeatures.optional_and_splat_args_supported? + @unlimited_args = expectation.expect_unlimited_arguments + else + @unlimited_args = false + end + + if RubyFeatures.kw_args_supported? + @kw_args = expectation.keywords + @arbitrary_kw_args = expectation.expect_arbitrary_keywords + else + @kw_args = [] + @arbitrary_kw_args = false + end + end + + self + end + + def valid? + missing_kw_args.empty? && + invalid_kw_args.empty? && + valid_non_kw_args? && + arbitrary_kw_args? && + unlimited_args? + end + + def error_message + if missing_kw_args.any? + "Missing required keyword arguments: %s" % [ + missing_kw_args.join(", ") + ] + elsif invalid_kw_args.any? + "Invalid keyword arguments provided: %s" % [ + invalid_kw_args.join(", ") + ] + elsif !valid_non_kw_args? + "Wrong number of arguments. Expected %s, got %s." % [ + @signature.non_kw_args_arity_description, + non_kw_args + ] + end + end + + private + + def valid_non_kw_args? + @signature.valid_non_kw_args?(min_non_kw_args, max_non_kw_args) + end + + def missing_kw_args + @signature.missing_kw_args_from(kw_args) + end + + def invalid_kw_args + @signature.invalid_kw_args_from(kw_args) + end + + def arbitrary_kw_args? + !@arbitrary_kw_args || @signature.arbitrary_kw_args? + end + + def unlimited_args? + !@unlimited_args || @signature.unlimited_args? + end + + def split_args(*args) + kw_args = if @signature.has_kw_args_in?(args) + last = args.pop + non_kw_args = last.reject { |k, _| k.is_a?(Symbol) } + if non_kw_args.empty? + last.keys + else + args << non_kw_args + last.select { |k, _| k.is_a?(Symbol) }.keys + end + else + [] + end + + [args.length, kw_args] + end + end + + # Figures out wether a given method can accept various arguments. + # Surprisingly non-trivial. + # + # @private + StrictSignatureVerifier = MethodSignatureVerifier + + # Allows matchers to be used instead of providing keyword arguments. In + # practice, when this happens only the arity of the method is verified. + # + # @private + class LooseSignatureVerifier < MethodSignatureVerifier + private + + def split_args(*args) + if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args) + args.pop + @signature = SignatureWithKeywordArgumentsMatcher.new(@signature) + end + + super(*args) + end + + # If a matcher is used in a signature in place of keyword arguments, all + # keyword argument validation needs to be skipped since the matcher is + # opaque. + # + # Instead, keyword arguments will be validated when the method is called + # and they are actually known. + # + # @private + class SignatureWithKeywordArgumentsMatcher + def initialize(signature) + @signature = signature + end + + def missing_kw_args_from(_kw_args) + [] + end + + def invalid_kw_args_from(_kw_args) + [] + end + + def non_kw_args_arity_description + @signature.non_kw_args_arity_description + end + + def valid_non_kw_args?(*args) + @signature.valid_non_kw_args?(*args) + end + + def has_kw_args_in?(args) + @signature.has_kw_args_in?(args) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/mutex.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/mutex.rb new file mode 100644 index 0000000000..778e9bb433 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/mutex.rb @@ -0,0 +1,73 @@ +module RSpec + module Support + # On 1.8.7, it's in the stdlib. + # We don't want to load the stdlib, b/c this is a test tool, and can affect + # the test environment, causing tests to pass where they should fail. + # + # So we're transcribing/modifying it from + # https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56 + # Some methods we don't need are deleted. Anything I don't + # understand (there's quite a bit, actually) is left in. + # + # Some formatting changes are made to appease the robot overlord: + # https://travis-ci.org/rspec/rspec-core/jobs/54410874 + # @private + class Mutex + def initialize + @waiting = [] + @locked = false + @waiting.taint + taint + end + + # @private + def lock + while Thread.critical = true && @locked + @waiting.push Thread.current + Thread.stop + end + @locked = true + Thread.critical = false + self + end + + # @private + def unlock + return unless @locked + Thread.critical = true + @locked = false + wakeup_and_run_waiting_thread + self + end + + # @private + def synchronize + lock + begin + yield + ensure + unlock + end + end + + private + + def wakeup_and_run_waiting_thread + begin + t = @waiting.shift + t.wakeup if t + rescue ThreadError + retry + end + Thread.critical = false + begin + t.run if t + rescue ThreadError + :noop + end + end + + # Avoid warnings for library wide checks spec + end unless defined?(::RSpec::Support::Mutex) || defined?(::Mutex) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/object_formatter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/object_formatter.rb new file mode 100644 index 0000000000..2798a57b7b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/object_formatter.rb @@ -0,0 +1,275 @@ +RSpec::Support.require_rspec_support 'matcher_definition' + +module RSpec + module Support + # Provide additional output details beyond what `inspect` provides when + # printing Time, DateTime, or BigDecimal + # @api private + class ObjectFormatter # rubocop:disable Metrics/ClassLength + ELLIPSIS = "..." + + attr_accessor :max_formatted_output_length + + # Methods are deferred to a default instance of the class to maintain the interface + # For example, calling ObjectFormatter.format is still possible + def self.default_instance + @default_instance ||= new + end + + def self.format(object) + default_instance.format(object) + end + + def self.prepare_for_inspection(object) + default_instance.prepare_for_inspection(object) + end + + def initialize(max_formatted_output_length=200) + @max_formatted_output_length = max_formatted_output_length + @current_structure_stack = [] + end + + def format(object) + if max_formatted_output_length.nil? + prepare_for_inspection(object).inspect + else + formatted_object = prepare_for_inspection(object).inspect + if formatted_object.length < max_formatted_output_length + formatted_object + else + beginning = truncate_string formatted_object, 0, max_formatted_output_length / 2 + ending = truncate_string formatted_object, -max_formatted_output_length / 2, -1 + beginning + ELLIPSIS + ending + end + end + end + + # Prepares the provided object to be formatted by wrapping it as needed + # in something that, when `inspect` is called on it, will produce the + # desired output. + # + # This allows us to apply the desired formatting to hash/array data structures + # at any level of nesting, simply by walking that structure and replacing items + # with custom items that have `inspect` defined to return the desired output + # for that item. Then we can just use `Array#inspect` or `Hash#inspect` to + # format the entire thing. + def prepare_for_inspection(object) + case object + when Array + prepare_array(object) + when Hash + prepare_hash(object) + else + inspector_class = INSPECTOR_CLASSES.find { |inspector| inspector.can_inspect?(object) } + inspector_class.new(object, self) + end + end + + def prepare_array(array) + with_entering_structure(array) do + array.map { |element| prepare_element(element) } + end + end + + def prepare_hash(input_hash) + with_entering_structure(input_hash) do + sort_hash_keys(input_hash).inject({}) do |output_hash, key_and_value| + key, value = key_and_value.map { |element| prepare_element(element) } + output_hash[key] = value + output_hash + end + end + end + + def sort_hash_keys(input_hash) + if input_hash.keys.all? { |k| k.is_a?(String) || k.is_a?(Symbol) } + Hash[input_hash.sort_by { |k, _v| k.to_s }] + else + input_hash + end + end + + def prepare_element(element) + if recursive_structure?(element) + case element + when Array then InspectableItem.new('[...]') + when Hash then InspectableItem.new('{...}') + else raise # This won't happen + end + else + prepare_for_inspection(element) + end + end + + def with_entering_structure(structure) + @current_structure_stack.push(structure) + return_value = yield + @current_structure_stack.pop + return_value + end + + def recursive_structure?(object) + @current_structure_stack.any? { |seen_structure| seen_structure.equal?(object) } + end + + InspectableItem = Struct.new(:text) do + def inspect + text + end + + def pretty_print(pp) + pp.text(text) + end + end + + BaseInspector = Struct.new(:object, :formatter) do + def self.can_inspect?(_object) + raise NotImplementedError + end + + def inspect + raise NotImplementedError + end + + def pretty_print(pp) + pp.text(inspect) + end + end + + class TimeInspector < BaseInspector + FORMAT = "%Y-%m-%d %H:%M:%S" + + def self.can_inspect?(object) + Time === object + end + + if Time.method_defined?(:nsec) + def inspect + object.strftime("#{FORMAT}.#{"%09d" % object.nsec} %z") + end + else # for 1.8.7 + def inspect + object.strftime("#{FORMAT}.#{"%06d" % object.usec} %z") + end + end + end + + class DateTimeInspector < BaseInspector + FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z" + + def self.can_inspect?(object) + defined?(DateTime) && DateTime === object + end + + # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is + # defined use a custom format string that includes more time precision. + def inspect + if defined?(ActiveSupport) + object.strftime(FORMAT) + else + object.inspect + end + end + end + + class BigDecimalInspector < BaseInspector + def self.can_inspect?(object) + defined?(BigDecimal) && BigDecimal === object + end + + def inspect + "#{object.to_s('F')} (#{object.inspect})" + end + end + + class DescribableMatcherInspector < BaseInspector + def self.can_inspect?(object) + Support.is_a_matcher?(object) && object.respond_to?(:description) + end + + def inspect + object.description + end + end + + class UninspectableObjectInspector < BaseInspector + OBJECT_ID_FORMAT = '%#016x' + + def self.can_inspect?(object) + object.inspect + false + rescue NoMethodError + true + end + + def inspect + "#<#{klass}:#{native_object_id}>" + end + + def klass + Support.class_of(object) + end + + # http://stackoverflow.com/a/2818916 + def native_object_id + OBJECT_ID_FORMAT % (object.__id__ << 1) + rescue NoMethodError + # In Ruby 1.9.2, BasicObject responds to none of #__id__, #object_id, #id... + '-' + end + end + + class DelegatorInspector < BaseInspector + def self.can_inspect?(object) + defined?(Delegator) && Delegator === object + end + + def inspect + "#<#{object.class}(#{formatter.format(object.send(:__getobj__))})>" + end + end + + class InspectableObjectInspector < BaseInspector + def self.can_inspect?(object) + object.inspect + true + rescue NoMethodError + false + end + + def inspect + object.inspect + end + end + + INSPECTOR_CLASSES = [ + TimeInspector, + DateTimeInspector, + BigDecimalInspector, + UninspectableObjectInspector, + DescribableMatcherInspector, + DelegatorInspector, + InspectableObjectInspector + ].tap do |classes| + # 2.4 has improved BigDecimal formatting so we do not need + # to provide our own. + # https://github.com/ruby/bigdecimal/pull/42 + classes.delete(BigDecimalInspector) if RUBY_VERSION >= '2.4' + end + + private + + # Returns the substring defined by the start_index and end_index + # If the string ends with a partial ANSI code code then that + # will be removed as printing partial ANSI + # codes to the terminal can lead to corruption + def truncate_string(str, start_index, end_index) + cut_str = str[start_index..end_index] + + # ANSI color codes are like: \e[33m so anything with \e[ and a + # number without a 'm' is an incomplete color code + cut_str.sub(/\e\[\d+$/, '') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/recursive_const_methods.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/recursive_const_methods.rb new file mode 100644 index 0000000000..b19ad0666a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/recursive_const_methods.rb @@ -0,0 +1,76 @@ +module RSpec + module Support + # Provides recursive constant lookup methods useful for + # constant stubbing. + module RecursiveConstMethods + # We only want to consider constants that are defined directly on a + # particular module, and not include top-level/inherited constants. + # Unfortunately, the constant API changed between 1.8 and 1.9, so + # we need to conditionally define methods to ignore the top-level/inherited + # constants. + # + # Given: + # class A; B = 1; end + # class C < A; end + # + # On 1.8: + # - C.const_get("Hash") # => ::Hash + # - C.const_defined?("Hash") # => false + # - C.constants # => ["B"] + # - None of these methods accept the extra `inherit` argument + # On 1.9: + # - C.const_get("Hash") # => ::Hash + # - C.const_defined?("Hash") # => true + # - C.const_get("Hash", false) # => raises NameError + # - C.const_defined?("Hash", false) # => false + # - C.constants # => [:B] + # - C.constants(false) #=> [] + if Module.method(:const_defined?).arity == 1 + def const_defined_on?(mod, const_name) + mod.const_defined?(const_name) + end + + def get_const_defined_on(mod, const_name) + return mod.const_get(const_name) if const_defined_on?(mod, const_name) + + raise NameError, "uninitialized constant #{mod.name}::#{const_name}" + end + + def constants_defined_on(mod) + mod.constants.select { |c| const_defined_on?(mod, c) } + end + else + def const_defined_on?(mod, const_name) + mod.const_defined?(const_name, false) + end + + def get_const_defined_on(mod, const_name) + mod.const_get(const_name, false) + end + + def constants_defined_on(mod) + mod.constants(false) + end + end + + def recursive_const_get(const_name) + normalize_const_name(const_name).split('::').inject(Object) do |mod, name| + get_const_defined_on(mod, name) + end + end + + def recursive_const_defined?(const_name) + parts = normalize_const_name(const_name).split('::') + parts.inject([Object, '']) do |(mod, full_name), name| + yield(full_name, name) if block_given? && !(Module === mod) + return false unless const_defined_on?(mod, name) + [get_const_defined_on(mod, name), [mod.name, name].join('::')] + end + end + + def normalize_const_name(const_name) + const_name.sub(/\A::/, '') + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/reentrant_mutex.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/reentrant_mutex.rb new file mode 100644 index 0000000000..55fbf892c0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/reentrant_mutex.rb @@ -0,0 +1,78 @@ +module RSpec + module Support + # Allows a thread to lock out other threads from a critical section of code, + # while allowing the thread with the lock to reenter that section. + # + # Based on Monitor as of 2.2 - + # https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9 + # + # Depends on Mutex, but Mutex is only available as part of core since 1.9.1: + # exists - http://ruby-doc.org/core-1.9.1/Mutex.html + # dne - http://ruby-doc.org/core-1.9.0/Mutex.html + # + # @private + class ReentrantMutex + def initialize + @owner = nil + @count = 0 + @mutex = Mutex.new + end + + def synchronize + enter + yield + ensure + exit + end + + private + + # This is fixing a bug #501 that is specific to Ruby 3.0. The new implementation + # depends on `owned?` that was introduced in Ruby 2.0, so both should work for Ruby 2.x. + if RUBY_VERSION.to_f >= 3.0 + def enter + @mutex.lock unless @mutex.owned? + @count += 1 + end + + def exit + unless @mutex.owned? + raise ThreadError, "Attempt to unlock a mutex which is locked by another thread/fiber" + end + @count -= 1 + @mutex.unlock if @count == 0 + end + else + def enter + @mutex.lock if @owner != Thread.current + @owner = Thread.current + @count += 1 + end + + def exit + @count -= 1 + return unless @count == 0 + @owner = nil + @mutex.unlock + end + end + end + + if defined? ::Mutex + # On 1.9 and up, this is in core, so we just use the real one + class Mutex < ::Mutex + # If you mock Mutex.new you break our usage of Mutex, so + # instead we capture the original method to return Mutexs. + NEW_MUTEX_METHOD = Mutex.method(:new) + + def self.new + NEW_MUTEX_METHOD.call + end + end + else # For 1.8.7 + # :nocov: + RSpec::Support.require_rspec_support "mutex" + # :nocov: + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/ruby_features.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/ruby_features.rb new file mode 100644 index 0000000000..d5d6aae533 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/ruby_features.rb @@ -0,0 +1,198 @@ +require 'rbconfig' +RSpec::Support.require_rspec_support "comparable_version" + +module RSpec + module Support + # @api private + # + # Provides query methods for different OS or OS features. + module OS + module_function + + def windows? + !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) + end + + def windows_file_path? + ::File::ALT_SEPARATOR == '\\' + end + end + + # @api private + # + # Provides query methods for different rubies + module Ruby + module_function + + def jruby? + RUBY_PLATFORM == 'java' + end + + def jruby_version + @jruby_version ||= ComparableVersion.new(JRUBY_VERSION) + end + + def jruby_9000? + jruby? && JRUBY_VERSION >= '9.0.0.0' + end + + def rbx? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' + end + + def non_mri? + !mri? + end + + def mri? + !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' + end + + def truffleruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby' + end + end + + # @api private + # + # Provides query methods for ruby features that differ among + # implementations. + module RubyFeatures + module_function + + if Ruby.jruby? && RUBY_VERSION.to_f < 1.9 + # On JRuby 1.7 `--1.8` mode, `Process.respond_to?(:fork)` returns true, + # but when you try to fork, it raises an error: + # NotImplementedError: fork is not available on this platform + # + # When we drop support for JRuby 1.7 and/or Ruby 1.8, we can drop + # this special case. + def fork_supported? + false + end + else + def fork_supported? + Process.respond_to?(:fork) + end + end + + def optional_and_splat_args_supported? + Method.method_defined?(:parameters) + end + + def caller_locations_supported? + respond_to?(:caller_locations, true) + end + + if Exception.method_defined?(:cause) + def supports_exception_cause? + true + end + else + def supports_exception_cause? + false + end + end + + if RUBY_VERSION.to_f >= 2.7 + def supports_taint? + false + end + else + def supports_taint? + true + end + end + ripper_requirements = [ComparableVersion.new(RUBY_VERSION) >= '1.9.2'] + + ripper_requirements.push(false) if Ruby.rbx? + + if Ruby.jruby? + ripper_requirements.push(Ruby.jruby_version >= '1.7.5') + # Ripper on JRuby 9.0.0.0.rc1 - 9.1.8.0 reports wrong line number + # or cannot parse source including `:if`. + # Ripper on JRuby 9.x.x.x < 9.1.17.0 can't handle keyword arguments + # Neither can JRuby 9.2, e.g. < 9.2.1.0 + ripper_requirements.push(!Ruby.jruby_version.between?('9.0.0.0.rc1', '9.2.0.0')) + end + + # TruffleRuby disables ripper due to low performance + ripper_requirements.push(false) if Ruby.truffleruby? + + if ripper_requirements.all? + def ripper_supported? + true + end + else + def ripper_supported? + false + end + end + + def distincts_kw_args_from_positional_hash? + RUBY_VERSION >= '3.0.0' + end + + if Ruby.mri? + def kw_args_supported? + RUBY_VERSION >= '2.0.0' + end + + def required_kw_args_supported? + RUBY_VERSION >= '2.1.0' + end + + def supports_rebinding_module_methods? + RUBY_VERSION.to_i >= 2 + end + else + # RBX / JRuby et al support is unknown for keyword arguments + begin + eval("o = Object.new; def o.m(a: 1); end;"\ + " raise SyntaxError unless o.method(:m).parameters.include?([:key, :a])") + + def kw_args_supported? + true + end + rescue SyntaxError + def kw_args_supported? + false + end + end + + begin + eval("o = Object.new; def o.m(a: ); end;"\ + "raise SyntaxError unless o.method(:m).parameters.include?([:keyreq, :a])") + + def required_kw_args_supported? + true + end + rescue SyntaxError + def required_kw_args_supported? + false + end + end + + begin + Module.new { def foo; end }.instance_method(:foo).bind(Object.new) + + def supports_rebinding_module_methods? + true + end + rescue TypeError + def supports_rebinding_module_methods? + false + end + end + end + + def module_refinement_supported? + Module.method_defined?(:refine) || Module.private_method_defined?(:refine) + end + + def module_prepends_supported? + Module.method_defined?(:prepend) || Module.private_method_defined?(:prepend) + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source.rb new file mode 100644 index 0000000000..ec0e1aeb1a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source.rb @@ -0,0 +1,85 @@ +RSpec::Support.require_rspec_support 'encoded_string' +RSpec::Support.require_rspec_support 'ruby_features' + +module RSpec + module Support + # @private + # Represents a Ruby source file and provides access to AST and tokens. + class Source + attr_reader :source, :path + + # This class protects us against having File read and expand_path + # stubbed out within tests. + class File + class << self + [:read, :expand_path].each do |method_name| + define_method(method_name, &::File.method(method_name)) + end + end + end + + def self.from_file(path) + source = File.read(path) + new(source, path) + end + + if String.method_defined?(:encoding) + def initialize(source_string, path=nil) + @source = RSpec::Support::EncodedString.new(source_string, Encoding.default_external) + @path = path ? File.expand_path(path) : '(string)' + end + else # for 1.8.7 + # :nocov: + def initialize(source_string, path=nil) + @source = RSpec::Support::EncodedString.new(source_string) + @path = path ? File.expand_path(path) : '(string)' + end + # :nocov: + end + + def lines + @lines ||= source.split("\n") + end + + def inspect + "#<#{self.class} #{path}>" + end + + if RSpec::Support::RubyFeatures.ripper_supported? + RSpec::Support.require_rspec_support 'source/node' + RSpec::Support.require_rspec_support 'source/token' + + def ast + @ast ||= begin + require 'ripper' + sexp = Ripper.sexp(source) + raise SyntaxError unless sexp + Node.new(sexp) + end + end + + def tokens + @tokens ||= begin + require 'ripper' + tokens = Ripper.lex(source) + Token.tokens_from_ripper_tokens(tokens) + end + end + + def nodes_by_line_number + @nodes_by_line_number ||= begin + nodes_by_line_number = ast.select(&:location).group_by { |node| node.location.line } + Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number) + end + end + + def tokens_by_line_number + @tokens_by_line_number ||= begin + nodes_by_line_number = tokens.group_by { |token| token.location.line } + Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/location.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/location.rb new file mode 100644 index 0000000000..29077c99c9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/location.rb @@ -0,0 +1,21 @@ +module RSpec + module Support + class Source + # @private + # Represents a source location of node or token. + Location = Struct.new(:line, :column) do + include Comparable + + def self.location?(array) + array.is_a?(Array) && array.size == 2 && array.all? { |e| e.is_a?(Integer) } + end + + def <=>(other) + line_comparison = (line <=> other.line) + return line_comparison unless line_comparison == 0 + column <=> other.column + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/node.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/node.rb new file mode 100644 index 0000000000..6f3086b660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/node.rb @@ -0,0 +1,110 @@ +RSpec::Support.require_rspec_support 'source/location' + +module RSpec + module Support + class Source + # @private + # A wrapper for Ripper AST node which is generated with `Ripper.sexp`. + class Node + include Enumerable + + attr_reader :sexp, :parent + + def self.sexp?(array) + array.is_a?(Array) && array.first.is_a?(Symbol) + end + + def initialize(ripper_sexp, parent=nil) + @sexp = ripper_sexp.freeze + @parent = parent + end + + def type + sexp[0] + end + + def args + @args ||= raw_args.map do |raw_arg| + if Node.sexp?(raw_arg) + Node.new(raw_arg, self) + elsif Location.location?(raw_arg) + Location.new(*raw_arg) + elsif raw_arg.is_a?(Array) + ExpressionSequenceNode.new(raw_arg, self) + else + raw_arg + end + end.freeze + end + + def children + @children ||= args.select { |arg| arg.is_a?(Node) }.freeze + end + + def location + @location ||= args.find { |arg| arg.is_a?(Location) } + end + + # We use a loop here (instead of recursion) to prevent SystemStackError + def each + return to_enum(__method__) unless block_given? + + node_queue = [] + node_queue << self + + while (current_node = node_queue.shift) + yield current_node + node_queue.concat(current_node.children) + end + end + + def each_ancestor + return to_enum(__method__) unless block_given? + + current_node = self + + while (current_node = current_node.parent) + yield current_node + end + end + + def inspect + "#<#{self.class} #{type}>" + end + + private + + def raw_args + sexp[1..-1] || [] + end + end + + # @private + # Basically `Ripper.sexp` generates arrays whose first element is a symbol (type of sexp), + # but it exceptionally generates typeless arrays for expression sequence: + # + # Ripper.sexp('foo; bar') + # => [ + # :program, + # [ # Typeless array + # [:vcall, [:@ident, "foo", [1, 0]]], + # [:vcall, [:@ident, "bar", [1, 5]]] + # ] + # ] + # + # We wrap typeless arrays in this pseudo type node + # so that it can be handled in the same way as other type node. + class ExpressionSequenceNode < Node + def type + :_expression_sequence + end + + private + + def raw_args + sexp + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/token.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/token.rb new file mode 100644 index 0000000000..ac3fb92dd2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/source/token.rb @@ -0,0 +1,94 @@ +RSpec::Support.require_rspec_support 'source/location' + +module RSpec + module Support + class Source + # @private + # A wrapper for Ripper token which is generated with `Ripper.lex`. + class Token + CLOSING_TYPES_BY_OPENING_TYPE = { + :on_lbracket => :on_rbracket, + :on_lparen => :on_rparen, + :on_lbrace => :on_rbrace, + :on_heredoc_beg => :on_heredoc_end + }.freeze + + CLOSING_KEYWORDS_BY_OPENING_KEYWORD = { + 'def' => 'end', + 'do' => 'end', + }.freeze + + attr_reader :token + + def self.tokens_from_ripper_tokens(ripper_tokens) + ripper_tokens.map { |ripper_token| new(ripper_token) }.freeze + end + + def initialize(ripper_token) + @token = ripper_token.freeze + end + + def location + @location ||= Location.new(*token[0]) + end + + def type + token[1] + end + + def string + token[2] + end + + def ==(other) + token == other.token + end + + alias_method :eql?, :== + + def inspect + "#<#{self.class} #{type} #{string.inspect}>" + end + + def keyword? + type == :on_kw + end + + def equals_operator? + type == :on_op && string == '=' + end + + def opening? + opening_delimiter? || opening_keyword? + end + + def closed_by?(other) + delimiter_closed_by?(other) || keyword_closed_by?(other) + end + + private + + def opening_delimiter? + CLOSING_TYPES_BY_OPENING_TYPE.key?(type) + end + + def opening_keyword? + return false unless keyword? + CLOSING_KEYWORDS_BY_OPENING_KEYWORD.key?(string) + end + + def delimiter_closed_by?(other) + other.type == CLOSING_TYPES_BY_OPENING_TYPE[type] + end + + def keyword_closed_by?(other) + return false unless keyword? + return true if other.string == CLOSING_KEYWORDS_BY_OPENING_KEYWORD[string] + + # Ruby 3's `end`-less method definition: `def method_name = body` + string == 'def' && other.equals_operator? && location.line == other.location.line + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec.rb new file mode 100644 index 0000000000..b53829a986 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec.rb @@ -0,0 +1,82 @@ +require 'rspec/support' +require 'rspec/support/spec/in_sub_process' + +RSpec::Support.require_rspec_support "spec/deprecation_helpers" +RSpec::Support.require_rspec_support "spec/diff_helpers" +RSpec::Support.require_rspec_support "spec/with_isolated_stderr" +RSpec::Support.require_rspec_support "spec/stderr_splitter" +RSpec::Support.require_rspec_support "spec/formatting_support" +RSpec::Support.require_rspec_support "spec/with_isolated_directory" +RSpec::Support.require_rspec_support "ruby_features" + +warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr) + +RSpec.configure do |c| + c.include RSpecHelpers + c.include RSpec::Support::WithIsolatedStdErr + c.include RSpec::Support::FormattingSupport + c.include RSpec::Support::InSubProcess + + unless defined?(Debugger) # debugger causes warnings when used + c.before do + warning_preventer.reset! + end + + c.after do + warning_preventer.verify_no_warnings! + end + end + + if c.files_to_run.one? + c.full_backtrace = true + c.default_formatter = 'doc' + end + + c.filter_run_when_matching :focus + + c.example_status_persistence_file_path = "./spec/examples.txt" + + c.define_derived_metadata :failing_on_windows_ci do |meta| + meta[:pending] ||= "This spec fails on Windows CI and needs someone to fix it." + end if RSpec::Support::OS.windows? && ENV['CI'] +end + +module RSpec + module Support + module Spec + def self.setup_simplecov(&block) + # Simplecov emits some ruby warnings when loaded, so silence them. + old_verbose, $VERBOSE = $VERBOSE, false + + return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3' + return if RUBY_ENGINE != 'ruby' || RSpec::Support::OS.windows? + + # Don't load it when we're running a single isolated + # test file rather than the whole suite. + return if RSpec.configuration.files_to_run.one? + + require 'simplecov' + start_simplecov(&block) + rescue LoadError + warn "Simplecov could not be loaded" + ensure + $VERBOSE = old_verbose + end + + def self.start_simplecov(&block) + SimpleCov.start do + add_filter "bundle/" + add_filter "tmp/" + add_filter do |source_file| + # Filter out `spec` directory except when it is under `lib` + # (as is the case in rspec-support) + source_file.filename.include?('/spec/') && !source_file.filename.include?('/lib/') + end + + instance_eval(&block) if block + end + end + private_class_method :start_simplecov + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/deprecation_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/deprecation_helpers.rb new file mode 100644 index 0000000000..8413ed60ef --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/deprecation_helpers.rb @@ -0,0 +1,48 @@ +module RSpecHelpers + def expect_deprecation_with_call_site(file, line, snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:deprecated => match(snippet), :call_site => include([file, line].join(':')))) + end + + def expect_deprecation_without_call_site(snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:deprecated => match(snippet), :call_site => eq(nil))) + end + + def expect_warn_deprecation_with_call_site(file, line, snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:message => match(snippet), :call_site => include([file, line].join(':')))) + end + + def expect_warn_deprecation(snippet=//) + expect(RSpec.configuration.reporter).to receive(:deprecation). + with(include(:message => match(snippet))) + end + + def allow_deprecation + allow(RSpec.configuration.reporter).to receive(:deprecation) + end + + def expect_no_deprecations + expect(RSpec.configuration.reporter).not_to receive(:deprecation) + end + alias expect_no_deprecation expect_no_deprecations + + def expect_warning_without_call_site(expected=//) + expect(::Kernel).to receive(:warn). + with(match(expected).and(satisfy { |message| !(/Called from/ =~ message) })) + end + + def expect_warning_with_call_site(file, line, expected=//) + expect(::Kernel).to receive(:warn). + with(match(expected).and(match(/Called from #{file}:#{line}/))) + end + + def expect_no_warnings + expect(::Kernel).not_to receive(:warn) + end + + def allow_warning + allow(::Kernel).to receive(:warn) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/diff_helpers.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/diff_helpers.rb new file mode 100644 index 0000000000..76ef10a67a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/diff_helpers.rb @@ -0,0 +1,31 @@ +require 'diff/lcs' + +module RSpec + module Support + module Spec + module DiffHelpers + # In the updated version of diff-lcs several diff headers change format slightly + # compensate for this and change minimum version in RSpec 4 + if ::Diff::LCS::VERSION.to_f < 1.4 + def one_line_header(line_number=2) + "-1,#{line_number} +1,#{line_number}" + end + else + def one_line_header(_=2) + "-1 +1" + end + end + + if Diff::LCS::VERSION.to_f < 1.4 || Diff::LCS::VERSION >= "1.4.4" + def removing_two_line_header + "-1,3 +1" + end + else + def removing_two_line_header + "-1,3 +1,5" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/formatting_support.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/formatting_support.rb new file mode 100644 index 0000000000..7e61cef495 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/formatting_support.rb @@ -0,0 +1,9 @@ +module RSpec + module Support + module FormattingSupport + def dedent(string) + string.gsub(/^\s+\|/, '').chomp + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/in_sub_process.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/in_sub_process.rb new file mode 100644 index 0000000000..2f5025eceb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/in_sub_process.rb @@ -0,0 +1,67 @@ +module RSpec + module Support + module InSubProcess + if Process.respond_to?(:fork) && !(Ruby.jruby? && RUBY_VERSION == '1.8.7') + + UnmarshableObject = Struct.new(:error) + + # Useful as a way to isolate a global change to a subprocess. + + def in_sub_process(prevent_warnings=true) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + exception_reader, exception_writer = IO.pipe + result_reader, result_writer = IO.pipe + + pid = Process.fork do + warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr) + + begin + result = yield + warning_preventer.verify_no_warnings! if prevent_warnings + # rubocop:disable Lint/HandleExceptions + rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception + # rubocop:enable Lint/HandleExceptions + end + + exception_writer.write marshal_dump_with_unmarshable_object_handling(exception) + exception_reader.close + exception_writer.close + + result_writer.write marshal_dump_with_unmarshable_object_handling(result) + result_reader.close + result_writer.close + + exit! # prevent at_exit hooks from running (e.g. minitest) + end + + exception_writer.close + result_writer.close + Process.waitpid(pid) + + exception = Marshal.load(exception_reader.read) + exception_reader.close + raise exception if exception + + result = Marshal.load(result_reader.read) + result_reader.close + result + end + alias :in_sub_process_if_possible :in_sub_process + + def marshal_dump_with_unmarshable_object_handling(object) + Marshal.dump(object) + rescue TypeError => error + Marshal.dump(UnmarshableObject.new(error)) + end + else + def in_sub_process(*) + skip "This spec requires forking to work properly, " \ + "and your platform does not support forking" + end + + def in_sub_process_if_possible(*) + yield + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/library_wide_checks.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/library_wide_checks.rb new file mode 100644 index 0000000000..56b0593640 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/library_wide_checks.rb @@ -0,0 +1,150 @@ +require 'rspec/support/spec/shell_out' + +module RSpec + module Support + module WhitespaceChecks + # This malformed whitespace detection logic has been borrowed from bundler: + # https://github.com/bundler/bundler/blob/v1.8.0/spec/quality_spec.rb + def check_for_tab_characters(filename) + failing_lines = [] + File.readlines(filename).each_with_index do |line, number| + failing_lines << number + 1 if line =~ /\t/ + end + + return if failing_lines.empty? + "#{filename} has tab characters on lines #{failing_lines.join(', ')}" + end + + def check_for_extra_spaces(filename) + failing_lines = [] + File.readlines(filename).each_with_index do |line, number| + next if line =~ /^\s+#.*\s+\n$/ + failing_lines << number + 1 if line =~ /\s+\n$/ + end + + return if failing_lines.empty? + "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" + end + end + end +end + +RSpec.shared_examples_for "library wide checks" do |lib, options| + consider_a_test_env_file = options.fetch(:consider_a_test_env_file, /MATCHES NOTHING/) + allowed_loaded_feature_regexps = options.fetch(:allowed_loaded_feature_regexps, []) + preamble_for_lib = options[:preamble_for_lib] + preamble_for_spec = "require 'rspec/core'; require 'spec_helper'" + skip_spec_files = options.fetch(:skip_spec_files, /MATCHES NOTHING/) + + include RSpec::Support::ShellOut + include RSpec::Support::WhitespaceChecks + + define_method :files_to_require_for do |sub_dir| + slash = File::SEPARATOR + lib_path_re = /#{slash + lib}[^#{slash}]*#{slash}lib/ + load_path = $LOAD_PATH.grep(lib_path_re).first + directory = load_path.sub(/lib$/, sub_dir) + files = Dir["#{directory}/**/*.rb"] + extract_regex = /#{Regexp.escape(directory) + File::SEPARATOR}(.+)\.rb$/ + + # We sort to ensure the files are loaded in a consistent order, regardless + # of OS. Otherwise, it could load in a different order on Travis than + # locally, and potentially trigger a "circular require considered harmful" + # warning or similar. + files.sort.map { |file| file[extract_regex, 1] } + end + + def command_from(code_lines) + code_lines.join("\n") + end + + def load_all_files(files, preamble, postamble=nil) + requires = files.map { |f| "require '#{f}'" } + command = command_from(Array(preamble) + requires + Array(postamble)) + + stdout, stderr, status = with_env 'NO_COVERAGE' => '1' do + options = %w[ -w ] + options << "--disable=gem" if RUBY_VERSION.to_f >= 1.9 && RSpec::Support::Ruby.mri? + run_ruby_with_current_load_path(command, *options) + end + + [stdout, strip_known_warnings(stderr), status.exitstatus] + end + + define_method :load_all_lib_files do + files = all_lib_files - lib_test_env_files + preamble = ['orig_loaded_features = $".dup', preamble_for_lib] + postamble = ['puts(($" - orig_loaded_features).join("\n"))'] + + @loaded_feature_lines, stderr, exitstatus = load_all_files(files, preamble, postamble) + ["", stderr, exitstatus] + end + + define_method :load_all_spec_files do + files = files_to_require_for("spec") + lib_test_env_files + files = files.reject { |f| f =~ skip_spec_files } + load_all_files(files, preamble_for_spec) + end + + attr_reader :all_lib_files, :lib_test_env_files, + :lib_file_results, :spec_file_results + + before(:context) do + @all_lib_files = files_to_require_for("lib") + @lib_test_env_files = all_lib_files.grep(consider_a_test_env_file) + + @lib_file_results, @spec_file_results = [ + # Load them in parallel so it's faster... + Thread.new { load_all_lib_files }, + Thread.new { load_all_spec_files } + ].map(&:join).map(&:value) + end + + def have_successful_no_warnings_output + eq ["", "", 0] + end + + it "issues no warnings when loaded", :slow do + expect(lib_file_results).to have_successful_no_warnings_output + end + + it "issues no warnings when the spec files are loaded", :slow do + expect(spec_file_results).to have_successful_no_warnings_output + end + + it 'only loads a known set of stdlibs so gem authors are forced ' \ + 'to load libs they use to have passing specs', :slow do + loaded_features = @loaded_feature_lines.split("\n") + if RUBY_VERSION == '1.8.7' + # On 1.8.7, $" returns the relative require path if that was used + # to require the file. LIB_REGEX will not match the relative version + # since it has a `/lib` prefix. Here we deal with this by expanding + # relative files relative to the $LOAD_PATH dir (lib). + Dir.chdir("lib") { loaded_features.map! { |f| File.expand_path(f) } } + end + + loaded_features.reject! { |feature| RSpec::CallerFilter::LIB_REGEX =~ feature } + loaded_features.reject! { |feature| allowed_loaded_feature_regexps.any? { |r| r =~ feature } } + + expect(loaded_features).to eq([]) + end + + RSpec::Matchers.define :be_well_formed do + match do |actual| + actual.empty? + end + + failure_message do |actual| + actual.join("\n") + end + end + + it "has no malformed whitespace", :slow do + error_messages = [] + `git ls-files -z`.split("\x0").each do |filename| + error_messages << check_for_tab_characters(filename) + error_messages << check_for_extra_spaces(filename) + end + expect(error_messages.compact).to be_well_formed + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/shell_out.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/shell_out.rb new file mode 100644 index 0000000000..864e540b33 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/shell_out.rb @@ -0,0 +1,105 @@ +require 'open3' +require 'rake/file_utils' +require 'shellwords' + +module RSpec + module Support + module ShellOut + def with_env(vars) + original = ENV.to_hash + vars.each { |k, v| ENV[k] = v } + + begin + yield + ensure + ENV.replace(original) + end + end + + if Open3.respond_to?(:capture3) # 1.9+ + def shell_out(*command) + stdout, stderr, status = Open3.capture3(*command) + return stdout, filter(stderr), status + end + else # 1.8.7 + # popen3 doesn't provide the exit status so we fake it out. + FakeProcessStatus = Struct.new(:exitstatus) + + def shell_out(*command) + stdout = stderr = nil + + Open3.popen3(*command) do |_in, out, err| + stdout = out.read + stderr = err.read + end + + status = FakeProcessStatus.new(0) + return stdout, filter(stderr), status + end + end + + def run_ruby_with_current_load_path(ruby_command, *flags) + command = [ + FileUtils::RUBY, + "-I#{$LOAD_PATH.map(&:shellescape).join(File::PATH_SEPARATOR)}", + "-e", ruby_command, *flags + ] + + # Unset these env vars because `ruby -w` will issue warnings whenever + # they are set to non-default values. + with_env 'RUBY_GC_HEAP_FREE_SLOTS' => nil, 'RUBY_GC_MALLOC_LIMIT' => nil, + 'RUBY_FREE_MIN' => nil do + shell_out(*command) + end + end + + LINES_TO_IGNORE = + [ + # Ignore bundler warning. + %r{bundler/source/rubygems}, + # Ignore bundler + rubygems warning. + %r{site_ruby/\d\.\d\.\d/rubygems}, + %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/rubygems}, + # This is required for windows for some reason + %r{lib/bundler/rubygems}, + # This is a JRuby file that generates warnings on 9.0.3.0 + %r{lib/ruby/stdlib/jar}, + # This is a JRuby file that generates warnings on 9.1.7.0 + %r{org/jruby/RubyKernel\.java}, + # This is a JRuby gem that generates warnings on 9.1.7.0 + %r{ffi-1\.13\.\d+-java}, + %r{uninitialized constant FFI}, + # These are related to the above, there is a warning about io from FFI + %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/io}, + %r{io/console on JRuby shells out to stty for most operations}, + # This is a JRuby 9.1.17.0 error on Github Actions + %r{io/console not supported; tty will not be manipulated}, + # This is a JRuby 9.2.1.x error + %r{jruby/kernel/gem_prelude}, + %r{lib/jruby\.jar!/jruby/preludes}, + ] + + def strip_known_warnings(input) + input.split("\n").reject do |l| + LINES_TO_IGNORE.any? { |to_ignore| l =~ to_ignore } || + # Remove blank lines + l == "" || l.nil? + end.join("\n") + end + + private + + if Ruby.jruby? + def filter(output) + output.each_line.reject do |line| + line.include?("lib/ruby/shared/rubygems") + end.join($/) + end + else + def filter(output) + output + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/stderr_splitter.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/stderr_splitter.rb new file mode 100644 index 0000000000..6da084cc9f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/stderr_splitter.rb @@ -0,0 +1,75 @@ +require 'stringio' + +module RSpec + module Support + class StdErrSplitter + def initialize(original) + @orig_stderr = original + @output_tracker = ::StringIO.new + @last_line = nil + end + + respond_to_name = (::RUBY_VERSION.to_f < 1.9) ? :respond_to? : :respond_to_missing? + define_method respond_to_name do |*args| + @orig_stderr.respond_to?(*args) || super(*args) + end + + def method_missing(name, *args, &block) + @output_tracker.__send__(name, *args, &block) if @output_tracker.respond_to?(name) + @orig_stderr.__send__(name, *args, &block) + end + + def ==(other) + @orig_stderr == other + end + + def reopen(*args) + reset! + @orig_stderr.reopen(*args) + end + + # To work around JRuby error: + # can't convert RSpec::Support::StdErrSplitter into String + def to_io + @orig_stderr.to_io + end + + # To work around JRuby error: + # TypeError: $stderr must have write method, RSpec::StdErrSplitter given + def write(line) + return if line =~ %r{^\S+/gems/\S+:\d+: warning:} # http://rubular.com/r/kqeUIZOfPG + + # Ruby 2.7.0 warnings from keyword arguments span multiple lines, extend check above + # to look for the next line. + return if @last_line =~ %r{^\S+/gems/\S+:\d+: warning:} && + line =~ %r{warning: The called method .* is defined here} + + # Ruby 2.7.0 complains about hashes used in place of keyword arguments + # Aruba 0.14.2 uses this internally triggering that here + return if line =~ %r{lib/ruby/2\.7\.0/fileutils\.rb:622: warning:} + + @orig_stderr.write(line) + @output_tracker.write(line) + ensure + @last_line = line + end + + def has_output? + !output.empty? + end + + def reset! + @output_tracker = ::StringIO.new + end + + def verify_no_warnings! + raise "Warnings were generated: #{output}" if has_output? + reset! + end + + def output + @output_tracker.string + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/string_matcher.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/string_matcher.rb new file mode 100644 index 0000000000..7df3199188 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/string_matcher.rb @@ -0,0 +1,45 @@ +require 'rspec/matchers' +# Special matcher for comparing encoded strings so that +# we don't run any expectation failures through the Differ, +# which also relies on EncodedString. Instead, confirm the +# strings have the same bytes. +RSpec::Matchers.define :be_identical_string do |expected| + if String.method_defined?(:encoding) + match do + expected_encoding? && + actual.bytes.to_a == expected.bytes.to_a + end + + failure_message do + "expected\n#{actual.inspect} (#{actual.encoding.name}) to be identical to\n"\ + "#{expected.inspect} (#{expected.encoding.name})\n"\ + "The exact bytes are printed below for more detail:\n"\ + "#{actual.bytes.to_a}\n"\ + "#{expected.bytes.to_a}\n"\ + end + + # Depends on chaining :with_same_encoding for it to + # check for string encoding. + def expected_encoding? + if defined?(@expect_same_encoding) && @expect_same_encoding + actual.encoding == expected.encoding + else + true + end + end + else + match do + actual.split(//) == expected.split(//) + end + + failure_message do + "expected\n#{actual.inspect} to be identical to\n#{expected.inspect}\n" + end + end + + chain :with_same_encoding do + @expect_same_encoding ||= true + end +end +RSpec::Matchers.alias_matcher :a_string_identical_to, :be_identical_string +RSpec::Matchers.alias_matcher :be_diffed_as, :be_identical_string diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_directory.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_directory.rb new file mode 100644 index 0000000000..6e38c82ea9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_directory.rb @@ -0,0 +1,13 @@ +require 'tmpdir' + +RSpec.shared_context "isolated directory" do + around do |ex| + Dir.mktmpdir do |tmp_dir| + Dir.chdir(tmp_dir, &ex) + end + end +end + +RSpec.configure do |c| + c.include_context "isolated directory", :isolated_directory => true +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_stderr.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_stderr.rb new file mode 100644 index 0000000000..8884c2fbe2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/spec/with_isolated_stderr.rb @@ -0,0 +1,13 @@ +module RSpec + module Support + module WithIsolatedStdErr + def with_isolated_stderr + original = $stderr + $stderr = StringIO.new + yield + ensure + $stderr = original + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/version.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/version.rb new file mode 100644 index 0000000000..7421355e3c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/version.rb @@ -0,0 +1,7 @@ +module RSpec + module Support + module Version + STRING = '3.12.0' + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/warnings.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/warnings.rb new file mode 100644 index 0000000000..380150be9b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/warnings.rb @@ -0,0 +1,39 @@ +require 'rspec/support' +RSpec::Support.require_rspec_support "caller_filter" + +module RSpec + module Support + module Warnings + def deprecate(deprecated, options={}) + warn_with "DEPRECATION: #{deprecated} is deprecated.", options + end + + # @private + # + # Used internally to print deprecation warnings + # when rspec-core isn't loaded + def warn_deprecation(message, options={}) + warn_with "DEPRECATION: \n #{message}", options + end + + # @private + # + # Used internally to print warnings + def warning(text, options={}) + warn_with "WARNING: #{text}.", options + end + + # @private + # + # Used internally to print longer warnings + def warn_with(message, options={}) + call_site = options.fetch(:call_site) { CallerFilter.first_non_rspec_line } + message += " Use #{options[:replacement]} instead." if options[:replacement] + message += " Called from #{call_site}." if call_site + Support.warning_notifier.call message + end + end + end + + extend RSpec::Support::Warnings +end diff --git a/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/with_keywords_when_needed.rb b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/with_keywords_when_needed.rb new file mode 100644 index 0000000000..dc76b5ee00 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/rspec-support-3.12.0/lib/rspec/support/with_keywords_when_needed.rb @@ -0,0 +1,33 @@ +RSpec::Support.require_rspec_support("method_signature_verifier") + +module RSpec + module Support + module WithKeywordsWhenNeeded + # This module adds keyword sensitive support for core ruby methods + # where we cannot use `ruby2_keywords` directly. + + module_function + + if RSpec::Support::RubyFeatures.kw_args_supported? + # Remove this in RSpec 4 in favour of explicitly passed in kwargs where + # this is used. Works around a warning in Ruby 2.7 + + def class_exec(klass, *args, &block) + if MethodSignature.new(block).has_kw_args_in?(args) + binding.eval(<<-CODE, __FILE__, __LINE__) + kwargs = args.pop + klass.class_exec(*args, **kwargs, &block) + CODE + else + klass.class_exec(*args, &block) + end + end + ruby2_keywords :class_exec if respond_to?(:ruby2_keywords, true) + else + def class_exec(klass, *args, &block) + klass.class_exec(*args, &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/LICENSE b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/LICENSE new file mode 100644 index 0000000000..3b9d3835d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/LICENSE @@ -0,0 +1,22 @@ +Copyright 2019-2020 Nobuyoshi Nakada, Yusuke Endoh + +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 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 COPYRIGHT HOLDER 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.0.0/gems/ruby2_keywords-0.0.4/README.md b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/README.md new file mode 100644 index 0000000000..4cc0282689 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/README.md @@ -0,0 +1,67 @@ +# ruby2_keywords + +Provides empty `Module#ruby2_keywords` method, for the forward +source-level compatibility against ruby2.7 and ruby3. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'ruby2_keywords' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install ruby2_keywords + +## Usage + +For class/module instance methods: + +```ruby +require 'ruby2_keywords' + +module YourModule + ruby2_keywords def delegating_method(*args) + other_method(*args) + end +end +``` + +For global methods: + +```ruby +require 'ruby2_keywords' + +ruby2_keywords def oldstyle_keywords(options = {}) +end +``` + +You can do the same for a method defined by `Module#define_method`: + +```ruby +define_method :delegating_method do |*args, &block| + other_method(*args, &block) +end +ruby2_keywords :delegating_method +``` + +## Contributing + +Bug reports and pull requests are welcome on [GitHub] or +[Ruby Issue Tracking System]. + +## License + +The gem is available as open source under the terms of the +[Ruby License] or the [2-Clause BSD License]. + +[GitHub]: https://github.com/ruby/ruby2_keywords/ +[Ruby Issue Tracking System]: https://bugs.ruby-lang.org +[Ruby License]: https://www.ruby-lang.org/en/about/license.txt +[2-Clause BSD License]: https://opensource.org/licenses/BSD-2-Clause diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/lib/ruby2_keywords.rb b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/lib/ruby2_keywords.rb new file mode 100644 index 0000000000..7a3f2fa858 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.4/lib/ruby2_keywords.rb @@ -0,0 +1,57 @@ +class Module + unless private_method_defined?(:ruby2_keywords) + private + # call-seq: + # ruby2_keywords(method_name, ...) + # + # Does nothing. + def ruby2_keywords(name, *) + # nil + end + end +end + +main = TOPLEVEL_BINDING.receiver +unless main.respond_to?(:ruby2_keywords, true) + # call-seq: + # ruby2_keywords(method_name, ...) + # + # Does nothing. + def main.ruby2_keywords(name, *) + # nil + end +end + +class Proc + unless method_defined?(:ruby2_keywords) + # call-seq: + # proc.ruby2_keywords -> proc + # + # Does nothing and just returns the receiver. + def ruby2_keywords + self + end + end +end + +class << Hash + unless method_defined?(:ruby2_keywords_hash?) + # call-seq: + # Hash.ruby2_keywords_hash?(hash) -> false + # + # Returns false. + def ruby2_keywords_hash?(hash) + false + end + end + + unless method_defined?(:ruby2_keywords_hash) + # call-seq: + # Hash.ruby2_keywords_hash(hash) -> new_hash + # + # Duplicates a given hash and returns the new hash. + def ruby2_keywords_hash(hash) + hash.dup + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/ChangeLog b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/ChangeLog new file mode 100644 index 0000000000..848806cb12 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/ChangeLog @@ -0,0 +1,214 @@ +-*- coding: utf-8 -*- + +commit 92ad9c5c3fff591b8383ada8b93c3da1279d24ad + Author: Benoit Daloze + AuthorDate: 2021-01-19 16:15:55 +0100 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-13 09:02:24 +0900 + + Add TruffleRuby in CI + +commit 07d7fa17e4c61102597280bd31a6b5972d8e5588 + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-11 17:23:30 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-11 21:51:58 +0900 + + bundle-install only on ruby 2.1 + +commit 5f993b84a469cdc1995077dc0d8391928bb7ac1a + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-11 12:18:26 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-11 21:39:44 +0900 + + Split Rakefile into rakelib + +commit 8e4d9a8de92e9f1f3690fbc224aac1e0d102c36e + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-11 21:38:24 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-11 21:38:24 +0900 + + Ignore lock file and generated logs + +commit 03b864c09e657c130a66c7ab68d962a31df3b819 + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-10 21:37:20 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 21:44:29 +0900 + + Do not use gemspec for gem dependecy + +commit 636c350c0a10ec75a9b01dd4db983abe6310136f + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-10 12:26:30 +0900 + Commit: GitHub + CommitDate: 2021-02-10 12:26:30 +0900 + + Reduced tests + + Target only the currently maintained versions and the oldest available version, omitting the in-betweens. + +commit 97b4de75c83c927eca773e689ecb49557a972024 + Author: Ivo Anjo + AuthorDate: 2021-02-04 11:58:41 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 11:54:45 +0900 + + Add docker-compose.yml for easy testing of older rubies + +commit 6974495d294cd59b8c0dba78a26b391f25154050 + Author: Ivo Anjo + AuthorDate: 2021-02-04 11:39:26 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 11:54:45 +0900 + + Explicitly declare support for Ruby >= 2.0.0 + + This can be used to clarify support, as well as in the future to drop + support for rubies, if so desired. + +commit 64aad913e16d7e6008aa6ca06cf3f1b6fa864c4a + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-10 00:42:59 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:42:59 +0900 + + Separated install from test + +commit 74cb318db44a3851f724ac72624f1509bbf1bdd4 + Author: Ivo Anjo + AuthorDate: 2021-02-04 12:09:11 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:39:31 +0900 + + Add older Rubies to CI as well + +commit 098295f4e9510a751097a6fc0e76c278ae9a1ff0 + Author: Ivo Anjo + AuthorDate: 2021-02-04 11:20:19 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:25:20 +0900 + + Avoid built-in old `test-unit` + + In Ruby <= 2.1, `test-unit` was shipped with Ruby itself (unbundling + was done for 2.2 -- see ). + + The `test-unit` version shipped with 2.1 breaks some of the tests. + To fix this, I've added the minimum needed version explicitly to the + `gemspec`, as well as added a `gems.rb` for allowing the use of + `bundler` to run the tests and ensure the correct `test-unit` is used. + +commit 1773502b1c445ae0ca1c31960a1b64b2f040f8c1 + Author: Ivo Anjo + AuthorDate: 2021-02-04 10:43:18 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:22:22 +0900 + + Avoid using `Binding#receiver` + + This feature is only available on Ruby 2.2+ and breaks older rubies. + + See for more details. + +commit 0784ef08e280a5eb3c08fd9198b381af0ec027f6 + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-09 23:46:24 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:22:22 +0900 + + Strip the source directory from globbed paths + +commit 7f5f4f8cd9c605741bec1cdabece0dd7e53afd9a + Author: Ivo Anjo + AuthorDate: 2021-02-04 10:15:27 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:22:22 +0900 + + Avoid using `base:` option for `Dir.glob` + + This option is only available on Ruby 2.5+ and breaks older rubies. + + See + for more details. + +commit f40159f5a66fff7bed873d68e06439ec960bc3f9 + Author: Ivo Anjo + AuthorDate: 2021-02-04 10:35:42 +0000 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:22:21 +0900 + + Avoid using numbered block parameters + + This feature is only available on Ruby 2.7+ and breaks older rubies. + + See + for more details. + +commit c898163464e896d63698f19a49bc0ab8cc593081 + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-09 23:50:56 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-10 00:21:21 +0900 + + Revert "Add TruffleRuby in CI" + + This reverts commit 294d9e79171b1b954f223f08acc6144f0fc6efd4. + +commit 88867dc48b9f0ec139cd349af40ae9dbea677b93 + Author: Nobuyoshi Nakada + AuthorDate: 2021-02-09 23:37:17 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-09 23:37:17 +0900 + + Moved the mandatory argument first + +commit 294d9e79171b1b954f223f08acc6144f0fc6efd4 + Author: Benoit Daloze + AuthorDate: 2021-01-19 16:15:55 +0100 + Commit: Nobuyoshi Nakada + CommitDate: 2021-02-09 23:09:57 +0900 + + Add TruffleRuby in CI + +commit 2f7e9000b4a64240616b1cbfbcff5e9174fdf6b1 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-20 13:19:12 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-24 20:01:28 +0900 + + Include ChangeLogs for old versions + +commit 4c54e01675202ad0a69bbd39a790290b9870e125 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-20 10:52:47 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-24 19:58:45 +0900 + + Added ChangeLog rule + +commit 9e5b2a4ba56d61a2b59f9db52c98155c0c449152 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-20 10:24:47 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-20 10:54:31 +0900 + + Added extra_rdoc_files to make README.md the main page + +commit 75927b417a79377770cddfe219b34aa87280a5e7 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-20 10:21:52 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-20 10:54:25 +0900 + + Separate tagging from version bump + +commit c353a3fffc323982d829275c82ae09fdbad94816 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-20 10:20:25 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-20 10:20:45 +0900 + + bump up to 0.0.5 diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/LICENSE b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/LICENSE new file mode 100644 index 0000000000..3b9d3835d6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/LICENSE @@ -0,0 +1,22 @@ +Copyright 2019-2020 Nobuyoshi Nakada, Yusuke Endoh + +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 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 COPYRIGHT HOLDER 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.0.0/gems/ruby2_keywords-0.0.5/README.md b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/README.md new file mode 100644 index 0000000000..42e1157b77 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/README.md @@ -0,0 +1,75 @@ +# ruby2_keywords + +Provides empty `Module#ruby2_keywords` method, for the forward +source-level compatibility against ruby2.7 and ruby3. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'ruby2_keywords' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install ruby2_keywords + +## Usage + +For class/module instance methods: + +```ruby +require 'ruby2_keywords' + +module YourModule + ruby2_keywords def delegating_method(*args) + other_method(*args) + end +end +``` + +For global methods: + +```ruby +require 'ruby2_keywords' + +ruby2_keywords def oldstyle_keywords(options = {}) +end +``` + +You can do the same for a method defined by `Module#define_method`: + +```ruby +define_method :delegating_method do |*args, &block| + other_method(*args, &block) +end +ruby2_keywords :delegating_method +``` + +## Contributing + +Bug reports and pull requests are welcome on [GitHub] or +[Ruby Issue Tracking System]. + +## Development + +After checking out the repo, run `bundle install` to install dependencies. +Then, run `bundle exec rake test` to run the tests. + +To test on older Ruby versions, you can use docker. E.g. to test on Ruby 2.0, +use `docker-compose run ruby-2.0`. + +## License + +The gem is available as open source under the terms of the +[Ruby License] or the [2-Clause BSD License]. + +[GitHub]: https://github.com/ruby/ruby2_keywords/ +[Ruby Issue Tracking System]: https://bugs.ruby-lang.org +[Ruby License]: https://www.ruby-lang.org/en/about/license.txt +[2-Clause BSD License]: https://opensource.org/licenses/BSD-2-Clause diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/lib/ruby2_keywords.rb b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/lib/ruby2_keywords.rb new file mode 100644 index 0000000000..09827b57a7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/lib/ruby2_keywords.rb @@ -0,0 +1,57 @@ +class Module + unless private_method_defined?(:ruby2_keywords) + private + # call-seq: + # ruby2_keywords(method_name, ...) + # + # Does nothing. + def ruby2_keywords(name, *) + # nil + end + end +end + +main = TOPLEVEL_BINDING.eval('self') +unless main.respond_to?(:ruby2_keywords, true) + # call-seq: + # ruby2_keywords(method_name, ...) + # + # Does nothing. + def main.ruby2_keywords(name, *) + # nil + end +end + +class Proc + unless method_defined?(:ruby2_keywords) + # call-seq: + # proc.ruby2_keywords -> proc + # + # Does nothing and just returns the receiver. + def ruby2_keywords + self + end + end +end + +class << Hash + unless method_defined?(:ruby2_keywords_hash?) + # call-seq: + # Hash.ruby2_keywords_hash?(hash) -> false + # + # Returns false. + def ruby2_keywords_hash?(hash) + false + end + end + + unless method_defined?(:ruby2_keywords_hash) + # call-seq: + # Hash.ruby2_keywords_hash(hash) -> new_hash + # + # Duplicates a given hash and returns the new hash. + def ruby2_keywords_hash(hash) + hash.dup + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.0 b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.0 new file mode 100644 index 0000000000..2da8f55ada --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.0 @@ -0,0 +1,25 @@ +-*- coding: utf-8 -*- + +commit 33787f35f09e26f4c1ca716fafc81144d5d21333 + Author: Nobuyoshi Nakada + AuthorDate: 2019-10-22 23:57:24 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2019-10-22 23:57:24 +0900 + + Added readme to files in gemspec + +commit 53b25f66cffa09e2c2b6730fd49241bb359f33db + Author: Nobuyoshi Nakada + AuthorDate: 2019-10-22 23:55:26 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2019-10-22 23:55:26 +0900 + + authors may not be empty + +commit 6e6756bfa47bcf15ecc10ce07237886339edc415 + Author: Nobuyoshi Nakada + AuthorDate: 2019-10-17 00:18:01 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2019-10-17 00:18:01 +0900 + + Initial version diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.1 b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.1 new file mode 100644 index 0000000000..3cc954f5b9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.1 @@ -0,0 +1,9 @@ +-*- coding: utf-8 -*- + +commit b879d6e1d72651af6317e67eaa129d5c9be62e40 + Author: Nobuyoshi Nakada + AuthorDate: 2019-12-02 08:08:31 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2019-12-02 08:09:04 +0900 + + Added the toplevel method diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.2 b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.2 new file mode 100644 index 0000000000..fc9fb813bd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.2 @@ -0,0 +1,55 @@ +-*- coding: utf-8 -*- + +commit a198860c7ceba43ccee428c20bdd082f2bdaba6e + Author: Nobuyoshi Nakada + AuthorDate: 2020-01-08 15:51:35 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-01-08 15:51:35 +0900 + + Achieve version numbers from tags + +commit 07e126eea667923b2d5f4a7584687cb1decd3a56 + Author: Yusuke Endoh + AuthorDate: 2020-01-06 15:27:08 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-01-08 15:38:19 +0900 + + Add a guard for Proc#ruby2_keywords + +commit ff392be2fbea77872d801ed0051c2f166dd6eee9 + Author: Yusuke Endoh + AuthorDate: 2020-01-03 23:51:21 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-01-08 15:38:19 +0900 + + Add a shim for Proc#ruby2_keywords + +commit d5d8c0c8f45102c512bb8015988116c5110b28db + Author: Nobuyoshi Nakada + AuthorDate: 2020-01-03 10:26:25 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-01-08 15:36:44 +0900 + + Check Module#ruby2_keywords arity + + It is considered a mistake, because calling this method with no + arguments has no effect. + +commit 9cf7c9791857db17afb235230059d6cbc2408e9e + Author: Jeremy Evans + AuthorDate: 2019-11-10 12:04:28 -0800 + Commit: Nobuyoshi Nakada + CommitDate: 2019-12-04 16:23:33 +0900 + + Fix usage example in README + + The examle warns in Ruby 2.7, and it isn't a case where you would + want to use ruby2_keywords. + +commit dcc6958efdf25045dce149bf4d0a327e8878c9dd + Author: Yusuke Endoh + AuthorDate: 2019-12-03 18:08:39 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2019-12-03 18:54:26 +0900 + + Update homepage to the github repository diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.3 b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.3 new file mode 100644 index 0000000000..70ff5b6489 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.3 @@ -0,0 +1,124 @@ +-*- coding: utf-8 -*- + +commit 396cc7991604632bc686e3c363504db42337cca3 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 20:57:52 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 21:10:28 +0900 + + Added tests + +commit aa06490df9efa905ef17c143e96edee547c4ffad + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 20:20:31 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 20:20:31 +0900 + + Fixed RDoc location + +commit 9603fec096b257d382776c09ab1f5fe88d289307 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 20:19:09 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 20:19:09 +0900 + + Make README.md the main page + +commit 5093cd212b44d1fbd8ef1c6b3f2bfa8f3427de16 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 19:21:06 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 19:21:06 +0900 + + Added least documents + +commit 52b8acf6a89de00f44c8854f0e30c2be4a3d7cb3 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 19:19:59 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 19:19:59 +0900 + + Define Hash.ruby2_keywords_hash singleton method + +commit 51c47c060d9678ae2c28bcf415bc87346cba1860 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 19:19:09 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 19:19:09 +0900 + + Define Hash.ruby2_keywords_hash? singleton method + +commit 2ee450c041cb1a3b15580c3963b778b33926503c + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 18:53:19 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 18:53:19 +0900 + + Package LICENSE file + + The source gemspec file is useless after building the gem file. + +commit a841a82a1ff485ab6dd5759f6f31dff17de45b65 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 14:41:53 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 14:51:06 +0900 + + README: fix Contributing and License + +commit cbecd4307612f6794962a701cb16ac620872c1f9 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 12:13:21 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 12:13:21 +0900 + + Added version guard against the default gem + +commit 52c15f0e55dfdcb8204e92c85a4dd5d524549533 + Author: Yusuke Endoh + AuthorDate: 2021-01-07 17:39:52 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-07 19:56:19 +0900 + + Use private_method_defined? instead of respond_to? + + `Module.respond_to?(:ruby2_keywords, true)` does NOT check if + `Module#ruby2_keywords` is available. It worked well because there is + toplevel `ruby2_keywords` method, but using `private_method_defined?` is + better, I think. + + Also, this fixes a syntactic error. + +commit 23981c5296aec6c5dbe104b8adc7ca0e85cb4313 + Author: Yusuke Endoh + AuthorDate: 2020-12-28 14:07:40 +0900 + Commit: GitHub + CommitDate: 2020-12-28 14:07:40 +0900 + + Add an example for Module#define_method (#7) + +commit 92e74341dffc9a41d7671ea82709ba2e091ef4e8 + Author: Nobuyoshi Nakada + AuthorDate: 2020-12-27 17:43:35 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-12-27 17:43:35 +0900 + + Added BSD-2-Clause to the licenses of the gemspec + +commit 46ed72d40db163f9edbddbe6e5706794484ac5bb + Author: Antonio Terceiro + AuthorDate: 2020-04-03 14:50:29 -0300 + Commit: Nobuyoshi Nakada + CommitDate: 2020-12-27 17:06:49 +0900 + + Add explicit license file + + Fixes #4 + +commit 53833c0f660239eeb572dd33d4a1fac503c4834a + Author: Nobuyoshi Nakada + AuthorDate: 2020-12-27 17:05:37 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2020-12-27 17:05:37 +0900 + + Support Hash.ruby2_keywords_hash? diff --git a/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.4 b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.4 new file mode 100644 index 0000000000..ba9b59168f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/ruby2_keywords-0.0.5/logs/ChangeLog-0.0.4 @@ -0,0 +1,53 @@ +-*- coding: utf-8 -*- + +commit 31766f4327e6e4555543b44fc6a5dc252c8ff6d9 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 23:49:55 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:49:55 +0900 + + bump up to 0.0.4 + +commit 8bf4b5b4169545ef5be46dec8cd6502d902a3e4a + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 23:49:51 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:49:51 +0900 + + Added bump target + +commit fba8eb45d6b2db2d0f829b0d20300e7d19268146 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 23:29:46 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:35:44 +0900 + + Build package + +commit 403ff84d12c9fe1f34397b3a164b0b2f73a560d1 + Author: Nobuyoshi Nakada + AuthorDate: 2021-01-19 23:25:17 +0900 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:33:35 +0900 + + Set SOURCE_DATE_EPOCH to make builds reproducible + +commit 956156ba793330928280c5301b093300a1a9f792 + Author: Nazar Matus + AuthorDate: 2021-01-19 16:07:37 +0200 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:33:11 +0900 + + Add Ruby 2.5 to the CI matrix + +commit d6d1775d793bcaf206af700120b0b4bd2dc3842d + Author: Nazar Matus + AuthorDate: 2021-01-19 15:47:38 +0200 + Commit: Nobuyoshi Nakada + CommitDate: 2021-01-19 23:33:11 +0900 + + Fix Ruby 2.5 incopatibility + + We don't really need that second optional argument, + as its default value is just what we need + https://ruby-doc.org/core-2.7.2/Module.html#method-i-private_method_defined-3F diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/.yardopts b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/.yardopts new file mode 100644 index 0000000000..60e00e9a35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/.yardopts @@ -0,0 +1,5 @@ +--readme README.md +--title 'Sinatra API Documentation' +--charset utf-8 +--markup markdown +'lib/**/*.rb' - '*.md' diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/AUTHORS.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/AUTHORS.md new file mode 100644 index 0000000000..925eb91ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/AUTHORS.md @@ -0,0 +1,81 @@ +Sinatra was designed and developed by Blake Mizerany in California. + +### Current Team + +* **Konstantin Haase** (maintainer) +* **Zachary Scott** +* **Kashyap Kondamudi** +* **Ashley Williams** +* **Trevor Bramble** +* **Kunpei Sakai** + +### Alumni + +* **Blake Mizerany** (creator) +* **Ryan Tomayko** +* **Simon Rozet** +* **Katrina Owen** + +### Thanks + +Sinatra would not have been possible without strong company backing. +In the past, financial and emotional support have been provided mainly by +[Heroku](http://heroku.com), [GitHub](https://github.com) and +[Engine Yard](http://www.engineyard.com/), and is now taken care of by +[Travis CI](http://travis-ci.com/). + +Special thanks to the following extraordinary individuals, without whom +Sinatra would not be possible: + +* [Ryan Tomayko](http://tomayko.com/) (rtomayko) for constantly fixing + whitespace errors __60d5006__ +* [Ezra Zygmuntowicz](http://brainspl.at/) (ezmobius) for initial help and + letting Blake steal some of merbs internal code. +* [Chris Schneider](http://gittr.com) (cschneid) for The Book, the blog, + [irclogger.com](http://irclogger.com/sinatra/), and a bunch of useful + patches. +* [Markus Prinz](http://nuclearsquid.com/) (cypher) for patches over the + years, caring about the README, and hanging in there when times were rough. +* [Erik Kastner](http://metaatem.net/) (kastner) for fixing `MIME_TYPES` under + Rack 0.5. +* [Ben Bleything](http://blog.bleything.net/) (bleything) for caring about HTTP + status codes and doc fixes. +* [Igal Koshevoy](http://twitter.com/igalko) (igal) for root path detection under + Thin/Passenger. +* [Jon Crosby](http://joncrosby.me/) (jcrosby) for coffee breaks, doc fixes, and + just because, man. +* [Karel Minarik](https://github.com/karmi) (karmi) for screaming until the + website came back up. +* [Jeremy Evans](http://code.jeremyevans.net/) (jeremyevans) for unbreaking + optional path params (twice!) +* [The GitHub guys](https://github.com/) for stealing Blake's table. +* [Nickolas Means](http://nmeans.org/) (nmeans) for Sass template support. +* [Victor Hugo Borja](https://github.com/vic) (vic) for splat'n routes specs and + doco. +* [Avdi Grimm](http://avdi.org/) (avdi) for basic RSpec support. +* [Jack Danger Canty](http://jåck.com/) for a more accurate root directory + and for making me watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just + now. +* Mathew Walker for making escaped paths work with static files. +* Millions of Us for having the problem that led to Sinatra's conception. +* [Songbird](http://getsongbird.com/) for the problems that helped Sinatra's + future become realized. +* [Rick Olson](http://techno-weenie.net/) (technoweenie) for the killer plug + at RailsConf '08. +* Steven Garcia for the amazing custom artwork you see on 404's and 500's +* [Pat Nakajima](http://patnakajima.com/) (nakajima) for fixing non-nested + params in nested params Hash's. +* Gabriel Andretta for having people wonder whether our documentation is + actually in English or in Spanish. +* Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang, + Mickael Riga, Bernhard Essl, Janos Hardi, Kouhei Yanagita and + "burningTyger" for willingly translating whatever ends up in the README. +* [Wordy](https://wordy.com/) for proofreading our README. **73e137d** +* cactus for digging through code and specs, multiple times. +* Nicolás Sanguinetti (foca) for strong demand of karma and shaping + helpers/register. + +And last but not least: + +* [Frank Sinatra](http://www.sinatra.com/) (chairman of the board) for having so much class he + deserves a web-framework named after him. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CHANGELOG.md new file mode 100644 index 0000000000..4b32d698f3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CHANGELOG.md @@ -0,0 +1,1557 @@ +## 2.1.0 / 2020-09-05 + +* Fix additional Ruby 2.7 keyword warnings [#1586](https://github.com/sinatra/sinatra/pull/1586) by Stefan Sundin + +* Drop Ruby 2.2 support [#1455](https://github.com/sinatra/sinatra/pull/1455) by Eloy Pérez + +* Add Rack::Protection::ReferrerPolicy [#1291](https://github.com/sinatra/sinatra/pull/1291) by Stefan Sundin + +* Add `default_content_type` setting. Fixes [#1238](https://github.com/sinatra/sinatra/pull/1238) [#1239](https://github.com/sinatra/sinatra/pull/1239) by Mike Pastore + +* Allow `set :` in sinatra-namespace [#1255](https://github.com/sinatra/sinatra/pull/1255) by Christian Höppner + +* Use prepend instead of include for helpers. Fixes [#1213](https://github.com/sinatra/sinatra/pull/1213) [#1214](https://github.com/sinatra/sinatra/pull/1214) by Mike Pastore + +* Fix issue with passed routes and provides Fixes [#1095](https://github.com/sinatra/sinatra/pull/1095) [#1606](https://github.com/sinatra/sinatra/pull/1606) by Mike Pastore, Jordan Owens + +* Add QuietLogger that excludes pathes from Rack::CommonLogger [1250](https://github.com/sinatra/sinatra/pull/1250) by Christoph Wagner + +* Sinatra::Contrib dependency updates. Fixes [#1207](https://github.com/sinatra/sinatra/pull/1207) [#1411](https://github.com/sinatra/sinatra/pull/1411) by Mike Pastore + +* Allow CSP to fallback to default-src. Fixes [#1484](https://github.com/sinatra/sinatra/pull/1484) [#1490](https://github.com/sinatra/sinatra/pull/1490) by Jordan Owens + +* Replace `origin_whitelist` with `permitted_origins`. Closes [#1620](https://github.com/sinatra/sinatra/issues/1620) [#1625](https://github.com/sinatra/sinatra/pull/1625) by rhymes + +* Use Rainbows instead of thin for async/stream features. Closes [#1624](https://github.com/sinatra/sinatra/issues/1624) [#1627](https://github.com/sinatra/sinatra/pull/1627) by Ryuichi KAWAMATA + +* Enable EscapedParams if passed via settings. Closes [#1615](https://github.com/sinatra/sinatra/issues/1615) [#1632](https://github.com/sinatra/sinatra/issues/1632) by Anders Bälter + +* Support for parameters in mime types. Fixes [#1141](https://github.com/sinatra/sinatra/issues/1141) by John Hope + +* Handle null byte when serving static files [#1574](https://github.com/sinatra/sinatra/issues/1574) by Kush Fanikiso + +* Improve development support and documentation and source code by Olle Jonsson, Pierre-Adrien Buisson, Shota Iguchi + +## 2.0.8.1 / 2020-01-02 + +* Allow multiple hashes to be passed in `merge` and `merge!` for `Sinatra::IndifferentHash` [#1572](https://github.com/sinatra/sinatra/pull/1572) by Shota Iguchi + +## 2.0.8 / 2020-01-01 + +* Lookup Tilt class for template engine without loading files [#1558](https://github.com/sinatra/sinatra/pull/1558). Fixes [#1172](https://github.com/sinatra/sinatra/issues/1172) by Jordan Owens + +* Add request info in NotFound exception [#1566](https://github.com/sinatra/sinatra/pull/1566) by Stefan Sundin + +* Add `.yaml` support in `Sinatra::Contrib::ConfigFile` [#1564](https://github.com/sinatra/sinatra/issues/1564). Fixes [#1563](https://github.com/sinatra/sinatra/issues/1563) by Emerson Manabu Araki + +* Remove only routing parameters from @params hash [#1569](https://github.com/sinatra/sinatra/pull/1569). Fixes [#1567](https://github.com/sinatra/sinatra/issues/1567) by Jordan Owens, Horacio + +* Support `capture` and `content_for` with Hamlit [#1580](https://github.com/sinatra/sinatra/pull/1580) by Takashi Kokubun + +* Eliminate warnings of keyword parameter for Ruby 2.7.0 [#1581](https://github.com/sinatra/sinatra/pull/1581) by Osamtimizer + +## 2.0.7 / 2019-08-22 + +* Fix a regression [#1560](https://github.com/sinatra/sinatra/pull/1560) by Kunpei Sakai + +## 2.0.6 / 2019-08-21 + +* Fix an issue setting environment from command line option [#1547](https://github.com/sinatra/sinatra/pull/1547), [#1554](https://github.com/sinatra/sinatra/pull/1554) by Jordan Owens, Kunpei Sakai + +* Support pandoc as a new markdown renderer [#1533](https://github.com/sinatra/sinatra/pull/1533) by Vasiliy + +* Remove outdated code for tilt 1.x [#1532](https://github.com/sinatra/sinatra/pull/1532) by Vasiliy + +* Remove an extra logic for `force_encoding` [#1527](https://github.com/sinatra/sinatra/pull/1527) by Jordan Owens + +* Avoid multiple errors even if `params` contains special values [#1526](https://github.com/sinatra/sinatra/pull/1527) by Kunpei Sakai + +* Support `bundler/inline` with `require 'sinatra'` integration [#1520](https://github.com/sinatra/sinatra/pull/1520) by Kunpei Sakai + +* Avoid `TypeError` when params contain a key without a value on Ruby < 2.4 [#1516](https://github.com/sinatra/sinatra/pull/1516) by Samuel Giddins + +* Improve development support and documentation and source code by Olle Jonsson, Basavanagowda Kanur, Yuki MINAMIYA + +## 2.0.5 / 2018-12-22 + +* Avoid FrozenError when params contains frozen value [#1506](https://github.com/sinatra/sinatra/pull/1506) by Kunpei Sakai + +* Add support for Erubi [#1494](https://github.com/sinatra/sinatra/pull/1494) by @tkmru + +* `IndifferentHash` monkeypatch warning improvements [#1477](https://github.com/sinatra/sinatra/pull/1477) by Mike Pastore + +* Improve development support and documentation and source code by Anusree Prakash, Jordan Owens, @ceclinux and @krororo. + +### sinatra-contrib + +* Add `flush` option to `content_for` [#1225](https://github.com/sinatra/sinatra/pull/1225) by Shota Iguchi + +* Drop activesupport dependency from sinatra-contrib [#1448](https://github.com/sinatra/sinatra/pull/1448) + +* Update `yield_content` to append default to ERB template buffer [#1500](https://github.com/sinatra/sinatra/pull/1500) by Jordan Owens + +### rack-protection + +* Don't track the Accept-Language header by default [#1504](https://github.com/sinatra/sinatra/pull/1504) by Artem Chistyakov + +## 2.0.4 / 2018-09-15 + +* Don't blow up when passing frozen string to `send_file` disposition [#1137](https://github.com/sinatra/sinatra/pull/1137) by Andrew Selder + +* Fix ubygems LoadError [#1436](https://github.com/sinatra/sinatra/pull/1436) by Pavel Rosický + +* Unescape regex captures [#1446](https://github.com/sinatra/sinatra/pull/1446) by Jordan Owens + +* Slight performance improvements for IndifferentHash [#1427](https://github.com/sinatra/sinatra/pull/1427) by Mike Pastore + +* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider + +## 2.0.3 / 2018-06-09 + +* Fix the backports gem regression [#1442](https://github.com/sinatra/sinatra/issues/1442) by Marc-André Lafortune + +## 2.0.2 / 2018-06-05 + +* Escape invalid query parameters [#1432](https://github.com/sinatra/sinatra/issues/1432) by Kunpei Sakai + * The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627). + +* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](https://github.com/sinatra/sinatra/issues/1431) by Arpit Chauhan + +* Add xml content-types to valid html_types for Rack::Protection [#1413](https://github.com/sinatra/sinatra/issues/1413) by Reenan Arbitrario + +* Encode route parameters using :default_encoding setting [#1412](https://github.com/sinatra/sinatra/issues/1412) by Brian m. Carlson + +* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](https://github.com/sinatra/sinatra/issues/1244) by John Hope + +* Add Sinatra::IndifferentHash#slice [#1405](https://github.com/sinatra/sinatra/issues/1405) by Shota Iguchi + +* Remove status code 205 from drop body response [#1398](https://github.com/sinatra/sinatra/issues/1398) by Shota Iguchi + +* Ignore empty captures from params [#1390](https://github.com/sinatra/sinatra/issues/1390) by Shota Iguchi + +* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien + +## 2.0.1 / 2018-02-17 + +* Repair nested namespaces, by avoiding prefix duplication [#1322](https://github.com/sinatra/sinatra/issues/1322). Fixes [#1310](https://github.com/sinatra/sinatra/issues/1310) by Kunpei Sakai + +* Add pattern matches to values for Mustermann::Concat [#1333](https://github.com/sinatra/sinatra/issues/1333). Fixes [#1332](https://github.com/sinatra/sinatra/issues/1332) by Dawa Ometto + +* Ship the VERSION file with the gem, to allow local unpacking [#1338](https://github.com/sinatra/sinatra/issues/1338) by Olle Jonsson + +* Fix issue with custom error handler on bad request [#1351](https://github.com/sinatra/sinatra/issues/1351). Fixes [#1350](https://github.com/sinatra/sinatra/issues/1350) by Jordan Owens + +* Override Rack::ShowExceptions#pretty to set custom template [#1377](https://github.com/sinatra/sinatra/issues/1377). Fixes [#1376](https://github.com/sinatra/sinatra/issues/1376) by Jordan Owens + +* Enhanced path validation in Windows [#1379](https://github.com/sinatra/sinatra/issues/1379) by Orange Tsai from DEVCORE + * The patch fixes [CVE-2018-7212](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7212) + +* Improve development support and documentation by Faheel Ahmad, Shota Iguchi, Olle Jonsson, Manabu Niseki, John Hope, Horacio, Ice-Storm, GraniteRock, Raman Skaskevich, Carlos Azuaje, 284km, Dan Rice and Zachary Scott + +## 2.0.0 / 2017-04-10 + + * Use Mustermann for patterns [#1086](https://github.com/sinatra/sinatra/issues/1086) by Konstantin Haase + + * Server now provides `-q` flag for quiet mode, which disables start/stop messages [#1153](https://github.com/sinatra/sinatra/issues/1153) by Vasiliy. + + * Session middleware can now be specified with `:session_store` setting [#1161](https://github.com/sinatra/sinatra/issues/1161) by Jordan Owens. + + * `APP_ENV` is now preferred and recommended over `RACK_ENV` for setting environment [#984](https://github.com/sinatra/sinatra/issues/984) by Damien Mathieu. + + * Add Reel support [#793](https://github.com/sinatra/sinatra/issues/793) by Patricio Mac Adden. + + * Make route params available during error handling [#895](https://github.com/sinatra/sinatra/issues/895) by Jeremy Evans. + + * Unify `not_found` and `error` 404 behavior [#896](https://github.com/sinatra/sinatra/issues/896) by Jeremy Evans. + + * Enable Ruby 2.3 `frozen_string_literal` feature [#1076](https://github.com/sinatra/sinatra/issues/1076) by Vladimir Kochnev. + + * Add Sinatra::ShowExceptions::TEMPLATE and patched Rack::ShowExceptions to prefer Sinatra template by Zachary Scott. + + * Sinatra::Runner is used internally for integration tests [#840](https://github.com/sinatra/sinatra/issues/840) by Nick Sutterer. + + * Fix case-sensitivity issue in `uri` method [#889](https://github.com/sinatra/sinatra/issues/889) by rennex. + + * Use `Rack::Utils.status_code` to allow `status` helper to use symbol as well as numeric codes [#968](https://github.com/sinatra/sinatra/issues/968) by Tobias H. Michaelsen. + + * Improved error handling for invalid params through Rack [#1070](https://github.com/sinatra/sinatra/issues/1070) by Jordan Owens. + + * Ensure template is cached only once [#1021](https://github.com/sinatra/sinatra/issues/1021) by Patrik Rak. + + * Rack middleware is initialized at server runtime rather than after receiving first request [#1205](https://github.com/sinatra/sinatra/issues/1205) by Itamar Turner-Trauring. + + * Improve Session Secret documentation to encourage better security practices [#1218](https://github.com/sinatra/sinatra/issues/1218) by Glenn Rempe + + * Exposed global and per-route options for Mustermann route parsing [#1233](https://github.com/sinatra/sinatra/issues/1233) by Mike Pastore + + * Use same `session_secret` for classic and modular apps in development [#1245](https://github.com/sinatra/sinatra/issues/1245) by Marcus Stollsteimer + + * Make authenticity token length a fixed value of 32 [#1181](https://github.com/sinatra/sinatra/issues/1181) by Jordan Owens + + * Modernize Rack::Protection::ContentSecurityPolicy with CSP Level 2 and 3 Directives [#1202](https://github.com/sinatra/sinatra/issues/1202) by Glenn Rempe + + * Adds preload option to Rack:Protection:StrictTransport [#1209](https://github.com/sinatra/sinatra/issues/1209) by Ed Robinson + + * Improve BadRequest logic. Raise and handle exceptions if status is 400 [#1212](https://github.com/sinatra/sinatra/issues/1212) by Mike Pastore + + * Make Rack::Test a development dependency [#1232](https://github.com/sinatra/sinatra/issues/1232) by Mike Pastore + + * Capture exception messages of raised NotFound and BadRequest [#1210](https://github.com/sinatra/sinatra/issues/1210) by Mike Pastore + + * Add explicit set method to contrib/cookies to override cookie settings [#1240](https://github.com/sinatra/sinatra/issues/1240) by Andrew Allen + + * Avoid executing filters even if prefix matches with other namespace [#1253](https://github.com/sinatra/sinatra/issues/1253) by namusyaka + + * Make `#has_key?` also indifferent in access, can accept String or Symbol [#1262](https://github.com/sinatra/sinatra/issues/1262) by Stephen Paul Weber + + * Add `allow_if` option to bypass json csrf protection [#1265](https://github.com/sinatra/sinatra/issues/1265) by Jordan Owens + + * rack-protection: Bundle StrictTransport, CookieTossing, and CSP [#1267](https://github.com/sinatra/sinatra/issues/1267) by Mike Pastore + + * Add `:strict_paths` option for managing trailing slashes [#1273](https://github.com/sinatra/sinatra/issues/1273) by namusyaka + + * Add full IndifferentHash implementation to params [#1279](https://github.com/sinatra/sinatra/issues/1279) by Mike Pastore + +## 1.4.8 / 2017-01-30 + + * Fix the deprecation warning from Ruby about Fixnum. [#1235](https://github.com/sinatra/sinatra/issues/1235) by Akira Matsuda + +## 1.4.7 / 2016-01-24 + + * Add Ashley Williams, Trevor Bramble, and Kashyap Kondamudi to team Sinatra. + + * Correctly handle encoded colons in routes. (Jeremy Evans) + + * Rename CHANGES to CHANGELOG.md and update Rakefile. [#1043](https://github.com/sinatra/sinatra/issues/1043) (Eliza Sorensen) + + * Improve documentation. [#941](https://github.com/sinatra/sinatra/issues/941), [#1069](https://github.com/sinatra/sinatra/issues/1069), [#1075](https://github.com/sinatra/sinatra/issues/1075), [#1025](https://github.com/sinatra/sinatra/issues/1025), [#1052](https://github.com/sinatra/sinatra/issues/1052) (Many great folks) + + * Introduce `Sinatra::Ext` to workaround Rack 1.6 bug to fix Ruby 1.8.7 + support. [#1080](https://github.com/sinatra/sinatra/issues/1080) (Zachary Scott) + + * Add CONTRIBUTING guide. [#987](https://github.com/sinatra/sinatra/issues/987) (Katrina Owen) + + +## 1.4.6 / 2015-03-23 + + * Improve tests and documentation. (Darío Hereñú, Seiichi Yonezawa, kyoendo, + John Voloski, Ferenc-, Renaud Martinet, Christian Haase, marocchino, + huoxito, Damir Svrtan, Amaury Medeiros, Jeremy Evans, Kashyap, shenqihui, + Ausmarton Fernandes, kami, Vipul A M, Lei Wu, 7stud, Taylor Shuler, + namusyaka, burningTyger, Cornelius Bock, detomastah, hakeda, John Hope, + Ruben Gonzalez, Andrey Deryabin, attilaolah, Anton Davydov, Nikita Penzin, + Dyego Costa) + + * Remove duplicate require of sinatra/base. (Alexey Muranov) + + * Escape HTML in 404 error page. (Andy Brody) + + * Refactor to method call in `Stream#close` and `#callback`. (Damir Svrtan) + + * Depend on latest version of Slim. (Damir Svrtan) + + * Fix compatibility with Tilt version 2. (Yegor Timoschenko) + + * Fix compatibility issue with Rack `pretty` method from ShowExceptions. + (Kashyap) + + * Show date in local time in exception messages. (tayler1) + + * Fix logo on error pages when using Ruby 1.8. (Jeremy Evans) + + * Upgrade test suite to Minitest version 5 and fix Ruby 2.2 compatibility. + (Vipul A M) + +## 1.4.5 / 2014-04-08 + + * Improve tests and documentation. (Seiichi Yonezawa, Mike Gehard, Andrew + Deitrick, Matthew Nicholas Bradley, GoGo tanaka, Carlos Lazo, Shim Tw, + kyoendo, Roman Kuznietsov, Stanislav Chistenko, Ryunosuke SATO, Ben Lewis, + wuleicanada, Patricio Mac Adden, Thais Camilo) + + * Fix Ruby warnings. (Vipul A M, Piotr Szotkowski) + + * Fix template cache memory leak. (Scott Holden) + + * Work around UTF-8 bug in JRuby. (namusyaka) + + * Don't set charset for JSON mime-type (Sebastian Borrazas) + + * Fix bug in request.accept? that might trigger a NoMethodError. (sbonami) + +## 1.4.4 / 2013-10-21 + + * Allow setting layout to false specifically for a single rendering engine. + (Matt Wildig) + + * Allow using wildcard in argument passed to `request.accept?`. (wilkie) + + * Treat missing Accept header like wild card. (Patricio Mac Adden) + + * Improve tests and documentation. (Darío Javier Cravero, Armen P., michelc, + Patricio Mac Adden, Matt Wildig, Vipul A M, utenmiki, George Timoschenko, + Diogo Scudelletti) + + * Fix Ruby warnings. (Vipul A M, Patricio Mac Adden) + + * Improve self-hosted server started by `run!` method or in classic mode. + (Tobias Bühlmann) + + * Reduce objects allocated per request. (Vipul A M) + + * Drop unused, undocumented options hash from Sinatra.new. (George Timoschenko) + + * Keep Content-Length header when response is a `Rack::File` or when streaming. + (Patricio Mac Adden, George Timoschenko) + + * Use reel if it's the only server available besides webrick. (Tobias Bühlmann) + + * Add `disable :traps` so setting up signal traps for self hosted server can be + skipped. (George Timoschenko) + + * The `status` option passed to `send_file` may now be a string. (George + Timoschenko) + + * Reduce file size of dev mode images for 404 and 500 pages. (Francis Go) + +## 1.4.3 / 2013-06-07 + + * Running a Sinatra file directly or via `run!` it will now ignore an + empty $PORT env variable. (noxqsgit) + + * Improve documentation. (burningTyger, Patricio Mac Adden, + Konstantin Haase, Diogo Scudelletti, Dominic Imhof) + + * Expose matched pattern as env["sinatra.route"]. (Aman Gupta) + + * Fix warning on Ruby 2.0. (Craig Little) + + * Improve running subset of tests in isolation. (Viliam Pucik) + + * Reorder private/public methods. (Patricio Mac Adden) + + * Loosen version dependency for rack, so it runs with Rails 3.2. + (Konstantin Haase) + + * Request#accept? now returns true instead of a truthy value. (Alan Harris) + +## 1.4.2 / 2013-03-21 + + * Fix parsing error for case where both the pattern and the captured part + contain a dot. (Florian Hanke, Konstantin Haase) + + * Missing Accept header is treated like */*. (Greg Denton) + + * Improve documentation. (Patricio Mac Adden, Joe Bottigliero) + +## 1.4.1 / 2013-03-15 + + * Make delegated methods available in config.ru (Konstantin Haase) + +## 1.4.0 / 2013-03-15 + + * Add support for LINK and UNLINK requests. (Konstantin Haase) + + * Add support for Yajl templates. (Jamie Hodge) + + * Add support for Rabl templates. (Jesse Cooke) + + * Add support for Wlang templates. (Bernard Lambeau) + + * Add support for Stylus templates. (Juan David Pastas, Konstantin Haase) + + * You can now pass a block to ERb, Haml, Slim, Liquid and Wlang templates, + which will be used when calling `yield` in the template. (Alexey Muranov) + + * When running in classic mode, no longer include Sinatra::Delegator in Object, + instead extend the main object only. (Konstantin Haase) + + * Improved route parsing: "/:name.?:format?" with "/foo.png" now matches to + {name: "foo", format: "png"} instead of {name: "foo.png"}. (Florian Hanke) + + * Add :status option support to send_file. (Konstantin Haase) + + * The `provides` condition now respects an earlier set content type. + (Konstantin Haase) + + * Exception#code is only used when :use_code is enabled. Moreover, it will + be ignored if the value is not between 400 and 599. You should use + Exception#http_status instead. (Konstantin Haase) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + + * Sinatra::Base.new now returns a Sinatra::Wrapper instance, exposing + `#settings` and `#helpers`, yet going through the middleware stack on + `#call`. It also implements a nice `#inspect`, so it plays nice with + Rails' `rake routes`. (Konstantin Haase) + + * In addition to WebRick, Thin and Mongrel, Sinatra will now automatically pick + up Puma, Trinidad, ControlTower or Net::HTTP::Server when installed. The + logic for picking the server has been improved and now depends on the Ruby + implementation used. (Mark Rada, Konstantin Haase, Patricio Mac Adden) + + * "Sinatra doesn't know this ditty" pages now show the app class when running + a modular application. This helps detecting where the response came from when + combining multiple modular apps. (Konstantin Haase) + + * When port is not set explicitly, use $PORT env variable if set and only + default to 4567 if not. Plays nice with foreman. (Konstantin Haase) + + * Allow setting layout on a per engine basis. (Zachary Scott, Konstantin Haase) + + * You can now use `register` directly in a classic app. (Konstantin Haase) + + * `redirect` now accepts URI or Addressable::URI instances. (Nicolas + Sanguinetti) + + * Have Content-Disposition header also include file name for `inline`, not + just for `attachment`. (Konstantin Haase) + + * Better compatibility to Rack 1.5. (James Tucker, Konstantin Haase) + + * Make route parsing regex more robust. (Zoltan Dezso, Konstantin Haase) + + * Improve Accept header parsing, expose parameters. (Pieter van de Bruggen, + Konstantin Haase) + + * Add `layout_options` render option. Allows you, amongst other things, to + render a layout from a different folder. (Konstantin Haase) + + * Explicitly setting `layout` to `nil` is treated like setting it to `false`. + (richo) + + * Properly escape attributes in Content-Type header. (Pieter van de Bruggen) + + * Default to only serving localhost in development mode. (Postmodern) + + * Setting status code to 404 in error handler no longer triggers not_found + handler. (Konstantin Haase) + + * The `protection` option now takes a `session` key for force + disabling/enabling session based protections. (Konstantin Haase) + + * Add `x_cascade` option to disable `X-Cascade` header on missing route. + (Konstantin Haase) + + * Improve documentation. (Kashyap, Stanislav Chistenko, Zachary Scott, + Anthony Accomazzo, Peter Suschlik, Rachel Mehl, ymmtmsys, Anurag Priyam, + burningTyger, Tony Miller, akicho8, Vasily Polovnyov, Markus Prinz, + Alexey Muranov, Erik Johnson, Vipul A M, Konstantin Haase) + + * Convert documentation to Markdown. (Kashyap, Robin Dupret, burningTyger, + Vasily Polovnyov, Iain Barnett, Giuseppe Capizzi, Neil West) + + * Don't set not_found content type to HTML in development mode with custom + not_found handler. (Konstantin Haase) + + * Fix mixed indentation for private methods. (Robin Dupret) + + * Recalculate Content-Length even if hard coded if body is reset. Relevant + mostly for error handlers. (Nathan Esquenazi, Konstantin Haase) + + * Plus sign is once again kept as such when used for URL matches. (Konstantin + Haase) + + * Take views option into account for template caching. (Konstantin Haase) + + * Consistent use of `headers` instead of `header` internally. (Patricio Mac Adden) + + * Fix compatibility to RDoc 4. (Bohuslav Kabrda) + + * Make chat example work with latest jQuery. (loveky, Tony Miller) + + * Make tests run without warnings. (Patricio Mac Adden) + + * Make sure value returned by `mime_type` is a String or nil, even when a + different object is passed in, like an AcceptEntry. (Konstantin Haase) + + * Exceptions in `after` filter are now handled like any other exception. + (Nathan Esquenazi) + +## 1.3.6 (backport release) / 2013-03-15 + +Backported from 1.4.0: + + * Take views option into account for template caching. (Konstantin Haase) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + +## 1.3.5 / 2013-02-25 + + * Fix for RubyGems 2.0 (Uchio KONDO) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + +## 1.3.4 / 2013-01-26 + + * Improve documentation. (Kashyap, Stanislav Chistenko, Konstantin Haase, + ymmtmsys, Anurag Priyam) + + * Adjustments to template system to work with Tilt edge. (Konstantin Haase) + + * Fix streaming with latest Rack release. (Konstantin Haase) + + * Fix default content type for Sinatra::Response with latest Rack release. + (Konstantin Haase) + + * Fix regression where + was no longer treated like space. (Ross Boucher) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + +## 1.3.3 / 2012-08-19 + + * Improved documentation. (burningTyger, Konstantin Haase, Gabriel Andretta, + Anurag Priyam, michelc) + + * No longer modify the load path. (Konstantin Haase) + + * When keeping a stream open, set up callback/errback correctly to deal with + clients closing the connection. (Konstantin Haase) + + * Fix bug where having a query param and a URL param by the same name would + concatenate the two values. (Konstantin Haase) + + * Prevent duplicated log output when application is already wrapped in a + `Rack::CommonLogger`. (Konstantin Haase) + + * Fix issue where `Rack::Link` and Rails were preventing indefinite streaming. + (Konstantin Haase) + + * No longer cause warnings when running Ruby with `-w`. (Konstantin Haase) + + * HEAD requests on static files no longer report a Content-Length of 0, but + instead the proper length. (Konstantin Haase) + + * When protecting against CSRF attacks, drop the session instead of refusing + the request. (Konstantin Haase) + +## 1.3.2 / 2011-12-30 + + * Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it, + too. (Konstantin Haase) + + * Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`. + (Konstantin Haase) + + * Route specific params are now available in the block passed to #stream. + (Konstantin Haase) + + * Fix bug where rendering a second template in the same request, after the + first one raised an exception, skipped the default layout. (Nathan Baum) + + * Fix bug where parameter escaping got enabled when disabling a different + protection. (Konstantin Haase) + + * Fix regression: Filters without a pattern may now again manipulate the params + hash. (Konstantin Haase) + + * Added examples directory. (Konstantin Haase) + + * Improved documentation. (Gabriel Andretta, Markus Prinz, Erick Zetta, Just + Lest, Adam Vaughan, Aleksander Dąbrowski) + + * Improved MagLev support. (Tim Felgentreff) + +## 1.3.1 / 2011-10-05 + + * Support adding more than one callback to the stream object. (Konstantin + Haase) + + * Fix for infinite loop when streaming on 1.9.2 with Thin from a modular + application (Konstantin Haase) + +## 1.3.0 / 2011-09-30 + + * Added `stream` helper method for easily creating streaming APIs, Server + Sent Events or even WebSockets. See README for more on that topic. + (Konstantin Haase) + + * If a HTTP 1.1 client is redirected from a different verb than GET, use 303 + instead of 302 by default. You may still pass 302 explicitly. Fixes AJAX + redirects in Internet Explorer 9 (to be fair, everyone else is doing it + wrong and IE is behaving correct). (Konstantin Haase) + + * Added support for HTTP PATCH requests. (Konstantin Haase) + + * Use rack-protection to defend against common opportunistic attacks. + (Josh Lane, Jacob Burkhart, Konstantin Haase) + + * Support for Creole templates, Creole is a standardized wiki markup, + supported by many wiki implementations. (Konstanin Haase) + + * The `erubis` method has been deprecated. If Erubis is available, Sinatra + will automatically use it for rendering ERB templates. `require 'erb'` + explicitly to prevent that behavior. (Magnus Holm, Ryan Tomayko, Konstantin + Haase) + + * Patterns now match against the escaped URLs rather than the unescaped + version. This makes Sinatra confirm with RFC 2396 section 2.2 and RFC 2616 + section 3.2.3 (escaped reserved characters should not be treated like the + unescaped version), meaning that "/:name" will also match `/foo%2Fbar`, but + not `/foo/bar`. To avoid incompatibility, pattern matching has been + adjusted. Moreover, since we do no longer need to keep an unescaped version + of path_info around, we handle all changes to `env['PATH_INFO']` correctly. + (Konstantin Haase) + + * `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in + modular applications. (Konstantin Haase) + + * Set up `Rack::Logger` or `Rack::NullLogger` depending on whether logging + was enabled or not. Also, expose that logger with the `logger` helper + method. (Konstantin Haase) + + * The sessions setting may be an options hash now. (Konstantin Haase) + + * Important: Ruby 1.8.6 support has been dropped. This version also depends + on at least Rack 1.3.0. This means that it is incompatible with Rails prior + to 3.1.0. Please use 1.2.x if you require an earlier version of Ruby or + Rack, which we will continue to supply with bug fixes. (Konstantin Haase) + + * Renamed `:public` to `:public_folder` to avoid overriding Ruby's built-in + `public` method/keyword. `set(:public, ...)` is still possible but shows a + warning. (Konstantin Haase) + + * It is now possible to use a different target class for the top level DSL + (aka classic style) than `Sinatra::Application` by setting + `Delegator.target`. This was mainly introduced to ease testing. (Konstantin + Haase) + + * Error handlers defined for an error class will now also handle subclasses + of that class, unless more specific error handlers exist. (Konstantin + Haase) + + * Error handling respects Exception#code, again. (Konstantin Haase) + + * Changing a setting will merge hashes: `set(:x, :a => 1); set(:x :b => 2)` + will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to + avoid this behavior. (Konstantin Haase) + + * Added `request.accept?` and `request.preferred_type` to ease dealing with + `Accept` headers. (Konstantin Haase) + + * Added `:static_cache_control` setting to automatically set cache control + headers to static files. (Kenichi Nakamura) + + * Added `informal?`, `success?`, `redirect?`, `client_error?`, + `server_error?` and `not_found?` helper methods to ease dealing with status + codes. (Konstantin Haase) + + * Uses SecureRandom to generate default session secret. (Konstantin Haase) + + * The `attachment` helper will set Content-Type (if it hasn't been set yet) + depending on the supplied file name. (Vasiliy Ermolovich) + + * Conditional requests on `etag` helper now work properly for unsafe HTTP + methods. (Matthew Schinckel, Konstantin Haase) + + * The `last_modified` helper does not stop execution and change the status code + if the status code is something different than 200. (Konstantin Haase) + + * Added support for If-Unmodified-Since header. (Konstantin Haase) + + * `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew + Armenia) + + * `Sinatra::Base.run!` takes a block allowing access to the Rack handler. + (David Waite) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Exception objects are now passed to error handlers. (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * Middleware setup is now distributed across multiple methods, allowing + Sinatra extensions to easily hook into the setup process. (Konstantin + Haase) + + * Internal refactoring and minor performance improvements. (Konstantin Haase) + + * Move Sinatra::VERSION to separate file, so it can be checked without + loading Sinatra. (Konstantin Haase) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + +## 1.2.9 (backports release) / 2013-03-15 + +IMPORTANT: THIS IS THE LAST 1.2.x RELEASE, PLEASE UPGRADE. + + * Display EOL warning when loading Sinatra. (Konstantin Haase) + + * Improve documentation. (Anurag Priyam, Konstantin Haase) + + * Do not modify the load path. (Konstantin Haase) + + * Display deprecation warning if RUBY_IGNORE_CALLERS is used. (Konstantin Haase) + + * Add backports library so we can still run on Ruby 1.8.6. (Konstantin Haase) + +## 1.2.8 (backports release) / 2011-12-30 + +Backported from 1.3.2: + +* Fix bug where rendering a second template in the same request after the + first one raised an exception skipped the default layout (Nathan Baum) + +## 1.2.7 (backports release) / 2011-09-30 + +Custom changes: + + * Fix Ruby 1.8.6 issue with Accept header parsing. (Konstantin Haase) + +Backported from 1.3.0: + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Fix uninitialized instance variable warning. (David Kellum) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + +## 1.2.6 / 2011-05-01 + + * Fix broken delegation, backport delegation tests from Sinatra 1.3. + (Konstantin Haase) + +## 1.2.5 / 2011-04-30 + + * Restore compatibility with Ruby 1.8.6. (Konstantin Haase) + +## 1.2.4 / 2011-04-30 + + * Sinatra::Application (classic style) does not use a session secret in + development mode, so sessions are not invalidated after every request when + using Shotgun. (Konstantin Haase) + + * The request object was shared between multiple Sinatra instances in the + same middleware chain. This caused issues if any non-sinatra routing + happened in-between two of those instances, or running a request twice + against an application (described in the README). The caching was reverted. + See GH[#239](https://github.com/sinatra/sinatra/issues/239) and GH[#256](https://github.com/sinatra/sinatra/issues/256) for more infos. (Konstantin Haase) + + * Fixes issues where the top level DSL was interfering with method_missing + proxies. This issue surfaced when Rails 3 was used with older Sass versions + and Sinatra >= 1.2.0. (Konstantin Haase) + + * Sinatra::Delegator.delegate is now able to delegate any method names, even + those containing special characters. This allows better integration into + other programming languages on Rubinius (probably on the JVM, too), like + Fancy. (Konstantin Haase) + + * Remove HEAD request logic and let Rack::Head handle it instead. (Paolo + "Nusco" Perrotta) + +## 1.2.3 / 2011-04-13 + + * This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2, + however, if you want to use a newer Tilt version, you have to upgrade to at + least this version of Sinatra. (Konstantin Haase) + + * Helpers dealing with time, like `expires`, handle objects that pretend to be + numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase) + +## 1.2.2 / 2011-04-08 + + * The `:provides => :js` condition now matches both `application/javascript` + and `text/javascript`. The `:provides => :xml` condition now matches both + `application/xml` and `text/xml`. The `Content-Type` header is set + accordingly. If the client accepts both, the `application/*` version is + preferred, since the `text/*` versions are deprecated. (Konstantin Haase) + + * The `provides` condition now handles wildcards in `Accept` headers correctly. + Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`. + (Konstantin Haase) + + * When parsing `Accept` headers, `Content-Type` preferences are honored + according to RFC 2616 section 14.1. (Konstantin Haase) + + * URIs passed to the `url` helper or `redirect` may now use any schema to be + identified as absolute URIs, not only `http` or `https`. (Konstantin Haase) + + * Handles `Content-Type` strings that already contain parameters correctly in + `content_type` (example: `content_type "text/plain; charset=utf-16"`). + (Konstantin Haase) + + * If a route with an empty pattern is defined (`get("") { ... }`) requests with + an empty path info match this route instead of "/". (Konstantin Haase) + + * In development environment, when running under a nested path, the image URIs + on the error pages are set properly. (Konstantin Haase) + +## 1.2.1 / 2011-03-17 + + * Use a generated session secret when using `enable :sessions`. (Konstantin + Haase) + + * Fixed a bug where the wrong content type was used if no content type was set + and a template engine was used with a different engine for the layout with + different default content types, say Less embedded in Slim. (Konstantin + Haase) + + * README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé, + Gregor Schmidt) + +## 1.2.0 / 2011-03-03 + + * Added `slim` rendering method for rendering Slim templates. (Steve + Hodgkiss) + + * The `markaby` rendering method now allows passing a block, making inline + usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase) + + * All render methods now take a `:layout_engine` option, allowing to use a + layout in a different template language. Even more useful than using this + directly (`erb :index, :layout_engine => :haml`) is setting this globally for + a template engine that otherwise does not support layouts, like Markdown or + Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase) + + * Before and after filters now support conditions, both with and without + patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase) + + * Added a `url` helper method which constructs absolute URLs. Copes with + reverse proxies and Rack handlers correctly. Aliased to `to`, so you can + write `redirect to('/foo')`. (Konstantin Haase) + + * If running on 1.9, patterns for routes and filters now support named + captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`. + (Steve Price) + + * All rendering methods now take a `:scope` option, which renders them in + another context. Note that helpers and instance variables will be + unavailable if you use this feature. (Paul Walker) + + * The behavior of `redirect` can now be configured with `absolute_redirects` + and `prefixed_redirects`. (Konstantin Haase) + + * `send_file` now allows overriding the Last-Modified header, which defaults + to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase) + + * You can use your own template lookup method by defining `find_template`. + This allows, among other things, using more than one views folder. + (Konstantin Haase) + + * Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel + Andretta, Konstantin Haase) + + * Improved error handling. (cactus, Konstantin Haase) + + * Skip missing template engines in tests correctly. (cactus) + + * Sinatra now ships with a Gemfile for development dependencies, since it eases + supporting different platforms, like JRuby. (Konstantin Haase) + +## 1.1.4 (backports release) / 2011-04-13 + + * Compatible with Tilt 1.3. (Konstantin Haase) + +## 1.1.3 / 2011-02-20 + + * Fixed issues with `user_agent` condition if the user agent header is missing. + (Konstantin Haase) + + * Fix some routing tests that have been skipped by accident (Ross A. Baker) + + * Fix rendering issues with Builder and Nokogiri (Konstantin Haase) + + * Replace last_modified helper with better implementation. (cactus, + Konstantin Haase) + + * Fix issue with charset not being set when using `provides` condition. + (Konstantin Haase) + + * Fix issue with `render` not picking up all alternative file extensions for + a rendering engine - it was not possible to register ".html.erb" without + tricks. (Konstantin Haase) + +## 1.1.2 / 2010-10-25 + +Like 1.1.1, but with proper CHANGES file. + +## 1.1.1 / 2010-10-25 + + * README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov) + and Portuguese (Luciano Sousa). + + * Nested templates without a `:layout` option can now be used from the layout + template without causing an infinite loop. (Konstantin Haase) + + * Inline templates are now encoding aware and can therefore be used with + unicode characters on Ruby 1.9. Magic comments at the beginning of the file + will be honored. (Konstantin Haase) + + * Default `app_file` is set correctly when running with bundler. Using + bundler caused Sinatra not to find the `app_file` and therefore not to find + the `views` folder on it's own. (Konstantin Haase) + + * Better handling of Content-Type when using `send_file`: If file extension + is unknown, fall back to `application/octet-stream` and do not override + content type if it has already been set, except if `:type` is passed + explicitly (Konstantin Haase) + + * Path is no longer cached if changed between handlers that do pattern + matching. This means you can change `request.path_info` in a pattern + matching before filter. (Konstantin Haase) + + * Headers set by cache_control now always set max_age as an Integer, making + sure it is compatible with RFC2616. (Konstantin Haase) + + * Further improved handling of string encodings on Ruby 1.9, templates now + honor default_encoding and URLs support unicode characters. (Konstantin + Haase) + +## 1.1.0 / 2010-10-24 + + * Before and after filters now support pattern matching, including the + ability to use captures: "before('/user/:name') { |name| ... }". This + avoids manual path checking. No performance loss if patterns are avoided. + (Konstantin Haase) + + * It is now possible to render SCSS files with the `scss` method, which + behaves exactly like `sass` except for the different file extension and + assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase) + + * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`, + `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown, + Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates. + (Konstantin Haase) + + * Now supports byte-range requests (the HTTP_RANGE header) for static files. + Multi-range requests are not supported, however. (Jens Alfke) + + * You can now use #settings method from class and top level for convenience. + (Konstantin Haase) + + * Setting multiple values now no longer relies on #to_hash and therefore + accepts any Enumerable as parameter. (Simon Rozet) + + * Nested templates default the `layout` option to `false` rather than `true`. + This eases the use of partials. If you wanted to render one haml template + embedded in another, you had to call `haml :partial, {}, :layout => false`. + As you almost never want the partial to be wrapped in the standard layout + in this situation, you now only have to call `haml :partial`. Passing in + `layout` explicitly is still possible. (Konstantin Haase) + + * If a the return value of one of the render functions is used as a response + body and the content type has not been set explicitly, Sinatra chooses a + content type corresponding to the rendering engine rather than just using + "text/html". (Konstantin Haase) + + * README is now available in Chinese (Wu Jiang), French (Mickael Riga), + German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos + Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese + README has been updated (Kouhei Yanagita). + + * It is now possible to access Sinatra's template_cache from the outside. + (Nick Sutterer) + + * The `last_modified` method now also accepts DateTime instances and makes + sure the header will always be set to a string. (Konstantin Haase) + + * 599 now is a legal status code. (Steve Shreeve) + + * This release is compatible with Ruby 1.9.2. Sinatra was trying to read + non existent files Ruby added to the call stack. (Shota Fukumori, + Konstantin Haase) + + * Prevents a memory leak on 1.8.6 in production mode. Note, however, that + this is due to a bug in 1.8.6 and request will have the additional overhead + of parsing templates again on that version. It is recommended to use at + least Ruby 1.8.7. (Konstantin Haase) + + * Compares last modified date correctly. `last_modified` was halting only + when the 'If-Modified-Since' header date was equal to the time specified. + Now, it halts when is equal or later than the time specified (Gabriel + Andretta). + + * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra + application under a subpath in Rails 3, the PATH_INFO is not prefixed with + a slash and no routes did match. (José Valim) + + * Better handling of encodings in 1.9, defaults params encoding to UTF-8. + (Konstantin Haase) + + * `show_exceptions` handling is now triggered after custom error handlers, if + it is set to `:after_handlers`, thus not disabling those handler in + development mode. (pangel, Konstantin Haase) + + * Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino) + + * `send_file` now always respects the `:type` option if set. Previously it + was discarded if no matching mime type was found, which made it impossible + to directly pass a mime type. (Konstantin Haase) + + * `redirect` always redirects to an absolute URI, even if a relative URI was + passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe + Garcia Ballester, Anthony Williams) + + * Broken examples for using Erubis, Haml and Test::Unit in README have been + fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden) + + * Sinatra now handles SIGTERM correctly. (Patrick Collison) + + * Fixes an issue with inline templates in modular applications that manually + call `run!`. (Konstantin Haase) + + * Spaces after inline template names are now ignored (Konstantin Haase) + + * It's now possible to use Sinatra with different package management + systems defining a custom require. (Konstantin Haase) + + * Lighthouse has been dropped in favor of GitHub issues. + + * Tilt is now a dependency and therefore no longer ships bundled with + Sinatra. (Ryan Tomayko, Konstantin Haase) + + * Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported. + (Konstantin Haase) + +## 1.0 / 2010-03-23 + + * It's now possible to register blocks to run after each request using + after filters. After filters run at the end of each request, after + routes and error handlers. (Jimmy Schementi) + + * Sinatra now uses Tilt for rendering + templates. This adds support for template caching, consistent + template backtraces, and support for new template engines, like + mustache and liquid. (Ryan Tomayko) + + * ERB, Erubis, and Haml templates are now compiled the first time + they're rendered instead of being string eval'd on each invocation. + Benchmarks show a 5x-10x improvement in render time. This also + reduces the number of objects created, decreasing pressure on Ruby's + GC. (Ryan Tomayko) + + * New 'settings' method gives access to options in both class and request + scopes. This replaces the 'options' method. (Chris Wanstrath) + + * New boolean 'reload_templates' setting controls whether template files + are reread from disk and recompiled on each request. Template read/compile + is cached by default in all environments except development. (Ryan Tomayko) + + * New 'erubis' helper method for rendering ERB template with Erubis. The + erubis gem is required. (Dylan Egan) + + * New 'cache_control' helper method provides a convenient way of + setting the Cache-Control response header. Takes a variable number + of boolean directives followed by a hash of value directives, like + this: cache_control :public, :must_revalidate, :max_age => 60 + (Ryan Tomayko) + + * New 'expires' helper method is like cache_control but takes an + integer number of seconds or Time object: + expires 300, :public, :must_revalidate + (Ryan Tomayko) + + * New request.secure? method for checking for an SSL connection. + (Adam Wiggins) + + * Sinatra apps can now be run with a `-o ` argument to specify + the address to bind to. (Ryan Tomayko) + + * Rack::Session::Cookie is now added to the middleware pipeline when + running in test environments if the :sessions option is set. + (Simon Rozet) + + * Route handlers, before filters, templates, error mappings, and + middleware are now resolved dynamically up the inheritance hierarchy + when needed instead of duplicating the superclass's version when + a new Sinatra::Base subclass is created. This should fix a variety + of issues with extensions that need to add any of these things + to the base class. (Ryan Tomayko) + + * Exception error handlers always override the raise_errors option now. + Previously, all exceptions would be raised outside of the application + when the raise_errors option was enabled, even if an error handler was + defined for that exception. The raise_errors option now controls + whether unhandled exceptions are raised (enabled) or if a generic 500 + error is returned (disabled). (Ryan Tomayko) + + * The X-Cascade response header is set to 'pass' when no matching route + is found or all routes pass. (Josh Peek) + + * Filters do not run when serving static files anymore. (Ryan Tomayko) + + * pass takes an optional block to be used as the route handler if no + subsequent route matches the request. (Blake Mizerany) + +The following Sinatra features have been obsoleted (removed entirely) in +the 1.0 release: + + * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test` + module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`, + `put_it`, `delete_it`, and `head_it` helper methods. The + [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should + be used instead. + + * Test framework specific libraries (`sinatra/test/spec`, + `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See + http://www.sinatrarb.com/testing.html for instructions on setting up a + testing environment under each of these frameworks. + + * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead. + `Sinatra::Base` acts more like `Sinatra::Default` in development mode. + For example, static file serving and sexy development error pages are + enabled by default. + + * Auto-requiring template libraries in the `erb`, `builder`, `haml`, + and `sass` methods is obsolete due to thread-safety issues. You must + require the template libraries explicitly in your app. + + * The `:views_directory` option to rendering methods is obsolete; use + `:views` instead. + + * The `:haml` and `:sass` options to rendering methods are obsolete. + Template engine options should be passed in the second Hash argument + instead. + + * The `use_in_file_templates` method is obsolete. Use + `enable :inline_templates` or `set :inline_templates, 'path/to/file'` + + * The 'media_type' helper method is obsolete. Use 'mime_type' instead. + + * The 'mime' main and class method is obsolete. Use 'mime_type' instead. + + * The request-level `send_data` method is no longer supported. + + * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer + supported. This may effect extensions written for versions prior to 0.9.2. + See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html) + for the officially supported extensions API. + + * The `set_option` and `set_options` methods are obsolete; use `set` + instead. + + * The `:env` setting (`settings.env`) is obsolete; use `:environment` + instead. + + * The request level `stop` method is obsolete; use `halt` instead. + + * The request level `entity_tag` method is obsolete; use `etag` + instead. + + * The request level `headers` method (HTTP response headers) is obsolete; + use `response['Header-Name']` instead. + + * `Sinatra.application` is obsolete; use `Sinatra::Application` instead. + + * Using `Sinatra.application = nil` to reset an application is obsolete. + This should no longer be necessary. + + * Using `Sinatra.default_options` to set base configuration items is + obsolete; use `Sinatra::Base.set(key, value)` instead. + + * The `Sinatra::ServerError` exception is obsolete. All exceptions raised + within a request are now treated as internal server errors and result in + a 500 response status. + + * The `:methodoverride' option to enable/disable the POST _method hack is + obsolete; use `:method_override` instead. + +## 0.9.2 / 2009-05-18 + + * This version is compatible with Rack 1.0. [Rein Henrichs] + + * The development-mode unhandled exception / error page has been + greatly enhanced, functionally and aesthetically. The error + page is used when the :show_exceptions option is enabled and an + exception propagates outside of a route handler or before filter. + [Simon Rozet / Matte Noble / Ryan Tomayko] + + * Backtraces that move through templates now include filenames and + line numbers where possible. [#51 / S. Brent Faulkner] + + * All templates now have an app-level option for setting default + template options (:haml, :sass, :erb, :builder). The app-level + option value must be a Hash if set and is merged with the + template options specified to the render method (Base#haml, + Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko] + + * The method signature for all template rendering methods has + been unified: "def engine(template, options={}, locals={})". + The options Hash now takes the generic :views, :layout, and + :locals options but also any template-specific options. The + generic options are removed before calling the template specific + render method. Locals may be specified using either the + :locals key in the options hash or a second Hash option to the + rendering method. [#191 / Ryan Tomayko] + + * The receiver is now passed to "configure" blocks. This + allows for the following idiom in top-level apps: + configure { |app| set :foo, app.root + '/foo' } + [TJ Holowaychuck / Ryan Tomayko] + + * The "sinatra/test" lib is deprecated and will be removed in + Sinatra 1.0. This includes the Sinatra::Test module and + Sinatra::TestHarness class in addition to all the framework + test helpers that were deprecated in 0.9.1. The Rack::Test + lib should be used instead: http://gitrdoc.com/brynary/rack-test + [#176 / Simon Rozet] + + * Development mode source file reloading has been removed. The + "shotgun" (http://rtomayko.github.com/shotgun/) program can be + used to achieve the same basic functionality in most situations. + Passenger users should use the "tmp/always_restart.txt" + file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko] + + * Auto-requiring template libs in the erb, builder, haml, and + sass methods is deprecated due to thread-safety issues. You must + require the template libs explicitly in your app file. [Simon Rozet] + + * A new Sinatra::Base#route_missing method was added. route_missing + is sent when no route matches the request or all route handlers + pass. The default implementation forwards the request to the + downstream app when running as middleware (i.e., "@app" is + non-nil), or raises a NotFound exception when no downstream app + is defined. Subclasses can override this method to perform custom + route miss logic. [Jon Crosby] + + * A new Sinatra::Base#route_eval method was added. The method + yields to the block and throws :halt with the result. Subclasses + can override this method to tap into the route execution logic. + [TJ Holowaychuck] + + * Fix the "-x" (enable request mutex / locking) command line + argument. Passing -x now properly sets the :lock option. + [S. Brent Faulkner, Ryan Tomayko] + + * Fix writer ("foo=") and predicate ("foo?") methods in extension + modules not being added to the registering class. + [#172 / Pat Nakajima] + + * Fix in-file templates when running alongside activesupport and + fatal errors when requiring activesupport before sinatra + [#178 / Brian Candler] + + * Fix various issues running on Google AppEngine. + [Samuel Goebert, Simon Rozet] + + * Fix in-file templates __END__ detection when __END__ exists with + other stuff on a line [Yoji Shidara] + +## 0.9.1.1 / 2009-03-09 + + * Fix directory traversal vulnerability in default static files + route. See [#177] for more info. + +## 0.9.1 / 2009-03-01 + + * Sinatra now runs under Ruby 1.9.1 [#61] + + * Route patterns (splats, :named, or Regexp captures) are now + passed as arguments to the block. [#140] + + * The "helpers" method now takes a variable number of modules + along with the normal block syntax. [#133] + + * New request-level #forward method for middleware components: passes + the env to the downstream app and merges the response status, headers, + and body into the current context. [#126] + + * Requests are now automatically forwarded to the downstream app when + running as middleware and no matching route is found or all routes + pass. + + * New simple API for extensions/plugins to add DSL-level and + request-level methods. Use Sinatra.register(mixin) to extend + the DSL with all public methods defined in the mixin module; + use Sinatra.helpers(mixin) to make all public methods defined + in the mixin module available at the request level. [#138] + See http://www.sinatrarb.com/extensions.html for details. + + * Named parameters in routes now capture the "." character. This makes + routes like "/:path/:filename" match against requests like + "/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt". + Previously, the route would not match at all. + + * Added request-level "redirect back" to redirect to the referring + URL. + + * Added a new "clean_trace" option that causes backtraces dumped + to rack.errors and displayed on the development error page to + omit framework and core library backtrace lines. The option is + enabled by default. [#77] + + * The ERB output buffer is now available to helpers via the @_out_buf + instance variable. + + * It's now much easier to test sessions in unit tests by passing a + ":session" option to any of the mock request methods. e.g., + get '/', {}, :session => { 'foo' => 'bar' } + + * The testing framework specific files ('sinatra/test/spec', + 'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated. + See http://sinatrarb.com/testing.html for instructions on setting up + a testing environment with these frameworks. + + * The request-level #send_data method from Sinatra 0.3.3 has been added + for compatibility but is deprecated. + + * Fix :provides causing crash on any request when request has no + Accept header [#139] + + * Fix that ERB templates were evaluated twice per "erb" call. + + * Fix app-level middleware not being run when the Sinatra application is + run as middleware. + + * Fixed some issues with running under Rack's CGI handler caused by + writing informational stuff to stdout. + + * Fixed that reloading was sometimes enabled when starting from a + rackup file [#110] + + * Fixed that "." in route patterns erroneously matched any character + instead of a literal ".". [#124] + +## 0.9.0.4 / 2009-01-25 + + * Using halt with more than 1 args causes ArgumentError [#131] + * using halt in a before filter doesn't modify response [#127] + * Add deprecated Sinatra::EventContext to unbreak plugins [#130] + * Give access to GET/POST params in filters [#129] + * Preserve non-nested params in nested params hash [#117] + * Fix backtrace dump with Rack::Lint [#116] + +## 0.9.0.3 / 2009-01-21 + + * Fall back on mongrel then webrick when thin not found. [#75] + * Use :environment instead of :env in test helpers to + fix deprecation warnings coming from framework. + * Make sinatra/test/rspec work again [#113] + * Fix app_file detection on windows [#118] + * Fix static files with Rack::Lint in pipeline [#121] + +## 0.9.0.2 / 2009-01-18 + + * Halting a before block should stop processing of routes [#85] + * Fix redirect/halt in before filters [#85] + +## 0.9.0 / 2009-01-18 + + * Works with and requires Rack >= 0.9.1 + + * Multiple Sinatra applications can now co-exist peacefully within a + single process. The new "Sinatra::Base" class can be subclassed to + establish a blank-slate Rack application or middleware component. + Documentation on using these features is forth-coming; the following + provides the basic gist: http://gist.github.com/38605 + + * Parameters with subscripts are now parsed into a nested/recursive + Hash structure. e.g., "post[title]=Hello&post[body]=World" yields + params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. + + * Regular expressions may now be used in route pattens; captures are + available at "params[:captures]". + + * New ":provides" route condition takes an array of mime types and + matches only when an Accept request header is present with a + corresponding type. [cypher] + + * New request-level "pass" method; immediately exits the current block + and passes control to the next matching route. + + * The request-level "body" method now takes a block; evaluation is + deferred until an attempt is made to read the body. The block must + return a String or Array. + + * New "route conditions" system for attaching rules for when a route + matches. The :agent and :host route options now use this system. + + * New "dump_errors" option controls whether the backtrace is dumped to + rack.errors when an exception is raised from a route. The option is + enabled by default for top-level apps. + + * Better default "app_file", "root", "public", and "views" location + detection; changes to "root" and "app_file" automatically cascade to + other options that depend on them. + + * Error mappings are now split into two distinct layers: exception + mappings and custom error pages. Exception mappings are registered + with "error(Exception)" and are run only when the app raises an + exception. Custom error pages are registered with "error(status_code)", + where "status_code" is an integer, and are run any time the response + has the status code specified. It's also possible to register an error + page for a range of status codes: "error(500..599)". + + * In-file templates are now automatically imported from the file that + requires 'sinatra'. The use_in_file_templates! method is still available + for loading templates from other files. + + * Sinatra's testing support is no longer dependent on Test::Unit. Requiring + 'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness + class, which can be used with any test framework. The 'sinatra/test/unit', + 'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files + can be required to setup a framework-specific testing environment. See the + README for more information. + + * Added support for Bacon (test framework). The 'sinatra/test/bacon' file + can be required to setup Sinatra test helpers on Bacon::Context. + + * Deprecated "set_option" and "set_options"; use "set" instead. + + * Deprecated the "env" option ("options.env"); use "environment" instead. + + * Deprecated the request level "stop" method; use "halt" instead. + + * Deprecated the request level "entity_tag" method; use "etag" instead. + Both "entity_tag" and "etag" were previously supported. + + * Deprecated the request level "headers" method (HTTP response headers); + use "response['Header-Name']" instead. + + * Deprecated "Sinatra.application"; use "Sinatra::Application" instead. + + * Deprecated setting Sinatra.application = nil to reset an application. + This should no longer be necessary. + + * Deprecated "Sinatra.default_options"; use + "Sinatra::Default.set(key, value)" instead. + + * Deprecated the "ServerError" exception. All Exceptions are now + treated as internal server errors and result in a 500 response + status. + + * Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it" + test helper methods. Use "get", "post", "put", "delete", and "head", + respectively, instead. + + * Removed Event and EventContext classes. Applications are defined in a + subclass of Sinatra::Base; each request is processed within an + instance. + +## 0.3.3 / 2009-01-06 + + * Pin to Rack 0.4.0 (this is the last release on Rack 0.4) + + * Log unhandled exception backtraces to rack.errors. + + * Use RACK_ENV environment variable to establish Sinatra + environment when given. Thin sets this when started with + the -e argument. + + * BUG: raising Sinatra::NotFound resulted in a 500 response + code instead of 404. + + * BUG: use_in_file_templates! fails with CR/LF [#45] + + * BUG: Sinatra detects the app file and root path when run under + thin/passenger. + +## 0.3.2 + + * BUG: Static and send_file read entire file into String before + sending. Updated to stream with 8K chunks instead. + + * Rake tasks and assets for building basic documentation website. + See http://sinatra.rubyforge.org + + * Various minor doc fixes. + +## 0.3.1 + + * Unbreak optional path parameters [jeremyevans] + +## 0.3.0 + + * Add sinatra.gemspec w/ support for github gem builds. Forks can now + enable the build gem option in github to get free username-sinatra.gem + builds: gem install username-sinatra.gem --source=http://gems.github.com/ + + * Require rack-0.4 gem; removes frozen rack dir. + + * Basic RSpec support; require 'sinatra/test/rspec' instead of + 'sinatra/test/spec' to use. [avdi] + + * before filters can modify request environment vars used for + routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting + type functionality. + + * In-file templates now uses @@ instead of ## as template separator. + + * Top-level environment test predicates: development?, test?, production? + + * Top-level "set", "enable", and "disable" methods for tweaking + app options. [rtomayko] + + * Top-level "use" method for building Rack middleware pipelines + leading to app. See README for usage. [rtomayko] + + * New "reload" option - set false to disable reloading in development. + + * New "host" option - host/ip to bind to [cschneid] + + * New "app_file" option - override the file to reload in development + mode [cschneid] + + * Development error/not_found page cleanup [sr, adamwiggins] + + * Remove a bunch of core extensions (String#to_param, String#from_param, + Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass) + + * Various grammar and formatting fixes to README; additions on + community and contributing [cypher] + + * Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api + + * Specs, documentation and fixes for splat'n routes [vic] + + * Fix whitespace errors across all source files. [rtomayko] + + * Fix streaming issues with Mongrel (body not closed). [bmizerany] + + * Fix various issues with environment not being set properly (configure + blocks not running, error pages not registering, etc.) [cypher] + + * Fix to allow locals to be passed to ERB templates [cschneid] + + * Fix locking issues causing random errors during reload in development. + + * Fix for escaped paths not resolving static files [Matthew Walker] + +## 0.2.1 + + * File upload fix and minor tweaks. + +## 0.2.0 + + * Initial gem release of 0.2 codebase. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CONTRIBUTING.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CONTRIBUTING.md new file mode 100644 index 0000000000..9f4cbc58c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contribute + +Want to show Sinatra some love? Help out by contributing! + +## Found a bug? + +Log it in our [issue tracker][ghi] or send a note to the [mailing list][ml]. +Be sure to include all relevant information, like the versions of Sinatra and +Ruby you're using. A [gist](http://gist.github.com/) of the code that caused +the issue as well as any error messages are also very helpful. + +## Need help? + +The [Sinatra mailing list][ml] has over 900 subscribers, many of which are happy +to help out newbies or talk about potential feature additions. You can also +drop by the [#sinatra](irc://chat.freenode.net/#sinatra) channel on +[irc.freenode.net](http://freenode.net). + +## Have a patch? + +Bugs and feature requests that include patches are much more likely to +get attention. Here are some guidelines that will help ensure your patch +can be applied as quickly as possible: + +1. **Use [Git](http://git-scm.com) and [GitHub](http://github.com):** + The easiest way to get setup is to fork the + [sinatra/sinatra repo](http://github.com/sinatra/sinatra/). + Or, the [sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/), + if the patch is doc related. + +2. **Write unit tests:** If you add or modify functionality, it must + include unit tests. If you don't write tests, we have to, and this + can hold up acceptance of the patch. + +3. **Mind the `README`:** If the patch adds or modifies a major feature, + modify the `README.md` file to reflect that. Again, if you don't + update the `README`, we have to, and this holds up acceptance. + +4. **Push it:** Once you're ready, push your changes to a topic branch + and add a note to the ticket with the URL to your branch. Or, say + something like, "you can find the patch on johndoe/foobranch". We also + gladly accept GitHub [pull requests](http://help.github.com/pull-requests/). + +__NOTE:__ _We will take whatever we can get._ If you prefer to attach diffs in +emails to the mailing list, that's fine; but do know that _someone_ will need +to take the diff through the process described above and this can hold things +up considerably. + +## Want to write docs? + +The process for contributing to Sinatra's website, documentation or the book +is the same as contributing code. We use Git for versions control and GitHub to +track patch requests. + +* [The sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/) + is where the website sources are managed. There are almost always people in + `#sinatra` that are happy to discuss, apply, and publish website patches. + +* [The Book](http://sinatra-org-book.herokuapp.com/) has its own [Git + repository](http://github.com/sinatra/sinatra-book/) and build process but is + managed the same as the website and project codebase. + +* [Sinatra Recipes](http://recipes.sinatrarb.com/) is a community + project where anyone is free to contribute ideas, recipes and tutorials. Which + also has its own [Git repository](http://github.com/sinatra/sinatra-recipes). + +* [The Introduction](http://www.sinatrarb.com/intro.html) is generated from + Sinatra's [README file](http://github.com/sinatra/sinatra/blob/master/README.md). + +* If you want to help translating the documentation, the README is already + available in + [Japanese](http://github.com/sinatra/sinatra/blob/master/README.ja.md), + [German](http://github.com/sinatra/sinatra/blob/master/README.de.md), + [Chinese](https://github.com/sinatra/sinatra/blob/master/README.zh.md), + [Russian](https://github.com/sinatra/sinatra/blob/master/README.ru.md), + [European](https://github.com/sinatra/sinatra/blob/master/README.pt-pt.md) and + [Brazilian](https://github.com/sinatra/sinatra/blob/master/README.pt-br.md) + Portuguese, + [French](https://github.com/sinatra/sinatra/blob/master/README.fr.md), + [Spanish](https://github.com/sinatra/sinatra/blob/master/README.es.md), + [Korean](https://github.com/sinatra/sinatra/blob/master/README.ko.md), and + [Hungarian](https://github.com/sinatra/sinatra/blob/master/README.hu.md). + The translations tend to fall behind the English version. Translations into + other languages would also be appreciated. + +## Looking for something to do? + +If you'd like to help out but aren't sure how, pick something that looks +interesting from the [issues][ghi] list and hack on. Make sure to leave a +comment on the ticket noting that you're investigating (a simple "Taking…" is +fine). + +[ghi]: http://github.com/sinatra/sinatra/issues +[ml]: http://groups.google.com/group/sinatrarb/topics "Sinatra Mailing List" + +* ["Help Wanted"](https://github.com/sinatra/sinatra/labels/help%20wanted): Anyone willing to pitch in is open to contribute to this ticket as they see fit (will try to add context / summarize or ask for requirements) + +* ["Good First Issue"](https://github.com/sinatra/sinatra/labels/good%20first%20issue): Potential first time contributors should start here + +* ["Wishlist"](https://github.com/sinatra/sinatra/labels/Wishlist): All the things I wish we had but have no time for diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Gemfile b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Gemfile new file mode 100644 index 0000000000..e5ecd9b8c5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Gemfile @@ -0,0 +1,80 @@ +# Why use bundler? +# Well, not all development dependencies install on all rubies. Moreover, `gem +# install sinatra --development` doesn't work, as it will also try to install +# development dependencies of our dependencies, and those are not conflict free. +# So, here we are, `bundle install`. +# +# If you have issues with a gem: `bundle install --without-coffee-script`. + +RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE +source 'https://rubygems.org' unless ENV['QUICK'] +gemspec + +gem 'rake' +gem 'rack', git: 'https://github.com/rack/rack.git' +gem 'rack-test', '>= 0.6.2' +gem "minitest", "~> 5.0" +gem 'yard' + +gem "rack-protection", path: "rack-protection" +gem "sinatra-contrib", path: "sinatra-contrib" + +gem "twitter-text", "1.14.7" + +if RUBY_ENGINE == 'jruby' + gem 'nokogiri', '!= 1.5.0' + gem 'trinidad' +end + +if RUBY_ENGINE == 'jruby' || RUBY_ENGINE == 'ruby' + gem "activesupport", "~> 5.1.6" +end + +if RUBY_ENGINE == "ruby" + gem 'less', '~> 2.0' + gem 'therubyracer' + gem 'redcarpet' + gem 'wlang', '>= 2.0.1' + gem 'bluecloth' + gem 'rdiscount' + gem 'RedCloth' + gem 'puma' + gem 'yajl-ruby' + gem 'nokogiri' + gem 'rainbows' + gem 'eventmachine' + gem 'slim', '~> 2.0' + gem 'coffee-script', '>= 2.0' + gem 'rdoc' + gem 'kramdown' + gem 'maruku' + gem 'creole' + gem 'wikicloth' + gem 'markaby' + gem 'radius' + gem 'asciidoctor' + gem 'liquid' + gem 'stylus' + gem 'rabl' + gem 'builder' + gem 'erubi' + gem 'erubis' + gem 'haml', '>= 3.0' + gem 'sass' + gem 'reel-rack' + gem 'celluloid', '~> 0.16.0' + gem 'commonmarker', '~> 0.20.0' + gem 'pandoc-ruby', '~> 2.0.2' + gem 'simplecov', require: false +end + +if RUBY_ENGINE == "rbx" + gem 'json' + gem 'rubysl' + gem 'rubysl-test-unit' + gem 'erubi' +end + +platforms :jruby do + gem 'json' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/LICENSE new file mode 100644 index 0000000000..c53f830ff5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2007, 2008, 2009 Blake Mizerany +Copyright (c) 2010-2017 Konstantin Haase +Copyright (c) 2015-2017 Zachary Scott + +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.0.0/gems/sinatra-2.1.0/MAINTENANCE.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/MAINTENANCE.md new file mode 100644 index 0000000000..0820670e3f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/MAINTENANCE.md @@ -0,0 +1,42 @@ +# Sinatra maintenance + +## Versions + +### Unstable release + +The next major version of Sinatra will be released from the master branch. + +* Current proposed major release: 2.0.0 + +### Stable release + +The current stable version of Sinatra is 1.4 series, and released from the stable branch. + +## Issues + +### New features + +New features will only be added to the master branch and will not be made available in point releases. + +### Bug fixes + +Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. + +* Current release series: 1.4.x + +### Security issues + +The current release series will receive patches and new versions in case of a security issue. + +* Current release series: 1.4.x + +### Severe security issues + +For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. + +* Current release series: 1.4.x +* Next most recent release series: 1.3.x + +### Unsupported Release Series + +When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. We may provide back-ports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.de.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.de.md new file mode 100644 index 0000000000..6a9dfd05e9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.de.md @@ -0,0 +1,3239 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](http://travis-ci.org/sinatra/sinatra) + +*Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter +Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 2.0 +Vorabausgabe).* + +Sinatra ist eine +[DSL](https://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das +schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand +ermöglicht: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hallo Welt!' +end +``` + +Sinatra-Gem installieren: + +```shell +gem install sinatra +``` + +und im gleichen Verzeichnis ausführen: + +```shell +ruby myapp.rb +``` + +Die Seite kann nun unter [http://localhost:4567](http://localhost:4567) +aufgerufen werden. + +Es wird empfohlen `gem installl thin` auszuführen, Sinatra wird dann +diesen Server verwenden. + +## Inhalt + +- [Sinatra](#sinatra) + * [Inhalt](#inhalt) + * [Routen](#routen) + + [Bedingungen](#bedingungen) + + [Rückgabewerte](#rückgabewerte) + + [Eigene Routen-Muster](#eigene-routen-muster) + * [Statische Dateien](#statische-dateien) + * [Views/Templates](#views-templates) + + [Direkte Templates](#direkte-templates) + + [Verfügbare Templatesprachen](#verfügbare-templatesprachen) + - [Haml Templates](#haml-templates) + - [Erb Templates](#erb-templates) + - [Builder Templates](#builder-templates) + - [Nokogiri Templates](#nokogiri-templates) + - [Sass Templates](#sass-templates) + - [SCSS Templates](#scss-templates) + - [Less Templates](#less-templates) + - [Liquid Templates](#liquid-templates) + - [Markdown Templates](#markdown-templates) + - [Textile Templates](#textile-templates) + - [RDoc Templates](#rdoc-templates) + - [AsciiDoc Templates](#asciidoc-templates) + - [Radius Templates](#radius-templates) + - [Markaby Templates](#markaby-templates) + - [RABL Templates](#rabl-templates) + - [Slim Templates](#slim-templates) + - [Creole Templates](#creole-templates) + - [MediaWiki Templates](#mediawiki-templates) + - [CoffeeScript Templates](#coffeescript-templates) + - [Stylus Templates](#stylus-templates) + - [Yajl Templates](#yajl-templates) + - [WLang Templates](#wlang-templates) + + [Auf Variablen in Templates zugreifen](#auf-variablen-in-templates-zugreifen) + + [Templates mit `yield` und verschachtelte Layouts](#templates-mit--yield--und-verschachtelte-layouts) + + [Inline-Templates](#inline-templates) + + [Benannte Templates](#benannte-templates) + + [Dateiendungen zuordnen](#dateiendungen-zuordnen) + + [Eine eigene Template-Engine hinzufügen](#eine-eigene-template-engine-hinzuf-gen) + + [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden) + * [Filter](#filter) + * [Helfer](#helfer) + + [Sessions verwenden](#sessions-verwenden) + - [Sitzungseinstellungen](#sitzungseinstellungen) + - [Eigene Sitzungs-Middleware auswählen](#eigene-sitzungs-middleware-ausw-hlen) + + [Anhalten](#anhalten) + + [Weiterspringen](#weiterspringen) + + [Eine andere Route ansteuern](#eine-andere-route-ansteuern) + + [Body, Status-Code und Header setzen](#body--status-code-und-header-setzen) + + [Response-Streams](#response-streams) + + [Logger](#logger) + + [Mime-Types](#mime-types) + + [URLs generieren](#urls-generieren) + + [Browser-Umleitung](#browser-umleitung) + + [Cache einsetzen](#cache-einsetzen) + + [Dateien versenden](#dateien-versenden) + + [Das Request-Objekt](#das-request-objekt) + + [Anhänge](#anhänge) + + [Umgang mit Datum und Zeit](#umgang-mit-datum-und-zeit) + + [Nachschlagen von Template-Dateien](#nachschlagen-von-template-dateien) + + [Konfiguration](#konfiguration) + - [Einstellung des Angriffsschutzes](#einstellung-des-angriffsschutzes) + - [Mögliche Einstellungen](#m-gliche-einstellungen) + * [Umgebungen](#umgebungen) + * [Fehlerbehandlung](#fehlerbehandlung) + + [Nicht gefunden](#nicht-gefunden) + + [Fehler](#fehler) + * [Rack-Middleware](#rack-middleware) + * [Testen](#testen) + * [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatra--base---middleware--bibliotheken-und-modulare-anwendungen) + + [Modularer vs. klassischer Stil](#modularer-vs-klassischer-stil) + + [Eine modulare Applikation bereitstellen](#eine-modulare-applikation-bereitstellen) + + [Eine klassische Anwendung mit einer config.ru verwenden](#eine-klassische-anwendung-mit-einer-configru-verwenden) + + [Wann sollte eine config.ru-Datei verwendet werden?](#wann-sollte-eine-configru-datei-verwendet-werden-) + + [Sinatra als Middleware nutzen](#sinatra-als-middleware-nutzen) + + [Dynamische Applikationserstellung](#dynamische-applikationserstellung) + * [Geltungsbereich und Bindung](#geltungsbereich-und-bindung) + + [Anwendungs- oder Klassen-Scope](#anwendungs--oder-klassen-scope) + + [Anfrage- oder Instanz-Scope](#anfrage--oder-instanz-scope) + + [Delegation-Scope](#delegation-scope) + * [Kommandozeile](#kommandozeile) + + [Multi-threading](#multi-threading) + * [Systemanforderungen](#systemanforderungen) + * [Der neuste Stand (The Bleeding Edge)](#der-neuste-stand--the-bleeding-edge-) + + [Mit Bundler](#mit-bundler) + + [Eigenes Repository](#eigenes-repository) + + [Gem erstellen](#gem-erstellen) + * [Versions-Verfahren](#versions-verfahren) + * [Mehr](#mehr) + +## Routen + +In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster +definiert. Jeder dieser Routen wird ein Ruby-Block zugeordnet: + +```ruby +get '/' do + .. zeige etwas .. +end + +post '/' do + .. erstelle etwas .. +end + +put '/' do + .. update etwas .. +end + +delete '/' do + .. entferne etwas .. +end + +options '/' do + .. zeige, was wir können .. +end + +link '/' do + .. verbinde etwas .. +end + +unlink '/' do + .. trenne etwas .. +end +``` + +Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. +Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt. + +Routen mit angehängtem Schrägstrich unterscheiden sich von Routen ohne: + +```ruby +get '/foo' do + # wird nicht bei "GET /foo/" aufgerufen +end +``` + +Die Muster der Routen können benannte Parameter beinhalten, die über den +`params`-Hash zugänglich gemacht werden: + +```ruby +get '/hallo/:name' do + # passt auf "GET /hallo/foo" und "GET /hallo/bar" + # params['name'] ist dann 'foo' oder 'bar' + "Hallo #{params['name']}!" +end +``` + +Man kann auf diese auch mit Block-Parametern zugreifen: + +```ruby +get '/hallo/:name' do |n| + # n entspricht hier params['name'] + "Hallo #{n}!" +end +``` + +Routen-Muster können auch mit sog. Splat- oder Wildcard-Parametern über das +`params['splat']`-Array angesprochen werden: + +```ruby +get '/sag/*/zu/*' do + # passt z.B. auf /sag/hallo/zu/welt + params['splat'] # => ["hallo", "welt"] +end + +get '/download/*.*' do + # passt auf /download/pfad/zu/datei.xml + params['splat'] # => ["pfad/zu/datei", "xml"] +end +``` + +Oder mit Block-Parametern: + +```ruby +get '/download/*.*' do |pfad, endung| + [pfad, endung] # => ["Pfad/zu/Datei", "xml"] +end +``` + +Routen mit regulären Ausdrücken sind auch möglich: + +```ruby +get /\/hallo\/([\w]+)/ do + "Hallo, #{params['captures'].first}!" +end +``` + +Und auch hier können Block-Parameter genutzt werden: + +```ruby +get %r{/hallo/([\w]+)} do |c| + # erkennt "GET /hallo/frank" oder "GET /sag/hallo/frank" usw. + "Hallo, #{c}!" +end +``` + +Routen-Muster können auch mit optionalen Parametern ausgestattet werden: + +```ruby +get '/posts/:format?' do + # passt auf "GET /posts/" sowie jegliche Erweiterung + # wie "GET /posts/json", "GET /posts/xml" etc. +end +``` + +Routen können auch den query-Parameter verwenden: + +```ruby +get '/posts' do + # passt zu "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # verwendet title- und author-Variablen. Der query-Parameter ist für + # die /post-Route optional +end +``` + +Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert +(siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem +Abgleich mit den Routen modifiziert wird. + +Die Mustermann-Optionen können für eine gegebene Route angepasst werden, +indem man der Route den `:mustermann_opts`-Hash mitgibt: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # Passt genau auf /posts mit explizitem Anchoring + "Wenn Dein Anchor-Muster passt, darfst Du klatschen!" +end +``` + +Das sieht zwar genauso aus wie eine Bedingung, ist es aber nicht. Diese +Option wird mit dem globalen `:mustermann_opts`-Hash zusammengeführt +(siehe weiter unten). + +### Bedingungen + +An Routen können eine Vielzahl von Bedingungen geknüpft werden, die erfüllt +sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine +Einschränkung des User-Agents über die interne Bedingung `:agent`: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Du verwendest Songbird Version #{params['agent'][0]}" +end +``` + +Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: + +```ruby +get '/foo' do + # passt auf andere Browser +end +``` + +Andere verfügbare Bedingungen sind `:host_name` und `:provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Adminbereich, Zugriff verweigert!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` durchsucht den Accept-Header der Anfrage + +Eigene Bedingungen können relativ einfach hinzugefügt werden: + +```ruby +set(:wahrscheinlichkeit) { |value| condition { rand <= value } } + +get '/auto_gewinnen', :wahrscheinlichkeit => 0.1 do + "Du hast gewonnen!" +end + +get '/auto_gewinnen' do + "Tut mir leid, verloren." +end +``` + +Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet +werden: + +```ruby +set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mein/account/", :auth => [:user, :admin] do + "Mein Account" +end + +get "/nur/admin/", :auth => :admin do + "Nur Admins dürfen hier rein!" +end +``` + +### Rückgabewerte + +Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body +festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware, +weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den +vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings +auch andere Werte akzeptiert. + +Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um +einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt: + +* Ein Array mit drei Elementen: `[Status (Integer), Headers (Hash), + Response-Body (antwortet auf #each)]`. +* Ein Array mit zwei Elementen: `[Status (Integer), Response-Body (antwortet + auf #each)]`. +* Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen + Block nur mit Strings als Übergabewerte aufruft. +* Ein Integer, das den Status-Code festlegt. + +Damit lässt sich relativ einfach Streaming implementieren: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming +direkt in die Route integriert. + +### Eigene Routen-Muster + +Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für +String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. +Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene +Routen-Muster erstellt werden: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch +einfacher: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Oder unter Verwendung eines negativen look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Statische Dateien + +Statische Dateien werden im `./public`-Ordner erwartet. Es ist möglich, +einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Zu beachten ist, dass der Ordnername `public` nicht Teil der URL ist. +Die Datei `./public/css/style.css` ist unter +`http://example.com/css/style.css` zu finden. + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +## Views/Templates + +Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils +einen String zurückgibt: + +```ruby +get '/' do + erb :index +end +``` + +Dieses Beispiel rendert `views/index.erb`. + +Anstelle eines Templatenamens kann man auch direkt die Templatesprache +verwenden: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates nehmen ein zweite Argument an, den Options-Hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` +(Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) + +Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Für alle Templates können auch Einstellungen, die für alle Routen gelten, +festgelegt werden: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Optionen, die an die Rendermethode weitergegeben werden, überschreiben die +Einstellungen, die mit `set` festgelegt wurden. + +Einstellungen: + +
    +
    locals
    +
    Liste von lokalen Variablen, die an das Dokument weitergegeben werden. + Praktisch für Partials: + + erb "<%= foo %>", :locals => {:foo => "bar"}
    + +
    default_encoding
    +
    Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt + auf settings.default_encoding.
    + +
    views
    +
    Ordner, aus dem die Templates geladen werden. Voreingestellt auf + settings.views.
    + +
    layout
    +
    Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht + (true oderfalse). Ist es ein Symbol, dann legt es fest, + welches Template als Layout verwendet wird: + + erb :index, :layout => !request.xhr?
    + +
    content_type
    +
    Content-Typ den das Template ausgibt. Voreinstellung hängt von der + Templatesprache ab.
    + +
    scope
    +
    Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb + der App-Instanz. Wird Scope geändert, sind Instanzvariablen und + Helfermethoden nicht verfügbar.
    + +
    layout_engine
    +
    Legt fest, welcher Renderer für das Layout verantwortlich ist. Hilfreich + für Sprachen, die sonst keine Templates unterstützen. Voreingestellt auf + den Renderer, der für das Template verwendet wird: + + set :rdoc, :layout_engine => :erb
    + +
    layout_options
    +
    Besondere Einstellungen, die nur für das Rendering verwendet werden: + + set :rdoc, :layout_options => { :views => 'views/layouts' }
    +
    + +Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis +befinden. Es kann jedoch ein anderer Ordner festgelegt werden: + +```ruby +set :views, settings.root + '/templates' +``` + +Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen +werden muss, auch dann, wenn sie sich in einem Unterordner befinden: + +```ruby +haml :'unterverzeichnis/template' +``` + +Wird einer Rendering-Methode ein String übergeben, wird dieser direkt +gerendert. + +### Direkte Templates + +```ruby +get '/' do + haml '%div.title Hallo Welt' +end +``` + +Hier wird der String direkt gerendert. + +Optional kann `:path` und `:line` für einen genaueren Backtrace +übergeben werden, wenn mit dem vorgegebenen String ein Pfad im +Dateisystem oder eine Zeilennummer verbunden ist: + +```ruby +get '/' do + haml '%div.title Hallo Welt', :path => 'examples/datei.haml', :line => 3 +end +``` + +### Verfügbare Templatesprachen + +Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche +verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu +Beginn ein `'require'`: + +```ruby +require 'rdiscount' # oder require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Abhängigkeithaml
    Dateierweiterung.haml
    Beispielhaml :index, :format => :html5
    + + +#### Erb Templates + + + + + + + + + + + + + + +
    Abhängigkeiterubis oder erb + (Standardbibliothek von Ruby)
    Dateierweiterungen.erb, .rhtml oder .erubis (nur Erubis)
    Beispielerb :index
    + + +#### Builder Templates + + + + + + + + + + + + + + +
    Abhängigkeitbuilder
    Dateierweiterung.builder
    Beispielbuilder { |xml| xml.em "Hallo" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Abhängigkeitnokogiri
    Dateierweiterung.nokogiri
    Beispielnokogiri { |xml| xml.em "Hallo" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Sass Templates + + + + + + + + + + + + + + +
    Abhängigkeitsass
    Dateierweiterung.sass
    Beispielsass :stylesheet, :style => :expanded
    + + +#### SCSS Templates + + + + + + + + + + + + + + +
    Abhängigkeitsass
    Dateierweiterung.scss
    Beispielscss :stylesheet, :style => :expanded
    + + +#### Less Templates + + + + + + + + + + + + + + +
    Abhängigkeitless
    Dateierweiterung.less
    Beispielless :stylesheet
    + + +#### Liquid Templates + + + + + + + + + + + + + + +
    Abhängigkeitliquid
    Dateierweiterung.liquid
    Beispielliquid :index, :locals => { :key => 'Wert' }
    + +Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Markdown Templates + + + + + + + + + + + + + + +
    AbhängigkeitEine der folgenden Bibliotheken: + RDiscount, + RedCarpet, + BlueCloth, + kramdown oder + maruku +
    Dateierweiterungen.markdown, .mkd und .md
    Beispielmarkdown :index, :layout_engine => :erb
    + +Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Markdown üblicherweise in Kombination +mit anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => markdown(:einfuehrung) } +``` + +Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= markdown(:Grüße) +``` + +Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht +in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### Textile Templates + + + + + + + + + + + + + + +
    AbhängigkeitRedCloth
    Dateierweiterung.textile
    Beispieltextile :index, :layout_engine => :erb
    + +Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => textile(:einfuehrung) } +``` + +Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= textile(:Grüße) +``` + +Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht +in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### RDoc Templates + + + + + + + + + + + + + + +
    Abhängigkeitrdoc
    Dateierweiterung.rdoc
    Beispieltextile :README, :layout_engine => :erb
    + +Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => rdoc(:einfuehrung) } +``` + +Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= rdoc(:Grüße) +``` + +Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in +RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    AbhängigkeitAsciidoctor
    Dateierweiterungen.asciidoc, .adoc und .ad
    Beispielasciidoc :README, :layout_engine => :erb
    + +Da man aus dem AsciiDoc-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Radius Templates + + + + + + + + + + + + + + +
    Abhängigkeitradius
    Dateierweiterung.radius
    Beispielradius :index, :locals => { :key => 'Wert' }
    + +Da man aus dem Radius-Template heraus keine Ruby-Methoden aufrufen kann, wird +man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt. + +#### Markaby Templates + + + + + + + + + + + + + + +
    Abhängigkeitmarkaby
    Dateierweiterung.mab
    Beispielmarkaby { h1 "Willkommen!" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### RABL Templates + + + + + + + + + + + + + + +
    Abhängigkeitrabl
    Dateierweiterung.rabl
    Beispielrabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    Abhängigkeitslim
    Dateierweiterung.slim
    Beispielslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    Abhängigkeitcreole
    Dateierweiterung.creole
    Beispielcreole :wiki, :layout_engine => :erb
    + +Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => creole(:einfuehrung) } +``` + +Beachte, dass man die `creole`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= creole(:Grüße) +``` + +Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts +nicht in Creole geschrieben werden. Es ist aber möglich, einen Renderer +für die Templates zu verwenden und einen anderen für das Layout, indem +die `:layout_engine`-Option verwendet wird. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    AbhängigkeitWikiCloth
    Dateierweiterungen.mediawiki und .mw
    Beispielmediawiki :wiki, :layout_engine => :erb
    + +Da man aus dem Mediawiki-Template heraus keine Ruby-Methoden aufrufen +und auch keine locals verwenden kann, wird man Mediawiki üblicherweise +in Kombination mit anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Beachte: Man kann die `mediawiki`-Methode auch aus anderen Templates +heraus aufrufen: + +```ruby +%h1 Grüße von Haml! +%p= mediawiki(:greetings) +``` + +Da man Ruby nicht von MediaWiki heraus aufrufen kann, können auch +Layouts nicht in MediaWiki geschrieben werden. Es ist aber möglich, +einen Renderer für die Templates zu verwenden und einen anderen für das +Layout, indem die `:layout_engine`-Option verwendet wird. + +#### CoffeeScript Templates + + + + + + + + + + + + + +
    Abhängigkeitcoffee-script + und eine Möglichkeit JavaScript auszuführen. +
    Dateierweiterung.coffee
    Beispielcoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Abhängigkeit + + Stylus + und eine Möglichkeit + + JavaScript auszuführen + . +
    Dateierweiterung.styl
    Beispielstylus :index
    + +Um Stylus-Templates ausführen zu können, müssen `stylus` und `stylus/tilt` +zuerst geladen werden: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Abhängigkeityajl-ruby
    Dateierweiterung.yajl
    Beispiel + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende +json Variable wird mit Hilfe von `#to_json` umgewandelt: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt +verwendet werden: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    Abhängigkeitwlang
    Dateierweiterung.wlang
    Beispielwlang :index, :locals => { :key => 'value' }
    + +Ruby-Methoden in Wlang aufzurufen entspricht nicht den idiomatischen Vorgaben +von Wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die +Wlang und `yield` verwenden, werden aber trotzdem unterstützt. + +Rendert den eingebetteten Template-String. + +### Auf Variablen in Templates zugreifen + +Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen +in Routen sind auch direkt im Template verfügbar: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Oder durch einen expliziten Hash von lokalen Variablen: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen +Templates eingesetzt. + +### Templates mit `yield` und verschachtelte Layouts + +Ein Layout ist üblicherweise ein Template, das ein `yield` aufruft. Ein +solches Template kann entweder wie oben beschrieben über die `:template` +Option verwendet werden oder mit einem Block gerendert werden: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Dieser Code entspricht weitestgehend `erb :index, :layout => :post`. + +Blöcke an Render-Methoden weiterzugeben ist besonders bei verschachtelten +Layouts hilfreich: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Der gleiche Effekt kann auch mit weniger Code erreicht werden: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Zur Zeit nehmen folgende Renderer Blöcke an: `erb`, `haml`, `liquid`, `slim ` +und `wlang`. + +Das gleich gilt auch für die allgemeine `render` Methode. + +### Inline-Templates + +Templates können auch am Ende der Datei definiert werden: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hallo Welt +``` + +Anmerkung: Inline-Templates, die in der Datei definiert sind, die `require +'sinatra'` aufruft, werden automatisch geladen. Um andere Inline-Templates in +weiteren Dateien aufzurufen, muss explizit `enable :inline_templates` +verwendet werden. + +### Benannte Templates + +Templates können auch mit der Top-Level `template`-Methode definiert werden: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hallo Welt!' +end + +get '/' do + haml :index +end +``` + +Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf +verwendet. Durch `:layout => false` kann das Ausführen individuell nach Route +verhindert werden, oder generell für ein Template, z.B. Haml via: +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? + # !request.xhr? prüft, ob es sich um einen asynchronen Request handelt. + # wenn nicht, dann verwende ein Layout (negiert durch !) +end +``` + +### Dateiendungen zuordnen + +Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` +genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt +werden soll, lässt sich dies wie folgt bewerkstelligen: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Eine eigene Template-Engine hinzufügen + +Zu allererst muss die Engine bei Tilt registriert und danach eine +Rendering-Methode erstellt werden: + +```ruby +Tilt.register :mtt, MeineTolleTemplateEngine + +helpers do + def mtt(*args) render(:mtt, *args) end +end + +get '/' do + mtt :index +end +``` + +Dieser Code rendert `./views/application.mtt`. Siehe +[github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über +Tilt zu erfahren. + +### Eigene Methoden zum Aufsuchen von Templates verwenden + +Um einen eigenen Mechanismus zum Aufsuchen von Templates zu +implementieren, muss `#find_template` definiert werden: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filter + +Before-Filter werden vor jedem Request in demselben Kontext, wie danach die +Routen, ausgeführt. So können etwa Request und Antwort geändert werden. +Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet +werden: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After-Filter werden nach jedem Request in demselben Kontext ausgeführt und +können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte +Instanzvariablen können in After-Filtern verwendet werden: + +```ruby +after do + puts response.status +end +``` + +Achtung: Wenn statt der body-Methode ein einfacher String verwendet +wird, ist der Response-body im after-Filter noch nicht verfügbar, da +er erst nach dem Durchlaufen des after-Filters erzeugt wird. + +Filter können optional auch mit einem Muster ausgestattet werden, das auf den +Request-Pfad passen muss, damit der Filter ausgeführt wird: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt +werden: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helfer + +Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden +definiert, die in Routen und Templates verwendet werden können: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Ebenso können Helfer auch in einem eigenen Modul definiert werden: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +Das Ergebnis ist das gleiche, wie beim Einbinden in die +Anwendungs-Klasse. + +### Sessions verwenden +Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. +Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet +werden: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +Um die Sicherheit zu erhöhen, werden Daten mit einem geheimen +Sitzungsschlüssel unter Verwendung von `HMAC-SHA1` in einem Cookie signiert. +Der Sitzungsschlüssel sollte optimalerweise ein kryptografisch zufällig +erzeugter Wert mit angemessener Länge sein, für den `HMAC-SHA1` größer +oder gleich 65 Bytes ist (256 Bits, 64 Hex-Zeichen). Es wird empfohlen, +keinen Schlüssel zu verwenden, dessen Zufälligkeit weniger als 32 Bytes +entspricht (also 256 Bits, 64 Hex-Zeichen). Es ist deshalb **wirklich +wichtig**, dass nicht einfach irgendetwas als Schlüssel verwendet wird, +sondern ein sicherer Zufallsgenerator die Zeichenkette erstellt. Menschen sind +nicht besonders gut, zufällige Zeichenfolgen zu erstellen. + +Sinatra generiert automatisch einen zufälligen 32 Byte langen zufälligen +Schlüssel. Da jedoch bei jedem Neustart der Schlüssel ebenfalls neu generiert +wird, ist es sinnvoll einen eigenen Schlüssel festzulegen, damit er über alle +Anwendungsinstanzen hinweg geteilt werden kann. + +Aus praktikablen und Sicherheitsgründen wird +[empfohlen](https://12factor.net/config), dass ein sicherer Zufallswert +erzeugt und in einer Umgebungsvariable abgelgegt wird, damit alle +Anwendungsinstanzen darauf zugreifen können. Dieser Sitzungsschlüssel +sollte in regelmäßigen Abständen erneuert werden. Zum Erzeugen von 64 +Byte starken Schlüsseln sind hier ein paar Beispiele vorgestellt: + +**Sitzungsschlüssel erzeugen** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8afi...usw...ec0f262ac +``` + +**Sitzungsschlüssel erzeugen (Bonuspunkte)** + +Um den systemweiten Zufallszahlengenerator zu verwenden, kann das +[sysrandom gem](https://github.com/cryptosphere/sysrandom) installiert +werden, anstelle von Zufallszahlen aus dem Userspace, auf die MRI zur +Zeit standardmäßig zugreift: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Sitzungsschlüssel-Umgebungsvariable** + +Wird eine `SESSION_SECRET`-Umgebungsvariable persistent gesetzt, kann +Sinatra darauf zugreifen. Da die folgende Methode von System zu System +variieren kann, ist dies als Beispiel zu verstehen: + +```bash +$ echo "export SESSION_SECRET=99ae8af...etc...ec0f262ac" >> ~/.bashrc +``` + +**Anwendungseinstellung für Sitzungsschlüssel** + +Die Anwendung sollte unabhängig von der `SESSION_SECRET`-Umgebungsvariable +auf einen sicheren zufälligen Schlüssel zurückgreifen. + +Auch hier sollte das +[sysrandom gem](https://github.com/cryptosphere/sysrandom) verwendet +werden: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Sitzungseinstellungen + +Im Options-Hash können weitere Einstellungen abgelegt werden: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Um Sitzungsdaten über mehrere Anwendungen und Subdomains hinweg zu +teilen, muss die Domain mit einem `*.*` vor der Domain ausgestattet +werden: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Eigene Sitzungs-Middleware auswählen + +Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter +Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten +höheren, teilweise überflüssigen Traffic. Es kann daher eine beliebige +Rack-Session Middleware verwendet werden. Folgende Methoden stehen zur +Verfügung: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Oder Sitzungen werden mit einem Options-Hash ausgestattet: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Eine weitere Methode ist der Verzicht auf `enable :session` und +stattdessen die Verwendung einer beliebigen anderen Middleware. + +Dabei ist jedoch zu beachten, dass der reguläre sitzungsbasierte +Sicherungsmechanismus **nicht automatisch aktiviert wird**. + +Die dazu benötigte Rack-Middleware muss explizit eingebunden werden: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Mehr dazu unter [Einstellung des Angiffsschutzes](https://github.com/sinatra/sinatra/blob/master/README.de.md#einstellung-des-angriffsschutzes). + +### Anhalten + +Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: + +```ruby +halt +``` + +Der Status kann beim Stoppen mit angegeben werden: + +```ruby +halt 410 +``` + +Oder auch den Response-Body: + +```ruby +halt 'Hier steht der Body' +``` + +Oder beides: + +```ruby +halt 401, 'verschwinde!' +``` + +Sogar mit Headern: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'Rache' +``` + +Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: + +```ruby +halt erb(:error) +``` + +### Weiterspringen + +Eine Route kann mittels `pass` zu der nächsten passenden Route springen: + +```ruby +get '/raten/:wer' do + pass unless params['wer'] == 'Frank' + 'Du hast mich!' +end + +get '/raten/*' do + 'Du hast mich nicht!' +end +``` + +Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route +gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster +gefunden wird. + +### Eine andere Route ansteuern + +Wenn nicht zu einer anderen Route gesprungen werden soll, sondern nur das +Ergebnis einer anderen Route gefordert wird, kann `call` für einen internen +Request verwendet werden: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht +werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die +`/foo` und `/bar` zugreifen können. + +Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine +Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` +verwendet werden. + +Weitere Informationen zu `call` finden sich in den Rack-Spezifikationen. + +### Body, Status-Code und Header setzen + +Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit +einem Returnwert in der Route zu setzen. In manchen Situationen kann es +jedoch sein, dass der Body an anderer Stelle während der Ausführung gesetzt +werden soll. Dafür kann man die Helfer-Methode `body` einsetzen. Ist sie +gesetzt, kann sie zu einem späteren Zeitpunkt aufgerufen werden: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom +Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming +einsetzen, siehe auch "Rückgabewerte"). + +Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + halt "Ich bin ein Teekesselchen" +end +``` + +Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne +Argumente den aktuellen Wert aus. + +### Response-Streams + +In manchen Situationen sollen Daten bereits an den Client zurückgeschickt +werden, bevor ein vollständiger Response bereit steht. Manchmal will man die +Verbindung auch erst dann beenden und Daten so lange an den Client +zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die +`stream`-Helfer-Methode, die es einem erspart, eigene Lösungen zu schreiben: + +```ruby +get '/' do + stream do |out| + out << "Das ist ja mal wieder fanta -\n" + sleep 0.5 + out << " (bitte warten …) \n" + sleep 1 + out << "- stisch!\n" + end +end +``` + +Damit lassen sich Streaming-APIs realisieren, sog. +[Server Sent Events](https://w3c.github.io/eventsource/), die als Basis für +[WebSockets](https://en.wikipedia.org/wiki/WebSocket) dienen. Ebenso können +sie verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von +langsamen Ressourcen abhängig ist. + +Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl +nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die +Applikation verwendet wird. Einige Server unterstützen +Streaming nicht oder nur teilweise. Sollte der Server Streaming nicht +unterstützen, wird ein vollständiger Response-Body zurückgeschickt, sobald der +an `stream` weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert +Streaming z.B. überhaupt nicht. + +Ist der optionale Parameter `keep_open` aktiviert, wird beim gestreamten +Objekt `close` nicht aufgerufen und es ist einem überlassen dies an einem +beliebigen späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei +Event-gesteuerten Serven wie Thin oder Rainbows möglich, andere Server werden +trotzdem den Stream beenden: + +```ruby +# Durchgehende Anfrage (long polling) + +set :server, :thin +connections = [] + +get '/subscribe' do + # Client-Registrierung beim Server, damit Events mitgeteilt werden können + stream(:keep_open) do |out| + connections << out + # tote Verbindungen entfernen + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # Den Client über eine neue Nachricht in Kenntnis setzen + # notify client that a new message has arrived + out << params['message'] << "\n" + + # Den Client zur erneuten Verbindung auffordern + out.close + end + + # Rückmeldung + "Mitteiling erhalten" +end +``` + +Es ist ebenfalls möglich, dass der Client die Verbindung schließt, während in +den Socket geschrieben wird. Deshalb ist es sinnvoll, vor einem +Schreibvorgang `out.closed?` zu prüfen. + +### Logger + +Im Geltungsbereich eines Request stellt die `logger` Helfer-Methode eine +`Logger` Instanz zur Verfügung: + +```ruby +get '/' do + logger.info "es passiert gerade etwas" + # ... +end +``` + +Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten +Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt +zurück. In den Routen und Filtern muss man sich also nicht weiter darum +kümmern. + +Beachte, dass das Loggen standardmäßig nur für `Sinatra::Application` +voreingestellt ist. Wird über `Sinatra::Base` vererbt, muss es erst aktiviert +werden: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Damit auch keine Middleware das Logging aktivieren kann, muss die `logging` +Einstellung auf `nil` gesetzt werden. Das heißt aber auch, dass `logger` in +diesem Fall `nil` zurückgeben wird. Üblicherweise wird das eingesetzt, wenn +ein eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was +in `env['rack.logger']` eingetragen ist. + +### Mime-Types + +Wenn `send_file` oder statische Dateien verwendet werden, kann es vorkommen, +dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit `mime_type` +per Dateiendung: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Es kann aber auch der `content_type`-Helfer verwendet werden: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLs generieren + +Zum Generieren von URLs sollte die `url`-Helfer-Methode genutzen werden, so +z.B. beim Einsatz von Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen. + +Diese Methode ist ebenso über das Alias `to` zu erreichen (siehe Beispiel +unten). + +### Browser-Umleitung + +Eine Browser-Umleitung kann mithilfe der `redirect`-Helfer-Methode erreicht +werden: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Weitere Parameter werden wie Argumente der `halt`-Methode behandelt: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'Hier bist du falsch' +``` + +Ebenso leicht lässt sich ein Schritt zurück mit dem Alias `redirect back` +erreichen: + +```ruby +get '/foo' do + "mach was" +end + +get '/bar' do + mach_was + redirect back +end +``` + +Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query +übergeben: + +```ruby +redirect to('/bar?summe=42') +``` + +oder eine Session verwendet werden: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache einsetzen + +Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein +ordentliches HTTP-Caching. + +Der Cache-Control-Header lässt sich ganz einfach einstellen: + +```ruby +get '/' do + cache_control :public + "schon gecached!" +end +``` + +Profitipp: Caching im before-Filter aktivieren + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Bei Verwendung der `expires`-Helfermethode zum Setzen des gleichnamigen +Headers, wird `Cache-Control` automatisch eigestellt: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Um alles richtig zu machen, sollten auch `etag` oder `last_modified` verwendet +werden. Es wird empfohlen, dass diese Helfer aufgerufen werden *bevor* die +eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client +eine aktuelle Version im Cache vorhält: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +ebenso ist es möglich einen +[schwachen ETag](https://de.wikipedia.org/wiki/HTTP_ETag) zu verwenden: + +```ruby +etag @article.sha1, :weak +``` + +Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür +notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy +Cache-Lösungen bietet sich z.B. +[rack-cache](https://github.com/rtomayko/rack-cache) an: + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein If-Match +oder ein If-None-Match Header auf `*` gesetzt wird in Abhängigkeit davon, ob +die Resource bereits existiert. Sinatra geht davon aus, dass Ressourcen bei +sicheren Anfragen (z.B. bei get oder Idempotenten Anfragen wie put) bereits +existieren, wobei anderen Ressourcen (besipielsweise bei post), als neue +Ressourcen behandelt werden. Dieses Verhalten lässt sich mit der +`:new_resource` Option ändern: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Soll das schwache ETag trotzdem verwendet werden, verwendet man die `:kind` +Option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Dateien versenden + +Um den Inhalt einer Datei als Response zurückzugeben, kann die +`send_file`-Helfer-Methode verwendet werden: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Für `send_file` stehen einige Hash-Optionen zur Verfügung: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +
    +
    filename
    +
    Dateiname als Response. + Standardwert ist der eigentliche Dateiname.
    + +
    last_modified
    +
    Wert für den Last-Modified-Header, Standardwert ist mtime der + Datei.
    + +
    type
    +
    Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, + von der Dateiendung abgeleitet.
    + +
    disposition
    +
    Verwendet für Content-Disposition. Mögliche Werte sind: nil + (Standard), :attachment und :inline.
    + +
    length
    +
    Content-Length-Header. Standardwert ist die Dateigröße.
    +
    Status
    +
    Zu versendender Status-Code. Nützlich, wenn eine statische Datei + als Fehlerseite zurückgegeben werden soll. Wenn der Rack-Handler es + unterstützt, dann können auch andere Methoden außer Streaming vom + Ruby-Prozess verwendet werden. Wird diese Helfermethode verwendet, + dann wird Sinatra sich automatisch um die Range-Anfrage kümmern.
    +
    + +### Das Request-Objekt + +Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus +zugegriffen werden (d.h. Filter, Routen, Fehlerbehandlung): + +```ruby +# App läuft unter http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # Request-Body des Client (siehe unten) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # Länge des request.body + request.media_type # Medientypus von request.body + request.host # "example.com" + request.get? # true (ähnliche Methoden für andere Verben) + request.form_data? # false + request["irgendein_param"] # Wert von einem Parameter; [] ist die Kurzform für den params Hash + request.referrer # Der Referrer des Clients oder '/' + request.user_agent # User-Agent (verwendet in der :agent Bedingung) + request.cookies # Hash des Browser-Cookies + request.xhr? # Ist das hier ein Ajax-Request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-Adresse des Clients + request.secure? # false (true wenn SSL) + request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) + request.env # vollständiger env-Hash von Rack übergeben +end +``` + +Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch +schreibbar: + +```ruby +before { request.path_info = "/" } + +get "/" do + "Alle Anfragen kommen hier an!" +end +``` + +Der `request.body` ist ein IO- oder StringIO-Objekt: + +```ruby +post "/api" do + request.body.rewind # falls schon jemand davon gelesen hat + daten = JSON.parse request.body.read + "Hallo #{daten['name']}!" +end +``` + +### Anhänge + +Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser +angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: + +```ruby +get '/' do + attachment + "Speichern!" +end +``` + +Ebenso kann eine Dateiname als Parameter hinzugefügt werden: + +```ruby +get '/' do + attachment "info.txt" + "Speichern!" +end +``` + +### Umgang mit Datum und Zeit + +Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert +ein Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und +ähnliche Klassen konvertieren: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "noch Zeit" +end +``` + +Diese Methode wird intern für `expires`, `last_modiefied` und ihresgleichen +verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem +Verhalten erweitern, indem man `time_for` in der eigenen Applikation +überschreibt: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "Hallo" +end +``` + +### Nachschlagen von Template-Dateien + +Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum +Rendern aufzufinden: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "könnte diese hier sein: #{file}" +end +``` + +Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann +sie nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum +Beispiel dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines +zu verwenden: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt +werden! + +Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich +existiert. Es wird lediglich der angegebene Block aufgerufen und nach allen +möglichen Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` +`block` verwendet, sobald eine Datei gefunden wurde. Ebenso werden +Template-Pfade samt Inhalt gecached, solange nicht im Entwicklungsmodus +gearbeitet wird. Das sollte im Hinterkopf behalten werden, wenn irgendwelche +verrückten Methoden zusammengebastelt werden. + +### Konfiguration + +Wird einmal beim Starten in jedweder Umgebung ausgeführt: + +```ruby +configure do + # setze eine Option + set :option, 'wert' + + # setze mehrere Optionen + set :a => 1, :b => 2 + + # das gleiche wie `set :option, true` + enable :option + + # das gleiche wie `set :option, false` + disable :option + + # dynamische Einstellungen mit Blöcken + set(:css_dir) { File.join(views, 'css') } +end +``` + +Läuft nur, wenn die Umgebung (`APP_ENV`-Umgebungsvariable) auf `:production` +gesetzt ist: + +```ruby +configure :production do + ... +end +``` + +Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: + +```ruby +configure :production, :test do + ... +end +``` + +Diese Einstellungen sind über `settings` erreichbar: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +#### Einstellung des Angriffsschutzes + +Sinatra verwendet +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme), um die +Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung +lässt sich selbstverständlich deaktivieren, der damit verbundene +Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen +Risiken. + +```ruby +disable :protection +``` + +Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` +einen Hash mit Optionen hinzu: + +```ruby +set :protection, :except => :path_traversal +``` + +Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere +Schutzmechanismen zu deaktivieren: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Standardmäßig setzt Sinatra einen sitzungbasierten Schutz nur dann ein, +wenn `:sessions` aktiviert wurde (siehe oben). Manchmal kann es aber +auch sein, dass Sitzungen außerhalb von Sinatra eingerichtet werden, +z.B. über eine config.ru oder einer zusätzlichen +`Rack::Builder`-Instance. In diesen Situationen kann eine +sitzungbasierte Sicherung eingesetzt werden mit Hilfe der +`:session`-Option: + +```ruby +set :protection, session => true +``` + +#### Mögliche Einstellungen + +
    +
    absolute_redirects
    +
    + Wenn ausgeschaltet, wird Sinatra relative Redirects zulassen. + Jedoch ist Sinatra dann nicht mehr mit RFC 2616 (HTTP 1.1) + konform, das nur absolute Redirects zulässt. +
    +
    + Sollte eingeschaltet werden, wenn die Applikation hinter einem + Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. + Beachte, dass die url-Helfer-Methode nach wie vor + absolute URLs erstellen wird, es sei denn, es wird als zweiter + Parameter false angegeben. +
    +
    Standardmäßig nicht aktiviert.
    + +
    add_charset
    +
    + Mime-Types werden hier automatisch der Helfer-Methode + content_type zugeordnet. Es empfielt sich, Werte + hinzuzufügen statt sie zu überschreiben: settings.add_charset + << "application/foobar" +
    + +
    app_file
    +
    + Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, + Inline-, View- und öffentliche Verzeichnis des Projekts festzustellen. +
    + +
    bind
    +
    + IP-Address, an die gebunden wird (Standardwert: 0.0.0.0 + oder localhost). Wird nur für den eingebauten Server + verwendet. +
    + +
    default_encoding
    +
    + Das Encoding, falls keines angegeben wurde. Standardwert ist + "utf-8". +
    + +
    dump_errors
    +
    Fehler im Log anzeigen.
    + +
    environment
    +
    + Momentane Umgebung. Standardmäßig auf ENV['APP_ENV'] oder + "development" eingestellt, soweit ersteres nicht vorhanden. +
    + +
    logging
    +
    Den Logger verwenden.
    + +
    lock
    +
    + Jeder Request wird gelocked. Es kann nur ein Request pro Ruby-Prozess + gleichzeitig verarbeitet werden. +
    +
    + Eingeschaltet, wenn die Applikation threadsicher ist. Standardmäßig + nicht aktiviert. +
    + +
    method_override
    +
    + Verwende _method, um put/delete-Formulardaten in Browsern zu + verwenden, die dies normalerweise nicht unterstützen. +
    + +
    mustermann_opts
    +
    + Ein Hash mit Standardeinstellungen, der an Mustermann.new beim + Kompilieren der Routen übergeben wird. +
    + +
    port
    +
    Port für die Applikation. Wird nur im internen Server verwendet.
    + +
    prefixed_redirects
    +
    + Entscheidet, ob request.script_name in Redirects + eingefügt wird oder nicht, wenn kein absoluter Pfad angegeben ist. + Auf diese Weise verhält sich redirect '/foo' so, als wäre + es ein redirect to('/foo'). Standardmäßig nicht + aktiviert. +
    + +
    protection
    +
    + Legt fest, ob der Schutzmechanismus für häufig Vorkommende Webangriffe + auf Webapplikationen aktiviert wird oder nicht. Weitere Informationen im + vorhergehenden Abschnitt. +
    + +
    public_folder
    +
    + Das öffentliche Verzeichnis, aus dem Daten zur Verfügung gestellt + werden können. Wird nur dann verwendet, wenn statische Daten zur + Verfügung gestellt werden können (s.u. static Option). + Leitet sich von der app_file Einstellung ab, wenn nicht + gesetzt. +
    + +
    quiet
    +
    + Deaktiviert Logs, die beim Starten und Beenden von Sinatra + ausgegeben werden. false ist die Standardeinstellung. +
    + +
    public_dir
    +
    Alias für public_folder, s.o.
    + +
    reload_templates
    +
    + Legt fest, ob Templates für jede Anfrage neu generiert werden. Im + development-Modus aktiviert. +
    + +
    root
    +
    + Wurzelverzeichnis des Projekts. Leitet sich von der app_file + Einstellung ab, wenn nicht gesetzt. +
    + +
    raise_errors
    +
    + Einen Ausnahmezustand aufrufen. Beendet die Applikation. Ist + automatisch aktiviert, wenn die Umgebung auf "test" + eingestellt ist. Ansonsten ist diese Option deaktiviert. +
    + +
    run
    +
    + Wenn aktiviert, wird Sinatra versuchen, den Webserver zu starten. Nicht + verwenden, wenn Rackup oder anderes verwendet werden soll. +
    + +
    running
    +
    Läuft der eingebaute Server? Diese Einstellung nicht ändern!
    + +
    server
    +
    + Server oder Liste von Servern, die als eingebaute Server zur + Verfügung stehen. Die Reihenfolge gibt die Priorität vor, die + Voreinstellung hängt von der verwendenten Ruby Implementierung ab. +
    + +
    sessions
    +
    + Sessions auf Cookiebasis mittels + Rack::Session::Cookie aktivieren. Für weitere Infos bitte + in der Sektion ‘Sessions verwenden’ nachschauen. +
    + +
    session_store
    +
    + Die verwendete Rack Sitzungs-Middleware. Verweist standardmäßig + auf Rack::Session::Cookie. Für weitere Infos bitte + in der Sektion ‘Sessions verwenden’ nachschauen. +
    + +
    show_exceptions
    +
    + Bei Fehlern einen Stacktrace im Browseranzeigen. Ist automatisch + aktiviert, wenn die Umgebung auf "development" + eingestellt ist. Ansonsten ist diese Option deaktiviert. +
    +
    + Kann auch auf :after_handler gestellt werden, um eine + anwendungsspezifische Fehlerbehandlung auszulösen, bevor der + Fehlerverlauf im Browser angezeigt wird. +
    + +
    static
    +
    + Entscheidet, ob Sinatra statische Dateien zur Verfügung stellen + soll oder nicht. Sollte nicht aktiviert werden, wenn ein Server + verwendet wird, der dies auch selbstständig erledigen kann. + Deaktivieren wird die Performance erhöhen. Standardmäßig + aktiviert. +
    + +
    static_cache_control
    +
    + Wenn Sinatra statische Daten zur Verfügung stellt, können mit + dieser Einstellung die Cache-Control Header zu den + Responses hinzugefügt werden. Die Einstellung verwendet dazu die + cache_control Helfer-Methode. Standardmäßig deaktiviert. + Ein Array wird verwendet, um mehrere Werte gleichzeitig zu + übergeben: set :static_cache_control, [:public, :max_age => + 300] +
    + +
    threaded
    +
    + Wird es auf true gesetzt, wird Thin aufgefordert + EventMachine.defer zur Verarbeitung des Requests einzusetzen. +
    + +
    traps
    +
    Legt fest, wie Sinatra mit System-Signalen umgehen soll.
    + +
    views
    +
    + Verzeichnis der Views. Leitet sich von der app_file Einstellung + ab, wenn nicht gesetzt. +
    + +
    x_cascade
    +
    + Einstellung, ob der X-Cascade Header bei fehlender Route gesetzt + wird oder nicht. Standardeinstellung ist true. +
    +
    + +## Umgebungen + +Es gibt drei voreingestellte Umgebungen in Sinatra: `"development"`, +`"production"` und `"test"`. Umgebungen können über die `APP_ENV` +Umgebungsvariable gesetzt werden. Die Standardeinstellung ist `"development"`. +In diesem Modus werden alle Templates zwischen Requests neu geladen. Dazu gibt +es besondere Fehlerseiten für 404 Stati und Fehlermeldungen. In `"production"` +und `"test"` werden Templates automatisch gecached. + +Um die Anwendung in einer anderen Umgebung auszuführen, kann man die +`APP_ENV`-Umgebungsvariable setzen: + +```shell +APP_ENV=production ruby my_app.rb +``` + +In der Anwendung kann man die die Methoden `development?`, `test?` und +`production?` verwenden, um die aktuelle Umgebung zu erfahren: + + +```ruby +get '/' do + if settings.development? + "development!" + else + "nicht development!" + end +end +``` + +## Fehlerbehandlung + +Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, +dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. + +### Nicht gefunden + +Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 +ist, wird der `not_found`-Handler ausgeführt: + +```ruby +not_found do + 'Seite kann nirgendwo gefunden werden.' +end +``` + +### Fehler + +Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem +Routen-Block oder in einem Filter geworfen wurde. In der +`development`-Umgebung wird es nur dann funktionieren, wenn die +`:show_exceptions`-Option auf `:after_handler` eingestellt wurde: + +```ruby +set :show_exceptions, :after_handler +``` + +Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden: + +```ruby +error do + 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].message +end +``` + +Benutzerdefinierte Fehler: + +```ruby +error MeinFehler do + 'Au weia, ' + env['sinatra.error'].message +end +``` + +Dann, wenn das passiert: + +```ruby +get '/' do + raise MeinFehler, 'etwas Schlimmes ist passiert' +end +``` + +bekommt man dieses: + +```shell +Au weia, etwas Schlimmes ist passiert +``` + +Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden: + +```ruby +error 403 do + 'Zugriff verboten' +end + +get '/geheim' do + 403 +end +``` + +Oder ein Status-Code-Bereich: + +```ruby +error 400..510 do + 'Hallo?' +end +``` + +Sinatra setzt verschiedene `not_found`- und `error`-Handler in der +Development-Umgebung ein, um hilfreiche Debugging-Informationen und +Stack-Traces anzuzeigen. + +## Rack-Middleware + +Sinatra baut auf [Rack](http://rack.github.io/) auf, einem minimalistischen +Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features +für Entwickler ist der Support von Middlewares, die zwischen den Server und +die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort +überwachen und/oder manipulieren können. + +Sinatra macht das Erstellen von Middleware-Verkettungen mit der +Top-Level-Methode `use` zu einem Kinderspiel: + +```ruby +require 'sinatra' +require 'meine_middleware' + +use Rack::Lint +use MeineMiddleware + +get '/hallo' do + 'Hallo Welt' +end +``` + +Die Semantik von `use` entspricht der gleichnamigen Methode der +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder)-DSL +(meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die +`use`-Methode mehrere/verschiedene Argumente und auch Blöcke +entgegennimmt: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'geheim' +end +``` + +Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, +URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet +viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So +muss `use` häufig nicht explizit verwendet werden. + +Hilfreiche Middleware gibt es z.B. hier: +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +oder im [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testen + +Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework +geschrieben werden. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +wird empfohlen: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hallo Welt!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hallo Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Du verwendest Songbird!", last_response.body + end +end +``` + +Hinweis: Wird Sinatra modular verwendet, muss +`Sinatra::Application` mit dem Namen der Applikations-Klasse +ersetzt werden. + +## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen + +Das Definieren einer Top-Level-Anwendung funktioniert gut für +Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie +Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder +auch Sinatra-Erweiterungen geschrieben werden sollen. + +Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie +sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, +Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins +Spiel: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hallo Welt!' + end +end +``` + +Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der +Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei +Veränderungen zu `Sinatra::Base` konvertiert werden: + +* Die Datei sollte `require 'sinatra/base'` anstelle von `require + 'sinatra'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden + in den Top-Level-Namespace importiert. +* Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in + einer Subklasse von `Sinatra::Base` definiert werden. + +`Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per +Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe +[Optionen und Konfiguration](http://www.sinatrarb.com/configuration.html) für +Details über mögliche Optionen. + +Damit eine App sich ähnlich wie eine klassische App verhält, kann man +auch eine Subclass von `Sinatra::Application` erstellen: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modularer vs. klassischer Stil + +Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil +einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein +Grund, eine modulare Applikation zu erstellen. + +Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer +modularen ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. +Sollen mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen +werden. Dabei ist es kein Problem klassische und modulare Anwendungen +miteinander zu vermischen. + +Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet +werden: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SzenarioClassicModularModular
    app_fileSinatra ladende DateiSinatra::Base subklassierende DateiSinatra::Application subklassierende Datei
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Eine modulare Applikation bereitstellen + +Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über +`run!`: + +```ruby +# mein_app.rb +require 'sinatra/base' + +class MeinApp < Sinatra::Base + # ... Anwendungscode hierhin ... + + # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird + run! if app_file == $0 +end +``` + +Starte mit: + +```shell +ruby mein_app.rb +``` + +Oder über eine `config.ru`-Datei, die es erlaubt, einen beliebigen +Rack-Handler zu verwenden: + +```ruby +# config.ru (mit rackup starten) +require './mein_app' +run MeineApp +``` + +Starte: + +```shell +rackup -p 4567 +``` + +### Eine klassische Anwendung mit einer config.ru verwenden + +Schreibe eine Anwendungsdatei: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hallo Welt!' +end +``` + +sowie eine dazugehörige `config.ru`-Datei: + +```ruby +require './app' +run Sinatra::Application +``` + +### Wann sollte eine config.ru-Datei verwendet werden? + +Anzeichen dafür, dass eine `config.ru`-Datei gebraucht wird: + +* Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, + Heroku, ...). +* Es gibt mehr als nur eine Subklasse von `Sinatra::Base`. +* Sinatra soll als Middleware verwendet werden, nicht als Endpunkt. + +**Es gibt keinen Grund, eine `config.ru`-Datei zu verwenden, nur weil eine +Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung +mit modularem Stil benötigt, um eine `config.ru`-Datei zu verwenden.** + +### Sinatra als Middleware nutzen + +Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es +kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden +beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich +nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere +Rack-Anwendung sein (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # Middleware wird vor Filtern ausgeführt + use LoginScreen + + before do + unless session['user_name'] + halt "Zugriff verweigert, bitte einloggen." + end + end + + get('/') { "Hallo #{session['user_name']}." } +end +``` + +### Dynamische Applikationserstellung + +Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, +ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit +`Sinatra.new` erreichen: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hallo" } } +my_app.run! +``` + +Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden +oder Sinatra in einer Bibliothek Verwendung findet. + +Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Geltungsbereich und Bindung + +Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur +Verfügung stehen. + +### Anwendungs- oder Klassen-Scope + +Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die +Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um +`Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit +angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur +Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die +`session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt +wird. + +Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungsscope! +set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ich bin nicht mehr im Anwendungs-Scope! + end +end +``` + +Im Anwendungs-Scope befindet man sich: + +* Innerhalb der Anwendungs-Klasse +* In Methoden, die von Erweiterungen definiert werden +* Im Block, der an `helpers` übergeben wird +* In Procs und Blöcken, die an `set` übergeben werden +* Der an `Sinatra.new` übergebene Block + +Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: + +* Über das Objekt, das an den `configure`-Block übergeben wird (`configure { + |c| ... }`). +* `settings` aus den anderen Scopes heraus. + +### Anfrage- oder Instanz-Scope + +Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse +erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus +kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder +`haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den +Anwendungs-Scope zugegriffen werden: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungs-Scope! + get '/neue_route/:name' do + # Anfrage-Scope für '/neue_route/:name' + @value = 42 + + settings.get "/#{params['name']}" do + # Anfrage-Scope für "/#{params['name']}" + @value # => nil (nicht dieselbe Anfrage) + end + + "Route definiert!" + end +end +``` + +Im Anfrage-Scope befindet man sich: + +* In get, head, post, put, delete, options, patch, link und unlink Blöcken +* In before und after Filtern +* In Helfer-Methoden +* In Templates/Views + +### Delegation-Scope + +Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope +weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, +da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als +delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf +die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es +gibt ein anderes `self`). Weitere Delegationen können mit +`Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. + +Im Delegation-Scop befindet man sich: + +* Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. +* In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. + +Schau am besten im Code nach: Hier ist [Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 +) definiert und wird in den [globalen Namespace +eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb). + +## Kommandozeile + +Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER] +``` + +Die Optionen sind: + +``` +-h # Hilfe +-p # Port setzen (Standard ist 4567) +-h # Host setzen (Standard ist 0.0.0.0) +-e # Umgebung setzen (Standard ist development) +-s # Rack-Server/Handler setzen (Standard ist thin) +-q # den lautlosen Server-Modus einschalten (Standard ist aus) +-x # Mutex-Lock einschalten (Standard ist aus) +``` + +### Multi-threading + +_Paraphrasiert von [dieser Antwort auf StackOverflow][so-answer] von +Konstantin_ + +Sinatra erlegt kein Nebenläufigkeitsmodell auf, sondern überlässt dies dem +selbst gewählten Rack-Proxy (Server), so wie Thin, Puma oder WEBrick. +Sinatra selbst ist Thread-sicher, somit ist es kein Problem wenn der +Rack-Proxy ein anderes Threading-Modell für Nebenläufigkeit benutzt. +Das heißt, dass wenn der Server gestartet wird, dass man die korrekte +Aufrufsmethode benutzen sollte für den jeweiligen Rack-Proxy. +Das folgende Beispiel ist eine Veranschaulichung eines mehrprozessigen +Thin Servers: + +``` ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +Um den Server zu starten, führt man das folgende Kommando aus: + +``` shell +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## Systemanforderungen + +Die folgenden Versionen werden offiziell unterstützt: + +
    +
    Ruby 2.3
    +
    + 2.3 wird vollständig unterstützt. Es gibt derzeit keine Pläne die + offizielle Unterstützung zu beenden +
    + +
    Rubinius
    +
    + Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird + empfohlen, den Puma Server zu + installieren (gem install puma) +
    + +
    JRuby
    +
    + Aktuelle JRuby Versionen werden offiziell unterstützt. Es wird empfohlen, + keine C-Erweiterungen zu verwenden und als Server Trinidad zu verwenden + (gem install trinidad). +
    +
    + +Versionen vor Ruby 2.2.2 werden ab Sinatra 2.0 nicht länger unterstützt. + +Nachfolgende Ruby-Versionen werden regelmäßig auf Unterstützung geprüft. + +Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von +Sinatra unterstützt, funktionieren aber normalerweise: + +* Ruby Enterprise Edition +* Ältere Versionen von JRuby und Rubinius +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 und 1.9.1 (wird aber nicht empfohlen) + +Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, +wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen +Implementierung liegt. + +Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head +(zukünftige Versionen von MRI) mit eingebunden. Es kann davon ausgegangen +werden, dass Sinatra MRI auch weiterhin vollständig unterstützen wird. + +Sinatra sollte auf jedem Betriebssystem laufen, das einen funktionierenden +Ruby-Interpreter aufweist. + +Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= +2.2. + +## Der neuste Stand (The Bleeding Edge) + +Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. +Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, +die so installiert werden: + +```shell +gem install sinatra --pre +``` + +### Mit Bundler + +Wenn die Applikation mit der neuesten Version von Sinatra und +[Bundler](http://bundler.io) genutzt werden soll, empfehlen wir den +nachfolgenden Weg. + +Soweit Bundler noch nicht installiert ist: + +```shell +gem install bundler +``` + +Anschließend wird eine `Gemfile`-Datei im Projektverzeichnis mit folgendem +Inhalt erstellt: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# evtl. andere Abhängigkeiten +gem 'haml' # z.B. wenn du Haml verwendest... +``` + +Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, +direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem +Gemfile von Sinatra hinzugefügt. + +Jetzt kannst du deine Applikation starten: + +```shell +bundle exec ruby myapp.rb +``` + +## Versions-Verfahren + +Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. +SemVer und SemVerTag. + +## Mehr + +* [Projekt-Website](http://www.sinatrarb.com/) - Ergänzende Dokumentation, + News und Links zu anderen Ressourcen. +* [Mitmachen](http://www.sinatrarb.com/contributing.html) - Einen Fehler + gefunden? Brauchst du Hilfe? Hast du einen Patch? +* [Issue-Tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing-Liste](http://groups.google.com/group/sinatrarb) +* IRC [#sinatra](irc://chat.freenode.net/#sinatra) auf + http://freenode.net Es gibt dort auch immer wieder deutschsprachige + Entwickler, die gerne weiterhelfen. +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack and see + [here](https://sinatra-slack.herokuapp.com/) for an invite. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Kochbuch Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der + Community +* API Dokumentation für die + [aktuelle Version](http://www.rubydoc.info//gems/sinatra) oder für + [HEAD](http://www.rubydoc.info/github/sinatra/sinatra) auf + http://rubydoc.info +* [CI Server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.es.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.es.md new file mode 100644 index 0000000000..6b103c4d0b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.es.md @@ -0,0 +1,3202 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](http://travis-ci.org/sinatra/sinatra) + +Sinatra es un +[DSL](https://es.wikipedia.org/wiki/Lenguaje_específico_del_dominio) para +crear aplicaciones web rápidamente en Ruby con un mínimo esfuerzo: + +```ruby +# miapp.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Instala la gema: + +```shell +gem install sinatra +ruby miapp.rb +``` + +Y corre la aplicación: +```shell +gem install sinatra +ruby miapp.rb +``` + +Ver en [http://localhost:4567](http://localhost:4567). + +El código que cambiaste no tendra efecto hasta que reinicies el servidor. +Por favor reinicia el servidor cada vez que cambies tu código o usa [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Se recomienda ejecutar `gem install thin`, porque Sinatra lo utilizará si está disponible. + + +## Tabla de Contenidos + +* [Sinatra](#sinatra) + * [Tabla de Contenidos](#tabla-de-contenidos) + * [Rutas](#rutas) + * [Condiciones](#condiciones) + * [Valores de Retorno](#valores-de-retorno) + * [Comparadores de Rutas Personalizados](#comparadores-de-rutas-personalizados) + * [Archivos Estáticos](#archivos-estáticos) + * [Vistas / Plantillas](#vistas--plantillas) + * [Plantillas Literales](#plantillas-literales) + * [Lenguajes de Plantillas Disponibles](#lenguajes-de-plantillas-disponibles) + * [Plantillas Haml](#plantillas-haml) + * [Plantillas Erb](#plantillas-erb) + * [Plantillas Builder](#plantillas-builder) + * [Plantillas Nokogiri](#plantillas-nokogiri) + * [Plantillas Sass](#plantillas-sass) + * [Plantillas SCSS](#plantillas-scss) + * [Plantillas Less](#plantillas-less) + * [Plantillas Liquid](#plantillas-liquid) + * [Plantillas Markdown](#plantillas-markdown) + * [Plantillas Textile](#plantillas-textile) + * [Plantillas RDoc](#plantillas-rdoc) + * [Plantillas AsciiDoc](#plantillas-asciidoc) + * [Plantillas Radius](#plantillas-radius) + * [Plantillas Markaby](#plantillas-markaby) + * [Plantillas RABL](#plantillas-rabl) + * [Plantillas Slim](#plantillas-slim) + * [Plantillas Creole](#plantillas-creole) + * [Plantillas MediaWiki](#mediawiki-templates) + * [Plantillas CofeeScript](#plantillas-coffeescript) + * [Plantillas Stylus](#plantillas-stylus) + * [Plantillas Yajl](#plantillas-yajl) + * [Plantillas Wlang](#plantillas-wlang) + * [Accediendo Variables en Plantillas](#accediendo-a-variables-en-plantillas) + * [Plantillas con `yield` y `layout` anidado](#plantillas-con-yield-y-layout-anidado) + * [Plantillas Inline](#plantillas-inline) + * [Plantillas Nombradas](#plantillas-nombradas) + * [Asociando Extensiones de Archivo](#asociando-extensiones-de-archivo) + * [Añadiendo Tu Propio Motor de Plantillas](#añadiendo-tu-propio-motor-de-plantillas) + * [Usando Lógica Personalizada para la Búsqueda en Plantillas](#usando-lógica-personalizada-para-la-búsqueda-en-plantillas) + * [Filtros](#filtros) + * [Helpers](#helpers) + * [Usando Sesiones](#usando-sesiones) + * [Secreto de Sesión](#secreto-de-sesión) + * [Configuración de Sesión](#configuración-de-sesión) + * [Escogiendo tu propio Middleware de Sesión](#escogiendo-tu-propio-middleware-de-sesión) + * [Interrupcion](#interrupción) + * [Paso](#paso) + * [Desencadenando Otra Ruta](#desencadenando-otra-ruta) + * [Configurando el Cuerpo, Código de Estado y los Encabezados](#configurando-el-cuerpo-código-de-estado-y-los-encabezados) + * [Streaming De Respuestas](#streaming-de-respuestas) + * [Logging](#logging) + * [Tipos Mime](#tipos-mime) + * [Generando URLs](#generando-urls) + * [Redirección del Navegador](#redirección-del-navegador) + * [Control del Cache](#control-del-cache) + * [Enviando Archivos](#enviando-archivos) + * [Accediendo al Objeto Request](#accediendo-al-objeto-request) + * [Archivos Adjuntos](#archivos-adjuntos) + * [Fecha y Hora](#fecha-y-hora) + * [Buscando los Archivos de las Plantillas](#buscando-los-archivos-de-las-plantillas) + * [Configuración](#configuración) + * [Configurando la Protección Contra Ataques](#configurando-la-protección-contra-ataques) + * [Configuraciones Disponibles](#configuraciones-disponibles) + * [Entornos](#entornos) + * [Manejo de Errores](#manejo-de-errores) + * [Not Found](#not-found) + * [Error](#error) + * [Rack Middleware](#rack-middleware) + * [Pruebas](#pruebas) + * [Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares](#sinatrabase---middleware-librerías-y-aplicaciones-modulares) + * [Estilo Modular vs Estilo Clásico](#estilo-modular-vs-clásico) + * [Sirviendo una Aplicación Modular](#sirviendo-una-aplicación-modular) + * [Usando una Aplicación de Estilo Clásico con config.ru](#usando-una-aplicación-clásica-con-un-archivo-configru) + * [¿Cuándo usar config.ru?](#cuándo-usar-configru) + * [Utilizando Sinatra como Middleware](#utilizando-sinatra-como-middleware) + * [Creación Dinámica de Aplicaciones](#creación-dinámica-de-aplicaciones) + * [Ámbitos y Ligaduras (Scopes and Binding)](#Ámbitos-y-ligaduras) + * [Alcance de una Aplicación/Clase](#Ámbito-de-aplicaciónclase) + * [Alcance de una Solicitud/Instancia](#Ámbito-de-peticióninstancia) + * [Alcance de Delegación](#Ámbito-de-delegación) + * [Línea de comandos](#línea-de-comandos) + * [Multi-threading](#multi-threading) + * [Requerimientos](#requerimientos) + * [A la Vanguardia](#a-la-vanguardia) + * [Usando bundler](#usando-bundler) + * [Versionado](#versionado) + * [Lecturas Recomendadas](#lecturas-recomendadas) + +## Rutas + +En Sinatra, una ruta es un método HTTP junto a un patrón de un URL. +Cada ruta está asociada a un bloque: + +```ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. crear algo .. +end + +put '/' do + .. reemplazar algo .. +end + +patch '/' do + .. modificar algo .. +end + +delete '/' do + .. aniquilar algo .. +end + +options '/' do + .. informar algo .. +end + +link '/' do + .. afiliar a algo .. +end + +unlink '/' do + .. separar algo .. +end + +``` + +Las rutas son comparadas en el orden en el que son definidas. La primera ruta +que coincide con la petición es invocada. + +Las rutas con barras al final son distintas a las que no tienen: + + +```ruby +get '/foo' do + # no es igual que "GET /foo/" +end +``` + +Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a +través del hash `params`: + + +```ruby +get '/hola/:nombre' do + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + "Hola #{params['nombre']}!" +end +``` + +También puede acceder a los parámetros nombrados usando parámetros de bloque: + +```ruby +get '/hola/:nombre' do |n| + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + # n almacena params['nombre'] + "Hola #{n}!" +end +``` + +Los patrones de ruta también pueden incluir parámetros splat (o wildcard), +accesibles a través del arreglo `params['splat']`: + +```ruby +get '/decir/*/al/*' do + # coincide con /decir/hola/al/mundo + params['splat'] # => ["hola", "mundo"] +end + +get '/descargar/*.*' do + # coincide con /descargar/path/al/archivo.xml + params['splat'] # => ["path/al/archivo", "xml"] +end +``` + +O, con parámetros de bloque: + +```ruby +get '/descargar/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Rutas con Expresiones Regulares: + +```ruby +get /\/hola\/([\w]+)/ do + "Hola, #{params['captures'].first}!" +end +``` + +O con un parámetro de bloque: + +```ruby +get %r{/hola/([\w]+)} do |c| + "Hola, #{c}!" +end +``` + +Los patrones de ruta pueden contener parámetros opcionales: + +```ruby +get '/posts/:formato?' do + # coincide con "GET /posts/" y además admite cualquier extensión, por + # ejemplo, "GET /posts/json", "GET /posts/xml", etc. +end +``` + +Las rutas también pueden usar parámetros de consulta: + +```ruby +get '/posts' do + # es igual que "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # usa las variables title y author; la consulta es opcional para la ruta /posts +end +``` + +A propósito, a menos que desactives la protección para el ataque *path +traversal* (ver más [abajo](#configurando-la-protección-contra-ataques)), el path de la petición puede ser modificado +antes de que se compare con los de tus rutas. + +Puedes perzonalizar las opciones de [Mustermann](https://github.com/sinatra/mustermann) usadas para una ruta pasando +el hash `:mustermann_opts`: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # es exactamente igual a /posts, con anclaje explícito + "¡Si igualas un patrón anclado aplaude!" +end +``` + +## Condiciones + +Las rutas pueden incluir una variedad de condiciones de selección, como por +ejemplo el user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Estás usando la versión de Songbird #{params['agent'][0]}" +end + +get '/foo' do + # Coincide con navegadores que no sean songbird +end +``` + +Otras condiciones disponibles son `host_name` y `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Área de Administración, Acceso denegado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` busca el encabezado Accept de la solicitud + + +Puede definir sus propias condiciones fácilmente: + +```ruby +set(:probabilidad) { |valor| condition { rand <= valor } } + +get '/gana_un_auto', :probabilidad => 0.1 do + "Ganaste!" +end + +get '/gana_un_auto' do + "Lo siento, perdiste." +end +``` + +Para una condición que toma multiples valores usa splat: + +```ruby +set(:autorizar) do |*roles| # <- mira el splat + condition do + unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol } + redirect "/iniciar_sesion/", 303 + end + end +end + +get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do + "Detalles de mi cuenta" +end + +get "/solo/administradores/", :autorizar => :administrador do + "Únicamente para administradores!" +end +``` + +## Valores de Retorno + +El valor de retorno de un bloque de ruta determina por lo menos el cuerpo de la respuesta +transmitida al cliente HTTP o por lo menos al siguiente middleware en la pila de Rack. +Lo más común es que sea un string, como en los ejemplos anteriores. +Sin embargo, otros valores también son aceptados. + +Puedes retornar cualquier objeto que sea una respuesta Rack válida, +un objeto que represente el cuerpo de una respuesta Rack o un código +de estado HTTP: + +* Un arreglo con tres elementos: `[estado (Integer), cabeceras (Hash), cuerpo de + la respuesta (responde a #each)]` +* Un arreglo con dos elementos: `[estado (Integer), cuerpo de la respuesta + (responde a #each)]` +* Un objeto que responde a `#each` y que le pasa únicamente strings al bloque + dado +* Un Integer representando el código de estado + +De esa manera, por ejemplo, podemos fácilmente implementar un ejemplo de streaming: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +También puedes usar el `stream` helper ([descrito abajo](#streaming-de-respuestas)) +para reducir el código repetitivo e incrustar la lógica de stream a la ruta + +## Comparadores de Rutas Personalizados + +Como se mostró anteriormente, Sinatra permite utilizar strings y expresiones +regulares para definir las rutas. Sin embargo, no termina ahí.Puedes +definir tus propios comparadores muy fácilmente: + +```ruby +class TodoMenosElPatron + Match = Struct.new(:captures) + + def initialize(excepto) + @excepto = excepto + @capturas = Match.new([]) + end + + def match(str) + @capturas unless @excepto === str + end +end + +def cualquiera_menos(patron) + TodoMenosElPatron.new(patron) +end + +get cualquiera_menos("/index") do + # ... +end +``` + +Tenga en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado +similar puede conseguirse más sencillamente: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +O, usando un look ahead negativo: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Archivos Estáticos + +Los archivos estáticos son servidos desde el directorio público +`./public`. Puede especificar una ubicación diferente ajustando la +opción `:public_folder`: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Note que el nombre del directorio público no está incluido en la URL. Por +ejemplo, el archivo `./public/css/style.css` se accede a través de +`http://ejemplo.com/css/style.css`. + +Use la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` (Ver mas [abajo](#control-del-cache)). + +## Vistas / Plantillas + +Cada lenguaje de plantilla se expone a través de un método de renderizado que +lleva su nombre. Estos métodos simplemente devuelven un string: + +```ruby +get '/' do + erb :index +end +``` + +Renderiza `views/index.erb`. + +En lugar del nombre de la plantilla puedes proporcionar directamente el +contenido de la misma: + +```ruby +get '/' do + codigo = "<%= Time.now %>" + erb codigo +end +``` + +Los métodos de renderizado, aceptan además un segundo argumento, el hash de +opciones: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Renderiza `views/index.erb` incrustado en `views/post.erb` (por +defecto, la plantilla `:index` es incrustada en `views/layout.erb` siempre y +cuando este último archivo exista). + +Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado +de la plantilla: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Además, puede definir las opciones para un lenguaje de plantillas de forma +general: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Las opciones pasadas al método de renderizado tienen precedencia sobre las +definidas mediante `set`. + +Opciones disponibles: + +
    + +
    locals
    +
    + Lista de variables locales pasadas al documento. Resultan muy útiles cuando + se combinan con parciales. + Ejemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + Encoding utilizado cuando el de un string es dudoso. Por defecto toma el + valor de settings.default_encoding. +
    + +
    views
    +
    + Directorio desde donde se cargan las vistas. Por defecto toma el valor de + settings.views. +
    + +
    layout
    +
    + Si es true o false indica que se debe usar, o no, un layout, + respectivamente. También puede ser un Symbol que especifique qué plantilla + usar. Ejemplo: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type que produce la plantilla. El valor por defecto depende de cada + lenguaje de plantillas. +
    + +
    scope
    +
    + Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia + de la aplicación. Ten en cuenta que si cambiás esta opción las variables de + instancia y los helpers van a dejar de estar disponibles. +
    + +
    layout_engine
    +
    + Motor de renderizado de plantillas que usa para el layout. Resulta + conveniente para lenguajes que no soportan layouts. Por defecto toma el valor + del motor usado para renderizar la plantilla. + Ejemplo: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Opciones especiales usadas únicamente para renderizar el layout. Ejemplo: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Se asume que las plantillas están ubicadas directamente bajo el directorio `./views`. +Para usar un directorio diferente: + +```ruby +set :views, settings.root + '/templates' +``` + +Es importante acordarse que siempre tienes que referenciar a las plantillas con +símbolos, incluso cuando se encuentran en un subdirectorio (en este caso +tienes que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Esto es debido +a que los métodos de renderización van a renderizar directamente cualquier string que +se les pase como argumento. + +### Plantillas Literales + +```ruby +get '/' do + haml '%div.titulo Hola Mundo' +end +``` + +Renderiza el string de la plantilla. Opcionalmente puedes especificar +`:path` y `:line` para un backtrace más claro si hay una ruta del sistema +de archivos o una línea asociada con ese string + +```ruby +get '/' do + haml '%div.titulo Hola Mundo', :path => 'ejemplos/archivo.haml', :line => 3 +end +``` + +### Lenguajes de Plantillas Disponibles + +Algunos lenguajes tienen varias implementaciones. Para especificar que +implementación usar (y para ser thread-safe), deberías requerirla antes de +usarla: + +```ruby +require 'rdiscount' # o require 'bluecloth' +get('/') { markdown :index } +``` + +#### Plantillas Haml + + + + + + + + + + + + + + +
    Dependenciashaml
    Expresiones de Archivo.haml
    Ejemplohaml :index, :format => :html5
    + +#### Plantillas Erb + + + + + + + + + + + + + + +
    Dependencias + erubis + o erb (incluida en Ruby) +
    Extensiones de Archivo.erb, .rhtml o .erubis (solamente con Erubis)
    Ejemploerb :index
    + +#### Plantillas Builder + + + + + + + + + + + + + + +
    Dependencias + builder +
    Extensiones de Archivo.builder
    Ejemplobuilder { |xml| xml.em "hola" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas Nokogiri + + + + + + + + + + + + + + +
    Dependenciasnokogiri
    Extensiones de Archivo.nokogiri
    Ejemplonokogiri { |xml| xml.em "hola" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas Sass + + + + + + + + + + + + + + +
    Dependenciassass
    Extensiones de Archivo.sass
    Ejemplosass :stylesheet, :style => :expanded
    + +#### Plantillas SCSS + + + + + + + + + + + + + + +
    Dependenciassass
    Extensiones de Archivo.scss
    Ejemploscss :stylesheet, :style => :expanded
    + +#### Plantillas Less + + + + + + + + + + + + + + +
    Dependenciasless
    Extensiones de Archivo.less
    Ejemploless :stylesheet
    + +#### Plantillas Liquid + + + + + + + + + + + + + + +
    Dependenciasliquid
    Extensiones de Archivo.liquid
    Ejemploliquid :index, :locals => { :clave => 'valor' }
    + +Como no va a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla Liquid, casi siempre va a querer pasarle locales. + +#### Plantillas Markdown + + + + + + + + + + + + + + +
    Dependencias + RDiscount, + RedCarpet, + BlueCloth, + kramdown o + maruku +
    Extensiones de Archivo.markdown, .mkd y .md
    Ejemplomarkdown :index, :layout_engine => :erb
    + +No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => markdown(:introduccion) } +``` + +Tenga en cuenta que también puedes llamar al método `markdown` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= markdown(:saludos) +``` + +Como no puede utilizar Ruby desde Markdown, no puede usar layouts escritos en +Markdown. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas Textile + + + + + + + + + + + + + + +
    DependenciasRedCloth
    Extensiones de Archivo.textile
    Ejemplotextile :index, :layout_engine => :erb
    + +No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => textile(:introduccion) } +``` + +Ten en cuenta que también puedes llamar al método `textile` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= textile(:saludos) +``` + +Como no puedes utilizar Ruby desde Textile, no puedes usar layouts escritos en +Textile. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas RDoc + + + + + + + + + + + + + + +
    DependenciasRDoc
    Extensiones de Archivo.rdoc
    Ejemplordoc :README, :layout_engine => :erb
    + +No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => rdoc(:introduccion) } +``` + +Ten en cuenta que también puedes llamar al método `rdoc` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= rdoc(:saludos) +``` + +Como no puedes utilizar Ruby desde RDoc, no puedes usar layouts escritos en RDoc. +De todos modos, es posible usar un motor de renderizado para el layout distinto +al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas AsciiDoc + + + + + + + + + + + + + + +
    DependenciaAsciidoctor
    Extensiones de Archivo.asciidoc, .adoc and .ad
    Ejemploasciidoc :README, :layout_engine => :erb
    + +Desde que no se puede utilizar métodos de Ruby desde una +plantilla AsciiDoc, casi siempre va a querer pasarle locales. + +#### Plantillas Radius + + + + + + + + + + + + + + +
    DependenciasRadius
    Extensiones de Archivo.radius
    Ejemploradius :index, :locals => { :clave => 'valor' }
    + +Desde que no se puede utilizar métodos de Ruby (excepto por `yield`) de una +plantilla Radius, casi siempre se necesita pasar locales. + +#### Plantillas Markaby + + + + + + + + + + + + + + +
    DependenciasMarkaby
    Extensiones de Archivo.mab
    Ejemplomarkaby { h1 "Bienvenido!" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas RABL + + + + + + + + + + + + + + +
    DependenciasRabl
    Extensiones de Archivo.rabl
    Ejemplorabl :index
    + +#### Plantillas Slim + + + + + + + + + + + + + + +
    DependenciasSlim Lang
    Extensiones de Archivo.slim
    Ejemploslim :index
    + +#### Plantillas Creole + + + + + + + + + + + + + + +
    DependenciasCreole
    Extensiones de Archivo.creole
    Ejemplocreole :wiki, :layout_engine => :erb
    + +No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => cerole(:introduccion) } +``` + +Debe tomar en cuenta que también puede llamar al método `creole` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= creole(:saludos) +``` + +Como no puedes utilizar Ruby desde Creole, no puedes usar layouts escritos en +Creole. De todos modos, es posible usar un motor de renderizado para el layout +distinto al de la plantilla pasando la opción `:layout_engine`. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependenciaWikiCloth
    Extension de Archivo.mediawiki and .mw
    Ejemplomediawiki :wiki, :layout_engine => :erb
    + +No es posible llamar métodos desde el markup de MediaWiki, ni pasar locales al mismo. +Por lo tanto usualmente lo usarás en combinación con otro motor de renderizado: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Nota que también puedes llamar al método `mediawiki` desde dentro de otras plantillas: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Debido a que no puedes llamar a Ruby desde MediaWiki, no puedes usar los diseños escritos en MediaWiki. +De todas maneras, es posible usar otro motor de renderizado para esa plantilla pasando la opción :layout_engine. + +#### Plantillas CoffeeScript + + + + + + + + + + + + + + +
    Dependencias + + CoffeeScript + y un + + mecanismo para ejecutar javascript + +
    Extensiones de Archivo.coffee
    Ejemplocoffee :index
    + +#### Plantillas Stylus + + + + + + + + + + + + + + +
    Dependencias + + Stylus + y un + + mecanismo para ejecutar javascript + +
    Extensiones de Archivo.styl
    Ejemplostylus :index
    + +Antes de poder usar las plantillas de Stylus, necesitas cargar `stylus` y `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Plantillas Yajl + + + + + + + + + + + + + + +
    Dependenciasyajl-ruby
    Extensiones de Archivo.yajl
    Ejemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +El contenido de la plantilla se evalúa como código Ruby, y la variable `json` es convertida a JSON mediante `#to_json`. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Las opciones `:callback` y `:variable` se pueden utilizar para decorar el objeto renderizado: + +```ruby +var contenido = {"foo":"bar","baz":"qux"}; +present(contenido); +``` + +#### Plantillas WLang + + + + + + + + + + + + + + +
    Dependenciaswlang
    Extensiones de Archivo.wlang
    Ejemplowlang :index, :locals => { :clave => 'valor' }
    + +Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla WLang, casi siempre vas a querer pasarle locales. + +### Accediendo a Variables en Plantillas + +Las plantillas son evaluadas dentro del mismo contexto que los manejadores de +ruta. Las variables de instancia asignadas en los manejadores de ruta son +accesibles directamente por las plantillas: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nombre' +end +``` + +O es posible especificar un Hash de variables locales explícitamente: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.nombre', :locals => { :bar => foo } +end +``` + +Esto es usado típicamente cuando se renderizan plantillas como parciales desde +adentro de otras plantillas. + +### Plantillas con `yield` y `layout` anidado + +Un layout es usualmente una plantilla que llama a `yield`. +Dicha plantilla puede ser usada tanto a travé de la opción `:template` +como describimos arriba, o puede ser rederizada con un bloque como a +continuación: + +```ruby +erb :post, :layout => false do + erb :index +end +``` +Este código es principalmente equivalente a `erb :index, :layout => :post`. + +Pasar bloques a métodos de renderizado es la forma mas útil de crear layouts anidados: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Esto también se puede hacer en menos líneas de código con: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Actualmente, los siguientes métodos de renderizado aceptan un bloque: `erb`, `haml`, +`liquid`, `slim `, `wlang`. También el método general de `render` acepta un bloque. + +### Plantillas Inline + +Las plantillas pueden ser definidas al final del archivo fuente: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.titulo Hola Mundo +``` + +NOTA: Únicamente las plantillas inline definidas en el archivo fuente que +requiere Sinatra son cargadas automáticamente. Llamá `enable +:inline_templates` explícitamente si tienes plantillas inline en otros +archivos fuente. + +### Plantillas Nombradas + +Las plantillas también pueden ser definidas usando el método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.titulo Hola Mundo!' +end + +get '/' do + haml :index +end +``` + +Si existe una plantilla con el nombre "layout", va a ser usada cada vez que +una plantilla es renderizada.Puedes desactivar los layouts individualmente +pasando `:layout => false` o globalmente con +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Asociando Extensiones de Archivo + +Para asociar una extensión de archivo con un motor de renderizado, usa +`Tilt.register`. Por ejemplo, si quieres usar la extensión `tt` para +las plantillas Textile, puedes hacer lo siguiente: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Añadiendo Tu Propio Motor de Plantillas + +Primero, registra tu motor con Tilt, y después, creá tu método de renderizado: + +```ruby +Tilt.register :mipg, MiMotorDePlantilla + +helpers do + def mypg(*args) render(:mypg, *args) end +end + +get '/' do + mypg :index +end +``` + +Renderiza `./views/index.mypg`. Mirá https://github.com/rtomayko/tilt +para aprender más de Tilt. + +### Usando Lógica Personalizada para la Búsqueda en Plantillas + +Para implementar tu propio mecanismo de búsqueda de plantillas puedes +escribir tu propio método `#find_template` + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filtros + +Los filtros `before` son evaluados antes de cada petición dentro del mismo +contexto que las rutas. Pueden modificar la petición y la respuesta. Las +variables de instancia asignadas en los filtros son accesibles por las rutas y +las plantillas: + +```ruby +before do + @nota = 'Hey!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Hey!' + params['splat'] #=> 'bar/baz' +end +``` + +Los filtros `after` son evaluados después de cada petición dentro del mismo +contexto y también pueden modificar la petición y la respuesta. Las variables +de instancia asignadas en los filtros `before` y en las rutas son accesibles por +los filtros `after`: + +```ruby +after do + puts response.status +end +``` + +Nota: A menos que uses el método `body` en lugar de simplemente devolver un +string desde una ruta, el cuerpo de la respuesta no va a estar disponible en +un filtro after, debido a que todavía no se ha generado. + +Los filtros aceptan un patrón opcional, que cuando está presente causa que los +mismos sean evaluados únicamente si el path de la petición coincide con ese +patrón: + +```ruby +before '/protegido/*' do + autenticar! +end + +after '/crear/:slug' do |slug| + session[:ultimo_slug] = slug +end +``` + +Al igual que las rutas, los filtros también pueden aceptar condiciones: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'ejemplo.com' do + # ... +end +``` + +## Helpers + +Usá el método top-level `helpers` para definir métodos ayudantes (helpers) que +pueden ser utilizados dentro de los manejadores de rutas y las plantillas: + +```ruby +helpers do + def bar(nombre) + "#{nombre}bar" + end +end + +get '/:nombre' do + bar(params['nombre']) +end +``` + +Por cuestiones organizativas, puede resultar conveniente organizar los métodos +helpers en distintos módulos: + +```ruby +module FooUtils + def foo(nombre) "#{nombre}foo" end +end + +module BarUtils + def bar(nombre) "#{nombre}bar" end +end + +helpers FooUtils, BarUtils +``` + +El efecto de utilizar `helpers` de esta manera es el mismo que resulta de +incluir los módulos en la clase de la aplicación. + +### Usando Sesiones + +Una sesión es usada para mantener el estado a través de distintas peticiones. +Cuando están activadas, proporciona un hash de sesión para cada sesión de usuario: + +```ruby +enable :sessions + +get '/' do + "valor = " << session[:valor].inspect +end + +get '/:valor' do + session[:valor] = params['valor'] +end +``` + +#### Secreto de Sesión + +Para mejorar la seguridad, los datos de la sesión en la cookie se firman con un secreto usando `HMAC-SHA1`. El secreto de esta sesión debería ser de manera óptima +un valor aleatorio criptográficamente seguro de una longitud adecuada para +`HMAC-SHA1` que es mayor o igual que 64 bytes (512 bits, 128 hex caracteres). +Se le aconsejará que no use un secreto que sea inferior a 32 +bytes de aleatoriedad (256 bits, 64 caracteres hexadecimales). +Por lo tanto, es **muy importante** que no solo invente el secreto, +sino que use un generador de números aleatorios para crearlo. +Los humanos somos extremadamente malos generando valores aleatorios + +De forma predeterminada, un secreto de sesión aleatorio seguro de 32 bytes se genera para usted por +Sinatra, pero cambiará con cada reinicio de su aplicación. Si tienes varias +instancias de tu aplicación y dejas que Sinatra genere la clave, cada instancia +tendría una clave de sesión diferente y probablemente no es lo que quieres. + +Para una mejor seguridad y usabilidad es +[recomendado](https://12factor.net/config) que genere un secreto de sesión +aleatorio seguro y se guarde en las variables de entorno en cada host que ejecuta +su aplicación para que todas las instancias de su aplicación compartan el mismo +secreto. Debería rotar periódicamente esta sesión secreta a un nuevo valor. +Aquí hay algunos ejemplos de cómo puede crear un secreto de 64 bytes y configurarlo: + +**Generación de Secreto de Sesión** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Generación de Secreto de Sesión (Puntos Extras)** + +Usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) para preferir +el uso de el sistema RNG para generar valores aleatorios en lugar de +espacio de usuario `OpenSSL` que MRI Ruby tiene por defecto: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Secreto de Sesión en Variable de Entorno** + +Establezca una variable de entorno `SESSION_SECRET` para Sinatra en el valor que +generaste. Haz que este valor sea persistente durante los reinicios de su host. El +método para hacer esto variará a través de los sistemas operativos, esto es para +propósitos ilustrativos solamente: + + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Configuración de la Aplicación y su Secreto de Sesión** + +Configura tu aplicación a prueba de fallas si la variable de entorno +`SESSION_SECRET` no esta disponible + +Para puntos extras usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) acá tambien: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Configuración de Sesión + +Si desea configurarlo más, también puede almacenar un hash con opciones +en la configuración `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Para compartir su sesión en otras aplicaciones en subdominios de foo.com, agregue el prefijo +dominio con un *.* como este en su lugar: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Escogiendo tu Propio Middleware de Sesión + +Tenga en cuenta que `enable :sessions` en realidad almacena todos los datos en una cookie. +Esto no siempre podria ser lo que quieres (almacenar muchos datos aumentará su +tráfico, por ejemplo). Puede usar cualquier middleware de sesión proveniente de Rack +para hacerlo, se puede utilizar uno de los siguientes métodos: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +O para configurar sesiones con un hash de opciones: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Otra opción es **no** llamar a `enable :sessions`, sino +su middleware de elección como lo haría con cualquier otro middleware. + +Es importante tener en cuenta que al usar este método, la protección +de sesiones **no estará habilitada por defecto**. + +También será necesario agregar el middleware de Rack para hacer eso: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Mira '[Configurando la protección contra ataques](#configurando-la-protección-contra-ataques)' para mas información. + +### Interrupción + +Para detener inmediatamente una petición dentro de un filtro o una ruta debes usar: + +```ruby +halt +``` + +También puedes especificar el estado: + +```ruby +halt 410 +``` + +O el cuerpo: + +```ruby +halt 'esto va a ser el cuerpo' +``` + +O los dos: + +```ruby +halt 401, 'salí de acá!' +``` + +Con cabeceras: + +```ruby +halt 402, { 'Content-Type' => 'text/plain' }, 'venganza' +``` + +Obviamente, es posible utilizar `halt` con una plantilla: + +```ruby +halt erb(:error) +``` + +### Paso + +Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con +la petición usando `pass`: + +```ruby +get '/adivina/:quien' do + pass unless params['quien'] == 'Franco' + 'Adivinaste!' +end + +get '/adivina/*' do + 'Erraste!' +end +``` + +Se sale inmediatamente del bloque de la ruta y se le pasa el control a la +siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve 404. + +### Desencadenando Otra Ruta + +A veces, `pass` no es lo que quieres, sino obtener el +resultado de la llamada a otra ruta. Simplemente use `call` para lograr esto: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Nota que en el ejemplo anterior, es conveniente mover `"bar"` a un +helper, y llamarlo desde `/foo` y `/bar`. Así, vas a simplificar +las pruebas y a mejorar el rendimiento. + +Si quieres que la petición se envíe a la misma instancia de la aplicación en +lugar de otra, usá `call!` en lugar de `call`. + +En la especificación de Rack puedes encontrar más información sobre +`call`. + +### Configurando el Cuerpo, Código de Estado y los Encabezados + +Es posible, y se recomienda, asignar el código de estado y el cuerpo de una +respuesta con el valor de retorno de una ruta. Sin embargo, en algunos +escenarios puede que sea conveniente asignar el cuerpo en un punto arbitrario +del flujo de ejecución con el método helper `body`. A partir de ahí, puedes usar ese +mismo método para acceder al cuerpo de la respuesta: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +También es posible pasarle un bloque a `body`, que será ejecutado por el Rack +handler (puedes usar esto para implementar streaming, mira ["Valores de Retorno"](#valores-de-retorno)). + +De manera similar, también puedes asignar el código de estado y encabezados: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +También, al igual que `body`, `status` y `headers` sin agregarles argumentos pueden usarse +para acceder a sus valores actuales. + +### Streaming De Respuestas + +A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no +terminaste de generar su cuerpo. También es posible que, en algunos casos, +quieras seguir enviando información hasta que el cliente cierre la conexión. +Cuando esto ocurra, el helper `stream` te va a ser de gran ayuda: + +```ruby +get '/' do + stream do |out| + out << "Esto va a ser legen -\n" + sleep 0.5 + out << " (esperalo) \n" + sleep 1 + out << "- dario!\n" + end +end +``` + +Puedes implementar APIs de streaming, +[Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) y puede ser usado +como base para [WebSockets](https://es.wikipedia.org/wiki/WebSockets). También +puede ser usado para incrementar el throughput si solo una parte del contenido +depende de un recurso lento. + +Hay que tener en cuenta que el comportamiento del streaming, especialmente el +número de peticiones concurrentes, depende del servidor web utilizado para +alojar la aplicación. Puede que algunos servidores no soporten streaming +directamente, así el cuerpo de la respuesta será enviado completamente de una +vez cuando el bloque pasado a `stream` finalice su ejecución. Si estás usando +Shotgun, el streaming no va a funcionar. + +Cuando se pasa `keep_open` como parámetro, no se va a enviar el mensaje +`close` al objeto de stream. Permite que tu lo cierres en el punto de ejecución +que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es +posible solo en servidores que soporten eventos, como Thin o Rainbows. El +resto de los servidores van a cerrar el stream de todos modos: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # registrar a un cliente interesado en los eventos del servidor + stream(:keep_open) do |out| + connections << out + # purgar conexiones muertas + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notificar al cliente que ha llegado un nuevo mensaje + out << params['message'] << "\n" + + # indicar al cliente para conectarse de nuevo + out.close + end + + # reconocer + "message received" +end +``` + +También es posible que el cliente cierre la conexión cuando intenta +escribir en el socket. Debido a esto, se recomienda verificar con +`out.closed?` antes de intentar escribir. + +### Logging + +En el ámbito de la petición, el helper `logger` (registrador) expone +una instancia de `Logger`: + +```ruby +get '/' do + logger.info "cargando datos" + # ... +end +``` + +Este logger tiene en cuenta la configuración de logueo de tu Rack +handler. Si el logueo está desactivado, este método va a devolver un +objeto que se comporta como un logger pero que en realidad no hace +nada. Así, no vas a tener que preocuparte por esta situación. + +Ten en cuenta que el logueo está habilitado por defecto únicamente +para `Sinatra::Application`. Si heredaste de +`Sinatra::Base`, probablemente quieras habilitarlo manualmente: + +```ruby +class MiApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Para evitar que se inicialice cualquier middleware de logging, configurá +`logging` a `nil`. Ten en cuenta que, cuando hagas esto, `logger` va a +devolver `nil`. Un caso común es cuando quieres usar tu propio logger. Sinatra +va a usar lo que encuentre en `env['rack.logger']`. + +### Tipos Mime + +Cuando usás `send_file` o archivos estáticos tal vez tengas tipos mime +que Sinatra no entiende. Usá `mime_type` para registrarlos a través de la +extensión de archivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +También lo puedes usar con el helper `content_type`: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generando URLs + +Para generar URLs deberías usar el método `url`. Por ejemplo, en Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes. + +Este método también puede invocarse mediante su alias `to` (mirá un ejemplo +a continuación). + +### Redirección del Navegador + +Puedes redireccionar al navegador con el método `redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Cualquier parámetro adicional se utiliza de la misma manera que los argumentos +pasados a `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'te confundiste de lugar, compañero' +``` + +También puedes redireccionar fácilmente de vuelta hacia la página desde donde +vino el usuario con `redirect back`: + +```ruby +get '/foo' do + "hacer algo" +end + +get '/bar' do + hacer_algo + redirect back +end +``` + +Para pasar argumentos con una redirección, puedes agregarlos a la cadena de +búsqueda: + +```ruby +redirect to('/bar?suma=42') +``` + +O usar una sesión: + +```ruby +enable :sessions + +get '/foo' do + session[:secreto] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secreto] +end +``` + +### Control del Cache + +Asignar tus encabezados correctamente es el cimiento para realizar un cacheo +HTTP correcto. + +Puedes asignar el encabezado Cache-Control fácilmente: + +```ruby +get '/' do + cache_control :public + "cachealo!" +end +``` + +Pro tip: configurar el cacheo en un filtro `before`: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si estás usando el helper `expires` para definir el encabezado correspondiente, +`Cache-Control` se va a definir automáticamente: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar cachés adecuadamente, deberías considerar usar `etag` o +`last_modified`. Es recomendable que llames a estos asistentes *antes* de hacer +cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si +el cliente ya tiene la versión actual en su caché: + +```ruby +get '/articulo/:id' do + @articulo = Articulo.find params['id'] + last_modified @articulo.updated_at + etag @articulo.sha1 + erb :articulo +end +``` + +También es posible usar una +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @articulo.sha1, :weak +``` + +Estos helpers no van a cachear nada por vos, sino que van a facilitar la +información necesaria para poder hacerlo. Si estás buscando soluciones rápidas +de cacheo con proxys reversos, mirá +[rack-cache](https://github.com/rtomayko/rack-cache): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hola" +end +``` + +Usá la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` a archivos estáticos (ver la sección de configuración +para más detalles). + +De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las +cabeceras If-Match o If-None-Match se le asigna el valor `*` cuando el +recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get) +y potentes (como put) que el recurso existe, mientras que para el resto +(como post) asume que no. Puedes cambiar este comportamiento con la opción +`:new_resource`: + +```ruby +get '/crear' do + etag '', :new_resource => true + Articulo.create + erb :nuevo_articulo +end +``` + +Si quieres seguir usando una weak ETag, indicalo con la opción `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Archivos + +Para enviar archivos, puedes usar el método `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Además acepta un par de opciones: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Estas opciones son: + +
    +
    filename
    +
    Nombre del archivo devuelto, por defecto es el nombre real del archivo.
    + +
    last_modified
    +
    Valor para el encabezado Last-Modified, por defecto toma el mtime del archivo.
    + +
    type
    +
    El Content-Type que se va a utilizar, si no está presente se intenta + adivinar a partir de la extensión del archivo.
    + +
    disposition
    +
    + Se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los + siguientes valores: nil (por defecto), :attachment y :inline +
    + +
    length
    +
    Encabezado Content-Length, por defecto toma el tamaño del archivo
    + +
    status
    +
    + Código de estado a enviar. Útil cuando se envía un archivo estático como un error página. Si es compatible con el controlador de Rack, otros medios que no sean la transmisión del proceso de Ruby será utilizado. Si usas este método de ayuda, Sinatra manejará automáticamente las solicitudes de rango. +
    +
    + +### Accediendo al objeto Request + +El objeto de la petición entrante puede ser accedido desde el nivel de la +petición (filtros, rutas y manejadores de errores) a través del método +`request`: + +```ruby +# app corriendo en http://ejemplo.com/ejemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # cuerpo de la petición enviado por el cliente (ver más abajo) + request.scheme # "http" + request.script_name # "/ejemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # longitud de request.body + request.media_type # tipo de medio de request.body + request.host # "ejemplo.com" + request.get? # true (hay métodos análogos para los otros verbos) + request.form_data? # false + request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA + request.referrer # la referencia del cliente o '/' + request.user_agent # user agent (usado por la condición :agent) + request.cookies # hash de las cookies del navegador + request.xhr? # es una petición ajax? + request.url # "http://ejemplo.com/ejemplo/foo" + request.path # "/ejemplo/foo" + request.ip # dirección IP del cliente + request.secure? # false (sería true sobre ssl) + request.forwarded? # true (si se está corriendo atrás de un proxy reverso) + request.env # hash de entorno directamente entregado por Rack +end +``` + +Algunas opciones, como `script_name` o `path_info` pueden +también ser escritas: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas las peticiones llegan acá" +end +``` + +El objeto `request.body` es una instancia de IO o StringIO: + +```ruby +post "/api" do + request.body.rewind # en caso de que alguien ya lo haya leído + datos = JSON.parse request.body.read + "Hola #{datos['nombre']}!" +end +``` + +### Archivos Adjuntos + +Puedes usar el helper `attachment` para indicarle al navegador que +almacene la respuesta en el disco en lugar de mostrarla en pantalla: + +```ruby +get '/' do + attachment + "guardalo!" +end +``` + +También puedes pasarle un nombre de archivo: + +```ruby +get '/' do + attachment "info.txt" + "guardalo!" +end +``` + +### Fecha y Hora + +Sinatra pone a tu disposición el helper `time_for`, que genera un objeto `Time` +a partir del valor que recibe como argumento. Este valor puede ser un +`String`, pero también es capaz de convertir objetos `DateTime`, `Date` y de +otras clases similares: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "todavía hay tiempo" +end +``` + +Este método es usado internamente por métodos como `expires` y `last_modified`, +entre otros. Por lo tanto, es posible extender el comportamiento de estos +métodos sobreescribiendo `time_for` en tu aplicación: + +```ruby +helpers do + def time_for(value) + case value + when :ayer then Time.now - 24*60*60 + when :mañana then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ayer + expires :mañana + "hola" +end +``` + +### Buscando los Archivos de las Plantillas + +El helper `find_template` se utiliza para encontrar los archivos de las +plantillas que se van a renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |archivo| + puts "podría ser #{archivo}" +end +``` + +Si bien esto no es muy útil, lo interesante es que puedes sobreescribir este +método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para +poder utilizar más de un directorio de vistas: + +```ruby +set :views, ['vistas', 'plantillas'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Otro ejemplo consiste en usar directorios diferentes para los distintos motores +de renderizado: + +```ruby +set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:defecto] + super(folder, name, engine, &block) + end +end +``` + +¡Es muy fácil convertir estos ejemplos en una extensión y compartirla! + +Notá que `find_template` no verifica si un archivo existe realmente, sino +que llama al bloque que recibe para cada path posible. Esto no representa un +problema de rendimiento debido a que `render` va a usar `break` ni bien +encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y +su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno +tener en cuenta lo anterior si escribís un método extraño. + +## Configuración + +Ejecutar una vez, en el inicio, en cualquier entorno: + +```ruby +configure do + # asignando una opción + set :opcion, 'valor' + + # asignando varias opciones + set :a => 1, :b => 2 + + # atajo para `set :opcion, true` + enable :opcion + + # atajo para `set :opcion, false` + disable :opcion + + # también puedes tener configuraciones dinámicas usando bloques + set(:css_dir) { File.join(views, 'css') } +end +``` + +Ejecutar únicamente cuando el entorno (la variable de entorno APP_ENV) es +`:production`: + +```ruby +configure :production do + ... +end +``` + +Ejecutar cuando el entorno es `:production` o `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Puedes acceder a estas opciones utilizando el método `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configurando la Protección Contra Ataques + +Sinatra usa [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +para defender a tu aplicación de los ataques más comunes. Si por algún motivo, +quieres desactivar esta funcionalidad, puedes hacerlo como se indica a +continuación (ten en cuenta que tu aplicación va a quedar expuesta a un +montón de vulnerabilidades bien conocidas): + +```ruby +disable :protection +``` + +También es posible desactivar una única capa de defensa: + +```ruby +set :protection, :except => :path_traversal +``` + +O varias: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +### Configuraciones Disponibles + +
    +
    absolute_redirects
    +
    + Si está deshabilitada, Sinatra va a permitir + redirecciones relativas, sin embargo, como consecuencia + de esto, va a dejar de cumplir con el RFC 2616 (HTTP + 1.1), que solamente permite redirecciones absolutas. +
    +
    + Activalo si tu apliación está corriendo atrás de un proxy + reverso que no se ha configurado adecuadamente. Notá que + el helper url va a seguir produciendo URLs absolutas, a + menos que le pasés false como segundo parámetro. +
    +
    Deshabilitada por defecto.
    + + +
    add_charset
    +
    + Tipos mime a los que el helper content_type les + añade automáticamente el charset. En general, no deberías + asignar directamente esta opción, sino añadirle los charsets + que quieras: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path del archivo principal de la aplicación, se utiliza + para detectar la raíz del proyecto, el directorio de las + vistas y el público, así como las plantillas inline. +
    + +
    bind
    +
    + Dirección IP que utilizará el servidor integrado (por + defecto: 0.0.0.0 o localhost + si su `environment` está configurado para desarrollo). +
    + +
    default_encoding
    +
    + Encoding utilizado cuando el mismo se desconoce (por + defecto "utf-8"). +
    + +
    dump_errors
    +
    + Mostrar errores en el log. +
    + +
    environment
    +
    + Entorno actual, por defecto toma el valor de + ENV['APP_ENV'], o "development" si no + está disponible. +
    + +
    logging
    +
    + Define si se utiliza el logger. +
    + +
    lock
    +
    + Coloca un lock alrededor de cada petición, procesando + solamente una por proceso. +
    +
    + Habilitá esta opción si tu aplicación no es thread-safe. + Se encuentra deshabilitada por defecto. +
    + +
    method_override
    +
    + Utiliza el parámetro _method para permtir + formularios put/delete en navegadores que no los + soportan. +
    + +
    mustermann_opts
    +
    + Un hash predeterminado de opciones para pasar a Mustermann.new + al compilar las rutas. +
    + +
    port
    +
    + Puerto en el que escuchará el servidor integrado. +
    + +
    prefixed_redirects
    +
    + Define si inserta request.script_name en las + redirecciones cuando no se proporciona un path absoluto. + De esta manera, cuando está habilitada, + redirect '/foo' se comporta de la misma manera + que redirect to('/foo'). Se encuentra + deshabilitada por defecto. +
    + +
    protection
    +
    + Define si se habilitan o no las protecciones de ataques web. + Ver la sección de protección encima. +
    + +
    public_folder
    +
    + Lugar del directorio desde donde se sirven los archivos + públicos. Solo se utiliza cuando se sirven archivos + estáticos (ver la opción static). Si no + está presente, se infiere del valor de la opción + app_file. +
    + +
    quiet
    +
    + Inhabilita los logs generados por los comandos de inicio y detención de Sinatra. + false por defecto. +
    + +
    reload_templates
    +
    + Define Si se vuelven a cargar plantillas entre las solicitudes o no. Habilitado en + modo de desarrollo. +
    + +
    root
    +
    + Lugar del directorio raíz del proyecto. Si no está + presente, se infiere del valor de la opción + app_file. +
    + +
    raise_errors
    +
    + Elevar excepciones (detiene la aplicación). Se + encuentra activada por defecto cuando el valor de + environment es "test". En caso + contrario estará desactivada. +
    + +
    run
    +
    + Cuando está habilitada, Sinatra se va a encargar de + iniciar el servidor web, no la habilites cuando estés + usando rackup o algún otro medio. +
    + +
    running
    +
    + Indica si el servidor integrado está ejecutándose, ¡no + cambiés esta configuración!. +
    + +
    server
    +
    + Servidor, o lista de servidores, para usar como servidor + integrado. Por defecto: ['thin', 'mongrel', 'webrick'], + el orden establece la prioridad. +
    + +
    server_settings
    +
    + Si está utilizando un servidor web WEBrick, presumiblemente para su entorno de desarrollo, puede pasar un hash de opciones a server_settings , como SSLEnable o SSLVerifyClient . Sin embargo, los servidores web como Puma y Thin no son compatibles, por lo que puede establecer server_settings definiéndolo como un método cuando llame a configure . +
    + +
    sessions
    +
    + Habilita el soporte de sesiones basadas en cookies a + través de Rack::Session::Cookie. Ver la + sección 'Usando Sesiones' para más información. +
    + +
    session_store
    +
    +Define el middleware de sesión Rack utilizado. Predeterminado a +Rack::Session::Cookie. Consulte la sección 'Uso de sesiones' para obtener más información. +información. +
    + +
    show_exceptions
    +
    + Muestra un stack trace en el navegador cuando ocurre una + excepción. Se encuentra activada por defecto cuando el + valor de environment es "development". + En caso contrario estará desactivada. +
    +
    + También se puede establecer en :after_handler para activar la aplicación especificada + que hara el manejo de errores antes de mostrar un stack trace en el navegador. +
    + +
    static
    +
    +
    Define si Sinatra debe servir los archivos estáticos.
    +
    Deshabilitar cuando se utiliza un servidor capaz de hacer esto por su cuenta.
    +
    La desactivación aumentará el rendimiento.
    +
    + Habilitado por defecto en estilo clásico, deshabilitado para aplicaciones modulares. +
    + + +
    static_cache_control
    +
    + Cuando Sinatra está sirviendo archivos estáticos, y + esta opción está habilitada, les va a agregar encabezados + Cache-Control a las respuestas. Para esto + utiliza el helper cache_control. Se encuentra + deshabilitada por defecto. Notar que es necesario + utilizar un array cuando se asignan múltiples valores: + set :static_cache_control, [:public, :max_age => 300]. +
    + +
    threaded
    +
    +Si se establece en true , le dirá a Thin que use + EventMachine.defer para procesar la solicitud. +
    + +
    traps
    +
    Define si Sinatra debería manejar las señales del sistema.
    + +
    views
    +
    + Path del directorio de las vistas. Si no está presente, + se infiere del valor de la opción app_file. +
    +
    x_cascade
    +
    + Establece o no el encabezado de X-Cascade si no hay una coincidencia de ruta. + verdadero por defecto. +
    +
    + +## Entornos + +Existen tres entornos (`environments`) predefinidos: `development`, +`production` y `test`. El entorno por defecto es +`development` y tiene algunas particularidades: + +* Se recargan las plantillas entre una petición y la siguiente, a diferencia +de `production` y `test`, donde se cachean. +* Se instalan manejadores de errores `not_found` y `error` +especiales que muestran un stack trace en el navegador cuando son disparados. + +Para utilizar alguno de los otros entornos puede asignarse el valor +correspondiente a la variable de entorno `APP_ENV`: +```shell +APP_ENV=production ruby my_app.rb +``` + +Los métodos `development?`, `test?` y `production?` te permiten conocer el +entorno actual. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Manejo de Errores + +Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas +y los filtros `before`, lo que significa que puedes usar, por ejemplo, +`haml`, `erb`, `halt`, etc. + +### Not Found + +Cuando se eleva una excepción `Sinatra::NotFound`, o el código de +estado de la respuesta es 404, el manejador `not_found` es invocado: + +```ruby +not_found do + 'No existo' +end +``` + +### Error + +El manejador `error` es invocado cada vez que una excepción es elevada +desde un bloque de ruta o un filtro. El objeto de la excepción se puede +obtener de la variable Rack `sinatra.error`: + +```ruby +error do + 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].message +end +``` + +Errores personalizados: + +```ruby +error MiErrorPersonalizado do + 'Lo que pasó fue...' + env['sinatra.error'].message +end +``` + +Entonces, si pasa esto: + +```ruby +get '/' do + raise MiErrorPersonalizado, 'algo malo' +end +``` + +Obtienes esto: + +``` + Lo que pasó fue... algo malo +``` + +También, puedes instalar un manejador de errores para un código de estado: + +```ruby +error 403 do + 'Acceso prohibido' +end + +get '/secreto' do + 403 +end +``` + +O un rango: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra instala manejadores `not_found` y `error` especiales +cuando se ejecuta dentro del entorno de desarrollo "development" y se muestran +en tu navegador para que tengas información adicional sobre el error. + +## Rack Middleware + +Sinatra corre sobre [Rack](http://rack.github.io/), una interfaz minimalista +que es un estándar para frameworks webs escritos en Ruby. Una de las +características más interesantes de Rack para los desarrolladores de aplicaciones +es el soporte de "middleware" -- componentes que se ubican entre el servidor y +tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para +proporcionar varios tipos de funcionalidades comunes. + +Sinatra hace muy sencillo construir tuberías de Rack middleware a través del +método top-level `use`: + +```ruby +require 'sinatra' +require 'mi_middleware_personalizado' + +use Rack::Lint +use MiMiddlewarePersonalizado + +get '/hola' do + 'Hola Mundo' +end +``` + +La semántica de `use` es idéntica a la definida para el DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) (más +frecuentemente usado en archivos rackup). Por ejemplo, el método `use` +acepta argumentos múltiples/variables así como bloques: + +```ruby +use Rack::Auth::Basic do |nombre_de_usuario, password| + nombre_de_usuario == 'admin' && password == 'secreto' +end +``` + +Rack es distribuido con una variedad de middleware estándar para logging, +debugging, enrutamiento URL, autenticación y manejo de sesiones. Sinatra +usa muchos de estos componentes automáticamente de acuerdo a su configuración +para que usualmente no tengas que usarlas (con `use`) explícitamente. + +Puedes encontrar middleware útil en +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +o en la [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Pruebas + +Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando +cualquier framework o librería de pruebas basada en Rack. Se recomienda usar +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'mi_app_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MiAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_mi_defecto + get '/' + assert_equal 'Hola Mundo!', last_response.body + end + + def test_con_parametros + get '/saludar', :name => 'Franco' + assert_equal 'Hola Frank!', last_response.body + end + + def test_con_entorno_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Estás usando Songbird!", last_response.body + end +end +``` + +Nota: Si está utilizando Sinatra en el estilo modular, reemplace +`Sinatra::Application` anterior con el nombre de clase de su aplicación. + +## Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares + +Definir tu aplicación en el nivel superior funciona bien para micro-aplicaciones +pero trae inconvenientes considerables a la hora de construir componentes +reutilizables como Rack middleware, Rails metal, librerías simples con un +componente de servidor o incluso extensiones de Sinatra. El DSL de alto nivel +asume una configuración apropiada para micro-aplicaciones (por ejemplo, un +único archivo de aplicación, los directorios `./public` y +`./views`, logging, página con detalles de excepción, etc.). Ahí es +donde `Sinatra::Base` entra en el juego: + +```ruby +require 'sinatra/base' + +class MiApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hola Mundo!' + end +end +``` + +Las subclases de `Sinatra::Base` tienen disponibles exactamente los +mismos métodos que los provistos por el DSL de top-level. La mayoría de las +aplicaciones top-level se pueden convertir en componentes +`Sinatra::Base` con dos modificaciones: + +* Tu archivo debe requerir `sinatra/base` en lugar de `sinatra`; de otra + manera, todos los métodos del DSL de sinatra son importados dentro del + espacio de nombres principal. +* Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación + en una subclase de `Sinatra::Base`. + +`Sinatra::Base` es una pizarra en blanco. La mayoría de las opciones están +desactivadas por defecto, incluyendo el servidor incorporado. Mirá +[Opciones y Configuraciones](http://www.sinatrarb.com/configuration.html) +para detalles sobre las opciones disponibles y su comportamiento. +Si quieres un comportamiento más similar +a cuando defines tu aplicación en el nivel superior (también conocido como Clásico) +estilo), puede subclase `Sinatra::Aplicación` + +```ruby +require 'sinatra/base' + +class MiAplicacion < Sinatra::Application + get '/' do + 'Hola Mundo!' + end +end +``` + +### Estilo Modular vs. Clásico + +Contrariamente a la creencia popular, no hay nada de malo con el estilo clásico. +Si se ajusta a tu aplicación, no es necesario que la cambies a una modular. + +La desventaja de usar el estilo clásico en lugar del modular consiste en que +solamente puedes tener una aplicación Sinatra por proceso Ruby. Si tienes +planificado usar más, cambiá al estilo modular. Al mismo tiempo, ten en +cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos +clásico y modular. + +A continuación se detallan las diferencias (sútiles) entre las configuraciones +de ambos estilos: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConfiguraciónClásicaModularModular
    app_filearchivo que carga sinatraarchivo con la subclase de Sinatra::Basearchivo con la subclase Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Sirviendo una Aplicación Modular + +Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla +activamente con `run!`: + +```ruby +# mi_app.rb +require 'sinatra/base' + +class MiApp < Sinatra::Base + # ... código de la app ... + + # iniciar el servidor si el archivo fue ejecutado directamente + run! if app_file == $0 +end +``` + +Iniciar con: + +```shell +ruby mi_app.rb +``` + +O, con un archivo `config.ru`, que permite usar cualquier handler Rack: + +```ruby +# config.ru +require './mi_app' +run MiApp +``` + +Después ejecutar: + +```shell +rackup -p 4567 +``` + +### Usando una Aplicación Clásica con un Archivo config.ru + +Escribí el archivo de tu aplicación: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Y el `config.ru` correspondiente: + +```ruby +require './app' +run Sinatra::Application +``` + +### ¿Cuándo usar config.ru? + +Indicadores de que probablemente quieres usar `config.ru`: + +* Quieres realizar el deploy con un handler Rack distinto (Passenger, Unicorn, + Heroku, ...). +* Quieres usar más de una subclase de `Sinatra::Base`. +* Quieres usar Sinatra únicamente para middleware, pero no como un endpoint. + +No hay necesidad de utilizar un archivo `config.ru` exclusivamente +porque tienes una aplicación modular, y no necesitás una aplicación modular para +iniciarla con `config.ru`. + +### Utilizando Sinatra como Middleware + +Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, +cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack +como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier +aplicación basada en Rack (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class PantallaDeLogin < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['nombre'] == 'admin' && params['password'] == 'admin' + session['nombre_de_usuario'] = params['nombre'] + else + redirect '/login' + end + end +end + +class MiApp < Sinatra::Base + # el middleware se ejecutará antes que los filtros + use PantallaDeLogin + + before do + unless session['nombre_de_usuario'] + halt "Acceso denegado, por favor iniciá sesión." + end + end + + get('/') { "Hola #{session['nombre_de_usuario']}." } +end +``` + +### Creación Dinámica de Aplicaciones + +Puede que en algunas ocasiones quieras crear nuevas aplicaciones en +tiempo de ejecución sin tener que asignarlas a una constante. Para +esto tienes `Sinatra.new`: + +```ruby +require 'sinatra/base' +mi_app = Sinatra.new { get('/') { "hola" } } +mi_app.run! +``` + +Acepta como argumento opcional una aplicación desde la que se +heredará: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MisHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Construir aplicaciones de esta forma resulta especialmente útil para +testear extensiones Sinatra o para usar Sinatra en tus librerías. + +Por otro lado, hace extremadamente sencillo usar Sinatra como +middleware: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run ProyectoRails::Application +``` + +## Ámbitos y Ligaduras + +El ámbito en el que te encuentras determina que métodos y variables están +disponibles. + +### Ámbito de Aplicación/Clase + +Cada aplicación Sinatra es una subclase de `Sinatra::Base`. Si estás +usando el DSL de top-level (`require 'sinatra'`), entonces esta clase es +`Sinatra::Application`, de otra manera es la subclase que creaste +explícitamente. Al nivel de la clase tienes métodos como `get` o `before`, pero +no puedes acceder a los objetos `request` o `session`, ya que hay una única +clase de la aplicación para todas las peticiones. + +Las opciones creadas utilizando `set` son métodos al nivel de la clase: + +```ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ya no estoy en el ámbito de la aplicación! + end +end +``` + +Tienes la ligadura al ámbito de la aplicación dentro de: + +* El cuerpo de la clase de tu aplicación +* Métodos definidos por extensiones +* El bloque pasado a `helpers` +* Procs/bloques usados como el valor para `set` +* El bloque pasado a `Sinatra.new` + +Este ámbito puede alcanzarse de las siguientes maneras: + +* A través del objeto pasado a los bloques de configuración (`configure { |c| ...}`) +* Llamando a `settings` desde dentro del ámbito de la petición + +### Ámbito de Petición/Instancia + +Para cada petición entrante, una nueva instancia de la clase de tu aplicación +es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este +ámbito puedes acceder a los objetos `request` y `session` o llamar a los métodos +de renderización como `erb` o `haml`. Puedes acceder al ámbito de la aplicación +desde el ámbito de la petición utilizando `settings`: + +```ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + get '/definir_ruta/:nombre' do + # Ámbito de petición para '/definir_ruta/:nombre' + @valor = 42 + + settings.get("/#{params['nombre']}") do + # Ámbito de petición para "/#{params['nombre']}" + @valor # => nil (no es la misma petición) + end + + "Ruta definida!" + end +end +``` + +Tienes la ligadura al ámbito de la petición dentro de: + +* bloques pasados a get, head, post, put, delete, options, patch, link y unlink +* filtros before/after +* métodos helpers +* plantillas/vistas + +### Ámbito de Delegación + +El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier +manera, no se comporta 100% como el ámbito de clase porque no tienes la ligadura +de la clase: únicamente métodos marcados explícitamente para delegación están +disponibles y no compartís variables/estado con el ámbito de clase (léase: +tienes un `self` diferente). Puedes agregar delegaciones de método llamando a +`Sinatra::Delegator.delegate :nombre_del_metodo`. + +Tienes és la ligadura al ámbito de delegación dentro de: + +* La ligadura del top-level, si hiciste `require "sinatra"` +* Un objeto extendido con el mixin `Sinatra::Delegator` + +Hechale un vistazo al código: acá está el +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +que [extiende el objeto main](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Línea de Comandos + +Las aplicaciones Sinatra pueden ser ejecutadas directamente: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Las opciones son: + +``` +-h # ayuda +-p # asigna el puerto (4567 es usado por defecto) +-o # asigna el host (0.0.0.0 es usado por defecto) +-e # asigna el entorno (development es usado por defecto) +-s # especifica el servidor/manejador rack (thin es usado por defecto) +-q # activar el modo silecioso para el servidor (está desactivado por defecto) +-x # activa el mutex lock (está desactivado por defecto) +``` + +### Multi-threading + +_Basado en [esta respuesta en StackOverflow](http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) escrita por Konstantin_ + +Sinatra no impone ningún modelo de concurrencia, sino que lo deja en manos del +handler Rack que se esté usando (Thin, Puma, WEBrick). Sinatra en sí mismo es +thread-safe, así que no hay problema en que el Rack handler use un modelo de +concurrencia basado en hilos. + +Esto significa que, cuando estemos arrancando el servidor, tendríamos que +especificar la opción adecuada para el handler Rack específico. En este ejemplo +vemos cómo arrancar un servidor Thin multihilo: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "¡Hola, Mundo!" + end +end + +App.run! +``` + +Para arrancar el servidor, el comando sería: + +```shell +thin --threaded start +``` + +## Requerimientos + +Las siguientes versiones de Ruby son soportadas oficialmente: + +
    +
    Ruby 2.3
    +
    + 2.3 Es totalmente compatible y recomendado. Actualmente no hay planes + soltar el apoyo oficial para ello. +
    + +
    Rubinius
    +
    + Rubinius es oficialmente compatible (Rubinius> = 2.x). Se recomienda instalar la gema puma + gem install puma. +
    + +
    JRuby
    +
    + La última versión estable de JRuby es oficialmente compatible. No lo es + recomienda usar extensiones C con JRuby. Se recomienda instalar la gema trinidad + gem install trinidad . +
    +
    + +Las versiones de Ruby anteriores a 2.2.2 ya no son compatibles con Sinatra 2.0 . + +Siempre le prestamos atención a las nuevas versiones de Ruby. + +Las siguientes implementaciones de Ruby no se encuentran soportadas +oficialmente. De cualquier manera, pueden ejecutar Sinatra: + +* Versiones anteriores de JRuby y Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev e IronRuby +* Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los uses) + +No ser soportada oficialmente, significa que si las cosas se rompen +ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino +el suyo. + +También ejecutamos nuestro CI contra ruby-head (futuras versiones de MRI), pero +no puede garantizar nada, ya que se mueve constantemente. Esperar próxima +2.x versiones para ser totalmente compatibles. + +Sinatra debería trabajar en cualquier sistema operativo compatible la implementación de Ruby +elegida + +Si ejecuta MacRuby, debe `gem install control_tower`. + +Sinatra actualmente no funciona con Cardinal, SmallRuby, BlueRuby o cualquier +versión de Ruby anterior a 2.2. + +## A la Vanguardia + +Si quieres usar el código de Sinatra más reciente, sientete libre de ejecutar +tu aplicación sobre la rama master, en general es bastante estable. + +También liberamos prereleases de vez en cuando, así, puedes hacer: + +```shell +gem install sinatra --pre +``` + +Para obtener algunas de las últimas características. + +### Usando Bundler + +Esta es la manera recomendada para ejecutar tu aplicación sobre la última +versión de Sinatra usando [Bundler](http://bundler.io). + +Primero, instala Bundler si no lo hiciste todavía: + +```shell +gem install bundler +``` + +Después, en el directorio de tu proyecto, creá un archivo `Gemfile`: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# otras dependencias +gem 'haml' # por ejemplo, si usás haml +``` + +Ten en cuenta que tienes que listar todas las dependencias directas de tu +aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) +porque Bundler las agrega directamente. + +Ahora puedes arrancar tu aplicación así: + +```shell +bundle exec ruby miapp.rb +``` + +## Versionado + +Sinatra utiliza el [Versionado Semántico](http://semver.org/), +siguiendo las especificaciones SemVer y SemVerTag. + +## Lecturas Recomendadas + +* [Sito web del proyecto](http://www.sinatrarb.com/) - Documentación + adicional, noticias, y enlaces a otros recursos. +* [Contribuyendo](http://www.sinatrarb.com/contributing) - ¿Encontraste un + error?. ¿Necesitas ayuda?. ¿Tienes un parche?. +* [Seguimiento de problemas](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Lista de Correo](http://groups.google.com/group/sinatrarb/topics) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) en http://freenode.net +* [Sinatra & Friends](https://sinatrarb.slack.com) en Slack y revisa + [acá](https://sinatra-slack.herokuapp.com/) Para una invitación. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutorial (en inglés). +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Recetas contribuidas + por la comunidad (en inglés). +* Documentación de la API para la + [última versión liberada](http://www.rubydoc.info/gems/sinatra) o para la + [rama de desarrollo actual](http://www.rubydoc.info/github/sinatra/sinatra) + en http://www.rubydoc.info/ +* [Servidor de CI](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.fr.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.fr.md new file mode 100644 index 0000000000..68c0e0aa35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.fr.md @@ -0,0 +1,3111 @@ +# Sinatra +*Attention : Ce document correspond à la traduction de la version anglaise et +il n'est peut-être plus à jour.* + +Sinatra est un [DSL](https://fr.wikipedia.org/wiki/Langage_dédié) pour +créer rapidement et facilement des applications web en Ruby : + +```ruby +# mon_application.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Installez la gem Sinatra : + +```shell +gem install sinatra +``` + +Puis lancez votre programme : + +```shell +ruby mon_application.rb +``` + +Le résultat est visible sur : [http://localhost:4567](http://localhost:4567) + +Le code que vous avez modifié ne sera pas pris en compte tant que vous ne +redémarrerez pas le serveur. Pensez à redémarrer le serveur à chaque +modification ou utilisez +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Il est recommandé d'exécuter également `gem install thin`, pour que +Sinatra utilise le server Thin quand il est disponible. + +## Table des matières + +* [Sinatra](#sinatra) + * [Table des matières](#table-des-matières) + * [Routes](#routes) + * [Conditions](#conditions) + * [Valeurs de retour](#valeurs-de-retour) + * [Masques de route spécifiques](#masques-de-route-spécifiques) + * [Fichiers statiques](#fichiers-statiques) + * [Vues / Templates](#vues--templates) + * [Templates littéraux](#templates-littéraux) + * [Langages de template disponibles](#langages-de-template-disponibles) + * [Templates Haml](#templates-haml) + * [Templates Erb](#templates-erb) + * [Templates Builder](#templates-builder) + * [Templates Nokogiri](#templates-nokogiri) + * [Templates Sass](#templates-sass) + * [Templates SCSS](#templates-scss) + * [Templates Less](#templates-less) + * [Templates Liquid](#templates-liquid) + * [Templates Markdown](#templates-markdown) + * [Templates Textile](#templates-textile) + * [Templates RDoc](#templates-rdoc) + * [Templates Asciidoc](#templates-asciidoc) + * [Templates Radius](#templates-radius) + * [Templates Markaby](#templates-markaby) + * [Templates RABL](#templates-rabl) + * [Templates Slim](#templates-slim) + * [Templates Creole](#templates-creole) + * [Templates MediaWiki](#templates-mediawiki) + * [Templates CoffeeScript](#templates-coffeescript) + * [Templates Stylus](#templates-stylus) + * [Templates Yajl](#templates-yajl) + * [Templates WLang](#templates-wlang) + * [Accéder aux variables dans un Template](#accéder-aux-variables-dans-un-template) + * [Templates avec `yield` et layouts imbriqués](#templates-avec-yield-et-layouts-imbriqués) + * [Templates dans le fichier source](#templates-dans-le-fichier-source) + * [Templates nommés](#templates-nommés) + * [Associer des extensions de fichier](#associer-des-extensions-de-fichier) + * [Ajouter son propre moteur de rendu](#ajouter-son-propre-moteur-de-rendu) + * [Utiliser des règles personnalisées pour la recherche de templates](#utiliser-des-règles-personnalisées-pour-la-recherche-de-templates) + * [Filtres](#filtres) + * [Helpers](#helpers) + * [Utiliser les sessions](#utiliser-les-sessions) + * [Halt](#halt) + * [Passer](#passer) + * [Déclencher une autre route](#déclencher-une-autre-route) + * [Définir le corps, le code retour et les en-têtes](#définir-le-corps-le-code-retour-et-les-en-têtes) + * [Faire du streaming](#faire-du-streaming) + * [Journalisation (Logging)](#journalisation-logging) + * [Types Mime](#types-mime) + * [Former des URLs](#former-des-urls) + * [Redirection du navigateur](#redirection-du-navigateur) + * [Contrôle du cache](#contrôle-du-cache) + * [Envoyer des fichiers](#envoyer-des-fichiers) + * [Accéder à l'objet requête](#accéder-à-lobjet-requête) + * [Fichiers joints](#fichiers-joints) + * [Gérer Date et Time](#gérer-date-et-time) + * [Chercher les fichiers de templates](#chercher-les-fichiers-de-templates) + * [Configuration](#configuration) + * [Se protéger des attaques](#se-protéger-des-attaques) + * [Paramètres disponibles](#paramètres-disponibles) + * [Environnements](#environnements) + * [Gérer les erreurs](#gérer-les-erreurs) + * [NotFound](#notfound) + * [Error](#error) + * [Les Middlewares Rack](#les-middlewares-rack) + * [Tester](#tester) + * [Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires](#sinatrabase---les-middlewares-bibliothèques-et-applications-modulaires) + * [Style modulaire vs. style classique](#style-modulaire-vs-style-classique) + * [Servir une application modulaire](#servir-une-application-modulaire) + * [Utiliser une application de style classique avec un fichier config.ru](#utiliser-une-application-de-style-classique-avec-un-fichier-configru) + * [Quand utiliser un fichier config.ru ?](#quand-utiliser-un-fichier-configru-) + * [Utiliser Sinatra comme Middleware](#utiliser-sinatra-comme-middleware) + * [Création dynamique d'applications](#création-dynamique-dapplications) + * [Contextes et Binding](#contextes-et-binding) + * [Contexte de l'application/classe](#contexte-de-lapplicationclasse) + * [Contexte de la requête/instance](#contexte-de-la-requêteinstance) + * [Le contexte de délégation](#le-contexte-de-délégation) + * [Ligne de commande](#ligne-de-commande) + * [Multi-threading](#multi-threading) + * [Configuration nécessaire](#configuration-nécessaire) + * [Essuyer les plâtres](#essuyer-les-plâtres) + * [Installer avec Bundler](#installer-avec-bundler) + * [Faire un clone local](#faire-un-clone-local) + * [Installer globalement](#installer-globalement) + * [Versions](#versions) + * [Mais encore](#mais-encore) + +## Routes + +Dans Sinatra, une route est une méthode HTTP couplée à un masque (pattern) +URL. Chaque route est associée à un bloc : + +```ruby +get '/' do + .. montrer quelque chose .. +end + +post '/' do + .. créer quelque chose .. +end + +put '/' do + .. remplacer quelque chose .. +end + +patch '/' do + .. changer quelque chose .. +end + +delete '/' do + .. effacer quelque chose .. +end + +options '/' do + .. paramétrer quelque chose .. +end + +link '/' do + .. relier quelque chose .. +end + +unlink '/' do + .. séparer quelque chose .. +end +``` + +Les routes sont évaluées dans l'ordre où elles ont été définies. La première +route qui correspond à la requête est appelée. + +Les routes se terminant par un slash sont différentes de celles qui n'en +comportent pas : + +```ruby +get '/foo' do + # Ne correspond pas à "GET /foo/" +end +``` + +Les masques peuvent inclure des paramètres nommés, accessibles par +l'intermédiaire du hash `params` : + +```ruby +get '/bonjour/:nom' do + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + "Bonjour #{params['nom']} !" +end +``` + +Vous pouvez aussi accéder aux paramètres nommés directement grâce aux +paramètres du bloc comme ceci : + +```ruby +get '/bonjour/:nom' do |n| + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + # n contient params['nom'] + "Bonjour #{n} !" +end +``` + +Une route peut contenir un `splat` (caractère joker), accessible par +l'intermédiaire du tableau `params['splat']` : + +```ruby +get '/dire/*/a/*' do + # répond à /dire/bonjour/a/monde + params['splat'] # => ["bonjour", "monde"] +end + +get '/telecharger/*.*' do + # répond à /telecharger/chemin/vers/fichier.xml + params['splat'] # => ["chemin/vers/fichier", "xml"] +end +``` + +Ou par l'intermédiaire des paramètres du bloc : + +```ruby +get '/telecharger/*.*' do |chemin, ext| + [chemin, ext] # => ["path/to/file", "xml"] +end +``` + +Une route peut aussi être définie par une expression régulière : + +```ruby +get /\/bonjour\/([\w]+)/ do + "Bonjour, #{params['captures'].first} !" +end +``` + +Là encore on peut utiliser les paramètres de bloc : + +```ruby +get %r{/bonjour/([\w]+)} do |c| + # répond à "GET /meta/bonjour/monde", "GET /bonjour/monde/1234" etc. + "Bonjour, #{c} !" +end +``` + +Les routes peuvent aussi comporter des paramètres optionnels : + +```ruby +get '/articles/:format?' do + # répond à "GET /articles/" ou avec une extension "GET /articles/json", "GET /articles/xml" etc... +end +``` + +Ainsi que des paramètres d'URL : + +```ruby +get '/articles' do + # répond à "GET /articles?titre=foo&auteur=bar" + titre = params['titre'] + auteur = params['auteur'] + # utilise les variables titre et auteur qui sont des paramètres d'URL optionnels pour la route /articles +end +``` + +À ce propos, à moins d'avoir désactivé la protection contre les attaques par +"path transversal" (voir plus loin), l'URL demandée peut avoir été modifiée +avant d'être comparée à vos routes. + +Vous pouvez personnaliser les options [Mustermann](https://github.com/sinatra/mustermann#readme) +utilisées pour une route donnée en fournissant un hash `:mustermann_opts` : + +```ruby +get '\A/articles\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # répond exactement à /articles, avec un ancrage explicite + "Si tu réponds à un pattern ancré tape dans tes mains ! +end +``` + +Cela ressemble à une [condition](#conditions), mais ce n'en est pas une ! +Ces options seront mergées dans le hash global `:mustermann_opts` décrit +[plus bas](#paramètres-disponibles). + +## Conditions + +Les routes peuvent définir toutes sortes de conditions, comme par exemple le +"user agent" : + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Vous utilisez Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Correspond à tous les autres navigateurs +end +``` + +Les autres conditions disponibles sont `host_name` et `provides` : + +```ruby +get '/', :host_name => /^admin\./ do + "Zone Administrateur, Accès refusé !" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` se base sur l'en-tête `Accept` de la requête. + +Vous pouvez facilement définir vos propres conditions : + +```ruby +set(:chance) { |valeur| condition { rand <= valeur } } + +get '/gagner_une_voiture', :chance => 0.1 do + "Vous avez gagné !" +end + +get '/gagner_une_voiture' do + "Désolé, vous avez perdu." +end +``` + +Utilisez un `splat` (caractère joker) dans le cas d'une condition qui prend +plusieurs valeurs : + +```ruby +set(:auth) do |*roles| # <- ici on utilise un splat + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mon/compte/", :auth => [:user, :admin] do + "Informations sur votre compte" +end + +get "/reserve/aux/admins/", :auth => :admin do + "Seuls les administrateurs sont acceptés ici !" +end +``` + +## Valeurs de retour + +La valeur renvoyée par le bloc correspondant à une route constitue le corps de +la réponse qui sera transmise au client HTTP ou du moins au prochain `middleware` +dans la pile Rack. Le plus souvent, il s'agit d'une chaîne de caractères, +comme dans les exemples précédents. Cependant, d'autres valeurs sont +acceptées. + +Vous pouvez renvoyer n'importe quel objet qu'il s'agisse d'une réponse Rack +valide, d'un corps de réponse Rack ou d'un code statut HTTP : + +* Un tableau de 3 éléments : `[code statut (Integer), en-têtes (Hash), corps + de la réponse (répondant à #each)]` +* Un tableau de 2 élements : `[code statut (Integer), corps de la réponse + (répondant à #each)]` +* Un objet qui répond à `#each` et qui ne transmet que des chaînes de + caractères au bloc fourni +* Un Integer représentant le code statut + +Ainsi, on peut facilement implémenter un exemple de streaming : + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Vous pouvez aussi utiliser le helper `stream` (présenté un peu plus loin) pour +éviter les répétitions et intégrer le traitement relatif au streaming dans le bloc +de code de la route. + +## Masques de route spécifiques + +Comme cela a été vu auparavant, Sinatra offre la possibilité d'utiliser des +masques sous forme de chaines de caractères ou des expressions régulières +pour définir les routes. Mais il est possible de faire bien plus. Vous pouvez +facilement définir vos propres masques : + +```ruby +class MasqueToutSauf + Masque = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Masque.new([]) + end + + def match(str) + @caputres unless @except === str + end +end + +def tout_sauf(masque) + MasqueToutSauf.new(masque) +end + +get tout_sauf("/index") do + # ... +end +``` + +Notez que l'exemple ci-dessus est plus compliqué qu'il ne devrait et peut être implémenté de la façon suivante : + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou bien en utilisant cette expression regulière : + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Fichiers statiques + +Les fichiers du dossier `./public` sont servis de façon statique. Vous pouvez spécifier un autre dossier avec le paramètre `:public_folder` : + +```ruby +set :public_folder, __dir__ + '/statique' +``` + +Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier +`./public/css/style.css` sera accessible à l'URL : +`http://exemple.com/css/style.css`. + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête `Cache-Control` (voir plus bas). + +## Vues / Templates + +Chaque langage de template est disponible via sa propre méthode de rendu, +lesquelles renvoient tout simplement une chaîne de caractères. + +```ruby +get '/' do + erb :index +end +``` + +Ceci génère la vue `views/index.erb`. + +Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer +le contenu du template : + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Les méthodes de templates acceptent un hash d'options comme second argument : + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Ceci génèrera la vue `views/index.erb` en l'intégrant au *layout* `views/post.erb` (`views/layout.erb` est la valeur par défaut si ce fichier existe). + +Toute option que Sinatra ne comprend pas sera passée au moteur de rendu : + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Vous pouvez également définir les options de chaque langage de template de façon +générale : + +```ruby +set :haml, :format => html5 + +get '/' do + haml :index +end +``` + +Les arguments passés à la méthode de rendu prennent le pas sur les options définies au moyen de `set`. + +Options disponibles : + +
    +
    locals
    +
    + Liste de variables locales passées au document. Pratique pour les vues + partielles. + Exemple : erb "<%= foo %>", :locals => {:foo => "bar"}. +
    + +
    default_encoding
    +
    + Encodage de caractères à utiliser en cas d'incertitude. Par défaut + settings.default_encoding. +
    + +
    views
    +
    + Dossier de vues dans lequel chercher les templates. Par défaut + settings.views. +
    + +
    layout
    +
    + S'il faut ou non utiliser un layout (true ou false). + Ou indique le template à utiliser lorsque c'est un symbole. Exemple : + erb :index, :layout => !request.xhr?. +
    + +
    content_type
    +
    + Content-Type que le template génère. La valeur par défaut dépend du langage de template. +
    + +
    scope
    +
    + Contexte dans lequel effectuer le rendu du template. Par défaut il s'agit + de l'instance de l'application. Si vous changez cela, les variables + d'instance et les méthodes utilitaires ne seront pas disponibles. +
    + +
    layout_engine
    +
    + Moteur de rendu à utiliser pour le layout. Utile pour les langages ne + supportant pas les layouts. Il s'agit par défaut du moteur utilisé pour + le rendu du template. Exemple : set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Options spécifiques à la génération du layout. Exemple : set :rdoc, + :layout_options => { :views => 'views/layouts' } +
    +
    + +Les templates sont supposés se trouver directement dans le dossier +`./views`. Pour utiliser un dossier de vues différent : + +```ruby +set :views, settings.root + '/templates' +``` + +Il est important de se souvenir que les templates sont toujours référencés +sous forme de symboles, même lorsqu'ils sont dans un sous-répertoire (dans +ce cas, utilisez `:'sous_repertoire/template'`). Il faut utiliser +un symbole car les méthodes de rendu évaluent le contenu des chaînes de +caractères au lieu de les considérer comme un chemin vers un fichier. + +### Templates littéraux + +```ruby +get '/' do + haml '%div.title Bonjour le monde' +end +``` + +Utilisera la chaine de caractères comme template pour générer la réponse. +Vous pouvez spécifier un `:path` et `:line` optionnels pour une trace plus +claire s'il existe un chemin dans le système de fichiers ou une ligne +associés à cette chaîne de caractères : + +```ruby +get '/' do + haml '%div.title Bonjour le monde', :path => 'exemples/fichier.haml', :line => 3 +end +``` + +### Langages de template disponibles + +Certains langages ont plusieurs implémentations. Pour préciser l'implémentation +à utiliser (et garantir l'aspect thread-safe), vous devez simplement l'avoir +chargée au préalable : + +```ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Templates Haml + + + + + + + + + + + + + + +
    Dépendanceshaml
    Extensions de fichier.haml
    Exemplehaml :index, :format => :html5
    + +#### Templates Erb + + + + + + + + + + + + + + +
    Dépendances + erubis + ou erb (inclus avec Ruby) +
    Extensions de fichier.erb, .rhtml ou .erubis (Erubis seulement)
    Exempleerb :index
    + +#### Templates Builder + + + + + + + + + + + + + + +
    Dépendances + builder +
    Extensions de fichier.builder
    Exemplebuilder { |xml| xml.em "salut" }
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Nokogiri + + + + + + + + + + + + + + +
    Dépendancesnokogiri
    Extensions de fichier.nokogiri
    Exemplenokogiri { |xml| xml.em "salut" } +
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Sass + + + + + + + + + + + + + + +
    Dépendancessass
    Extensions de fichier.sass
    Exemplesass :stylesheet, :style => :expanded
    + +#### Templates SCSS + + + + + + + + + + + + + + +
    Dépendancessass
    Extensions de fichier.scss
    Exemplescss :stylesheet, :style => :expanded

    +
    + +#### Templates Less + + + + + + + + + + + + + + +
    Dépendancesless
    Extensions de fichier.less
    Exempleless :stylesheet +
    + +#### Templates Liquid + + + + + + + + + + + + + + +
    Dépendancesliquid
    Extensions de fichier.liquid
    Exempleliquid :index, :locals => { :key => 'value' }
    + +Comme vous ne pouvez appeler de méthodes Ruby (autres que `yield`) +dans un template Liquid, vous aurez sûrement à lui passer des variables +locales. + +#### Templates Markdown + + + + + + + + + + + + + + + +

    Dépendances

    + Au choix : + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    Extensions de fichier.markdown, .mkd et .md
    Exemplemarkdown :index, :layout_engine => :erb
    + +Il n’est pas possible d’appeler des méthodes depuis markdown, ni de +lui passer de variables locales. Par conséquent, il sera souvent utilisé +en combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `markdown` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= markdown(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthode Ruby depuis Markdown, vous ne +pouvez pas utiliser de layouts écrits en Markdown. Toutefois, il +est possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Textile + + + + + + + + + + + + + + +
    DépendancesRedCloth
    Extensions de fichier.textile
    Exempletextile :index, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes depuis textile, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => textile(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `textile` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= textile(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthode Ruby depuis Textile, vous ne pouvez +pas utiliser de layouts écrits en Textile. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates RDoc + + + + + + + + + + + + + + +
    DépendancesRDoc
    Extensions de fichier.rdoc
    Exemplerdoc :README, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes Ruby depuis rdoc, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => rdoc(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `rdoc` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= rdoc(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis RDoc, vous ne pouvez +pas utiliser de layouts écrits en RDoc. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Asciidoc + + + + + + + + + + + + + + +
    DépendancesAsciidoctor
    Extensions de fichier.asciidoc, .adoc and .ad
    Exempleasciidoc :README, :layout_engine => :erb
    + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template +AsciiDoc, vous aurez sûrement à lui passer des variables locales. + +#### Templates Radius + + + + + + + + + + + + + + +
    DépendancesRadius
    Extensions de fichier.radius
    Exempleradius :index, :locals => { :key => 'value' }
    + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template +Radius, vous aurez sûrement à lui passer des variables locales. + +#### Templates Markaby + + + + + + + + + + + + + + +
    DépendancesMarkaby
    Extensions de fichier.mab
    Exemplemarkaby { h1 "Bienvenue !" }
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates RABL + + + + + + + + + + + + + + +
    DépendancesRabl
    Extensions de fichier.rabl
    Exemplerabl :index
    + +#### Templates Slim + + + + + + + + + + + + + + +
    DépendancesSlim Lang
    Extensions de fichier.slim
    Exempleslim :index
    + +#### Templates Creole + + + + + + + + + + + + + + +
    DépendancesCreole
    Extensions de fichier.creole
    Exemplecreole :wiki, :layout_engine => :erb
    + +Il n'est pas possible d'appeler de méthodes Ruby depuis creole, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `creole` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= creole(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis Creole, vous ne pouvez +pas utiliser de layouts écrits en Creole. Toutefois, il est possible +d'utiliser un moteur de rendu différent pour le template et pour le layout +en utilisant l'option `:layout_engine`. + +#### Templates MediaWiki + + + + + + + + + + + + + + +
    DépendancesWikiCloth
    Extensions de fichier.mediawiki and .mw
    Exemplemediawiki :wiki, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes Ruby depuis Mediawiki, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `mediawiki` depuis un +autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= mediawiki(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis MediaWiki, vous ne pouvez +pas utiliser de layouts écrits en MediaWiki. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates CoffeeScript + + + + + + + + + + + + + + +
    Dépendances + + CoffeeScript + + et un + + moyen d'exécuter javascript + +
    Extensions de fichier.coffee
    Exemplecoffee :index
    + +#### Templates Stylus + + + + + + + + + + + + + + +
    Dépendances + + Stylus + + et un + + moyen d'exécuter javascript + +
    Extensions de fichier.styl
    Exemplestylus :index
    + +Avant de pouvoir utiliser des templates Stylus, vous devez auparavant charger +`stylus` et `stylus/tilt` : + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemple +end +``` + +#### Templates Yajl + + + + + + + + + + + + + + +
    Dépendances + yajl-ruby +
    Extensions de fichier.yajl
    Exempleyajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'ressource'

    +
    + +La source du template est évaluée en tant que chaine Ruby, puis la +variable json obtenue est convertie avec #to_json. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Les options `:callback` et `:variable` peuvent être utilisées pour décorer +l’objet retourné. + +```ruby +var ressource = {"foo":"bar","baz":"qux"}; present(ressource); +``` + +#### Templates WLang + + + + + + + + + + + + + + +
    Dépendanceswlang
    Extensions de fichier.wlang
    Exemplewlang :index, :locals => { :key => 'value' }
    + +L’appel de code ruby au sein des templates n’est pas idiomatique en wlang. +L’écriture de templates sans logique est encouragée, via le passage de variables +locales. Il est néanmoins possible d’écrire un layout en wlang et d’y utiliser +`yield`. + +### Accéder aux variables dans un Template + +Un template est évalué dans le même contexte que l'endroit d'où il a été +appelé (gestionnaire de route). Les variables d'instance déclarées dans le +gestionnaire de route sont directement accessibles dans le template : + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nom' +end +``` + +Alternativement, on peut passer un hash contenant des variables locales : + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nom', :locals => { :foo => foo } +end +``` + +Ceci est généralement nécessaire lorsque l'on veut utiliser un template depuis un autre template (partiel) et qu'il faut donc adapter le nom des variables. + +### Templates avec `yield` et layouts imbriqués + +En général, un layout est un simple template qui appelle `yield`. Ce genre de +template peut s'utiliser via l'option `:template` comme décrit précédemment ou +peut être rendu depuis un bloc : + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Ce code est plus ou moins équivalent à `erb :index, :layout => :post`. + +Le fait de passer des blocs aux méthodes de rendu est particulièrement utile +pour gérer des templates imbriqués : + +```ruby +erb :layout_principal, :layout => false do + erb :layout_admin do + erb :utilisateur + end +end +``` + +Ou plus brièvement : + +```ruby +erb :layout_admin, :layout => :layout_principal do + erb :utilisateur +end +``` + +Actuellement, les méthodes de rendu qui acceptent un bloc sont : `erb`, `haml`, +`liquid`, `slim ` et `wlang`. La méthode générale `render` accepte elle aussi +un bloc. + + +### Templates dans le fichier source + +Des templates peuvent être définis dans le fichier source comme ceci : + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Bonjour le monde ! +``` + +NOTE : Les templates du fichier source qui contient `require 'sinatra'` +sont automatiquement chargés. Si vous avez des templates dans d'autres +fichiers source, il faut explicitement les déclarer avec +`enable :inline_templates`. + + +### Templates nommés + +Les templates peuvent aussi être définis grâce à la méthode de haut niveau `template` : + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Bonjour le monde !' +end + +get '/' do + haml :index +end +``` + +Si un template nommé "layout" existe, il sera utilisé à chaque fois qu'un +template sera affiché. Vous pouvez désactivez les layouts au cas par cas en +passant `:layout => false` ou bien les désactiver par défaut au moyen +de `set :haml, :layout => false` : + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associer des extensions de fichier + +Pour associer une extension de fichier avec un moteur de rendu, utilisez +`Tilt.register`. Par exemple, si vous désirez utiliser l'extension +de fichier `tt` pour les templates Textile, vous pouvez faire comme suit : + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Ajouter son propre moteur de rendu + +En premier lieu, déclarez votre moteur de rendu avec Tilt, ensuite créez +votre méthode de rendu : + +```ruby +Tilt.register :monmoteur, MonMerveilleuxMoteurDeRendu + +helpers do + def monmoteur(*args) render(:monmoteur, *args) end +end + +get '/' do + monmoteur :index +end +``` + +Utilisera `./views/index.monmoteur`. Voir [le projet Github](https://github.com/rtomayko/tilt) pour en savoir plus sur Tilt. + +### Utiliser des règles personnalisées pour la recherche de templates + +Pour implémenter votre propre mécanisme de recherche de templates, vous +pouvez écrire votre propre méthode `#find_template` : + +```ruby +configure do + set :views, [ './vues/a', './vues/b' ] +end + +def find_template(vues, nom, moteur, &bloc) + Array(vues).each do |v| + super(v, nom, moteur, &bloc) + end +end +``` + +## Filtres + +Les filtres `before` sont exécutés avant chaque requête, dans le même contexte +que les routes, et permettent de modifier la requête et sa réponse. Les +variables d'instance déclarées dans les filtres sont accessibles au niveau +des routes et des templates : + +```ruby +before do + @note = 'Coucou !' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Coucou !' + params['splat'] #=> 'bar/baz' +end +``` + +Les filtres `after` sont exécutés après chaque requête à l'intérieur du même +contexte et permettent de modifier la requête et sa réponse. Les variables +d'instance déclarées dans les filtres `before` ou les routes sont accessibles +au niveau des filtres `after` : + +```ruby +after do + puts response.status +end +``` + +Note : Le corps de la réponse n'est pas disponible au niveau du filtre `after` +car il ne sera généré que plus tard (sauf dans le cas où vous utilisez la +méthode `body` au lieu de simplement renvoyer une chaine depuis vos routes). + +Les filtres peuvent être associés à un masque, ce qui permet de limiter leur +exécution aux cas où la requête correspond à ce masque : + +```ruby +before '/secret/*' do + authentification! +end + +after '/faire/:travail' do |travail| + session['dernier_travail'] = travail +end +``` + +Tout comme les routes, les filtres acceptent également des conditions : + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Utilisez la méthode de haut niveau `helpers` pour définir des méthodes +qui seront accessibles dans vos gestionnaires de route et dans vos templates : + +```ruby +helpers do + def bar(nom) + "#{nom}bar" + end +end + +get '/:nom' do + bar(params['nom']) +end +``` + +Vous pouvez aussi définir les méthodes helper dans un module séparé : + +```ruby +module FooUtils + def foo(nom) "#{nom}foo" end +end + +module BarUtils + def bar(nom) "#{nom}bar" end +end + +helpers FooUtils, BarUtils +``` + +Cela a le même résultat que d'inclure les modules dans la classe de +l'application. + +### Utiliser les sessions + +Les sessions sont utilisées pour conserver un état entre les requêtes. Une fois +activées, vous avez un hash de session par session utilisateur : + +```ruby +enable :sessions + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:valeur' do + session['valeur'] = params['valeur'] +end +``` + +Notez que `enable :sessions` enregistre en fait toutes les données dans +un cookie. Ce n'est pas toujours ce que vous voulez (enregistrer beaucoup de +données va augmenter le traffic par exemple). Vous pouvez utiliser n'importe +quel middleware Rack de session afin d'éviter cela. N'utilisez **pas** +`enable :sessions` dans ce cas mais chargez le middleware de votre +choix comme vous le feriez pour n'importe quel autre middleware : + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:valeur' do + session['valeur'] = params['valeur'] +end +``` + +Pour renforcer la sécurité, les données de session dans le cookie sont signées +avec une clé secrète de session. Une clé secrète est générée pour vous au +hasard par Sinatra. Toutefois, comme cette clé change à chaque démarrage de +votre application, vous pouvez définir cette clé vous-même afin que toutes +les instances de votre application la partage : + +```ruby +set :session_secret, 'super secret' +``` + +Si vous souhaitez avoir plus de contrôle, vous pouvez également enregistrer un +hash avec des options lors de la configuration de `sessions` : + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Pour que les différents sous-domaines de foo.com puissent partager une session, +vous devez précéder le domaine d'un *.* (point) : + +```ruby +set :sessions, :domain => '.foo.com' +``` + + +### Halt + +Pour arrêter immédiatement la requête dans un filtre ou un gestionnaire de +route : + +```ruby +halt +``` + +Vous pouvez aussi passer le code retour ... + +```ruby +halt 410 +``` + +Ou le texte ... + +```ruby +halt 'Ceci est le texte' +``` + +Ou les deux ... + +```ruby +halt 401, 'Partez !' +``` + +Ainsi que les en-têtes ... + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +Bien sûr il est possible de combiner un template avec `halt` : + +```ruby +halt erb(:erreur) +``` + +### Passer + +Une route peut passer le relais aux autres routes qui correspondent également +avec `pass` : + +```ruby +get '/devine/:qui' do + pass unless params['qui'] == 'Frank' + "Tu m'as eu !" +end + +get '/devine/*' do + 'Manqué !' +end +``` + +On sort donc immédiatement de ce gestionnaire et on continue à chercher, +dans les masques suivants, le prochain qui correspond à la requête. +Si aucun des masques suivants ne correspond, un code 404 est retourné. + +### Déclencher une autre route + +Parfois, `pass` n'est pas ce que vous recherchez, au lieu de cela vous +souhaitez obtenir le résultat d'une autre route. Pour cela, utilisez +simplement `call` : + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Notez que dans l'exemple ci-dessus, vous faciliterez les tests et améliorerez +la performance en déplaçant simplement `"bar"` dans un helper +utilisé à la fois par `/foo` et `/bar`. + +Si vous souhaitez que la requête soit envoyée à la même instance de +l'application plutôt qu'à une copie, utilisez `call!` au lieu de +`call`. + +Lisez la spécification Rack si vous souhaitez en savoir plus sur +`call`. + +### Définir le corps, le code retour et les en-têtes + +Il est possible et recommandé de définir le code retour et le corps de la +réponse au moyen de la valeur de retour d'un bloc définissant une route. +Quoiqu'il en soit, dans certains cas vous pourriez avoir besoin de définir +le coprs de la réponse à un moment arbitraire de l'exécution. Vous pouvez le +faire au moyen de la méthode `body`. Si vous faites ainsi, vous pouvez alors +utiliser cette même méthode pour accéder au corps de la réponse : + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Il est également possible de passer un bloc à `body`, qui sera exécuté par le +gestionnaire Rack (ceci peut être utilisé pour implémenter un streaming, +voir "Valeurs de retour"). + +Pareillement au corps de la réponse, vous pouvez également définir le code +retour et les en-têtes : + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "Je suis une théière !" +end +``` + +Comme pour `body`, `headers` et `status` peuvent être utilisés sans arguments +pour accéder à leurs valeurs. + +### Faire du streaming + +Il y a des cas où vous voulez commencer à renvoyer des données pendant que +vous êtes en train de générer le reste de la réponse. Dans les cas les plus +extrèmes, vous souhaitez continuer à envoyer des données tant que le client +n'abandonne pas la connexion. Vous pouvez alors utiliser le helper `stream` +pour éviter de créer votre propre système : + +```ruby +get '/' do + stream do |out| + out << "Ca va être hallu -\n" + sleep 0.5 + out << " (attends la suite) \n" + sleep 1 + out << "- cinant !\n" + end +end +``` + +Cela permet d'implémenter des API de streaming ou de +[Server Sent Events](https://w3c.github.io/eventsource/) et peut servir de +base pour des [WebSockets](https://en.wikipedia.org/wiki/WebSocket). Vous +pouvez aussi l'employer pour augmenter le débit quand une partie du contenu +provient d'une ressource lente. + +Le fonctionnement du streaming, notamment le nombre de requêtes simultanées, +dépend énormément du serveur web utilisé. Certains ne prennent pas du tout en +charge le streaming. Lorsque le serveur ne gère pas le streaming, la partie +body de la réponse sera envoyée au client en une seule fois, après +l'exécution du bloc passé au helper `stream`. Le streaming ne +fonctionne pas du tout avec Shotgun. + +En utilisant le helper `stream` avec le paramètre `keep_open`, il n'appelera +pas la méthode `close` du flux, vous laissant la possibilité de le fermer à +tout moment au cours de l'exécution. Ceci ne fonctionne qu'avec les serveurs +evented (ie non threadés) tels que Thin et Rainbows. Les autres serveurs +fermeront malgré tout le flux : + +```ruby +# interrogation prolongée + +set :server, :thin +connexions = [] + +get '/souscrire' do + # abonne un client aux évènements du serveur + stream(:keep_open) do |out| + connexions << out + # purge les connexions abandonnées + connexions.reject!(&:closed?) + end +end + +post '/message' do + connexions.each do |out| + # prévient le client qu'un nouveau message est arrivé + out << params['message'] << "\n" + + # indique au client de se connecter à nouveau + out.close + end + + # compte-rendu + "message reçu" +end +``` + +Il est aussi possible pour le client de fermer la connexion en essayant +d'écrire sur le socket. Pour cette raison, il est recommandé de vérifier +`out.closed?` avant d'essayer d'y écrire. + +### Journalisation (Logging) + +Dans le contexte de la requête, la méthode utilitaire `logger` expose une +instance de `Logger` : + +```ruby +get '/' do + logger.info "chargement des données" + # ... +end +``` + +Ce logger va automatiquement prendre en compte les paramètres de +configuration pour la journalisation de votre gestionnaire Rack. Si la +journalisation est désactivée, cette méthode renverra un objet factice et +vous n'avez pas à vous en inquiéter dans vos routes en le filtrant. + +Notez que la journalisation est seulement activée par défaut pour +`Sinatra::Application`, donc si vous héritez de `>Sinatra::Base`, +vous aurez à l'activer vous-même : + +```ruby +class MonApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Si vous souhaitez utiliser votre propre logger, vous devez définir le paramètre +`logging` à `nil` pour être certain qu'aucun middleware de logging ne sera +installé (notez toutefois que `logger` renverra alors `nil`). Dans ce cas, +Sinatra utilisera ce qui sera présent dans `env['rack.logger']`. + +### Types Mime + +Quand vous utilisez `send_file` ou des fichiers statiques, vous +pouvez rencontrer des types mime que Sinatra ne connaît pas. Utilisez +`mime_type` pour les déclarer par extension de fichier : + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Vous pouvez également les utiliser avec la méthode `content_type` : + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Former des URLs + +Pour former des URLs, vous devriez utiliser la méthode `url`, par exemple en +Haml : + +```ruby +%a{:href => url('/foo')} foo +``` + +Cela prend en compte les proxy inverse et les routeurs Rack, s'ils existent. + +Cette méthode est également disponible sous l'alias `to` (voir ci-dessous +pour un exemple). + +### Redirection du navigateur + +Vous pouvez déclencher une redirection du navigateur avec la méthode +`redirect` : + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Tout paramètre additionnel sera utilisé comme argument pour la méthode +`halt` : + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'mauvais endroit mon pote' +``` + +Vous pouvez aussi rediriger vers la page dont l'utilisateur venait au moyen de +`redirect back` : + +```ruby +get '/foo' do + "faire quelque chose" +end + +get '/bar' do + faire_quelque_chose + redirect back +end +``` + +Pour passer des arguments à une redirection, ajoutez-les soit à la requête : + +```ruby +redirect to('/bar?sum=42') +``` + +Ou bien utilisez une session : + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### Contrôle du cache + +Définissez correctement vos en-têtes à la base pour un bon cache HTTP. + +Vous pouvez facilement définir l'en-tête Cache-Control de la manière suivante : + +```ruby +get '/' do + cache_control :public + "met le en cache !" +end +``` + +Conseil de pro : définir le cache dans un filtre before : + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si vous utilisez la méthode `expires` pour définir l'en-tête correspondant, +`Cache-Control` sera alors défini automatiquement : + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Pour utiliser correctement le cache, vous devriez utiliser `etag` ou +`last_modified`. Il est recommandé d'utiliser ces méthodes *avant* de faire +d'importantes modifications, car elles vont immédiatement déclencher la réponse +si le client a déjà la version courante dans son cache : + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Il est également possible d'utiliser un +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) : + +```ruby +etag @article.sha1, :weak +``` + +Ces méthodes ne sont pas chargées de mettre des données en cache, mais elles +fournissent les informations nécessaires pour le cache de votre navigateur. Si vous êtes à la +recherche d'une solution rapide pour un reverse-proxy de cache, essayez +[rack-cache](https://github.com/rtomayko/rack-cache) : + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête `Cache-Control` (voir plus loin). + +D'après la RFC 2616, votre application devrait se comporter différement lorsque +l'en-tête If-Match ou If-None-Match est défini à `*` en tenant compte du +fait que la ressource demandée existe déjà ou pas. Sinatra considère que les +requêtes portant sur des ressources sûres (tel que get) ou idempotentes (tel que +put) existent déjà et pour les autres ressources (par exemple dans le cas +de requêtes post) qu'il s'agit de nouvelles ressources. Vous pouvez modifier ce +comportement en passant une option `:new_resource` : + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :nouvel_article +end +``` + +Si vous souhaitez avoir un ETag faible, utilisez l'option `:kind` : + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Envoyer des fichiers + +Pour envoyer des fichiers, vous pouvez utiliser la méthode `send_file` : + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Quelques options sont également acceptées : + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Les options sont : + +
    +
    filename
    +
    + le nom du fichier dans la réponse, par défaut le nom du fichier envoyé. +
    + +
    type
    +
    + type de contenu à utiliser, deviné à partir de l’extension de fichier si + absent +
    + +
    disposition
    +
    + utilisé pour Content-Disposition, les valeurs possibles étant : nil + (par défaut), :attachment et :inline +
    + +
    length
    +
    en-tête Content-Length, par défaut la taille du fichier
    + +
    status
    +
    + code état à renvoyer. Utile quand un fichier statique sert de page d’erreur. + Si le gestionnaire Rack le supporte, d'autres moyens que le streaming via le + processus Ruby seront utilisés. Si vous utilisez cette méthode, Sinatra gérera + automatiquement les requêtes de type range. +
    +
    + +### Accéder à l'objet requête + +L'objet correspondant à la requête envoyée peut être récupéré dans le contexte +de la requête (filtres, routes, gestionnaires d'erreur) au moyen de la méthode +`request` : + +```ruby +# application tournant à l'adresse http://exemple.com/exemple +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # vrai + request.preferred_type(t) # 'text/html' + request.body # corps de la requête envoyée par le client + # (voir ci-dessous) + request.scheme # "http" + request.script_name # "/exemple" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # taille de request.body + request.media_type # type de média pour request.body + request.host # "exemple.com" + request.get? # vrai (méthodes similaires pour les autres + # verbes HTTP) + request.form_data? # faux + request["UN_ENTETE"] # valeur de l'en-tête UN_ENTETE + request.referrer # référant du client ou '/' + request.user_agent # user agent (utilisé par la condition :agent) + request.cookies # tableau contenant les cookies du navigateur + request.xhr? # requête AJAX ? + request.url # "http://exemple.com/exemple/foo" + request.path # "/exemple/foo" + request.ip # adresse IP du client + request.secure? # faux + request.forwarded? # vrai (si on est derrière un proxy inverse) + request.env # tableau brut de l'environnement fourni par Rack +end +``` + +Certaines options, telles que `script_name` ou `path_info` +peuvent également être modifiées : + +```ruby +before { request.path_info = "/" } + +get "/" do + "toutes les requêtes arrivent ici" +end +``` + +`request.body` est un objet IO ou StringIO : + +```ruby +post "/api" do + request.body.rewind # au cas où il a déjà été lu + donnees = JSON.parse request.body.read + "Bonjour #{donnees['nom']} !" +end +``` + +### Fichiers joints + +Vous pouvez utiliser la méthode `attachment` pour indiquer au navigateur que +la réponse devrait être stockée sur le disque plutôt qu'affichée : + + +```ruby +get '/' do + attachment + "enregistre-le !" +end +``` + +Vous pouvez également lui passer un nom de fichier : + +```ruby +get '/' do + attachment "info.txt" + "enregistre-le !" +end +``` + +### Gérer Date et Time + +Sinatra fourni un helper `time_for` pour convertir une valeur donnée en +objet `Time`. Il peut aussi faire la conversion à partir d'objets `DateTime`, +`Date` ou de classes similaires : + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "encore temps" +end +``` + +Cette méthode est utilisée en interne par `expires`, `last_modified` et +consorts. Par conséquent, vous pouvez très facilement étendre le +fonctionnement de ces méthodes en surchargeant le helper `time_for` dans +votre application : + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "salut" +end +``` + +### Chercher les fichiers de templates + +La méthode `find_template` est utilisée pour trouver les fichiers de +templates à générer : + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "pourrait être #{file}" +end +``` + +Ce n'est pas très utile. En revanche, il est utile de pouvoir surcharger +cette méthode afin de définir son propre mécanisme de recherche. Par exemple, +vous pouvez utiliser plus d'un répertoire de vues : + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(vues, nom, moteur, &bloc) + Array(vues).each { |v| super(v, nom, moteur, &bloc) } + end +end +``` + +Un autre exemple est d'utiliser des répertoires différents pour des moteurs +de rendu différents : + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(vues, nom, moteur, &bloc) + _, dossier = vues.detect { |k,v| moteur == Tilt[k] } + dossier ||= vues[:default] + super(dossier, nom, moteur, &bloc) + end +end +``` + +Vous pouvez également écrire cela dans une extension et la partager avec +d'autres ! + +Notez que `find_template` ne vérifie pas que le fichier existe mais +va plutôt exécuter le bloc pour tous les chemins possibles. Cela n'induit pas +de problème de performance dans le sens où `render` va utiliser `break` dès +qu'un fichier sera trouvé. De plus, l'emplacement des templates (et leur +contenu) est mis en cache si vous n'êtes pas en mode développement. Vous +devez garder cela en tête si vous écrivez une méthode vraiment dingue. + +## Configuration + +Lancé une seule fois au démarrage de tous les environnements : + +```ruby +configure do + # définir un paramètre + set :option, 'valeur' + + # définir plusieurs paramètres + set :a => 1, :b => 2 + + # équivalent à "set :option, true" + enable :option + + # équivalent à "set :option, false"" + disable :option + + # vous pouvez également avoir des paramètres dynamiques avec des blocs + set(:css_dir) { File.join(views, 'css') } +end +``` + +Lancé si l'environnement (variable d'environnement APP_ENV) est `:production` : + +```ruby + configure :production do + ... + end +``` + +Lancé si l'environnement est `:production` ou `:test` : + +```ruby + configure :production, :test do + ... + end +``` + +Vous pouvez accéder à ces paramètres via `settings` : + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Se protéger des attaques + +Sinatra utilise [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +pour protéger votre application contre les principales attaques opportunistes. +Vous pouvez très simplement désactiver cette fonctionnalité (ce qui exposera +votre application à beaucoup de vulnerabilités courantes) : + +```ruby +disable :protection +``` + +Pour désactiver seulement un type de protection, vous pouvez définir `protection` +avec un hash d'options : + +```ruby +set :protection, :except => :path_traversal +``` + +Vous pouvez également lui passer un tableau pour désactiver plusieurs types de +protection : + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Par défaut, il faut que `:sessions` soit activé pour que Sinatra mette en place +un système de protection au niveau de la session. Dans le cas où vous gérez +vous même les sessions, vous devez utiliser l'option `:session` pour que cela +soit le cas : + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### Paramètres disponibles + +
    +
    absolute_redirects
    +
    Si désactivé, Sinatra permettra les redirections relatives. Toutefois, + Sinatra ne sera plus conforme à la RFC 2616 (HTTP 1.1), qui n’autorise + que les redirections absolues.

    + + Activez si votre application tourne derrière un proxy inverse qui n’a + pas été correctement configuré. Notez que la méthode url + continuera de produire des URLs absolues, sauf si vous lui passez + false comme second argument.

    + +

    Désactivé par défaut.

    + +
    add_charset
    +

    types mime pour lesquels la méthode content_type va + automatiquement ajouter l’information du charset.

    + +

    Vous devriez lui ajouter des valeurs plutôt que de l’écraser :

    + +
    settings.add_charset >> "application/foobar"
    + +
    app_file
    +

    chemin pour le fichier de l’application principale, utilisé pour + détecter la racine du projet, les dossiers public et vues, et les + templates en ligne.

    + +
    bind
    +
    adresse IP sur laquelle se brancher (par défaut : 0.0.0.0). Utiliser + seulement pour le serveur intégré.
    + +
    default_encoding
    +
    encodage à utiliser si inconnu (par défaut "utf-8")
    + +
    dump_errors
    +
    afficher les erreurs dans le log. +
    + +
    environment
    +
    environnement courant, par défaut ENV['APP_ENV'], ou + "development" si absent.
    + +
    logging
    +
    utiliser le logger.
    + +
    lock
    +

    Place un lock autour de chaque requête, n’exécutant donc + qu’une seule requête par processus Ruby.

    + +

    Activé si votre application n’est pas thread-safe. Désactivé + par défaut.

    + +
    method_override
    +
    utilise la magie de _method afin de permettre des formulaires + put/delete dans des navigateurs qui ne le permettent pas. + +
    +
    port
    +
    port à écouter. Utiliser seulement pour le serveur intégré.
    + +
    mustermann_opts
    +
    + Un hash d'options à passer à Mustermann.new lors de la compilation + des chemins de routes +
    + +
    prefixed_redirects
    +
    si oui ou non request.script_name doit être inséré dans les + redirections si un chemin non absolu est utilisé. Ainsi, redirect + '/foo' se comportera comme redirect to('/foo'). Désactivé + par défaut.
    + +
    protection
    +
    défini s’il faut activer ou non la protection contre les attaques web. + Voir la section protection précédente.
    + +
    public_dir
    +
    alias pour public_folder. Voir ci-dessous.
    + +
    public_folder
    +
    chemin pour le dossier à partir duquel les fichiers publics sont servis. + Utilisé seulement si les fichiers statiques doivent être servis (voir le + paramètre static). Si non défini, il découle du paramètre + app_file.
    + +
    quiet
    +
    + Désactive les journaux (logs) générés par les commandes start et stop + de Sinatra. false par défaut. +
    + +
    reload_templates
    +
    si oui ou non les templates doivent être rechargés entre les requêtes. + Activé en mode développement.
    + +
    root
    +
    chemin pour le dossier racine du projet. Si non défini, il découle du + paramètre app_file.
    + +
    raise_errors
    +
    soulever les erreurs (ce qui arrêtera l’application). Désactivé par + défaut sauf lorsque environment est défini à + "test".
    + +
    run
    +
    si activé, Sinatra s’occupera de démarrer le serveur, ne pas activer si + vous utiliser rackup ou autres.
    + +
    running
    +
    est-ce que le serveur intégré est en marche ? ne changez pas ce + paramètre !
    + +
    server
    +
    serveur ou liste de serveurs à utiliser pour le serveur intégré. Par + défaut [‘thin’, ‘mongrel’, ‘webrick’], l’ordre indiquant la + priorité.
    + +
    server_settings
    +
    + Si vous utilisez un serveur Webrick, sans doute pour votre environnement de + développement, vous pouvez passer des options à server_settings, + comme SSLEnable ou SSLVerifyClient. Cependant, les + serveurs comme Puma et Thin ne le permettent pas, et vous pouvez donc + définir server_settings en tant que méthode lorsque vous appelez + configure. +
    + +
    sessions
    +
    active le support des sessions basées sur les cookies, en utilisant + Rack::Session::Cookie. Reportez-vous à la section ‘Utiliser les + sessions’ pour plus d’informations.
    + +
    session_store
    +
    + Le middleware Rack utilisé pour les sessions. Rack::Session::Cookie + par défaut. Voir la section 'Utiliser les sessions' pour plus de détails. +
    + +
    show_exceptions
    +
    affiche la trace de l’erreur dans le navigateur lorsqu’une exception se + produit. Désactivé par défaut sauf lorsque environment est + défini à "development".
    + +
    static
    +
    Si oui ou non Sinatra doit s’occuper de servir les fichiers statiques. + Désactivez si vous utilisez un serveur capable de le gérer lui même. Le + désactiver augmentera la performance. Activé par défaut pour le style + classique, désactivé pour le style modulaire.
    + +
    static_cache_control
    +
    A définir quand Sinatra rend des fichiers statiques pour ajouter les + en-têtes Cache-Control. Utilise le helper cache_control. + Désactivé par défaut. Utiliser un array explicite pour définir des + plusieurs valeurs : set :static_cache_control, [:public, :max_age => + 300]
    + +
    threaded
    +
    à définir à true pour indiquer à Thin d’utiliser + EventMachine.defer pour traiter la requête.
    + +
    traps
    +
    Indique si Sinatra doit gérer les signaux système.
    + +
    views
    +
    chemin pour le dossier des vues. Si non défini, il découle du paramètre + app_file.
    + +
    x_cascade
    +
    + Indique s'il faut ou non définir le header X-Cascade lorsqu'aucune route + ne correspond. Défini à true par défaut. +
    +
    + +## Environnements + +Il existe trois environnements prédéfinis : `"development"`, +`"production"` et `"test"`. Les environnements peuvent être +sélectionné via la variable d'environnement `APP_ENV`. Sa valeur par défaut +est `"development"`. Dans ce mode, tous les templates sont rechargés à +chaque requête. Des handlers spécifiques pour `not_found` et +`error` sont installés pour vous permettre d'avoir une pile de trace +dans votre navigateur. En mode `"production"` et `"test"` les +templates sont mis en cache par défaut. + +Pour exécuter votre application dans un environnement différent, définissez la +variable d'environnement `APP_ENV` : + +``` shell +APP_ENV=production ruby my_app.rb +``` + +Vous pouvez utiliser une des méthodes `development?`, `test?` et `production?` +pour déterminer quel est l'environnement en cours : + +```ruby +get '/' do + if settings.development? + "développement !" + else + "pas en développement !" + end +end +``` + +## Gérer les erreurs + +Les gestionnaires d'erreur s'exécutent dans le même contexte que les routes ou +les filtres, ce qui veut dire que vous avez accès (entre autres) aux bons +vieux `haml`, `erb`, `halt`, etc. + +### NotFound + +Quand une exception Sinatra::NotFound est soulevée, ou que le code +retour est 404, le gestionnaire not_found est invoqué : + +```ruby +not_found do + 'Pas moyen de trouver ce que vous cherchez' +end +``` + +### Error + +Le gestionnaire `error` est invoqué à chaque fois qu'une exception est +soulevée dans une route ou un filtre. Notez qu'en développement, il ne +sera exécuté que si vous définissez l'option show exceptions à +`:after_handler` : + +```ruby +set :show_exceptions, :after_handler +``` + +L'objet exception est accessible via la +variable Rack `sinatra.error` : + +```ruby +error do + 'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].message +end +``` + +Erreur personnalisée : + +```ruby +error MonErreurSurMesure do + 'Oups ! Il est arrivé...' + env['sinatra.error'].message +end +``` + +Donc si cette erreur est soulevée : + +```ruby +get '/' do + raise MonErreurSurMesure, 'quelque chose de mal' +end +``` + +La réponse sera : + +``` +Oups ! Il est arrivé... quelque chose de mal +``` + +Alternativement, vous pouvez avoir un gestionnaire d'erreur associé à un code +particulier : + +```ruby +error 403 do + 'Accès interdit' +end + +get '/secret' do + 403 +end +``` + +Ou un intervalle : + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installe pour vous quelques gestionnaires `not_found` et +`error` génériques lorsque vous êtes en environnement de +`development`. + +## Les Middlewares Rack + +Sinatra fonctionne avec [Rack](http://rack.github.io/), une interface standard +et minimale pour les web frameworks Ruby. Un des points forts de Rack est le +support de ce que l'on appelle des "middlewares" -- composants qui viennent se +situer entre le serveur et votre application, et dont le but est de +visualiser/manipuler la requête/réponse HTTP, et d'offrir diverses +fonctionnalités classiques. + +Sinatra permet d'utiliser facilement des middlewares Rack via la méthode de +haut niveau `use` : + +```ruby +require 'sinatra' +require 'mon_middleware_perso' + +use Rack::Lint +use MonMiddlewarePerso + +get '/bonjour' do + 'Bonjour le monde' +end +``` + +La sémantique de `use` est identique à celle définie dans le DSL de +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(le plus souvent utilisé dans un fichier `rackup`). Par exemple, la méthode +`use` accepte divers arguments ainsi que des blocs : + +```ruby +use Rack::Auth::Basic do |identifiant, mot_de_passe| + identifiant == 'admin' && mot_de_passe == 'secret' +end +``` + +Rack est distribué avec de nombreux middlewares standards pour loguer, débuguer, +faire du routage URL, de l'authentification ou gérer des sessions. Sinatra gère +plusieurs de ces composants automatiquement via son système de configuration, ce +qui vous dispense de faire un `use` pour ces derniers. + +Vous trouverez d'autres middlewares intéressants sur +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readm), +ou en consultant le [wiki de Rack](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Tester + +Les tests pour Sinatra peuvent être écrit avec n'importe quelle bibliothèque +basée sur Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) est +recommandé : + +```ruby +require 'mon_application_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MonTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_ma_racine + get '/' + assert_equal 'Bonjour le monde !', last_response.body + end + + def test_avec_des_parametres + get '/rencontrer', :nom => 'Frank' + assert_equal 'Salut Frank !', last_response.body + end + + def test_avec_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Vous utilisez Songbird !", last_response.body + end +end +``` + +Note : si vous utilisez le style modulaire de Sinatra, remplacez +`Sinatra::Application` par le nom de la classe de votre application. + +## Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires + +Définir votre application au niveau supérieur fonctionne bien dans le cas des +micro-applications mais présente pas mal d'inconvénients pour créer des +composants réutilisables sous forme de middlewares Rack, de Rails metal, de +simples librairies avec un composant serveur ou même d'extensions Sinatra. Le +niveau supérieur suppose une configuration dans le style des micro-applications +(une application d'un seul fichier, des répertoires `./public` et +`./views`, des logs, une page d'erreur, etc...). C'est là que +`Sinatra::Base` prend tout son intérêt : + +```ruby +require 'sinatra/base' + +class MonApplication < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Bonjour le monde !' + end +end +``` + +Les méthodes de la classe `Sinatra::Base` sont parfaitement identiques à +celles disponibles via le DSL de haut niveau. Il suffit de deux modifications +pour transformer la plupart des applications de haut niveau en un composant +`Sinatra::Base` : + +* Votre fichier doit charger `sinatra/base` au lieu de `sinatra`, sinon toutes + les méthodes du DSL Sinatra seront importées dans l'espace de nom principal. +* Les gestionnaires de routes, la gestion d'erreur, les filtres et les options + doivent être placés dans une classe héritant de `Sinatra::Base`. + +`Sinatra::Base` est une page blanche. La plupart des options sont +désactivées par défaut, y compris le serveur intégré. Reportez-vous à +[Options et Configuration](http://www.sinatrarb.com/configuration.html) +pour plus d'informations sur les options et leur fonctionnement. Si vous +souhaitez un comportement plus proche de celui obtenu lorsque vous définissez +votre application au niveau supérieur (aussi connu sous le nom de style +Classique), vous pouvez créer une classe héritant de `Sinatra::Application`. + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Bonjour le monde !' + end +end +``` + +### Style modulaire vs. style classique + +Contrairement aux idées reçues, il n'y a rien de mal à utiliser le style +classique. Si c'est ce qui convient pour votre application, vous n'avez +aucune raison de passer à une application modulaire. + +Le principal inconvénient du style classique sur le style modulaire est que vous +ne pouvez avoir qu'une application par processus Ruby. Si vous pensez en +utiliser plus, passez au style modulaire. Et rien ne vous empêche de mixer style +classique et style modulaire. + +Si vous passez d'un style à l'autre, souvenez-vous des quelques différences +mineures en ce qui concerne les paramètres par défaut : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ParamètreClassiqueModulaireModulaire
    app_filefichier chargeant sinatrafichier héritant de Sinatra::Basefichier héritant de Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Servir une application modulaire + +Il y a deux façons de faire pour démarrer une application modulaire, démarrez +avec `run!` : + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... code de l'application ici ... + + # démarre le serveur si ce fichier est directement exécuté + run! if app_file == $0 +end +``` + +Démarrez ensuite avec : + +```shell +ruby my_app.rb +``` + +Ou alors avec un fichier `config.ru`, qui permet d'utiliser n'importe +quel gestionnaire Rack : + +```ruby +# config.ru +require './my_app' +run MyApp +``` + +Exécutez : + +```shell +rackup -p 4567 +``` + +### Utiliser une application de style classique avec un fichier config.ru + +Ecrivez votre application : + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Et un fichier `config.ru` correspondant : + +```ruby +require './app' +run Sinatra::Application +``` + +### Quand utiliser un fichier config.ru ? + +Quelques cas où vous devriez utiliser un fichier `config.ru` : + +* Vous souhaitez déployer avec un autre gestionnaire Rack (Passenger, Unicorn, + Heroku, ...). +* Vous souhaitez utiliser plus d'une sous-classe de `Sinatra::Base`. +* Vous voulez utiliser Sinatra comme un middleware, non en tant que + endpoint. + +**Il n'est pas nécessaire de passer par un fichier `config.ru` pour la +seule raison que vous êtes passé au style modulaire, et vous n'avez pas besoin +de passer au style modulaire pour utiliser un fichier `config.ru`.** + +### Utiliser Sinatra comme Middleware + +Non seulement Sinatra peut utiliser d'autres middlewares Rack, il peut +également être à son tour utilisé au-dessus de n'importe quel endpoint Rack +en tant que middleware. Cet endpoint peut très bien être une autre +application Sinatra, ou n'importe quelle application basée sur Rack +(Rails/Ramaze/Camping/...) : + +```ruby +require 'sinatra/base' + +class EcranDeConnexion < Sinatra::Base + enable :sessions + + get('/connexion') { haml :connexion } + + post('/connexion') do + if params['nom'] = 'admin' && params['motdepasse'] = 'admin' + session['nom_utilisateur'] = params['nom'] + else + redirect '/connexion' + end + end +end + +class MonApp < Sinatra::Base + # le middleware sera appelé avant les filtres + use EcranDeConnexion + + before do + unless session['nom_utilisateur'] + halt "Accès refusé, merci de vous connecter." + end + end + + get('/') { "Bonjour #{session['nom_utilisateur']}." } +end +``` + +### Création dynamique d'applications + +Il se peut que vous ayez besoin de créer une nouvelle application à l'exécution +sans avoir à les assigner à une constante, vous pouvez le faire grâce à +`Sinatra.new` : + +```ruby +require 'sinatra/base' +mon_app = Sinatra.new { get('/') { "salut" } } +mon_app.run! +``` + +L'application dont elle hérite peut être passé en argument optionnel : + +```ruby +# config.ru +require 'sinatra/base' + +controleur = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controleur) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controleur) { get('/') { 'b' } } +end +``` + +C'est notamment utile pour tester des extensions pour Sinatra ou bien pour +utiliser Sinatra dans votre propre bibliothèque. + +Cela permet également d'utiliser très facilement Sinatra comme middleware : + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Contextes et Binding + +Le contexte dans lequel vous êtes détermine les méthodes et variables +disponibles. + +### Contexte de l'application/classe + +Une application Sinatra correspond à une sous-classe de `Sinatra::Base`. Il +s'agit de `Sinatra::Application` si vous utilisez le DSL de haut niveau +(`require 'sinatra'`). Sinon c'est la sous-classe que vous avez définie. Dans +le contexte de cette classe, vous avez accès aux méthodes telles que `get` ou +`before`, mais pas aux objets `request` ou `session` étant donné que toutes +les requêtes sont traitées par une seule classe d'application. + +Les options définies au moyen de `set` deviennent des méthodes de classe : + +```ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Eh, je ne suis plus dans le contexte de l'application ! + end +end +``` + +Vous avez le binding du contexte de l'application dans : + +* Le corps de la classe d'application +* Les méthodes définies par les extensions +* Le bloc passé à `helpers` +* Les procs/blocs utilisés comme argument pour `set` +* Le bloc passé à `Sinatra.new` + +Vous pouvez atteindre ce contexte (donc la classe) de la façon suivante : + +* Via l'objet passé dans les blocs `configure` (`configure { |c| ... }`) +* En utilisant `settings` dans le contexte de la requête + +### Contexte de la requête/instance + +Pour chaque requête traitée, une nouvelle instance de votre classe +d'application est créée et tous vos gestionnaires sont exécutés dans ce +contexte. Depuis celui-ci, vous pouvez accéder aux objets `request` et +`session` ou faire appel aux fonctions de rendu telles que `erb` ou `haml`. +Vous pouvez accéder au contexte de l'application depuis le contexte de la +requête au moyen de `settings` : + +```ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + get '/ajouter_route/:nom' do + # Contexte de la requête pour '/ajouter_route/:nom' + @value = 42 + + settings.get("/#{params['nom']}") do + # Contexte de la requête pour "/#{params['nom']}" + @value # => nil (on est pas au sein de la même requête) + end + + "Route ajoutée !" + end +end +``` + +Vous avez le binding du contexte de la requête dans : + +* les blocs get, head, post, put, delete, options, patch, link et unlink +* les filtres before et after +* les méthodes utilitaires (définies au moyen de `helpers`) +* les vues et templates + +### Le contexte de délégation + +Le contexte de délégation se contente de transmettre les appels de méthodes au +contexte de classe. Toutefois, il ne se comporte pas à 100% comme le contexte +de classe car vous n'avez pas le binding de la classe : seules les méthodes +spécifiquement déclarées pour délégation sont disponibles et il n'est pas +possible de partager de variables/états avec le contexte de classe +(comprenez : `self` n'est pas le même). Vous pouvez ajouter des délégations de +méthode en appelant `Sinatra::Delegator.delegate :method_name`. + +Vous avez le binding du contexte de délégation dans : + +* Le binding de haut niveau, si vous avez utilisé `require "sinatra"` +* Un objet qui inclut le module `Sinatra::Delegator` + +Pour vous faire une idée, vous pouvez jeter un coup d'oeil au +[mixin Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +qui [étend l'objet principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Ligne de commande + +Les applications Sinatra peuvent être lancées directement : + +```shell +ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR] +``` + +Avec les options : + +``` +-h # aide +-p # déclare le port (4567 par défaut) +-o # déclare l'hôte (0.0.0.0 par défaut) +-e # déclare l'environnement (development par défaut) +-s # déclare le serveur/gestionnaire à utiliser (thin par défaut) +-x # active le mutex lock (off par défaut) +``` + +### Multi-threading + +_Cette partie est basée sur [une réponse StackOverflow][so-answer] de Konstantin._ + +Sinatra n'impose pas de modèle de concurrence. Sinatra est thread-safe, vous pouvez +donc utiliser n'importe quel gestionnaire Rack, comme Thin, Puma ou WEBrick en mode +multi-threaded. + +Cela signifie néanmoins qu'il vous faudra spécifier les paramètres correspondant au +gestionnaire Rack utilisé lors du démarrage du serveur. + +L'exemple ci-dessous montre comment vous pouvez exécuter un serveur Thin de manière +multi-threaded: + +``` +# app.rb +require 'sinatra/base' + +classe App < Sinatra::Base + get '/' do + 'Bonjour le monde !' + end +end + +App.run! +``` + +Pour démarrer le serveur, exécuter la commande suivante: + +``` +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## Configuration nécessaire + +Les versions suivantes de Ruby sont officiellement supportées : + +
    +
    Ruby 2.2
    +
    + 2.2 est totalement supporté et recommandé. L'abandon de son support + officiel n'est pas à l'ordre du jour. +
    + +
    Rubinius
    +
    + Rubinius est officiellement supporté (Rubinius >= 2.x). Un gem install + puma est recommandé. +
    + +
    JRuby
    +
    + La dernière version stable de JRuby est officiellement supportée. Il est + déconseillé d'utiliser des extensions C avec JRuby. Un gem install + trinidad est recommandé. +
    +
    + +Les versions antérieures à 2.2.2 ne sont plus supportées depuis Sinatra 2.0. + +Nous gardons également un oeil sur les versions Ruby à venir. + +Les implémentations Ruby suivantes ne sont pas officiellement supportées mais +sont malgré tout connues pour permettre de faire fonctionner Sinatra : + +* Versions plus anciennes de JRuby et Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation) + +Le fait de ne pas être officiellement supporté signifie que si quelque chose +ne fonctionne pas sur cette plateforme uniquement alors c'est un problème de la +plateforme et pas un bug de Sinatra. + +Nous lançons également notre intégration continue (CI) avec ruby-head (la +future 2.1.0), mais nous ne pouvont rien garantir étant donné les évolutions +continuelles. La version 2.1.0 devrait être totalement supportée. + +Sinatra devrait fonctionner sur n'importe quel système d'exploitation +supporté par l'implémentation Ruby choisie. + +Si vous utilisez MacRuby, vous devriez `gem install control_tower`. + +Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, BlueRuby ou +toute version de Ruby antérieure à 1.8.7 à l'heure actuelle. + +## Essuyer les plâtres + +Si vous souhaitez tester la toute dernière version de Sinatra, n'hésitez pas +à faire tourner votre application sur la branche master, celle-ci devrait être +stable. + +Pour cela, la méthode la plus simple est d'installer une gem de prerelease que +nous publions de temps en temps : + +```shell +gem install sinatra --pre +``` +Ce qui permet de bénéficier des toutes dernières fonctionnalités. + +### Installer avec Bundler + +Il est cependant conseillé de passer par [Bundler](http://bundler.io) pour +faire tourner votre application avec la dernière version de Sinatra. + +Pour commencer, installez bundler si nécessaire : + +```shell +gem install bundler +``` + +Ensuite, créez un fichier `Gemfile` dans le dossier de votre projet : + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# autres dépendances +gem 'haml' # si par exemple vous utilisez haml +gem 'activerecord', '~> 3.0' # au cas où vous auriez besoin de ActiveRecord 3.x +``` + +Notez que vous devez lister toutes les dépendances de votre application dans +ce fichier `Gemfile`. Les dépendances directes de Sinatra (Rack et Tilt) seront +automatiquement téléchargées et ajoutées par Bundler. + +Vous pouvez alors lancer votre application de la façon suivante : + +```shell +bundle exec ruby myapp.rb +``` + +## Versions + +Sinatra se conforme aux [versions sémantiques](http://semver.org/), aussi bien +SemVer que SemVerTag. + +## Mais encore + +* [Site internet](http://www.sinatrarb.com/) - Plus de documentation, + de news, et des liens vers d'autres ressources. +* [Contribuer](http://www.sinatrarb.com/contributing) - Vous avez trouvé un + bug ? Besoin d'aide ? Vous avez un patch ? +* [Suivi des problèmes](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](http://groups.google.com/group/sinatrarb/topics) +* IRC : [#sinatra](irc://chat.freenode.net/#sinatra) sur http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutoriels et recettes +* [Sinatra Recipes](http://recipes.sinatrarb.com/) trucs et astuces rédigés par + la communauté +* Documentation API de la [dernière version](http://www.rubydoc.info/gems/sinatra) + ou du [HEAD courant](http://www.rubydoc.info/github/sinatra/sinatra) sur + http://www.rubydoc.info/ +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.hu.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.hu.md new file mode 100644 index 0000000000..f6c8239d2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.hu.md @@ -0,0 +1,728 @@ +# Sinatra +*Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű +változatnak, és lehet, hogy nem naprakész.* + +A Sinatra egy [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) +webalkalmazások Ruby nyelven történő fejlesztéséhez, minimális +energiabefektetéssel: + +```ruby + # myapp.rb + require 'sinatra' + get '/' do + 'Helló Világ!' + end +``` + +Telepítsd a gem-et és indítsd el az alkalmazást a következőképpen: + +```ruby + sudo gem install sinatra + ruby myapp.rb +``` + +Az alkalmazás elérhető lesz itt: [http://localhost:4567](http://localhost:4567) + +## Útvonalak (routes) + +A Sinatrában az útvonalat egy HTTP metódus és egy URL-re illeszkedő minta +párosa alkotja. Minden egyes útvonalhoz tartozik egy blokk: + +```ruby + get '/' do + .. megjelenítünk valamit .. + end + + post '/' do + .. létrehozunk valamit .. + end + + put '/' do + .. frissítünk valamit .. + end + + delete '/' do + .. törlünk valamit .. + end +``` + +Az útvonalak illeszkedését a rendszer a definiálásuk sorrendjében +ellenőrzi. Sorrendben mindig az első illeszkedő útvonalhoz tartozó metódus kerül +meghívásra. + +Az útvonalminták tartalmazhatnak paramétereket is, melyeket a `params` +hash-ből érhetünk el: + +```ruby + get '/hello/:name' do + # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra + # ekkor params['name'] értéke 'foo' vagy 'bar' lesz + "Helló #{params['name']}!" + end +``` + +A kulcsszavas argumentumokat (named parameters) blokk paraméterek útján +is el tudod érni: + +```ruby + get '/hello/:name' do |n| + "Helló #{n}!" + end +``` + +Az útvonalmintákban szerepelhetnek joker paraméterek is, melyeket a +`params['splat']` tömbön keresztül tudunk elérni. + +```ruby + get '/say/*/to/*' do + # illeszkedik a /say/hello/to/world mintára + params['splat'] # => ["hello", "world"] + end + + get '/download/*.*' do + # illeszkedik a /download/path/to/file.xml mintára + params['splat'] # => ["path/to/file", "xml"] + end +``` + +Reguláris kifejezéseket is felvehetünk az útvonalba: + +```ruby + get /\/hello\/([\w]+)/ do + "Helló, #{params['captures'].first}!" + end +``` + +Vagy blokk paramétereket: + +```ruby + get %r{/hello/([\w]+)} do |c| + "Helló, #{c}!" + end +``` + +Az útvonalak azonban számos egyéb illeszkedési feltétel szerint is +tervezhetők, így például az user agent karakterláncot alapul véve: + +```ruby + get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "A Songbird #{params['agent'][0]} verzióját használod" + end + + get '/foo' do + # illeszkedik az egyéb user agentekre + end +``` + +## Statikus állományok + +A statikus fájlok kiszolgálása a `./public` könyvtárból +történik, de természetesen más könyvtárat is megadhatsz erre a célra, +mégpedig a :public_folder kapcsoló beállításával: + + set :public_folder, __dir__ + '/static' + +Fontos megjegyezni, hogy a nyilvános könyvtár neve nem szerepel az URL-ben. +A ./public/css/style.css fájl az +`http://example.com/css/style.css` URL-en lesz elérhető. + +## Nézetek és Sablonok + +A sablonfájlokat rendszerint a `./views` könyvtárba helyezzük, de +itt is lehetőség nyílik egyéb könyvtár használatára: + + set :views, __dir__ + '/templates' + +Nagyon fontos észben tartani, hogy a sablononkra mindig szimbólumokkal +hivatkozunk, még akkor is, ha egyéb (ebben az esetben a +:'subdir/template') könyvtárban tároljuk őket. A renderelő +metódusok minden, nekik közvetlenül átadott karakterláncot megjelenítenek. + +### Haml sablonok + +HAML sablonok rendereléséhez szükségünk lesz a haml gem-re vagy könyvtárra: + +```ruby + # Importáljuk be a haml-t az alkalmazásba + require 'haml' + + get '/' do + haml :index + end +``` + +Ez szépen lerendereli a `./views/index.haml` sablont. + +A [Haml kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml + + get '/' do + haml :index, :haml_options => {:format => :html4 } # immár felülírva + end +``` + +### Erb sablonok + + # Importáljuk be az erb-t az alkalmazásba + +```ruby + require 'erb' + + get '/' do + erb :index + end +``` + +Ez a `./views/index.erb` sablont fogja lerenderelni. + +### Builder sablonok + +Szükségünk lesz a builder gem-re vagy könyvtárra a builder sablonok +rendereléséhez: + + # Importáljuk be a builder-t az alkalmazásba + +```ruby + require 'builder' + + get '/' do + builder :index + end +``` + +Ez pedig a `./views/index.builder` állományt fogja renderelni. + +### Sass sablonok + +Sass sablonok használatához szükség lesz a haml gem-re vagy könyvtárra: + + # Be kell importálni a haml, vagy a sass könyvtárat + +```ruby + require 'sass' + + get '/stylesheet.css' do + sass :stylesheet + end +``` + +Így a `./views/stylesheet.sass` fájl máris renderelhető. + +A [Sass kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested + + get '/stylesheet.css' do + sass :stylesheet, :sass_options => {:style => :expanded } # felülírva + end +``` + +### Beágyazott sablonok + +```ruby + get '/' do + haml '%div.title Helló Világ' + end +``` + +Lerendereli a beágyazott sablon karakerláncát. + +### Változók elérése a sablonokban + +A sablonok ugyanabban a kontextusban kerülnek kiértékelésre, mint az +útvonal metódusok (route handlers). Az útvonal metódusokban megadott +változók közvetlenül elérhetőek lesznek a sablonokban: + +```ruby + get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' + end +``` + +De megadhatod egy lokális változókat tartalmazó explicit hash-ben is: + +```ruby + get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } + end +``` + +Ezt leginkább akkor érdemes megtenni, ha partial-eket akarunk renderelni +valamely más sablonból. + +### Fájlon belüli sablonok + +Sablonokat úgy is megadhatunk, hogy egyszerűen az alkalmazás fájl +végére begépeljük őket: + +```ruby + require 'rubygems' + require 'sinatra' + + get '/' do + haml :index + end + + __END__ + + @@ layout + %html + = yield + + @@ index + %div.title Helló Világ!!!!! +``` + +Megjegyzés: azok a fájlon belüli sablonok, amelyek az alkalmazás fájl végére +kerülnek és függnek a sinatra könyvtártól, automatikusan betöltődnek. +Ha ugyanezt más alkalmazásfájlban is szeretnéd megtenni, hívd meg +a use_in_file_templates! metódust az adott fájlban. + +### Kulcsszavas sablonok + +Sablonokat végül a felsőszintű template metódussal is +definiálhatunk: + +```ruby + template :layout do + "%html\n =yield\n" + end + + template :index do + '%div.title Helló Világ!' + end + + get '/' do + haml :index + end +``` + +Ha létezik "layout" nevű sablon, akkor az minden esetben meghívódik, amikor +csak egy sablon renderelésre kerül. A layoutokat ki lehet kapcsolni a +`:layout => false` meghívásával. + +```ruby + get '/' do + haml :index, :layout => !request.xhr? + end +``` + +## Helperek + +Használd a felső szintű helpers metódust azokhoz a helper +függvényekhez, amiket az útvonal metódusokban és a sablonokban akarsz +használni: + +```ruby + helpers do + def bar(name) + "#{name}bar" + end + end + + get '/:name' do + bar(params['name']) + end +``` + +## Szűrők (filters) + +Az előszűrők (before filter) az adott hívás kontextusában minden egyes +kérés alkalmával kiértékelődnek, így módosíthatják a kérést és a +választ egyaránt. A szűrőkbe felvett példányváltozók elérhetőek lesznek +az útvonalakban és a sablonokban is: + +```ruby + before do + @note = 'Csá!' + request.path_info = '/foo/bar/baz' + end + + get '/foo/*' do + @note #=> 'Szeva!' + params['splat'] #=> 'bar/baz' + end +``` + +Az utószűrők az egyes kérések után, az adott kérés kontextusában kerülnek +kiértékelésre, így ezek is képesek módosítani a kérést és a választ egyaránt. +Az előszűrőkben és úvonalakban létrehozott példányváltozók elérhetőek lesznek +az utószűrők számára: + +```ruby + after do + puts response.status + end +``` + +## Megállítás + +Egy kérés szűrőben vagy útvonalban történő azonnal blokkolásához +használd a következő parancsot: + + halt + +A megállításkor egy blokktörzset is megadhatsz ... + + halt 'ez fog megjelenni a törzsben' + +Vagy állítsd be a HTTP státuszt és a törzset is egyszerre ... + + halt 401, 'menj innen!' + +## Passzolás + +Az útvonalak továbbadhatják a végrehajtást egy másik útvonalnak +a `pass` függvényhívással: + +```ruby + get '/guess/:who' do + pass unless params['who'] == 'Frici' + "Elkaptál!" + end + + get '/guess/*' do + "Elhibáztál!" + end +``` + +Az útvonal blokkja azonnal kilép és átadja a vezérlést a következő +illeszkedő útvonalnak. Ha nem talál megfelelő útvonalat, a Sinatra +egy 404-es hibával tér vissza. + +## Beállítások + +Csak indításkor, de minden környezetre érvényesen fusson le: + +```ruby + configure do + ... + end +``` + +Csak akkor fusson le, ha a környezet (a APP_ENV környezeti változóban) +`:production`-ra van állítva: + +```ruby + configure :production do + ... + end +``` + +Csak akkor fusson le, ha a környezet :production vagy :test: + +```ruby + configure :production, :test do + ... + end +``` + +## Hibakezelés + +A hibakezelők ugyanabban a kontextusban futnak le, mint az útvonalak és +előszűrők, ezért számukra is elérhetőek mindazok a könyvtárak, amelyek +az utóbbiak rendelkezésére is állnak; így például a `haml`, +az `erb`, a `halt` stb. + +### Nem található + +Amikor a `Sinatra::NotFound` kivétel fellép, vagy a válasz HTTP +státuszkódja 404-es, mindig a `not_found` metódus hívódik meg. + +```ruby + not_found do + 'Sehol sem találom, amit keresel' + end +``` + +### Hiba + +Az `error` metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy +előszűrő kivételt vált ki. A kivétel objektum lehívható a +`sinatra.error` Rack változótól: + +```ruby + error do + 'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].message + end +``` + +Egyéni hibakezelés: + +```ruby + error MyCustomError do + 'Szóval az van, hogy...' + env['sinatra.error'].message + end +``` + +És amikor fellép: + +```ruby + get '/' do + raise MyCustomError, 'valami nem stimmel!' + end +``` + +Ez fog megjelenni: + + Szóval az van, hogy... valami nem stimmel! + +A Sinatra speciális `not_found` és `error` hibakezelőket +használ, amikor a futtatási környezet fejlesztői módba van kapcsolva. + +## Mime típusok + +A `send_file` metódus használatakor, vagy statikus fájlok +kiszolgálásakor előfordulhat, hogy a Sinatra nem ismeri fel a fájlok +mime típusát. Ilyenkor használd a +mime_type+ kapcsolót a fájlkiterjesztés +bevezetéséhez: + +```ruby + mime_type :foo, 'text/foo' +``` + +## Rack Middleware + +A Sinatra egy Ruby keretrendszerek számára kifejlesztett egyszerű és szabványos +interfészre, a [Rack](http://rack.github.io/) -re épül. A Rack fejlesztői +szempontból egyik legérdekesebb jellemzője, hogy támogatja az úgynevezett +"middleware" elnevezésű komponenseket, amelyek beékelődnek a szerver és az +alkalmazás közé, így képesek megfigyelni és/vagy módosítani a HTTP +kéréseket és válaszokat. Segítségükkel különféle, egységesen működő +funkciókat építhetünk be rendszerünkbe. + +A Sinatra keretrendszerben gyerekjáték a Rack middleware-ek behúzása a +`use` metódus segítségével: + +```ruby + require 'sinatra' + require 'my_custom_middleware' + + use Rack::Lint + use MyCustomMiddleware + + get '/hello' do + 'Helló Világ' + end +``` + +A `use` metódus szemantikája megegyezik a +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL-ben +használt +use+ metóduséval (az említett DSL-t leginkább rackup állományokban +használják). Hogy egy példát említsünk, a `use` metódus elfogad +változókat és blokkokat egyaránt, akár kombinálva is ezeket: + +```ruby + use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'titkos' + end +``` + +A Rack terjesztéssel egy csomó alap middleware komponens is érkezik, +amelyekkel a naplózás, URL útvonalak megadása, autentikáció és +munkamenet-kezelés könnyen megvalósítható. A Sinatra ezek közül elég +sokat automatikusan felhasznál a beállításoktól függően, így ezek +explicit betöltésével (+use+) nem kell bajlódnod. + +## Tesztelés + +Sinatra teszteket bármely Rack alapú tesztelő könyvtárral vagy +keretrendszerrel készíthetsz. Mi a [Rack::Test](http://gitrdoc.com/brynary/rack-test) +könyvtárat ajánljuk: + +```ruby + require 'my_sinatra_app' + require 'rack/test' + + class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Helló Világ!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frici' + assert_equal 'Helló Frici!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbird-öt használsz!", last_response.body + end + end +``` + +Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok +a 0.9.2-es kiadástól kezdve elavultnak számítanak. + +## Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások + +Az alkalmazást felső szinten építeni megfelelhet mondjuk egy kisebb +app esetén, ám kifejezetten károsnak bizonyulhat olyan komolyabb, +újra felhasználható komponensek készítésekor, mint például egy Rack +middleware, Rails metal, egyszerűbb kiszolgáló komponenssel bíró +könyvtárak vagy éppen Sinatra kiterjesztések. A felső szintű DSL +bepiszkítja az Objektum névteret, ráadásul kisalkalmazásokra szabott +beállításokat feltételez (így például egyetlen alkalmazásfájl, +`./public` +és `./views` könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). +Itt jön a képbe a Sinatra::Base osztály: + +```ruby + require 'sinatra/base' + + class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Helló Világ!' + end + end +``` + +A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként +vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül +használhatod (`use`) vagy futtathatod (`run`) az osztályodat egy rackup +konfigurációs állományban (`config.ru`), vagy egy szerverkomponenst +tartalmazó könyvtár vezérlésekor: + +```ruby + MyApp.run! :host => 'localhost', :port => 9090 +``` + +A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső +szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű +alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben: + +* A fájlban nem a `sinatra`, hanem a `sinatra/base` osztályt kell + beimportálni, mert egyébként az összes Sinatra DSL metódus a fő + névtérbe kerül. +* Az alkalmazás útvonalait, hibakezelőit, szűrőit és beállításait + a Sinatra::Base osztály gyermekosztályaiban kell megadni. + +A `Sinatra::Base` osztály igazából egy üres lap: a legtöbb funkció +alapból ki van kapcsolva, beleértve a beépített szervert is. A +beállításokkal és az egyes kapcsolók hatásával az +[Options and Configuration](http://www.sinatrarb.com/configuration.html) lap +foglalkozik. + +Széljegyzet: A Sinatra felső szintű DSL-je egy egyszerű delegációs +rendszerre épül. A Sinatra::Application osztály - a Sinatra::Base egy +speciális osztályaként - fogadja az összes :get, :put, :post, +:delete, :before, :error, :not_found, :configure és :set üzenetet, +ami csak a felső szintre beérkezik. Érdemes utánanézned a kódban, +miképp [kerül be](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25) +a [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064) +a fő névtérbe. + +## Parancssori lehetőségek + +Sinatra alkalmazásokat közvetlenül futtathatunk: + +``` + ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER] +``` + +Az alábbi kapcsolókat ismeri fel a rendszer: + + -h # segítség + -p # a port beállítása (alapértelmezés szerint ez a 4567-es) + -e # a környezet beállítása (alapértelmezés szerint ez a development) + -s # a rack szerver/handler beállítása (alapértelmezetten ez a thin) + -x # a mutex lock bekapcsolása (alapértelmezetten ki van kapcsolva) + +## Több szálon futtatás + +_Parafrázis [Konstantin StackOverflow válasza][so-answer] alapján_ + +A Sinatra nem szabja meg az konkurenciakezelés módját, hanem az alatta működő +Rack kezelőre (szerverre) hagyja ezt a feladatot, ami például a Thin, a Puma, +vagy a WEBrick. A Sinatra önmagában szálbiztos, tehát semmilyen probléma sem +adódik, ha a Rack kezelő többszálú konkurenciamodellt használ. Ezek szerint +szerverindításkor meg kell adni a Rack szervernek megfelelő indítási módot. +A következő példa egy többszálú Thin szerver indítását mutatja be. + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +A szerverindítás parancsa a következő lenne: + +``` shell +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/a/6282999/1725341 + +## Fejlesztői változat + +Ha a Sinatra legfrissebb, fejlesztői változatát szeretnéd használni, +készíts egy helyi másolatot és indítsd az alkalmazásodat úgy, +hogy a `sinatra/lib` könyvtár elérhető legyen a +`LOAD_PATH`-on: + +``` + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb +``` + +De hozzá is adhatod a sinatra/lib könyvtárat a LOAD_PATH-hoz +az alkalmazásodban: + +```ruby + $LOAD_PATH.unshift __dir__ + '/sinatra/lib' + require 'rubygems' + require 'sinatra' + + get '/about' do + "A következő változatot futtatom " + Sinatra::VERSION + end +``` + +A Sinatra frissítését később így végezheted el: + +``` + cd myproject/sinatra + git pull +``` + +## További információk + +* [A projekt weboldala](http://www.sinatrarb.com/) - Kiegészítő dokumentáció, + hírek, hasznos linkek +* [Közreműködés](http://www.sinatrarb.com/contributing.html) - Hibát találtál? + Segítségre van szükséged? Foltot küldenél be? +* [Lighthouse](http://sinatra.lighthouseapp.com) - Hibakövetés és kiadások +* [Twitter](https://twitter.com/sinatra) +* [Levelezőlista](http://groups.google.com/group/sinatrarb) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) a http://freenode.net címen diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ja.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ja.md new file mode 100644 index 0000000000..99aff979d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ja.md @@ -0,0 +1,2814 @@ +# Sinatra + +*注) +本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照してください。* + +Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るための[DSL](https://ja.wikipedia.org/wiki/メインページドメイン固有言語)です。 + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +gemをインストールし、 + +```shell +gem install sinatra +``` + +次のように実行します。 + +```shell +ruby myapp.rb +``` + +[http://localhost:4567](http://localhost:4567) を開きます。 + +ThinがあればSinatraはこれを利用するので、`gem install thin`することをお薦めします。 + +## 目次 + +* [Sinatra](#sinatra) + * [目次](#目次) + * [ルーティング(Routes)](#ルーティングroutes) + * [条件(Conditions)](#条件conditions) + * [戻り値(Return Values)](#戻り値return-values) + * [カスタムルーティングマッチャー(Custom Route Matchers)](#カスタムルーティングマッチャーcustom-route-matchers) + * [静的ファイル(Static Files)](#静的ファイルstatic-files) + * [ビュー / テンプレート(Views / Templates)](#ビュー--テンプレートviews--templates) + * [リテラルテンプレート(Literal Templates)](#リテラルテンプレートliteral-templates) + * [利用可能なテンプレート言語](#利用可能なテンプレート言語) + * [Haml テンプレート](#haml-テンプレート) + * [Erb テンプレート](#erb-テンプレート) + * [Builder テンプレート](#builder-テンプレート) + * [Nokogiri テンプレート](#nokogiri-テンプレート) + * [Sass テンプレート](#sass-テンプレート) + * [SCSS テンプレート](#scss-テンプレート) + * [Less テンプレート](#less-テンプレート) + * [Liquid テンプレート](#liquid-テンプレート) + * [Markdown テンプレート](#markdown-テンプレート) + * [Textile テンプレート](#textile-テンプレート) + * [RDoc テンプレート](#rdoc-テンプレート) + * [AsciiDoc テンプレート](#asciidoc-テンプレート) + * [Radius テンプレート](#radius-テンプレート) + * [Markaby テンプレート](#markaby-テンプレート) + * [RABL テンプレート](#rabl-テンプレート) + * [Slim テンプレート](#slim-テンプレート) + * [Creole テンプレート](#creole-テンプレート) + * [MediaWiki テンプレート](#mediawiki-テンプレート) + * [CoffeeScript テンプレート](#coffeescript-テンプレート) + * [Stylus テンプレート](#stylus-テンプレート) + * [Yajl テンプレート](#yajl-テンプレート) + * [WLang テンプレート](#wlang-テンプレート) + * [テンプレート内での変数へのアクセス](#テンプレート内での変数へのアクセス) + * [`yield`を伴うテンプレートとネストしたレイアウト](#yieldを伴うテンプレートとネストしたレイアウト) + * [インラインテンプレート(Inline Templates)](#インラインテンプレートinline-templates) + * [名前付きテンプレート(Named Templates)](#名前付きテンプレートnamed-templates) + * [ファイル拡張子の関連付け](#ファイル拡張子の関連付け) + * [オリジナルテンプレートエンジンの追加](#オリジナルテンプレートエンジンの追加) + * [カスタムロジックを使用したテンプレートの探索](#カスタムロジックを使用したテンプレートの探索) + * [フィルタ(Filters)](#フィルタfilters) + * [ヘルパー(Helpers)](#ヘルパーhelpers) + * [セッションの使用](#セッションの使用) + * [セッションミドルウェアの選択](#セッションミドルウェアの選択) + * [停止(Halting)](#停止halting) + * [パッシング(Passing)](#パッシングpassing) + * [別ルーティングの誘発](#別ルーティングの誘発) + * [ボディ、ステータスコードおよびヘッダの設定](#ボディステータスコードおよびヘッダの設定) + * [ストリーミングレスポンス(Streaming Responses)](#ストリーミングレスポンスstreaming-responses) + * [ロギング(Logging)](#ロギングlogging) + * [MIMEタイプ(Mime Types)](#mimeタイプmime-types) + * [URLの生成](#urlの生成) + * [ブラウザリダイレクト(Browser Redirect)](#ブラウザリダイレクトbrowser-redirect) + * [キャッシュ制御(Cache Control)](#キャッシュ制御cache-control) + * [ファイルの送信](#ファイルの送信) + * [リクエストオブジェクトへのアクセス](#リクエストオブジェクトへのアクセス) + * [アタッチメント(Attachments)](#アタッチメントattachments) + * [日付と時刻の取り扱い](#日付と時刻の取り扱い) + * [テンプレートファイルの探索](#テンプレートファイルの探索) + * [コンフィギュレーション(Configuration)](#コンフィギュレーションconfiguration) + * [攻撃防御に対する設定](#攻撃防御に対する設定) + * [利用可能な設定](#利用可能な設定) + * [環境設定(Environments)](#環境設定environments) + * [エラーハンドリング(Error Handling)](#エラーハンドリングerror-handling) + * [Not Found](#not-found) + * [エラー(Error)](#エラーerror) + * [Rackミドルウェア(Rack Middleware)](#rackミドルウェアrack-middleware) + * [テスト(Testing)](#テストtesting) + * [Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ](#sinatrabase---ミドルウェアライブラリおよびモジュラーアプリ) + * [モジュラースタイル vs クラッシックスタイル](#モジュラースタイル-vs-クラッシックスタイル) + * [モジュラーアプリケーションの提供](#モジュラーアプリケーションの提供) + * [config.ruを用いたクラッシックスタイルアプリケーションの使用](#configruを用いたクラッシックスタイルアプリケーションの使用) + * [config.ruはいつ使うのか?](#configruはいつ使うのか) + * [Sinatraのミドルウェアとしての利用](#sinatraのミドルウェアとしての利用) + * [動的なアプリケーションの生成](#動的なアプリケーションの生成) + * [スコープとバインディング(Scopes and Binding)](#スコープとバインディングscopes-and-binding) + * [アプリケーション/クラスのスコープ](#アプリケーションクラスのスコープ) + * [リクエスト/インスタンスのスコープ](#リクエストインスタンスのスコープ) + * [デリゲートスコープ](#デリゲートスコープ) + * [コマンドライン](#コマンドライン) + * [マルチスレッド](#マルチスレッド) + * [必要環境](#必要環境) + * [最新開発版](#最新開発版) + * [Bundlerを使う場合](#bundlerを使う場合) + * [直接組み込む場合](#直接組み込む場合) + * [グローバル環境にインストールする場合](#グローバル環境にインストールする場合) + * [バージョニング(Versioning)](#バージョニングversioning) + * [参考文献](#参考文献) + +## ルーティング(Routes) + +Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。 +ルーティングはブロックに結び付けられています。 + +```ruby +get '/' do + .. 何か見せる .. +end + +post '/' do + .. 何か生成する .. +end + +put '/' do + .. 何か更新する .. +end + +patch '/' do + .. 何か修正する .. +end + +delete '/' do + .. 何か削除する .. +end + +options '/' do + .. 何か満たす .. +end + +link '/' do + .. 何かリンクを張る .. +end + +unlink '/' do + .. 何かアンリンクする .. +end +``` + +ルーティングは定義された順番にマッチします。 +リクエストに最初にマッチしたルーティングが呼び出されます。 + +トレイリングスラッシュを付けたルートは、そうでないルートと異なったものになります。 + +```ruby + get '/foo' do + # Does not match "GET /foo/" + end +``` + +ルーティングのパターンは名前付きパラメータを含むことができ、 +`params`ハッシュで取得できます。 + +```ruby +get '/hello/:name' do + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + "Hello #{params['name']}!" +end +``` + +また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。 + +```ruby +get '/hello/:name' do |n| + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + # n が params['name'] を保持 + "Hello #{n}!" +end +``` + +ルーティングパターンはアスタリスク(すなわちワイルドカード)を含むこともでき、 +`params['splat']` で取得できます。 + +```ruby +get '/say/*/to/*' do + # /say/hello/to/world にマッチ + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml にマッチ + params['splat'] # => ["path/to/file", "xml"] +end +``` + +ここで、ブロックパラメータを使うこともできます。 + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +ルーティングを正規表現にマッチさせることもできます。 + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +ここでも、ブロックパラメータが使えます。 + +```ruby +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +ルーティングパターンは、オプショナルパラメータを取ることもできます。 + +```ruby +get '/posts/:format?' do + # "GET /posts/" と "GET /posts/json", "GET /posts/xml" の拡張子などにマッチ +end +``` + +ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、 +ルーティングにマッチする前にリクエストパスが修正される可能性があります。 + +## 条件(Conditions) + +ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。 + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbirdのバージョン #{params['agent'][0]}を使ってます。" +end + +get '/foo' do + # Songbird以外のブラウザにマッチ +end +``` + +ほかに`host_name`と`provides`条件が利用可能です。 + +```ruby +get '/', :host_name => /^admin\./ do + "Adminエリアです。アクセスを拒否します!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +独自の条件を定義することも簡単にできます。 + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "あなたの勝ちです!" +end + +get '/win_a_car' do + "残念、あなたの負けです。" +end +``` + +複数の値を取る条件には、アスタリスクを使います。 + +```ruby +set(:auth) do |*roles| # <- ここでアスタリスクを使う + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "アカウントの詳細" +end + +get "/only/admin/", :auth => :admin do + "ここは管理者だけ!" +end +``` + +## 戻り値(Return Values) + +ルーティングブロックの戻り値は、HTTPクライアントまたはRackスタックでの次のミドルウェアに渡されるレスポンスボディを決定します。 + +これは大抵の場合、上の例のように文字列ですが、それ以外の値も使用することができます。 + +Rackレスポンス、Rackボディオブジェクト、HTTPステータスコードのいずれかとして妥当なオブジェクトであればどのようなオブジェクトでも返すことができます。 + +* 3つの要素を含む配列: + `[ステータス(Integer), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]` +* 2つの要素を含む配列: + `[ステータス(Integer), レスポンスボディ(#eachに応答する)]` +* `#each`に応答するオブジェクト。通常はそのまま何も返さないが、 +与えられたブロックに文字列を渡す。 +* ステータスコードを表現する整数(Integer) + +これにより、例えばストリーミングを簡単に実装することができます。 + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +後述する`stream`ヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。 + +## カスタムルーティングマッチャー(Custom Route Matchers) + +先述のようにSinatraはルーティングマッチャーとして、文字列パターンと正規表現を使うことをビルトインでサポートしています。しかしこれに留まらず、独自のマッチャーを簡単に定義することもできるのです。 + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +ノート: この例はオーバースペックであり、以下のようにも書くことができます。 + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +または、否定先読みを使って: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 静的ファイル(Static Files) + +静的ファイルは`./public`ディレクトリから配信されます。 +`:public_folder`オプションを指定することで別の場所を指定することができます。 + +```ruby +set :public_folder, __dir__ + '/static' +``` + +ノート: この静的ファイル用のディレクトリ名はURL中に含まれません。 +例えば、`./public/css/style.css`は`http://example.com/css/style.css`でアクセスできます。 + +`Cache-Control`の設定をヘッダーへ追加するには`:static_cache_control`の設定(下記参照)を加えてください。 + +## ビュー / テンプレート(Views / Templates) + +各テンプレート言語はそれ自身のレンダリングメソッドを通して展開されます。それらのメソッドは単に文字列を返します。 + +```ruby +get '/' do + erb :index +end +``` + +これは、`views/index.erb`をレンダリングします。 + +テンプレート名を渡す代わりに、直接そのテンプレートの中身を渡すこともできます。 + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。 + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +これは、`views/post.erb`内に埋め込まれた`views/index.erb`をレンダリングします(デフォルトは`views/layout.erb`があればそれになります)。 + +Sinatraが理解できないオプションは、テンプレートエンジンに渡されることになります。 + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +テンプレート言語ごとにオプションをセットすることもできます。 + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +レンダリングメソッドに渡されたオプションは`set`で設定されたオプションを上書きします。 + +利用可能なオプション: + +
    +
    locals
    +
    + ドキュメントに渡されるローカルのリスト。パーシャルに便利。 + 例: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + 文字エンコーディングが確実でない場合に指定。デフォルトは、settings.default_encoding。 +
    + +
    views
    +
    + テンプレートを読み出すビューのディレクトリ。デフォルトは、settings.views。 +
    + +
    layout
    +
    + レイアウトを使うかの指定(true または false)。値がシンボルの場合は、使用するテンプレートが指定される。例: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + テンプレートが生成するContent-Type。デフォルトはテンプレート言語ごとに異なる。 +
    + +
    scope
    +
    + テンプレートをレンダリングするときのスコープ。デフォルトは、アプリケーションのインスタンス。これを変更した場合、インスタンス変数およびヘルパーメソッドが利用できなくなる。 +
    + +
    layout_engine
    +
    + レイアウトをレンダリングするために使用するテンプレートエンジン。レイアウトをサポートしない言語で有用。デフォルトはテンプレートに使われるエンジン。例: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + レイアウトをレンダリングするときだけに使う特別なオプション。例: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +テンプレートは`./views`ディレクトリ下に配置されています。 +他のディレクトリを使用する場合の例: + +```ruby +set :views, settings.root + '/templates' +``` + +テンプレートの参照は、テンプレートがサブディレクトリ内にある場合でも常にシンボルで指定することを覚えておいてください。 +(これは`:'subdir/template'`または`'subdir/template'.to_sym`のように指定することを意味します。) +レンダリングメソッドにシンボルではなく文字列を渡してしまうと、そのまま文字列として出力してしまいます。 + +### リテラルテンプレート(Literal Templates) + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +これはテンプレート文字列をレンダリングしています。 +テンプレート文字列に関連するファイルパスや行数を`:path`や`:line`オプションで指定することで、バックトレースを明確にすることができます。 + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### 利用可能なテンプレート言語 + +いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。 + +```ruby +require 'rdiscount' # または require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml テンプレート + + + + + + + + + + + + + + +
    依存haml
    ファイル拡張子.haml
    haml :index, :format => :html5
    + +#### Erb テンプレート + + + + + + + + + + + + + + +
    依存 + erubi + または erubis + または erb (Rubyに同梱) +
    ファイル拡張子.erb, .rhtml または .erubi (Erubiだけ) または.erubis (Erubisだけ)
    erb :index
    + +#### Builder テンプレート + + + + + + + + + + + + + + +
    依存 + builder +
    ファイル拡張子.builder
    builder { |xml| xml.em "hi" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### Nokogiri テンプレート + + + + + + + + + + + + + + +
    依存nokogiri
    ファイル拡張子.nokogiri
    nokogiri { |xml| xml.em "hi" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### Sass テンプレート + + + + + + + + + + + + + + +
    依存sass
    ファイル拡張子.sass
    sass :stylesheet, :style => :expanded
    + +#### Scss テンプレート + + + + + + + + + + + + + + +
    依存sass
    ファイル拡張子.scss
    scss :stylesheet, :style => :expanded
    + +#### Less テンプレート + + + + + + + + + + + + + + +
    依存less
    ファイル拡張子.less
    less :stylesheet
    + +#### Liquid テンプレート + + + + + + + + + + + + + + +
    依存liquid
    ファイル拡張子.liquid
    liquid :index, :locals => { :key => 'value' }
    + +LiquidテンプレートからRubyのメソッド(`yield`を除く)を呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Markdown テンプレート + + + + + + + + + + + + + + +
    依存 + 次の何れか: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    ファイル拡張子.markdown, .mkd and .md
    markdown :index, :layout_engine => :erb
    + +Markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +ノート: 他のテンプレート内で`markdown`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### Textile テンプレート + + + + + + + + + + + + + + +
    依存RedCloth
    ファイル拡張子.textile
    textile :index, :layout_engine => :erb
    + +Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +ノート: 他のテンプレート内で`textile`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +TexttileからはRubyを呼ぶことができないので、Textileで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### RDoc テンプレート + + + + + + + + + + + + + + +
    依存RDoc
    ファイル拡張子.rdoc
    rdoc :README, :layout_engine => :erb
    + +RDocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +ノート: 他のテンプレート内で`rdoc`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDocからはRubyを呼ぶことができないので、RDocで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### AsciiDoc テンプレート + + + + + + + + + + + + + + +
    依存Asciidoctor
    ファイル拡張子.asciidoc, .adoc and .ad
    asciidoc :README, :layout_engine => :erb
    + +AsciiDocテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Radius テンプレート + + + + + + + + + + + + + + +
    依存Radius
    ファイル拡張子.radius
    radius :index, :locals => { :key => 'value' }
    + +RadiusテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Markaby テンプレート + + + + + + + + + + + + + + +
    依存Markaby
    ファイル拡張子.mab
    markaby { h1 "Welcome!" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### RABL テンプレート + + + + + + + + + + + + + + +
    依存Rabl
    ファイル拡張子.rabl
    rabl :index
    + +#### Slim テンプレート + + + + + + + + + + + + + + +
    依存Slim Lang
    ファイル拡張子.slim
    slim :index
    + +#### Creole テンプレート + + + + + + + + + + + + + + +
    依存Creole
    ファイル拡張子.creole
    creole :wiki, :layout_engine => :erb
    + +Creoleからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +ノート: 他のテンプレート内で`creole`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +CreoleからはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### MediaWiki テンプレート + + + + + + + + + + + + + + +
    依存WikiCloth
    ファイル拡張子.mediawiki および .mw
    mediawiki :wiki, :layout_engine => :erb
    + +MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。 + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +ノート: 他のテンプレートから部分的に`mediawiki`メソッドを呼び出すことも可能です。 + +#### CoffeeScript テンプレート + + + + + + + + + + + + + + +
    依存 + + CoffeeScript + および + + JavaScriptの起動方法 + +
    ファイル拡張子.coffee
    coffee :index
    + +#### Stylus テンプレート + + + + + + + + + + + + + + +
    依存 + + Stylus + および + + JavaScriptの起動方法 + +
    ファイル拡張子.styl
    stylus :index
    + +Stylusテンプレートを使えるようにする前に、まず`stylus`と`stylus/tilt`を読み込む必要があります。 + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl テンプレート + + + + + + + + + + + + + + +
    依存yajl-ruby
    ファイル拡張子.yajl
    + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +テンプレートのソースはRubyの文字列として評価され、その結果のJSON変数は`#to_json`を使って変換されます。 + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`および`:variable`オプションは、レンダリングされたオブジェクトを装飾するために使うことができます。 + +```ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +#### WLang テンプレート + + + + + + + + + + + + + + +
    依存wlang
    ファイル拡張子.wlang
    wlang :index, :locals => { :key => 'value' }
    + +WLang内でのRubyメソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトは`yield`をサポートしています。 + +### テンプレート内での変数へのアクセス + +テンプレートはルーティングハンドラと同じコンテキストの中で評価されます。ルーティングハンドラでセットされたインスタンス変数はテンプレート内で直接使うことができます。 + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +また、ローカル変数のハッシュで明示的に指定することもできます。 + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +これは他のテンプレート内で部分テンプレートとして表示する典型的な手法です。 + +### `yield`を伴うテンプレートとネストしたレイアウト + +レイアウトは通常、`yield`を呼ぶ単なるテンプレートに過ぎません。 +そのようなテンプレートは、既に説明した`:template`オプションを通して使われるか、または次のようなブロックを伴ってレンダリングされます。 + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +このコードは、`erb :index, :layout => :post`とほぼ等価です。 + +レンダリングメソッドにブロックを渡すスタイルは、ネストしたレイアウトを作るために最も役立ちます。 + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +これはまた次のより短いコードでも達成できます。 + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +現在、次のレンダリングメソッドがブロックを取れます: `erb`, `haml`, +`liquid`, `slim `, `wlang`。 +また汎用の`render`メソッドもブロックを取れます。 + +### インラインテンプレート(Inline Templates) + +テンプレートはソースファイルの最後で定義することもできます。 + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world!!!!! +``` + +ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合には`enable :inline_templates`を明示的に呼んでください。 + +### 名前付きテンプレート(Named Templates) + +テンプレートはトップレベルの`template`メソッドで定義することもできます。 + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +「layout」という名前のテンプレートが存在する場合は、そのテンプレートファイルは他のテンプレートがレンダリングされる度に使用されます。`:layout => false`で個別に、または`set :haml, :layout => false`でデフォルトとして、レイアウトを無効にすることができます。 + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### ファイル拡張子の関連付け + +任意のテンプレートエンジンにファイル拡張子を関連付ける場合は、`Tilt.register`を使います。例えば、Textileテンプレートに`tt`というファイル拡張子を使いたい場合は、以下のようにします。 + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### オリジナルテンプレートエンジンの追加 + +まず、Tiltでそのエンジンを登録し、次にレンダリングメソッドを作ります。 + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +これは、`./views/index.myat`をレンダリングします。Tiltについての詳細は、https://github.com/rtomayko/tilt を参照してください。 + +### カスタムロジックを使用したテンプレートの探索 + +オリジナルテンプレートの検索メカニズムを実装するためには、`#find_template`メソッドを実装します。 + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## フィルタ(Filters) + +beforeフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの前に評価され、それによってリクエストとレスポンスを変更可能にします。フィルタ内でセットされたインスタンス変数はルーティングとテンプレートからアクセスすることができます。 + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +afterフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの後に評価され、それによってこれもリクエストとレスポンスを変更可能にします。beforeフィルタとルーティング内でセットされたインスタンス変数はafterフィルタからアクセスすることができます。 + +```ruby +after do + puts response.status +end +``` + +ノート: `body`メソッドを使わずにルーティングから文字列を返すだけの場合、その内容はafterフィルタでまだ利用できず、その後に生成されることになります。 + +フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。 + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +ルーティング同様、フィルタもまた条件を取ることができます。 + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## ヘルパー(Helpers) + +トップレベルの`helpers`メソッドを使用してルーティングハンドラやテンプレートで使うヘルパーメソッドを定義できます。 + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +あるいは、ヘルパーメソッドをモジュール内で個別に定義することもできます。 + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。 + +### セッションの使用 + +セッションはリクエスト間での状態維持のために使用されます。セッションを有効化すると、ユーザセッションごとに一つのセッションハッシュが与えられます。 + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +ノート: `enable :sessions`は実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合は`enable :sessions`を呼ばずに、選択したミドルウェアを他のミドルウェアのときと同じようにして取り込んでください。 + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +セキュリティ向上のため、クッキー内のセッションデータはセッション秘密鍵(session secret)で署名されます。Sinatraによりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。 + +```ruby +set :session_secret, 'super secret' +``` + +更に、設定変更をしたい場合は、`sessions`の設定においてオプションハッシュを保持することもできます。 + +```ruby +set :sessions, :domain => 'foo.com' +``` + +foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に *.* を付けます。 + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### セッションミドルウェアの選択 + +`enable :sessions`とすることで、クッキー内の全てのデータを実際に保存してしまうことに注意してください。 +これは、あなたが望む挙動ではない(例えば、大量のデータを保存することでトラフィックが増大してしまう)かもしれません。 +あなたは、次のいずれかの方法によって、任意のRackセッションミドルウェアを使用することができます。 + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +オプションのハッシュを設定するためには、次のようにします。 + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +他の方法は`enable :sessions`を**しない**で、他のミドルウェアの選択と同様にあなた自身でミドルウェアを選択することです。 + +この方法を選択する場合は、セッションベースの保護は**デフォルトで有効にならない**ということに注意することが重要です。 + +これを満たすためのRackミドルウェアを追加することが必要になります。 + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +より詳しい情報は、「攻撃防御に対する設定」の項を参照してください。 + +### 停止(Halting) + +フィルタまたはルーティング内で直ちにリクエストを止める場合 + +```ruby +halt +``` + +この際、ステータスを指定することもできます。 + +```ruby +halt 410 +``` + +body部を指定することも、 + +```ruby +halt 'ここにbodyを書く' +``` + +ステータスとbody部を指定することも、 + +```ruby +halt 401, '立ち去れ!' +``` + +ヘッダを付けることもできます。 + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ' +``` + +もちろん、テンプレートを`halt`に結びつけることも可能です。 + +```ruby +halt erb(:error) +``` + +### パッシング(Passing) + +ルーティングは`pass`を使って次のルーティングに飛ばすことができます。 + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + "見つかっちゃった!" +end + +get '/guess/*' do + "はずれです!" +end +``` + +ルーティングブロックからすぐに抜け出し、次にマッチするルーティングを実行します。マッチするルーティングが見当たらない場合は404が返されます。 + +### 別ルーティングの誘発 + +`pass`を使ってルーティングを飛ばすのではなく、他のルーティングを呼んだ結果を得たいという場合があります。 +これは`call`を使用することで実現できます。 + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +ノート: 先の例において、テストを楽にしパフォーマンスを改善するには、`"bar"`を単にヘルパーに移し、`/foo`および`/bar`から使えるようにしたほうが良いです。 + +リクエストが、その複製物でない同じアプリケーションのインスタンスに送られるようにしたいときは、`call`に代えて`call!`を使ってください。 + +`call`についての詳細はRackの仕様を参照してください。 + +### ボディ、ステータスコードおよびヘッダの設定 + +ステータスコードおよびレスポンスボディを、ルーティングブロックの戻り値にセットすることが可能であり、これは推奨されています。しかし、あるケースでは実行フローの任意のタイミングでボディをセットしたくなるかもしれません。`body`ヘルパーメソッドを使えばそれができます。そうすると、それ以降、ボディにアクセスするためにそのメソッドを使うことができるようになります。 + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +また、`body`にはブロックを渡すことができ、これはRackハンドラにより実行されることになります(これはストリーミングを実装するのに使われます。"戻り値"の項を参照してください。) + +ボディと同様に、ステータスコードおよびヘッダもセットできます。 + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +引数を伴わない`body`、`headers`、`status`などは、それらの現在の値にアクセスするために使えます。 + +### ストリーミングレスポンス(Streaming Responses) + +レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。`stream`ヘルパーを使えば、独自ラッパーを作る必要はありません。 + +```ruby +get '/' do + stream do |out| + out << "それは伝 -\n" + sleep 0.5 + out << " (少し待つ) \n" + sleep 1 + out << "- 説になる!\n" + end +end +``` + +これはストリーミングAPI、[Server Sent Events](https://w3c.github.io/eventsource/)の実装を可能にし、[WebSockets](https://en.wikipedia.org/wiki/WebSocket)の土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。 + +ノート: ストリーミングの挙動、特に並行リクエスト(cuncurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。いくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディは`stream`に渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。 + +オプション引数が`keep_open`にセットされている場合、ストリームオブジェクト上で`close`は呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはThinやRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。 + +```ruby +# ロングポーリング + +set :server, :thin +connections = [] + +get '/subscribe' do + # サーバイベントにおけるクライアントの関心を登録 + stream(:keep_open) do |out| + connections << out + # 死んでいるコネクションを排除 + connections.reject!(&:closed?) + end +end + +post '/message' do + connections.each do |out| + # クライアントへ新規メッセージ到着の通知 + out << params['message'] << "\n" + + # クライアントへの再接続の指示 + out.close + end + + # 肯定応答 + "message received" +end +``` + +クライアントはソケットに書き込もうとしている接続を閉じることも可能です。そのため、記述しようとする前に`out.closed?`をチェックすることを勧めます。 + +### ロギング(Logging) + +リクエストスコープにおいて、`logger`ヘルパーは`Logger`インスタンスを作り出します。 + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。 + +ノート: ロギングは、`Sinatra::Application`に対してのみデフォルトで有効にされているので、`Sinatra::Base`を継承している場合は、ユーザがこれを有効化する必要があります。 + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +ロギングミドルウェアが設定されてしまうのを避けるには、`logging`設定を`nil`にセットします。しかしこの場合、`logger`が`nil`を返すことを忘れないように。よくあるユースケースは、オリジナルのロガーをセットしたいときです。Sinatraは、とにかく`env['rack.logger']`で見つかるものを使います。 + +### MIMEタイプ(Mime Types) + +`send_file`か静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は `mime_type` を使ってファイル拡張子毎に登録してください。 + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +これは`content_type`ヘルパーで利用することができます: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLの生成 + +URLを生成するためには`url`ヘルパーメソッドが使えます。Hamlではこのようにします。 + +```ruby +%a{:href => url('/foo')} foo +``` + +これはリバースプロキシおよびRackルーティングを、それらがあれば考慮に入れます。 + +このメソッドには`to`というエイリアスがあります(以下の例を参照)。 + +### ブラウザリダイレクト(Browser Redirect) + +`redirect` ヘルパーメソッドを使うことで、ブラウザをリダイレクトさせることができます。 + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +他に追加されるパラメータは、`halt`に渡される引数と同様に取り扱われます。 + +```ruby +redirect to('/bar'), 303 +redirect 'https://www.google.com/', 'wrong place, buddy' +``` + +また、`redirect back`を使えば、簡単にユーザが来たページへ戻るリダイレクトを作れます。 + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +redirectに引数を渡すには、それをクエリーに追加するか、 + +```ruby +redirect to('/bar?sum=42') +``` + +または、セッションを使います。 + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### キャッシュ制御(Cache Control) + +ヘッダを正しく設定することが、適切なHTTPキャッシングのための基礎となります。 + +キャッシュ制御ヘッダ(Cache-Control header)は、次のように簡単に設定できます。 + +```ruby +get '/' do + cache_control :public + "キャッシュしました!" +end +``` + +ヒント: キャッシングをbeforeフィルタ内で設定します。 + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires`ヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。 + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +キャッシュを適切に使うために、`etag`または`last_modified`を使うことを検討してください。これらのヘルパーを、重い仕事をさせる *前* に呼ぶことを推奨します。そうすれば、クライアントが既にキャッシュに最新版を持っている場合はレスポンスを直ちに破棄するようになります。 + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +また、[weak ETag](https://ja.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)を使うこともできます。 + +```ruby +etag @article.sha1, :weak +``` + +これらのヘルパーは、キャッシングをしてくれませんが、必要な情報をキャッシュに与えてくれます。もし手早いリバースプロキシキャッシングの解決策をお探しなら、 [rack-cache](https://github.com/rtomayko/rack-cache)を試してください。 + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +`:static_cache_control`設定(以下を参照)を、キャッシュ制御ヘッダ情報を静的ファイルに追加するために使ってください。 + +RFC 2616によれば、アプリケーションは、If-MatchまたはIf-None-Matchヘッダが`*`に設定されている場合には、要求されたリソースが既に存在するか否かに応じて、異なる振る舞いをすべきとなっています。Sinatraは、getのような安全なリクエストおよびputのような冪等なリクエストは既に存在しているものとして仮定し、一方で、他のリソース(例えば、postリクエスト)は新たなリソースとして取り扱われるよう仮定します。この振る舞いは、`:new_resource`オプションを渡すことで変更できます。 + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +ここでもWeak ETagを使いたい場合は、`:kind`オプションを渡してください。 + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### ファイルの送信 + +ファイルを送信するには、`send_file`ヘルパーメソッドを使います。 + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +これはオプションを取ることもできます。 + +```ruby +send_file 'foo.png', :type => :jpg +``` + +オプション一覧 + +
    +
    filename
    +
    ファイル名。デフォルトは実際のファイル名。
    + +
    last_modified
    +
    Last-Modifiedヘッダの値。デフォルトはファイルのmtime。
    + +
    type
    +
    コンテンツの種類。設定がない場合、ファイル拡張子から類推される。
    + +
    disposition
    +
    + Content-Dispositionに使われる。許容値: nil (デフォルト)、 + :attachment および :inline +
    + +
    length
    +
    Content-Lengthヘッダ。デフォルトはファイルサイズ。
    + +
    status
    +
    + 送られるステータスコード。静的ファイルをエラーページとして送るときに便利。 + + Rackハンドラでサポートされている場合は、Rubyプロセスからのストリーミング以外の手段が使われる。このヘルパーメソッドを使うと、Sinatraは自動で範囲リクエスト(range requests)を扱う。 +
    +
    + +### リクエストオブジェクトへのアクセス + +受信するリクエストオブジェクトは、`request`メソッドを通じてリクエストレベル(フィルタ、ルーティング、エラーハンドラ)からアクセスすることができます。 + +```ruby +# アプリケーションが http://example.com/example で動作している場合 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # クライアントによって送信されたリクエストボディ(下記参照) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.bodyの長さ + request.media_type # request.bodyのメディアタイプ + request.host # "example.com" + request.get? # true (他の動詞にも同種メソッドあり) + request.form_data? # false + request["some_param"] # some_param変数の値。[]はパラメータハッシュのショートカット + request.referrer # クライアントのリファラまたは'/' + request.user_agent # ユーザエージェント (:agent 条件によって使用される) + request.cookies # ブラウザクッキーのハッシュ + request.xhr? # Ajaxリクエストかどうか + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # クライアントのIPアドレス + request.secure? # false (sslではtrueになる) + request.forwarded? # true (リバースプロキシの裏で動いている場合) + request.env # Rackによって渡された生のenvハッシュ +end +``` + +`script_name`や`path_info`などのオプションは次のように利用することもできます。 + +```ruby +before { request.path_info = "/" } + +get "/" do + "全てのリクエストはここに来る" +end +``` + +`request.body`はIOまたはStringIOのオブジェクトです。 + +```ruby +post "/api" do + request.body.rewind # 既に読まれているときのため + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### アタッチメント(Attachments) + +`attachment`ヘルパーを使って、レスポンスがブラウザに表示されるのではなく、ディスクに保存されることをブラウザに対し通知することができます。 + +```ruby +get '/' do + attachment + "保存しました!" +end +``` + +ファイル名を渡すこともできます。 + +```ruby +get '/' do + attachment "info.txt" + "保存しました!" +end +``` + +### 日付と時刻の取り扱い + +Sinatraは`time_for`ヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまた`DateTime`、`Date`および類似のクラスを変換できます。 + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "まだ時間がある" +end +``` + +このメソッドは、`expires`、`last_modified`といった種類のものの内部で使われています。そのため、アプリケーションにおいて、`time_for`をオーバーライドすることでそれらのメソッドの挙動を簡単に拡張できます。 + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### テンプレートファイルの探索 + +`find_template`ヘルパーは、レンダリングのためのテンプレートファイルを見つけるために使われます。 + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +この例はあまり有益ではありません。しかし、このメソッドを、独自の探索機構で働くようオーバーライドするなら有益になります。例えば、複数のビューディレクトリを使えるようにしたいときがあります。 + + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +他の例としては、異なるエンジン用の異なるディレクトリを使う場合です。 + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +これをエクステンションとして書いて、他の人と簡単に共有することもできます! + +ノート: `find_template`はファイルが実際に存在するかのチェックをしませんが、与えられたブロックをすべての可能なパスに対し呼び出します。これがパフォーマンス上の問題にはならないのは、`render`はファイルを見つけると直ちに`break`を使うからです。また、テンプレートの場所(および内容)は、developmentモードでの起動でない限りはキャッシュされます。このことは、複雑なメソッド(a really crazy method)を書いた場合は記憶しておく必要があります。 + +## コンフィギュレーション(Configuration) + +どの環境でも起動時に1回だけ実行されます。 + +```ruby +configure do + # 1つのオプションをセット + set :option, 'value' + + # 複数のオプションをセット + set :a => 1, :b => 2 + + # `set :option, true`と同じ + enable :option + + # `set :option, false`と同じ + disable :option + + # ブロックを使って動的な設定をすることもできます。 + set(:css_dir) { File.join(views, 'css') } +end +``` + +環境設定(`APP_ENV`環境変数)が`:production`に設定されている時だけ実行する方法: + +```ruby +configure :production do + ... +end +``` + +環境設定が`:production`か`:test`に設定されている時だけ実行する方法: + +```ruby +configure :production, :test do + ... +end +``` + +設定したオプションには`settings`からアクセスできます: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 攻撃防御に対する設定 + +Sinatraは[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)を使用することで、アプリケーションを一般的な日和見的攻撃から守っています。これは簡単に無効化できます(が、アプリケーションに大量の一般的な脆弱性を埋め込むことになってしまいます)。 + +```ruby +disable :protection +``` + +ある1つの防御を無効にするには、`protection`にハッシュでオプションを指定します。 + +```ruby +set :protection, :except => :path_traversal +``` + +配列を渡すことで、複数の防御を無効にすることもできます。 + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +デフォルトでSinatraは、`:sessions`が有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、`:session`オプションを渡すことにより、セッションベースの防御を設定することができます。 + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 利用可能な設定 + +
    +
    absolute_redirects
    +
    + 無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。 +
    +
    + アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。 +
    +
    デフォルトは無効。
    + +
    add_charset
    +
    + Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。 + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。 +
    + +
    bind
    +
    バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
    + +
    default_encoding
    +
    不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
    + +
    dump_errors
    +
    ログにおけるエラーの表示。
    + +
    environment
    +
    + 現在の環境。デフォルトはENV['APP_ENV']、それが無い場合は"development"。 +
    + +
    logging
    +
    ロガーの使用。
    + +
    lock
    +
    + 各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。 +
    +
    アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
    + +
    method_override
    +
    + put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。 +
    + +
    port
    +
    待ち受けポート。ビルトインサーバのみで有効。
    + +
    prefixed_redirects
    +
    + 絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。 +
    + +
    protection
    +
    Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
    + +
    public_dir
    +
    public_folderのエイリアス。以下を参照。
    + +
    public_folder
    +
    + publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。 +
    + +
    reload_templates
    +
    + リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。 +
    + +
    root
    +
    + プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。 +
    + +
    raise_errors
    +
    + 例外発生の設定(アプリケーションは止まる)。environment"test"に設定されているときはデフォルトは有効。それ以外は無効。 +
    + +
    run
    +
    + 有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。 +
    + +
    running
    +
    ビルトインサーバが稼働中か?この設定を変更しないこと!
    + +
    server
    +
    + ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。 +
    + +
    sessions
    +
    + Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。 +
    + +
    show_exceptions
    +
    + 例外発生時にブラウザにスタックトレースを表示する。environment"development"に設定されているときは、デフォルトで有効。それ以外は無効。 +
    +
    + また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。 +
    + +
    static
    +
    Sinatraが静的ファイルの提供を取り扱うかの設定。
    +
    その取り扱いができるサーバを使う場合は無効。
    +
    無効化でパフォーマンスは改善する
    +
    + クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。 +
    + +
    static_cache_control
    +
    + Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。 +
    +
    + 複数の値をセットするときは明示的に配列を使う: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + trueに設定されているときは、Thinにリクエストを処理するためにEventMachine.deferを使うことを通知する。 +
    + +
    views
    +
    + ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。 +
    + +
    x_cascade
    +
    + マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue。 +
    +
    + +## 環境設定(Environments) + +3種類の既定環境、`"development"`、`"production"`および`"test"`があります。環境は、`APP_ENV`環境変数を通して設定できます。デフォルト値は、`"development"`です。`"development"`環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別の`not_found`および`error`ハンドラがブラウザにスタックトレースを表示します。`"production"`および`"test"`環境においては、テンプレートはデフォルトでキャッシュされます。 + +異なる環境を走らせるには、`APP_ENV`環境変数を設定します。 + +```shell +APP_ENV=production ruby my_app.rb +``` + +既定メソッド、`development?`、`test?`および`production?`を、現在の環境設定を確認するために使えます。 + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## エラーハンドリング(Error Handling) + +エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、`haml`、`erb`、`halt`といった便利なものが全て使えることを意味します。 + +### 未検出(Not Found) + +`Sinatra::NotFound`例外が発生したとき、またはレスポンスのステータスコードが404のときに、`not_found`ハンドラが発動します。 + +```ruby +not_found do + 'ファイルが存在しません' +end +``` + +### エラー(Error) + +`error`ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。 +しかし、環境設定がdevelopmentの場合は`:after_handler`を設定している場合のみ発動するようになります。 + +```ruby +set :show_exceptions, :after_handler +``` + +例外オブジェクトはRack変数`sinatra.error`から取得できます。 + +```ruby +error do + 'エラーが発生しました。 - ' + env['sinatra.error'].message +end +``` + +エラーをカスタマイズする場合は、 + +```ruby +error MyCustomError do + 'エラーメッセージ...' + env['sinatra.error'].message +end +``` + +と書いておいて、下記のように呼び出します。 + +```ruby +get '/' do + raise MyCustomError, '何かがまずかったようです' +end +``` + +そうするとこうなります。 + +``` +エラーメッセージ... 何かがまずかったようです +``` + +あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。 + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +範囲指定もできます。 + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatraを開発環境の下で実行している場合は、特別な`not_found`および`error`ハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。 + +## Rackミドルウェア(Rack Middleware) + +SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースである[Rack](https://rack.github.io/)上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。 + +Sinatraはトップレベルの`use`メソッドを通して、Rackミドルウェアパイプラインの構築を楽にします。 + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`の文法は、[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder)DSLで定義されているそれ(rackupファイルで最もよく使われる)と同じです。例えば `use`メソッドは複数の引数、そしてブロックも取ることができます。 + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rackは、ロギング、デバッギング、URLルーティング、認証、セッション管理など、多様な標準的ミドルウェアを共に配布されています。Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、通常、それらを`use`で明示的に指定する必要はありません。 + +便利なミドルウェアを以下で見つけられます。 + +[rack](https://github.com/rack/rack/tree/master/lib/rack)、 +[rack-contrib](https://github.com/rack/rack-contrib#readm)、 +または[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)。 + +## テスト(Testing) + +SinatraでのテストはRackベースのテストライブラリまたはフレームワークを使って書くことができます。[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)をお薦めします。 + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbirdを使ってます!", last_response.body + end +end +``` + +ノート: モジュラースタイルでSinatraを使う場合は、上記`Sinatra::Application`をアプリケーションのクラス名に置き換えてください。 + +## Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ + +軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、`./public`および`./views`ディレクトリ、ロギング、例外詳細ページなど)を仮定しています。そこで`Sinatra::Base`の出番です。 + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base`のサブクラスで利用できるメソッドは、トップレベルDSLで利用できるものと全く同じです。ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することで`Sinatra::Base`コンポーネントに変えることができます。 + +* `sinatra`の代わりに`sinatra/base`を読み込む + (そうしない場合、SinatraのDSLメソッドの全てがmainの名前空間にインポートされます) +* ルーティング、エラーハンドラ、フィルタ、オプションを`Sinatra::Base`のサブクラスに書く + +`Sinatra::Base`はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルトで無効になっています。利用可能なオプションとその挙動の詳細については[Configuring Settings](http://www.sinatrarb.com/configuration.html)(英語)をご覧ください。 + +もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、`Sinatra::Application`をサブクラス化させてください。 + +```ruby +require "sinatra/base" + +class MyApp < Sinatra::Application + get "/" do + 'Hello world!' + end +end +``` + +### モジュラースタイル vs クラッシックスタイル + +一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。それがそのアプリケーションに合っているのであれば、モジュラーアプリケーションに移行する必要はありません。 + +モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。複数が必要な場合はモジュラースタイルに移行してください。モジュラースタイルとクラッシックスタイルを混合できないということはありません。 + +一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    設定クラッシックモジュラーモジュラー
    app_filesinatraを読み込むファイルSinatra::Baseをサブクラス化したファイルSinatra::Applicationをサブクラス化したファイル
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### モジュラーアプリケーションの提供 + +モジュラーアプリケーションを開始、つまり`run!`を使って開始させる二種類のやり方があります。 + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... アプリケーションのコードを書く ... + + # Rubyファイルが直接実行されたらサーバを立ち上げる + run! if app_file == $0 +end +``` + +として、次のように起動するか、 + +```shell +ruby my_app.rb +``` + +または、Rackハンドラを使えるようにする`config.ru`ファイルを書いて、 + +```ruby +# config.ru (rackupで起動) +require './my_app' +run MyApp +``` + +起動します。 + +```shell +rackup -p 4567 +``` + +### config.ruを用いたクラッシックスタイルアプリケーションの使用 + +アプリケーションファイルと、 + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +対応する`config.ru`を書きます。 + +```ruby +require './app' +run Sinatra::Application +``` + +### config.ruはいつ使うのか? + +`config.ru`ファイルは、以下の場合に適しています。 + +* 異なるRackハンドラ(Passenger, Unicorn, Herokuなど)でデプロイしたいとき +* `Sinatra::Base`の複数のサブクラスを使いたいとき +* Sinatraをミドルウェアとして利用し、エンドポイントとしては利用しないとき + +**モジュラースタイルに移行したという理由だけで、`config.ru`に移行する必要はなく、`config.ru`で起動するためにモジュラースタイルを使う必要はありません。** + +### Sinatraのミドルウェアとしての利用 + +Sinatraは他のRackミドルウェアを利用することができるだけでなく、 +全てのSinatraアプリケーションは、それ自体ミドルウェアとして別のRackエンドポイントの前に追加することが可能です。 + +このエンドポイントには、別のSinatraアプリケーションまたは他のRackベースのアプリケーション(Rails/Ramaze/Camping/…)が用いられるでしょう。 + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] = 'admin' and params['password'] = 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # ミドルウェアはbeforeフィルタの前に実行される + use LoginScreen + + before do + unless session['user_name'] + halt "アクセスは拒否されました。ログインしてください。" + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 動的なアプリケーションの生成 + +新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。`Sinatra.new`を使えばそれができます。 + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +これは省略できる引数として、それが継承するアプリケーションを取ります。 + +```ruby +# config.ru (rackupで起動) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +これは特にSinatraのextensionをテストするときや、Sinatraを自身のライブラリで利用する場合に役立ちます。 + +これはまた、Sinatraをミドルウェアとして利用することを極めて簡単にします。 + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## スコープとバインディング(Scopes and Binding) + +現在のスコープはどのメソッドや変数が利用可能かを決定します。 + +### アプリケーション/クラスのスコープ + +全てのSinatraアプリケーションはSinatra::Baseのサブクラスに相当します。 +もしトップレベルDSLを利用しているならば(`require 'sinatra'`)このクラスはSinatra::Applicationであり、 +そうでなければ、あなたが明示的に作成したサブクラスです。 +クラスレベルでは`get`や`before`のようなメソッドを持っています。 +しかし`request`や`session`オブジェクトには、全てのリクエストに対する単一のアプリケーションクラスがあるだけなので、アクセスできません。 + +`set`によって作られたオプションはクラスレベルのメソッドです。 + +```ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # もうアプリケーションスコープの中にいないよ! + end +end +``` + +次の場所ではアプリケーションスコープバインディングを持ちます。 + +* アプリケーションクラス本体 +* 拡張によって定義されたメソッド +* `helpers`に渡されたブロック +* `set`の値として使われるProcまたはブロック +* `Sinatra.new`に渡されたブロック + +このスコープオブジェクト(クラス)は次のように利用できます。 + +* configureブロックに渡されたオブジェクト経由(`configure { |c| ... }`) +* リクエストスコープの中での`settings` + +### リクエスト/インスタンスのスコープ + +やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 +このスコープの内側からは`request`や`session`オブジェクトにアクセスすることができ、`erb`や`haml`のようなレンダリングメソッドを呼び出すことができます。 +リクエストスコープの内側からは、`settings`ヘルパーによってアプリケーションスコープにアクセスすることができます。 + +```ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + get '/define_route/:name' do + # '/define_route/:name'のためのリクエストスコープ + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"のためのリクエストスコープ + @value # => nil (not the same request) + end + + "ルーティングが定義された!" + end +end +``` + +次の場所ではリクエストスコープバインディングを持ちます。 + +* get/head/post/put/delete/options/patch/link/unlink ブロック +* before/after フィルタ +* helper メソッド +* テンプレート/ビュー + +### デリゲートスコープ + +デリゲートスコープは、単にクラススコープにメソッドを転送します。 +しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません。 +委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注: +異なった`self`を持っています)。 +`Sinatra::Delegator.delegate :method_name`を呼び出すことによってデリゲートするメソッドを明示的に追加することができます。 + +次の場所ではデリゲートスコープを持ちます。 + +* もし`require "sinatra"`しているならば、トップレベルバインディング +* `Sinatra::Delegator` mixinでextendされたオブジェクト + +コードをご覧ください: ここでは [Sinatra::Delegator +mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)は[mainオブジェクトにextendされています](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。 + +## コマンドライン + +Sinatraアプリケーションは直接実行できます。 + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +オプション: + +``` +-h # ヘルプ +-p # ポート指定(デフォルトは4567) +-o # ホスト指定(デフォルトは0.0.0.0) +-e # 環境を指定 (デフォルトはdevelopment) +-s # rackserver/handlerを指定 (デフォルトはthin) +-x # mutex lockを付ける (デフォルトはoff) +``` + +### マルチスレッド + +_この[StackOverflow](https://stackoverflow.com/a/6282999/5245129) +のKonstantinによる回答を言い換えています。_ + +Sinatraでは同時実行モデルを負わせることはできませんが、根本的な部分であるThinやPuma、WebrickのようなRackハンドラ(サーバー)部分に委ねることができます。 +Sinatra自身はスレッドセーフであり、もしRackハンドラが同時実行モデルのスレッドを使用していても問題はありません。 +つまり、これはサーバーを起動させる時、特定のRackハンドラに対して正しい起動処理を特定することが出来ます。 +この例はThinサーバーをマルチスレッドで起動する方法のデモです。 + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! +``` + +サーバーを開始するコマンドです。 + +``` +thin --threaded start +``` + +## 必要環境 + +次のRubyバージョンが公式にサポートされています。 + +
    +
    Ruby 1.8.7
    +
    + 1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。 +
    + +
    Ruby 1.9.2
    +
    + 1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。 +
    + +
    Ruby 1.9.3
    +
    + 1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。 +
    + +
    Ruby 2.0.0
    +
    + 2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。 +
    + +
    Rubinius
    +
    + Rubiniusは公式にサポートされています(Rubinius >= 2.x)。 + gem install pumaすることが推奨されています。 +
    + +
    JRuby
    +
    + JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。 + gem install trinidadすることが推奨されています。 +
    +
    + +開発チームは常に最新となるRubyバージョンに注視しています。 + +次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。 + +* JRubyとRubiniusの古いバージョン +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません) + +公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。 + +開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。 + +Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。 + +MacRubyを使う場合は、`gem install control_tower`してください。 + +Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。 + +## 最新開発版 + +Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、 + +```shell +gem install sinatra --pre +``` + +すれば、最新の機能のいくつかを利用できます。 + +### Bundlerを使う場合 + +最新のSinatraでアプリケーションを動作させたい場合には、[Bundler](https://bundler.io)を使うのがお薦めのやり方です。 + +まず、Bundlerがなければそれをインストールします。 + +```shell +gem install bundler +``` + +そして、プロジェクトのディレクトリで、`Gemfile`を作ります。 + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 他の依存ライブラリ +gem 'haml' # Hamlを使う場合 +gem 'activerecord', '~> 3.0' # ActiveRecord 3.xが必要かもしれません +``` + +ノート: `Gemfile`にアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。 + +これで、以下のようにしてアプリケーションを起動することができます。 + +```shell +bundle exec ruby myapp.rb +``` + +### 直接組み込む場合 + +ローカルにクローンを作って、`sinatra/lib`ディレクトリを`$LOAD_PATH`に追加してアプリケーションを起動します。 + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +追ってSinatraのソースを更新する方法。 + +```shell +cd myapp/sinatra +git pull +``` + +### グローバル環境にインストールする場合 + +Sinatraのgemを自身でビルドすることもできます。 + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +gemをルートとしてインストールする場合は、最後のステップはこうなります。 + +```shell +sudo rake install +``` + +## バージョニング(Versioning) + +Sinatraは、[Semantic Versioning](https://semver.org/)におけるSemVerおよびSemVerTagの両方に準拠しています。 + +## 参考文献 + +* [プロジェクトサイト](http://www.sinatrarb.com/) - ドキュメント、ニュース、他のリソースへのリンクがあります。 +* [プロジェクトに参加(貢献)する](http://www.sinatrarb.com/contributing.html) - バグレポート パッチの送信、サポートなど +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [メーリングリスト](https://groups.google.com/group/sinatrarb/topics) +* http://freenode.net上のIRC: [#sinatra](irc://chat.freenode.net/#sinatra) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) クックブック、チュートリアル +* [Sinatra Recipes](http://recipes.sinatrarb.com/) コミュニティによるレシピ集 +* http://www.rubydoc.info/上のAPIドキュメント: [最新版(latest release)用](http://www.rubydoc.info/gems/sinatra)または[現在のHEAD用](http://www.rubydoc.info/github/sinatra/sinatra) +* [CIサーバ](https://travis-ci.org/sinatra/sinatra) +* [Greenbear Laboratory Rack日本語マニュアル](http://route477.net/w/RackReferenceJa.html) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ko.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ko.md new file mode 100644 index 0000000000..4ed635368b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ko.md @@ -0,0 +1,2967 @@ +# Sinatra + +*주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있습니다.* + +Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게 +해 주는 [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)입니다. + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +아래의 명령어로 젬을 설치합니다. + +```shell +gem install sinatra +``` + +아래의 명령어로 실행합니다. + +```shell +ruby myapp.rb +``` + +[http://localhost:4567](http://localhost:4567) 를 확인해 보세요. + +`gem install thin`도 함께 실행하기를 권장합니다. +thin이 설치되어 있을 경우 Sinatra는 thin을 통해 실행합니다. + +## 목차 + +* [Sinatra](#sinatra) + * [목차](#목차) + * [라우터(Routes)](#라우터routes) + * [조건(Conditions)](#조건conditions) + * [반환값(Return Values)](#반환값return-values) + * [커스텀 라우터 매처(Custom Route Matchers)](#커스텀-라우터-매처custom-route-matchers) + * [정적 파일(Static Files)](#정적-파일static-files) + * [뷰 / 템플릿(Views / Templates)](#뷰--템플릿views--templates) + * [리터럴 템플릿(Literal Templates)](#리터럴-템플릿literal-templates) + * [가능한 템플릿 언어들(Available Template Languages)](#가능한-템플릿-언어들available-template-languages) + * [Haml 템플릿](#haml-템플릿) + * [Erb 템플릿](#erb-템플릿) + * [Builder 템플릿](#builder-템플릿) + * [Nokogiri 템플릿](#nokogiri-템플릿) + * [Sass 템플릿](#sass-템플릿) + * [SCSS 템플릿](#scss-템플릿) + * [Less 템플릿](#less-템플릿) + * [Liquid 템플릿](#liquid-템플릿) + * [Markdown 템플릿](#markdown-템플릿) + * [Textile 템플릿](#textile-템플릿) + * [RDoc 템플릿](#rdoc-템플릿) + * [AsciiDoc 템플릿](#asciidoc-템플릿) + * [Radius 템플릿](#radius-템플릿) + * [Markaby 템플릿](#markaby-템플릿) + * [RABL 템플릿](#rabl-템플릿) + * [Slim 템플릿](#slim-템플릿) + * [Creole 템플릿](#creole-템플릿) + * [MediaWiki 템플릿](#mediawiki-템플릿) + * [CoffeeScript 템플릿](#coffeescript-템플릿) + * [Stylus 템플릿](#stylus-템플릿) + * [Yajl 템플릿](#yajl-템플릿) + * [WLang 템플릿](#wlang-템플릿) + * [템플릿에서 변수에 접근하기](#템플릿에서-변수에-접근하기) + * [템플릿에서의 `yield` 와 중첩 레이아웃](#템플릿에서의-yield-와-중첩-레이아웃) + * [인라인 템플릿](#인라인-템플릿) + * [이름을 가지는 템플릿(Named Templates)](#이름을-가지는-템플릿named-templates) + * [파일 확장자 연결하기](#파일-확장자-연결하기) + * [나만의 고유한 템플릿 엔진 추가하기](#나만의-고유한-템플릿-엔진-추가하기) + * [템플릿 검사를 위한 커스텀 로직 사용하기](#템플릿-검사를-위한-커스텀-로직-사용하기) + * [필터(Filters)](#필터filters) + * [헬퍼(Helpers)](#헬퍼helpers) + * [세션(Sessions) 사용하기](#세션sessions-사용하기) + * [중단하기(Halting)](#중단하기halting) + * [넘기기(Passing)](#넘기기passing) + * [다른 라우터 부르기(Triggering Another Route)](#다른-라우터-부르기triggering-another-route) + * [본문, 상태 코드 및 헤더 설정하기](#본문-상태-코드-및-헤더-설정하기) + * [응답 스트리밍(Streaming Responses)](#응답-스트리밍streaming-responses) + * [로깅(Logging)](#로깅logging) + * [마임 타입(Mime Types)](#마임-타입mime-types) + * [URL 생성하기](#url-생성하기) + * [브라우저 재지정(Browser Redirect)](#브라우저-재지정browser-redirect) + * [캐시 컨트롤(Cache Control)](#캐시-컨트롤cache-control) + * [파일 전송하기(Sending Files)](#파일-전송하기sending-files) + * [요청 객체에 접근하기(Accessing the Request Object)](#요청-객체에-접근하기accessing-the-request-object) + * [첨부(Attachments)](#첨부attachments) + * [날짜와 시간 다루기](#날짜와-시간-다루기) + * [템플릿 파일 참조하기](#템플릿-파일-참조하기) + * [설정(Configuration)](#설정configuration) + * [공격 방어 설정하기(Configuring attack protection)](#공격-방어-설정하기configuring-attack-protection) + * [가능한 설정들(Available Settings)](#가능한-설정들available-settings) + * [환경(Environments)](#환경environments) + * [에러 처리(Error Handling)](#에러-처리error-handling) + * [찾을 수 없음(Not Found)](#찾을-수-없음not-found) + * [에러](#에러) + * [Rack 미들웨어(Rack Middleware)](#rack-미들웨어rack-middleware) + * [테스팅(Testing)](#테스팅testing) + * [Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps)](#sinatrabase---미들웨어middleware-라이브러리libraries-그리고-모듈-앱modular-apps) + * [모듈(Modular) vs. 전통적 방식(Classic Style)](#모듈modular-vs-전통적-방식classic-style) + * [모듈 애플리케이션(Modular Application) 제공하기](#모듈-애플리케이션modular-application-제공하기) + * [config.ru로 전통적 방식의 애플리케이션 사용하기](#configru로-전통적-방식의-애플리케이션-사용하기) + * [언제 config.ru를 사용할까?](#언제-configru를-사용할까) + * [Sinatra를 미들웨어로 사용하기](#sinatra를-미들웨어로-사용하기) + * [동적인 애플리케이션 생성(Dynamic Application Creation)](#동적인-애플리케이션-생성dynamic-application-creation) + * [범위(Scopes)와 바인딩(Binding)](#범위scopes와-바인딩binding) + * [애플리케이션/클래스 범위](#애플리케이션클래스-범위) + * [요청/인스턴스 범위](#요청인스턴스-범위) + * [위임 범위(Delegation Scope)](#위임-범위delegation-scope) + * [명령행(Command Line)](#명령행command-line) + * [다중 스레드(Multi-threading)](#다중-스레드multi-threading) + * [요구사항(Requirement)](#요구사항requirement) + * [최신(The Bleeding Edge)](#최신the-bleeding-edge) + * [Bundler를 사용하여](#bundler를-사용하여) + * [직접 하기(Roll Your Own)](#직접-하기roll-your-own) + * [전역으로 설치(Install Globally)](#전역으로-설치install-globally) + * [버저닝(Versioning)](#버저닝versioning) + * [더 읽을 거리(Further Reading)](#더-읽을-거리further-reading) + +## 라우터(Routes) + +Sinatra에서, 라우터(route)는 URL-매칭 패턴과 쌍을 이루는 HTTP 메서드입니다. +각각의 라우터는 블록과 연결됩니다. + +```ruby +get '/' do + .. 무언가 보여주기(show) .. +end + +post '/' do + .. 무언가 만들기(create) .. +end + +put '/' do + .. 무언가 대체하기(replace) .. +end + +patch '/' do + .. 무언가 수정하기(modify) .. +end + +delete '/' do + .. 무언가 없애기(annihilate) .. +end + +options '/' do + .. 무언가 주기(appease) .. +end + +link '/' do + .. 무언가 관계맺기(affiliate) .. +end + +unlink '/' do + .. 무언가 격리하기(separate) .. +end +``` + +라우터는 정의된 순서에 따라 매치되고 요청에 대해 가장 먼저 매칭된 라우터가 호출됩니다. + +라우터 패턴에는 이름을 가진 매개변수가 포함될 수 있으며, `params` 해시로 접근할 수 있습니다. + +```ruby +get '/hello/:name' do + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + "Hello #{params['name']}!" +end +``` + +또한 블록 매개변수를 통하여도 이름을 가진 매개변수에 접근할 수 있습니다. + +```ruby +get '/hello/:name' do |n| + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + # n 에는 params['name']가 저장 + "Hello #{n}!" +end +``` + +라우터 패턴에는 스플랫(splat, 또는 와일드카드)도 매개변수도 포함될 수 있으며, 이럴 경우 `params['splat']` 배열을 통해 접근할 수 있습니다. + +```ruby +get '/say/*/to/*' do + # /say/hello/to/world와 매치 + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml과 매치 + params['splat'] # => ["path/to/file", "xml"] +end +``` + +블록 매개변수로도 접근할 수 있습니다. + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +라우터는 정규표현식으로 매치할 수 있습니다. + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +블록 매개변수로도 사용가능합니다. + +```ruby +get %r{/hello/([\w]+)} do |c| + # "GET /meta/hello/world", "GET /hello/world/1234" 등과 매치 + "Hello, #{c}!" +end +``` + +라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있습니다. + +```ruby +get '/posts/:format?' do + # "GET /posts/" 는 물론 "GET /posts/json", "GET /posts/xml" 와 같은 어떤 확장자와도 매칭 +end +``` + +쿼리 파라메터로도 이용가능 합니다. + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +한편, 경로 탐색 공격 방지(path traversal attack protection, 아래 참조)를 비활성화시키지 않았다면, +요청 경로는 라우터와 매칭되기 이전에 수정될 수 있습니다. + +### 조건(Conditions) + +라우터는 사용자 에이전트(user agent)같은 다양한 매칭 조건을 포함할 수 있습니다. + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbird 버전 #{params['agent'][0]}을 사용하는군요!" +end + +get '/foo' do + # songbird 브라우저가 아닌 경우 매치 +end +``` + +다른 가능한 조건에는 `host_name`과 `provides`가 있습니다. + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides`는 request의 허용된 해더를 검색합니다. + +사용자 정의 조건도 쉽게 만들 수 있습니다. + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "내가 졌소!" +end + +get '/win_a_car' do + "미안해서 어쩌나." +end +``` + +여러 값을 받는 조건에는 스플랫(splat)을 사용합니다. + +```ruby +set(:auth) do |*roles| # <- 이게 스플랫 + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "내 계정 정보" +end + +get "/only/admin/", :auth => :admin do + "관리자 외 접근불가!" +end +``` + +### 반환값(Return Values) + +라우터 블록의 반환 값은 HTTP 클라이언트로 전달되는 응답 본문만을 결정하거나, 또는 Rack 스택에서 다음 번 미들웨어만를 결정합니다. +위의 예제에서 볼 수 있지만 대부분의 경우 이 반환값은 문자열입니다.하지만 다른 값도 허용됩니다. + +유효한 Rack 응답, Rack 본문 객체 또는 HTTP 상태 코드가 되는 어떠한 객체라도 반환할 수 있습니다. + +* 세 요소를 가진 배열: `[상태 (Integer), 헤더 (Hash), 응답 본문 (#each에 반응)]` +* 두 요소를 가진 배열: `[상태 (Integer), 응답 본문 (#each에 반응)]` +* `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체 +* 상태 코드를 의미하는 Integer + +이것을 이용한 예를 들자면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있습니다. + +```ruby +class Stream + def each +100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +`stream` 헬퍼 메서드(아래 참조)를 사용하면 이런 번거로움을 줄이고 스트리밍 로직을 라우터 속에 포함 시킬 수도 있습니다. + +### 커스텀 라우터 매처(Custom Route Matchers) + +위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있습니다. +하지만, 그게 끝은 아닙니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있습니다. + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +사실 위의 예제는 조금 과하게 작성된 면이 있습니다. 간단하게 표현할 수도 있어요. + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +또는 거꾸로 탐색(negative look ahead)할 수도 있습니다. + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 정적 파일(Static Files) + +정적 파일들은 `./public` 디렉터리에서 제공됩니다. 위치를 다른 곳으로 +변경하려면 `:public_folder` 옵션을 지정하면 됩니다. + +```ruby +set :public_folder, __dir__ + '/static' +``` + +public 디렉터리명은 URL에 포함되지 않는다는 점에 주의하세요. +`./public/css/style.css` 파일은 아마 `http://example.com/css/style.css` 로 접근할 수 있을 것입니다. + +`Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하면 됩니다. + +## 뷰 / 템플릿(Views / Templates) + +템플릿 언어들은 각각의 렌더링 메서드를 통해 표출됩니다. +이들 메서드는 문자열을 반환할 뿐입니다. + +```ruby +get '/' do + erb :index +end +``` + +이 구문은 `views/index.erb`를 렌더합니다. + +템플릿 이름 대신 템플릿의 내용을 직접 넘길 수도 있습니다. + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +템플릿은 두 번째 인자로 옵션값의 해시를 받습니다. + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +이 구문은 `views/post.erb` 속에 내장된 `views/index.erb`를 렌더합니다. +(`views/layout.erb`파일이 존재할 경우 기본값은 `views/layout.erb`입니다.) + +Sinatra가 이해하지 못하는 모든 옵션값들은 템플릿 엔진으로 전달됩니다. + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +옵션값은 템플릿 언어별로 전역적으로 설정할 수도 있습니다. + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +render 메서드에서 전달된 옵션값들은 `set`을 통해 설정한 옵션값보다 우선합니다. + +가능한 옵션값들은 다음과 같습니다. + +
    +
    locals
    +
    + 문서로 전달되는 local 목록. 파셜과 함께 사용하기 좋음. + 예제: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + 불확실한 경우에 사용할 문자열 인코딩. + 기본값은 settings.default_encoding. +
    + +
    views
    +
    + 템플릿을 로드할 뷰 폴더. + 기본값은 settings.views. +
    + +
    layout
    +
    + 레이아웃을 사용할지 여부 (true 또는 false), 만약 + 이 값이 심볼일 경우, 사용할 템플릿을 지정. 예제: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + 템플릿이 생성하는 Content-Type, 기본값은 템플릿 언어에 의존. +
    + +
    scope
    +
    + 템플릿을 렌더링하는 범위. 기본값은 어플리케이션 인스턴스. + 만약 이 값을 변경하면, 인스턴스 변수와 헬퍼 메서드들을 사용할 수 없게 됨. +
    + +
    layout_engine
    +
    + 레이아웃 렌더링에 사용할 템플릿 엔진. 레이아웃을 지원하지 않는 언어인 경우에 유용. + 기본값은 템플릿에서 사용하는 엔진. 예제: set :rdoc, :layout_engine => :erb +
    +
    + + +템플릿은 `./views` 디렉터리에 있는 것으로 가정됩니다. 뷰 디렉터리를 +다른 곳으로 하고 싶으시면 이렇게 하세요. + +```ruby +set :views, settings.root + '/templates' +``` + +템플릿은 언제나 심볼로 참조되어야 한다는 것에 주의하세요. +템플릿이 하위 디렉터리에 위치한 경우(그럴 경우에는 `:'subdir/template'`을 +사용)에도 예외는 없습니다. 반드시 심볼이어야 하는 이유는, 문자열을 넘기면 +렌더링 메서드가 전달된 문자열을 직접 렌더하기 때문입니다. + +### 리터럴 템플릿(Literal Templates) + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +템플릿 문자열을 렌더합니다. + +### 가능한 템플릿 언어들(Available Template Languages) + +일부 언어는 여러 개의 구현이 있습니다. (스레드에 안전하게 thread-safe) 어느 구현을 +사용할지 저정하려면, 먼저 require 하기만 하면 됩니다. + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml 템플릿 + + + + + + + + + + + + + + +
    의존성haml
    파일 확장자.haml
    예제haml :index, :format => :html5
    + +#### Erb 템플릿 + + + + + + + + + + + + + + +
    의존성erubis 또는 erb (루비 속에 포함)
    파일 확장자.erb, .rhtml, .erubis (Erubis만 해당)
    예제erb :index
    + +#### Builder 템플릿 + + + + + + + + + + + + + + +
    의존성builder
    파일 확장자.builder
    예제builder { |xml| xml.em "hi" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Nokogiri 템플릿 + + + + + + + + + + + + + + +
    의존성nokogiri
    파일 확장자.nokogiri
    예제nokogiri { |xml| xml.em "hi" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Sass 템플릿 + + + + + + + + + + + + + + +
    의존성sass
    파일 확장자.sass
    예제sass :stylesheet, :style => :expanded
    + +#### SCSS 템플릿 + + + + + + + + + + + + + + +
    의존성sass
    파일 확장자.scss
    예제scss :stylesheet, :style => :expanded
    + +#### Less 템플릿 + + + + + + + + + + + + + + +
    의존성less
    파일 확장자.less
    예제less :stylesheet
    + +#### Liquid 템플릿 + + + + + + + + + + + + + + +
    의존성liquid
    파일 확장자.liquid
    예제liquid :index, :locals => { :key => 'value' }
    + +Liquid 템플릿에서는 루비 메서드(`yield` 제외)를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markdown 템플릿 + + + + + + + + + + + + + + +
    의존성 + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku + 중 아무거나 +
    파일 확장.markdown, .mkd, .md
    예제markdown :index, :layout_engine => :erb
    + +Markdown에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +다른 템플릿 속에서 `markdown` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 안녕 Haml! +%p= markdown(:greetings) +``` + +Markdown에서 루비를 호출할 수 없기 때문에, Markdown으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### Textile 템플릿 + + + + + + + + + + + + + + +
    의존성RedCloth
    파일 확장자.textile
    예제textile :index, :layout_engine => :erb
    + +Textile에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +다른 템플릿 속에서 `textile` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 안녕 Haml! +%p= textile(:greetings) +``` + +Textile에서 루비를 호출할 수 없기 때문에, Textile으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### RDoc 템플릿 + + + + + + + + + + + + + + +
    의존성rdoc
    파일 확장자.rdoc
    예제rdoc :README, :layout_engine => :erb
    + +RDoc에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +다른 템플릿 속에서 `rdoc` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDoc에서 루비를 호출할 수 없기 때문에, RDoc으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### AsciiDoc 템플릿 + + + + + + + + + + + + + + +
    의존성Asciidoctor
    파일 확장자.asciidoc, .adoc and .ad
    예제asciidoc :README, :layout_engine => :erb
    + +AsciiDoc 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Radius 템플릿 + + + + + + + + + + + + + + +
    의존성radius
    파일 확장자.radius
    예제radius :index, :locals => { :key => 'value' }
    + +Radius 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markaby 템플릿 + + + + + + + + + + + + + + +
    의존성markaby
    파일확장.mab
    예제markaby { h1 "Welcome!" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### RABL 템플릿 + + + + + + + + + + + + + + +
    의존성rabl
    파일 확장자.rabl
    예제rabl :index
    + +#### Slim 템플릿 + + + + + + + + + + + + + + +
    의존성slim
    파일 확장자.slim
    예제slim :index
    + +#### Creole 템플릿 + + + + + + + + + + + + + + +
    의존성creole
    파일 확장자.creole
    예제creole :wiki, :layout_engine => :erb
    + +Creole에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +다른 템플릿 속에서 `creole` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Creole에서 루비를 호출할 수 없기 때문에, Creole으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### MediaWiki 템플릿 + + + + + + + + + + + + + + +
    의존성WikiCloth
    파일 확장자.mediawiki and .mw
    예제mediawiki :wiki, :layout_engine => :erb
    + +MediaWiki 마크업에서는 메서드 호출 뿐 아니라 locals 전달도 불가능합니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +다른 템플릿 속에서 `mediawiki` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +MediaWiki에서 루비를 호출할 수 없기 때문에, MediaWiki으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### CoffeeScript 템플릿 + + + + + + + + + + + + + + +
    의존성 + + CoffeeScript + 와 + + 자바스크립트 실행법 + +
    파일 확장자.coffee
    예제coffee :index
    + +#### Stylus 템플릿 + + + + + + + + + + + + + + +
    의존성 + + Stylus + 와 + + 자바스크립트 실행법 + +
    파일 확장자.styl
    예제stylus :index
    + +Stylus 템플릿을 사용가능하게 하려면, 먼저 `stylus`와 `stylus/tilt`를 로드 +해야합니다. + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl 템플릿 + + + + + + + + + + + + + + +
    의존성yajl-ruby
    파일 확장자.yajl
    예제 + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 `#to_json`으로 변환됩니다. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`과 `:variable` 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있습니다. + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang 템플릿 + + + + + + + + + + + + + + +
    의존성WLang
    파일 확장자.wlang
    예제wlang :index, :locals => { :key => 'value' }
    + +WLang 템플릿에서는 루비 메서드를 사용하는게 일반적이지 않기 +때문에, 거의 대부분의 경우 locals를 전달합니다. 그래도 +WLang으로 쓰여진 레이아웃과 `yield`는 지원합니다. + +### 템플릿에서 변수에 접근하기 + +템플릿은 라우터 핸들러와 같은 맥락(context)에서 평가됩니다. 라우터 +핸들러에서 설정한 인스턴스 변수들은 템플릿에서 직접 접근 가능합니다. + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +명시적으로 로컬 변수의 해시를 지정할 수도 있습니다. + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +이 방법은 주로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할 +때 사용됩니다. + +### 템플릿에서의 `yield` 와 중첩 레이아웃 + +레이아웃은 보통 `yield`만 호출하는 템플릿입니다. +위에 설명된 `:template` 옵션을 통해 템플릿을 사용하거나, +다음 예제처럼 블록으로 렌더링 할 수 있습니다. + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +위 코드는 `erb :index, :layout => :post`와 대부분 동일합니다. + +렌더링 메서드에 블록 넘기기는 중첩 레이아웃을 만들때 유용합니다. + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +위의 코드도 줄일 수 있습니다. + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +현재, `erb`, `haml`, `liquid`, `slim `, `wlang`는 블럭을 지원합니다. +일반적인 `render` 메소드도 블럭을 지원합니다. + +### 인라인 템플릿 + +템플릿은 소스 파일의 마지막에서 정의할 수도 있습니다. + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html += yield + +@@ index +%div.title Hello world. +``` + +참고: sinatra를 require한 소스 파일에 정의된 인라인 템플릿은 자동으로 +로드됩니다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로 +`enable :inline_templates`을 호출하면 됩니다. + +### 이름을 가지는 템플릿(Named Templates) + +템플릿은 톱 레벨(top-level)에서 `template`메서드로도 정의할 수 있습니다. + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +"layout"이라는 이름의 템플릿이 존재하면, 템플릿이 렌더될 때마다 사용됩니다. +레이아웃을 비활성화할 때는 `:layout => false`를 전달하여 개별적으로 +비활성시키거나 `set :haml, :layout => false`으로 기본값을 비활성으로 둘 수 +있습니다. + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### 파일 확장자 연결하기 + +어떤 파일 확장자를 특정 템플릿 엔진과 연결하려면, `Tilt.register`를 사용하면 +됩니다. 예를 들어, `tt`라는 파일 확장자를 Textile 템플릿과 연결하고 싶다면, +다음과 같이 하면 됩니다. + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### 나만의 고유한 템플릿 엔진 추가하기 + +우선, Tilt로 여러분 엔진을 등록하고, 렌더링 메서드를 생성합니다. + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +위 코드는 `./views/index.myat` 를 렌더합니다. +Tilt에 대한 더 자세한 내용은 https://github.com/rtomayko/tilt 참조하세요. + +### 템플릿 검사를 위한 커스텀 로직 사용하기 + +고유한 템플릿 룩업을 구현하기 위해서는 `#find_template` 메서드를 만드셔야 합니다. + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## 필터(Filters) + +사전 필터(before filter)는 라우터와 동일한 맥락에서 매 요청 전에 평가되며 +요청과 응답을 변형할 수 있습니다. 필터에서 설정된 인스턴스 변수들은 라우터와 +템플릿에서 접근 가능합니다. + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +사후 필터(after filter)는 라우터와 동일한 맥락에서 매 요청 이후에 평가되며 +마찬가지로 요청과 응답을 변형할 수 있습니다. 사전 필터와 라우터에서 설정된 +인스턴스 변수들은 사후 필터에서 접근 가능합니다. + +```ruby +after do + puts response.status +end +``` + +참고: 만약 라우터에서 `body` 메서드를 사용하지 않고 그냥 문자열만 반환한 +경우라면, body는 나중에 생성되는 탓에, 아직 사후 필터에서 사용할 수 없을 +것입니다. + +필터는 패턴을 취할 수도 있으며, 이 경우 요청 경로가 그 패턴과 매치할 +경우에만 필터가 평가될 것입니다. + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +라우터와 마찬가지로, 필터 역시 조건을 취할 수 있습니다. + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## 헬퍼(Helpers) + +톱-레벨의 `helpers` 메서드를 사용하여 라우터 핸들러와 템플릿에서 사용할 헬퍼 +메서드들을 정의할 수 있습니다. + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +또는, 헬퍼 메서드는 별도의 모듈 속에 정의할 수도 있습니다. + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +이 것은 모듈을 애플리케이션 클래스에 포함(include)시킨 것과 같습니다. + +### 세션(Sessions) 사용하기 + +세션은 요청 동안에 상태를 유지하기 위해 사용합니다. +세션이 활성화되면, 사용자 세션 당 세션 해시 하나씩을 갖게 됩니다. + +```ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +`enable :sessions`은 실은 모든 데이터를 쿠키 속에 저장하는 것에 주의하세요. +이 방식이 바람직하지 않을 수도 있습니다. (예를 들어, 많은 양의 데이터를 +저장하게 되면 트래픽이 늘어납니다). +이런 경우에는 랙 세션 미들웨어(Rack session middleware)를 사용할 수 있습니다. +`enable :sessions`을 호출하지 **않는** 대신에, 선택한 미들웨어를 다른 +미들웨어들처럼 포함시키면 됩니다. + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +보안 강화을 위해서, 쿠키 속의 세션 데이터는 세션 시크릿(secret)으로 +사인(sign)됩니다. Sinatra는 무작위 시크릿을 생성합니다. 하지만, 이 +시크릿은 애플리케이션 시작 시마다 변경되기 때문에, 애플리케이션의 +모든 인스턴스들이 공유할 시크릿을 직접 만들 수도 있습니다. + +```ruby +set :session_secret, 'super secret' +``` + +조금 더 세부적인 설정이 필요하다면, `sessions` 설정에서 옵션이 있는 +해시를 저장할 수도 있습니다. + +```ruby +set :sessions, :domain => 'foo.com' +``` + +세션을 다른 foo.com의 서브도메인 들과 공유하기 원한다면, 다음에 나오는 +것 처럼 도메인 앞에 *.*을 붙이셔야 합니다. + +```ruby +set :sessions, :domain => '.foo.com' +``` + +### 중단하기(Halting) + +필터나 라우터에서 요청을 즉각 중단하고 싶을 때 사용하합니다. + +```ruby +halt +``` + +중단할 때 상태를 지정할 수도 있습니다. + +```ruby +halt 410 +``` + +본문을 넣을 수도 있습니다. + +```ruby +halt 'this will be the body' +``` + +둘 다 할 수도 있습니다. + +```ruby +halt 401, 'go away!' +``` + +헤더를 추가할 경우에는 다음과 같이 하면 됩니다. + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +당연히 `halt`와 템플릿은 같이 사용할 수 있습니다. + +```ruby +halt erb(:error) +``` + +### 넘기기(Passing) + +라우터는 `pass`를 사용하여 다음 번 매칭되는 라우터로 처리를 넘길 수 있습니다. + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +이 때 라우터 블록에서 즉각 빠져나오게 되고 제어는 다음 번 매칭되는 라우터로 +넘어갑니다. 만약 매칭되는 라우터를 찾지 못하면, 404가 반환됩니다. + +### 다른 라우터 부르기(Triggering Another Route) + +때로는 `pass`가 아니라, 다른 라우터를 호출한 결과를 얻고 싶을 때도 +있습니다. 이럴때는 간단하게 `call`을 사용하면 됩니다. + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do +"bar" +end +``` + +위 예제의 경우, `"bar"`를 헬퍼로 옮겨 `/foo`와 `/bar` 모두에서 사용하도록 +하면 테스팅을 쉽게 하고 성능을 높일 수 있습니다. + +요청의 사본이 아닌 바로 그 인스턴스로 보내지도록 하고 싶다면, +`call` 대신 `call!`을 사용하면 됩니다. + +`call`에 대한 더 자세한 내용은 Rack 명세를 참고하세요. + +### 본문, 상태 코드 및 헤더 설정하기 + +라우터 블록의 반환값과 함께 상태 코드(status code)와 응답 본문(response body)을 +설정할수 있고 권장됩니다. 하지만, 경우에 따라서는 본문을 실행 흐름 중의 임의 +지점에서 설정해야 할때도 있습니다. 이런 경우 `body` 헬퍼 메서드를 사용하면 +됩니다. 이렇게 하면, 그 순간부터 본문에 접근할 때 그 메서드를 사용할 수가 있습니다. + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +`body`로 블록을 전달하는 것도 가능하며, 이 블록은 랙(Rack) 핸들러에 의해 +실행됩니다. (이 방법은 스트리밍을 구현할 때 사용할 수 있습니다. "값 +반환하기"를 참고하세요). + +본문와 마찬가지로, 상태코드와 헤더도 설정할 수 있습니다. + +```ruby +get '/foo' do + status 418 + headers \ +"Allow" => "BREW, POST, GET, PROPFIND, WHEN", +"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +`body`처럼, `header`와 `status`도 매개변수 없이 사용하여 현재 값을 +액세스할 수 있습니다. + +### 응답 스트리밍(Streaming Responses) + +응답 본문의 일정 부분을 계속 생성하는 가운데 데이터를 내보내기 시작하고 +싶을 경우가 있습니다. 극단적인 예제로, 클라이언트가 접속을 끊기 전까지 +계속 데이터를 내보내고 싶을 경우도 있죠. 여러분만의 래퍼(wrapper)를 +만들지 않으려면 `stream` 헬퍼를 사용하면 됩니다. + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +이렇게 스트리밍 API나 [서버 발송 이벤트Server Sent +Events](https://w3c.github.io/eventsource/)를 구현할 수 있고, 이 방법은 +[WebSockets](https://en.wikipedia.org/wiki/WebSocket)을 위한 기반으로 사용됩니다. +이 방법은 일부 콘텐츠가 느린 자원에 의존하는 경우에 스로풋(throughtput)을 +높이기 위해 사용되기도 합니다. + +스트리밍 동작, 특히 동시 요청의 수는 애플리케이션을 서빙하는 웹서버에 크게 +의존합니다. 일부의 경우 아예 스트리밍을 지원하지 조차 않습니다. 만약 서버가 +스트리밍을 지원하지 않는다면, 본문은 `stream` 으로 전달된 블록이 수행을 마친 +후에 한꺼번에 반환됩니다. 이런 한번에 쏘는 샷건같은 방식으로는 스트리밍은 +움직이지 않습니다. + +선택적 매개변수 `keep_open`이 설정되어 있다면, 스트림 객체에서 `close`를 +호출하지 않을 것이고, 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수 +있습니다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동하고 +다른 서버들은 여전히 스트림을 닫습니다. + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +### 로깅(Logging) + +요청 스코프(request scope) 내에서, `Logger`의 인스턴스인 `logger` +헬퍼를 사용할 수 있습니다. + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +이 로거는 자동으로 Rack 핸들러에서 설정한 로그설정을 참고합니다. +만약 로깅이 비활성상태라면, 이 메서드는 더미(dummy) 객체를 반환하기 때문에, +라우터나 필터에서 이 부분에 대해 걱정할 필요는 없습니다. + +로깅은 `Sinatra::Application`에서만 기본으로 활성화되어 있음에 유의합시다. +만약 `Sinatra::Base`로부터 상속받은 경우라면 직접 활성화시켜 줘야 합니다. + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +로깅 미들웨어를 사용하지 않으려면, `logging` 설정을 `nil`로 두면 됩니다. +하지만, 이 경우 주의할 것은 `logger`는 `nil`을 반환하는 것입니다. +통상적인 유스케이스는 여러분만의 로거를 사용하고자 할 경우일 것입니다. +Sinatra는 `env['rack.logger']`에서 찾은 로거를 사용할 것입니다. + +### 마임 타입(Mime Types) + +`send_file`이나 정적인 파일을 사용할 때에 Sinatra가 인식하지 못하는 +마임 타입이 있을 수 있습니다. 이 경우 `mime_type`을 사용하여 파일 +확장자를 등록합니다. + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +`content_type` 헬퍼로 쓸 수도 있습니다. + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URL 생성하기 + +URL을 생성할때 `url` 헬퍼 메서드를 사용합니다. 예를 들어 Haml에서는 이렇게 +합니다. + +```ruby +%a{:href => url('/foo')} foo +``` + +이것은 리버스 프록시(reverse proxies)와 Rack 라우터가 있다면 참고합니다. + +이 메서드는 `to`라는 별칭으로도 사용할 수 있습니다. (아래 예제 참조) + +### 브라우저 재지정(Browser Redirect) + +`redirect` 헬퍼 메서드를 사용하여 브라우저를 리다이렉트 시킬 수 있습니다. + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +다른 부가적인 매개변수들은 `halt`에 전달하는 인자들과 비슷합니다. + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +`redirect back`을 사용하면 쉽게 사용자가 왔던 페이지로 다시 돌아가게 +할 수 있습니다. + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +리다이렉트와 함께 인자를 전달하려면, 쿼리로 붙이거나, + +```ruby +redirect to('/bar?sum=42') +``` + +세션을 사용하면 됩니다. + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### 캐시 컨트롤(Cache Control) + +헤더를 정확하게 설정하는 것은 적절한 HTTP 캐싱의 기본입니다. + +Cache-Control 헤더를 다음과 같이 간단하게 설정할 수 있습니다. + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +프로 팁: 캐싱은 사전 필터에서 설정하세요. + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires` 헬퍼를 사용하여 그에 상응하는 헤더를 설정한다면, +`Cache-Control`이 자동으로 설정됩니다. + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +캐시를 잘 사용하려면, `etag` 또는 `last_modified`을 사용해 보세요. +무거운 작업을 하기 *전*에 이들 헬퍼를 호출하길 권장합니다. 이렇게 하면, +클라이언트 캐시에 현재 버전이 이미 들어 있을 경우엔 즉각 응답을 +뿌릴(flush) 것입니다. + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +[약한 ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)를 +사용할 수 도 있습니다. + +```ruby +etag @article.sha1, :weak +``` + +이들 헬퍼는 어떠한 캐싱도 하지 않으며, 대신 캐시에 필요한 정보를 제공합니다. +손쉬운 리버스 프록시(reverse-proxy) 캐싱 솔루션을 찾고 있다면, +[rack-cache](https://github.com/rtomayko/rack-cache)를 써보세요. + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +정적 파일에 `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` +설정(아래 참조)을 쓰세요. + +RFC 2616에 따르면 If-Match 또는 If-None-Match 헤더가 `*`로 설정된 경우 요청한 +리소스(resource)가 이미 존재하느냐 여부에 따라 다르게 취급해야 한다고 되어 +있습니다. Sinatra는 (get 처럼) 안전하거나 (put 처럼) 멱등인 요청에 대한 리소스는 +이미 존재한다고 가정하지만, 다른 리소스(예를 들면 post 요청 같은)의 경우는 +새 리소스로 취급합니다. 이 행동은 `:new_resource` 옵션을 전달하여 변경할 수 있습니다. + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +약한 ETag를 사용하고자 한다면, `:kind`으로 전달합시다. + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### 파일 전송하기(Sending Files) + +응답(response)으로 파일의 컨탠츠를 리턴하려면, `send_file` 헬퍼 메서드를 사용하면 됩니다. + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +이 메서드는 몇 가지 옵션을 받습니다. + +```ruby +send_file 'foo.png', :type => :jpg +``` + +옵션들: + +
    +
    filename
    +
    응답에서 사용되는 파일명. 기본값은 실제 파일명.
    + +
    last_modified
    +
    Last-Modified 헤더값. 기본값은 파일의 mtime.
    + +
    type
    +
    Content-Type 헤더값. 없으면 파일 확장자로부터 유추.
    + +
    disposition
    +
    + Content-Disposition 헤더값. 가능한 값들: nil (기본값), + :attachment:inline +
    + +
    length
    +
    Content-Length 헤더값, 기본값은 파일 크기.
    + +
    status
    +
    + 전송할 상태 코드. 오류 페이지로 정적 파일을 전송할 경우에 유용. + + Rack 핸들러가 지원할 경우, Ruby 프로세스로부터의 스트리밍이 아닌 + 다른 수단이 사용가능함. 이 헬퍼 메서드를 사용하게 되면, Sinatra는 + 자동으로 범위 요청(range request)을 처리함. +
    +
    + + +### 요청 객체에 접근하기(Accessing the Request Object) + +들어오는 요청 객에는 요청 레벨(필터, 라우터, 오류 핸들러)에서 `request` +메서드를 통해 접근 가능합니다. + +```ruby +# http://example.com/example 상에서 실행 중인 앱 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # 클라이언트로부터 전송된 요청 본문 (아래 참조) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body의 길이 + request.media_type # request.body의 미디어 유형 + request.host # "example.com" + request.get? # true (다른 동사에 대해 유사한 메서드 있음) + request.form_data? # false + request["SOME_HEADER"] # SOME_HEADER 헤더의 값 + request.referrer # 클라이언트의 리퍼러 또는 '/' + request.user_agent # 사용자 에이전트 (:agent 조건에서 사용됨) + request.cookies # 브라우저 쿠키의 해시 + request.xhr? # 이게 ajax 요청인가요? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 클라이언트 IP 주소 + request.secure? # false (ssl 접속인 경우 true) + request.forwarded? # true (리버스 프록시 하에서 작동 중이라면) + request.env # Rack에 의해 처리되는 로우(raw) env 해시 +end +``` + +`script_name`, `path_info`같은 일부 옵션들은 이렇게 쓸 수도 있습니다. + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body`는 IO 객체이거나 StringIO 객체입니다. + +```ruby +post "/api" do + request.body.rewind # 누군가 이미 읽은 경우 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### 첨부(Attachments) + +`attachment` 헬퍼를 사용하여 응답이 브라우저에 표시하는 대신 +디스크에 저장되어야 함을 블라우저에게 알릴 수 있습니다. + +```ruby +get '/' do + attachment + "store it!" +end +``` + +파일명을 전달할 수도 있습니다. + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### 날짜와 시간 다루기 + +Sinatra는 `time_for_` 헬퍼 메서드를 제공합니다. 이 메서드는 +주어진 값으로부터 Time 객체를 생성한다. `DateTime`, `Date` 같은 +비슷한 클래스들도 변환됩니다. + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +이 메서드는 내부적으로 `expires` 나 `last_modified` 같은 곳에서 사용됩니다. +따라서 여러분은 애플리케이션에서 `time_for`를 오버라이딩하여 이들 메서드의 +동작을 쉽게 확장할 수 있습니다. + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### 템플릿 파일 참조하기 + +`find_template`는 렌더링할 템플릿 파일을 찾는데 사용됩니다. + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +이것만으로는 그렇게 유용하지는 않습니다만, 이 메서드를 오버라이드하여 여러분만의 +참조 메커니즘에서 가로채게 하면 유용해집니다. 예를 들어, 하나 이상의 뷰 디렉터리를 +사용하고자 한다면 이렇게 하세요. + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +다른 예제는 각 엔진마다 다른 디렉터리를 사용할 경우입니다. + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +여러분은 이것을 간단하게 확장(extension)으로 만들어 다른 사람들과 공유할 수 있다! + +`find_template`은 그 파일이 실제 존재하는지 검사하지 않음에 유의합니다. +모든 가능한 경로에 대해 주어진 블록을 호출할 뿐입니다. 이것은 성능 문제는 +되지 않습니다. 왜냐하면 `render`는 파일이 발견되는 즉시 `break`하기 때문입니다. +또한, 템플릿 위치(그리고 콘텐츠)는 개발 모드에서 실행 중이 아니라면 캐시될 것입니다. +정말로 멋진 메세드를 작성하고 싶다면 이 점을 명심하세요. + +## 설정(Configuration) + +모든 환경에서, 시작될 때, 한번만 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure do + # 옵션 하나 설정 + set :option, 'value' + + # 여러 옵션 설정 + set :a => 1, :b => 2 + + # `set :option, true`와 동일 + enable :option + + # `set :option, false`와 동일 + disable :option + + # 블록으로 동적인 설정을 할 수도 있음 + set(:css_dir) { File.join(views, 'css') } +end +``` + +환경(APP_ENV 환경 변수)이 `:production`일 때만 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure :production do + ... +end +``` + +환경이 `:production` 또는 `:test`일 때 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure :production, :test do + ... +end +``` + +이 옵션들은 `settings`를 통해 접근 가능합니다. + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 공격 방어 설정하기(Configuring attack protection) + +Sinatra는 [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)을 사용하여 +일반적이고 일어날 수 있는 공격에 대비합니다. 이 모듈은 간단하게 비활성시킬 수 있습니다. +(하지만 애플리케이션에 엄청나게 많은 취약성을 야기합니다.) + +```ruby +disable :protection +``` + +하나의 방어층만 스킵하려면, 옵션 해시에 `protection`을 설정하면 됩니다. + +```ruby +set :protection, :except => :path_traversal +``` + +배열로 넘김으로써 방어층 여러 개를 비활성화할 수 있습니다. + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +기본적으로 `:sessions`가 활성 중일 때만 Sinatra는 방어층을 설정합니다. +때로는 자신만의 세션을 설정할 때도 있습니다. 이런 경우 `:session` 옵션을 +넘겨줌으로써 세션을 기반으로한 방어층을 설정 할 수 있습니다. + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 가능한 설정들(Available Settings) + +
    +
    absolute_redirects
    +
    + 만약 비활성이면, Sinatra는 상대경로 리다이렉트를 허용할 것이지만, + 이렇게 되면 Sinatra는 더 이상 오직 절대경로 리다이렉트만 허용하고 있는 + RFC 2616(HTTP 1.1)에 위배됨. +
    +
    + 적정하게 설정되지 않은 리버스 프록시 하에서 앱을 실행 중이라면 활성화시킬 것. + rul 헬퍼는, 만약 두 번째 매개변수로 false를 전달하지만 않는다면, + 여전히 절대경로 URL을 생성할 것임에 유의. +
    +
    기본값은 비활성.
    + +
    add_charset
    +
    + content_type가 문자셋 정보에 자동으로 추가하게 될 마임(mime) 타입. + 이 옵션은 오버라이딩하지 말고 추가해야 함. + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + 메인 애플리케이션 파일의 경로. 프로젝트 루트, 뷰, public 폴더, + 인라인 템플릿을 파악할 때 사용됨. +
    + +
    bind
    +
    바인드할 IP 주소(기본값: 0.0.0.0 이나 + `environment`가 개발로 설정 되어있으면 localhost). 오직 + 빌트인(built-in) 서버에서만 사용됨.
    + +
    default_encoding
    +
    인코딩을 알 수 없을 때 인코딩(기본값은 "utf-8").
    + +
    dump_errors
    +
    로그안의 에러 출력.
    + +
    environment
    +
    + 현재 환경, 기본값은 ENV['APP_ENV'] ENV에 없을 경우엔 "development". +
    + +
    logging
    +
    로거(logger) 사용.
    + +
    lock
    +
    + Ruby 프로세스 당 요청을 동시에 할 경우에만 매 요청에 걸쳐 잠금(lock)을 설정. +
    +
    앱이 스레드에 안전(thread-safe)하지 않으면 활성화시킬 것. 기본값은 비활성.
    + +
    method_override
    +
    + put/delete를 지원하지 않는 브라우저에서 put/delete 폼을 허용하는 + _method 꼼수 사용. +
    + +
    port
    +
    접속 포트. 빌트인 서버에서만 사용됨.
    + +
    prefixed_redirects
    +
    + 절대경로가 주어지지 않은 리다이렉트에 request.script_name를 + 삽입할지 여부를 결정. 활성화 하면 redirect '/foo'는 + redirect to('/foo')처럼 동작. 기본값은 비활성. +
    + +
    protection
    +
    웹 공격 방어를 활성화시킬 건지 여부. 위의 보안 섹션 참조.
    + +
    public_dir
    +
    public_folder의 별칭. 밑을 참조.
    + +
    public_folder
    +
    + public 파일이 제공될 폴더의 경로. + static 파일 제공이 활성화된 경우만 사용됨(아래 static참조). + 만약 설정이 없으면 app_file로부터 유추됨. +
    + +
    reload_templates
    +
    + 요청 간에 템플릿을 리로드(reload)할 건지 여부. 개발 모드에서는 활성됨. +
    + +
    root
    +
    + 프로젝트 루트 디렉터리 경로. 설정이 없으면 app_file 설정으로부터 유추됨. +
    + +
    raise_errors
    +
    + 예외 발생(애플리케이션은 중단됨). + 기본값은 environment"test"인 경우는 활성, 그렇지 않으면 비활성. +
    + +
    run
    +
    + 활성화되면, Sinatra가 웹서버의 시작을 핸들링. + rackup 또는 다른 도구를 사용하는 경우라면 활성화시키지 말 것. +
    + +
    running
    +
    빌트인 서버가 실행 중인가? 이 설정은 변경하지 말 것!
    + +
    server
    +
    + 빌트인 서버로 사용할 서버 또는 서버 목록. + 기본값은 루비구현에 따라 다르며 순서는 우선순위를 의미. +
    + +
    sessions
    +
    + Rack::Session::Cookie를 사용한 쿠키 기반 세션 활성화. + 보다 자세한 정보는 '세션 사용하기' 참조. +
    + +
    show_exceptions
    +
    + 예외 발생 시에 브라우저에 스택 추적을 보임. + 기본값은 environment"development"인 + 경우는 활성, 나머지는 비활성. +
    +
    + :after_handler를 설정함으로써 브라우저에서 + 스택 트레이스를 보여주기 전에 앱에 특화된 에러 핸들링을 + 할 수도 있음. +
    + +
    static
    +
    Sinatra가 정적(static) 파일을 핸들링할 지 여부를 설정.
    +
    이 기능이 가능한 서버를 사용하는 경우라면 비활성시킬 것.
    +
    비활성시키면 성능이 올라감.
    +
    + 기본값은 전통적 방식에서는 활성, 모듈 앱에서는 비활성. +
    + +
    static_cache_control
    +
    + Sinatra가 정적 파일을 제공하는 경우, 응답에 Cache-Control 헤더를 + 추가할 때 설정. cache_control 헬퍼를 사용. + 기본값은 비활성. +
    +
    + 여러 값을 설정할 경우는 명시적으로 배열을 사용할 것: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + true로 설정하면, Thin이 요청을 처리하는데 있어 + EventMachine.defer를 사용하도록 함. +
    + +
    views
    +
    + 뷰 폴더 경로. 설정하지 않은 경우 app_file로부터 유추됨. +
    + +
    x_cascade
    +
    + 라우트를 찾지못했을 때의 X-Cascade 해더를 설정여부. + 기본값은 true +
    +
    + + +## 환경(Environments) + +3가지의 미리 정의된 `environments` `"development"`, `"production"`, `"test"` +가 있습니다. 환경은 `APP_ENV` 환경 변수를 통해서도 설정됩니다. 기본값은 +`"development"`입니다. `"development"` 모드에서는 모든 템플릿들은 요청 간에 +리로드됩니다. 또, `"development"` 모드에서는 특별한 `not_found` 와 `error` +핸들러가 브라우저에서 스택 트레이스를 볼 수 있게합니다. +`"production"`과 `"test"`에서는 기본적으로 템플릿은 캐시됩니다. + +다른 환경으로 실행시키려면 `APP_ENV` 환경 변수를 사용하세요. + +```shell +APP_ENV=production ruby my_app.rb +``` + +현재 설정된 환경이 무엇인지 검사하기 위해서는 준비된 `development?`, `test?`, +`production?` 메서드를 사용할 수 있습니다. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## 에러 처리(Error Handling) + +예외 핸들러는 라우터 및 사전 필터와 동일한 맥락에서 실행됩니다. +이 말인즉, `haml`, `erb`, `halt`같은 이들이 제공하는 모든 것들을 사용할 수 +있다는 뜻입니다. + +### 찾을 수 없음(Not Found) + +`Sinatra::NotFound` 예외가 발생하거나 또는 응답의 상태 코드가 404라면, +`not_found` 핸들러가 호출됩니다. + +```ruby +not_found do + '아무 곳에도 찾을 수 없습니다.' +end +``` + +### 에러 + +`error` 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출됩니다. +하지만 개발 환경에서는 예외 확인 옵션을 `:after_handler`로 설정되어 있을 경우에만 +실행됨을 주의하세요. + +```ruby +set :show_exceptions, :after_handler +``` + +예외 객체는 Rack 변수 `sinatra.error`로부터 얻을 수 있습니다. + +```ruby +error do + '고약한 오류가 발생했군요 - ' + env['sinatra.error'].message +end +``` + +사용자 정의 오류는 이렇게 정의합니다. + +```ruby +error MyCustomError do + '무슨 일이 생겼나면요...' + env['sinatra.error'].message +end +``` + +그런 다음, 이 오류가 발생하면 이렇게 처리합니다. + +```ruby +get '/' do + raise MyCustomError, '안좋은 일' +end +``` + +결과는 이렇습니다. + +``` +무슨 일이 생겼냐면요... 안좋은 일 +``` + +상태 코드에 대해 오류 핸들러를 설치할 수도 있습니다. + +```ruby +error 403 do + '액세스가 금지됨' +end + +get '/secret' do + 403 +end +``` + +범위로 지정할 수도 있습니다. + +```ruby +error 400..510 do + '어이쿠' +end +``` + +Sinatra는 개발 환경에서 동작할 때 브라우저에 괜찮은 스택 트레이스와 추가적인 +디버그 정보를 보여주기 위해 특별한 `not_found` 와 `error` 핸들러를 설치합니다. + +## Rack 미들웨어(Middleware) + +Sinatra는 [Rack](http://rack.github.io/) 위에서 동작하며, Rack은 루비 웹 +프레임워크를 위한 최소한의 표준 인터페이스입니다. Rack이 애플리케이션 개발자들에게 +제공하는 가장 흥미로운 기능은 "미들웨어(middleware)"에 대한 지원입니다. +여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을 +모니터링하거나/조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트입니다. + +Sinatra는 톱레벨의 `use` 메서드를 사용하여 Rack 미들웨어의 파이프라인을 만드는 일을 +식은 죽 먹기로 만듭니다. + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`문법은 [Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(rackup 파일에서 가장 많이 사용)에서 정의한 것과 동일합니다. 예를 들어, `use` 메서드는 +블록이나 여러 개의/가변적인 인자도 받을 수 있습니다. + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack은 로깅, 디버깅, URL 라우팅, 인증, 그리고 세센 핸들링을 위한 다양한 표준 +미들웨어로 분산되어 있습니다. Sinatra는 설정에 기반하여 이들 컴포넌트들 중 +많은 것들을 자동으로 사용하며, 따라서 여러분은 일반적으로는 `use`를 명시적으로 +사용할 필요가 없을 것입니다. + +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) +에서 유용한 미들웨어들을 찾을 수 있습니다. + +## 테스팅(Testing) + +Sinatra 테스트는 많은 Rack 기반 테스팅 라이브러리, 프레임워크를 사용하여 작성가능합니다. +그 중 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)를 권장합니다. + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +주의: Sinatra를 모듈러 방식으로 사용한다면, `Sinatra::Application` +를 앱에서 사용하는 클래스 이름으로 바꾸세요. + +## Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps) + +톱레벨에서 앱을 정의하는 것은 마이크로 앱(micro-app) 수준에서는 잘 동작하지만, +Rack 미들웨어나, Rails 메탈(metal) 또는 서버 컴포넌트를 갖는 간단한 라이브러리, +또는 더 나아가 Sinatra 익스텐션(extension) 같은 재사용 가능한 컴포넌트들을 구축할 +경우에는 심각한 약점이 있습니다. 톱레벨은 마이크로 앱 스타일의 설정을 가정하는 것 +입니다. (즉, 하나의 단일 애플리케이션 파일과 `./public` 및 `./views` 디렉터리, +로깅, 예외 상세 페이지 등등). 이 곳에서 `Sinatra::Base`가 필요합니다. + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base` 서브클래스에서 사용가능한 메서드들은 톱레벨 DSL로 접근 가능한 것들과 +동일합니다. 대부분의 톱레벨 앱들은 다음 두 가지만 수정하면 `Sinatra::Base` 컴포넌트로 +변환 가능합니다. + +* 파일은 `sinatra`가 아닌 `sinatra/base`를 require해야 합니다. + 그렇지 않으면 모든 Sinatra의 DSL 메서드들이 메인 네임스페이스에 불러지게 + 됩니다. +* 앱의 라우터, 예외 핸들러, 필터, 옵션은 `Sinatra::Base`의 서브클래스에 두어야 + 합니다. + +`Sinatra::Base`는 백지상태(blank slate)입니다. 빌트인 서버를 비롯한 대부분의 옵션들이 +기본값으로 꺼져 있습니다. 가능한 옵션들과 그 작동에 대한 상세는 [옵션과 +설정](http://www.sinatrarb.com/configuration.html)을 참조하세요. + +### 모듈(Modular) vs. 전통적 방식(Classic Style) + +일반적인 믿음과는 반대로, 전통적 방식에 잘못된 부분은 없습니다. 여러분 애플리케이션에 +맞다면, 모듈 애플리케이션으로 전환할 필요는 없습니다. + +모듈 방식이 아닌 전통적 방식을 사용할 경우 생기는 주된 단점은 루비 프로세스 당 +하나의 Sinatra 애플리케이션만 사용할 수 있다는 점입니다. 만약 하나 이상을 사용할 +계획이라면 모듈 방식으로 전환하세요. 모듈 방식과 전통적 방식을 섞어쓰지 못할 +이유는 없습니다. + +방식을 전환할 경우에는, 기본값 설정의 미묘한 차이에 유의해야 합니다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    설정전통적 방식모듈
    app_filesinatra를 로딩하는 파일Sinatra::Base를 서브클래싱한 파일
    run$0 == app_filefalse
    logging truefalse
    method_overridetruefalse
    inline_templatestruefalse
    statictrueFile.exist?(public_folder)
    + +### 모듈 애플리케이션(Modular Application) 제공하기 + +모듈 앱을 시작하는 두 가지 일반적인 옵션이 있습니다. +`run!`으로 능동적으로 시작하는 방법은 이렇습니다. + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... 여기에 앱 코드가 온다 ... + + # 루비 파일이 직접 실행될 경우에 서버를 시작 + run! if app_file == $0 +end +``` + +이렇게 시작할 수도 있습니다. + +```shell +ruby my_app.rb +``` + +`config.ru`와 함께 사용할수도 있습니다. 이 경우는 어떠한 Rack 핸들러도 사용할 수 있도록 +허용 합다. + +```ruby +# config.ru +require './my_app' +run MyApp +``` + +실행은 이렇게 합니다. + +```shell +rackup -p 4567 +``` + +### config.ru로 전통적 방식의 애플리케이션 사용하기 + +앱 파일을 다음과 같이 작성합니다. + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +대응하는 `config.ru`는 다음과 같이 작성합니다. + +```ruby +require './app' +run Sinatra::Application +``` + +### 언제 config.ru를 사용할까? + +`config.ru`는 다음 경우에 권장 됩니다. + +* 다른 Rack 핸들러(Passenger, Unicorn, Heroku, ...)로 배포하고자 할 때. +* 하나 이상의 `Sinatra::Base` 서브클래스를 사용하고자 할 때. +* Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때. + +**모듈 방식으로 전환했다는 이유만으로 `config.ru`로 전환할 필요는 없으며, +또한 `config.ru`를 사용한다고 해서 모듈 방식을 사용해야 하는 것도 아닙니다.** + +### Sinatra를 미들웨어로 사용하기 + +Sinatra에서 다른 Rack 미들웨어를 사용할 수 있을 뿐 아니라, +어떤 Sinatra 애플리케이션에서도 순차로 어떠한 Rack 종착점 앞에 미들웨어로 +추가될 수 있습니다. 이 종착점은 다른 Sinatra 애플리케이션이 될 수도 있고, +또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/...)이 될 수도 +있습니다. + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 미들웨어는 사전 필터보다 앞서 실행됨 + use LoginScreen + + before do +unless session['user_name'] + halt "접근 거부됨, 로그인 하세요." +end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 동적인 애플리케이션 생성(Dynamic Application Creation) + +어떤 상수에 할당하지 않고 런타임에서 새 애플리케이션들을 생성하려면, +`Sinatra.new`를 쓰면 됩니다. + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +선택적 인자로 상속할 애플리케이션을 받을 수 있습니다. + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +이 방법은 Sintra 익스텐션을 테스팅하거나 또는 여러분의 라이브러리에서 Sinatra를 +사용할 경우에 특히 유용합니다. + +이 방법은 Sinatra를 미들웨어로 사용하는 것을 아주 쉽게 만들어 주기도 합니다. + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## 범위(Scopes)와 바인딩(Binding) + +현재 어느 범위에 있느냐가 어떤 메서드와 변수를 사용할 수 있는지를 결정합니다. + +### 애플리케이션/클래스 범위 + +모든 Sinatra 애플리케이션은 `Sinatra::Base`의 서브클래스에 대응됩니다. +만약 톱레벨 DSL (`require 'sinatra'`)을 사용한다면, 이 클래스는 +`Sinatra::Application`이며, 그렇지 않을 경우라면 여러분이 명시적으로 생성한 +그 서브클래스가 됩니다. 클래스 레벨에서는 `get` 이나 `before` 같은 메서드들을 +가지나, `request` 객체나 `session` 에는 접근할 수 없습니다. 왜냐면 모든 요청에 +대해 애플리케이션 클래스는 오직 하나이기 때문입니다. + +`set`으로 생성한 옵션들은 클래스 레벨의 메서드들입니다. + +```ruby +class MyApp < Sinatra::Base + # 저기요, 저는 애플리케이션 범위에 있다구요! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요! + end +end +``` + +애플리케이션 범위에는 이런 것들이 있습니다. + +* 애플리케이션 클래스 본문 +* 확장으로 정의된 메서드 +* `helpers`로 전달된 블록 +* `set`의 값으로 사용된 Procs/blocks +* `Sinatra.new`로 전달된 블록 + +범위 객체 (클래스)는 다음과 같이 접근할 수 있습니다. + +* configure 블록으로 전달된 객체를 통해(`configure { |c| ... }`) +* 요청 범위 내에서 `settings` + +### 요청/인스턴스 범위 + +매 요청마다, 애플리케이션 클래스의 새 인스턴스가 생성되고 모든 핸들러 블록은 +그 범위 내에서 실행됩니다. 범위 내에서 여러분은 `request` 와 `session` 객체에 +접근하거나 `erb` 나 `haml` 같은 렌더링 메서드를 호출할 수 있습니다. 요청 범위 +내에서 `settings` 헬퍼를 통해 애플리케이션 범위에 접근 가능합니다. + +```ruby +class MyApp < Sinatra::Base + # 이봐요, 전 애플리케이션 범위에 있다구요! + get '/define_route/:name' do + # '/define_route/:name'의 요청 범위 + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"의 요청 범위 + @value # => nil (동일한 요청이 아님) + end + + "라우터가 정의됨!" + end +end +``` + +요청 범위에는 이런 것들이 있습니다. + +* get/head/post/put/delete/options 블록 +* before/after 필터 +* 헬퍼(helper) 메서드 +* 템플릿/뷰 + +### 위임 범위(Delegation Scope) + +위임 범위(delegation scope)는 메서드를 단순히 클래스 범위로 보냅니다(forward). +하지만 클래스 바인딩을 갖지 않기에 완전히 클래스 범위처럼 동작하지는 않습니다. +오직 명시적으로 위임(delegation) 표시된 메서드들만 사용 가능하고, +또한 클래스 범위와 변수/상태를 공유하지 않습니다 (유의: `self`가 다름). +`Sinatra::Delegator.delegate :method_name`을 호출하여 메서드 위임을 명시적으로 +추가할 수 있습니다. + +위임 범위에는 이런 것들이 있습니다. + +* 톱레벨 바인딩, `require "sinatra"`를 한 경우 +* `Sinatra::Delegator` 믹스인으로 확장된 객체 + +직접 코드를 살펴보길 바랍니다. +[Sinatra::Delegator 믹스인](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +은 [메인 객체를 확장한 것](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)입니다. + +## 명령행(Command Line) + +Sinatra 애플리케이션은 직접 실행할 수 있습니다. + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +옵션들은 다음과 같습니다. + +``` +-h # 도움말 +-p # 포트 설정 (기본값은 4567) +-o # 호스트 설정 (기본값은 0.0.0.0) +-e # 환경 설정 (기본값은 development) +-s # rack 서버/핸들러 지정 (기본값은 thin) +-x # mutex 잠금 켜기 (기본값은 off) +``` + +### 다중 스레드(Multi-threading) + +_Konstantin의 [StackOverflow의 답변][so-answer]에서 가져왔습니다_ + +시나트라는 동시성 모델을 전혀 사용하지 않지만, Thin, Puma, WEBrick 같은 +기저의 Rack 핸들러(서버)는 사용합니다. 시나트라 자신은 스레드에 안전하므로 +랙 핸들러가 동시성 스레드 모델을 사용한다고해도 문제가 되지는 않습니다. +이는 서버를 시작할 때, 서버에 따른 정확한 호출 방법을 사용했을 때의 +이야기입니다. 밑의 예제는 다중 스레드 Thin 서버를 시작하는 방법입니다. + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +서버를 시작하는 명령어는 다음과 같습니다. + +```shell +thin --threaded start +``` + + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## 요구사항(Requirement) + +다음의 루비 버전은 공식적으로 지원됩니다. +
    +
    Ruby 1.8.7
    +
    + 1.8.7은 완전하게 지원되지만, 꼭 그래야할 특별한 이유가 없다면, + 1.9.2로 업그레이드하거나 또는 JRuby나 Rubinius로 전환할 것을 권장합니다. + 1.8.7에 대한 지원은 Sinatra 2.0 이전에는 중단되지 않을 것입니다. + Ruby 1.8.6은 더이상 지원하지 않습니다. +
    + +
    Ruby 1.9.2
    +
    + 1.9.2는 완전하게 지원됩니다. 1.9.2p0은, Sinatra를 실행했을 때 세그먼트 오류가 + 발생할수 있으므로 쓰지 마세요. 공식 지원은 Sinatra 1.5 이전에는 중단되지 않을 + 것입니다. +
    + +
    Ruby 1.9.3
    +
    + 1.9.3은 완전하게 지원되고 권장합니다. 이전 버전에서 1.9.3으로 전환할 경우 모든 + 세션이 무효화되므로 주의하세요. 1.9.3에 대한 지원은 Sinatra 2.0 이전에는 + 중단되지 않을 것입니다. +
    + +
    Ruby 2.x
    +
    + 2.x은 완전하게 지원되고 권장합니다. 현재 공식 지원 중지 계획은 없습니다. +
    + +
    Rubinius
    +
    + Rubinius는 공식적으로 지원됩니다. (Rubinius >= 2.x) + gem install puma를 권장합니다. +
    + +
    JRuby
    +
    + JRuby의 마지막 안정판은 공식적으로 지원됩니다. C 확장을 JRuby와 사용하는 + 것은 권장되지 않습니다. + gem install trinidad를 권장합니다. +
    +
    + +새로 나오는 루비 버전도 주시하고 있습니다. + +다음 루비 구현체들은 공식적으로 지원하지 않지만 +여전히 Sinatra를 실행할 수 있는 것으로 알려져 있습니다. + +* JRuby와 Rubinius 예전 버전 +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 및 1.9.1 (이 버전들은 사용하지 말 것을 권합니다) + +공식적으로 지원하지 않는다는 것의 의미는 무언가가 그 플랫폼에서만 잘못 동작하고, +지원되는 플랫폼에서는 정상적으로 동작할 경우, 우리의 문제가 아니라 그 플랫폼의 문제로 +간주한다는 뜻입니다. + +또한 우리는 CI를 ruby-head (MRI의 이후 릴리즈) 브랜치에 맞춰 실행하지만, +계속해서 변하고 있기 때문에 아무 것도 보장할 수는 없습니다. +앞으로 나올 2.x가 완전히 지원되길 기대합시다. + +Sinatra는 선택한 루비 구현체가 지원하는 어떠한 운영체제에서도 작동해야 +합니다. + +MacRuby를 사용한다면, gem install control_tower 를 실행해 주세요. + +현재 Cardinal, SmallRuby, BlueRuby 또는 1.8.7 이전의 루비 버전에서는 +Sinatra를 실행할 수 없을 것입니다. + +## 최신(The Bleeding Edge) + +Sinatra의 가장 최근 코드를 사용하고자 한다면, 애플리케이션을 마스터 브랜치에 맞춰 +실행하면 되므로 부담가지지 마세요. 하지만 덜 안정적일 것입니다. + +주기적으로 사전배포(prerelease) 젬을 푸시하기 때문에, 최신 기능들을 얻기 위해 +다음과 같이 할 수도 있습니다. + +```shell +gem install sinatra --pre +``` + +### Bundler를 사용하여 + +여러분 애플리케이션을 최신 Sinatra로 실행하고자 한다면, +[Bundler](http://bundler.io)를 사용할 것을 권장합니다. + +우선, 아직 설치하지 않았다면 bundler를 설치합니다. + +```shell +gem install bundler +``` + +그런 다음, 프로젝트 디렉터리에서, `Gemfile`을 만듭니다. + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 다른 의존관계들 +gem 'haml' # 예를 들어, haml을 사용한다면 +gem 'activerecord', '~> 3.0' # 아마도 ActiveRecord 3.x도 필요할 것 +``` + +`Gemfile`안에 애플리케이션의 모든 의존성을 적어야 합니다. +하지만, Sinatra가 직접적인 의존관계에 있는 것들(Rack과 Tilt)은 +Bundler가 자동으로 찾아서 추가할 것입니다. + +이제 앱을 실행할 수 있습니다. + +```shell +bundle exec ruby myapp.rb +``` + +### 직접 하기(Roll Your Own) + +로컬 클론(clone)을 생성한 다음 `$LOAD_PATH`에 `sinatra/lib` 디렉터리를 주고 +여러분 앱을 실행합니다. + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +이후에 Sinatra 소스를 업데이트하려면 이렇게 하세요. + +```shell +cd myapp/sinatra +git pull +``` + +### 전역으로 설치(Install Globally) + +젬을 직접 빌드할 수 있습니다. + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 합니다. + +```shell +sudo rake install +``` + +## 버저닝(Versioning) + +Sinatra는 [시맨틱 버저닝Semantic Versioning](http://semver.org/) +[(번역)](http://surpreem.com/archives/380)의 SemVer, +SemVerTag를 준수합니다. + +## 더 읽을 거리(Further Reading) + +* [프로젝트 웹사이트](http://www.sinatrarb.com/) - 추가 문서들, 뉴스, + 그리고 다른 리소스들에 대한 링크. +* [기여하기](http://www.sinatrarb.com/contributing) - 버그를 찾았나요? + 도움이 필요한가요? 패치를 하셨나요? +* [이슈 트래커](https://github.com/sinatra/sinatra/issues) +* [트위터](https://twitter.com/sinatra) +* [메일링 리스트](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) http://freenode.net +* 슬랙의 [Sinatra & Friends](https://sinatrarb.slack.com)입니다. + [여기](https://sinatra-slack.herokuapp.com/)에서 가입가능합니다. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 튜토리얼 +* [Sinatra Recipes](http://recipes.sinatrarb.com/) 커뮤니티가 만드는 레시피 +* http://www.rubydoc.info/에 있는 [최종 릴리스](http://www.rubydoc.info/gems/sinatra) + 또는 [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra)에 대한 API 문서 +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.malayalam.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.malayalam.md new file mode 100644 index 0000000000..422be089f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.malayalam.md @@ -0,0 +1,3141 @@ +# സിനാട്ര + +[![gem വേർഷൻ ](https://badge.fury.io/rb/sinatra.svg)](http://badge.fury.io/rb/sinatra) +[![ബിൽഡ് സ്റ്റാറ്റസ്](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) +[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver) + + വെബ് അപ്പ്ലിക്കേഷൻസ് എളുപ്പത്തിൽ ഉണ്ടാക്കാനുള്ള ഒരു ലൈബ്രറി [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) ആണ്.: + +```റൂബി +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +gem ഇൻസ്റ്റാൾ ചെയ്യുവാൻ: + +```shell +gem install sinatra +``` + +റൺ ചെയ്യുവാൻ : + +```shell / ഷെൽ +ruby myapp.rb +``` + +View at: [http://localhost:4567](http://localhost:4567) + + സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാതെ നിങ്ങളുടെ കോഡ് ചേഞ്ച് കാണാൻ സാധിക്കുകയില്ല +കോഡ് ചേഞ്ച് ചെയ്യുമ്പോൾ സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാൻ മറക്കരുത് +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +എപ്പോഴും `gem install thin`,എന്ന് റൺ ചെയ്യുക , ഇത് ഏറ്റവും പുതിയ അപ്ലിക്കേഷൻ സെലക്ട് ചെയ്യാൻ നമ്മളെ സഹായിക്കും . + +## ഉള്ളടക്കം + +* [സിനാട്ര](#sinatra) + * [ഉള്ളടക്കം](#table-of-contents) + * [റൂട്സ്](#routes) + * [കണ്ടിഷൻസ്](#conditions) + * [റിട്ടേൺ വാല്യൂസ്](#return-values) + * [കസ്റ്റമ് റൂട്ട് മടീച്ചേഴ്സ് ](#custom-route-matchers) + * [സ്റ്റാറ്റിക് files](#static-files) + * [വ്യൂസ് / ടെംപ്ലേറ്റ്സ്](#views--templates) + * [ലിറ്ററൽ ടെംപ്ലേറ്റ്സ് ](#literal-templates) + * [ലഭ്യമായ ടെംപ്ലേറ്റ്സ് ഭാഷകൾ ](#available-template-languages) + * [Haml ടെംപ്ലേറ്റ്സ്](#haml-templates) + * [Erb ടെംപ്ലേറ്റ്സ്](#erb-templates) + * [Builder ടെംപ്ലേറ്റ്സ്](#builder-templates) + * [nokogiri ടെംപ്ലേറ്റ്സ്](#nokogiri-templates) + * [Sass ടെംപ്ലേറ്റ്സ്](#sass-templates) + * [SCSS ടെംപ്ലേറ്റ്സ്](#scss-templates) + * [Less ടെംപ്ലേറ്റ്സ്](#less-templates) + * [Liquid ടെംപ്ലേറ്റ്സ്](#liquid-templates) + * [Markdown ടെംപ്ലേറ്റ്സ്](#markdown-templates) + * [Textile ടെംപ്ലേറ്റ്സ്](#textile-templates) + * [RDoc ടെംപ്ലേറ്റ്സ്](#rdoc-templates) + * [AsciiDoc ടെംപ്ലേറ്റ്സ്](#asciidoc-templates) + * [Radius ടെംപ്ലേറ്റ്സ്](#radius-templates) + * [Markaby ടെംപ്ലേറ്റ്സ്](#markaby-templates) + * [RABL ടെംപ്ലേറ്റ്സ്](#rabl-templates) + * [Slim ടെംപ്ലേറ്റ്സ്](#slim-templates) + * [Creole ടെംപ്ലേറ്റ്സ്](#creole-templates) + * [MediaWiki ടെംപ്ലേറ്റ്സ്](#mediawiki-templates) + * [CoffeeScript ടെംപ്ലേറ്റ്സ്](#coffeescript-templates) + * [Stylus ടെംപ്ലേറ്റ്സ്](#stylus-templates) + * [Yajl ടെംപ്ലേറ്റ്സ്](#yajl-templates) + * [WLang ടെംപ്ലേറ്റ്സ്](#wlang-templates) + * [വാരിയബിൾസിനെ എടുക്കാൻ സഹായിക്കുന്ന ടെംപ്ലേറ്റ്സ്](#accessing-variables-in-templates) + * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) + * [Inline ടെംപ്ലേറ്റ്സ്](#inline-templates) + * [പേരുള്ള ടെംപ്ലേറ്റ്സ്](#named-templates) + * [Associating File Extensions](#associating-file-extensions) + * [നിങ്ങളുടെ സ്വന്തം ടെമ്പ്ലേറ്റ് എങ്ങിനെ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു ](#adding-your-own-template-engine) + * [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup) + * [Filters](#filters) + * [Helpers](#helpers) + * [സെഷൻസ് ഉപയോഗിക്കുന്നു ](#using-sessions) + * [രഹസ്യമായി സെഷൻസ് സംരക്ഷിക്കുക ](#session-secret-security) + * [Session Config](#session-config) + * [സെഷൻ middlewate തിരഞ്ഞെടുക്കുക](#choosing-your-own-session-middleware) + * [ഹാൾട് ചെയ്യുക ](#halting) + * [Passing](#passing) + * [മറ്റൊരു റൂട്ട് ട്രിഗർ ചെയ്യുക ](#triggering-another-route) + * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) + * [Streaming Responses](#streaming-responses) + * [Logging](#logging) + * [Mime Types](#mime-types) + * [ URLs Generating](#generating-urls) + * [Browser റീഡിറക്ട് ചെയ്യുക ](#browser-redirect) + * [Cache Control](#cache-control) + * [Sending Files](#sending-files) + * [Accessing the Request Object](#accessing-the-request-object) + * [അറ്റാച്മെന്റ്സ് ](#attachments) + * [ദിവസവും സമയവും ഡീൽ ചെയ്യക ](#dealing-with-date-and-time) + * [Template Files നോക്കുന്നു ](#looking-up-template-files) + * [Configuration](#configuration) + * [Configuring attack protection](#configuring-attack-protection) + * [Available Settings](#available-settings) + * [Environments](#environments) + * [ കൈകാര്യം ചെയ്യുക ](#error-handling) + * [കണ്ടെത്താൻ ആയില്ല ](#not-found) + * [തെറ്റ്](#error) + * [Rack Middleware](#rack-middleware) + * [ടെസ്റ്റ് ചെയ്യുക ](#testing) + * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) + * [Modular vs. Classic Style](#modular-vs-classic-style) + * [Serving a Modular Application](#serving-a-modular-application) + * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) + * [When to use a config.ru?](#when-to-use-a-configru) + * [Using Sinatra as Middleware](#using-sinatra-as-middleware) + * [Dynamic Application Creation](#dynamic-application-creation) + * [Scopes and Binding](#scopes-and-binding) + * [Application/Class Scope](#applicationclass-scope) + * [Request/Instance Scope](#requestinstance-scope) + * [Delegation Scope](#delegation-scope) + * [Command Line](#command-line) + * [Multi-threading](#multi-threading) + * [ആവശ്യങ്ങൾ ](#requirement) + * [The Bleeding Edge](#the-bleeding-edge) + * [With Bundler](#with-bundler) + * [വേർഷൻ ചെയ്യുക ](#versioning) + * [Further Reading](#further-reading) + +## Routes + +In Sinatra, a route is an HTTP method paired with a URL-matching pattern. +Each route is associated with a block: + +```റൂബി +get '/' do + .. show something .. +end + +post '/' do + .. create something .. +end + +put '/' do + .. replace something .. +end + +patch '/' do + .. modify something .. +end + +delete '/' do + .. annihilate something .. +end + +options '/' do + .. appease something .. +end + +link '/' do + .. affiliate something .. +end + +unlink '/' do + .. separate something .. +end +``` + +റൂട്സ് മാച്ച് ചെയ്യാനാ രീതിയില് ആണ് അത് നിർവചിക്കുന്നത് . ഏത് റിക്വസ്റ്റ് ആണോ റൂട്ട് ആയി ചേരുന്നത് ആ റൂട്ട് ആണ് വിളിക്കപെടുക . + +ട്രെയ്ലറിങ് സ്ലാഷ്‌സ് ഉള്ള റൂട്സ് അത് ഇല്ലാത്തതിൽ നിന്ന് വ്യത്യാസം ഉള്ളത് ആണ് : + +```ruby +get '/foo' do + # Does not match "GET /foo/" +end +``` + +Route patterns may include named parameters, accessible via the +`params` hash: + +```ruby +get '/hello/:name' do + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + "Hello #{params['name']}!" +end +``` + +```ruby +get '/hello/:name' do |n| + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + # n stores params['name'] + "Hello #{n}!" +end +``` +റൂട്ട് പാട്ടേഴ്സിൽ പേരുള്ള splat ഉണ്ടാകാറുണ്ട് അതിനെ 'params['splat']' array ഉപയോഗിച്ച ഉപയോഗപ്പെടുത്താവുന്നത് ആണ് + + + +```ruby +get '/say/*/to/*' do + # matches /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # matches /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Or with block parameters: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +റെഗുലർ expressions : + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +ബ്ലോക്ക് പരാമീറ്റർസ് ഉള്ള റൂട്ട് മാച്ചിങ് : + +```ruby +get %r{/hello/([\w]+)} do |c| + # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc. + "Hello, #{c}!" +end +``` + + + +```ruby +get '/posts/:format?' do + # matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc +end +``` +റൂട്ട് പാറ്റെൺസ് ഇത് query പരാമീറ്റർസ് ഉണ്ടാകാം : + + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` +അതുപോലെ നിങ്ങൾ പാത ട്രവേഴ്സല് അറ്റാച്ച് പ്രൊട്ടക്ഷൻ (#configuring-attack-protection) ഡിസബിലെ ചെയ്തട്ടില്ലെങ്കിൽ റെക്‌സ് പാത മോഡിഫിയ ചെയ്യണത്തിനു മുൻപ് അത് മാച്ച് ചെയ്യപ്പെടും + + +You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme) +options used for a given route by passing in a `:mustermann_opts` hash: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` +ഇത് [condition](#conditions), പോലെ തോന്നുമെങ്കിലും ഇ ഒപ്റേൻസ് global `:mustermann_opts` ആയി മെർജ് ചെയ്യപ്പെട്ടിരിക്കുന്നു + +## കണ്ടിഷൻസ് + +യൂസർ അഗെന്റ്റ് പോലുള്ള മാച്ചിങ് റൂട്സ് ഇത് അടങ്ങി ഇരിക്കുന്നു +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Matches non-songbird browsers +end +``` + + ഇതുപോലുള്ള വേറെ കണ്ടിഷൻസ് ആണ് host_name , provides +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides ` ആക്‌സെപ്റ് ഹെൽഡർസ് നെ അന്വഷിക്കുന്നു + + നിങ്ങളുടെ കണ്ടിഷൻസ് ഇനി എളുപ്പത്തിൽ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +splat ഉപയോഗിച്ച പലതരത്തിൽ ഉള്ള കണ്ടിഷൻസ് ഉണ്ടാക്കാൻ സാധിക്കുന്നു : + +```ruby +set(:auth) do |*roles| # <- notice the splat here + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +## Return Values + + + + +റൂട്ട് ബ്ലോക്കിന്റെ റിട്ടേൺ വാല്യൂ HTTP client യിലേക്ക് കടത്തിവിടുന്ന രേസ്പോൻസ് ബോഡിയെ തീരുമാനിക്കുന്നു. സാധാരണയായി ഇത് ഒരു സ്ട്രിംഗ് ആണ്. പക്ഷെ മറ്റു വാല്യൂകളെയും ഇത് സ്വീകരിക്കും + +* മൂന്ന് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), headers (Hash), response + body (responds to #each)]` +* രണ്ട് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), response body (responds to + #each)]` +* An object that responds to `#each` and passes nothing but strings to + the given block +* Integer സ്റ്റാറ്റസ് കോഡിനെ കാണിക്കുന്നു + +ഇത് നമക്ക് സ്ട്രീമിംഗ് ഉദാഹരണങ്ങൾ ഉണ്ടാക്കാം +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +`stream` helper' ഉപയോഗിച്ച([described below](#streaming-responses))റൂട്ട് ഇലെ ബോയ്ലർ പ്ലേറ്റ്സ് ഇനി കുറക്കാം + +## Custom Route Matchers + +മുകളിൽ കാണിച്ചിരിക്കുന്ന പോലെ , സിനാട്ര ഉപയോഗിച്ച String patterns, regular expressions കൈകാര്യം ചെയ്യാം മാത്രമല്ല നിങ്ങളുടെ സ്വന്തം matchers ഉം ഉണ്ടാക്കാം +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +ഇതിനെ ഇങ്ങനെയും കാണിക്കാം + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Or, using negative look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Static Files + +സ്റ്റാറ്റിക് ഫിലെസ് `./public` എന്ന ഡയറക്ടറി ഇത് ആണ് ഉണ്ടാകുക +നിങ്ങൾക്ക് `:public_folder` വഴി വേറെ പാത ഉണ്ടാക്കാം + + +```ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +URL ഇളിൽ ഡയറക്ടറി പാത ഉണ്ടാകില്ല . A file +`./public/css/style.css` is made available as +`http://example.com/css/style.css`. + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info. + +## Views / Templates + +എല്ലാ ടെമ്പ്ലേറ്റ് ഭാഷയും അതിന്റെ സ്വതം റെൻഡറിങ് മെതോഡിൽ ആണ് പുറത്തു കാണപ്പെടുക. ഇത് ഒരു സ്ട്രിംഗ് ഇനി റിട്ടേൺ ചെയ്യും + +```ruby +get '/' do + erb :index +end +``` + +This renders `views/index.erb`. + +ടെമ്പ്ലേറ്റ് ഇന്റെ പേരിനു പകരം നിങ്ങൾക്ക് ടെപ്ലേറ്റ് ഇന്റെ കോൺടെന്റ് കടത്തി വിടാം + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +ടെമ്പ്ലേറ്റ് മറ്റൊരു അർജുമെന്റിനെ കടത്തി വിടുന്നു +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +This will render `views/index.erb` embedded in the +`views/post.erb` (default is `views/layout.erb`, if it exists). + +സിനാട്ര ക്ക് മനസ്സിലാകാത്ത ടെമ്പ്ലേറ്റ് ഇനി ടെമ്പ്ലേറ്റ് എന്ജിനിലേക്ക് കടത്തി വിടും : + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +നിങ്ങൾക്ക് ഓപ്ഷണൽ ആയി ലാംഗ്വേജ് ജനറലിൽ സെറ്റ് ചെയ്യാൻ കഴിയും : + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +ഉപയോഗിച്ച റെൻഡർ മെതോഡിൽ ഒപ്റേൻസ് പാസ് ചെയ്യാൻ പാട്ടും `set`. + +Available Options: + +
    +
    locals
    +
    + List of locals passed to the document. Handy with partials. + Example: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding to use if uncertain. Defaults to + settings.default_encoding. +
    + +
    views
    +
    + Views folder to load templates from. Defaults to settings.views. +
    + +
    layout
    +
    + Whether to use a layout (true or false). If it's a + Symbol, specifies what template to use. Example: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type the template produces. Default depends on template language. +
    + +
    scope
    +
    + Scope to render template under. Defaults to the application + instance. If you change this, instance variables and helper methods + will not be available. +
    + +
    layout_engine
    +
    + Template engine to use for rendering the layout. Useful for + languages that do not support layouts otherwise. Defaults to the + engine used for the template. Example: set :rdoc, :layout_engine + => :erb +
    + +
    layout_options
    +
    + Special options only used for rendering the layout. Example: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Templates are assumed to be located directly under the `./views` +directory. To use a different views directory: + +```ruby +set :views, settings.root + '/templates' +``` + + +One important thing to remember is that you always have to reference +templates with symbols, even if they're in a subdirectory (in this case, +use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a +symbol because otherwise rendering methods will render any strings +passed to them directly. + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Renders the template string. You can optionally specify `:path` and +`:line` for a clearer backtrace if there is a filesystem path or line +associated with that string: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Available Template Languages + +Some languages have multiple implementations. To specify what implementation +to use (and to be thread-safe), you should simply require it first: + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependencyhaml
    File Extension.haml
    Examplehaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependency + erubis + or erb (included in Ruby) +
    File Extensions.erb, .rhtml or .erubis (Erubis only)
    Exampleerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependency + builder +
    File Extension.builder
    Examplebuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependencynokogiri
    File Extension.nokogiri
    Examplenokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.sass
    Examplesass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.scss
    Examplescss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependencyless
    File Extension.less
    Exampleless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependencyliquid
    File Extension.liquid
    Exampleliquid :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods (except for `yield`) from a Liquid +template, you almost always want to pass locals to it. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependency + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    File Extensions.markdown, .mkd and .md
    Examplemarkdown :index, :layout_engine => :erb
    + +It is not possible to call methods from Markdown, nor to pass locals to it. +You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Note that you may also call the `markdown` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Since you cannot call Ruby from Markdown, you cannot use layouts written in +Markdown. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### Textile Templates + + + + + + + + + + + + + + +
    DependencyRedCloth
    File Extension.textile
    Exampletextile :index, :layout_engine => :erb
    + +It is not possible to call methods from Textile, nor to pass locals to +it. You therefore will usually use it in combination with another +rendering engine: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Note that you may also call the `textile` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Since you cannot call Ruby from Textile, you cannot use layouts written in +Textile. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependencyRDoc
    File Extension.rdoc
    Examplerdoc :README, :layout_engine => :erb
    + +It is not possible to call methods from RDoc, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Note that you may also call the `rdoc` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Since you cannot call Ruby from RDoc, you cannot use layouts written in +RDoc. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    DependencyAsciidoctor
    File Extension.asciidoc, .adoc and .ad
    Exampleasciidoc :README, :layout_engine => :erb
    + +Since you cannot call Ruby methods directly from an AsciiDoc template, you +almost always want to pass locals to it. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependencyRadius
    File Extension.radius
    Exampleradius :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods directly from a Radius template, you +almost always want to pass locals to it. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependencyMarkaby
    File Extension.mab
    Examplemarkaby { h1 "Welcome!" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependencyRabl
    File Extension.rabl
    Examplerabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependencySlim Lang
    File Extension.slim
    Exampleslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependencyCreole
    File Extension.creole
    Examplecreole :wiki, :layout_engine => :erb
    + +It is not possible to call methods from Creole, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note that you may also call the `creole` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Since you cannot call Ruby from Creole, you cannot use layouts written in +Creole. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependencyWikiCloth
    File Extension.mediawiki and .mw
    Examplemediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass +locals to it. You therefore will usually use it in combination with +another rendering engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Since you cannot call Ruby from MediaWiki, you cannot use layouts written in +MediaWiki. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependency + + CoffeeScript + and a + + way to execute javascript + +
    File Extension.coffee
    Examplecoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependency + + Stylus + and a + + way to execute javascript + +
    File Extension.styl
    Examplestylus :index
    + +Before being able to use Stylus templates, you need to load `stylus` and +`stylus/tilt` first: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependencyyajl-ruby
    File Extension.yajl
    Example + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + + +The template source is evaluated as a Ruby string, and the +resulting json variable is converted using `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +The `:callback` and `:variable` options can be used to decorate the rendered +object: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    DependencyWLang
    File Extension.wlang
    Examplewlang :index, :locals => { :key => 'value' }
    + +Since calling ruby methods is not idiomatic in WLang, you almost always +want to pass locals to it. Layouts written in WLang and `yield` are +supported, though. + +### Accessing Variables in Templates + +Templates are evaluated within the same context as route handlers. Instance +variables set in route handlers are directly accessible by templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Or, specify an explicit Hash of local variables: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +This is typically used when rendering templates as partials from within +other templates. + +### Templates with `yield` and nested layouts + +A layout is usually just a template that calls `yield`. +Such a template can be used either through the `:template` option as +described above, or it can be rendered with a block as follows: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +This code is mostly equivalent to `erb :index, :layout => :post`. + +Passing blocks to rendering methods is most useful for creating nested +layouts: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +This can also be done in fewer lines of code with: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Currently, the following rendering methods accept a block: `erb`, `haml`, +`liquid`, `slim `, `wlang`. Also the general `render` method accepts a block. + +### Inline Templates + +Templates may be defined at the end of the source file: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +NOTE: Inline templates defined in the source file that requires sinatra are +automatically loaded. Call `enable :inline_templates` explicitly if you +have inline templates in other source files. + +### Named Templates + +Templates may also be defined using the top-level `template` method: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +If a template named "layout" exists, it will be used each time a template +is rendered. You can individually disable layouts by passing +`:layout => false` or disable them by default via +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associating File Extensions + +To associate a file extension with a template engine, use +`Tilt.register`. For instance, if you like to use the file extension +`tt` for Textile templates, you can do the following: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adding Your Own Template Engine + +First, register your engine with Tilt, then create a rendering method: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renders `./views/index.myat`. Learn more about +[Tilt](https://github.com/rtomayko/tilt#readme). + +### Using Custom Logic for Template Lookup + +To implement your own template lookup mechanism you can write your +own `#find_template` method: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filters + +Before filters are evaluated before each request within the same context +as the routes will be and can modify the request and response. Instance +variables set in filters are accessible by routes and templates: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After filters are evaluated after each request within the same context +as the routes will be and can also modify the request and response. +Instance variables set in before filters and routes are accessible by +after filters: + +```ruby +after do + puts response.status +end +``` + +Note: Unless you use the `body` method rather than just returning a +String from the routes, the body will not yet be available in the after +filter, since it is generated later on. + +Filters optionally take a pattern, causing them to be evaluated only if the +request path matches that pattern: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Like routes, filters also take conditions: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Use the top-level `helpers` method to define helper methods for use in +route handlers and templates: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Alternatively, helper methods can be separately defined in a module: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +The effect is the same as including the modules in the application class. + +### Using Sessions + +A session is used to keep state during requests. If activated, you have one +session hash per user session: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### Session Secret Security + +To improve security, the session data in the cookie is signed with a session +secret using `HMAC-SHA1`. This session secret should optimally be a +cryptographically secure random value of an appropriate length which for +`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex +characters). You would be advised not to use a secret that is less than 32 +bytes of randomness (256 bits, 64 hex characters). It is therefore **very +important** that you don't just make the secret up, but instead use a secure +random number generator to create it. Humans are extremely bad at generating +random values. + +By default, a 32 byte secure random session secret is generated for you by +Sinatra, but it will change with every restart of your application. If you +have multiple instances of your application, and you let Sinatra generate the +key, each instance would then have a different session key which is probably +not what you want. + +For better security and usability it's +[recommended](https://12factor.net/config) that you generate a secure random +secret and store it in an environment variable on each host running your +application so that all of your application instances will share the same +secret. You should periodically rotate this session secret to a new value. +Here are some examples of how you might create a 64 byte secret and set it: + +**Session Secret Generation** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Generation (Bonus Points)** + +Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to +prefer use of system RNG facilities to generate random values instead of +userspace `OpenSSL` which MRI Ruby currently defaults to: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Environment Variable** + +Set a `SESSION_SECRET` environment variable for Sinatra to the value you +generated. Make this value persistent across reboots of your host. Since the +method for doing this will vary across systems this is for illustrative +purposes only: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Session Secret App Config** + +Setup your app config to fail-safe to a secure random secret +if the `SESSION_SECRET` environment variable is not available. + +For bonus points use the [sysrandom +gem](https://github.com/cryptosphere/sysrandom#readme) here as well: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Session Config + +If you want to configure it further, you may also store a hash with options +in the `sessions` setting: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +To share your session across other apps on subdomains of foo.com, prefix the +domain with a *.* like this instead: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Choosing Your Own Session Middleware + +Note that `enable :sessions` actually stores all data in a cookie. This +might not always be what you want (storing lots of data will increase your +traffic, for instance). You can use any Rack session middleware in order to +do so, one of the following methods can be used: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Or to set up sessions with a hash of options: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Another option is to **not** call `enable :sessions`, but instead pull in +your middleware of choice as you would any other middleware. + +It is important to note that when using this method, session based +protection **will not be enabled by default**. + +The Rack middleware to do that will also need to be added: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +See '[Configuring attack protection](#configuring-attack-protection)' for more information. + +### Halting + +To immediately stop a request within a filter or route use: + +```ruby +halt +``` + +You can also specify the status when halting: + +```ruby +halt 410 +``` + +Or the body: + +```ruby +halt 'this will be the body' +``` + +Or both: + +```ruby +halt 401, 'go away!' +``` + +With headers: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +It is of course possible to combine a template with `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +A route can punt processing to the next matching route using `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +The route block is immediately exited and control continues with the next +matching route. If no matching route is found, a 404 is returned. + +### Triggering Another Route + +Sometimes `pass` is not what you want, instead you would like to get the +result of calling another route. Simply use `call` to achieve this: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note that in the example above, you would ease testing and increase +performance by simply moving `"bar"` into a helper used by both `/foo` and +`/bar`. + +If you want the request to be sent to the same application instance rather +than a duplicate, use `call!` instead of `call`. + +Check out the Rack specification if you want to learn more about `call`. + +### Setting Body, Status Code and Headers + +It is possible and recommended to set the status code and response body with +the return value of the route block. However, in some scenarios you might +want to set the body at an arbitrary point in the execution flow. You can do +so with the `body` helper method. If you do so, you can use that method from +there on to access the body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +It is also possible to pass a block to `body`, which will be executed by the +Rack handler (this can be used to implement streaming, [see "Return Values"](#return-values)). + +Similar to the body, you can also set the status code and headers: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Like `body`, `headers` and `status` with no arguments can be used to access +their current values. + +### Streaming Responses + +Sometimes you want to start sending out data while still generating parts of +the response body. In extreme examples, you want to keep sending data until +the client closes the connection. You can use the `stream` helper to avoid +creating your own wrapper: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +This allows you to implement streaming APIs, +[Server Sent Events](https://w3c.github.io/eventsource/), and can be used as +the basis for [WebSockets](https://en.wikipedia.org/wiki/WebSocket). It can +also be used to increase throughput if some but not all content depends on a +slow resource. + +Note that the streaming behavior, especially the number of concurrent +requests, highly depends on the web server used to serve the application. +Some servers might not even support streaming at all. If the server does not +support streaming, the body will be sent all at once after the block passed +to `stream` finishes executing. Streaming does not work at all with Shotgun. + +If the optional parameter is set to `keep_open`, it will not call `close` on +the stream object, allowing you to close it at any later point in the +execution flow. This only works on evented servers, like Thin and Rainbows. +Other servers will still close the stream: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +It's also possible for the client to close the connection when trying to +write to the socket. Because of this, it's recommended to check +`out.closed?` before trying to write. + +### Logging + +In the request scope, the `logger` helper exposes a `Logger` instance: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +This logger will automatically take your Rack handler's logging settings into +account. If logging is disabled, this method will return a dummy object, so +you do not have to worry about it in your routes and filters. + +Note that logging is only enabled for `Sinatra::Application` by default, so +if you inherit from `Sinatra::Base`, you probably want to enable it yourself: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +To avoid any logging middleware to be set up, set the `logging` setting to +`nil`. However, keep in mind that `logger` will in that case return `nil`. A +common use case is when you want to set your own logger. Sinatra will use +whatever it will find in `env['rack.logger']`. + +### Mime Types + +When using `send_file` or static files you may have mime types Sinatra +doesn't understand. Use `mime_type` to register them by file extension: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +You can also use it with the `content_type` helper: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generating URLs + +For generating URLs you should use the `url` helper method, for instance, in +Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +It takes reverse proxies and Rack routers into account, if present. + +This method is also aliased to `to` (see [below](#browser-redirect) for an example). + +### Browser Redirect + +You can trigger a browser redirect with the `redirect` helper method: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Any additional parameters are handled like arguments passed to `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +You can also easily redirect back to the page the user came from with +`redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +To pass arguments with a redirect, either add them to the query: + +```ruby +redirect to('/bar?sum=42') +``` + +Or use a session: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache Control + +Setting your headers correctly is the foundation for proper HTTP caching. + +You can easily set the Cache-Control header like this: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Pro tip: Set up caching in a before filter: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +If you are using the `expires` helper to set the corresponding header, +`Cache-Control` will be set automatically for you: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +To properly use caches, you should consider using `etag` or `last_modified`. +It is recommended to call those helpers *before* doing any heavy lifting, as +they will immediately flush a response if the client already has the current +version in its cache: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +It is also possible to use a +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +These helpers will not do any caching for you, but rather feed the necessary +information to your cache. If you are looking for a quick +reverse-proxy caching solution, try +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info to static files. + +According to RFC 2616, your application should behave differently if the +If-Match or If-None-Match header is set to `*`, depending on whether the +resource requested is already in existence. Sinatra assumes resources for +safe (like get) and idempotent (like put) requests are already in existence, +whereas other resources (for instance post requests) are treated as new +resources. You can change this behavior by passing in a `:new_resource` +option: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +If you still want to use a weak ETag, pass in a `:kind` option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Sending Files + +To return the contents of a file as the response, you can use the `send_file` +helper method: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +It also takes options: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +The options are: + +
    +
    filename
    +
    File name to be used in the response, + defaults to the real file name.
    +
    last_modified
    +
    Value for Last-Modified header, defaults to the file's mtime.
    + +
    type
    +
    Value for Content-Type header, guessed from the file extension if + missing.
    + +
    disposition
    +
    + Value for Content-Disposition header, possible values: nil + (default), :attachment and :inline +
    + +
    length
    +
    Value for Content-Length header, defaults to file size.
    + +
    status
    +
    + Status code to be sent. Useful when sending a static file as an error + page. If supported by the Rack handler, other means than streaming + from the Ruby process will be used. If you use this helper method, + Sinatra will automatically handle range requests. +
    +
    + +### Accessing the Request Object + +The incoming request object can be accessed from request level (filter, +routes, error handlers) through the `request` method: + +```ruby +# app running on http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # request body sent by the client (see below) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # length of request.body + request.media_type # media type of request.body + request.host # "example.com" + request.get? # true (similar methods for other verbs) + request.form_data? # false + request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. + request.referrer # the referrer of the client or '/' + request.user_agent # user agent (used by :agent condition) + request.cookies # hash of browser cookies + request.xhr? # is this an ajax request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # client IP address + request.secure? # false (would be true over ssl) + request.forwarded? # true (if running behind a reverse proxy) + request.env # raw env hash handed in by Rack +end +``` + +Some options, like `script_name` or `path_info`, can also be written: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +The `request.body` is an IO or StringIO object: + +```ruby +post "/api" do + request.body.rewind # in case someone already read it + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Attachments + +You can use the `attachment` helper to tell the browser the response should +be stored on disk rather than displayed in the browser: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +You can also pass it a file name: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Dealing with Date and Time + +Sinatra offers a `time_for` helper method that generates a Time object from +the given value. It is also able to convert `DateTime`, `Date` and similar +classes: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +This method is used internally by `expires`, `last_modified` and akin. You +can therefore easily extend the behavior of those methods by overriding +`time_for` in your application: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Looking Up Template Files + +The `find_template` helper is used to find template files for rendering: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +This is not really useful. But it is useful that you can actually override +this method to hook in your own lookup mechanism. For instance, if you want +to be able to use more than one view directory: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Another example would be using different directories for different engines: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +You can also easily wrap this up in an extension and share with others! + +Note that `find_template` does not check if the file really exists but +rather calls the given block for all possible paths. This is not a +performance issue, since `render` will use `break` as soon as a file is +found. Also, template locations (and content) will be cached if you are not +running in development mode. You should keep that in mind if you write a +really crazy method. + +## Configuration + +Run once, at startup, in any environment: + +```ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +``` + +Run only when the environment (`APP_ENV` environment variable) is set to +`:production`: + +```ruby +configure :production do + ... +end +``` + +Run when the environment is set to either `:production` or `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +You can access those options via `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configuring attack protection + +Sinatra is using +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) to +defend your application against common, opportunistic attacks. You can +easily disable this behavior (which will open up your application to tons +of common vulnerabilities): + +```ruby +disable :protection +``` + +To skip a single defense layer, set `protection` to an options hash: + +```ruby +set :protection, :except => :path_traversal +``` +You can also hand in an array in order to disable a list of protections: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +By default, Sinatra will only set up session based protection if `:sessions` +have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up +sessions "outside" of the Sinatra app, such as in the config.ru or with a +separate `Rack::Builder` instance. In that case you can still set up session +based protection by passing the `:session` option: + +```ruby +set :protection, :session => true +``` + +### Available Settings + +
    +
    absolute_redirects
    +
    + If disabled, Sinatra will allow relative redirects, however, Sinatra + will no longer conform with RFC 2616 (HTTP 1.1), which only allows + absolute redirects. +
    +
    + Enable if your app is running behind a reverse proxy that has not been + set up properly. Note that the url helper will still produce + absolute URLs, unless you pass in false as the second + parameter. +
    +
    Disabled by default.
    + +
    add_charset
    +
    + Mime types the content_type helper will automatically add the + charset info to. You should add to it rather than overriding this + option: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path to the main application file, used to detect project root, views + and public folder and inline templates. +
    + +
    bind
    +
    + IP address to bind to (default: 0.0.0.0 or + localhost if your `environment` is set to development). Only + used for built-in server. +
    + +
    default_encoding
    +
    Encoding to assume if unknown (defaults to "utf-8").
    + +
    dump_errors
    +
    Display errors in the log.
    + +
    environment
    +
    + Current environment. Defaults to ENV['APP_ENV'], or + "development" if not available. +
    + +
    logging
    +
    Use the logger.
    + +
    lock
    +
    + Places a lock around every request, only running processing on request + per Ruby process concurrently. +
    +
    Enabled if your app is not thread-safe. Disabled by default.
    + +
    method_override
    +
    + Use _method magic to allow put/delete forms in browsers that + don't support it. +
    + +
    mustermann_opts
    +
    + A default hash of options to pass to Mustermann.new when compiling routing + paths. +
    + +
    port
    +
    Port to listen on. Only used for built-in server.
    + +
    prefixed_redirects
    +
    + Whether or not to insert request.script_name into redirects + if no absolute path is given. That way redirect '/foo' would + behave like redirect to('/foo'). Disabled by default. +
    + +
    protection
    +
    + Whether or not to enable web attack protections. See protection section + above. +
    + +
    public_dir
    +
    Alias for public_folder. See below.
    + +
    public_folder
    +
    + Path to the folder public files are served from. Only used if static + file serving is enabled (see static setting below). Inferred + from app_file setting if not set. +
    + +
    quiet
    +
    + Disables logs generated by Sinatra's start and stop commands. + false by default. +
    + +
    reload_templates
    +
    + Whether or not to reload templates between requests. Enabled in + development mode. +
    + +
    root
    +
    + Path to project root folder. Inferred from app_file setting + if not set. +
    + +
    raise_errors
    +
    + Raise exceptions (will stop application). Enabled by default when + environment is set to "test", disabled otherwise. +
    + +
    run
    +
    + If enabled, Sinatra will handle starting the web server. Do not + enable if using rackup or other means. +
    + +
    running
    +
    Is the built-in server running now? Do not change this setting!
    + +
    server
    +
    + Server or list of servers to use for built-in server. Order indicates + priority, default depends on Ruby implementation. +
    + +
    server_settings
    +
    + If you are using a WEBrick web server, presumably for your development + environment, you can pass a hash of options to server_settings, + such as SSLEnable or SSLVerifyClient. However, web + servers such as Puma and Thin do not support this, so you can set + server_settings by defining it as a method when you call + configure. +
    + +
    sessions
    +
    + Enable cookie-based sessions support using + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    session_store
    +
    + The Rack session middleware used. Defaults to + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    show_exceptions
    +
    + Show a stack trace in the browser when an exception happens. Enabled by + default when environment is set to "development", + disabled otherwise. +
    +
    + Can also be set to :after_handler to trigger app-specified + error handling before showing a stack trace in the browser. +
    + +
    static
    +
    Whether Sinatra should handle serving static files.
    +
    Disable when using a server able to do this on its own.
    +
    Disabling will boost performance.
    +
    + Enabled by default in classic style, disabled for modular apps. +
    + +
    static_cache_control
    +
    + When Sinatra is serving static files, set this to add + Cache-Control headers to the responses. Uses the + cache_control helper. Disabled by default. +
    +
    + Use an explicit array when setting multiple values: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + If set to true, will tell Thin to use + EventMachine.defer for processing the request. +
    + +
    traps
    +
    Whether Sinatra should handle system signals.
    + +
    views
    +
    + Path to the views folder. Inferred from app_file setting if + not set. +
    + +
    x_cascade
    +
    + Whether or not to set the X-Cascade header if no route matches. + Defaults to true. +
    +
    + +## Environments + +There are three predefined `environments`: `"development"`, +`"production"` and `"test"`. Environments can be set through the +`APP_ENV` environment variable. The default value is `"development"`. +In the `"development"` environment all templates are reloaded between +requests, and special `not_found` and `error` handlers display stack +traces in your browser. In the `"production"` and `"test"` environments, +templates are cached by default. + +To run different environments, set the `APP_ENV` environment variable: + +```shell +APP_ENV=production ruby my_app.rb +``` + +You can use predefined methods: `development?`, `test?` and `production?` to +check the current environment setting: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Error Handling + +Error handlers run within the same context as routes and before filters, +which means you get all the goodies it has to offer, like `haml`, `erb`, +`halt`, etc. + +### Not Found + +When a `Sinatra::NotFound` exception is raised, or the response's status +code is 404, the `not_found` handler is invoked: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +The `error` handler is invoked any time an exception is raised from a route +block or a filter. But note in development it will only run if you set the +show exceptions option to `:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +The exception object can be obtained from the `sinatra.error` Rack variable: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Custom errors: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Then, if this happens: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +You get this: + +``` +So what happened was... something bad +``` + +Alternatively, you can install an error handler for a status code: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Or a range: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installs special `not_found` and `error` handlers when +running under the development environment to display nice stack traces +and additional debugging information in your browser. + +## Rack Middleware + +Sinatra rides on [Rack](https://rack.github.io/), a minimal standard +interface for Ruby web frameworks. One of Rack's most interesting +capabilities for application developers is support for "middleware" -- +components that sit between the server and your application monitoring +and/or manipulating the HTTP request/response to provide various types +of common functionality. + +Sinatra makes building Rack middleware pipelines a cinch via a top-level +`use` method: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +The semantics of `use` are identical to those defined for the +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(most frequently used from rackup files). For example, the `use` method +accepts multiple/variable args as well as blocks: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack is distributed with a variety of standard middleware for logging, +debugging, URL routing, authentication, and session handling. Sinatra uses +many of these components automatically based on configuration so you +typically don't have to `use` them explicitly. + +You can find useful middleware in +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testing + +Sinatra tests can be written using any Rack-based testing library or +framework. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +is recommended: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Note: If you are using Sinatra in the modular style, replace +`Sinatra::Application` above with the class name of your app. + +## Sinatra::Base - Middleware, Libraries, and Modular Apps + +Defining your app at the top-level works well for micro-apps but has +considerable drawbacks when building reusable components such as Rack +middleware, Rails metal, simple libraries with a server component, or even +Sinatra extensions. The top-level assumes a micro-app style configuration +(e.g., a single application file, `./public` and `./views` +directories, logging, exception detail page, etc.). That's where +`Sinatra::Base` comes into play: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +The methods available to `Sinatra::Base` subclasses are exactly the same +as those available via the top-level DSL. Most top-level apps can be +converted to `Sinatra::Base` components with two modifications: + +* Your file should require `sinatra/base` instead of `sinatra`; + otherwise, all of Sinatra's DSL methods are imported into the main + namespace. +* Put your app's routes, error handlers, filters, and options in a subclass + of `Sinatra::Base`. + +`Sinatra::Base` is a blank slate. Most options are disabled by default, +including the built-in server. See [Configuring +Settings](http://www.sinatrarb.com/configuration.html) for details on +available options and their behavior. If you want behavior more similar +to when you define your app at the top level (also known as Classic +style), you can subclass `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modular vs. Classic Style + +Contrary to common belief, there is nothing wrong with the classic +style. If it suits your application, you do not have to switch to a +modular application. + +The main disadvantage of using the classic style rather than the modular +style is that you will only have one Sinatra application per Ruby +process. If you plan to use more than one, switch to the modular style. +There is no reason you cannot mix the modular and the classic styles. + +If switching from one style to the other, you should be aware of +slightly different default settings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SettingClassicModularModular
    app_filefile loading sinatrafile subclassing Sinatra::Basefile subclassing Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Serving a Modular Application + +There are two common options for starting a modular app, actively +starting with `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +``` + +Start with: + +```shell +ruby my_app.rb +``` + +Or with a `config.ru` file, which allows using any Rack handler: + +```ruby +# config.ru (run with rackup) +require './my_app' +run MyApp +``` + +Run: + +```shell +rackup -p 4567 +``` + +### Using a Classic Style Application with a config.ru + +Write your app file: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +And a corresponding `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### When to use a config.ru? + +A `config.ru` file is recommended if: + +* You want to deploy with a different Rack handler (Passenger, Unicorn, + Heroku, ...). +* You want to use more than one subclass of `Sinatra::Base`. +* You want to use Sinatra only for middleware, and not as an endpoint. + +**There is no need to switch to a `config.ru` simply because you +switched to the modular style, and you don't have to use the modular +style for running with a `config.ru`.** + +### Using Sinatra as Middleware + +Not only is Sinatra able to use other Rack middleware, any Sinatra +application can in turn be added in front of any Rack endpoint as +middleware itself. This endpoint could be another Sinatra application, +or any other Rack-based application (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # middleware will run before filters + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Dynamic Application Creation + +Sometimes you want to create new applications at runtime without having to +assign them to a constant. You can do this with `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +It takes the application to inherit from as an optional argument: + +```ruby +# config.ru (run with rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +This is especially useful for testing Sinatra extensions or using Sinatra in +your own library. + +This also makes using Sinatra as middleware extremely easy: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Scopes and Binding + +The scope you are currently in determines what methods and variables are +available. + +### Application/Class Scope + +Every Sinatra application corresponds to a subclass of `Sinatra::Base`. +If you are using the top-level DSL (`require 'sinatra'`), then this +class is `Sinatra::Application`, otherwise it is the subclass you +created explicitly. At class level you have methods like `get` or +`before`, but you cannot access the `request` or `session` objects, as +there is only a single application class for all requests. + +Options created via `set` are methods at class level: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, I'm no longer in the application scope! + end +end +``` + +You have the application scope binding inside: + +* Your application class body +* Methods defined by extensions +* The block passed to `helpers` +* Procs/blocks used as value for `set` +* The block passed to `Sinatra.new` + +You can reach the scope object (the class) like this: + +* Via the object passed to configure blocks (`configure { |c| ... }`) +* `settings` from within the request scope + +### Request/Instance Scope + +For every incoming request, a new instance of your application class is +created, and all handler blocks run in that scope. From within this scope you +can access the `request` and `session` objects or call rendering methods like +`erb` or `haml`. You can access the application scope from within the request +scope via the `settings` helper: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + get '/define_route/:name' do + # Request scope for '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Request scope for "/#{params['name']}" + @value # => nil (not the same request) + end + + "Route defined!" + end +end +``` + +You have the request scope binding inside: + +* get, head, post, put, delete, options, patch, link and unlink blocks +* before and after filters +* helper methods +* templates/views + +### Delegation Scope + +The delegation scope just forwards methods to the class scope. However, it +does not behave exactly like the class scope, as you do not have the class +binding. Only methods explicitly marked for delegation are available, and you +do not share variables/state with the class scope (read: you have a different +`self`). You can explicitly add method delegations by calling +`Sinatra::Delegator.delegate :method_name`. + +You have the delegate scope binding inside: + +* The top level binding, if you did `require "sinatra"` +* An object extended with the `Sinatra::Delegator` mixin + +Have a look at the code for yourself: here's the +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Command Line + +Sinatra applications can be run directly: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Options are: + +``` +-h # help +-p # set the port (default is 4567) +-o # set the host (default is 0.0.0.0) +-e # set the environment (default is development) +-s # specify rack server/handler (default is thin) +-q # turn on quiet mode for server (default is off) +-x # turn on the mutex lock (default is off) +``` + +### Multi-threading + +_Paraphrasing from +[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129) +by Konstantin_ + +Sinatra doesn't impose any concurrency model, but leaves that to the +underlying Rack handler (server) like Thin, Puma or WEBrick. Sinatra +itself is thread-safe, so there won't be any problem if the Rack handler +uses a threaded model of concurrency. This would mean that when starting +the server, you'd have to specify the correct invocation method for the +specific Rack handler. The following example is a demonstration of how +to start a multi-threaded Thin server: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +To start the server, the command would be: + +```shell +thin --threaded start +``` + +## Requirement + +The following Ruby versions are officially supported: +
    +
    Ruby 2.2
    +
    + 2.2 is fully supported and recommended. There are currently no plans to + drop official support for it. +
    + +
    Rubinius
    +
    + Rubinius is officially supported (Rubinius >= 2.x). It is recommended to + gem install puma. +
    + +
    JRuby
    +
    + The latest stable release of JRuby is officially supported. It is not + recommended to use C extensions with JRuby. It is recommended to + gem install trinidad. +
    +
    + +Versions of Ruby prior to 2.2.2 are no longer supported as of Sinatra 2.0. + +We also keep an eye on upcoming Ruby versions. + +The following Ruby implementations are not officially supported but still are +known to run Sinatra: + +* Older versions of JRuby and Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those) + +Not being officially supported means if things only break there and not on a +supported platform, we assume it's not our issue but theirs. + +We also run our CI against ruby-head (future releases of MRI), but we +can't guarantee anything, since it is constantly moving. Expect upcoming +2.x releases to be fully supported. + +Sinatra should work on any operating system supported by the chosen Ruby +implementation. + +If you run MacRuby, you should `gem install control_tower`. + +Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +Ruby version prior to 2.2. + +## The Bleeding Edge + +If you would like to use Sinatra's latest bleeding-edge code, feel free +to run your application against the master branch, it should be rather +stable. + +We also push out prerelease gems from time to time, so you can do a + +```shell +gem install sinatra --pre +``` + +to get some of the latest features. + +### With Bundler + +If you want to run your application with the latest Sinatra, using +[Bundler](https://bundler.io) is the recommended way. + +First, install bundler, if you haven't: + +```shell +gem install bundler +``` + +Then, in your project directory, create a `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# other dependencies +gem 'haml' # for instance, if you use haml +``` + +Note that you will have to list all your application's dependencies in +the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, +however, be automatically fetched and added by Bundler. + +Now you can run your app like this: + +```shell +bundle exec ruby myapp.rb +``` + +## Versioning + +Sinatra follows [Semantic Versioning](https://semver.org/), both SemVer and +SemVerTag. + +## Further Reading + +* [Project Website](http://www.sinatrarb.com/) - Additional documentation, + news, and links to other resources. +* [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need + help? Have a patch? +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net) +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack + ([get an invite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed + recipes +* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra) + or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on + [RubyDoc](http://www.rubydoc.info/) +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.md new file mode 100644 index 0000000000..07afea3632 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.md @@ -0,0 +1,3203 @@ +# Sinatra + +[![Gem Version](https://badge.fury.io/rb/sinatra.svg)](http://badge.fury.io/rb/sinatra) +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) +[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver) + +Sinatra is a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for +quickly creating web applications in Ruby with minimal effort: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Install the gem: + +```shell +gem install sinatra +``` + +And run with: + +```shell +ruby myapp.rb +``` + +View at: [http://localhost:4567](http://localhost:4567) + +The code you changed will not take effect until you restart the server. +Please restart the server every time you change or use +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +It is recommended to also run `gem install puma`, which Sinatra will +pick up if available. + +## Table of Contents + +* [Sinatra](#sinatra) + * [Table of Contents](#table-of-contents) + * [Routes](#routes) + * [Conditions](#conditions) + * [Return Values](#return-values) + * [Custom Route Matchers](#custom-route-matchers) + * [Static Files](#static-files) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Available Template Languages](#available-template-languages) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Accessing Variables in Templates](#accessing-variables-in-templates) + * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) + * [Inline Templates](#inline-templates) + * [Named Templates](#named-templates) + * [Associating File Extensions](#associating-file-extensions) + * [Adding Your Own Template Engine](#adding-your-own-template-engine) + * [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup) + * [Filters](#filters) + * [Helpers](#helpers) + * [Using Sessions](#using-sessions) + * [Session Secret Security](#session-secret-security) + * [Session Config](#session-config) + * [Choosing Your Own Session Middleware](#choosing-your-own-session-middleware) + * [Halting](#halting) + * [Passing](#passing) + * [Triggering Another Route](#triggering-another-route) + * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) + * [Streaming Responses](#streaming-responses) + * [Logging](#logging) + * [Mime Types](#mime-types) + * [Generating URLs](#generating-urls) + * [Browser Redirect](#browser-redirect) + * [Cache Control](#cache-control) + * [Sending Files](#sending-files) + * [Accessing the Request Object](#accessing-the-request-object) + * [Attachments](#attachments) + * [Dealing with Date and Time](#dealing-with-date-and-time) + * [Looking Up Template Files](#looking-up-template-files) + * [Configuration](#configuration) + * [Configuring attack protection](#configuring-attack-protection) + * [Available Settings](#available-settings) + * [Environments](#environments) + * [Error Handling](#error-handling) + * [Not Found](#not-found) + * [Error](#error) + * [Rack Middleware](#rack-middleware) + * [Testing](#testing) + * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) + * [Modular vs. Classic Style](#modular-vs-classic-style) + * [Serving a Modular Application](#serving-a-modular-application) + * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) + * [When to use a config.ru?](#when-to-use-a-configru) + * [Using Sinatra as Middleware](#using-sinatra-as-middleware) + * [Dynamic Application Creation](#dynamic-application-creation) + * [Scopes and Binding](#scopes-and-binding) + * [Application/Class Scope](#applicationclass-scope) + * [Request/Instance Scope](#requestinstance-scope) + * [Delegation Scope](#delegation-scope) + * [Command Line](#command-line) + * [Multi-threading](#multi-threading) + * [Requirement](#requirement) + * [The Bleeding Edge](#the-bleeding-edge) + * [With Bundler](#with-bundler) + * [Versioning](#versioning) + * [Further Reading](#further-reading) + +## Routes + +In Sinatra, a route is an HTTP method paired with a URL-matching pattern. +Each route is associated with a block: + +```ruby +get '/' do + .. show something .. +end + +post '/' do + .. create something .. +end + +put '/' do + .. replace something .. +end + +patch '/' do + .. modify something .. +end + +delete '/' do + .. annihilate something .. +end + +options '/' do + .. appease something .. +end + +link '/' do + .. affiliate something .. +end + +unlink '/' do + .. separate something .. +end +``` + +Routes are matched in the order they are defined. The first route that +matches the request is invoked. + +Routes with trailing slashes are different from the ones without: + +```ruby +get '/foo' do + # Does not match "GET /foo/" +end +``` + +Route patterns may include named parameters, accessible via the +`params` hash: + +```ruby +get '/hello/:name' do + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + "Hello #{params['name']}!" +end +``` + +You can also access named parameters via block parameters: + +```ruby +get '/hello/:name' do |n| + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + # n stores params['name'] + "Hello #{n}!" +end +``` + +Route patterns may also include splat (or wildcard) parameters, accessible +via the `params['splat']` array: + +```ruby +get '/say/*/to/*' do + # matches /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # matches /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Or with block parameters: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Route matching with Regular Expressions: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +Or with a block parameter: + +```ruby +get %r{/hello/([\w]+)} do |c| + # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc. + "Hello, #{c}!" +end +``` + +Route patterns may have optional parameters: + +```ruby +get '/posts/:format?' do + # matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc +end +``` + +Routes may also utilize query parameters: + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +By the way, unless you disable the path traversal attack protection (see +[below](#configuring-attack-protection)), the request path might be modified before +matching against your routes. + +You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme) +options used for a given route by passing in a `:mustermann_opts` hash: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` + +It looks like a [condition](#conditions), but it isn't one! These options will +be merged into the global `:mustermann_opts` hash described +[below](#available-settings). + +## Conditions + +Routes may include a variety of matching conditions, such as the user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Matches non-songbird browsers +end +``` + +Other available conditions are `host_name` and `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` searches the request's Accept header. + +You can easily define your own conditions: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +For a condition that takes multiple values use a splat: + +```ruby +set(:auth) do |*roles| # <- notice the splat here + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +## Return Values + +The return value of a route block determines at least the response body +passed on to the HTTP client, or at least the next middleware in the +Rack stack. Most commonly, this is a string, as in the above examples. +But other values are also accepted. + +You can return any object that would either be a valid Rack response, Rack +body object or HTTP status code: + +* An Array with three elements: `[status (Integer), headers (Hash), response + body (responds to #each)]` +* An Array with two elements: `[status (Integer), response body (responds to + #each)]` +* An object that responds to `#each` and passes nothing but strings to + the given block +* A Integer representing the status code + +That way we can, for instance, easily implement a streaming example: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +You can also use the `stream` helper method ([described below](#streaming-responses)) to reduce +boiler plate and embed the streaming logic in the route. + +## Custom Route Matchers + +As shown above, Sinatra ships with built-in support for using String +patterns and regular expressions as route matches. However, it does not +stop there. You can easily define your own matchers: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note that the above example might be over-engineered, as it can also be +expressed as: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Or, using negative look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Static Files + +Static files are served from the `./public` directory. You can specify +a different location by setting the `:public_folder` option: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Note that the public directory name is not included in the URL. A file +`./public/css/style.css` is made available as +`http://example.com/css/style.css`. + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info. + +## Views / Templates + +Each template language is exposed via its own rendering method. These +methods simply return a string: + +```ruby +get '/' do + erb :index +end +``` + +This renders `views/index.erb`. + +Instead of a template name, you can also just pass in the template content +directly: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates take a second argument, the options hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +This will render `views/index.erb` embedded in the +`views/post.erb` (default is `views/layout.erb`, if it exists). + +Any options not understood by Sinatra will be passed on to the template +engine: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +You can also set options per template language in general: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Options passed to the render method override options set via `set`. + +Available Options: + +
    +
    locals
    +
    + List of locals passed to the document. Handy with partials. + Example: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding to use if uncertain. Defaults to + settings.default_encoding. +
    + +
    views
    +
    + Views folder to load templates from. Defaults to settings.views. +
    + +
    layout
    +
    + Whether to use a layout (true or false). If it's a + Symbol, specifies what template to use. Example: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type the template produces. Default depends on template language. +
    + +
    scope
    +
    + Scope to render template under. Defaults to the application + instance. If you change this, instance variables and helper methods + will not be available. +
    + +
    layout_engine
    +
    + Template engine to use for rendering the layout. Useful for + languages that do not support layouts otherwise. Defaults to the + engine used for the template. Example: set :rdoc, :layout_engine + => :erb +
    + +
    layout_options
    +
    + Special options only used for rendering the layout. Example: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Templates are assumed to be located directly under the `./views` +directory. To use a different views directory: + +```ruby +set :views, settings.root + '/templates' +``` + + +One important thing to remember is that you always have to reference +templates with symbols, even if they're in a subdirectory (in this case, +use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a +symbol because otherwise rendering methods will render any strings +passed to them directly. + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Renders the template string. You can optionally specify `:path` and +`:line` for a clearer backtrace if there is a filesystem path or line +associated with that string: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Available Template Languages + +Some languages have multiple implementations. To specify what implementation +to use (and to be thread-safe), you should simply require it first: + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependencyhaml
    File Extension.haml
    Examplehaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependency + erubi + or erubis + or erb (included in Ruby) +
    File Extensions.erb, .rhtml or .erubi (Erubi only) + or .erubis (Erubis only)
    Exampleerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependency + builder +
    File Extension.builder
    Examplebuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependencynokogiri
    File Extension.nokogiri
    Examplenokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.sass
    Examplesass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.scss
    Examplescss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependencyless
    File Extension.less
    Exampleless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependencyliquid
    File Extension.liquid
    Exampleliquid :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods (except for `yield`) from a Liquid +template, you almost always want to pass locals to it. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependency + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku + commonmarker + pandoc +
    File Extensions.markdown, .mkd and .md
    Examplemarkdown :index, :layout_engine => :erb
    + +It is not possible to call methods from Markdown, nor to pass locals to it. +You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Note that you may also call the `markdown` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Since you cannot call Ruby from Markdown, you cannot use layouts written in +Markdown. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### Textile Templates + + + + + + + + + + + + + + +
    DependencyRedCloth
    File Extension.textile
    Exampletextile :index, :layout_engine => :erb
    + +It is not possible to call methods from Textile, nor to pass locals to +it. You therefore will usually use it in combination with another +rendering engine: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Note that you may also call the `textile` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Since you cannot call Ruby from Textile, you cannot use layouts written in +Textile. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependencyRDoc
    File Extension.rdoc
    Examplerdoc :README, :layout_engine => :erb
    + +It is not possible to call methods from RDoc, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Note that you may also call the `rdoc` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Since you cannot call Ruby from RDoc, you cannot use layouts written in +RDoc. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    DependencyAsciidoctor
    File Extension.asciidoc, .adoc and .ad
    Exampleasciidoc :README, :layout_engine => :erb
    + +Since you cannot call Ruby methods directly from an AsciiDoc template, you +almost always want to pass locals to it. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependencyRadius
    File Extension.radius
    Exampleradius :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods directly from a Radius template, you +almost always want to pass locals to it. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependencyMarkaby
    File Extension.mab
    Examplemarkaby { h1 "Welcome!" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependencyRabl
    File Extension.rabl
    Examplerabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependencySlim Lang
    File Extension.slim
    Exampleslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependencyCreole
    File Extension.creole
    Examplecreole :wiki, :layout_engine => :erb
    + +It is not possible to call methods from Creole, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note that you may also call the `creole` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Since you cannot call Ruby from Creole, you cannot use layouts written in +Creole. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependencyWikiCloth
    File Extension.mediawiki and .mw
    Examplemediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass +locals to it. You therefore will usually use it in combination with +another rendering engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Since you cannot call Ruby from MediaWiki, you cannot use layouts written in +MediaWiki. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependency + + CoffeeScript + and a + + way to execute javascript + +
    File Extension.coffee
    Examplecoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependency + + Stylus + and a + + way to execute javascript + +
    File Extension.styl
    Examplestylus :index
    + +Before being able to use Stylus templates, you need to load `stylus` and +`stylus/tilt` first: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependencyyajl-ruby
    File Extension.yajl
    Example + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + + +The template source is evaluated as a Ruby string, and the +resulting json variable is converted using `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +The `:callback` and `:variable` options can be used to decorate the rendered +object: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    DependencyWLang
    File Extension.wlang
    Examplewlang :index, :locals => { :key => 'value' }
    + +Since calling ruby methods is not idiomatic in WLang, you almost always +want to pass locals to it. Layouts written in WLang and `yield` are +supported, though. + +### Accessing Variables in Templates + +Templates are evaluated within the same context as route handlers. Instance +variables set in route handlers are directly accessible by templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Or, specify an explicit Hash of local variables: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +This is typically used when rendering templates as partials from within +other templates. + +### Templates with `yield` and nested layouts + +A layout is usually just a template that calls `yield`. +Such a template can be used either through the `:template` option as +described above, or it can be rendered with a block as follows: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +This code is mostly equivalent to `erb :index, :layout => :post`. + +Passing blocks to rendering methods is most useful for creating nested +layouts: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +This can also be done in fewer lines of code with: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Currently, the following rendering methods accept a block: `erb`, `haml`, +`liquid`, `slim `, `wlang`. Also the general `render` method accepts a block. + +### Inline Templates + +Templates may be defined at the end of the source file: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +NOTE: Inline templates defined in the source file that requires sinatra are +automatically loaded. Call `enable :inline_templates` explicitly if you +have inline templates in other source files. + +### Named Templates + +Templates may also be defined using the top-level `template` method: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +If a template named "layout" exists, it will be used each time a template +is rendered. You can individually disable layouts by passing +`:layout => false` or disable them by default via +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associating File Extensions + +To associate a file extension with a template engine, use +`Tilt.register`. For instance, if you like to use the file extension +`tt` for Textile templates, you can do the following: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adding Your Own Template Engine + +First, register your engine with Tilt, then create a rendering method: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renders `./views/index.myat`. Learn more about +[Tilt](https://github.com/rtomayko/tilt#readme). + +### Using Custom Logic for Template Lookup + +To implement your own template lookup mechanism you can write your +own `#find_template` method: + +```ruby +configure do + set :views, [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filters + +Before filters are evaluated before each request within the same context +as the routes will be and can modify the request and response. Instance +variables set in filters are accessible by routes and templates: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After filters are evaluated after each request within the same context +as the routes will be and can also modify the request and response. +Instance variables set in before filters and routes are accessible by +after filters: + +```ruby +after do + puts response.status +end +``` + +Note: Unless you use the `body` method rather than just returning a +String from the routes, the body will not yet be available in the after +filter, since it is generated later on. + +Filters optionally take a pattern, causing them to be evaluated only if the +request path matches that pattern: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Like routes, filters also take conditions: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Use the top-level `helpers` method to define helper methods for use in +route handlers and templates: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Alternatively, helper methods can be separately defined in a module: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +The effect is the same as including the modules in the application class. + +### Using Sessions + +A session is used to keep state during requests. If activated, you have one +session hash per user session: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### Session Secret Security + +To improve security, the session data in the cookie is signed with a session +secret using `HMAC-SHA1`. This session secret should optimally be a +cryptographically secure random value of an appropriate length which for +`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex +characters). You would be advised not to use a secret that is less than 32 +bytes of randomness (256 bits, 64 hex characters). It is therefore **very +important** that you don't just make the secret up, but instead use a secure +random number generator to create it. Humans are extremely bad at generating +random values. + +By default, a 32 byte secure random session secret is generated for you by +Sinatra, but it will change with every restart of your application. If you +have multiple instances of your application, and you let Sinatra generate the +key, each instance would then have a different session key which is probably +not what you want. + +For better security and usability it's +[recommended](https://12factor.net/config) that you generate a secure random +secret and store it in an environment variable on each host running your +application so that all of your application instances will share the same +secret. You should periodically rotate this session secret to a new value. +Here are some examples of how you might create a 64 byte secret and set it: + +**Session Secret Generation** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Generation (Bonus Points)** + +Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to +prefer use of system RNG facilities to generate random values instead of +userspace `OpenSSL` which MRI Ruby currently defaults to: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Environment Variable** + +Set a `SESSION_SECRET` environment variable for Sinatra to the value you +generated. Make this value persistent across reboots of your host. Since the +method for doing this will vary across systems this is for illustrative +purposes only: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Session Secret App Config** + +Setup your app config to fail-safe to a secure random secret +if the `SESSION_SECRET` environment variable is not available. + +For bonus points use the [sysrandom +gem](https://github.com/cryptosphere/sysrandom#readme) here as well: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Session Config + +If you want to configure it further, you may also store a hash with options +in the `sessions` setting: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +To share your session across other apps on subdomains of foo.com, prefix the +domain with a *.* like this instead: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Choosing Your Own Session Middleware + +Note that `enable :sessions` actually stores all data in a cookie. This +might not always be what you want (storing lots of data will increase your +traffic, for instance). You can use any Rack session middleware in order to +do so, one of the following methods can be used: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Or to set up sessions with a hash of options: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Another option is to **not** call `enable :sessions`, but instead pull in +your middleware of choice as you would any other middleware. + +It is important to note that when using this method, session based +protection **will not be enabled by default**. + +The Rack middleware to do that will also need to be added: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +See '[Configuring attack protection](#configuring-attack-protection)' for more information. + +### Halting + +To immediately stop a request within a filter or route use: + +```ruby +halt +``` + +You can also specify the status when halting: + +```ruby +halt 410 +``` + +Or the body: + +```ruby +halt 'this will be the body' +``` + +Or both: + +```ruby +halt 401, 'go away!' +``` + +With headers: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +It is of course possible to combine a template with `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +A route can punt processing to the next matching route using `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +The route block is immediately exited and control continues with the next +matching route. If no matching route is found, a 404 is returned. + +### Triggering Another Route + +Sometimes `pass` is not what you want, instead you would like to get the +result of calling another route. Simply use `call` to achieve this: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note that in the example above, you would ease testing and increase +performance by simply moving `"bar"` into a helper used by both `/foo` and +`/bar`. + +If you want the request to be sent to the same application instance rather +than a duplicate, use `call!` instead of `call`. + +Check out the Rack specification if you want to learn more about `call`. + +### Setting Body, Status Code and Headers + +It is possible and recommended to set the status code and response body with +the return value of the route block. However, in some scenarios you might +want to set the body at an arbitrary point in the execution flow. You can do +so with the `body` helper method. If you do so, you can use that method from +there on to access the body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +It is also possible to pass a block to `body`, which will be executed by the +Rack handler (this can be used to implement streaming, [see "Return Values"](#return-values)). + +Similar to the body, you can also set the status code and headers: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Like `body`, `headers` and `status` with no arguments can be used to access +their current values. + +### Streaming Responses + +Sometimes you want to start sending out data while still generating parts of +the response body. In extreme examples, you want to keep sending data until +the client closes the connection. You can use the `stream` helper to avoid +creating your own wrapper: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +This allows you to implement streaming APIs, +[Server Sent Events](https://w3c.github.io/eventsource/), and can be used as +the basis for [WebSockets](https://en.wikipedia.org/wiki/WebSocket). It can +also be used to increase throughput if some but not all content depends on a +slow resource. + +Note that the streaming behavior, especially the number of concurrent +requests, highly depends on the web server used to serve the application. +Some servers might not even support streaming at all. If the server does not +support streaming, the body will be sent all at once after the block passed +to `stream` finishes executing. Streaming does not work at all with Shotgun. + +If the optional parameter is set to `keep_open`, it will not call `close` on +the stream object, allowing you to close it at any later point in the +execution flow. This only works on evented servers, like Rainbows. +Other servers will still close the stream: + +```ruby +# config.ru +require 'sinatra/base' + +class App < Sinatra::Base + connections = [] + + get '/subscribe', provides: 'text/event-stream' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end + end + + post '/' do + connections.each do |out| + # notify client that a new message has arrived + out << "data: #{params[:msg]}\n\n" + + # indicate client to connect again + out.close + end + + 204 # response without entity body + end +end + +run App +``` + +```ruby +# rainbows.conf +Rainbows! do + use :EventMachine +end +```` + +Run: + +```shell +rainbows -c rainbows.conf +``` + +It's also possible for the client to close the connection when trying to +write to the socket. Because of this, it's recommended to check +`out.closed?` before trying to write. + +### Logging + +In the request scope, the `logger` helper exposes a `Logger` instance: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +This logger will automatically take your Rack handler's logging settings into +account. If logging is disabled, this method will return a dummy object, so +you do not have to worry about it in your routes and filters. + +Note that logging is only enabled for `Sinatra::Application` by default, so +if you inherit from `Sinatra::Base`, you probably want to enable it yourself: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +To avoid any logging middleware to be set up, set the `logging` setting to +`nil`. However, keep in mind that `logger` will in that case return `nil`. A +common use case is when you want to set your own logger. Sinatra will use +whatever it will find in `env['rack.logger']`. + +### Mime Types + +When using `send_file` or static files you may have mime types Sinatra +doesn't understand. Use `mime_type` to register them by file extension: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +You can also use it with the `content_type` helper: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generating URLs + +For generating URLs you should use the `url` helper method, for instance, in +Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +It takes reverse proxies and Rack routers into account, if present. + +This method is also aliased to `to` (see [below](#browser-redirect) for an example). + +### Browser Redirect + +You can trigger a browser redirect with the `redirect` helper method: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Any additional parameters are handled like arguments passed to `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +You can also easily redirect back to the page the user came from with +`redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +To pass arguments with a redirect, either add them to the query: + +```ruby +redirect to('/bar?sum=42') +``` + +Or use a session: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache Control + +Setting your headers correctly is the foundation for proper HTTP caching. + +You can easily set the Cache-Control header like this: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Pro tip: Set up caching in a before filter: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +If you are using the `expires` helper to set the corresponding header, +`Cache-Control` will be set automatically for you: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +To properly use caches, you should consider using `etag` or `last_modified`. +It is recommended to call those helpers *before* doing any heavy lifting, as +they will immediately flush a response if the client already has the current +version in its cache: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +It is also possible to use a +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +These helpers will not do any caching for you, but rather feed the necessary +information to your cache. If you are looking for a quick +reverse-proxy caching solution, try +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info to static files. + +According to RFC 2616, your application should behave differently if the +If-Match or If-None-Match header is set to `*`, depending on whether the +resource requested is already in existence. Sinatra assumes resources for +safe (like get) and idempotent (like put) requests are already in existence, +whereas other resources (for instance post requests) are treated as new +resources. You can change this behavior by passing in a `:new_resource` +option: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +If you still want to use a weak ETag, pass in a `:kind` option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Sending Files + +To return the contents of a file as the response, you can use the `send_file` +helper method: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +It also takes options: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +The options are: + +
    +
    filename
    +
    File name to be used in the response, + defaults to the real file name.
    +
    last_modified
    +
    Value for Last-Modified header, defaults to the file's mtime.
    + +
    type
    +
    Value for Content-Type header, guessed from the file extension if + missing.
    + +
    disposition
    +
    + Value for Content-Disposition header, possible values: nil + (default), :attachment and :inline +
    + +
    length
    +
    Value for Content-Length header, defaults to file size.
    + +
    status
    +
    + Status code to be sent. Useful when sending a static file as an error + page. If supported by the Rack handler, other means than streaming + from the Ruby process will be used. If you use this helper method, + Sinatra will automatically handle range requests. +
    +
    + +### Accessing the Request Object + +The incoming request object can be accessed from request level (filter, +routes, error handlers) through the `request` method: + +```ruby +# app running on http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # request body sent by the client (see below) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # length of request.body + request.media_type # media type of request.body + request.host # "example.com" + request.get? # true (similar methods for other verbs) + request.form_data? # false + request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. + request.referrer # the referrer of the client or '/' + request.user_agent # user agent (used by :agent condition) + request.cookies # hash of browser cookies + request.xhr? # is this an ajax request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # client IP address + request.secure? # false (would be true over ssl) + request.forwarded? # true (if running behind a reverse proxy) + request.env # raw env hash handed in by Rack +end +``` + +Some options, like `script_name` or `path_info`, can also be written: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +The `request.body` is an IO or StringIO object: + +```ruby +post "/api" do + request.body.rewind # in case someone already read it + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Attachments + +You can use the `attachment` helper to tell the browser the response should +be stored on disk rather than displayed in the browser: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +You can also pass it a file name: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Dealing with Date and Time + +Sinatra offers a `time_for` helper method that generates a Time object from +the given value. It is also able to convert `DateTime`, `Date` and similar +classes: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +This method is used internally by `expires`, `last_modified` and akin. You +can therefore easily extend the behavior of those methods by overriding +`time_for` in your application: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Looking Up Template Files + +The `find_template` helper is used to find template files for rendering: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +This is not really useful. But it is useful that you can actually override +this method to hook in your own lookup mechanism. For instance, if you want +to be able to use more than one view directory: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Another example would be using different directories for different engines: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +You can also easily wrap this up in an extension and share with others! + +Note that `find_template` does not check if the file really exists but +rather calls the given block for all possible paths. This is not a +performance issue, since `render` will use `break` as soon as a file is +found. Also, template locations (and content) will be cached if you are not +running in development mode. You should keep that in mind if you write a +really crazy method. + +## Configuration + +Run once, at startup, in any environment: + +```ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +``` + +Run only when the environment (`APP_ENV` environment variable) is set to +`:production`: + +```ruby +configure :production do + ... +end +``` + +Run when the environment is set to either `:production` or `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +You can access those options via `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configuring attack protection + +Sinatra is using +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) to +defend your application against common, opportunistic attacks. You can +easily disable this behavior (which will open up your application to tons +of common vulnerabilities): + +```ruby +disable :protection +``` + +To skip a single defense layer, set `protection` to an options hash: + +```ruby +set :protection, :except => :path_traversal +``` +You can also hand in an array in order to disable a list of protections: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +By default, Sinatra will only set up session based protection if `:sessions` +have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up +sessions "outside" of the Sinatra app, such as in the config.ru or with a +separate `Rack::Builder` instance. In that case you can still set up session +based protection by passing the `:session` option: + +```ruby +set :protection, :session => true +``` + +### Available Settings + +
    +
    absolute_redirects
    +
    + If disabled, Sinatra will allow relative redirects, however, Sinatra + will no longer conform with RFC 2616 (HTTP 1.1), which only allows + absolute redirects. +
    +
    + Enable if your app is running behind a reverse proxy that has not been + set up properly. Note that the url helper will still produce + absolute URLs, unless you pass in false as the second + parameter. +
    +
    Disabled by default.
    + +
    add_charset
    +
    + Mime types the content_type helper will automatically add the + charset info to. You should add to it rather than overriding this + option: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path to the main application file, used to detect project root, views + and public folder and inline templates. +
    + +
    bind
    +
    + IP address to bind to (default: 0.0.0.0 or + localhost if your `environment` is set to development). Only + used for built-in server. +
    + +
    default_content_type
    +
    + Content-Type to assume if unknown (defaults to "text/html"). Set + to nil to not set a default Content-Type on every response; when + configured so, you must set the Content-Type manually when emitting content + or the user-agent will have to sniff it (or, if nosniff is enabled + in Rack::Protection::XSSHeader, assume application/octet-stream). +
    + +
    default_encoding
    +
    Encoding to assume if unknown (defaults to "utf-8").
    + +
    dump_errors
    +
    Display errors in the log.
    + +
    environment
    +
    + Current environment. Defaults to ENV['APP_ENV'], or + "development" if not available. +
    + +
    logging
    +
    Use the logger.
    + +
    lock
    +
    + Places a lock around every request, only running processing on request + per Ruby process concurrently. +
    +
    Enabled if your app is not thread-safe. Disabled by default.
    + +
    method_override
    +
    + Use _method magic to allow put/delete forms in browsers that + don't support it. +
    + +
    mustermann_opts
    +
    + A default hash of options to pass to Mustermann.new when compiling routing + paths. +
    + +
    port
    +
    Port to listen on. Only used for built-in server.
    + +
    prefixed_redirects
    +
    + Whether or not to insert request.script_name into redirects + if no absolute path is given. That way redirect '/foo' would + behave like redirect to('/foo'). Disabled by default. +
    + +
    protection
    +
    + Whether or not to enable web attack protections. See protection section + above. +
    + +
    public_dir
    +
    Alias for public_folder. See below.
    + +
    public_folder
    +
    + Path to the folder public files are served from. Only used if static + file serving is enabled (see static setting below). Inferred + from app_file setting if not set. +
    + +
    quiet
    +
    + Disables logs generated by Sinatra's start and stop commands. + false by default. +
    + +
    reload_templates
    +
    + Whether or not to reload templates between requests. Enabled in + development mode. +
    + +
    root
    +
    + Path to project root folder. Inferred from app_file setting + if not set. +
    + +
    raise_errors
    +
    + Raise exceptions (will stop application). Enabled by default when + environment is set to "test", disabled otherwise. +
    + +
    run
    +
    + If enabled, Sinatra will handle starting the web server. Do not + enable if using rackup or other means. +
    + +
    running
    +
    Is the built-in server running now? Do not change this setting!
    + +
    server
    +
    + Server or list of servers to use for built-in server. Order indicates + priority, default depends on Ruby implementation. +
    + +
    server_settings
    +
    + If you are using a WEBrick web server, presumably for your development + environment, you can pass a hash of options to server_settings, + such as SSLEnable or SSLVerifyClient. However, web + servers such as Puma do not support this, so you can set + server_settings by defining it as a method when you call + configure. +
    + +
    sessions
    +
    + Enable cookie-based sessions support using + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    session_store
    +
    + The Rack session middleware used. Defaults to + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    show_exceptions
    +
    + Show a stack trace in the browser when an exception happens. Enabled by + default when environment is set to "development", + disabled otherwise. +
    +
    + Can also be set to :after_handler to trigger app-specified + error handling before showing a stack trace in the browser. +
    + +
    static
    +
    Whether Sinatra should handle serving static files.
    +
    Disable when using a server able to do this on its own.
    +
    Disabling will boost performance.
    +
    + Enabled by default in classic style, disabled for modular apps. +
    + +
    static_cache_control
    +
    + When Sinatra is serving static files, set this to add + Cache-Control headers to the responses. Uses the + cache_control helper. Disabled by default. +
    +
    + Use an explicit array when setting multiple values: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + If set to true, will tell server to use + EventMachine.defer for processing the request. +
    + +
    traps
    +
    Whether Sinatra should handle system signals.
    + +
    views
    +
    + Path to the views folder. Inferred from app_file setting if + not set. +
    + +
    x_cascade
    +
    + Whether or not to set the X-Cascade header if no route matches. + Defaults to true. +
    +
    + +## Environments + +There are three predefined `environments`: `"development"`, +`"production"` and `"test"`. Environments can be set through the +`APP_ENV` environment variable. The default value is `"development"`. +In the `"development"` environment all templates are reloaded between +requests, and special `not_found` and `error` handlers display stack +traces in your browser. In the `"production"` and `"test"` environments, +templates are cached by default. + +To run different environments, set the `APP_ENV` environment variable: + +```shell +APP_ENV=production ruby my_app.rb +``` + +You can use predefined methods: `development?`, `test?` and `production?` to +check the current environment setting: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Error Handling + +Error handlers run within the same context as routes and before filters, +which means you get all the goodies it has to offer, like `haml`, `erb`, +`halt`, etc. + +### Not Found + +When a `Sinatra::NotFound` exception is raised, or the response's status +code is 404, the `not_found` handler is invoked: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +The `error` handler is invoked any time an exception is raised from a route +block or a filter. But note in development it will only run if you set the +show exceptions option to `:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +The exception object can be obtained from the `sinatra.error` Rack variable: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Custom errors: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Then, if this happens: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +You get this: + +``` +So what happened was... something bad +``` + +Alternatively, you can install an error handler for a status code: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Or a range: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installs special `not_found` and `error` handlers when +running under the development environment to display nice stack traces +and additional debugging information in your browser. + +## Rack Middleware + +Sinatra rides on [Rack](https://rack.github.io/), a minimal standard +interface for Ruby web frameworks. One of Rack's most interesting +capabilities for application developers is support for "middleware" -- +components that sit between the server and your application monitoring +and/or manipulating the HTTP request/response to provide various types +of common functionality. + +Sinatra makes building Rack middleware pipelines a cinch via a top-level +`use` method: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +The semantics of `use` are identical to those defined for the +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(most frequently used from rackup files). For example, the `use` method +accepts multiple/variable args as well as blocks: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack is distributed with a variety of standard middleware for logging, +debugging, URL routing, authentication, and session handling. Sinatra uses +many of these components automatically based on configuration so you +typically don't have to `use` them explicitly. + +You can find useful middleware in +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testing + +Sinatra tests can be written using any Rack-based testing library or +framework. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +is recommended: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Note: If you are using Sinatra in the modular style, replace +`Sinatra::Application` above with the class name of your app. + +## Sinatra::Base - Middleware, Libraries, and Modular Apps + +Defining your app at the top-level works well for micro-apps but has +considerable drawbacks when building reusable components such as Rack +middleware, Rails metal, simple libraries with a server component, or even +Sinatra extensions. The top-level assumes a micro-app style configuration +(e.g., a single application file, `./public` and `./views` +directories, logging, exception detail page, etc.). That's where +`Sinatra::Base` comes into play: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +The methods available to `Sinatra::Base` subclasses are exactly the same +as those available via the top-level DSL. Most top-level apps can be +converted to `Sinatra::Base` components with two modifications: + +* Your file should require `sinatra/base` instead of `sinatra`; + otherwise, all of Sinatra's DSL methods are imported into the main + namespace. +* Put your app's routes, error handlers, filters, and options in a subclass + of `Sinatra::Base`. + +`Sinatra::Base` is a blank slate. Most options are disabled by default, +including the built-in server. See [Configuring +Settings](http://www.sinatrarb.com/configuration.html) for details on +available options and their behavior. If you want behavior more similar +to when you define your app at the top level (also known as Classic +style), you can subclass `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modular vs. Classic Style + +Contrary to common belief, there is nothing wrong with the classic +style. If it suits your application, you do not have to switch to a +modular application. + +The main disadvantage of using the classic style rather than the modular +style is that you will only have one Sinatra application per Ruby +process. If you plan to use more than one, switch to the modular style. +There is no reason you cannot mix the modular and the classic styles. + +If switching from one style to the other, you should be aware of +slightly different default settings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SettingClassicModularModular
    app_filefile loading sinatrafile subclassing Sinatra::Basefile subclassing Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Serving a Modular Application + +There are two common options for starting a modular app, actively +starting with `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +``` + +Start with: + +```shell +ruby my_app.rb +``` + +Or with a `config.ru` file, which allows using any Rack handler: + +```ruby +# config.ru (run with rackup) +require './my_app' +run MyApp +``` + +Run: + +```shell +rackup -p 4567 +``` + +### Using a Classic Style Application with a config.ru + +Write your app file: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +And a corresponding `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### When to use a config.ru? + +A `config.ru` file is recommended if: + +* You want to deploy with a different Rack handler (Passenger, Unicorn, + Heroku, ...). +* You want to use more than one subclass of `Sinatra::Base`. +* You want to use Sinatra only for middleware, and not as an endpoint. + +**There is no need to switch to a `config.ru` simply because you +switched to the modular style, and you don't have to use the modular +style for running with a `config.ru`.** + +### Using Sinatra as Middleware + +Not only is Sinatra able to use other Rack middleware, any Sinatra +application can in turn be added in front of any Rack endpoint as +middleware itself. This endpoint could be another Sinatra application, +or any other Rack-based application (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # middleware will run before filters + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Dynamic Application Creation + +Sometimes you want to create new applications at runtime without having to +assign them to a constant. You can do this with `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +It takes the application to inherit from as an optional argument: + +```ruby +# config.ru (run with rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +This is especially useful for testing Sinatra extensions or using Sinatra in +your own library. + +This also makes using Sinatra as middleware extremely easy: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Scopes and Binding + +The scope you are currently in determines what methods and variables are +available. + +### Application/Class Scope + +Every Sinatra application corresponds to a subclass of `Sinatra::Base`. +If you are using the top-level DSL (`require 'sinatra'`), then this +class is `Sinatra::Application`, otherwise it is the subclass you +created explicitly. At class level you have methods like `get` or +`before`, but you cannot access the `request` or `session` objects, as +there is only a single application class for all requests. + +Options created via `set` are methods at class level: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, I'm no longer in the application scope! + end +end +``` + +You have the application scope binding inside: + +* Your application class body +* Methods defined by extensions +* The block passed to `helpers` +* Procs/blocks used as value for `set` +* The block passed to `Sinatra.new` + +You can reach the scope object (the class) like this: + +* Via the object passed to configure blocks (`configure { |c| ... }`) +* `settings` from within the request scope + +### Request/Instance Scope + +For every incoming request, a new instance of your application class is +created, and all handler blocks run in that scope. From within this scope you +can access the `request` and `session` objects or call rendering methods like +`erb` or `haml`. You can access the application scope from within the request +scope via the `settings` helper: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + get '/define_route/:name' do + # Request scope for '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Request scope for "/#{params['name']}" + @value # => nil (not the same request) + end + + "Route defined!" + end +end +``` + +You have the request scope binding inside: + +* get, head, post, put, delete, options, patch, link and unlink blocks +* before and after filters +* helper methods +* templates/views + +### Delegation Scope + +The delegation scope just forwards methods to the class scope. However, it +does not behave exactly like the class scope, as you do not have the class +binding. Only methods explicitly marked for delegation are available, and you +do not share variables/state with the class scope (read: you have a different +`self`). You can explicitly add method delegations by calling +`Sinatra::Delegator.delegate :method_name`. + +You have the delegate scope binding inside: + +* The top level binding, if you did `require "sinatra"` +* An object extended with the `Sinatra::Delegator` mixin + +Have a look at the code for yourself: here's the +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Command Line + +Sinatra applications can be run directly: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Options are: + +``` +-h # help +-p # set the port (default is 4567) +-o # set the host (default is 0.0.0.0) +-e # set the environment (default is development) +-s # specify rack server/handler (default is puma) +-q # turn on quiet mode for server (default is off) +-x # turn on the mutex lock (default is off) +``` + +### Multi-threading + +_Paraphrasing from +[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129) +by Konstantin_ + +Sinatra doesn't impose any concurrency model, but leaves that to the +underlying Rack handler (server) like Puma or WEBrick. Sinatra +itself is thread-safe, so there won't be any problem if the Rack handler +uses a threaded model of concurrency. This would mean that when starting +the server, you'd have to specify the correct invocation method for the +specific Rack handler. The following example is a demonstration of how +to start a multi-threaded Rainbows server: + +```ruby +# config.ru + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +run App +``` + +```ruby +# rainbows.conf + +# Rainbows configurator is based on Unicorn. +Rainbows! do + use :ThreadSpawn +end +``` + +To start the server, the command would be: + +```shell +rainbows -c rainbows.conf +``` + +## Requirement + +The following Ruby versions are officially supported: +
    +
    Ruby 2.3
    +
    + 2.3 is fully supported and recommended. There are currently no plans to + drop official support for it. +
    + +
    Rubinius
    +
    + Rubinius is officially supported (Rubinius >= 2.x). It is recommended to + gem install puma. +
    + +
    JRuby
    +
    + The latest stable release of JRuby is officially supported. It is not + recommended to use C extensions with JRuby. It is recommended to + gem install trinidad. +
    +
    + +Versions of Ruby prior to 2.3 are no longer supported as of Sinatra 2.1.0. + +We also keep an eye on upcoming Ruby versions. + +The following Ruby implementations are not officially supported but still are +known to run Sinatra: + +* Older versions of JRuby and Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those) + +Not being officially supported means if things only break there and not on a +supported platform, we assume it's not our issue but theirs. + +We also run our CI against ruby-head (future releases of MRI), but we +can't guarantee anything, since it is constantly moving. Expect upcoming +2.x releases to be fully supported. + +Sinatra should work on any operating system supported by the chosen Ruby +implementation. + +If you run MacRuby, you should `gem install control_tower`. + +Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +Ruby version prior to 2.2. + +## The Bleeding Edge + +If you would like to use Sinatra's latest bleeding-edge code, feel free +to run your application against the master branch, it should be rather +stable. + +We also push out prerelease gems from time to time, so you can do a + +```shell +gem install sinatra --pre +``` + +to get some of the latest features. + +### With Bundler + +If you want to run your application with the latest Sinatra, using +[Bundler](https://bundler.io) is the recommended way. + +First, install bundler, if you haven't: + +```shell +gem install bundler +``` + +Then, in your project directory, create a `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# other dependencies +gem 'haml' # for instance, if you use haml +``` + +Note that you will have to list all your application's dependencies in +the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, +however, be automatically fetched and added by Bundler. + +Now you can run your app like this: + +```shell +bundle exec ruby myapp.rb +``` + +## Versioning + +Sinatra follows [Semantic Versioning](https://semver.org/), both SemVer and +SemVerTag. + +## Further Reading + +* [Project Website](http://www.sinatrarb.com/) - Additional documentation, + news, and links to other resources. +* [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need + help? Have a patch? +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net) +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack + ([get an invite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed + recipes +* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra) + or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on + [RubyDoc](http://www.rubydoc.info/) +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-br.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-br.md new file mode 100644 index 0000000000..52928b9d5c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-br.md @@ -0,0 +1,3787 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Alguns dos trechos de código a seguir utilizam caracteres UTF-8. Então, caso +esteja utilizando uma versão de ruby inferior à `2.0.0`, adicione o encoding +no início de seus arquivos: + +```ruby +# encoding: utf-8 +``` + +Sinatra é uma +[DSL](https://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar aplicações web em Ruby com o mínimo de esforço e rapidez: + +```ruby +# minha_app.rb +require 'sinatra' + +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem: + +```shell +gem install sinatra +``` + +Em seguida execute: + +```shell +ruby minha_app.rb +``` + +Acesse em: [http://localhost:4567](http://localhost:4567) + +Códigos alterados só terão efeito após você reiniciar o servidor. +Por favor, reinicie o servidor após qualquer mudança ou use +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +É recomendado também executar `gem install thin`. Caso esta gem esteja +disponível, o Sinatra irá utilizá-la. + +## Conteúdo + +* [Sinatra](#sinatra) + * [Conteúdo](#conteúdo) + * [Rotas](#rotas) + * [Condições](#condições) + * [Valores Retornados](#valores-retornados) + * [Validadores de rota personalizados](#validadores-de-rota-personalizados) + * [Arquivos estáticos](#arquivos-estáticos) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Linguagens de template disponíveis](#linguagens-de-template-disponíveis) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Acessando Variáveis nos Templates](#acessando-variáveis-nos-templates) + * [Templates com `yield` e layouts aninhados](#templates-com-yield-e-layouts-aninhados) + * [Templates Inline](#templates-inline) + * [Templates Nomeados](#templates-nomeados) + * [Associando extensões de arquivos](#associando-extensões-de-arquivos) + * [Adicionando seu Próprio Engine de Template](#adicionando-seu-próprio-engine-de-template) + * [Customizando lógica para encontrar templates](#customizando-lógica-para-encontrar-templates) + * [Filtros](#filtros) + * [Helpers](#helpers) + * [Utilizando Sessões](#utilizando-sessões) + * [Segurança Secreta da Sessão](#segurança-secreta-da-sessão) + * [Configuração da Sessão](#configuração-da-sessão) + * [Escolhande Seu Próprio Middleware de Sessão](#escolhendo-middleware-de-sessão) + * [Halting](#halting) + * [Passing](#passing) + * [Desencadeando Outra Rota](#desencadeando-outra-rota) + * [Definindo Corpo, Código de Status e Cabeçalhos](#definindo-corpo-codigo-de-status-cabeçalhos) + * [Transmitindo Respostas](#transmitindo-respostas) + * [Usando Logs](#usando-logs) + * [Tipos Mime](#tipos-mime) + * [Gerando URLS](#gerando-urls) + * [Redirecionamento do Navegador](#redirecionamento-do-navagador) + * [Controle de Cache](#controle-de-cache) + * [Enviando Arquivos](#enviando-arquivos) + * [Acessando o Objeto de Requisição](#acessando-o-objeto-de-requisição) + * [Anexos](#anexos) + * [Trabalhando com Data e Hora](#trabalhando-com-data-e-hora) + * [Procurando Arquivos de Modelo](#procurando-arquivos-de-modelo) + * [Configuração](#configuração) + * [Configurando proteção a ataques](#configurando-proteção-a-ataques) + * [Configurações Disponíveis](#configurações-disponíveis) + * [Ambientes](#ambientes) + * [Tratamento de Erros](#tratamento-de-erros) + * [Não Encontrado](#não-encontrado) + * [Erro](#erro) + * [Rack Middleware](#rack-middleware) + * [Testando](#testando) + * [Sinatra::Base - Middleware, Bibliotecas e Aplicações Modulares](#sinatrabase-middleware-bibliotecas-e-aplicações-modulares) + * [Modular vs. Estilo Clássico](#modular-vs-estilo-clássico) + * [Servindo uma Aplicação Modular](#servindo-uma-aplicação-modular) + * [Usando uma Aplicação de Estilo Clássico com um config.ru](#usando-uma-aplicação-de-estilo-clássico-com-um-configru) + * [Quando usar um config.ru?](#quando-usar-um-configru) + * [Usando Sinatra como Middleware](#usando-sinatra-como-middleware) + * [Criação de Aplicações Dinâmicas](#criação-de-aplicações-dinamicas) + * [Escopos e Ligação](#escopos-e-ligação) + * [Escopo de Aplicação/Classe](#escopo-de-aplicação-classe) + * [Escopo de Instância/Requisição](#escopo-de-instância-requisição) + * [Escopo de Delegação](#escopo-de-delegação) + * [Linha de comando](#linha-de-comando) + * [Multi-threading](#multi-threading) + * [Requerimentos](#requerimentos) + * [A última versão](#a-última-versão) + * [Com Bundler](#com-bundler) + * [Versionando](#versionando) + * [Mais](#mais) + +## Rotas + +No Sinatra, uma rota é um método HTTP emparelhado com um padrão de URL. +Cada rota possui um bloco de execução: + +```ruby +get '/' do + .. mostrando alguma coisa .. +end + +post '/' do + .. criando alguma coisa .. +end + +put '/' do + .. atualizando alguma coisa .. +end + +patch '/' do + .. modificando alguma coisa .. +end + +delete '/' do + .. removendo alguma coisa .. +end + +options '/' do + .. estabelecendo alguma coisa .. +end + +link '/' do + .. associando alguma coisa .. +end + +unlink '/' do + .. separando alguma coisa .. +end +``` + +As rotas são interpretadas na ordem em que são definidas. A primeira +rota encontrada responde a requisição. + +Rotas com barras à direita são diferentes das que não contém as barras: + +```ruby +get '/foo' do + # Não é o mesmo que "GET /foo/" +end +``` + +Padrões de rota podem conter parâmetros nomeados, acessíveis por meio do +hash `params`: + +```ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Você também pode acessar parâmetros nomeados por meio dos parâmetros de +um bloco: + +```ruby +get '/ola/:nome' do |n| + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + # n guarda o valor de params['nome'] + "Olá #{n}!" +end +``` + +Padrões de rota também podem conter parâmetros splat (curinga), +acessível por meio do array `params['splat']`: + +```ruby +get '/diga/*/para/*' do + # corresponde a /diga/ola/para/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/caminho/do/arquivo.xml + params['splat'] # => ["caminho/do/arquivo", "xml"] +end +``` + +Ou com parâmetros de um bloco: + +```ruby +get '/download/*.*' do |caminho, ext| + [caminho, ext] # => ["caminho/do/arquivo", "xml"] +end +``` + +Rotas podem casar com expressões regulares: + +```ruby +get /\/ola\/([\w]+)/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com parâmetros de um bloco: + +```ruby +get %r{/ola/([\w]+)} do |c| + # corresponde a "GET /meta/ola/mundo", "GET /ola/mundo/1234" etc. + "Olá, #{c}!" +end +``` + +Padrões de rota podem contar com parâmetros opcionais: + +```ruby +get '/posts/:formato?' do + # corresponde a "GET /posts/" e qualquer extensão "GET /posts/json", "GET /posts/xml", etc. +end +``` + +Rotas também podem utilizar query strings: + +```ruby +get '/posts' do + # corresponde a "GET /posts?titulo=foo&autor=bar" + titulo = params['titulo'] + autor = params['autor'] + # utiliza as variaveis titulo e autor; a query é opicional para a rota /posts +end +``` + +A propósito, a menos que você desative a proteção contra ataques (veja +[abaixo](#configurando-proteção-a-ataques)), o caminho solicitado pode ser +alterado antes de concluir a comparação com as suas rotas. + +Você pode customizar as opções usadas do +[Mustermann](https://github.com/sinatra/mustermann#readme) para uma +rota passando `:mustermann_opts` num hash: + +```ruby +get '\A/posts\z', :musterman_opts => { :type => regexp, :check_anchors => false } do + # corresponde a /posts exatamente, com ancoragem explícita + "Se você combinar um padrão ancorado bata palmas!" +end +``` + +Parece com uma [condição](#condições) mas não é! Essas opções serão +misturadas no hash global `:mustermann_opts` descrito +[abaixo](#configurações-disponíveis) + +## Condições + +Rotas podem incluir uma variedade de condições, tal como o `user agent`: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está usando o Songbird versão #{params['agent'][0]}" +end + +get '/foo' do + # Correspondente a navegadores que não sejam Songbird +end +``` + +Outras condições disponíveis são `host_name` e `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Área administrativa. Acesso negado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` procura o cabeçalho Accept das requisições + +Você pode facilmente definir suas próprias condições: + +```ruby +set(:probabilidade) { |valor| condition { rand <= valor } } + +get '/ganha_um_carro', :probabilidade => 0.1 do + "Você ganhou!" +end + +get '/ganha_um_carro' do + "Sinto muito, você perdeu." +end +``` + +Use splat, para uma condição que leva vários valores: + +```ruby +set(:auth) do |*roles| # <- observe o splat aqui + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/minha/conta/", :auth => [:usuario, :administrador] do + "Detalhes da sua conta" +end + +get "/apenas/administrador/", :auth => :administrador do + "Apenas administradores são permitidos aqui!" +end +``` + +## Retorno de valores + +O valor de retorno do bloco de uma rota determina pelo menos o corpo da +resposta passado para o cliente HTTP, ou pelo menos o próximo middleware +na pilha Rack. Frequentemente, isto é uma `string`, tal como nos +exemplos acima. Entretanto, outros valores também são aceitos. + +Você pode retornar uma resposta válida ou um objeto para o Rack, sendo +eles de qualquer tipo de objeto que queira. Além disso, é possível +retornar um código de status HTTP. + +* Um array com três elementros: `[status (Integer), cabecalho (Hash), + corpo da resposta (responde à #each)]` + +* Um array com dois elementros: `[status (Integer), corpo da resposta + (responde à #each)]` + +* Um objeto que responda à `#each` sem passar nada, mas, sim, `strings` + para um dado bloco + +* Um objeto `Integer` representando o código de status + +Dessa forma, podemos implementar facilmente um exemplo de streaming: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Você também pode usar o método auxiliar `stream` +([descrito abaixo](#respostas-de-streaming)) para reduzir códigos +boilerplate e para incorporar a lógica de streaming (transmissão) na rota. + +## Validadores de Rota Personalizados + +Como apresentado acima, a estrutura do Sinatra conta com suporte +embutido para uso de padrões de String e expressões regulares como +validadores de rota. No entanto, ele não pára por aí. Você pode +facilmente definir os seus próprios validadores: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note que o exemplo acima pode ser robusto e complicado em excesso. Pode +também ser implementado como: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou, usando algo mais denso à frente: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do diretório +`./public`. Você pode especificar um local diferente pela opção +`:public_folder` + +```ruby +set :public_folder, __dir__ + '/estatico' +``` + +Note que o nome do diretório público não é incluido na URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://exemplo.com/css/style.css`. + +Use a configuração `:static_cache_control` (veja [abaixo](#controle-de-cache)) +para adicionar a informação `Cache-Control` no cabeçalho. + +## Views / Templates + +Cada linguagem de template é exposta através de seu próprio método de +renderização. Estes métodos simplesmente retornam uma string: + +```ruby +get '/' do + erb :index +end +``` + +Isto renderiza `views/index.rb` + +Ao invés do nome do template, você também pode passar direto o conteúdo do +template: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates também aceitam como um segundo argumento, um hash de opções: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Isto irá renderizar a `views/index.erb` inclusa dentro da `views/post.erb` +(o padrão é a `views/layout.erb`, se existir). + +Qualquer opção não reconhecida pelo Sinatra será passada adiante para o engine +de template: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Você também pode definir opções padrões para um tipo de template: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Opções passadas para o método de renderização sobrescreve as opções definitas +através do método `set`. + +Opções disponíveis: + +
    +
    locals
    +
    + Lista de locais passado para o documento. Conveniente para *partials* + Exemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding para ser utilizada em caso de incerteza. o padrão é + settings.default_encoding. +
    + +
    views
    +
    + Diretório de onde os templates são carregados. O padrão é + settings.views. +
    + +
    layout
    +
    + Para definir quando utilizar ou não um + layout (true ou false). E se for um + Symbol, especifica qual template usar. Exemplo: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + O *Content-Type* que o template produz. O padrão depente + da linguagem de template utilizada. +
    + +
    scope
    +
    + Escopo em que o template será renderizado. Padrão é a + instância da aplicação. Se você mudar isto as variáveis + de instância métodos auxiliares não serão + disponibilizados. +
    + +
    layout_engine
    +
    + A engine de template utilizada para renderizar seu layout. + Útil para linguagens que não suportam templates de outra + forma. O padrão é a engine do template utilizado. Exemplo: + set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Opções especiais utilizadas apenas para renderizar o + layout. Exemplo: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +É pressuposto que os templates estarão localizados diretamente sob o +diretório `./views`. Para usar um diretório diferente: + +```ruby +set :views, settings.root + '/templates' +``` + +Uma coisa importante para se lembrar é que você sempre deve +referenciar os templates utilizando *symbols*, mesmo que +eles estejam em um subdiretório (neste caso use: +`:'subdir/template'` or `'subdir/template'.to_sym`). Você deve +utilizar um *symbol* porque senão o método de renderização irá +renderizar qualquer outra string que você passe diretamente +para ele + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza um template string. Você pode opcionalmente especificar `path` +e `:line` para um backtrace mais claro se existir um caminho do sistema de +arquivos ou linha associada com aquela string. + +```ruby +get '/' do + haml '%div.title Olá Mundo', :path => 'exemplos/arquivo.haml', :line => 3 +end +``` + +### Linguagens de template disponíveis + +Algumas linguagens possuem multiplas implementações. Para especificar qual +implementação deverá ser utilizada (e para ser *thread-safe*), você deve +requere-la primeiro: + +```ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependênciahaml
    Extensão do Arquivo.haml
    Exemplohaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependência + erubis + or erb (included in Ruby) +
    Extensão dos Arquivos.erb, .rhtml or .erubis (Erubis only)
    Exemploerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependêcia + + builder + +
    Extensão do Arquivo.builder
    Exemplobuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see exemplo). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependêncianokogiri
    Extensão do Arquivo.nokogiri
    Exemplonokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see exemplo). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependênciasass
    Extensão do Arquivo.sass
    Exemplosass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependênciasass
    Extensão do Arquivo.scss
    Exemploscss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependêncialess
    Extensão do Arquivo.less
    Exemploless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependência + liquid +
    Extensão do Arquivo.liquid
    Exemploliquid :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby (exceto pelo método `yield`) pelo template +Liquid, você quase sempre precisará passar o `locals` para ele. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependência + Anyone of: + + RDiscount + , + + RedCarpet + , + + BlueCloth + , + + kramdown + , + + maruku + +
    Extensão do Arquivos.markdown, .mkd and .md
    Exemplomarkdown :index, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => markdown(:introducao) } +``` + +Note que vcoê também pode chamar o método `markdown` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= markdown(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Markdown, você não +pode utilizar um layout escrito em Markdown. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + + + + + + + + + + + + + + +
    DependênciaRedCloth
    Extensão do Arquivo.textile
    Exemplotextile :index, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => textile(:introducao) } +``` + +Note que vcoê também pode chamar o método `textile` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= textile(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Textile, você não +pode utilizar um layout escrito em Textile. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependênciaRDoc
    Extensão do Arquivo.rdoc
    Exemplordoc :README, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => rdoc(:introducao) } +``` + +Note que vcoê também pode chamar o método `rdoc` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= rdoc(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo RDoc, você não +pode utilizar um layout escrito em RDoc. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    Dependência + Asciidoctor +
    Extensão do Arquivo.asciidoc, .adoc and .ad
    Exemploasciidoc :README, :layout_engine => :erb
    + +Já que você não pode chamar o Ruby pelo template AsciiDoc, +você quase sempre precisará passar o `locals` para ele. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependênciaRadius
    Extensão do Arquivo.radius
    Exemploradius :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby pelo template Radius, +você quase sempre precisará passar o `locals` para ele. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependênciaMarkaby
    Extensão do Arquivo.mab
    Exemplomarkaby { h1 "Welcome!" }
    + +Este também recebe um bloco para templates (veja o exemplo). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependênciaRabl
    Extensão do Arquivo.rabl
    Exemplorabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependênciaSlim Lang
    Extensão do Arquivo.slim
    Exemploslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependênciaCreole
    Extensão do Arquivo.creole
    Exemplocreole :wiki, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note que você também pode chamar o método `creole` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= creole(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Creole, você não +pode utilizar um layout escrito em Creole. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    Dependência + + WikiCloth + +
    Extensão do Arquivo.mediawiki and .mw
    Exemplomediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass locals to +it. You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Já que você não pode chamar o Ruby pelo MediaWiki, você não +pode utilizar um layout escrito em MediaWiki. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependência + + CoffeeScript + and a + + way to execute javascript + +
    Extensão do Arquivo.coffee
    Exemplocoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependência + + Stylus + and a + + way to execute javascript + +
    Extensão do Arquivo.styl
    Exemplostylus :index
    + +Antes que vcoê possa utilizar o template Stylus primeiro você deve carregar +`stylus` e `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemplo +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependência + + yajl-ruby + +
    Extensão do Arquivo.yajl
    Exemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +O código-fonte do template é executado como uma string Ruby e a variável +resultante em json é convertida utilizando `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +O `:callback` e `:variable` são opções que podem ser utilizadas para o objeto +de renderização: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    Dependência + WLang +
    Extensão do Arquivo.wlang
    Exemplowlang :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby (exceto pelo método +`yield`) pelo template WLang, +você quase sempre precisará passar o `locals` para ele. + +## Acessando Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto como manipuladores de +rota. Variáveis de instância definidas em manipuladores de rota são +diretamente acessadas por templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito de variáveis locais: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizando quando renderizamos templates como +partials dentro de outros templates. + +### Templates com `yield` e layouts aninhados + +Um layout geralmente é apenas um template que executa `yield`. +Tal template pode ser utilizado pela opção `:template` descrita acima ou pode +ser renderizado através de um bloco, como a seguir: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Este código é quase equivalente a `erb :index, :layout => :post` + +Passando blocos para os métodos de renderização é útil para criar layouts +aninhados: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Também pode ser feito com menos linhas de código: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Atualmente os métodos listados aceitam blocos: `erb`, `haml`, +`liquid`, `slim `, `wlang`. E o método geral `render` também aceita blocos. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo. +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo Sinatra. Digite `enable :inline_templates` explicitamente se +você tem templates inline em outros arquivos fonte. + +### Templates Nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado toda vez +que um template for renderizado. Você pode desabilitar layouts passando +`:layout => false` ou desabilita-los por padrão +via `set :haml, :layout => false` + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associando Extensões de Arquivos + +Para associar uma extensão de arquivo com um engine de template use o método +`Tilt.register`. Por exemplo, se você quiser usar a extensão `tt` para os +templates Textile você pode fazer o seguinte: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adicionando seu Próprio Engine de Template + +Primeiro registre seu engine utilizando o Tilt, e então crie um método de +renderização: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renderize `./views/index.myat`. Aprenda mais sobre o +Tilt [aqui](https://github.com/rtomayko/tilt#readme). + +### Customizando Lógica para Encontrar Templates + +Para implementar sua própria lógica para busca de templates você pode escrever +seu próprio método `#find_template` + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância definidas nos filtros são acessadas através de rotas e +templates: + +```ruby +before do + @nota = 'Oi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Oi!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do mesmo contexto da +requisição e também podem modificar a requisição e a resposta. Variáveis de +instância definidas nos filtros before e rotas são acessadas através dos +filtros after: + +```ruby +after do + puts response.status +end +``` + +Nota: A não ser que você use o metódo `body` ao invés de apenas retornar uma +String das rotas, o corpo ainda não estará disponível no filtro after, uma vez +que é gerado depois. + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Como rotas, filtros também aceitam condições: + +```ruby +before :agent => /Songbird/ do + #... +end + +after '/blog/*', :host_name => 'exemplo.com' do + #... +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +```ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +Alternativamente, métodos auxiliares podem ser definidos separadamente em +módulos: + +```ruby +module FooUtils + def foo(nome) "#{nome}foo" end +end + +module BarUtils + def bar(nome) "#{nome}bar" end +end + +helpers FooUtils, BarUtils +``` + +O efeito é o mesmo que incluir os módulos na classe da aplicação. + +### Utilizando Sessões + + Uma sessão é usada para manter o estado durante requisições. Se ativada, você + terá disponível um hash de sessão para cada sessão de usuário: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` +#### Segredo Seguro da Sessão + +Para melhorar a segurança, os dados da sessão no cookie são assinado com uma +segredo de sessão usando `HMAC-SHA1`. Esse segredo de sessão deve ser, de +preferência, um valor criptograficamente randômico, seguro, de um comprimento +apropriado no qual `HMAC-SHA1` é maior ou igual a 64 bytes (512 bits, 128 +carecteres hexadecimais). Você será avisado para não usar uma chave secreta +menor que 32 bytes de randomicidade (256 bits, 64 caracteres hexadecimais). +Portanto, é **muito importante** que você não invente o segredo, mas use um +gerador de números aleatórios seguro para cria-lo. Humanos são extremamente +ruins em gerar números aleatórios. + +Por padrão, um segredo de sessão aleatório seguro de 32 bytes é gerada para +você pelo Sinatra, mas ele mudará toda vez que você reiniciar sua aplicação. Se +você tiver múltiplas instâncias da sua aplicação e você deixar que o Sinatra +gere a chave, cada instância teria uma chave de sessão diferente, o que +certamente não é o que você quer. + +Para melhor segurança e usabilidade é +[recomendado](https://12factor.net/config) que você gere um segredo randômico +secreto e salve-o em uma variável de ambiente em cada host rodando sua +aplicação, assim todas as instâncias da sua aplicação irão compartilhar o mesmo +segredo. Você deve, periodicamente, mudar esse segredo de sessão para um novo +valor. Abaixo, são mostrados alguns exemplos de como você pode criar um segredo +de 64 bytes e usa-lo: + +**Gerando Segredo de Sessão** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Gerando Segredo de Sessão (Pontos adicionais)** + +Use preferencialmente a +[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) para utilizar +as facilidades do sistema RNG para gerar valores aleatórios ao invés +do `OpenSSL` no qual o MRI Ruby padroniza para: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Sucessfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Segredo de Sessão numa Variável de Ambiente** + +Defina uma variável de ambiente `SESSION_SECRET` para o Sinatra com o valor que +você gerou. Salve esse valor entre as reinicializações do seu host. Já que a +forma de fazer isso irá variar entre os sistemas operacionais, o exemplo abaixo +serve apenas para fins ilustrativos: + +```bash +# echo "export SESSION_SECRET = 99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Configurando o Segredo de Sessão na Aplicação** + +Configure sua aplicação para uma falha de segredo seguro aleatório se a +variável de ambiente `SESSION_SECRET` não estiver disponível. + +Como ponto adicional use a +[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) da seguinte +forma: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securearandom' +set :session_secret, ENV.fecth(`SESSION_SECRET`) { SecureRandom.hex(64) } +``` + +#### Configuração de Sessão + +Se você deseja configurar isso adicionalmente, você pode salvar um hash com +opções na definição de `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Para compartilhar sua sessão com outras aplicações no subdomínio de foo.com, +adicione um *.* antes do domínio como no exemplo abaixo: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Escolhendo Seu Próprio Middleware de Sessão + +Perceba que `enable :sessions` na verdade guarda todos seus dados num cookie. +Isto pode não ser o que você deseja sempre (armazenar muitos dados irá aumentar +seu tráfego, por exemplo). Você pode usar qualquer middleware de sessão Rack +para fazer isso, um dos seguintes métodos pode ser usado: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Ou definir as sessões com um hash de opções: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Outra opção é **não** usar `enable :sessions`, mas ao invés disso trazer seu +middleware escolhido como você faria com qualquer outro middleware. + +É importante lembrar que usando esse método, a proteção baseada na sessão +**não estará habilitada por padrão**. + +Para o middleware Rack fazer isso, será preciso que isso também seja adicionado: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` +Veja '[Configurando proteção a ataques](#configurando-proteção-a-ataques)' para +mais informações. + +### Parando + +Para parar imediatamente uma requisição com um filtro ou rota utilize: + +```ruby +halt +``` + +Você também pode especificar o status quando parar: + +```ruby +halt 410 +``` + +Ou o corpo: + +```ruby +halt 'isso será o corpo' +``` + +Ou ambos: + +```ruby +halt 401, 'vá embora!' +``` + +Com cabeçalhos: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +Também é obviamente possível combinar um template com o `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +Uma rota pode processar aposta para a próxima rota correspondente usando +`pass`: + +```ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Você me pegou!' +end + +get '/adivinhar/*' do + 'Você falhou!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota compatível. Se nenhuma rota compatível for encontrada, um +404 é retornado. + +### Desencadeando Outra Rota + +As vezes o `pass` não é o que você quer, ao invés dele talvez você queira obter +o resultado chamando outra rota. Simplesmente utilize o método `call` neste +caso: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note que no exemplo acima você ganharia performance e facilitaria os testes ao +simplesmente mover `"bar"` para um método auxiliar usado por ambos `/foo` +e `/bar`. + +Se você quer que a requisição seja enviada para a mesma instância da aplicação +no lugar de uma duplicada, use `call!` no lugar de `call`. + +Veja a especificação do Rack se você quer aprender mais sobre o `call`. + +### Definindo Corpo, Código de Status e Cabeçalhos + +É possível e recomendado definir o código de status e o corpo da resposta com o +valor retornado do bloco da rota. Entretanto, em alguns cenários você pode +querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode +fazer isso com o metódo auxiliar `body`. Se você fizer isso, poderá usar esse +metódo de agora em diante para acessar o body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Também é possivel passar um bloco para `body`, que será executado pelo +manipulador Rack (isso pode ser usado para implementar transmissão, +[veja "Retorno de Valores"](#retorno-de-valores)). + +Similar ao corpo, você pode também definir o código de status e cabeçalhos: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN" + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "Eu sou um bule de chá!" +end +``` + +Assim como `body`, `headers` e `status` sem argumentos podem ser usados para +acessar seus valores atuais. + +### Transmitindo Respostas + +As vezes você quer começar a mandar dados enquanto está gerando partes do corpo +da resposta. Em exemplos extremos, você quer continuar enviando dados até o +cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para +evitar criar seu próprio empacotador: + +```ruby +get '/' do + stream do |out| + out << "Isso será len -\n" + sleep 0.5 + out << " Aguarde \n" + sleep 1 + out << " dário!\n" + end +end +``` + +Isso permite você implementar APIs de Transmissão, +[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode +ser usado como a base para +[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também +para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender +de um recurso lento. + +Perceba que o comportamento da transmissão, especialmente o número de +requisições concorrentes, depende altamente do servidor web usado para servir a +aplicação. Alguns servidores podem até mesmo não suportar transmissão de +maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado +completamente após que o bloco passado para `stream` terminar de executar. +Transmissão não funciona de nenhuma maneira com Shotun. + +Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no +objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no +fluxo de execução. Isso funciona apenas em servidores orientados a eventos, +como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão: + +```ruby +# long polling + +set :server, :thin +conexoes = [] + +get '/assinar' do + # registra o interesse de um cliente em servidores de eventos + stream(:keep_open) do |saida| + conexoes << saida + # retire conexões mortas + conexoes.reject!(&:closed?) + end +end + +post '/:messagem' do + conexoes.each do |saida| + # notifica o cliente que uma nova mensagem chegou + saida << params['messagem'] << "\n" + + # indica ao cliente para se conectar novamente + saida.close + end + + # confirma + "messagem recebida" +end +``` + +Também é possivel para o cliente fechar a conexão quando está tentando escrever +para o socket. Devido a isso, é recomendado checar `out.closed?` antes de +tentar escrever. + +### Usando Logs + +No escopo da requisição, o método auxiliar `logger` expõe uma instância +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Esse logger irá automaticamente botar as configurações de log do manipulador +Rack na sua conta. Se a produção de logs estiver desabilitads, esse método +retornará um objeto dummy, então você não terá que se preocupar com suas rotas +e filtros. + +Perceba que a produção de logs está habilitada apenas para +`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`, +você provavelmente irá querer habilitar: + +```ruby + class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end + end +``` + +Para evitar que qualquer middleware de logs seja configurado, defina a +configuração `logging` como `nil`. Entretanto, tenha em mente que `logger` +retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir +seu próprio logger. Sinatra irá usar qualquer um que ele achar +em `env['rack.logger']` + +### Tipos Mime + +Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos +mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão +do arquivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Você pode utilizar também com o método auxiliar `content_type`: + +```ruby +get '/' do + content-type :foo + "foo foo foo" +end +``` + +### Gerando URLs + +Para gerar URLs você deve usar o metódo auxiliar `url` no Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Isso inclui proxies reversos e rotas Rack, se presentes. + +Esse método é também apelidado para `to` (veja +[abaixo](#redirecionamento-do-browser) para um exemplo). + +### Redirecionamento do Browser + +Você pode lançar um redirecionamento no browser com o metódo auxiliar +`redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Quaisquer paramêtros adicionais são interpretados como argumentos passados +ao `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'lugar errado, amigo' +``` + +Você pode também facilmente redirecionar para a página da qual o usuário veio +com `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Para passar argumentos com um redirecionamento, adicione-os a query: + +```ruby +redirect to('/bar?sum=42') +``` + +Ou use uma sessão: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Controle de Cache + +Definir sues cabeçalhos corretamente é o principal passo para uma correta cache +HTTP. + +Você pode facilmente definir o cabeçalho de Cache-Control como: + +```ruby +get '/' do + cache_control :public + "guarde isso!" +end +``` + +Dica profissional: Configure a cache em um filtro anterior: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Se você está usando o método auxiliar `expires` para definir seu cabeçalho +correspondente, `Cache-Control` irá ser definida automaticamente para você: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar propriciamente caches, você deve considerar usar `etag` ou +`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer +qualquer tipo de processamento pesado, já que eles irão imediatamente retornar +uma resposta se o cliente já possui a versão atual na sua cache: + +```ruby +get "/artigo/:id" do + @artigo = Artigo.find params['id'] + last_modified @artigo.updated_at + etag @artigo.sha1 + erb :artigo +end +``` + +Também é possível usar uma +[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` +Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas +irão alimentar as informações necessárias para sua cache. Se você está +pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "olá" +end +``` + +Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache)) +para adicionar o cabeçalho de informação `Cache-Control` para arquivos +estáticos. + +De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o +cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o +recurso requisitado já existe. Sinatra assume que recursos para requisições +seguras (como get) e idempotentes (como put) já existem, enquanto que para +outros recursos (por exemplo requisições post) são tratados como novos +recursos. Você pode mudar esse comportamento passando em uma opção +`:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Artigo.create + erb :novo_artigo +end +``` + +Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Arquivos + +Para retornar os conteúdos de um arquivo como as resposta, você pode usar o +metódo auxiliar `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Também aceita opções: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +As opções são: + +
    +
    filename
    +
    Nome do arquivo a ser usado na respota, + o padrão é o nome do arquivo reak
    +
    last_modified
    +
    Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do + arquivo.
    + +
    type
    +
    Valor do cabeçalho Content-Type, extraído da extensão do arquivo se + inexistente.
    + +
    disposition
    +
    + Valor do cabeçalho Content-Disposition, valores possíveis: nil + (default), :attachment and :inline +
    + +
    length
    +
    Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do + arquivo.
    + +
    status
    +
    + Código de status a ser enviado. Útil quando está se enviando um arquivo + estático como uma página de erro. Se suportado pelo handler do Rack, + outros meios além de transmissão do processo do Ruby serão usados. + So você usar esse metódo auxiliar, o Sinatra irá automaticamente lidar com + requisições de alcance. +
    +
    + +### Acessando o Objeto da Requisção + +O objeto vindo da requisição pode ser acessado do nível de requsição (filtros, +rotas, manipuladores de erro) através do método `request`: + +```ruby +# app rodando em http://exemplo.com/exemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # corpo da requisição enviado pelo cliente (veja abaixo) + request.scheme # "http" + request.script_name # "/exemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # tamanho do request.body + request.media_type # tipo de mídia of request.body + request.host # "exemplo.com" + request.get? # true (metodo similar para outros tipos de requisição) + request.form_data? # false + request["algum_ param"] # valor do paramêtro 'algum_param'. [] é um atalho para o hash de parametros + request.referrer # a referência do cliente ou '/' + request.user_agent # agente de usuário (usado por :agent condition) + request.cookies # hash dos cookies do browser + request.xhr? # isto é uma requisição ajax? + request.url # "http://exemplo.com/exemplo/foo" + request.path # "/exemplo/foo" + request.ip # endereço de IP do cliente + request.secure? # false (seria true se a conexão fosse ssl) + request.forwarded? # true (se está rodando por um proxy reverso) + request.env # raw env hash handed in by Rack +end +``` + +Algumas opções, como `script_name` ou `path_info, podem ser escritas como: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas requisições acabam aqui" +end +``` +`request.body` é uma ES ou um objeo StringIO: + +```ruby +post "/api" do + request.body.rewind # em caso de algo já ter lido + data = JSON.parse request.body.read + "Oi #{data['nome']}!" +end +``` + +### Anexos + +Você pode usar o método auxiliar `attachment` para dizer ao navegador que a +reposta deve ser armazenada no disco no lugar de ser exibida no browser: + +```ruby +get '/' do + attachment "info.txt" + "salve isso!" +end +``` + +### Trabalhando com Data e Hora + +O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do +valor dado. É também possível converter `DateTime`, `Date` e classes similares: + + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "continua no tempo" +end +``` + +Esse método é usado internamente por `expires`, `last_modified` e akin. Você +pode portanto facilmente estender o comportamento desses métodos sobrescrevendo +`time_for` na sua aplicação: + +```ruby +helpers do + def time_for(valor) + case valor + when :ontem then Time.now - 24*60*60 + when :amanha then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ontem + expires :amanha + "oi" +end +``` + +### Pesquisando por Arquivos de Template + +O método auxiliar `find_template` é usado para encontrar arquivos de template +para renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |arquivo| + puts "pode ser #{arquivo}" +end +``` + +Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever +esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se +você quer ser capaz de usar mais de um diretório de view: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Outro exemplo seria utilizando diretórios diferentes para motores (engines) +diferentes: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras +pessoas! + +Perceba que `find_template` não verifica se o arquivo realmente existe. Ao +invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não +significa um problema de perfomance, já que `render` irá usar `break` assim que +o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates +serão guardados na cache se você não estiver rodando no modo de +desenvolvimento. Você deve se lembrar disso se você escrever um método +realmente maluco. + +## Configuração + +É possível e recomendado definir o código de status e o corpo da resposta com o +valor retornado do bloco da rota. Entretanto, em alguns cenários você pode +querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode +fazer isso com o metódo auxiliar `body`. Se você fizer isso, poderá usar esse +metódo de agora em diante para acessar o body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Também é possivel passar um bloco para `body`, que será executado pelo +manipulador Rack (isso pode ser usado para implementar transmissão, +[veja "Retorno de Valores"](#retorno-de-valores)). + +Similar ao corpo, você pode também definir o código de status e cabeçalhos: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN" + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "Eu sou um bule de chá!" +end +``` + +Assim como `body`, `headers` e `status` sem argumentos podem ser usados para +acessar seus valores atuais. + +### Transmitindo Respostas + +As vezes você quer começar a mandar dados enquanto está gerando partes do corpo +da resposta. Em exemplos extremos, você quer continuar enviando dados até o +cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para +evitar criar seu próprio empacotador: + +```ruby +get '/' do + stream do |out| + out << "Isso será len -\n" + sleep 0.5 + out << " Aguarde \n" + sleep 1 + out << " dário!\n" + end +end +``` + +Isso permite você implementar APIs de Transmissão, +[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode +ser usado como a base para +[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também +para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender +de um recurso lento. + +Perceba que o comportamento da transmissão, especialmente o número de +requisições concorrentes, depende altamente do servidor web usado para servir a +aplicação. Alguns servidores podem até mesmo não suportar transmissão de +maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado +completamente após que o bloco passado para `stream` terminar de executar. +Transmissão não funciona de nenhuma maneira com Shotun. + +Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no +objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no +fluxo de execução. Isso funciona apenas em servidores orientados a eventos, +como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão: + +```ruby +# long polling + +set :server, :thin +conexoes = [] + +get '/assinar' do + # registra o interesse de um cliente em servidores de eventos + stream(:keep_open) do |saida| + conexoes << saida + # retire conexões mortas + conexoes.reject!(&:closed?) + end +end + +post '/:messagem' do + conexoes.each do |saida| + # notifica o cliente que uma nova mensagem chegou + saida << params['messagem'] << "\n" + + # indica ao cliente para se conectar novamente + saida.close + end + + # confirma + "messagem recebida" +end +``` + +Também é possivel para o cliente fechar a conexão quando está tentando escrever +para o socket. Devido a isso, é recomendado checar `out.closed?` antes de +tentar escrever. + +### Usando Logs + +No escopo da requisição, o método auxiliar `logger` expõe uma instância +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Esse logger irá automaticamente botar as configurações de log do manipulador +Rack na sua conta. Se a produção de logs estiver desabilitads, esse método +retornará um objeto dummy, então você não terá que se preocupar com suas rotas +e filtros. + +Perceba que a produção de logs está habilitada apenas para +`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`, +você provavelmente irá querer habilitar: + +```ruby + class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end + end +``` + +Para evitar que qualquer middleware de logs seja configurado, defina a +configuração `logging` como `nil`. Entretanto, tenha em mente que `logger` +retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir +seu próprio logger. Sinatra irá usar qualquer um que ele achar em +`env['rack.logger']` + +### Tipos Mime + +Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos +Mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão +do arquivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Você pode utilizar também com o método auxiliar `content_type`: + +```ruby +get '/' do + content-type :foo + "foo foo foo" +end +``` + +### Gerando URLs + +Para gerar URLs você deve usar o metódo auxiliar `url` no Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Isso inclui proxies reversos e rotas Rack, se presentes. + +Esse método é também apelidado para `to` (veja +[abaixo](#redirecionamento-do-browser) para um exemplo). + +### Redirecionamento do Browser + +Você pode lançar um redirecionamento no browser com o metódo auxiliar +`redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Quaisquer paramêtros adicionais são interpretados como argumentos passados ao +`halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'lugar errado, amigo' +``` + +Você pode também facilmente redirecionar para a página da qual o usuário veio +com `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Para passar argumentos com um redirecionamento, adicione-os a query: + +```ruby +redirect to('/bar?sum=42') +``` + +Ou use uma sessão: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Controle de Cache + +Definir sues cabeçalhos corretamente é o principal passo para uma correta cache +HTTP. + +Você pode facilmente definir o cabeçalho de Cache-Control como: + +```ruby +get '/' do + cache_control :public + "guarde isso!" +end +``` + +Dica profissional: Configure a cache em um filtro anterior: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Se você está usando o método auxiliar `expires` para definir seu cabeçalho +correspondente, `Cache-Control` irá ser definida automaticamente para você: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar propriciamente caches, você deve considerar usar `etag` ou +`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer +qualquer tipo de processamento pesado, já que eles irão imediatamente retornar +uma resposta se o cliente já possui a versão atual na sua cache: + +```ruby +get "/artigo/:id" do + @artigo = Artigo.find params['id'] + last_modified @artigo.updated_at + etag @artigo.sha1 + erb :artigo +end +``` + +Também é possível usar uma +[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` +Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas +irão alimentar as informações necessárias para sua cache. Se você está +pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "olá" +end +``` + +Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache)) +para adicionar o cabeçalho de informação `Cache-Control` para arquivos +estáticos. + +De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o +cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o +recurso requisitado já existe. Sinatra assume que recursos para requisições +seguras (como get) e idempotentes (como put) já existem, enquanto que para +outros recursos (por exemplo requisições post) são tratados como novos +recursos. Você pode mudar esse comportamento passando em uma opção +`:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Artigo.create + erb :novo_artigo +end +``` + +Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Arquivos + +Para retornar os conteúdos de um arquivo como as resposta, você pode usar o +metódo auxiliar `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Também aceita opções: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +As opções são: + +
    +
    filename
    +
    Nome do arquivo a ser usado na respota, + o padrão é o nome do arquivo reak
    +
    last_modified
    +
    Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do + arquivo.
    + +
    type
    +
    Valor do cabeçalho Content-Type, extraído da extensão do arquivo se + inexistente.
    + +
    disposition
    +
    + Valor do cabeçalho Content-Disposition, valores possíveis: nil + (default), :attachment and :inline +
    + +
    length
    +
    Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do + arquivo.
    + +
    status
    +
    + Código de status a ser enviado. Útil quando está se enviando um arquivo + estático como uma página de erro. Se suportado pelo handler do Rack, + outros meios além de transmissão do processo do Ruby serão usados. + So você usar esse metódo auxiliar, o Sinatra irá automaticamente lidar + com requisições de alcance. +
    +
    + +### Acessando o Objeto da Requisção + +O objeto vindo da requisição pode ser acessado do nível de requsição (filtros, +rotas, manipuladores de erro) através do método `request`: + +```ruby +# app rodando em http://exemplo.com/exemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # corpo da requisição enviado pelo cliente (veja abaixo) + request.scheme # "http" + request.script_name # "/exemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # tamanho do request.body + request.media_type # tipo de mídia of request.body + request.host # "exemplo.com" + request.get? # true (metodo similar para outros tipos de requisição) + request.form_data? # false + request["algum_ param"] # valor do paramêtro 'algum_param'. [] é um atalho para o hash de parametros + request.referrer # a referência do cliente ou '/' + request.user_agent # agente de usuário (usado por :agent condition) + request.cookies # hash dos cookies do browser + request.xhr? # isto é uma requisição ajax? + request.url # "http://exemplo.com/exemplo/foo" + request.path # "/exemplo/foo" + request.ip # endereço de IP do cliente + request.secure? # false (seria true se a conexão fosse ssl) + request.forwarded? # true (se está rodando por um proxy reverso) + request.env # raw env hash handed in by Rack +end +``` + +Algumas opções, como `script_name` ou `path_info, podem ser escritas como: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas requisições acabam aqui" +end +``` +`request.body` é uma ES ou um objeo StringIO: + +```ruby +post "/api" do + request.body.rewind # em caso de algo já ter lido + data = JSON.parse request.body.read + "Oi #{data['nome']}!" +end +``` + +### Anexos + +Você pode usar o método auxiliar `attachment` para dizer ao navegador que a +reposta deve ser armazenada no disco no lugar de ser exibida no browser: + +```ruby +get '/' do + attachment "info.txt" + "salve isso!" +end +``` + +### Trabalhando com Data e Hora + +O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do +valor dado. É também possível converter `DateTime`, `Date` e classes similares: + + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "continua no tempo" +end +``` + +Esse método é usado internamente por `expires`, `last_modified` e akin. Você +pode portanto facilmente estender o comportamento desses métodos sobrescrevendo +`time_for` na sua aplicação: + +```ruby +helpers do + def time_for(valor) + case valor + when :ontem then Time.now - 24*60*60 + when :amanha then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ontem + expires :amanha + "oi" +end +``` + +### Pesquisando por Arquivos de Template + +O método auxiliar `find_template` é usado para encontrar arquivos de template +para renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |arquivo| + puts "pode ser #{arquivo}" +end +``` + +Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever +esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se +você quer ser capaz de usar mais de um diretório de view: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Outro exemplo seria utilizando diretórios diferentes para motores (engines) +diferentes: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras +pessoas! + +Perceba que `find_template` não verifica se o arquivo realmente existe. Ao +invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não +significa um problema de perfomance, já que `render` irá usar `break` assim que +o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates +serão guardados na cache se você não estiver rodando no modo de +desenvolvimento. Você deve se lembrar disso se você escrever um método +realmente maluco. + +## Configuração + +Rode uma vez, na inicialização, em qualquer ambiente: + +```ruby +configure do + ... +end +``` + +```ruby +configure do + # configurando uma opção + set :option, 'value' + + # configurando múltiplas opções + set :a => 1, :b => 2 + + # o mesmo que `set :option, true` + enable :option + + # o mesmo que `set :option, false` + disable :option + + # você pode também ter configurações dinâmicas com blocos + set(:css_dir) { File.join(views, 'css') } +end +``` + +Rode somente quando o ambiente (`APP_ENV` variável de ambiente) é definida para +`:production`: + +```ruby +configure :production do + ... +end +``` + +Rode quando o ambiente é definido para `:production` ou `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Você pode acessar essas opções por meio de `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configurando proteção a ataques + +O Sinatra está usando +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +para defender sua aplicação contra ataques oportunistas comuns. Você pode +facilmente desabilitar esse comportamento (o que irá abrir sua aplicação à +toneladas de vulnerabilidades comuns): + +```ruby +disable :protection +``` + +Para pular uma única camada de defesa, defina `protection` como um hash de +opções: + +```ruby +set :protection, :except => :path_traversal +``` + +Você também pode definir em um array, visando desabilitar uma lista de +proteções: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Por padrão, o Sinatra irá configurar apenas sessões com proteção se `:sessions` +tiver sido habilitado. Veja '[Utilizando Sessões](#utilizando-sessões)'. As +vezes você pode querer configurar sessões "fora" da aplicação do Sinatra, como +em config.ru ou com uma instância de `Rack::Builder` separada. Nesse caso, você +pode continuar configurando uma sessão com proteção passando a opção `:session`: + +```ruby +set :protection, :session => true +``` + +### Configurações Disponíveis + +
    +
    absolute_redirects
    +
    + Se desabilitada, o Sinatra irá permitir redirecionamentos relativos, + entretanto, isso não estará conforme a RFC 2616 (HTTP 1.1), que permite + apenas redirecionamentos absolutos. +
    +
    + Habilite se sua aplicação estiver rodando antes de um proxy reverso que + não foi configurado corretamente. Note que o método auxiliar url + irá continuar produzindo URLs absolutas, a não ser que você passe + false como segundo parâmetro. +
    +
    Desabilitado por padrão.
    + +
    add_charset
    +
    + Para tipos Mime o método auxiliar content_type irá + automaticamente adicionar a informção de codificação. Você deve adcionar + isto no lugar de sobrescrever essa opção: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Caminho para o arquivo principal da aplicação, usado para detectar a raíz + do projeto, views e pastas públicas e templates inline. +
    + +
    bind
    +
    + Endereço IP a ser ligado (padrão: 0.0.0.0 ou localhost + se seu ambiente está definido como desenvolvimento). Usado apenas para + servidor embutido. +
    + +
    default_encoding
    +
    Codificação assumida caso a mesma seja desconhecida (padrão corresponde + a "utf-8").
    + +
    dump_errors
    +
    Exibe erros no log.
    + +
    environment
    +
    + Ambiente atual. O padrão é ENV['APP_ENV'], ou + "development" se o primeiro não estiver disponível. +
    + +
    logging
    +
    Usa o logger.
    + +
    lock
    +
    + Coloca um bloqueio em torno de cada requisição, executando apenas + processamento sob requisição por processo Ruby simultaneamente. +
    +
    Habilitado se sua aplicação não for 'thread-safe'. Desabilitado por + padrão.
    + +
    method_override
    +
    + Use a mágica _method para permitir formulários put/delete em + navegadores que não oferecem suporte à essas operações. +
    + +
    mustermann_opts
    +
    + Um hash de opções padrão para passar a Mustermann.new quando se está + compilado os caminho de roteamento. +
    + +
    port
    +
    Porta a ser escutada. Usado apenas para servidores embutidos.
    + +
    prefixed_redirects
    +
    + Inserir ou não inserir request.script_name nos redirecionamentos + se nenhum caminho absoluto for dado. Dessa forma redirect '/foo' + irá se comportar como redirect to('/foo'). +
    +
    Desabilitado por padrão.
    + +
    protection
    +
    + Habilitar ou não proteções a ataques web. Veja a sessão de proteção acima. +
    + +
    public_dir
    +
    Apelido para public_folder. Veja abaixo.
    + +
    public_folder
    +
    + Caminho para o diretório de arquivos públicos. Usado apenas se a exibição + de arquivos estáticos estiver habilitada (veja a configuração + static abaixo). Deduzido da configuração app_file se + não for definido. +
    + +
    quiet
    +
    + Desabilita logs gerados pelos comandos de inicio e parada do Sinatra. + false por padrão. +
    + +
    reload_templates
    +
    + Se deve ou não recarregar templates entre as requisições. Habilitado no + modo de desenvolvimento. +
    + +
    root
    +
    + Caminho para o diretório raíz do projeto. Deduzido da configuração + app_file se não for definido. +
    + +
    raise_errors
    +
    + Lança exceções (irá para a aplicação). Habilitado por padrão quando o + ambiente está definido para "test, desabilitado em caso + contrário. +
    + +
    run
    +
    + Se habilitado, o Sinatra irá lidar com o início do servidor web. Não + habilite se estiver usando rackup ou outros meios. +
    + +
    running
    +
    É o servidor embutido que está rodando agora? Não mude essa + configuração!
    + +
    server
    +
    + Servidor ou listas de servidores para usar o servidor embutido. A ordem + indica prioridade, por padrão depende da implementação do Ruby +
    + +
    server_settings
    +
    + Se você estiver usando um servidor web WEBrick, presumidamente para seu + ambiente de desenvolvimento, você pode passar um hash de opções para + server_settings, tais como SSLEnable ou + SSLVerifyClient. Entretanto, servidores web como Puma e Thin não + suportam isso, então você pode definir server_settings como um + metódo quando chamar configure. +
    + +
    sessions
    +
    + Habilita o suporte a sessões baseadas em cookie usando + Rack::Session::Cookie. Veja a seção 'Usando Sessões' para mais + informações. +
    + +
    session_store
    +
    + O middleware de sessão Rack usado. O padrão é + Rack::Session::Cookie. Veja a sessão 'Usando Sessões' para mais + informações. +
    + +
    show_exceptions
    +
    + Mostra um relatório de erros no navegador quando uma exceção ocorrer. + Habilitado por padrão quando o ambiente é definido como + "development", desabilitado caso contrário. +
    +
    + Pode também ser definido para :after_handler para disparar um + manipulador de erro específico da aplicação antes de mostrar um relatório + de erros no navagador. +
    + +
    static
    +
    + Define se o Sinatra deve lidar com o oferecimento de arquivos + estáticos. +
    +
    + Desabilitado quando está utilizando um servidor capaz de fazer isso + sozinho. +
    +
    Desabilitar irá aumentar a perfomance
    +
    + Habilitado por padrão no estilo clássico, desabilitado para aplicações + modulares. +
    + +
    static_cache_control
    +
    + Quando o Sinatra está oferecendo arquivos estáticos, definir isso irá + adicionar cabeçalhos Cache-Control nas respostas. Usa o método + auxiliar cache-control. Desabilitado por padrão. +
    +
    + Use um array explícito quando estiver definindo múltiplos valores: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + Se estiver definido como true, irá definir que o Thin use + EventMachine.defer para processar a requisição. +
    + +
    traps
    +
    Define se o Sinatra deve lidar com sinais do sistema.
    + +
    views
    +
    + Caminho para o diretório de views. Deduzido da configuração + app_file se não estiver definido. +
    + +
    x_cascade
    +
    + Se deve ou não definir o cabeçalho X-Cascade se nenhuma rota combinar. + Definido como padrão true +
    +
    + +## Ambientes + +Existem três `environments` (ambientes) pré-definidos: `"development"` +(desenvolvimento), `"production"` (produção) e `"test"` (teste). Ambientes +podem ser definidos através da variável de ambiente `APP_ENV`. O valor padrão é +`"development"`. No ambiente `"development"` todos os templates são +recarregados entre as requisições e manipuladores especiais como `not_found` e +`error` exibem relatórios de erros no seu navegador. Nos ambientes de +`"production"` e `"test"`, os templates são guardos em cache por padrão. + +Para rodar diferentes ambientes, defina a variável de ambiente `APP_ENV`: + +```shell +APP_ENV=production ruby minha_app.rb +``` + +Você pode usar métodos pré-definidos: `development?`, `test?` e `production?` +para checar a configuração atual de ambiente: + +```ruby +get '/' do + if settings.development? + "desenvolvimento!" + else + "não está em desenvolvimento!" + end +end +``` + +## Tratamento de Erros + +Manipuladores de erros rodam dentro do mesmo contexto como rotas e filtros +before, o que significa que você pega todos os "presentes" que eles têm para +oferecer, como `haml`, `erb`, `halt`, etc. + +### Não Encontrado + +Quando uma exceção `Sinatra::NotFound` é lançada, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +```ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado toda a vez que uma exceção é lançada a +partir de um bloco de rota ou um filtro. Note que em desenvolvimento, ele irá +rodar apenas se você tiver definido a opção para exibir exceções em +`:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +O objeto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +```ruby +error do + 'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros customizados: + +```ruby +error MeuErroCustomizado do + 'Então que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +```ruby +get '/' do + raise MeuErroCustomizado, 'alguma coisa ruim' +end +``` + +Você receberá isso: + +``` +Então que aconteceu foi... alguma coisa ruim +```` + +Alternativamente, você pode instalar um manipulador de erro para um código +de status: + +```ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um alcance: + +```ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra instala os manipuladores especiais `not_found` e `error` +quando roda sobre o ambiente de desenvolvimento para exibir relatórios de erros +bonitos e informações adicionais de "debug" no seu navegador. + +## Rack Middleware + +O Sinatra roda no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Um das capacidades mais +interessantes do Rack para desenvolver aplicativos é suporte a +“middleware” – componentes que ficam entre o servidor e sua aplicação +monitorando e/ou manipulando o request/response do HTTP para prover +vários tipos de funcionalidades comuns. + +O Sinatra faz construtores pipelines do middleware Rack facilmente em um +nível superior utilizando o método `use`: + +```ruby +require 'sinatra' +require 'meu_middleware_customizado' + +use Rack::Lint +use MeuMiddlewareCustomizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis bem como blocos: + +```ruby +use Rack::Auth::Basic do |usuario, senha| + usuario == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuido com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão. Sinatra +utilizada muitos desses componentes automaticamente baseando sobre +configuração, então, tipicamente você não tem `use` explicitamente. + +Você pode achar middlwares utéis em +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +ou em [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +```ruby +require 'minha_aplicacao_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parametros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Se você está usando o Sinatra no estilo modular, substitua +`Sinatra::Application' acima com o nome da classe da sua aplicação + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação em um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis incovenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: uma simples +arquivo de aplicação, diretórios `./public` e `./views`, logs, página de +detalhes de exceção, etc.). É onde o `Sinatra::Base` entra em jogo: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Ola mundo!' + end +end +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +* Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; caso +contrário, todos os métodos DSL do Sinatra são importados para o espaço de +nomes principal. + +* Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções +numa subclasse de `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desabilitadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. Se você quer +comportamento mais similiar à quando você definiu sua aplicação em nível mais +alto (também conhecido como estilo Clássico), você pode usar subclasses de +`Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Application + get '/' do + 'Olá mundo!' + end +end +``` + +### Estilo Clássico vs. Modular + +Ao contrário da crença comum, não há nada de errado com o estilo clássico. Se +encaixa com sua aplicação, você não tem que mudar para uma aplicação modular. + +As desvantagens principais de usar o estilo clássico no lugar do estilo modular +é que você ira ter apenas uma aplicação Sinatra por processo Ruby. Se seu plano +é usar mais de uma, mude para o estilo modular. Não há nenhum impedimento para +você misturar os estilos clássico e modular. + +Se vai mudar de um estilo para outro, você deve tomar cuidado com algumas +configurações diferentes: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConfiguraçãoClássicoModularModular
    app_filearquivo carregando sinatraarquivo usando subclasse Sinatra::Basearquivo usando subclasse Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Servindo uma Aplicação Modular: + +Existem duas opções comuns para começar uma aplicação modular, ativamente +começando com `run!`: + +```ruby +# minha_app.rb +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + # ... código da aplicação aqui ... + + # inicie o servidor se o arquivo ruby foi executado diretamente + run! if app_file == $0 +end +``` + +Inicie com with: + +```shell +ruby minha_app.rb +``` + +Ou com um arquivo `config.ru`, que permite você usar qualquer manipulador Rack: + +```ruby +# config.ru (roda com rackup) +require './minha_app' +run MinhaApp +``` + +Rode: + +```shell +rackup -p 4567 +``` + +### Usando uma Aplicação de Estilo Clássico com um config.ru: + +Escreva o arquivo da sua aplicação: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Olá mundo!' +end +``` + +E um `config.ru` correspondente: + +```ruby +require './app' +run Sinatra::Application +``` + +### Quando usar um config.ru? + +Um arquivo `config.ru` é recomendado se: + +* Você quer lançar com um manipulador Rack diferente (Passenger, Unicorn, +Heroku, ...). + +* Você quer usar mais de uma subclasse de `Sinatra::Base`. +* Você quer usar Sinatra apenas como middleware, mas não como um "endpoint". + +**Não há necessidade de mudar para um `config.ru` simplesmente porque você mudou para o estilo modular, e você não tem que usar o estilo modular para rodar com um `config.ru`.** + +### Usando Sinatra como Middleware + +O Sinatra não é capaz apenas de usar outro middleware Rack, qualquer aplicação +Sinatra pode ser adicionada na frente de qualquer "endpoint" Rack como +middleware. Esse endpoint pode ser outra aplicação Sinatra, ou qualquer outra +aplicação baseada em Rack (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class TelaLogin < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['nome'] == 'admin' && params['senha'] == 'admin' + session['nome_usuario'] = params['nome'] + else + redirect '/login' + end + end +end + +class MinhaApp < Sinatra::Base + # middleware irá rodar filtros before + use TelaLogin + + before do + unless session['nome_usuario'] + halt "Acesso negado, por favor login." + end + end + + get('/') { "Olá #{session['nome_usuario']}." } +end +``` + +### Criação de Aplicações Dinâmicas + +Às vezes, você quer criar novas aplicações em tempo de execução sem ter que +associa-las a uma constante. Você pode fazer isso com `Sinatra.new`: + +```ruby +require 'sinatra/base' +minha_app = Sinatra.new { get('/') { "oi" } } +minha_app.run! +``` + +Isso leva a aplicação à herdar como um argumento opcional: + +```ruby +# config.ru (roda com rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MeusHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Isso é especialmente útil para testar extensões do Sinatra ou usar Sinatra na +sua própria biblioteca. + +Isso também faz o uso do Sinatra como middleware extremamente fácil: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +# Escopos e Ligação + +O escopo que você está atualmente determina quais métodos e varáveis está +disponíveis. + +### Escopo de Aplicação/Classe + +Toda aplicação Sinatra corresponde à um subclasse de `Sinatra::Base`. Se você +está utilizando a DSL de nível mais alto (`require 'sinatra`), então esta +classe é `Sinatra::Application`, caso contrário é a subclasse que você criou +explicitamente. No nível de classe você tem métodos como `get` ou `before`, mas +você não pode acessar os objetos `request` ou `session`, como existe apenas uma +única classe de aplicativo para todas as solicitações. + +Opções criadas via `set` são métodos a nível de classe: + +```ruby +class MinhaApp < Sinatra::Base + # Hey, eu estou no escopo da aplicação! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, eu não estou mais no escopo da aplicação! + end +end +``` + +Você tem a ligação ao escopo da aplicação dentro: + +* Do corpo da classe da sua aplicação +* De métodos definidos por extensões +* Do bloco passado a `helpers` +* De Procs/Blocos usados como valor para `set`` +* Do bloco passado a `Sinatra.new`` + +Você pode atingir o escopo do objeto (a classe) de duas maneiras: + +* Por meio do objeto passado aos blocos "configure" (`configure { |c| ...}`) +* `settings` de dentro do escopo da requisição + +### Escopo de Instância/Requisição + +Para toda requsição que chega, uma nova instância da classe da sua aplicação é +criada e todos blocos de manipulação rodam nesse escopo. Dentro desse escopo +você pode acessar os objetos `request` e `session` ou chamar métodos de +renderização como `erb` ou `haml`. Você pode acessar o escopo da aplicação de +dentro do escopo da requisição através do método auxiliar `settings`: + +```ruby +class MinhaApp < Sinatra::Base + # Hey, eu estou no escopo da aplicação! + get '/define_rota/:nome' do + # Escopo da requisição para '/define_rota/:nome' + @valor = 42 + + settings.get("/#{params['nome']}") do + # Escopo da requisição para "/#{params['nome']}" + @valor # => nil (não é a mesma requisição) + end + + "Rota definida!" + end +end +``` + +Você tem a ligação ao escopo da requisição dentro dos: + +* blocos get, head, post, put, delete, options, patch, link e unlink +* filtros after e before +* métodos "helper" (auxiliares) +* templates/views + +### Escopo de Delegação + +O escopo de delegação apenas encaminha métodos ao escopo da classe. Entretando, +ele não se comporta exatamente como o escopo da classse já que você não tem a +ligação da classe. Apenas métodos marcados explicitamente para delegação +estarão disponíveis e você não compartilha variáveis/estado com o escopo da +classe (leia: você tem um `self` diferente). Você pode explicitamente adicionar +delegações de métodos chamando `Sinatra::Delegator.delegate :method_name`. + +Você tem a ligação com o escopo delegado dentro: + +* Da ligação de maior nível, se você digitou `require "sinatra"` +* De um objeto estendido com o mixin `Sinatra::Delegator` + +Dê uma olhada no código você mesmo: aqui está [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) em [estendendo o objeto principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Linha de Comando + +Aplicações Sinatra podem ser executadas diretamente: + +```shell +ruby minhaapp.rb [-h] [-x] [-q] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s MANIPULADOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # ativa o bloqueio mutex (padrão é desligado) +``` + +### Multi-threading + +_Parafraseando [esta resposta no StackOverflow](resposta-so) por Konstantin_ + +Sinatra não impõe nenhum modelo de concorrencia, mas deixa isso como +responsabilidade do Rack (servidor) subjacente como o Thin, Puma ou WEBrick. +Sinatra por si só é thread-safe, então não há nenhum problema se um Rack +handler usar um modelo de thread de concorrência. Isso significaria que ao +iniciar o servidor, você teria que espeficiar o método de invocação correto +para o Rack handler específico. Os seguintes exemplos é uma demonstração de +como iniciar um servidor Thin multi-thread: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + 'Olá mundo' + end +end + +App.run! +``` + +Para iniciar o servidor seria: + +```shell +thin --threaded start +``` + +## Requerimentos + +As seguintes versões do Ruby são oficialmente suportadas: +
    +
    Ruby 2.2
    +
    + 2.2 é totalmente suportada e recomendada. Atualmente não existem planos + para para o suporte oficial para ela. +
    + +
    Rubinius
    +
    + Rubinius é oficialmente suportado (Rubinius >= 2.x). É recomendado rodar + gem install puma. +
    + +
    JRuby
    +
    + A útlima versão estável lançada do JRuby é oficialmente suportada. Não é + recomendado usar extensões em C com o JRuby. É recomendado rodar + gem install trinidad. +
    +
    + +Versões do Ruby antes da 2.2.2 não são mais suportadas pelo Sinatra 2.0. + +Nós também estamos de olhos em versões futuras do Ruby. + +As seguintes implementações do Ruby não são oficialmente suportadas mas sabemos +que rodam o Sinatra: + +* Versões antigas do JRuby e Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 e 1.9.1 (mas nós não recomendamos o uso dessas) + +Não ser oficialmente suportada significa que se algo quebrar e não estiver nas +plataformas suporta, iremos assumir que não é um problema nosso e sim das +plataformas. + +Nós também rodas nossa IC sobre ruby-head (lançamentos futuros do MRI), mas nós +não podemos garantir nada, já que está em constante mudança. Espera-se que +lançamentos futuros da versão 2.x sejam totalmente suportadas. + +Sinatra deve funcionar em qualquer sistema operacional suportado pela +implementação Ruby escolhida. + +Se você rodar MacRuby, você deve rodar `gem install control_tower`. + +O Sinatra atualmente não roda em Cardinal, SmallRuby, BlueRuby ou qualquer +versão do Ruby anterior ao 2.2. + +## A última versão + +Se você gostaria de utilizar o código da última versão do Sinatra, sinta-se +livre para rodar a aplicação com o ramo master, ele deve ser estável. + +Nós também lançamos pré-lançamentos de gems de tempos em tempos, então você +pode fazer: + +```shell +gem install sinatra --pre +``` + +para obter alguma das últimas funcionalidades. + +### Com Bundler + +Se você quer rodar sua aplicação com a última versão do Sinatra usando +[Bundler](https://bundler.io) é recomendado fazer dessa forma. + +Primeiramente, instale o Bundler, se você ainda não tiver: + +```shell +gem install bundler +``` + +Então, no diretório do seu projeto, crie uma `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# outras dependências +gem 'haml' # por exemplo, se você usar haml +``` + +Perceba que você terá que listar todas suas dependências de aplicação no +`Gemfile`. As dependências diretas do Sinatra (Rack e Tilt) irão, entretanto, +ser automaticamente recuperadas e adicionadas pelo Bundler. + +Então você pode rodar sua aplicação assim: + +```shell +bundle exec ruby myapp.rb +``` + +## Versionando + +O Sinatras segue [Versionamento Semântico](https://semver.org/), tanto SemVer +como SemVerTag. + +## Mais + +* [Website do Projeto](http://www.sinatrarb.com/) - Documentação +adicional, novidades e links para outros recursos. +* [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um +bug? Precisa de ajuda? Tem um patch? +* [Acompanhar Problemas](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Lista de Email](http://groups.google.com/group/sinatrarb/topics) +* [Sinatra & Amigos](https://sinatrarb.slack.com) no Slack +([consiga um convite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) - Livro de "Receitas" +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - "Receitas" de +contribuições da comunidade +* Documentação da API para a +[última release](http://www.rubydoc.info/gems/sinatra) +ou para o [HEAD atual](http://www.rubydoc.info/github/sinatra/sinatra) +no [Ruby Doc](http://www.rubydoc.info/) +* [Servidor de CI](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-pt.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-pt.md new file mode 100644 index 0000000000..27a0789adf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.pt-pt.md @@ -0,0 +1,791 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Sinatra é uma +[DSL](https://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar rapidamente aplicações web em Ruby com o mínimo de esforço: + +```ruby +# minhaapp.rb +require 'rubygems' +require 'sinatra' +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem e execute com: + +```shell +sudo gem install sinatra +ruby minhaapp.rb +``` + +Aceda em: [http://localhost:4567](http://localhost:4567) + +## Rotas + +No Sinatra, uma rota é um metodo HTTP associado a uma URL correspondente +padrão. Cada rota é associada a um bloco: + +```ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. criar algo .. +end + +put '/' do + .. atualizar algo .. +end + +delete '/' do + .. apagar algo .. +end +``` + +Rotas são encontradas na ordem em que são definidas. A primeira rota que +é encontrada invoca o pedido. + +Padrões de rota podem incluir parâmetros nomeados, acessíveis através da +hash `params`: + +```ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Pode também aceder a parâmetros nomeados através do bloco de parâmetros: + +```ruby +get '/ola/:nome' do |n| + "Olá #{n}!" +end +``` + +Padrões de rota podem também incluir parâmetros splat (asteriscos), +acessíveis através do array `params['splat']`. + +```ruby +get '/diga/*/ao/*' do + # corresponde a /diga/ola/ao/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/pasta/do/arquivo.xml + params['splat'] # => ["pasta/do/arquivo", "xml"] +end +``` + +Rotas correspondem-se com expressões regulares: + +```ruby +get /\/ola\/([\w]+)/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com um bloco de parâmetro: + +```ruby +get %r{/ola/([\w]+)} do |c| + "Olá, #{c}!" +end +``` + +Rotas podem incluir uma variedade de condições correspondentes, por +exemplo, o agente usuário: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está a utilizar a versão #{params['agent'][0]} do Songbird." +end + +get '/foo' do + # Corresponde a um navegador não Songbird +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do directório +`./public`. Você pode especificar um local diferente através da opção +`:public_folder` + +```ruby +set :public_folder, __dir__ + '/estatico' +``` + +Note que o nome do directório público não é incluido no URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://example.com/css/style.css`. + +## Views / Templates + +Templates presumem-se estar localizados sob o directório `./views`. Para +utilizar um directório de views diferente: + +```ruby +set :views, __dir__ + '/modelo' +``` + +Uma coisa importante a ser lembrada é que você sempre tem as referências +dos templates como símbolos, mesmo se eles estiverem num sub-directório +(nesse caso utilize `:'subdir/template'`). Métodos de renderização irão +processar qualquer string passada directamente para elas. + +### Haml Templates + +A gem/biblioteca haml é necessária para renderizar templates HAML: + +```ruby +# É necessário requerir 'haml' na aplicação. +require 'haml' + +get '/' do + haml :index +end +``` + +Renderiza `./views/index.haml`. + +[Opções +Haml](http://haml.info/docs/yardoc/file.REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +```ruby +set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml + +get '/' do + haml :index, :haml_options => {:format => :html4 } # substituido +end +``` + +### Erb Templates + +```ruby +# É necessário requerir 'erb' na aplicação. +require 'erb' + +get '/' do + erb :index +end +``` + +Renderiza `./views/index.erb` + +### Erubis + +A gem/biblioteca erubis é necessária para renderizar templates erubis: + +```ruby +# É necessário requerir 'erubis' na aplicação. +require 'erubis' + +get '/' do + erubis :index +end +``` + +Renderiza `./views/index.erubis` + +### Builder Templates + +A gem/biblioteca builder é necessária para renderizar templates builder: + +```ruby +# É necessário requerir 'builder' na aplicação. +require 'builder' + +get '/' do + content_type 'application/xml', :charset => 'utf-8' + builder :index +end +``` + +Renderiza `./views/index.builder`. + +### Sass Templates + +A gem/biblioteca sass é necessária para renderizar templates sass: + +```ruby +# É necessário requerir 'haml' ou 'sass' na aplicação. +require 'sass' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet +end +``` + +Renderiza `./views/stylesheet.sass`. + +[Opções +Sass](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +```ruby +set :sass, {:style => :compact } # o estilo padrão do Sass é :nested + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet, :style => :expanded # substituido +end +``` + +### Less Templates + +A gem/biblioteca less é necessária para renderizar templates Less: + +```ruby +# É necessário requerir 'less' na aplicação. +require 'less' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + less :stylesheet +end +``` + +Renderiza `./views/stylesheet.less`. + +### Templates Inline + +```ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza a string, em uma linha, no template. + +### Acedendo a Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto que os manipuladores de +rota. Variáveis de instância definidas em rotas manipuladas são +directamente acedidas por templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito para variáveis locais: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizado quando renderizamos templates parciais +(partials) dentro de outros templates. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte(.rb): + +```ruby +require 'rubygems' +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo!!!!! +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo sinatra. Digite \`enable :inline\_templates\` se tem +templates inline no outro arquivo fonte. + +### Templates nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado sempre que +um template for renderizado. Pode desactivar layouts usando +`:layout => false`. + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +```ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância definidas nos filtros são acedidas através de rotas e +templates: + +```ruby +before do + @nota = 'Olá!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Olá!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do contexto da +requisição e também podem modificar o pedido e a resposta. Variáveis de +instância definidas nos filtros before e rotas são acedidas através dos +filtros after: + +```ruby +after do + puts response.status +end +``` + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +```ruby +before '/protected/*' do + autenticar! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +## Halting + +Para parar imediatamente uma requisição dentro de um filtro ou rota +utilize: + +```ruby +halt +``` + +Pode também especificar o status ao parar… + +```ruby +halt 410 +``` + +Ou com um corpo de texto… + +```ruby +halt 'isto será o corpo de texto' +``` + +Ou também… + +```ruby +halt 401, 'vamos embora!' +``` + +Com cabeçalhos… + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +## Passing + +Dentro de uma rota, pode passar para a próxima rota correspondente +usando `pass`: + +```ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Apanhaste-me!' +end + +get '/adivinhar/*' do + 'Falhaste!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um +404 é retornado. + +## Configuração + +Correndo uma vez, na inicialização, em qualquer ambiente: + +```ruby +configure do + ... +end +``` + +Correndo somente quando o ambiente (`APP_ENV` environment variável) é +definido para `:production`: + +```ruby +configure :production do + ... +end +``` + +Correndo quando o ambiente é definido para `:production` ou `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +## Lidar com Erros + +Lida-se com erros no mesmo contexto das rotas e filtros before, o que +signifca que `haml`, `erb`, etc, estão disponíveis. + +### Não Encontrado + +Quando um `Sinatra::NotFound` exception é levantado, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +```ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado sempre que uma exceção é lançada a +partir de um bloco de rota ou um filtro. O objecto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +```ruby +error do + 'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros personalizados: + +```ruby +error MeuErroPersonalizado do + 'O que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +```ruby +get '/' do + raise MeuErroPersonalizado, 'alguma coisa desagradável' +end +``` + +O resultado será: + +``` +O que aconteceu foi...alguma coisa desagradável +``` + +Alternativamente, pode definir um manipulador de erro para um código de +status: + +```ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um range (alcance): + +```ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra define os manipuladores especiais `not_found` e `error` quando +corre no ambiente de desenvolvimento. + +## Mime Types + +Quando utilizamos `send_file` ou arquivos estáticos pode ter mime types +Sinatra não entendidos. Use `mime_type` para os registar por extensão de +arquivos: + +```ruby +mime_type :foo, 'text/foo' +``` + +Pode também utilizar isto com o helper `content_type`: + +```ruby +content_type :foo +``` + +## Middleware Rack + +O Sinatra corre no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Uma das capacidades mais +interessantes do Rack, para desenvolver aplicações, é o suporte de +“middleware” – componentes que residem entre o servidor e a aplicação, +monitorizando e/ou manipulando o pedido/resposta (request/response) HTTP +para providenciar varios tipos de funcionalidades comuns. + +O Sinatra torna a construção de pipelines do middleware Rack fácil a um +nível superior utilizando o método `use`: + +```ruby +require 'sinatra' +require 'meu_middleware_personalizado' + +use Rack::Lint +use MeuMiddlewarePersonalizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis, bem como blocos: + +```ruby +use Rack::Auth::Basic do |utilizador, senha| + utilizador == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuido com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão.Sinatra +utiliza muitos desses componentes automaticamente dependendo da +configuração, por isso, tipicamente nao é necessário utilizar `use` +explicitamente. + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +```ruby +require 'minha_aplicacao_sinatra' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parametros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Os módulos de classe embutidos `Sinatra::Test` e +`Sinatra::TestHarness` são depreciados na versão 0.9.2. + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação a um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis incovenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: um simples arquivo +de aplicação, directórios `./public` e `./views`, logs, página de detalhes +de excepção, etc.). É onde o Sinatra::Base entra em jogo: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Olá mundo!' + end +end +``` + +A classe MinhaApp é um componente Rack independente que pode utilizar +como um middleware Rack, uma aplicação Rack, ou metal Rails. Pode +utilizar ou executar esta classe com um arquivo rackup `config.ru`; +ou, controlar um componente de servidor fornecendo como biblioteca: + +```ruby +MinhaApp.run! :host => 'localhost', :port => 9090 +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +- Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; + outra coisa, todos os métodos DSL do Sinatra são importados para o + espaço principal. + +- Coloque as rotas da sua aplicação, manipuladores de erro, filtros e + opções na subclasse de um `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desactivadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. + +SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples +sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial +da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, +`:before`, `:error`, `:not_found`, `:configure`, e `:set` messages enviados +para o alto nível. Dê você mesmo uma vista de olhos ao código: aqui está o +[Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) +sendo [incluido dentro de um espaço +principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) + +## Linha de Comandos + +As aplicações Sinatra podem ser executadas directamente: + +```shell +ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # activa o bloqueio (padrão é desligado) +``` + +## A última versão + +Se gostaria de utilizar o código da última versão do Sinatra, crie um +clone local e execute sua aplicação com o directório `sinatra/lib` no +`LOAD_PATH`: + +```shell +cd minhaapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib minhaapp.rb +``` + +Alternativamente, pode adicionar o directório do `sinatra/lib` no +`LOAD_PATH` do seu aplicativo: + +```ruby +$LOAD_PATH.unshift __dir__ + '/sinatra/lib' +require 'rubygems' +require 'sinatra' + +get '/sobre' do + "Estou correndo a versão" + Sinatra::VERSION +end +``` + +Para actualizar o código do Sinatra no futuro: + +```shell +cd meuprojeto/sinatra +git pull +``` + +## Mais + +- [Website do Projeto](http://www.sinatrarb.com/) - Documentação + adicional, novidades e links para outros recursos. + +- [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um + bug? Precisa de ajuda? Tem um patch? + +- [Acompanhar Questões](https://github.com/sinatra/sinatra/issues) + +- [Twitter](https://twitter.com/sinatra) + +- [Lista de Email](http://groups.google.com/group/sinatrarb/topics) + +- [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em + [freenode.net](http://freenode.net) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ru.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ru.md new file mode 100644 index 0000000000..2aa8aa4f03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.ru.md @@ -0,0 +1,3207 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) + +*Внимание: Этот документ является переводом английской версии и может быть +устаревшим* + +Sinatra — это предметно-ориентированный каркас +([DSL](https://ru.wikipedia.org/wiki/Предметно-ориентированный_язык)) +для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Установите gem: + +```shell +gem install sinatra +``` + +и запустите приложение при помощи: + +```shell +ruby myapp.rb +``` + +Оцените результат: [http://localhost:4567](http://localhost:4567) + +Имейте ввиду, что изменения в коде не будут видны до тех пор, пока вы не перезапустите +сервер. Пожалуйста, перезагружайте сервер каждый раз как вносите изменения или добавьте +в проект [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Рекомендуется также установить Thin сервер (`gem install thin`), который автоматически +работает с Sinatra приложениями. + +## Содержание + +* [Sinatra](#sinatra) + * [Содержание](#Содержание) + * [Маршруты](#Маршруты) + * [Условия](#Условия) + * [Возвращаемые значения](#Возвращаемые-значения) + * [Собственные детекторы совпадений для маршрутов](#Собственные-детекторы-совпадений-для-маршрутов) + * [Статические файлы](#Статические-файлы) + * [Представления / Шаблоны](#Представления--Шаблоны) + * [Буквальные шаблоны](#Буквальные-шаблоны) + * [Доступные шаблонизаторы](#Доступные-шаблонизаторы) + * [Haml шаблоны](#haml-шаблоны) + * [Erb шаблоны](#erb-шаблоны) + * [Builder шаблоны](#builder-шаблоны) + * [Nokogiri шаблоны](#nokogiri-шаблоны) + * [Sass шаблоны](#sass-шаблоны) + * [SCSS шаблоны](#scss-шаблоны) + * [Less шаблоны](#less-шаблоны) + * [Liquid шаблоны](#liquid-шаблоны) + * [Markdown шаблоны](#markdown-шаблоны) + * [Textile шаблоны](#textile-шаблоны) + * [RDoc шаблоны](#rdoc-шаблоны) + * [AsciiDoc шаблоны](#asciidoc-шаблоны) + * [Radius шаблоны](#radius-шаблоны) + * [Markaby шаблоны](#markaby-шаблоны) + * [RABL шаблоны](#rabl-шаблоны) + * [Slim шаблоны](#slim-шаблоны) + * [Creole шаблоны](#creole-шаблоны) + * [MediaWiki шаблоны](#mediawiki-шаблоны) + * [CoffeeScript шаблоны](#coffeescript-шаблоны) + * [Stylus шаблоны](#stylus-шаблоны) + * [Yajl шаблоны](#yajl-шаблоны) + * [WLang шаблоны](#wlang-шаблоны) + * [Доступ к переменным в шаблонах](#Доступ-к-переменным-в-шаблонах) + * [Шаблоны с `yield` и вложенные лэйауты](#Шаблоны-с-yield-и-вложенные-лэйауты) + * [Включённые шаблоны](#Включённые-шаблоны) + * [Именованные шаблоны](#Именованные-шаблоны) + * [Привязка файловых расширений](#Привязка-файловых-расширений) + * [Добавление собственного движка рендеринга](#Добавление-собственного-движка-рендеринга) + * [Использование пользовательской логики для поиска шаблона](#Использование-пользовательской-логики-для-поиска-шаблона) + * [Фильтры](#Фильтры) + * [Методы-помощники](#Методы-помощники) + * [Использование сессий](#Использование-сессий) + * [Безопасность сессии](#Безопасность-сессии) + * [Конфигурация сессии](#Конфигурация-сессии) + * [Выбор вашей собственной "прослойки" сессии](#Выбор-вашей-собственной-прослойки-сессии) + * [Прерывание](#Прерывание) + * [Передача](#Передача) + * [Вызов другого маршрута](#Вызов-другого-маршрута) + * [Установка тела, статус кода и заголовков ответа](#Установка-тела-статус-кода-и-заголовков-ответа) + * [Потоковые ответы](#Потоковые-ответы) + * [Логирование](#Логирование) + * [Mime-типы](#mime-типы) + * [Генерирование URL](#Генерирование-url) + * [Перенаправление (редирект)](#Перенаправление-редирект) + * [Управление кэшированием](#Управление-кэшированием) + * [Отправка файлов](#Отправка-файлов) + * [Доступ к объекту запроса](#Доступ-к-объекту-запроса) + * [Вложения](#Вложения) + * [Работа со временем и датами](#Работа-со-временем-и-датами) + * [Поиск файлов шаблонов](#Поиск-файлов-шаблонов) + * [Конфигурация](#Конфигурация) + * [Настройка защиты от атак](#Настройка-защиты-от-атак) + * [Доступные настройки](#Доступные-настройки) + * [Режим, окружение](#Режим-окружение) + * [Обработка ошибок](#Обработка-ошибок) + * [Not Found](#not-found) + * [Error](#error) + * [Rack "прослойки"](#rack-прослойки) + * [Тестирование](#Тестирование) + * [Sinatra::Base — "прослойки", библиотеки и модульные приложения](#sinatrabase--прослойки-библиотеки-и-модульные-приложения) + * [Модульные приложения против классических](#Модульные-приложения-против-классических) + * [Запуск модульных приложений](#Запуск-модульных-приложений) + * [Запуск классических приложений с config.ru](#Запуск-классических-приложений-с-configru) + * [Когда использовать config.ru?](#Когда-использовать-configru) + * [Использование Sinatra в качестве "прослойки"](#Использование-sinatra-в-качестве-прослойки) + * [Создание приложений "на лету"](#Создание-приложений-на-лету) + * [Области видимости и привязка](#Области-видимости-и-привязка) + * [Область видимости приложения / класса](#Область-видимости-приложения--класса) + * [Область видимости запроса / экземпляра](#Область-видимости-запроса--экземпляра) + * [Область видимости делегирования](#Область-видимости-делегирования) + * [Командная строка](#Командная-строка) + * [Многопоточность](#Многопоточность) + * [Системные требования](#Системные-требования) + * [Самая свежая версия](#Самая-свежая-версия) + * [При помощи Bundler](#При-помощи-bundler) + * [Версии](#Версии) + * [Дальнейшее чтение](#Дальнейшее-чтение) + +## Маршруты + +В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут +связан с блоком кода: + +```ruby +get '/' do + # .. что-то показать .. +end + +post '/' do + # .. что-то создать .. +end + +put '/' do + # .. что-то заменить .. +end + +patch '/' do + # .. что-то изменить .. +end + +delete '/' do + # .. что-то удалить .. +end + +options '/' do + # .. что-то ответить .. +end + +link '/' do + # .. что-то подключить .. +end + +unlink '/' do + # .. что-то отключить .. +end +``` + +Маршруты сверяются с запросом в порядке очерёдности их записи в файле +приложения. Первый же совпавший с запросом маршрут и будет вызван. + +Маршруты с конечным слэшем отличаются от маршрутов без него: + +```ruby +get '/foo' do + # не соответствует "GET /foo/" +end +``` + +Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше +`params`: + +```ruby +get '/hello/:name' do + # соответствует "GET /hello/foo" и "GET /hello/bar", + # где params['name'] - это 'foo' или 'bar' + "Hello #{params['name']}!" +end +``` + +Также можно получить доступ к именованным параметрам через параметры блока: + +```ruby +get '/hello/:name' do |n| + # соответствует "GET /hello/foo" и "GET /hello/bar", + # где params['name'] - это 'foo' или 'bar' + # n хранит params['name'] + "Hello #{n}!" +end +``` + +Шаблоны маршрутов также могут включать в себя splat параметры (или '*' маску, +обозначающую любой символ), доступные в массиве `params['splat']`: + +```ruby +get '/say/*/to/*' do + # соответствует /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # соответствует /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Или с параметрами блока: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Можно также использовать регулярные выражения в качестве шаблонов маршрутов: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +Или с параметром блока: + +```ruby +# Соответствует "GET /meta/hello/world", "GET /hello/world/1234" и т.д. +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +Шаблоны маршрутов могут иметь необязательные параметры: + +```ruby +get '/posts/:format?' do + # соответствует "GET /posts/", "GET /posts/json", "GET /posts/xml" и т.д. +end +``` + +Маршруты также могут использовать параметры запроса: + +```ruby +get '/posts' do + # соответствует "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # используются переменные title и author; запрос не обязателен для маршрута /posts +end +``` + +**Имеейте ввиду**: если вы не отключите защиту от обратного пути в директориях +(_path traversal_, см. ниже), путь запроса может быть изменён до начала +поиска подходящего маршрута. + +Вы можете настроить Mustermann опции, используемые для данного маршрута, путём передачи в `:mustermann_opts` хэш: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # в точности соответствует /posts, с явной привязкой + "If you match an anchored pattern clap your hands!" +end +``` + +Это похоже на [условие](#Условия), но это не так! Эти опции будут объеденины в глобальный `:mustermann_opts` хэш, описанный +[ниже](#Доступные-настройки). + +### Условия + +Маршруты могут включать в себя различные условия совпадений, такие как, например, +строка агента пользователя (user agent): + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # соответствует не-songbird браузерам +end +``` + +Другими доступными условиями являются `host_name` и `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` ищет заголовок запроса `Accept`. + +Вы можете с лёгкостью задавать собственные условия: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +Ипользуйте splat-оператор (`*`) для условий, которые принимают несколько аргументов: + +```ruby +set(:auth) do |*roles| # <- обратите внимание на звёздочку + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +### Возвращаемые значения + +Возвращаемое значение блока маршрута ограничивается телом ответа, которое +будет передано HTTP клиенту, или, по крайней мере, следующей "прослойке" (middleware) +в Rack стеке. Чаще всего это строка как в примерах выше. Но также приемлемы и +другие значения. + +Вы можете вернуть любой объект, который будет либо корректным Rack ответом, +либо объектом Rack body, либо кодом состояния HTTP: + +* массив с тремя переменными: `[код (Integer), заголовки (Hash), тело ответа + (должно отвечать на #each)]`; +* массив с двумя переменными: `[код (Integer), тело ответа (должно отвечать + на #each)]`; +* объект, отвечающий на `#each`, который передает только строковые типы + данных в этот блок; +* Integer, представляющий код состояния HTTP. + +Таким образом легко можно реализовать, например, потоковую передачу: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Вы также можете использовать вспомогательный метод `stream` (описанный ниже) +для того, чтобы уменьшить количество шаблонного кода и встроить потоковую +логику прямо в маршрут. + +### Собственные детекторы совпадений для маршрутов + +Как показано выше, Sinatra поставляется со встроенной поддержкой строк и +регулярных выражений в качестве шаблонов URL. Но и это ещё не всё. Вы можете +легко определить свои собственные детекторы совпадений (matchers) для +маршрутов: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Обратите внимание на то, что предыдущий пример, возможно, чересчур усложнён, потому что он +может быть реализован следующим образом: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Или с использованием негативной опережающей проверки (отрицательное look-ahead условие): + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Статические файлы + +Статические файлы раздаются из `./public` директории. Вы можете указать другое +месторасположение при помощи опции `:public_folder`: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Учтите, что имя директории со статическими файлами не включено в URL. +Например, файл `./public/css/style.css` будет доступен как +`http://example.com/css/style.css`. + +Используйте опцию `:static_cache_control` (см. ниже) для того, чтобы добавить +заголовок `Cache-Control`. + +## Представления / Шаблоны + +Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту +возвращают строку: + +```ruby +get '/' do + erb :index +end +``` + +Данный код отрендерит файл `views/index.erb`. + +Вместо имени шаблона вы так же можете передавать непосредственно само +содержимое шаблона: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Метод рендеринга шаблона принимает в качестве второго аргумента хэш с опциями: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Данный метод отрендерит шаблон `views/index.erb`, который будет вложен в `views/post.erb` +(по умолчанию: `views/layout.erb`, если файл существует). + +Любые опции, которые Sinatra не распознает, будут переданы в шаблонизатор: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Вы также можете глобально задавать опции для шаблонизаторов: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Опции, переданные в метод, переопределяют опции, заданные при помощи `set`. + +Доступные опции: + +
    +
    locals
    +
    + Список локальных переменных, передаваемых в документ. + Пример: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + Кодировка, которую следует использовать в том случае, если не удалось + определить оригинальную. По умолчанию: settings.default_encoding. +
    + +
    views
    +
    + Директория с шаблонами. По умолчанию: settings.views. +
    + +
    layout
    +
    + Определяет необходимость использования лэйаута (true или false). + Если же в качестве значения передан символ, то его значение будет интерпретировано + как наименования файла шаблона лэйаута. + Пример: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type отображенного шаблона. По умолчанию: задаётся шаблонизатором. +
    + +
    scope
    +
    + Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр + приложения. Если вы измените эту опцию, то переменные экземпляра и + методы-помощники станут недоступными в ваших шаблонах. +
    + +
    layout_engine
    +
    + Шаблонизатор, который следует использовать для отображения лэйаута. + Полезная опция для шаблонизаторов, в которых отсутствует поддержка + лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого + шаблона. Пример: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Специальные опции, используемые только для рендеринга лэйаута. Пример: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +По умолчанию в качестве пути для хранения шаблонов принята директория `./views`. +Чтобы назначить другую директорию с шаблонами необходимо изменить настройки: + +```ruby +set :views, settings.root + '/templates' +``` + +Важное замечание: вы всегда должны ссылаться на шаблоны при помощи символов +(Symbol), даже тогда, когда они расположены в поддиректории (в этом случае +используйте конструкции вида `:'subdir/template'`). Вы должны использовать +символы в связи с тем, что в ином случае шаблонизаторы попросту отображают +любые строки, переданные им. + +### Буквальные шаблоны + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Отобразит шаблон, содержимое которого передано строкой. Опционально можно +указать дополнительные опции `:path` и `:line` для того, чтобы улучшить бэктрейс. +Делайте это в том случае, если строка определена в некотором файле, к которому +можно указать путь и номер строки, где расположена исходная строка: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Доступные шаблонизаторы + +Некоторые языки шаблонов имеют несколько реализаций. Для того, чтобы указать +конкретную реализацию, которую вы хотите использовать, вам следует просто +подключить нужную библиотеку: + +```ruby +require 'rdiscount' # или require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml шаблоны + + + + + + + + + + + + + + +
    Зависимостиhaml
    Расширения файлов.haml
    Примерhaml :index, :format => :html5
    + +#### Erb шаблоны + + + + + + + + + + + + + + +
    Зависимости + erubis + или erb (включён в Ruby) +
    Расширения файлов.erb, .rhtml or .erubis (только Erubis)
    Примерerb :index
    + +#### Builder шаблоны + + + + + + + + + + + + + + +
    Зависимости + builder +
    Расширения файлов.builder
    Примерbuilder { |xml| xml.em "hi" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### Nokogiri шаблоны + + + + + + + + + + + + + + +
    Зависимостиnokogiri
    Расширения файлов.nokogiri
    Примерnokogiri { |xml| xml.em "hi" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### Sass шаблоны + + + + + + + + + + + + + + +
    Зависимостиsass
    Расширения файлов.sass
    Примерsass :stylesheet, :style => :expanded
    + +#### SCSS шаблоны + + + + + + + + + + + + + + +
    Зависимостиsass
    Расширения файлов.scss
    Примерscss :stylesheet, :style => :expanded
    + +#### Less шаблоны + + + + + + + + + + + + + + +
    Зависимостиless
    Расширения файлов.less
    Примерless :stylesheet
    + +#### Liquid шаблоны + + + + + + + + + + + + + + +
    Зависимостиliquid
    Расширения файлов.liquid
    Примерliquid :index, :locals => { :key => 'value' }
    + +В связи с тем, что в Liquid шаблонах невозможно вызывать методы из Ruby +(за исключением `yield`), вам почти всегда понадобиться передавать в шаблон +локальные переменные. + +#### Markdown шаблоны + + + + + + + + + + + + + + +
    Зависимости + Любая из библиотек: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    Расширения файлов.markdown, .mkd and .md
    Примерmarkdown :index, :layout_engine => :erb
    + +В Markdown невозможно вызывать методы или передавать локальные переменные. +По этой причине вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `markdown` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Вы не можете вызывать Ruby код код из Markdown, соответственно вы не можете +использовать лэйауты на Markdown. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### Textile шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRedCloth
    Расширения файлов.textile
    Примерtextile :index, :layout_engine => :erb
    + +В Textile невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать данный шаблон +совместно с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `textile` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Вы не можете вызывать Ruby код код из Textile, соответственно вы не можете +использовать лэйауты на Textile. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### RDoc шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRDoc
    Расширения файлов.rdoc
    Примерrdoc :README, :layout_engine => :erb
    + +В RDoc невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `rdoc` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Вы не можете вызывать Ruby код код из RDoc, соответственно вы не можете использовать +лэйауты на RDoc. Тем не менее, существует возможность использовать один шаблонизатор +для отображения шаблона, а другой для лэйаута при помощи опции +`:layout_engine`. + +#### AsciiDoc шаблоны + + + + + + + + + + + + + + +
    ЗависимостиAsciidoctor
    Расширения файлов.asciidoc, .adoc и .ad
    Примерasciidoc :README, :layout_engine => :erb
    + +Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Radius шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRadius
    Расширения файлов.radius
    Примерradius :index, :locals => { :key => 'value' }
    + +Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Markaby шаблоны + + + + + + + + + + + + + + +
    ЗависимостиMarkaby
    Расширения файлов.mab
    Примерmarkaby { h1 "Welcome!" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### RABL шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRabl
    Расширения файлов.rabl
    Примерrabl :index
    + +#### Slim шаблоны + + + + + + + + + + + + + + +
    ЗависимостиSlim Lang
    Расширения файлов.slim
    Примерslim :index
    + +#### Creole шаблоны + + + + + + + + + + + + + + +
    ЗависимостиCreole
    Расширения файлов.creole
    Примерcreole :wiki, :layout_engine => :erb
    + +В Creole невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать данный шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +обратите внимание на то, что вы можете вызывать метод `creole` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Вы не можете вызывать Ruby код из Creole, соответственно вы не можете +использовать лэйауты на Creole. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### MediaWiki шаблоны + + + + + + + + + + + + + + +
    ЗависимостиWikiCloth
    Расширения файлов.mediawiki и .mw
    Примерmediawiki :wiki, :layout_engine => :erb
    + +В разметке MediaWiki невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `mediawiki` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Вы не можете вызывать Ruby код из MediaWiki, соответственно вы не можете +использовать лэйауты на MediaWiki. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### CoffeeScript шаблоны + + + + + + + + + + + + + + +
    Зависимости + + CoffeeScript + и + + способ запускать JavaScript + +
    Расширения файлов.coffee
    Примерcoffee :index
    + +#### Stylus шаблоны + + + + + + + + + + + + + + +
    Зависимости + + Stylus + и + + способ запускать JavaScript + +
    Расширение файла.styl
    Примерstylus :index
    + +Перед тем, как использовать шаблоны Stylus, необходимо сперва подключить +`stylus` и `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl шаблоны + + + + + + + + + + + + + + +
    Зависимостиyajl-ruby
    Расширения файлов.yajl
    Пример + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +Содержимое шаблона интерпретируется как код на Ruby, а результирующая +переменная json затем конвертируется при помощи `#to_json`. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Опции `:callback` и `:variable` используются для "декорирования" итогового +объекта: + +```ruby +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang шаблоны + + + + + + + + + + + + + + +
    ЗависимостиWLang
    Расширения файлов.wlang
    Примерwlang :index, :locals => { :key => 'value' }
    + +Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за +исключением `yield`), то вы почти всегда будете передавать в шаблон локальные +переменные. Лэйауты также могут быть описаны при помощи WLang. + +### Доступ к переменным в шаблонах + +Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. +Переменные экземпляра, установленные в процессе обработки маршрутов, будут +доступны напрямую в шаблонах: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Вы также можете установить их при помощи хэша локальных переменных: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Это обычный подход, применяемый тогда, когда шаблоны рендерятся как части других шаблонов. + +### Шаблоны с `yield` и вложенные лэйауты + +Лэйаут (layout) обычно представляет собой шаблон, который исполняет +`yield`. Такой шаблон может быть использован либо при помощи опции `:template`, +как описано выше, либо при помощи блока: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Эти инструкции по сути эквивалентны `erb :index, :layout => :post`. + +Передача блоков интерпретирующим шаблоны методам наиболее полезна для +создания вложенных лэйаутов: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +То же самое может быть сделано в более короткой форме: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +В настоящее время следующие методы шаблонизаторов +принимают блок: `erb`, `haml`, `liquid`, `slim `, `wlang`. Кроме того, +общий метод построения шаблонов `render` также принимает блок. + +### Включённые шаблоны + +Шаблоны также могут быть определены в конце исходного файла: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +Обратите внимание: включённые шаблоны, определённые в исходном файле, который подключил +Sinatra, будут загружены автоматически. Вызовите `enable :inline_templates` +напрямую в том случае, если используете включённые шаблоны в других файлах. + +### Именованные шаблоны + +Шаблоны также могут быть определены при помощи метода `template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +Если шаблон с именем "layout" существует, то он будет использоваться каждый +раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае при +помощи опции `:layout => false` или отключить его для всего приложения: `set :haml, +:layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Привязка файловых расширений + +Для того, чтобы связать расширение файла с движком рендеринга, используйте +`Tilt.register`. Так, например, вызовите следующий код в том случае, если вы +хотите использовать расширение `tt` для шаблонов Textile: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Добавление собственного движка рендеринга + +Сначала зарегистрируйте собственный движок в Tilt, а затем создайте метод, отвечающий +за рендеринг: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Данный код отрендерит `./views/index.myat`. +Подробнее о [Tilt](https://github.com/rtomayko/tilt#readme). + +### Использование пользовательской логики для поиска шаблона + +Для того, чтобы реализовать собственный механизм поиска шаблона, +необходимо написать метод `#find_template`: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Фильтры + +`before`-фильтры выполняются перед каждым запросом в том же контексте, что и +маршруты, и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +`after`-фильтры выполняются после каждого запроса в том же контексте +и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в `before`-фильтрах и маршрутах, будут доступны в +`after`-фильтрах: + +```ruby +after do + puts response.status +end +``` + +Обратите внимание: если вы используете метод `body`, а не просто возвращаете строку из +маршрута, то тело ответа не будет доступно в `after`-фильтрах, так как оно +будет сгенерировано позднее. + +Фильтры также могут использовать шаблоны URL и будут выполнены только в том случае, +если путь запроса совпадет с указанным шаблоном: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Как и маршруты, фильтры могут использовать условия: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Методы-помощники + +Используйте высокоуровневый метод `helpers` для того, чтобы определить +методы-помощники, которые могут быть использованы в обработчиках маршрутов +и шаблонах: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Также методы-помощники могут быть заданы в отдельных модулях: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +Эффект равносилен включению модулей в класс приложения. + +### Использование сессий + +Сессия используется для того, чтобы сохранять состояние между запросами. Если +эта опция включена, то у вас будет один хэш сессии на один пользовательский сеанс: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +### Безопасность сессии + +Для того, чтобы повысить безопасность, данные сессии в файле 'cookie' +подписываются ключом сессии с использованием `HMAC-SHA1`. Этот ключ сессии +должен быть оптимальным криптографическим 'secure random' значением соответствующей +длины, которая для `HMAC-SHA1` больше или равна 64 байтам (512 бит, 128 +шестнадцатеричных символов). Не рекомендуется использовать ключ, длина +которого менее 32 байт (256 бит, 64 шестнадцатеричных символа). Поэтому +**очень важно**, чтобы вы не просто составили значение ключа, а использовали +безопасный генератор случайных чисел для его создания. Люди очень плохо +придумывают случайные значения. + +По умолчанию, Sinatra создаёт для вас безопасный случайный ключ сессии из +32 байт, однако он будет меняться при каждом перезапуске приложения. Если у +вас есть несколько экземпляров вашего приложения, и вы доверили Sinatra +генерацию ключа, то каждый экземпляр будет иметь отличный ключ сессии, +что, вероятно, не совсем то, что вам необходимо. + +Для лучшей безопасности и удобства использования +[рекомендуется](https://12factor.net/config) генерировать случайный безопасный +ключ и хранить его в переменной среды на каждом хосте, на котором запущено +приложение, чтобы все экземпляры вашего приложения использовали один и тот +же ключ. Вы должны периодически менять значение ключа сессии на новое. +Вот несколько примеров того, как вы можете создать 64-байтный ключ +и установить его: + +**Генерация ключа сессии** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Генерация ключа сессии (бонусные пункты)** + +Используйте [гем 'sysrandom'](https://github.com/cryptosphere/sysrandom#readme). +Предпочтительнее использовать системные средства RNG для генерации случайных +значений вместо пространства пользователя `OpenSSL`, который в настоящее время +по умолчанию используется в MRI Ruby: + +```text +$ gem install sysrandom +Создание собственных расширений. Это может занять некоторое время... +Успешно установлен sysrandom-1.x +1 gem установлен + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Переменная среды для ключа сессии** + +Задайте переменной среды `SESSION_SECRET` значение, которое вы +сгенерировали. Данная переменная автоматически будет использована Sinatra. +Сделайте это значение постоянным при перезагрузке вашего +сервера. Поскольку метод для генерации будет различным в разных системах, +то код ниже приведён только в качестве примера: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Конфигурация приложения** + +В целях безопасности настройте конфигурацию вашего приложения таким образом, +чтобы оно генерировало случайный безопасный ключ тогда, когда переменная +среды `SESSION_SECRET` не доступна. + +В качестве бонусных пунктов здесь тоже используйте +[гем 'sysrandom'gem](https://github.com/cryptosphere/sysrandom): + +```ruby +require 'securerandom' +# -или- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Конфигурация сессии + +Если вы хотите больше настроек для сессий, вы можете задать их, передав хэш +опций в параметр `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах +foo.com, добавьте *.* перед доменом: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Выбор вашей собственной "прослойки" сессии + +Обратите внимание на то, что при использовании `enable :sessions` все данные +сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, +сохранение больших объёмов данных увеличит ваш трафик). В таком случае вы +можете использовать альтернативную Rack "прослойку" (middleware), реализующую +механизм сессий. Для этого используете один из способов ниже: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Или установите параметры сессии при помощи хэша опций: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Вы также можете **не вызывать** `enable :sessions`, а вместо этого использовать +необходимую вам Rack прослойку так же, как вы это обычно делаете. + +Очень важно обратить внимание на то, что когда вы используете этот метод, +основной способ защиты сессии **не будет включён по умолчанию**. + +Вам также потребуется добавить следующие Rack middleware для этого: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Смотрите раздел ["Настройка защиты от атак"](#Настройка-защиты-от-атак) для более подробной информации. + +### Прерывание + +Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, +используйте следующую команду: + +```ruby +halt +``` + +Можно также указать статус при прерывании: + +```ruby +halt 410 +``` + +Тело: + +```ruby +halt 'this will be the body' +``` + +И то, и другое: + +```ruby +halt 401, 'go away!' +``` + +Можно указать заголовки: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +И, конечно, можно использовать шаблоны с `halt`: + +```ruby +halt erb(:error) +``` + +### Передача + +Маршрут может передать обработку запроса следующему совпадающему маршруту +используя метод `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +Блок маршрута сразу же прерывается, а контроль переходит к следующему +совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на +запрос будет 404. + +### Вызов другого маршрута + +Иногда `pass` не подходит, например, если вы хотите получить результат вызова +другого обработчика маршрута. В таком случае просто используйте `call`: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Обратите внимание на то, что в предыдущем примере можно облегчить тестирование и +повысить производительность, перенеся `"bar"` в метод-помощник, используемый и в +`/foo`, и в `/bar`. + +Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, +а не в его копию, используйте `call!` вместо `call`. + +Если хотите узнать больше о `call`, смотрите спецификацию Rack. + +### Установка тела, статус кода и заголовков ответа + +Хорошим тоном является установка кода состояния HTTP и тела ответа в +возвращаемом значении обработчика маршрута. Тем не менее, в некоторых +ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке +потока исполнения. Вы можете сделать это при помощи метода-помощника `body`. +Если вы задействуете метод `body`, то вы можете использовать его и в +дальнейшем, чтобы получить доступ к телу ответа: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Также можно передать блок в метод `body`, который затем будет вызван +обработчиком Rack (такой подход может быть использован для реализации +потокового ответа, см. ["Возвращаемые значения"](#Возвращаемые-значения)). + +Аналогично вы можете установить код ответа и его заголовки: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Как и `body`, методы `headers` и `status`, вызванные без аргументов, +возвращают свои текущие значения. + +### Потоковые ответы + +Иногда требуется начать отправлять данные клиенту прямо в процессе +генерирования частей этих данных. В особых случаях требуется постоянно +отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете +использовать метод `stream` вместо разработки собственных "обёрток". + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +Это позволяет вам реализовать стриминговые API, +[Server Sent Events](https://w3c.github.io/eventsource/), +и может служить основой для [WebSockets](https://en.wikipedia.org/wiki/WebSocket). +Также такой подход можно использовать для увеличения производительности в том случае, +когда какая-то часть контента (а не весь) зависит от медленного ресурса. + +Обратите внимание на то, что возможности стриминга, особенно количество одновременно +обслуживаемых запросов, очень сильно зависят от используемого веб-сервера. +Некоторые серверы могут и вовсе не поддерживать стриминг. Если сервер не +поддерживает стриминг, то все данные будут отправлены за один раз сразу после +того, как блок, переданный в `stream`, завершится. Стриминг вообще не работает +при использовании Shotgun. + +Если метод используется с параметром `keep_open`, то он не будет вызывать +`close` у объекта потока, что позволит вам закрыть его позже в любом другом +месте. Это работает только с событийными серверами, например, с Thin и +Rainbows. Другие же серверы всё равно будут закрывать поток: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # регистрация клиента в событиях сервера + stream(:keep_open) do |out| + connections << out + # удаление "мёртвых клиентов" + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # уведомить клиента о новом сообщении + out << params['message'] << "\n" + + # указать клиенту на необходимость снова соединиться + out.close + end + + # допуск + "message received" +end +``` + +Также клиент может закрыть соединение при попытке записи в сокет. В связи с +этим рекомендуется выполнить проверку `out.closed?` прежде, чем пытаться произвести запись. + +### Логирование + +В области видимости запроса метод `logger` предоставляет доступ к экземпляру +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Этот логер автоматически учитывает ваши настройки логирования в Rack. Если +логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы +можете смело использовать его в маршрутах и фильтрах. + +Обратите внимание на то, что логирование включено по умолчанию только для +`Sinatra::Application`. Если ваше приложение является подклассом `Sinatra::Base`, то +вы, скорее всего, захотите включить его вручную: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Чтобы избежать использования любой логирующей "прослойки", задайте опции +`logging` значение `nil`. При этом не забывайте, что в такой ситуации +`logger` будет возвращать `nil`. Чаще всего так делают, когда задают свой собственный +логер. Sinatra будет использовать то, что находится в `env['rack.logger']`. + +### Mime-типы + +Когда вы используете `send_file` или статические файлы, у вас могут быть +mime-типы, которые Sinatra не понимает по умолчанию. Используйте `mime_type` +для их регистрации по расширению файла: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Вы также можете использовать это в методе-помощнике `content_type`: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Генерирование URL + +Чтобы сформировать URL, вам следует использовать метод `url`, например, в Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они +присутствуют. + +Наряду с `url` вы можете использовать `to` (смотрите пример ниже). + +### Перенаправление (редирект) + +Вы можете перенаправить браузер пользователя при помощи метода `redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Любые дополнительные параметры используются по аналогии с аргументами метода +`halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +Вы также можете перенаправить пользователя обратно на страницу, с которой он +пришёл, при помощи `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Для того, чтобы передать какие-либо параметры вместе с перенаправлением, +добавьте их в строку запроса: + +```ruby +redirect to('/bar?sum=42') +``` + +либо используйте сессию: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Управление кэшированием + +Установка корректных заголовков — основа правильного HTTP кэширования. + +Вы можете легко выставить заголовок Cache-Control следующим образом: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Совет: задавайте кэширование в `before`-фильтре: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Если вы используете метод `expires` для задания соответствующего заголовка, то +`Cache-Control` будет выставлен автоматически: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Чтобы использовать кэширование правильно, вам стоит подумать о +применении `etag` или `last_modified`. Рекомендуется использовать эти +методы-помощники *до* выполнения ресурсоёмких вычислений, так как они +немедленно отправят ответ клиенту в том случае, если текущая версия +уже присутствует в их кэше: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Также вы можете использовать +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +Эти методы-помощники не станут ничего кэшировать, однако они дадут +необходимую информацию для вашего кэша. Если вы ищете лёгкое решение для +кэширования, попробуйте [rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок +`Cache-Control` к статическим файлам. + +В соответствии с RFC 2616 ваше приложение должно вести себя по-разному, когда +заголовки If-Match или If-None-Match имеют значение `*`, в зависимости от +того, существует или нет запрашиваемый ресурс. Sinatra предполагает, что +ресурсы, к которым обращаются при помощи безопасных (GET) и идемпотентных (PUT) +методов, уже существуют, а остальные ресурсы (к которым обращаются, например, +при помощи POST) считает новыми. Вы можете изменить данное поведение при помощи +опции `:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Если вы хотите использовать weak ETag, задайте опцию `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Отправка файлов + +Для отправки файлов пользователю вы можете использовать метод `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Этот метод имеет несколько опций: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Возможные опции: + +
    +
    filename
    +
    имя файла, по умолчанию: реальное имя файла.
    + +
    last_modified
    +
    значение для заголовка Last-Modified, по умолчанию: mtime (время + изменения) файла.
    + +
    type
    +
    тип файла, по умолчанию: определяется по расширению файла.
    + +
    disposition
    +
    + используется для заголовка Content-Disposition, возможные значения: nil + (по умолчанию), :attachment и :inline +
    + +
    length
    +
    значения для заголовка Content-Length, по умолчанию: размер файла.
    + +
    status
    +
    + Код ответа. Полезно в том случае, когда отсылается статический файл в качестве + страницы с сообщением об ошибке. Если поддерживается обработчик Rack, будут использоваться + другие средства, кроме потоковой передачи из процесса Ruby. Если вы используете + этот вспомогательный метод, Sinatra автоматически обрабатывает запросы диапазона. +
    +
    + +### Доступ к объекту запроса + +Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, +маршрутах, обработчиках ошибок) при помощи `request` метода: + +```ruby +# приложение запущено на http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # тело запроса, посланное клиентом (см. ниже) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # длина тела запроса + request.media_type # медиатип тела запроса + request.host # "example.com" + request.get? # true (есть аналоги для других методов HTTP) + request.form_data? # false + request["some_param"] # значение параметра some_param. Шорткат для хэша params + request.referrer # источник запроса клиента либо '/' + request.user_agent # user agent (используется для :agent условия) + request.cookies # хэш, содержащий cookies браузера + request.xhr? # является ли запрос ajax запросом? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-адрес клиента + request.secure? # false (true, если запрос сделан через SSL) + request.forwarded? # true (если сервер работает за обратным прокси) + request.env # "сырой" env хэш, полученный Rack +end +``` + +Некоторые опции, такие как `script_name` или `path_info`, доступны для +модификации: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body` является IO или StringIO объектом: + +```ruby +post "/api" do + request.body.rewind # в случае, если кто-то уже прочитал тело запроса + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Вложения + +Вы можете использовать метод `attachment`, чтобы сообщить браузеру о том, +что ответ сервера должен быть сохранён на диск, а не отображён: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +Вы также можете указать имя файла: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Работа со временем и датами + +Sinatra предлагает метод-помощник `time_for`, который из заданного значения +создает объект Time. Он также может конвертировать `DateTime`, `Date` и +схожие классы: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +Этот метод используется внутри Sinatra методами `expires`, `last_modified` и +им подобными. Поэтому вы легко можете изменить и дополнить поведение этих методов, +переопределив `time_for` в своём приложении: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Поиск файлов шаблонов + +Для поиска шаблонов и их последующего рендеринга используется метод +`find_template`: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +Это не слишком полезный пример. Зато полезен тот факт, что вы можете +переопределить этот метод, чтобы использовать свой собственный механизм +поиска. Например, если вы хотите, чтобы можно было использовать несколько +директорий с шаблонами: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Другой пример, в котором используются разные директории для движков +рендеринга: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Вы можете легко вынести этот код в расширение и поделиться им с остальными! + +Обратите внимание на тот факт, что `find_template` не проверяет, существует ли +файл на самом деле, а вызывает заданный блок для всех возможных путей. Дело тут не в +производительности, а в том, что `render` вызовет `break` как только файл +будет найден. Содержимое и местонахождение шаблонов будет закэшировано в том случае, +если приложение запущено не в режиме разработки (`set :environment, :development`). +Вы должны помнить об этих нюансах, если пишите по-настоящему "сумасшедший" +метод. + +## Конфигурация + +Этот блок исполняется один раз при старте в любом окружении (environment): + +```ruby +configure do + # задание одной опции + set :option, 'value' + + # устанавливаем несколько опций + set :a => 1, :b => 2 + + # то же самое, что и `set :option, true` + enable :option + + # то же самое, что и `set :option, false` + disable :option + + # у вас могут быть "динамические" опции с блоками + set(:css_dir) { File.join(views, 'css') } +end +``` + +Следующий пример будет выполнен только тогда, когда окружение +(переменная `APP_ENV`) будет задана как `:production`: + +```ruby +configure :production do + ... +end +``` + +Следующий код будет выполнен в том случае, когда окружение +будет задано как `:production` или `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Вы можете получить доступ к этим опциям при помощи метода `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Настройка защиты от атак + +Sinatra использует +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) для защиты +приложения от простых атак. Вы можете легко выключить эту защиту (что сделает +ваше приложение чрезвычайно уязвимым к большому числу различных уязвимостей): + +```ruby +disable :protection +``` + +Чтобы отключить какой-либо конкретный уровень защиты, передайте хэш опций +в параметр `protection`: + +```ruby +set :protection, :except => :path_traversal +``` + +Вы также можете отключить сразу несколько уровней защиты: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +По умолчанию Sinatra будет устанавливать `session based` защиту только если +включена опция `:sessions`. См. ["Использование сессий""](#Использование-сессий). +Иногда вы захотите настроить сессии "вне" приложения Sinatra, например, в +config.ru или при помощи отдельного экземпляра `Rack::Builder`. В таком случае +вы также можете настроить `session based` защиту, передав опцию `:session`: + +```ruby +set :protection, :session => true +``` + +### Доступные настройки + +
    +
    absolute_redirects
    +
    + если отключено, то Sinatra будет позволять использование относительных + перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP + 1.1), который разрешает только абсолютные перенаправления. +
    +
    + включайте эту опцию, если ваше приложение работает за обратным прокси, + который настроен не совсем корректно. Обратите внимание на тот факт, что + метод url всё равно будет генерировать абсолютные URL в том случае, + если вы не передадите false вторым аргументом. +
    +
    отключено по умолчанию.
    + +
    add_charset
    +
    + mime-типы, к которым метод content_type будет автоматически добавлять + информацию о кодировке. Вам следует добавлять значения к этой опции + вместо её переопределения: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + путь к главному файлу приложения, используется для нахождения корневой + директории проекта, директорий с шаблонами и статическими файлами, + вложенных шаблонов. +
    + +
    bind
    +
    + используемый IP-адрес (по умолчанию: 0.0.0.0 или + localhost если опция `environment` настроена на "development"). + Используется только встроенным сервером. +
    + +
    default_encoding
    +
    кодировка, если неизвестна (по умолчанию: "utf-8").
    + +
    dump_errors
    +
    отображать ошибки в логе.
    + +
    environment
    +
    + текущее окружение, по умолчанию, значение ENV['APP_ENV'] или + "development", если ENV['APP_ENV'] недоступна. +
    + +
    logging
    +
    использовать логер.
    + +
    lock
    +
    + создаёт блокировку для каждого запроса, которая гарантирует обработку + только одного запроса в текущий момент времени в Ruby процессе. +
    +
    + включайте в том случае, если ваше приложение не потокобезопасно (thread-safe). + Отключено по умолчанию. +
    + +
    method_override
    +
    + использовать "магический" параметр _method для поддержки + PUT/DELETE форм в браузерах, которые не поддерживают данные методы. +
    + +
    mustermann_opts
    +
    + хэш настроек по умолчанию для перехода к Mustermann.new при компиляции + маршрутов маршрутизации. +
    + +
    port
    +
    + порт, на котором будет работать сервер. + Используется только встроенным сервером. +
    + +
    prefixed_redirects
    +
    + добавлять или нет параметр request.script_name к редиректам, если не + задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя + как redirect to('/foo'). Отключено по умолчанию. +
    + +
    protection
    +
    включена или нет защита от атак. Смотрите секцию защиты от атак выше.
    + +
    public_dir
    +
    алиас для public_folder.
    + +
    public_folder
    +
    + путь к общедоступной директории, откуда будут раздаваться файлы. + Используется, только если включена раздача статических файлов + (см. опцию static ниже). Берётся из настройки app_file, + если не установлена. +
    + +
    quiet
    +
    + отключает журналы, созданные командами запуска и остановки Sinatra. + false по умолчанию. +
    + +
    reload_templates
    +
    + перезагружать или нет шаблоны на каждый запрос. Включено в режиме + разработки. +
    + +
    root
    +
    + путь к корневой директории проекта. Берётся из настройки app_file, + если не установлен. +
    + +
    raise_errors
    +
    + выбрасывать исключения (будет останавливать приложение). + По умолчанию включено только в окружении "test". +
    + +
    run
    +
    + если включено, Sinatra будет самостоятельно запускать веб-сервер. Не + включайте, если используете rackup или аналогичные средства. +
    + +
    running
    +
    работает ли сейчас встроенный сервер? Не меняйте эту опцию!
    + +
    server
    +
    + сервер или список серверов, которые следует использовать в качестве + встроенного сервера. Порядок задаёт приоритет, по умолчанию зависит + от реализации Ruby. +
    + +
    server_settings
    +
    + Если вы используете в качестве сервера WEBrick, например для работы в + режиме разработки, то вы можете передать набор опций для server_settings, + таких как SSLEnable или SSLVerifyClient. Тем не менее, такие + серверы как Puma или Thin не поддерживают эти параметры, поэтому вы можете + устанавливать server_settings указав его как метод при вызове + configure. +
    + +
    sessions
    +
    + включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. + Смотрите раздел "Использование сессий" выше. +
    + +
    session_store
    +
    + используемая Rack "прослойка" для сессии. По умолчанию Rack::Session::Cookie. + Смотрите раздел "Использование сессий" выше. +
    + +
    show_exceptions
    +
    + показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию + включено только в окружении "development". +
    +
    + может также быть установлено в :after_handler для запуска специфичной + для приложения обработки ошибок, перед показом трассировки стека в браузере. +
    + +
    static
    +
    указывает, должна ли Sinatra осуществлять раздачу статических файлов.
    +
    Отключите, если используете какой-либо веб-сервер для этой цели.
    +
    Отключение значительно улучшит производительность приложения.
    +
    По умолчанию включено в классических и отключено в модульных приложениях.
    + +
    static_cache_control
    +
    + когда Sinatra раздаёт статические файлы, используйте эту опцию для того, + чтобы добавить им заголовок Cache-Control. Для этого используется + метод-помощник cache_control. По умолчанию отключено. +
    +
    + используйте массив, когда надо задать несколько значений: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + если включено, то Thin будет использовать EventMachine.defer для + обработки запросов. +
    + +
    traps
    +
    указывает, должна ли Sinatra обрабатывать системные сигналы.
    + +
    views
    +
    + путь к директории с шаблонами. Берётся из настройки app_file в том + случае, если не установлен. +
    + +
    x_cascade
    +
    + Указывает, необходимо ли устанавливать заголовок X-Cascade если никакие маршруты не совпадают. + true по умолчанию. +
    +
    + +## Режим, окружение + +Есть 3 предопределённых режима работы приложения (окружения): `"development"`, +`"production"` и `"test"`. Режим может быть задан через переменную окружения `APP_ENV`. +Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны +перезагружаются между запросами, а также задаются специальные обработчики +`not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях +`"production"` и `"test"` шаблоны по умолчанию кэшируются. + +Для запуска приложения в определённом окружении установите переменную +окружения `APP_ENV`: + +```shell +APP_ENV=production ruby my_app.rb +``` + +Вы можете использовать предопределённые методы `development?`, `test?` и +`production?`, чтобы определить текущее окружение. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Обработка ошибок + +Обработчики ошибок исполняются в том же контексте, что и маршруты и +`before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`, +`halt` и т.д. доступны и им. + +### Not Found + +В случае возникновения исключения `Sinatra::NotFound` или возврата кода ответа 404 +будет вызван обработчик `not_found`: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +Обработчик ошибок `error` будет вызван тогда, когда исключение выброшено из блока +маршрута либо из фильтра. Тем не менее, обратите внимание на то, что в режиме разработки +он будет запускаться только в том случае, если вы установите опцию "show exceptions" +на `: after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +Объект-исключение доступен как переменная `sinatra.error` в Rack: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Пользовательские ошибки: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Тогда, если это возникнет: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +То вы получите: + +``` +So what happened was... something bad +``` + +Также вы можете установить обработчик ошибок для кода состояния HTTP: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Либо набора кодов: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra устанавливает специальные обработчики `not_found` и `error`, когда +приложение запущено в режиме разработки (окружение `:development`) чтобы +отображать информативные трассировки стека и дополнительную информацию об отладке +в вашем браузере. + +## Rack "прослойки" + +Sinatra использует [Rack](https://rack.github.io/) - минимальный стандартный +интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для +разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — +компонентов, находящихся "между" сервером и вашим приложением, которые +отслеживают и/или манипулируют HTTP запросами/ответами для предоставления +различной функциональности. + +Sinatra позволяет очень просто создавать пайплайны из подобных Rack "прослоек" +при помощи метода `use`: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +Семантика `use` идентична той, что определена для +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(чаще всего используется в rackup файлах). Так, например, метод `use` принимает как +множественные переменные, так и блоки: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack распространяется с различными стандартными "прослойками" для логирования, +отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra +использует многие из этих компонентов автоматически, основываясь на +конфигурации, чтобы вам не приходилось подключать их при помощи `use` вручную. + +Вы можете найти полезные прослойки в +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +или в [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Тестирование + +Тесты для Sinatra приложений могут быть написаны при помощи любых библиотек или +фреймворков, поддерживающих тестирование Rack. Разработчики гема Sinatra рекомендуют +использовать [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Примечание: если вы используете Sinatra в модульном стиле, замените +`Sinatra::Application` в примере выше на имя класса вашего приложения. + +## Sinatra::Base — "прослойки", библиотеки и модульные приложения + +Описание вашего приложения на верхнем уровне хорошо работает для микроприложений, +но имеет значительные недостатки при создании многоразовых компонентов, таких как +Rack "прослойка", Rails metal, простые библиотеки с серверным компонентом или +даже расширения Sinatra. Верхний уровень предполагает конфигурацию стиля +микроприложений (например, одиночный файл приложения, `./public` и `./views`, +каталоги, логирование, страницу подробных сведений об исключениях и т.д.). +И тут на помощь приходит `Sinatra::Base`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +Методы, доступные подклассам `Sinatra::Base` идентичны тем, что доступны +приложениям при помощи DSL верхнего уровня. Большинство таких приложений могут быть +конвертированы в `Sinatra::Base` компоненты при помощи двух модификаций: + +* Вы должны подключать `sinatra/base` вместо `sinatra`, иначе все DSL методы, + предоставляемые Sinatra, будут импортированы в глобальное пространство + имён. +* Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс + `Sinatra::Base`. + +`Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный +сервер, по умолчанию отключены. Смотрите +[Опции и конфигурация](http://www.sinatrarb.com/configuration.html) +для детальной информации об опциях и их поведении. Если вы хотите, чтобы +поведение было более похоже на то, когда вы определяете своё приложение +на верхнем уровне (также известно как классический стиль), вы можете +унаследоваться от `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Модульные приложения против классических + +Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего +плохого. Если этот стиль подходит вашему приложению, вы не обязаны +переписывать его в модульное приложение. + +Основным недостатком классического стиля является тот факт, что у вас может +быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете +использовать больше, переключайтесь на модульный стиль. Вы можете смело +смешивать модульный и классический стили. + +Переходя с одного стиля на другой, примите во внимание следующие изменения в +настройках: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ОпцияКлассическийМодульныйМодульный
    app_fileфайл с приложениемфайл с подклассом Sinatra::Baseфайл с подклассом Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Запуск модульных приложений + +Есть два общепринятых способа запускать модульные приложения: запуск напрямую +при помощи `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... здесь код приложения ... + + # запускаем сервер, если исполняется текущий файл + run! if app_file == $0 +end +``` + +Затем: + +```shell +ruby my_app.rb +``` + +Или при помощи конфигурационного файла `config.ru`, который позволяет +использовать любой Rack-совместимый сервер приложений: + +```ruby +# config.ru (запускается при помощи Rackup) +require './my_app' +run MyApp +``` + +Запускаем: + +```shell +rackup -p 4567 +``` + +### Запуск классических приложений с config.ru + +Файл приложения: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +И соответствующий `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### Когда использовать config.ru? + +Использование файла `config.ru` рекомендовано если: + +* вы хотите разворачивать своё приложение на различных Rack-совместимых + серверах (Passenger, Unicorn, Heroku, ...); +* вы хотите использовать более одного подкласса `Sinatra::Base`; +* вы хотите использовать Sinatra только в качестве "прослойки" Rack. + +**Совсем необязательно переходить на использование `config.ru` лишь потому, +что вы стали использовать модульный стиль приложения. И необязательно +использовать модульный стиль, чтобы запускать приложение при помощи +`config.ru`.** + +### Использование Sinatra в качестве "прослойки" + +Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra +приложение само может быть добавлено к любому Rack endpoint в качестве +"прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra +приложение или любое другое приложение, основанное на Rack (Rails/Hamami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # "прослойка" будет запущена перед фильтрами + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Создание приложений "на лету" + +Иногда требуется создавать Sinatra приложения "на лету" (например, из другого +приложения), не сохраняя их в константу. Это возможно при помощи `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +Этот метод может принимать аргументом приложение, от которого следует +наследоваться: + +```ruby +# config.ru (запускается при помощи Rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Это особенно полезно для тестирования расширений Sinatra и при использовании +Sinatra внутри вашей библиотеки. + +Это также значительно упрощает использование Sinatra в качестве прослойки: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Области видимости и привязка + +Текущая область видимости определяет методы и переменные, доступные в данный +момент. + +### Область видимости приложения / класса + +Любое Sinatra приложение соответствует подклассу `Sinatra::Base`. Если вы +используете DSL верхнего уровня (`require 'sinatra'`), то этим классом будет +`Sinatra::Application`, иначе это будет подкласс, который вы создали вручную. +На уровне класса вам будут доступны такие методы, как `get` или `before`, но +вы не сможете получить доступ к объектам `request` или `session`, так как +существует только один класс приложения для всех запросов. + +Опции, созданные при помощи `set`, являются методами уровня класса: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Я больше не в области видимости приложения! + end +end +``` + +У вас будет привязка к области видимости приложения внутри: + +* тела вашего класса приложения; +* методов, определённых расширениями; +* блока, переданного в `helpers`; +* блоков, использованных как значения для `set`; +* блока, переданного в `Sinatra.new`. + +Вы можете получить доступ к объекту области видимости (классу приложения) +следующими способами: + +* через объект, переданный блокам конфигурации (`configure { |c| ... }`); +* `settings` внутри области видимости запроса. + +### Область видимости запроса / экземпляра + +Для каждого входящего запроса будет создан новый экземпляр вашего приложения, +и все блоки обработчика будут запущены в этом контексте. В этой области +видимости вам доступны `request` и `session` объекты, а также вызовы методов +рендеринга, такие как `erb` или `haml`. Вы можете получить доступ к области +видимости приложения из контекста запроса используя метод-помощник +`settings`: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + get '/define_route/:name' do + # Область видимости запроса '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Область видимости запроса "/#{params['name']}" + @value # => nil (другой запрос) + end + + "Route defined!" + end +end +``` + +У вас будет привязка к области видимости запроса в: + +* get, head, post, put, delete, options, patch, link и unlink блоках; +* before и after фильтрах; +* методах-помощниках; +* шаблонах/отображениях. + +### Область видимости делегирования + +Область видимости делегирования просто перенаправляет методы в область +видимости класса. Однако, она не полностью ведет себя как область видимости +класса, так как у вас нет привязки к классу. Только методы, явно помеченные +для делегирования, будут доступны, а переменных/состояний области видимости +класса не будет (иначе говоря, у вас будет другой `self` объект). Вы можете +непосредственно добавить методы делегирования, используя +`Sinatra::Delegator.delegate :method_name`. + +У вас будет контекст делегирования внутри: + +* привязки верхнего уровня, если вы сделали `require "sinatra"`; +* объекта, расширенного при помощи `Sinatra::Delegator`. + +Посмотрите сами в код: вот +[примесь Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +[расширяет главный объект](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Командная строка + +Sinatra приложения могут быть запущены напрямую: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Поддерживаемые опции: + +``` +-h # раздел помощи +-p # указание порта (по умолчанию 4567) +-o # указание хоста (по умолчанию 0.0.0.0) +-e # указание окружения, режима (по умолчанию development) +-s # указание rack сервера/обработчика (по умолчанию thin) +-q # включить тихий режим для сервера (по умолчанию выключен) +-x # включить мьютекс-блокировку (по умолчанию выключена) +``` + +### Многопоточность + +_Данный раздел является перефразированным [ответом пользователя Konstantin](https://stackoverflow.com/a/6282999/5245129) на StackOverflow_ + +Sinatra не навязывает каких-либо моделей параллелизма, но для этих целей можно +использовать любой Rack обработчик (сервер), например Thin, Puma или WEBrick. Сама +по себе Sinatra потокобезопасна, поэтому нет никаких проблем в использовании +поточной модели параллелизма в Rack обработчике. Это означает, что когда +запускается сервер, вы должны указать правильный метод вызова для конкретного +Rack обработчика. Пример ниже показывает, как можно запустить мультипоточный +Thin сервер: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +Для запуска сервере выполните следующую команду: + +```shell +thin --threaded start +``` + +## Системные требования + +Следующие версии Ruby официально поддерживаются: +
    +
    Ruby 2.3
    +
    + Версия 2.3 полностью поддерживается и рекомендуется. В настоящее время нет + планов отказаться от официальной поддержки. +
    + +
    Rubinius
    +
    + Rubinius официально поддерживается (Rubinius >= 2.x). Рекомендуется + выполнить gem install puma. +
    + +
    JRuby
    +
    + Официально поддерживается последний стабильный релиз JRuby. Не + рекомендуется использовать расширений на C в JRuby. Рекомендуется + выполнить gem install trinidad. +
    +
    + +Версии Ruby ниже 2.2.2 более не поддерживаются в Sinatra 2.0. + +Мы также следим за предстоящими к выходу версиями Ruby. + +Следующие реализации Ruby не поддерживаются официально, однако известно, что +на них запускается Sinatra: + +* старые версии JRuby и Rubinius; +* Ruby Enterprise Edition; +* MacRuby, Maglev, IronRuby; +* Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию). + +То, что версия официально не поддерживается, означает, что, если что-то не +работает на этой версии, а на поддерживаемой работает — это не наша проблема, +а их. + +Мы также запускаем наши CI-тесты на ruby-head (будущие версии MRI), но мы не +можем ничего гарантировать, так как ведётся постоянная разработка. +Предполагается, что предстоящие релизы 2.x будут полностью поддерживаться. + +Sinatra должна работать на любой операционной системе, в которой есть одна из +указанных выше версий Ruby. + +Если вы запускаете MacRuby, вы должны выполнить `gem install control_tower`. + +Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой +версии Ruby ниже 2.2. + +## Самая свежая версия + +Если вы хотите использовать самый последний релиз Sinatra, не бойтесь запускать +своё приложение вместе с кодом из master ветки Sinatra, так как она весьма стабильна. + +Мы также время от времени выпускаем предварительные версии, которые вы можете +установить следующим образом: + +```shell +gem install sinatra --pre +``` + +таким образом вы сможете воспользоваться некоторыми самыми последними возможностями. + +### При помощи Bundler + +Если вы хотите запускать своё приложение с последней версией Sinatra, то +рекомендуем использовать [Bundler](http://bundler.io). + +Для начала установите Bundler, если у вас его ещё нет: + +```shell +gem install bundler +``` + +Затем создайте файл `Gemfile` в директории вашего проекта: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# другие зависимости +gem 'haml' # в том случае, если используете haml +``` + +Имейте ввиду, что вам необходимо будет указывать все зависимости вашего приложения +в `Gemfile`. Необходимые зависимости самой библиотеки Sinatra (Rack и Tilt) +будут автоматически скачаны и добавлены Bundler. + +Теперь вы можете запускать своё приложение следующим образом: + +```shell +bundle exec ruby myapp.rb +``` + +## Версии + +Sinatra использует [Semantic Versioning](https://semver.org/): как SemVer, так и +SemVerTag. + +## Дальнейшее чтение + +* [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная + документация, новости и ссылки на другие ресурсы. +* [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили + баг? Нужна помощь? Написали патч? +* [Отслеживание проблем/ошибок](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Группы рассылки](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) на [Freenode](https://freenode.net) +* [Sinatra и Друзья](https://sinatrarb.slack.com) на Slack, а так же + [ссылка для инвайта](https://sinatra-slack.herokuapp.com/). +* [Sinatra Book](https://github.com/sinatra/sinatra-book) учебник и сборник рецептов +* [Sinatra Recipes](http://recipes.sinatrarb.com/) сборник рецептов +* API документация к [последнему релизу](http://www.rubydoc.info/gems/sinatra) + или [текущему HEAD](http://www.rubydoc.info/github/sinatra/sinatra) на + http://www.rubydoc.info/ +* [Сервер непрерывной интеграции](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.zh.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.zh.md new file mode 100644 index 0000000000..483ab25690 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/README.zh.md @@ -0,0 +1,2934 @@ +# Sinatra + +*注:本文档是英文版的翻译,内容更新有可能不及时。如有不一致的地方,请以英文版为准。* + +Sinatra 是一门基于 +Ruby 的[领域专属语言](https://en.wikipedia.org/wiki/Domain-specific_language),致力于轻松、快速地创建网络应用: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +安装 Sinatra 这个 gem: + +```shell +gem install sinatra +``` + +然后运行 myapp.rb 中的代码: + +```shell +ruby myapp.rb +``` + +在该地址查看: [http://localhost:4567](http://localhost:4567) + +推荐运行 `gem install thin` 安装 Thin。这样,Sinatra 会优先选择 Thin 作为服务器。 + +## 目录 + +* [Sinatra](#sinatra) + * [目录](#目录) + * [路由](#路由) + * [条件](#条件) + * [返回值](#返回值) + * [自定义路由匹配器](#自定义路由匹配器) + * [静态文件](#静态文件) + * [视图 / 模板](#视图--模板) + * [字面量模板](#字面量模板) + * [可选的模板语言](#可选的模板语言) + * [Haml 模板](#haml-模板) + * [Erb 模板](#erb-模板) + * [Builder 模板](#builder-模板) + * [Nokogiri 模板](#nokogiri-模板) + * [Sass 模板](#sass-模板) + * [SCSS 模板](#scss-模板) + * [Less 模板](#less-模板) + * [Liquid 模板](#liquid-模板) + * [Markdown 模板](#markdown-模板) + * [Textile 模板](#textile-模板) + * [RDoc 模板](#rdoc-模板) + * [AsciiDoc 模板](#asciidoc-模板) + * [Radius 模板](#radius-模板) + * [Markaby 模板](#markaby-模板) + * [RABL 模板](#rabl-模板) + * [Slim 模板](#slim-模板) + * [Creole 模板](#creole-模板) + * [MediaWiki 模板](#mediawiki-模板) + * [CoffeeScript 模板](#coffeescript-模板) + * [Stylus 模板](#stylus-模板) + * [Yajl 模板](#yajl-模板) + * [WLang 模板](#wlang-模板) + * [在模板中访问变量](#在模板中访问变量) + * [带 `yield` 的模板和嵌套布局](#带-yield-的模板和嵌套布局) + * [内联模板](#内联模板) + * [具名模板](#具名模板) + * [关联文件扩展名](#关联文件扩展名) + * [添加自定义模板引擎](#添加自定义模板引擎) + * [自定义模板查找逻辑](#自定义模板查找逻辑) + * [过滤器](#过滤器) + * [辅助方法](#辅助方法) + * [使用会话](#使用会话) + * [中断请求](#中断请求) + * [传递请求](#传递请求) + * [触发另一个路由](#触发另一个路由) + * [设置响应主体、状态码和响应首部](#设置响应主体状态码和响应首部) + * [响应的流式传输](#响应的流式传输) + * [日志](#日志) + * [媒体类型](#媒体类型) + * [生成 URL](#生成-url) + * [浏览器重定向](#浏览器重定向) + * [缓存控制](#缓存控制) + * [发送文件](#发送文件) + * [访问请求对象](#访问请求对象) + * [附件](#附件) + * [处理日期和时间](#处理日期和时间) + * [查找模板文件](#查找模板文件) + * [配置](#配置) + * [配置攻击防护](#配置攻击防护) + * [可选的设置](#可选的设置) + * [环境](#环境) + * [错误处理](#错误处理) + * [未找到](#未找到) + * [错误](#错误) + * [Rack 中间件](#rack-中间件) + * [测试](#测试) + * [Sinatra::Base - 中间件、库和模块化应用](#sinatrabase---中间件库和模块化应用) + * [模块化风格 vs. 经典风格](#模块化风格-vs-经典风格) + * [运行一个模块化应用](#运行一个模块化应用) + * [使用 config.ru 运行经典风格的应用](#使用-configru-运行经典风格的应用) + * [何时使用 config.ru?](#何时使用-configru) + * [把 Sinatra 当作中间件使用](#把-sinatra-当作中间件使用) + * [创建动态应用](#创建动态应用) + * [作用域和绑定](#作用域和绑定) + * [应用/类作用域](#应用类作用域) + * [请求/实例作用域](#请求实例作用域) + * [代理作用域](#代理作用域) + * [命令行](#命令行) + * [多线程](#多线程) + * [必要条件](#必要条件) + * [紧跟前沿](#紧跟前沿) + * [通过 Bundler 使用 Sinatra](#通过-bundler-使用-sinatra) + * [使用自己本地的 Sinatra](#使用自己本地的-sinatra) + * [全局安装](#全局安装) + * [版本](#版本) + * [更多资料](#更多资料) + +## 路由 + +在 Sinatra 中,一个路由分为两部分:HTTP 方法和 URL 匹配范式。每个路由都有一个要执行的代码块: + +```ruby +get '/' do + .. 显示内容 .. +end + +post '/' do + .. 创建内容 .. +end + +put '/' do + .. 替换内容 .. +end + +patch '/' do + .. 修改内容 .. +end + +delete '/' do + .. 删除内容 .. +end + +options '/' do + .. 显示命令列表 .. +end + +link '/' do + .. 建立某种联系 .. +end + +unlink '/' do + .. 解除某种联系 .. +end +``` + +路由按照它们定义时的顺序进行匹配。第一个与请求匹配的路由会被调用。 + +路由范式可以包括具名参数,具名参数可以通过 `params` hash 访问: + +```ruby +get '/hello/:name' do + # 匹配 "GET /hello/foo" 和 "GET /hello/bar" + # params['name'] 的值是 'foo' 或者 'bar' + "Hello #{params['name']}!" +end +``` + +也可以通过代码块参数访问具名参数: + +```ruby +get '/hello/:name' do |n| + # 匹配 "GET /hello/foo" 和 "GET /hello/bar" + # params['name'] 的值是 'foo' 或者 'bar' + # n 存储 params['name'] 的值 + "Hello #{n}!" +end +``` + +路由范式也可以包含通配符参数, 参数值可以通过 `params['splat']` 数组访问。 + +```ruby +get '/say/*/to/*' do + # 匹配 "GET /say/hello/to/world" + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # 匹配 "GET /download/path/to/file.xml" + params['splat'] # => ["path/to/file", "xml"] +end +``` + +或者通过代码块参数访问: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +通过正则表达式匹配路由: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +或者使用代码块参数: + +```ruby +get %r{/hello/([\w]+)} do |c| + # 匹配 "GET /meta/hello/world"、"GET /hello/world/1234" 等 + "Hello, #{c}!" +end +``` + +路由范式可以包含可选参数: + +```ruby +get '/posts/:format?' do + # 匹配 "GET /posts/" 和任意扩展 "GET /posts/json"、"GET /posts/xml" 等 +end +``` + +路由也可以使用查询参数: + +```ruby +get '/posts' do + # 匹配 "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # 使用 title 和 author 变量;对于 /posts 路由来说,查询字符串是可选的 +end +``` +顺便一提,除非你禁用了路径遍历攻击防护(见下文),请求路径可能在匹配路由前发生改变。 + +你也可以通过`:mustermann_opt`选项定义[Mustermann](https://github.com/sinatra/mustermann)来匹配路由。 + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` + +它看起来像一个[条件](https://github.com/sinatra/sinatra/blob/master/README.zh.md#%E6%9D%A1%E4%BB%B6),但实际不是!这些选项将被合并到全局的`mustermann_opts`。 + +### 条件 + +路由可以包含各种匹配条件,比如 user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "你正在使用 Songbird,版本是 #{params['agent'][0]}" +end + +get '/foo' do + # 匹配非 Songbird 浏览器 +end +``` + +其它可以使用的条件有 `host_name` 和 `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "管理员区域,无权进入!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` 会搜索请求的 Accept 首部字段。 + +也可以轻易地使用自定义条件: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +对于一个需要提供多个值的条件,可以使用 splat: + +```ruby +set(:auth) do |*roles| # <- 注意此处使用了 splat + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +### 返回值 + +路由代码块的返回值至少决定了返回给 +HTTP 客户端的响应主体,或者至少决定了在 +Rack 堆栈中的下一个中间件。大多数情况下,返回值是一个字符串,就像上面的例子中的一样。但是,其它类型的值也是可以接受的。 + +你可以返回任何对象,该对象要么是一个合理的 Rack 响应,要么是一个 Rack body 对象,要么是 HTTP 状态码: + +* 一个包含三个元素的数组: `[状态 (Integer), 响应首部 (Hash), 响应主体 (可以响应 #each 方法)]` +* 一个包含两个元素的数组: `[状态 (Integer), 响应主体 (可以响应 #each 方法)]` +* 一个响应 `#each` 方法,只传回字符串的对象 +* 一个代表状态码的数字 + +例如,我们可以轻松地实现流式传输: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +也可以使用 `stream` 辅助方法(见下文描述)以减少样板代码并在路由中直接使用流式传输。 + +### 自定义路由匹配器 + +如上文所示,Sinatra +本身支持使用字符串和正则表达式作为路由匹配。但不限于此,你可以轻松地定义自己的匹配器: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +上面的例子可能太繁琐了, 因为它也可以用更简单的方式表述: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +或者,使用消极向前查找: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 静态文件 + +静态文件从 `./public` 目录提供服务。可以通过设置`:public_folder` 选项设定一个不同的位置: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +请注意 public 目录名并没有包含在 URL 中。文件 `./public/css/style.css` 可以通过 +`http://example.com/css/style.css` 访问。 + +可以使用 `:static_cache_control` 设置(见下文)添加 `Cache-Control` 首部信息。 + +## 视图 / 模板 + +每一门模板语言都将自身的渲染方法暴露给 +Sinatra 调用。这些渲染方法只是简单地返回字符串。 + +```ruby +get '/' do + erb :index +end +``` + +这段代码会渲染 `views/index.erb` 文件。 + +除了模板文件名,也可以直接传入模板内容: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +渲染方法接受第二个参数,即选项 hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +这段代码会将 `views/index.erb` 嵌入在 `views/post.erb` +布局中并一起渲染(`views/layout.erb` 是默认的布局,如果它存在的话)。 + +任何 Sinatra 不能理解的选项都会传递给模板引擎。 + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +也可以为每种模板语言设置通用的选项: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +在渲染方法中传入的选项会覆盖通过 `set` 设置的通用选项。 + +可用的选项: + +
    +
    locals
    +
    + 传递给模板文档的 locals 对象列表。对于 partials + 很方便。例如:erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    默认的字符编码。默认值为 settings.default_encoding
    + +
    views
    +
    存放模板文件的目录。默认为 settings.views
    + +
    layout
    +
    + 是否使用布局 (truefalse)。 + 如果使用一个符号类型的值,则是用于明确使用的模板。例如: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    由模板生成的 Content-Type。默认值由模板语言决定。
    + +
    scope
    +
    + 渲染模板时的作用域。默认值为应用类的实例对象。如果更改此项,实例变量和辅助方法将不可用。 +
    + +
    layout_engine
    +
    + 渲染布局所使用的模板引擎。用于不支持布局的模板语言。默认值为模板所使用的引擎。例如: + set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + 渲染布局的特殊选项。例如: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Sinatra 假定模板文件直接位于 `./views` 目录。要使用不同的视图目录: + +```ruby +set :views, settings.root + '/templates' +``` + + +需要牢记的一点是,你必须通过符号引用模板, 即使它们存放在子目录下 +(在这种情况下,使用 `:'subdir/template'` 或 `'subdir/template'.to_sym`)。 +如果你不使用符号,渲染方法会直接渲染你传入的任何字符串。 + +### 字面量模板 + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +这段代码直接渲染模板字符串。 + +### 可选的模板语言 + +一些语言有多种实现。为了确定使用哪种实现(以及保证线程安全),你应该首先引入该实现: + +```ruby +require 'rdiscount' # 或 require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml 模板 + + + + + + + + + + + + + + +
    依赖项haml
    文件扩展名.haml
    例子haml :index, :format => :html5
    + +#### Erb 模板 + + + + + + + + + + + + + + +
    依赖项 + erubis + 或 erb (Ruby 标准库中已经包含) +
    文件扩展名.erb, .rhtml or .erubis (仅用于 Erubis)
    例子erb :index
    + +#### Builder 模板 + + + + + + + + + + + + + + +
    依赖项 + builder +
    文件扩展名.builder
    例子builder { |xml| xml.em "hi" }
    + +`builder` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### Nokogiri 模板 + + + + + + + + + + + + + + +
    依赖项nokogiri
    文件扩展名.nokogiri
    例子nokogiri { |xml| xml.em "hi" }
    + +`nokogiri` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### Sass 模板 + + + + + + + + + + + + + + +
    依赖项sass
    文件扩展名.sass
    例子sass :stylesheet, :style => :expanded
    + +#### SCSS 模板 + + + + + + + + + + + + + + +
    依赖项sass
    文件扩展名.scss
    例子scss :stylesheet, :style => :expanded
    + +#### Less 模板 + + + + + + + + + + + + + + +
    依赖项less
    文件扩展名.less
    例子less :stylesheet
    + +#### Liquid 模板 + + + + + + + + + + + + + + +
    依赖项liquid
    文件扩展名.liquid
    例子liquid :index, :locals => { :key => 'value' }
    + +因为不能在 Liquid 模板中调用 Ruby 方法(除了 `yield`),你几乎总是需要传递 locals 对象给它。 + +#### Markdown 模板 + + + + + + + + + + + + + + +
    依赖项 + 下列任一: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    文件扩展名.markdown, .mkd and .md
    例子markdown :index, :layout_engine => :erb
    + +不能在 markdown 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +请注意你也可以在其它模板中调用 markdown 方法: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +因为不能在 Markdown 中使用 Ruby 语言,你不能使用 Markdown 书写的布局。 +不过,使用其它渲染引擎作为模板的布局是可能的,这需要通过传入 `:layout_engine` 选项。 + +#### Textile 模板 + + + + + + + + + + + + + + +
    依赖项RedCloth
    文件扩展名.textile
    例子textile :index, :layout_engine => :erb
    + +不能在 textile 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +请注意你也可以在其他模板中调用 `textile` 方法: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +因为不能在 Textile 中调用 Ruby 方法,你不能用 Textile 书写布局。 +不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### RDoc 模板 + + + + + + + + + + + + + + +
    依赖项RDoc
    文件扩展名.rdoc
    例子rdoc :README, :layout_engine => :erb
    + +不能在 rdoc 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +请注意你也可以在其他模板中调用 `rdoc` 方法: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +因为不能在 RDoc 中调用 Ruby 方法,你不能用 RDoc 书写布局。 +不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### AsciiDoc 模板 + + + + + + + + + + + + + + +
    依赖项Asciidoctor
    文件扩展名.asciidoc, .adoc and .ad
    例子asciidoc :README, :layout_engine => :erb
    + +因为不能在 AsciiDoc 模板中直接调用 Ruby 方法,你几乎总是需要传递 locals 对象给它。 + +#### Radius 模板 + + + + + + + + + + + + + + +
    依赖项Radius
    文件扩展名.radius
    例子radius :index, :locals => { :key => 'value' }
    + +因为不能在 Radius 模板中直接调用 Ruby 方法,你几乎总是可以传递 locals 对象给它。 + +#### Markaby 模板 + + + + + + + + + + + + + + +
    依赖项Markaby
    文件扩展名.mab
    例子markaby { h1 "Welcome!" }
    + +`markaby` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### RABL 模板 + + + + + + + + + + + + + + +
    依赖项Rabl
    文件扩展名.rabl
    例子rabl :index
    + +#### Slim 模板 + + + + + + + + + + + + + + +
    依赖项Slim Lang
    文件扩展名.slim
    例子slim :index
    + +#### Creole 模板 + + + + + + + + + + + + + + +
    依赖项Creole
    文件扩展名.creole
    例子creole :wiki, :layout_engine => :erb
    + +不能在 creole 中调用 Ruby 方法,也不能传递 locals 对象给它。 +因此你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +注意你也可以在其它模板内调用 `creole` 方法: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +因为不能在 Creole 模板文件内调用 Ruby 方法,你不能用 Creole 书写布局文件。 +然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### MediaWiki 模板 + + + + + + + + + + + + + + +
    依赖项WikiCloth
    文件扩展名.mediawiki and .mw
    例子mediawiki :wiki, :layout_engine => :erb
    + +在 MediaWiki 标记文件内不能调用 Ruby 方法,也不能传递 locals 对象给它。 +因此你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +注意你也可以在其它模板内调用 `mediawiki` 方法: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +因为不能在 MediaWiki 文件内调用 Ruby 方法,你不能用 MediaWiki 书写布局文件。 +然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### CoffeeScript 模板 + + + + + + + + + + + + + + +
    依赖项 + + CoffeeScript + 以及一种 + + 执行 JavaScript 的方式 + +
    文件扩展名.coffee
    例子coffee :index
    + +#### Stylus 模板 + + + + + + + + + + + + + + +
    依赖项 + + Stylus + 以及一种 + + 执行 JavaScript 的方式 + +
    文件扩展名.styl
    例子stylus :index
    + +在使用 Stylus 模板之前,你需要先加载 `stylus` 和 `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl 模板 + + + + + + + + + + + + + + +
    依赖项yajl-ruby
    文件扩展名.yajl
    例子 + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +模板文件的源码作为一个 Ruby 字符串被求值,得到的 json 变量是通过 `#to_json` 方法转换的: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +可以使用 `:callback` 和 `:variable` 选项装饰被渲染的对象: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang 模板 + + + + + + + + + + + + + + +
    依赖项WLang
    文件扩展名.wlang
    例子wlang :index, :locals => { :key => 'value' }
    + +因为在 WLang 中调用 Ruby 方法不符合语言习惯,你几乎总是需要传递 locals 给 WLang 木板。 +然而,可以用 WLang 编写布局文件,也可以在 WLang 中使用 `yield` 方法。 + +### 在模板中访问变量 + +模板的求值发生在路由处理器内部的上下文中。模板可以直接访问路由处理器中设置的实例变量。 + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +或者,也可以显式地指定一个由局部变量组成的 locals 哈希: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } +end +``` + +locals 哈希典型的使用情景是在别的模板中渲染 partials。 + +### 带 `yield` 的模板和嵌套布局 + +布局通常就是使用了 `yield` 方法的模板。 +这样的布局文件可以通过上面描述的 `:template` 选项指定,也可以通过下面的代码块渲染: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +这段代码几乎完全等同于 `erb :index, :layout => :post`。 + +向渲染方法传递代码块对于创建嵌套布局是最有用的: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +代码行数可以更少: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +当前,以下的渲染方法接受一个代码块:`erb`、`haml`、`liquid`、`slim ` 和 `wlang`。 +通用的 `render` 方法也接受。 + +### 内联模板 + +模板可以在源文件的末尾定义: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +注意:在引入了 sinatra 的源文件中定义的内联模板会自动载入。 +如果你在其他源文件中也有内联模板,需要显式调用 `enable :inline_templates`。 + +### 具名模板 + +可以使用顶层 `template` 方法定义模板: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +如果存在名为 “layout” 的模板,该模板会在每个模板渲染的时候作为布局使用。 +你可以为渲染方法传送 `:layout => false` 来禁用该次渲染的布局, +也可以设置 `set :haml, :layout => false` 来默认禁用布局。 + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### 关联文件扩展名 + +为了将一个文件扩展名到对应的模版引擎,要使用 `Tilt.register`。 +比如,如果你喜欢使用 `tt` 作为 Textile 模版的扩展名,你可以这样做: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### 添加自定义模板引擎 + +首先,通过 Tilt 注册你自定义的引擎,然后创建一个渲染方法: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +这段代码将会渲染 `./views/index.myat` 文件。 +查看 https://github.com/rtomayko/tilt 以了解更多关于 Tilt 的信息。 + +### 自定义模板查找逻辑 + +要实现自定义的模板查找机制,你可以构建自己的 `#find_template` 方法: + +```ruby +configure do + set :views, [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## 过滤器 + +`before` 过滤器在每个请求之前调用,调用的上下文与请求的上下文相同,并且可以修改请求和响应。 +在过滤器中设置的变量可以被路由和模板访问: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +`after` 过滤器在每个请求之后调用,调用上下文与请求的上下文相同,并且也会修改请求和响应。 +在 `before` 过滤器和路由中设置的实例变量可以被 `after` 过滤器访问: + +```ruby +after do + puts response.status +end +``` + +请注意:除非你显式使用 `body` 方法,而不是在路由中直接返回字符串, +响应主体在 `after` 过滤器是不可访问的, 因为它在之后才会生成。 + +过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +和路由一样,过滤器也可以带有条件: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## 辅助方法 + +使用顶层的 `helpers` 方法来定义辅助方法, 以便在路由处理器和模板中使用: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +也可以在多个分散的模块中定义辅助方法: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +以上代码块与在应用类中包含模块等效。 + +### 使用会话 + +会话用于在请求之间保持状态。如果激活了会话,每一个用户会话都对应一个会话 hash: + +```ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### 会话加密 + +为提高安全性,cookie 中的会话数据使用`HMAC-SHA1`进行加密。会话加密的最佳实践应当是像`HMAC-SHA1`这样生成大于或等于64字节 (512 bits, 128 hex characters)的随机值。应当避免使用少于32字节(256 bits, 64 hex characters)的随机值。应当使用生成器来创建安全的密钥,而不是拍脑袋决定。 + +默认情况下,Sinatra会生成一个32字节的密钥,但随着应用程序的每次重新启动,它都会发生改变。如果有多个应用程序的实例,使用Sinatra生成密钥,每个实例将有不同的密钥,这可能不是您想要的。 + +为了更好的安全性和可用性,[建议](https://12factor.net/config)生成安全的随机密钥,并将其存储在运行应用程序的每个主机上的环境变量中,以便所有应用程序实例都将共享相同的密钥。并且应该定期更新会话密钥。下面是一些创建64比特密钥的例子: + +#### 生成密钥 + +```ruby +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +#### 生成密钥(小贴士) + +MRI Ruby目前认为[sysrandom gem](https://github.com/cryptosphere/sysrandom)使用系统的随机数生成器要比用户态的`OpenSSL`好。 + +```ruby +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +#### 从环境变量使用密钥 + +将Sinatra的SESSION_SECRET环境变量设置为生成的值。在主机的重新启动之间保存这个值。由于这样做的方法会因系统而异,仅供说明之用: +``` +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +#### 应用的密钥配置 + +如果SESSION SECRET环境变量不可用,将把应用的随机密钥设置为不安全的。 + +关于[sysrandom gem](https://github.com/cryptosphere/sysrandom)的更多用法: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### 会话配置 + +如果你想进一步配置会话,可以在设置 `sessions` 时提供一个选项 hash 作为第二个参数: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +为了在 foo.com 的子域名间共享会话数据,可以在域名前添加一个 *.*: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### 选择你自己的会话中间件 + +请注意 `enable :sessions` 实际将所有的数据保存在一个 cookie 中。 +这可能并不总是你想要的(cookie 中存储大量的数据会增加你的流量)。 +你可以使用任何 Rack session 中间件:要达到此目的,**不要**使用 `enable :sessions`, +而是按照自己的需要引入想使用的中间件: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +另一种选择是不要调用enable:sessions,而是像你想要的其他中间件一样加入你的中间件。 + +重要的是要注意,使用此方法时,默认情况下不会启用基于会话的保护。 + +还需要添加Rack中间件: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` +更多[安全防护配置](https://github.com/sinatra/sinatra#configuring-attack-protection)的信息。 + +### 中断请求 + +要想在过滤器或路由中立即中断一个请求: + +```ruby +halt +``` + +你也可以指定中断时的状态码: + +```ruby +halt 410 +``` + +或者响应主体: + +```ruby +halt 'this will be the body' +``` + +或者同时指定两者: + +```ruby +halt 401, 'go away!' +``` + +也可以指定响应首部: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +当然也可以使用模板: + +``` +halt erb(:error) +``` + +### 传递请求 + +一个路由可以放弃对请求的处理并将处理让给下一个匹配的路由,这要通过 `pass` 实现: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +执行 `pass` 后,控制流从该路由代码块直接退出,并继续前进到下一个匹配的路由。 +如果没有匹配的路由,将返回 404。 + +### 触发另一个路由 + +有些时候,`pass` 并不是你想要的,你希望得到的是调用另一个路由的结果。 +使用 `call` 就可以做到这一点: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +请注意在以上例子中,你只需简单地移动 `"bar"` 到一个被 `/foo` 和 `/bar` 同时使用的辅助方法中, +就可以简化测试和增加性能。 + +如果你希望请求发送到同一个应用,而不是应用副本,应使用 `call!` 而不是 `call`。 + +如果想更多了解关于 `call` 的信息,请查看 Rack 规范。 + +### 设置响应主体、状态码和响应首部 + +推荐在路由代码块的返回值中设定状态码和响应主体。 +但是,在某些场景下你可能想在别处设置响应主体,这时你可以使用 `body` 辅助方法。 +设置之后,你可以在那以后使用该方法访问响应主体: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +也可以传递一个代码块给 `body` 方法, +它会被 Rack 处理器执行(这可以用来实现流式传输,参见“返回值”)。 + +与响应主体类似,你也可以设定状态码和响应首部: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +正如 `body` 方法,不带参数调用 `headers` 和 `status` 方法可以访问它们的当前值。 + +### 响应的流式传输 + +有时你可能想在完全生成响应主体前返回数据。 +更极端的情况是,你希望在客户端关闭连接前一直发送数据。 +为满足这些需求,可以使用 `stream` 辅助方法而不必重新造轮子: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +`stream` 辅助方法允许你实现流式 API 和 +[服务器端发送事件](https://w3c.github.io/eventsource/), +同时它也是实现 [WebSockets](https://en.wikipedia.org/wiki/WebSocket) 的基础。 +如果你应用的部分(不是全部)内容依赖于访问缓慢的资源,它也可以用来提高并发能力。 + +请注意流式传输,尤其是并发请求数,高度依赖于应用所使用的服务器。 +一些服务器可能根本不支持流式传输。 +如果服务器不支持,传递给 `stream` 方法的代码块执行完毕之后,响应主体会一次性地发送给客户端。 +Shotgun 完全不支持流式传输。 + +如果 `:keep_open` 作为可选参数传递给 `stream` 方法,将不会在流对象上调用 `close` 方法, +这允许你在控制流的下游某处手动关闭。该参数只对事件驱动的服务器(如 Thin 和 Rainbows)生效。 +其它服务器仍会关闭流式传输: + +```ruby +# 长轮询 + +set :server, :thin +connections = [] + +get '/subscribe' do + # 在服务器端的事件中注册客户端 + stream(:keep_open) do |out| + connections << out + # 清除关闭的连接 + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # 通知客户端有条新消息 + out << params['message'] << "\n" + + # 使客户端重新连接 + out.close + end + + # 确认 + "message received" +end +``` + +### 日志 + +在请求作用域下,`logger` 辅助方法会返回一个 `Logger` 类的实例: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +该 `logger` 方法会自动参考 Rack 处理器的日志设置。 +若日志被禁用,该方法会返回一个无关痛痒的对象,所以你完全不必担心这会影响路由和过滤器。 + +注意只有 `Sinatra::Application` 默认开启了日志,若你的应用继承自 `Sinatra::Base`, +很可能需要手动开启: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +为避免使用任何与日志有关的中间件,需要将 `logging` 设置项设为 `nil`。 +然而,在这种情况下,`logger` 辅助方法会返回 `nil`。 +一种常见的使用场景是你想要使用自己的日志工具。 +Sinatra 会使用 `env['rack.logger']` 的值作为日志工具,无论该值是什么。 + +### 媒体类型 + +使用 `send_file` 或者静态文件的时候,Sinatra 可能不会识别你的媒体类型。 +使用 `mime_type` 通过文件扩展名来注册媒体类型: + +```ruby +mime_type :foo, 'text/foo' +``` + +你也可以使用 `content_type` 辅助方法: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### 生成 URL + +为了生成 URL,你应当使用 `url` 辅助方法,例如,在 Haml 中: + +```ruby +%a{:href => url('/foo')} foo +``` + +如果使用了反向代理和 Rack 路由,生成 URL 的时候会考虑这些因素。 + +这个方法还有一个别名 `to` (见下面的例子)。 + +### 浏览器重定向 + +你可以通过 `redirect` 辅助方法触发浏览器重定向: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +其他参数的用法,与 `halt` 相同: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +用 `redirect back` 可以把用户重定向到原始页面: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +如果想传递参数给 redirect,可以用查询字符串: + +```ruby +redirect to('/bar?sum=42') +``` + +或者使用会话: + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### 缓存控制 + +正确设置响应首部是合理利用 HTTP 缓存的基础。 + +可以这样设定 Cache-Control 首部字段: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +核心提示: 应当在 `before` 过滤器中设定缓存。 + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +如果你使用 `expires` 辅助方法设定响应的响应首部, 会自动设定 `Cache-Control` 字段: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +为了合理使用缓存,你应该考虑使用 `etag` 或 `last_modified` 方法。 +推荐在执行繁重任务*之前*使用这些辅助方法,这样一来, +如果客户端在缓存中已经有相关内容,就会立即得到响应: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +也可以使用 [weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +这些辅助方法并不会为你做任何缓存,而是将必要的信息发送给你的缓存。 +如果你正在寻找快捷的反向代理缓存方案,可以尝试 +[rack-cache](https://github.com/rtomayko/rack-cache): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +使用 `:statis_cache_control` 设置(见下文)为静态文件添加 `Cache-Control` 首部字段。 + +根据 RFC 2616,如果 If-Match 或 If-None-Match 首部设置为 `*`,根据所请求的资源存在与否, +你的应用应当有不同的行为。 +Sinatra 假设安全请求(如 GET)和幂等性请求(如 PUT)所访问的资源是已经存在的, +而其它请求(如 POST 请求)所访问的资源是新资源。 +你可以通过传入 `:new_resource` 选项改变这一行为。 + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +如果你仍想使用 weak ETag,可以传入一个 `:kind` 选项: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### 发送文件 + +为了将文件的内容作为响应返回,可以使用 `send_file` 辅助方法: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +该辅助方法接受一些选项: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +可用的选项有: + +
    +
    filename
    +
    响应中使用的文件名,默认是真实的文件名。
    + +
    last_modified
    +
    Last-Modified 响应首部的值,默认是文件的 mtime (修改时间)。
    + +
    type
    +
    Content-Type 响应首部的值,如果未指定,会根据文件扩展名猜测。
    + +
    disposition
    +
    + Content-Disposition 响应首部的值, + 可选的值有: nil (默认)、:attachment 和 + :inline +
    + +
    length
    +
    Content-Length 响应首部的值,默认是文件的大小。
    + +
    status
    +
    + 将要返回的状态码。当以一个静态文件作为错误页面时,这很有用。 + + 如果 Rack 处理器支持的话,Ruby 进程也能使用除 streaming 以外的方法。 + 如果你使用这个辅助方法, Sinatra会自动处理 range 请求。 +
    +
    + +### 访问请求对象 + +传入的请求对象可以在请求层(过滤器、路由、错误处理器内部)通过 `request` 方法访问: + +```ruby +# 在 http://example.com/example 上运行的应用 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # 客户端设定的请求主体(见下文) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body 的长度 + request.media_type # request.body 的媒体类型 + request.host # "example.com" + request.get? # true (其它动词也具有类似方法) + request.form_data? # false + request["some_param"] # some_param 参数的值。[] 是访问 params hash 的捷径 + request.referrer # 客户端的 referrer 或者 '/' + request.user_agent # 用户代理 (:agent 条件使用该值) + request.cookies # 浏览器 cookies 哈希 + request.xhr? # 这是否是 ajax 请求? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 客户端 IP 地址 + request.secure? # false (如果是 ssl 则为 true) + request.forwarded? # true (如果是运行在反向代理之后) + request.env # Rack 中使用的未处理的 env hash +end +``` + +一些选项,例如 `script_name` 或者 `path_info` 也是可写的: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body` 是一个 IO 或者 StringIO 对象: + +```ruby +post "/api" do + request.body.rewind # 如果已经有人读了它 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### 附件 + +你可以使用 `attachment` 辅助方法来告诉浏览器响应应当被写入磁盘而不是在浏览器中显示。 + +```ruby +get '/' do + attachment + "store it!" +end +``` + +你也可以传递给该方法一个文件名: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### 处理日期和时间 + +Sinatra 提供了一个 `time_for` 辅助方法,其目的是根据给定的值生成 Time 对象。 +该方法也能够转换 `DateTime`、`Date` 和类似的类: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +`expires`、`last_modified` 和类似方法都在内部使用了该方法。 +因此,通过在应用中重写 `time_for` 方法,你可以轻松地扩展这些方法的行为: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### 查找模板文件 + +`find_template` 辅助方法用于在渲染时查找模板文件: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +这其实并不是很有用,除非你需要重载这个方法来实现你自己的查找机制。 +比如,如果你想使用不只一个视图目录: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +另一个例子是对不同的引擎使用不同的目录: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +你可以很容易地封装成一个扩展,然后与他人分享! + +请注意 `find_template` 并不会检查文件是否存在,而是为任何可能的路径调用传入的代码块。 +这并不会导致性能问题,因为 `render` 会在找到文件的时候马上使用 `break`。 +同样的,模板的路径(和内容)会在 development 以外的模式下被缓存。 +你应该时刻提醒自己这一点, 如果你真的想写一个非常疯狂的方法的话。 + +## 配置 + +在启动时运行一次,在任何环境下都是如此: + +```ruby +configure do + # 设置一个选项 + set :option, 'value' + + # 设置多个选项 + set :a => 1, :b => 2 + + # 等同于 `set :option, true` + enable :option + + # 等同于 `set :option, false` + disable :option + + # 也可以用代码块做动态设置 + set(:css_dir) { File.join(views, 'css') } +end +``` + +只有当环境 (`APP_ENV` 环境变量) 被设定为 `:production` 时才运行: + +```ruby +configure :production do + ... +end +``` + +当环境被设定为 `:production` 或者 `:test` 时运行: + +```ruby +configure :production, :test do + ... +end +``` + +你可以用 `settings` 访问这些配置项: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 配置攻击防护 + +Sinatra 使用 [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +来抵御常见的攻击。你可以轻易地禁用该行为(但这会大大增加应用被攻击的概率)。 + +```ruby +disable :protection +``` + +为了绕过某单层防护,可以设置 `protection` 为一个选项 hash: + +```ruby +set :protection, :except => :path_traversal +``` + +你可以传入一个数组,以禁用一系列防护措施: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +默认地,如果 `:sessions` 是启用的,Sinatra 只会使用基于会话的防护措施。 +当然,有时你可能想根据自己的需要设置会话。 +在这种情况下,你可以通过传入 `:session` 选项来开启基于会话的防护。 + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 可选的设置 + +
    +
    absolute_redirects
    +
    + 如果被禁用,Sinatra 会允许使用相对路径重定向。 + 然而这样的话,Sinatra 就不再遵守 RFC 2616 (HTTP 1.1), 该协议只允许绝对路径重定向。 +
    +
    + 如果你的应用运行在一个未恰当设置的反向代理之后,你需要启用这个选项。 + 注意 url 辅助方法仍然会生成绝对 URL,除非你传入false 作为第二参数。 +
    +
    默认禁用。
    + +
    add_charset
    +
    + 设置 content_type 辅助方法会自动为媒体类型加上字符集信息。 + 你应该添加而不是覆盖这个选项: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + 主应用文件的路径,用来检测项目的根路径, views 和 public 文件夹和内联模板。 +
    + +
    bind
    +
    + 绑定的 IP 地址 (默认: 0.0.0.0,开发环境下为 localhost)。 + 仅对于内置的服务器有用。 +
    + +
    default_encoding
    +
    默认编码 (默认为 "utf-8")。
    + +
    dump_errors
    +
    在日志中显示错误。
    + +
    environment
    +
    + 当前环境,默认是 ENV['APP_ENV'], + 或者 "development" (如果 ENV['APP_ENV'] 不可用)。 +
    + +
    logging
    +
    使用 logger。
    + +
    lock
    +
    对每一个请求放置一个锁,只使用进程并发处理请求。
    +
    如果你的应用不是线程安全则需启动。默认禁用。
    + +
    method_override
    +
    + 使用 _method 魔法,以允许在不支持的浏览器中在使用 put/delete 方法提交表单。 +
    + +
    port
    +
    监听的端口号。只对内置服务器有用。
    + +
    prefixed_redirects
    +
    + 如果没有使用绝对路径,是否添加 request.script_name 到重定向请求。 + 如果添加,redirect '/foo' 会和 redirect to('/foo') 相同。 + 默认禁用。 +
    + +
    protection
    +
    是否启用网络攻击防护。参见上面的保护部分
    + +
    public_dir
    +
    public_folder 的别名。见下文。
    + +
    public_folder
    +
    + public 文件存放的路径。只有启用了静态文件服务(见下文的 static)才会使用。 + 如果未设置,默认从 app_file 推断。 +
    + +
    reload_templates
    +
    + 是否每个请求都重新载入模板。在开发模式下开启。 +
    + +
    root
    +
    到项目根目录的路径。默认从 app_file 设置推断。
    + +
    raise_errors
    +
    + 抛出异常(会停止应用)。 + 当 environment 设置为 "test" 时会默认开启,其它环境下默认禁用。 +
    + +
    run
    +
    如果启用,Sinatra 会负责 web 服务器的启动。若使用 rackup 或其他方式则不要启用。
    + +
    running
    +
    内置的服务器在运行吗? 不要修改这个设置!
    + +
    server
    +
    服务器,或用于内置服务器的服务器列表。顺序表明了优先级,默认顺序依赖 Ruby 实现。
    + +
    sessions
    +
    + 使用 Rack::Session::Cookie,启用基于 cookie 的会话。 + 查看“使用会话”部分以获得更多信息。 +
    + +
    show_exceptions
    +
    + 当有异常发生时,在浏览器中显示一个 stack trace。 + 当 environment 设置为 "development" 时,默认启用, + 否则默认禁用。 +
    +
    + 也可以设置为 :after_handler, + 这会在浏览器中显示 stack trace 之前触发应用级别的错误处理。 +
    + +
    static
    +
    决定 Sinatra 是否服务静态文件。
    +
    当服务器能够自行服务静态文件时,会禁用。
    +
    禁用会增强性能。
    +
    在经典风格中默认启用,在模块化应用中默认禁用。
    + +
    static_cache_control
    +
    + 当 Sinatra 提供静态文件服务时,设置此选项为响应添加 Cache-Control 首部。 + 使用 cache_control 辅助方法。默认禁用。 +
    +
    + 当设置多个值时使用数组: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + 若设置为 true,会告诉 Thin 使用 EventMachine.defer 处理请求。 +
    + +
    traps
    +
    Sinatra 是否应该处理系统信号。
    + +
    views
    +
    views 文件夹的路径。若未设置则会根据 app_file 推断。
    + +
    x_cascade
    +
    若没有路由匹配,是否设置 X-Cascade 首部。默认为 true
    +
    + +## 环境 + +Sinatra 中有三种预先定义的环境:"development"、"production" 和 "test"。 +环境可以通过 `APP_ENV` 环境变量设置。默认值为 "development"。 +在开发环境下,每次请求都会重新加载所有模板, +特殊的 `not_found` 和 `error` 错误处理器会在浏览器中显示 stack trace。 +在测试和生产环境下,模板默认会缓存。 + +在不同的环境下运行,设置 `APP_ENV` 环境变量: + +```shell +APP_ENV=production ruby my_app.rb +``` + +可以使用预定义的三种方法: `development?`、`test?` 和 `production?` 来检查当前环境: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development" + end +end +``` + +## 错误处理 + +错误处理器在与路由和 before 过滤器相同的上下文中运行, +这意味着你可以使用许多好东西,比如 `haml`, `erb`, `halt`,等等。 + +### 未找到 + +当一个 `Sinatra::NotFound` 错误被抛出时,或者当响应的状态码是 404 时, +会调用 `not_found` 处理器: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### 错误 + +在任何路由代码块或过滤器抛出异常时,会调用 `error` 处理器。 +但注意在开发环境下只有将 show exceptions 项设置为 `:after_handler` 时,才会生效。 + +```ruby +set :show_exceptions, :after_handler +``` + +可以用 Rack 变量 `sinatra.error` 访问异常对象: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +自定义错误: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +当下面的代码执行时: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +你会得到错误信息: + +``` +So what happened was... something bad +``` + +或者,你也可以为状态码设置错误处理器: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +或者为某个范围内的状态码统一设置错误处理器: + +```ruby +error 400..510 do + 'Boom' +end +``` + +在开发环境下,Sinatra会使用特殊的 `not_found` 和 `error` 处理器, +以便在浏览器中显示美观的 stack traces 和额外的调试信息。 + +## Rack 中间件 + +Sinatra 依赖 [Rack](http://rack.github.io/), 一个面向 Ruby 网络框架的最小化标准接口。 +Rack 最有趣的功能之一是支持“中间件”——位于服务器和你的应用之间的组件, +它们监控或操作 HTTP 请求/响应以提供多种常用功能。 + +Sinatra 通过顶层的 `use` 方法,让建立 Rack 中间件管道异常简单: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use` 的语义和在 [Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +DSL (在 rackup 文件中最频繁使用)中定义的完全一样。例如,`use` 方法接受 +多个/可变参数,以及代码块: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack 拥有有多种标准中间件,用于日志、调试、URL 路由、认证和会话处理。 +根据配置,Sinatra 可以自动使用这里面的许多组件, +所以你一般不需要显式地 `use` 它们。 + +你可以在 [rack](https://github.com/rack/rack/tree/master/lib/rack)、 +[rack-contrib](https://github.com/rack/rack-contrib#readm) 或 +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) +中找到有用的中间件。 + +## 测试 + +可以使用任何基于 Rack 的测试程序库或者框架来编写Sinatra的测试。 +推荐使用 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +注意:如果你使用 Sinatra 的模块化风格,应该用你应用的类名替代 `Sinatra::Application`。 + +## Sinatra::Base - 中间件、库和模块化应用 + +在顶层定义你的应用很适合微型项目, +但是在构建可复用的组件(如 Rack 中间件、Rails metal、带服务器组件的库或 Sinatra 扩展)时, +却有相当大的缺陷。 +顶层 DSL 认为你采用的是微型应用风格的配置 (例如:唯一应用文件、 +`./public` 和 `./views` 目录、日志、异常细节页面等)。 +如果你的项目不采用微型应用风格,应该使用 `Sinatra::Base`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +Sinatra::Base 的子类可以使用的方法实际上就是顶层 DSL 中可以使用的方法。 +大部分顶层应用可以通过两方面的改变转换为 Sinatra::Base 组件: + +* 你的文件应当引入 `sinatra/base` 而不是 `sinatra`; +否则,Sinatra 的所有 DSL 方法将会被导入主命名空间。 + +* 把应用的路由、错误处理器、过滤器和选项放在一个 Sinatra::Base 的子类中。 + +`Sinatra::Base` 是一个白板。大部分选项(包括内置的服务器)默认是禁用的。 +可以参考[配置](http://www.sinatrarb.com/configuration.html) +以查看可用选项的具体细节和它们的行为。如果你想让你的应用更像顶层定义的应用(即经典风格), +你可以继承 `Sinatra::Applicaiton`。 + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### 模块化风格 vs. 经典风格 + +与通常的认识相反,经典风格并没有任何错误。 +如果它适合你的应用,你不需要切换到模块化风格。 + +与模块化风格相比,经典风格的主要缺点在于,每个 Ruby 进程只能有一个 Sinatra 应用。 +如果你计划使用多个 Sinatra 应用,应该切换到模块化风格。 +你也完全可以混用模块化风格和经典风格。 + +如果从一种风格转换到另一种,你需要注意默认设置中的一些细微差别: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    设置经典风格模块化风格模块化风格
    app_file加载 sinatra 的文件继承 Sinatra::Base 的文件继承 Sinatra::Application 的文件
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### 运行一个模块化应用 + +模块化应用的启动有两种常见方式,其中之一是使用 `run!` 方法主动启动: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... 这里是应用代码 ... + + # 如果直接执行该文件,那么启动服务器 + run! if app_file == $0 +end +``` + +执行该文件就会启动服务器: + +```shell +ruby my_app.rb +``` + +另一种方式是使用 `config.ru` 文件,这种方式允许你使用任何 Rack 处理器: + +```ruby +# config.ru (用 rackup 启动) +require './my_app' +run MyApp +``` + +运行: + +```shell +rackup -p 4567 +``` + +### 使用 config.ru 运行经典风格的应用 + +编写你的应用: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +添加相应的 `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### 何时使用 config.ru? + +下列情况,推荐使用 `config.ru`: + +* 部署时使用不同的 Rack 处理器 (Passenger、Unicorn、Heroku 等)。 +* 使用多个 `Sinatra::Base` 的子类。 +* 把 Sinatra 当作中间件使用,而非端点。 + +**你不必仅仅因为想使用模块化风格而切换到 `config.ru`,同样的, +你也不必仅仅因为要运行 `config.ru` 而切换到模块化风格。** + +### 把 Sinatra 当作中间件使用 + +Sinatra 可以使用其它 Rack 中间件, +反过来,任何 Sinatra 应用程序自身都可以被当作中间件,添加到任何 Rack 端点前面。 +此端点可以是任何 Sinatra 应用,或任何基于 Rack 的应用程序 (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 中间件的执行发生在 before 过滤器之前 + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 创建动态应用 + +有时你希望在运行时创建新应用,而不必把应用预先赋值给常量。这时可以使用 `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +`Sinatra.new` 接受一个可选的参数,表示要继承的应用: + +```ruby +# config.ru (用 rackup 启动) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +当你测试 Sinatra 扩展或在自己的类库中使用 Sinatra 时,这非常有用。 + +这也让把 Sinatra 当作中间件使用变得极其容易: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## 作用域和绑定 + +当前作用域决定了可以使用的方法和变量。 + +### 应用/类作用域 + +每个 Sinatra 应用都对应 `Sinatra::Base` 类的一个子类。 +如果你在使用顶层 DSL (`require 'sinatra'`),那么这个类就是 `Sinatra::Application`, +否则该类是你显式创建的子类。 +在类层面,你可以使用 `get` 或 `before` 这样的方法, +但不能访问 `request` 或 `session` 对象, 因为对于所有的请求,只有单一的应用类。 + +通过 `set` 创建的选项是类方法: + +```ruby +class MyApp < Sinatra::Base + # 嘿,我在应用作用域! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 嘿,我已经不在应用作用域了! + end +end +``` + +下列位置绑定的是应用作用域: + +* 应用类内部 +* 通过扩展定义的方法内部 +* 传递给 `helpers` 方法的代码块内部 +* 作为 `set` 值的 procs/blocks 内部 +* 传递给 `Sinatra.new` 的代码块内部 + +你可以这样访问变量域对象(应用类): +* 通过传递给 configure 代码块的对象 (`configure { |c| ... }`) +* 在请求作用域中使用 `settings` + +### 请求/实例作用域 + +对于每个请求,Sinatra 会创建应用类的一个新实例。所有的处理器代码块都在该实例对象的作用域中运行。 +在该作用域中, 你可以访问 `request` 和 `session` 对象, +或调用渲染方法(如 `erb`、`haml`)。你可以在请求作用域中通过 `settings` 辅助方法 +访问应用作用域: + +```ruby +class MyApp < Sinatra::Base + # 嘿,我在应用作用域! + get '/define_route/:name' do + # '/define_route/:name' 的请求作用域 + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}" 的请求作用域 + @value # => nil (并不是同一个请求) + end + + "Route defined!" + end +end +``` + +以下位置绑定的是请求作用域: + +* get、head、post、put、delete、options、patch、link 和 unlink 代码块内部 +* before 和 after 过滤器内部 +* 辅助方法内部 +* 模板/视图内部 + +### 代理作用域 + +代理作用域只是把方法转送到类作用域。 +然而,它与类作用域的行为并不完全相同, 因为你并不能在代理作用域获得类的绑定。 +只有显式地标记为供代理使用的方法才是可用的, +而且你不能和类作用域共享变量/状态。(解释:你有了一个不同的 `self`)。 +你可以通过调用 `Sinatra::Delegator.delegate :method_name` 显式地添加方法代理。 + +以下位置绑定的是代理变量域: +* 顶层绑定,如果你执行了 `require "sinatra"` +* 扩展了 `Sinatra::Delegator` 这一 mixin 的对象内部 + +自己在这里看一下源码:[Sinatra::Delegator +mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +已经 +[被扩展进了 main 对象](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。 + +## 命令行 + +可以直接运行 Sinatra 应用: + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +选项是: + +``` +-h # 显示帮助 +-p # 设置端口号 (默认是 4567) +-o # 设定主机名 (默认是 0.0.0.0) +-e # 设置环境 (默认是 development) +-s # 声明 rack 服务器/处理器 (默认是 thin) +-x # 打开互斥锁 (默认是 off) +``` + +### 多线程 + +_根据 Konstantin 的 [这个 StackOverflow 答案] [so-answer] 改写_ + +Sinatra 本身并不使用任何并发模型,而是将并发的任务留给底层的 +Rack 处理器(服务器),如 Thin、Puma 或 WEBrick。Sinatra 本身是线程安全的,所以 +Rack 处理器使用多线程并发模型并无任何问题。这意味着在启动服务器时,你必须指定特定 +Rack 处理器的正确调用方法。 +下面的例子展示了如何启动一个多线程的 Thin 服务器: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +启动服务器的命令是: + +```shell +thin --threaded start +``` + + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## 必要条件 + +以下 Ruby 版本受官方支持: +
    +
    Ruby 1.8.7
    +
    + Sinatra 完全支持 1.8.7,但是,除非必要,我们推荐你升级或者切换到 + JRuby 或 Rubinius。Sinatra 2.0 之前都不会取消对 1.8.7 + 的支持。Ruby 1.8.6 目前已不受支持。 +
    + +
    Ruby 1.9.2
    +
    + Sinatra 完全支持 1.9.2。 + 不要使用 1.9.2p0,它在运行 Sinatra 程序时会产生 segmentation faults 错误。 + 至少在 Sinatra 1.5 发布之前,官方对 1.9.2 的支持仍会继续。 +
    + +
    Ruby 1.9.3
    +
    + Sinatra 完全支持并推荐使用 1.9.3。请注意从更早的版本迁移到 1.9.3 会使所有的会话失效。 + 直到 Sinatra 2.0 发布之前,官方仍然会支持 1.9.3。 +
    + +
    Ruby 2.x
    +
    + Sinatra 完全支持并推荐使用 2.x。目前尚无停止支持 2.x 的计划。 +
    + +
    Rubinius
    +
    + Sinatra 官方支持 Rubinius (Rubinius >= 2.x)。推荐 gem install puma。 +
    + +
    JRuby
    +
    + Sinatra 官方支持 JRuby 的最新稳定版本,但不推荐在 JRuby 上使用 C 扩展。 + 推荐 gem install trinidad。 +
    +
    + +我们也在时刻关注新的 Ruby 版本。 + +以下 Ruby 实现不受 Sinatra 官方支持,但可以运行 Sinatra: + +* 老版本 JRuby 和 Rubinius +* Ruby 企业版 +* MacRuby、Maglev、IronRuby +* Ruby 1.9.0 和 1.9.1 (不推荐使用) + +不受官方支持的意思是,如果仅在不受支持的 Ruby 实现上发生错误,我们认为不是我们的问题,而是该实现的问题。 + +我们同时也针对 ruby-head (MRI 的未来版本)运行 CI,但由于 ruby-head 一直处在变化之中, +我们不能作任何保证。我们期望完全支持未来的 2.x 版本。 + +Sinatra 应该会运行在任何支持上述 Ruby 实现的操作系统上。 + +如果你使用 MacRuby,你应该 `gem install control_tower`。 + +Sinatra 目前不支持 Cardinal、SmallRuby、BlueRuby 或其它 1.8.7 之前的 Ruby 版本。 + +## 紧跟前沿 + +如果你想使用 Sinatra 的最新代码,请放心使用 master 分支来运行你的程序,它是相当稳定的。 + +我们也会不定期推出 prerelease gems,所以你也可以运行 + +```shell +gem install sinatra --pre +``` + +来获得最新的特性。 + +### 通过 Bundler 使用 Sinatra + +如果你想在应用中使用最新的 Sinatra,推荐使用 [Bundler](http://bundler.io)。 + +首先,安装 Bundler,如果你还没有安装的话: + +```shell +gem install bundler +``` + +然后,在你的项目目录下创建一个 `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 其它依赖 +gem 'haml' # 假如你使用 haml +gem 'activerecord', '~> 3.0' # 也许你还需要 ActiveRecord 3.x +``` + +请注意你必须在 `Gemfile` 中列出应用的所有依赖项。 +然而, Sinatra 的直接依赖项 (Rack 和 Tilt) 则会被 Bundler 自动获取和添加。 + +现在你可以这样运行你的应用: + +```shell +bundle exec ruby myapp.rb +``` + +### 使用自己本地的 Sinatra + +创建一个本地克隆,并通过 `$LOAD_PATH` 里的 `sinatra/lib` 目录运行你的应用: + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +为了在未来更新 Sinatra 源代码: + +```shell +cd myapp/sinatra +git pull +``` + +### 全局安装 + +你可以自行编译 Sinatra gem: + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +如果你以 root 身份安装 gems,最后一步应该是: + +```shell +sudo rake install +``` + +## 版本 + +Sinatra 遵循[语义化版本](http://semver.org),无论是 SemVer 还是 SemVerTag。 + +## 更多资料 + +* [项目官网](http://www.sinatrarb.com/) - 更多文档、新闻和其它资源的链接。 +* [贡献](http://www.sinatrarb.com/contributing) - 找到一个 bug?需要帮助?有了一个 patch? +* [问题追踪](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [邮件列表](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack,点击 +[这里](https://sinatra-slack.herokuapp.com/) 获得邀请。 +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 教程 +* [Sinatra Recipes](http://recipes.sinatrarb.com/) 社区贡献的实用技巧 +* http://www.rubydoc.info/ 上[最新版本](http://www.rubydoc.info//gems/sinatra)或[当前 HEAD](http://www.rubydoc.info/github/sinatra/sinatra) 的 API 文档 +* [CI 服务器](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Rakefile b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Rakefile new file mode 100644 index 0000000000..3209d36450 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/Rakefile @@ -0,0 +1,225 @@ +require 'rake/clean' +require 'rake/testtask' +require 'fileutils' +require 'date' + +task :default => :test +task :spec => :test + +CLEAN.include "**/*.rbc" + +def source_version + @source_version ||= File.read(File.expand_path("../VERSION", __FILE__)).strip +end + +def prev_feature + source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s } +end + +def prev_version + return prev_feature + '.0' if source_version.end_with? '.0' + source_version.gsub(/\d+$/) { |s| s.to_i - 1 } +end + +# SPECS =============================================================== + +task :test do + ENV['LANG'] = 'C' + ENV.delete 'LC_CTYPE' +end + +Rake::TestTask.new(:test) do |t| + t.test_files = FileList['test/*_test.rb'] + t.ruby_opts = ['-r rubygems'] if defined? Gem + t.ruby_opts << '-I.' + t.warning = true +end + +Rake::TestTask.new(:"test:core") do |t| + core_tests = %w[base delegator encoding extensions filter + helpers mapped_error middleware radius rdoc + readme request response result route_added_hook + routing server settings sinatra static templates] + t.test_files = core_tests.map {|n| "test/#{n}_test.rb"} + t.ruby_opts = ["-r rubygems"] if defined? Gem + t.ruby_opts << "-I." + t.warning = true +end + +# Rcov ================================================================ + +namespace :test do + desc 'Measures test coverage' + task :coverage do + rm_f "coverage" + ENV['COVERAGE'] = '1' + Rake::Task['test'].invoke + end +end + +# Website ============================================================= + +desc 'Generate RDoc under doc/api' +task 'doc' => ['doc:api'] +task('doc:api') { sh "yardoc -o doc/api" } +CLEAN.include 'doc/api' + +# README =============================================================== + +task :add_template, [:name] do |t, args| + Dir.glob('README.*') do |file| + code = File.read(file) + if code =~ /^===.*#{args.name.capitalize}/ + puts "Already covered in #{file}" + else + template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] + if !template + puts "Liquid not found in #{file}" + else + puts "Adding section to #{file}" + template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) + code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" + File.open(file, "w") { |f| f << code } + end + end + end +end + +# Thanks in announcement =============================================== + +team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase", "Zachary Scott"] +desc "list of contributors" +task :thanks, ['release:all', :backports] do |t, a| + a.with_defaults :release => "#{prev_version}..HEAD", + :backports => "#{prev_feature}.0..#{prev_feature}.x" + included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } + excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } + commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } + authors = commits.keys.sort_by { |n| - commits[n].size } - team + puts authors[0..-2].join(', ') << " and " << authors.last, + "(based on commits included in #{a.release}, but not in #{a.backports})" +end + +desc "list of authors" +task :authors, [:commit_range, :format, :sep] do |t, a| + a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' + authors = Hash.new(0) + blake = "Blake Mizerany" + overall = 0 + mapping = { + "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, + "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", + "Wu Jiang (nouse)" => "Wu Jiang" } + `git shortlog -s #{a.commit_range}`.lines.map do |line| + line = line.force_encoding 'binary' if line.respond_to? :force_encoding + num, name = line.split("\t", 2).map(&:strip) + authors[mapping[name] || name] += num.to_i + overall += num.to_i + end + puts "#{overall} commits by #{authors.count} authors:" + puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) +end + +desc "generates TOC" +task :toc, [:readme] do |t, a| + a.with_defaults :readme => 'README.md' + + def self.link(title) + title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') + end + + puts "* [Sinatra](#sinatra)" + title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain + File.binread(a.readme).scan(/^##.*/) do |line| + puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } + end +end + +# PACKAGING ============================================================ + +if defined?(Gem) + GEMS_AND_ROOT_DIRECTORIES = { + "sinatra" => ".", + "sinatra-contrib" => "./sinatra-contrib", + "rack-protection" => "./rack-protection" + } + + def package(gem, ext='') + "pkg/#{gem}-#{source_version}" + ext + end + + directory 'pkg/' + CLOBBER.include('pkg') + + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + file package(gem, '.gem') => ["pkg/", "#{directory + '/' + gem}.gemspec"] do |f| + sh "cd #{directory} && gem build #{gem}.gemspec" + mv directory + "/" + File.basename(f.name), f.name + end + + file package(gem, '.tar.gz') => ["pkg/"] do |f| + sh <<-SH + git archive \ + --prefix=#{gem}-#{source_version}/ \ + --format=tar \ + HEAD -- #{directory} | gzip > #{f.name} + SH + end + end + + namespace :package do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Build #{gem} packages" + task gem => %w[.gem .tar.gz].map { |e| package(gem, e) } + end + + desc "Build all packages" + task :all => GEMS_AND_ROOT_DIRECTORIES.keys + end + + namespace :install do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Build and install #{gem} as local gem" + task gem => package(gem, '.gem') do + sh "gem install #{package(gem, '.gem')}" + end + end + + desc "Build and install all of the gems as local gems" + task :all => GEMS_AND_ROOT_DIRECTORIES.keys + end + + namespace :release do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Release #{gem} as a package" + task gem => "package:#{gem}" do + sh <<-SH + gem install #{package(gem, '.gem')} --local && + gem push #{package(gem, '.gem')} + SH + end + end + + desc "Commits the version to github repository" + task :commit_version do + %w[ + lib/sinatra + sinatra-contrib/lib/sinatra/contrib + rack-protection/lib/rack/protection + ].each do |path| + path = File.join(path, 'version.rb') + File.write(path, File.read(path).sub(/VERSION = '(.+?)'/, "VERSION = '#{source_version}'")) + end + + sh <<-SH + git commit --allow-empty -a -m '#{source_version} release' && + git tag -s v#{source_version} -m '#{source_version} release' && + git push && (git push origin || true) && + git push --tags && (git push origin --tags || true) + SH + end + + desc "Release all gems as packages" + task :all => [:test, :commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/SECURITY.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/SECURITY.md new file mode 100644 index 0000000000..e5affc29f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/SECURITY.md @@ -0,0 +1,35 @@ +# Reporting a security bug + +All security bugs in Sinatra should be reported to the core team through our private mailing list [sinatra-security@googlegroups.com](https://groups.google.com/group/sinatra-security). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report. + +After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours. + +If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take: + +* Contact the current security coordinator [Zachary Scott](mailto:zzak@ruby-lang.org) directly + +## Disclosure Policy + +Sinatra has a 5 step disclosure policy, that is upheld to the best of our ability. + +1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process. +2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems. +3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement. +4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions. +5. On the embargo date, the [mailing list][mailing-list] and [security list][security-list] are sent a copy of the announcement. The changes are pushed to the public repository and new gems released to rubygems. + +Typically the embargo date will be set 72 hours from the time vendor-sec is first notified, however this may vary depending on the severity of the bug or difficulty in applying a fix. + +This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner. + +## Security Updates + +Security updates will be posted on the [mailing list][mailing-list] and [security list][security-list]. + +## Comments on this Policy + +If you have any suggestions to improve this policy, please send an email the core team at [sinatrarb@googlegroups.com](https://groups.google.com/group/sinatrarb). + + +[mailing-list]: http://groups.google.com/group/sinatrarb/topics +[security-list]: http://groups.google.com/group/sinatra-security/topics diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/VERSION b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/VERSION new file mode 100644 index 0000000000..7ec1d6db40 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/VERSION @@ -0,0 +1 @@ +2.1.0 diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/chat.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/chat.rb new file mode 100755 index 0000000000..4d03bd1c42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/chat.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby -I ../lib -I lib +# coding: utf-8 +require_relative 'rainbows' +require 'sinatra' +set :server, :rainbows +connections = [] + +get '/' do + halt erb(:login) unless params[:user] + erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } +end + +get '/stream', :provides => 'text/event-stream' do + stream :keep_open do |out| + connections << out + out.callback { connections.delete(out) } + end +end + +post '/' do + connections.each { |out| out << "data: #{params[:msg]}\n\n" } + 204 # response without entity body +end + +__END__ + +@@ layout + + + Super Simple Chat with Sinatra + + + + <%= yield %> + + +@@ login +
    + + + +
    + +@@ chat +
    
    +
    + +
    + + + diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.conf b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.conf new file mode 100644 index 0000000000..31742e961b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.conf @@ -0,0 +1,3 @@ +Rainbows! do + use :EventMachine +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.rb new file mode 100644 index 0000000000..895e19a2be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/rainbows.rb @@ -0,0 +1,20 @@ +require 'rainbows' + +module Rack + module Handler + class Rainbows + def self.run(app, **options) + rainbows_options = { + listeners: ["#{options[:Host]}:#{options[:Port]}"], + worker_processes: 1, + timeout: 30, + config_file: ::File.expand_path('rainbows.conf', __dir__), + } + + ::Rainbows::HttpServer.new(app, rainbows_options).start.join + end + end + + register :rainbows, ::Rack::Handler::Rainbows + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/simple.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/simple.rb new file mode 100755 index 0000000000..2697f94bf2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/simple.rb @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby -I ../lib -I lib +require 'sinatra' +get('/') { 'this is a simple app' } diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/stream.ru b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/stream.ru new file mode 100644 index 0000000000..74af0a6148 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/examples/stream.ru @@ -0,0 +1,26 @@ +# this example does *not* work properly with WEBrick +# +# run *one* of these: +# +# rackup -s mongrel stream.ru # gem install mongrel +# unicorn stream.ru # gem install unicorn +# puma stream.ru # gem install puma +# rainbows -c rainbows.conf stream.ru # gem install rainbows eventmachine + +require 'sinatra/base' + +class Stream < Sinatra::Base + get '/' do + content_type :txt + + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end + end +end + +run Stream diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra.rb new file mode 100644 index 0000000000..68261380cb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra.rb @@ -0,0 +1,3 @@ +require 'sinatra/main' + +enable :inline_templates diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb new file mode 100644 index 0000000000..11cd284a9d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb @@ -0,0 +1,2020 @@ +# coding: utf-8 +# frozen_string_literal: true + +# external dependencies +require 'rack' +require 'tilt' +require 'rack/protection' +require 'mustermann' +require 'mustermann/sinatra' +require 'mustermann/regular' + +# stdlib dependencies +require 'thread' +require 'time' +require 'uri' + +# other files we need +require 'sinatra/indifferent_hash' +require 'sinatra/show_exceptions' +require 'sinatra/version' + +module Sinatra + # The request object. See Rack::Request for more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Request + class Request < Rack::Request + HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ + HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ + + # Returns an array of acceptable media types for the response + def accept + @env['sinatra.accept'] ||= begin + if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != '' + @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS). + map! { |e| AcceptEntry.new(e) }.sort + else + [AcceptEntry.new('*/*')] + end + end + end + + def accept?(type) + preferred_type(type).to_s.include?(type) + end + + def preferred_type(*types) + return accept.first if types.empty? + types.flatten! + return types.first if accept.empty? + accept.detect do |accept_header| + type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) } + return type if type + end + end + + alias secure? ssl? + + def forwarded? + @env.include? "HTTP_X_FORWARDED_HOST" + end + + def safe? + get? or head? or options? or trace? + end + + def idempotent? + safe? or put? or delete? or link? or unlink? + end + + def link? + request_method == "LINK" + end + + def unlink? + request_method == "UNLINK" + end + + def params + super + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}" + end + + class AcceptEntry + attr_accessor :params + attr_reader :entry + + def initialize(entry) + params = entry.scan(HEADER_PARAM).map! do |s| + key, value = s.strip.split('=', 2) + value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') + [key, value] + end + + @entry = entry + @type = entry[/[^;]+/].delete(' ') + @params = Hash[params] + @q = @params.delete('q') { 1.0 }.to_f + end + + def <=>(other) + other.priority <=> self.priority + end + + def priority + # We sort in descending order; better matches should be higher. + [ @q, -@type.count('*'), @params.size ] + end + + def to_str + @type + end + + def to_s(full = false) + full ? entry : to_str + end + + def respond_to?(*args) + super or to_str.respond_to?(*args) + end + + def method_missing(*args, &block) + to_str.send(*args, &block) + end + end + + class MimeTypeEntry + attr_reader :params + + def initialize(entry) + params = entry.scan(HEADER_PARAM).map! do |s| + key, value = s.strip.split('=', 2) + value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') + [key, value] + end + + @type = entry[/[^;]+/].delete(' ') + @params = Hash[params] + end + + def accepts?(entry) + File.fnmatch(entry, self) && matches_params?(entry.params) + end + + def to_str + @type + end + + def matches_params?(params) + return true if @params.empty? + + params.all? { |k,v| !@params.has_key?(k) || @params[k] == v } + end + end + end + + # The response object. See Rack::Response and Rack::Response::Helpers for + # more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Response + # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers + class Response < Rack::Response + DROP_BODY_RESPONSES = [204, 304] + + def body=(value) + value = value.body while Rack::Response === value + @body = String === value ? [value.to_str] : value + end + + def each + block_given? ? super : enum_for(:each) + end + + def finish + result = body + + if drop_content_info? + headers.delete "Content-Length" + headers.delete "Content-Type" + end + + if drop_body? + close + result = [] + end + + if calculate_content_length? + # if some other code has already set Content-Length, don't muck with it + # currently, this would be the static file-handler + headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s + end + + [status.to_i, headers, result] + end + + private + + def calculate_content_length? + headers["Content-Type"] and not headers["Content-Length"] and Array === body + end + + def drop_content_info? + status.to_i / 100 == 1 or drop_body? + end + + def drop_body? + DROP_BODY_RESPONSES.include?(status.to_i) + end + end + + # Some Rack handlers (Rainbows!) implement an extended body object protocol, however, + # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. + # This middleware will detect an extended body object and will make sure it reaches the + # handler directly. We do this here, so our middleware and middleware set up by the app will + # still be able to run. + class ExtendedRack < Struct.new(:app) + def call(env) + result, callback = app.call(env), env['async.callback'] + return result unless callback and async?(*result) + after_response { callback.call result } + setup_close(env, *result) + throw :async + end + + private + + def setup_close(env, status, headers, body) + return unless body.respond_to? :close and env.include? 'async.close' + env['async.close'].callback { body.close } + env['async.close'].errback { body.close } + end + + def after_response(&block) + raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine + EventMachine.next_tick(&block) + end + + def async?(status, headers, body) + return true if status == -1 + body.respond_to? :callback and body.respond_to? :errback + end + end + + # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing, + # if another CommonLogger is already in the middleware chain. + class CommonLogger < Rack::CommonLogger + def call(env) + env['sinatra.commonlogger'] ? @app.call(env) : super + end + + superclass.class_eval do + alias call_without_check call unless method_defined? :call_without_check + def call(env) + env['sinatra.commonlogger'] = true + call_without_check(env) + end + end + end + + class BadRequest < TypeError #:nodoc: + def http_status; 400 end + end + + class NotFound < NameError #:nodoc: + def http_status; 404 end + end + + # Methods available to routes, before/after filters, and views. + module Helpers + # Set or retrieve the response status code. + def status(value = nil) + response.status = Rack::Utils.status_code(value) if value + response.status + end + + # Set or retrieve the response body. When a block is given, + # evaluation is deferred until the body is read with #each. + def body(value = nil, &block) + if block_given? + def block.each; yield(call) end + response.body = block + elsif value + # Rack 2.0 returns a Rack::File::Iterator here instead of + # Rack::File as it was in the previous API. + unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream) + headers.delete 'Content-Length' + end + response.body = value + else + response.body + end + end + + # Halt processing and redirect to the URI provided. + def redirect(uri, *args) + if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' + status 303 + else + status 302 + end + + # According to RFC 2616 section 14.30, "the field value consists of a + # single absolute URI" + response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) + halt(*args) + end + + # Generates the absolute URI for a given path in the app. + # Takes Rack routers and reverse proxies into account. + def uri(addr = nil, absolute = true, add_script_name = true) + return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i + uri = [host = String.new] + if absolute + host << "http#{'s' if request.secure?}://" + if request.forwarded? or request.port != (request.secure? ? 443 : 80) + host << request.host_with_port + else + host << request.host + end + end + uri << request.script_name.to_s if add_script_name + uri << (addr ? addr : request.path_info).to_s + File.join uri + end + + alias url uri + alias to uri + + # Halt processing and return the error status provided. + def error(code, body = nil) + code, body = 500, code.to_str if code.respond_to? :to_str + response.body = body unless body.nil? + halt code + end + + # Halt processing and return a 404 Not Found. + def not_found(body = nil) + error 404, body + end + + # Set multiple response headers with Hash. + def headers(hash = nil) + response.headers.merge! hash if hash + response.headers + end + + # Access the underlying Rack session. + def session + request.session + end + + # Access shared logger object. + def logger + request.logger + end + + # Look up a media type by file extension in Rack's mime registry. + def mime_type(type) + Base.mime_type(type) + end + + # Set the Content-Type of the response body given a media type or file + # extension. + def content_type(type = nil, params = {}) + return response['Content-Type'] unless type + default = params.delete :default + mime_type = mime_type(type) || default + fail "Unknown media type: %p" % type if mime_type.nil? + mime_type = mime_type.dup + unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } + params[:charset] = params.delete('charset') || settings.default_encoding + end + params.delete :charset if mime_type.include? 'charset' + unless params.empty? + mime_type << (mime_type.include?(';') ? ', ' : ';') + mime_type << params.map do |key, val| + val = val.inspect if val =~ /[";,]/ + "#{key}=#{val}" + end.join(', ') + end + response['Content-Type'] = mime_type + end + + # Set the Content-Disposition to "attachment" with the specified filename, + # instructing the user agents to prompt to save. + def attachment(filename = nil, disposition = :attachment) + response['Content-Disposition'] = disposition.to_s.dup + if filename + params = '; filename="%s"' % File.basename(filename) + response['Content-Disposition'] << params + ext = File.extname(filename) + content_type(ext) unless response['Content-Type'] or ext.empty? + end + end + + # Use the contents of the file at +path+ as the response body. + def send_file(path, opts = {}) + if opts[:type] or not response['Content-Type'] + content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' + end + + disposition = opts[:disposition] + filename = opts[:filename] + disposition = :attachment if disposition.nil? and filename + filename = path if filename.nil? + attachment(filename, disposition) if disposition + + last_modified opts[:last_modified] if opts[:last_modified] + + file = Rack::File.new(File.dirname(settings.app_file)) + result = file.serving(request, path) + + result[1].each { |k,v| headers[k] ||= v } + headers['Content-Length'] = result[1]['Content-Length'] + opts[:status] &&= Integer(opts[:status]) + halt (opts[:status] || result[0]), result[2] + rescue Errno::ENOENT + not_found + end + + # Class of the response body in case you use #stream. + # + # Three things really matter: The front and back block (back being the + # block generating content, front the one sending it to the client) and + # the scheduler, integrating with whatever concurrency feature the Rack + # handler is using. + # + # Scheduler has to respond to defer and schedule. + class Stream + def self.schedule(*) yield end + def self.defer(*) yield end + + def initialize(scheduler = self.class, keep_open = false, &back) + @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open + @callbacks, @closed = [], false + end + + def close + return if closed? + @closed = true + @scheduler.schedule { @callbacks.each { |c| c.call } } + end + + def each(&front) + @front = front + @scheduler.defer do + begin + @back.call(self) + rescue Exception => e + @scheduler.schedule { raise e } + end + close unless @keep_open + end + end + + def <<(data) + @scheduler.schedule { @front.call(data.to_s) } + self + end + + def callback(&block) + return yield if closed? + @callbacks << block + end + + alias errback callback + + def closed? + @closed + end + end + + # Allows to start sending data to the client even though later parts of + # the response body have not yet been generated. + # + # The close parameter specifies whether Stream#close should be called + # after the block has been executed. This is only relevant for evented + # servers like Rainbows. + def stream(keep_open = false) + scheduler = env['async.callback'] ? EventMachine : Stream + current = @params.dup + body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } } + end + + # Specify response freshness policy for HTTP caches (Cache-Control header). + # Any number of non-value directives (:public, :private, :no_cache, + # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with + # a Hash of value directives (:max_age, :s_maxage). + # + # cache_control :public, :must_revalidate, :max_age => 60 + # => Cache-Control: public, must-revalidate, max-age=60 + # + # See RFC 2616 / 14.9 for more on standard cache control directives: + # http://tools.ietf.org/html/rfc2616#section-14.9.1 + def cache_control(*values) + if values.last.kind_of?(Hash) + hash = values.pop + hash.reject! { |k, v| v == false } + hash.reject! { |k, v| values << k if v == true } + else + hash = {} + end + + values.map! { |value| value.to_s.tr('_','-') } + hash.each do |key, value| + key = key.to_s.tr('_', '-') + value = value.to_i if ['max-age', 's-maxage'].include? key + values << "#{key}=#{value}" + end + + response['Cache-Control'] = values.join(', ') if values.any? + end + + # Set the Expires header and Cache-Control/max-age directive. Amount + # can be an integer number of seconds in the future or a Time object + # indicating when the response should be considered "stale". The remaining + # "values" arguments are passed to the #cache_control helper: + # + # expires 500, :public, :must_revalidate + # => Cache-Control: public, must-revalidate, max-age=500 + # => Expires: Mon, 08 Jun 2009 08:50:17 GMT + # + def expires(amount, *values) + values << {} unless values.last.kind_of?(Hash) + + if amount.is_a? Integer + time = Time.now + amount.to_i + max_age = amount + else + time = time_for amount + max_age = time - Time.now + end + + values.last.merge!(:max_age => max_age) + cache_control(*values) + + response['Expires'] = time.httpdate + end + + # Set the last modified time of the resource (HTTP 'Last-Modified' header) + # and halt if conditional GET matches. The +time+ argument is a Time, + # DateTime, or other object that responds to +to_time+. + # + # When the current request includes an 'If-Modified-Since' header that is + # equal or later than the time specified, execution is immediately halted + # with a '304 Not Modified' response. + def last_modified(time) + return unless time + time = time_for time + response['Last-Modified'] = time.httpdate + return if env['HTTP_IF_NONE_MATCH'] + + if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i + halt 304 if since >= time.to_i + end + + if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i + halt 412 if since < time.to_i + end + rescue ArgumentError + end + + ETAG_KINDS = [:strong, :weak] + # Set the response entity tag (HTTP 'ETag' header) and halt if conditional + # GET matches. The +value+ argument is an identifier that uniquely + # identifies the current version of the resource. The +kind+ argument + # indicates whether the etag should be used as a :strong (default) or :weak + # cache validator. + # + # When the current request includes an 'If-None-Match' header with a + # matching etag, execution is immediately halted. If the request method is + # GET or HEAD, a '304 Not Modified' response is sent. + def etag(value, options = {}) + # Before touching this code, please double check RFC 2616 14.24 and 14.26. + options = {:kind => options} unless Hash === options + kind = options[:kind] || :strong + new_resource = options.fetch(:new_resource) { request.post? } + + unless ETAG_KINDS.include?(kind) + raise ArgumentError, ":strong or :weak expected" + end + + value = '"%s"' % value + value = "W/#{value}" if kind == :weak + response['ETag'] = value + + if success? or status == 304 + if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource + halt(request.safe? ? 304 : 412) + end + + if env['HTTP_IF_MATCH'] + halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource + end + end + end + + # Sugar for redirect (example: redirect back) + def back + request.referer + end + + # whether or not the status is set to 1xx + def informational? + status.between? 100, 199 + end + + # whether or not the status is set to 2xx + def success? + status.between? 200, 299 + end + + # whether or not the status is set to 3xx + def redirect? + status.between? 300, 399 + end + + # whether or not the status is set to 4xx + def client_error? + status.between? 400, 499 + end + + # whether or not the status is set to 5xx + def server_error? + status.between? 500, 599 + end + + # whether or not the status is set to 404 + def not_found? + status == 404 + end + + # whether or not the status is set to 400 + def bad_request? + status == 400 + end + + # Generates a Time object from the given value. + # Used by #expires and #last_modified. + def time_for(value) + if value.is_a? Numeric + Time.at value + elsif value.respond_to? :to_s + Time.parse value.to_s + else + value.to_time + end + rescue ArgumentError => boom + raise boom + rescue Exception + raise ArgumentError, "unable to convert #{value.inspect} to a Time object" + end + + private + + # Helper method checking if a ETag value list includes the current ETag. + def etag_matches?(list, new_resource = request.post?) + return !new_resource if list == '*' + list.to_s.split(/\s*,\s*/).include? response['ETag'] + end + + def with_params(temp_params) + original, @params = @params, temp_params + yield + ensure + @params = original if original + end + end + + # Template rendering methods. Each method takes the name of a template + # to render as a Symbol and returns a String with the rendered output, + # as well as an optional hash with additional options. + # + # `template` is either the name or path of the template as symbol + # (Use `:'subdir/myview'` for views in subdirectories), or a string + # that will be rendered. + # + # Possible options are: + # :content_type The content type to use, same arguments as content_type. + # :layout If set to something falsy, no layout is rendered, otherwise + # the specified layout is used (Ignored for `sass` and `less`) + # :layout_engine Engine to use for rendering the layout. + # :locals A hash with local variables that should be available + # in the template + # :scope If set, template is evaluate with the binding of the given + # object rather than the application instance. + # :views Views directory to use. + module Templates + module ContentTyped + attr_accessor :content_type + end + + def initialize + super + @default_layout = :layout + @preferred_extension = nil + end + + def erb(template, options = {}, locals = {}, &block) + render(:erb, template, options, locals, &block) + end + + def erubis(template, options = {}, locals = {}) + warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ + "If you have Erubis installed, it will be used automatically." + render :erubis, template, options, locals + end + + def haml(template, options = {}, locals = {}, &block) + render(:haml, template, options, locals, &block) + end + + def sass(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :sass, template, options, locals + end + + def scss(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :scss, template, options, locals + end + + def less(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :less, template, options, locals + end + + def stylus(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :styl, template, options, locals + end + + def builder(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:builder, template, options, locals, &block) + end + + def liquid(template, options = {}, locals = {}, &block) + render(:liquid, template, options, locals, &block) + end + + def markdown(template, options = {}, locals = {}) + options[:exclude_outvar] = true + render :markdown, template, options, locals + end + + def textile(template, options = {}, locals = {}) + render :textile, template, options, locals + end + + def rdoc(template, options = {}, locals = {}) + render :rdoc, template, options, locals + end + + def asciidoc(template, options = {}, locals = {}) + render :asciidoc, template, options, locals + end + + def radius(template, options = {}, locals = {}) + render :radius, template, options, locals + end + + def markaby(template = nil, options = {}, locals = {}, &block) + render_ruby(:mab, template, options, locals, &block) + end + + def coffee(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :js + render :coffee, template, options, locals + end + + def nokogiri(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:nokogiri, template, options, locals, &block) + end + + def slim(template, options = {}, locals = {}, &block) + render(:slim, template, options, locals, &block) + end + + def creole(template, options = {}, locals = {}) + render :creole, template, options, locals + end + + def mediawiki(template, options = {}, locals = {}) + render :mediawiki, template, options, locals + end + + def wlang(template, options = {}, locals = {}, &block) + render(:wlang, template, options, locals, &block) + end + + def yajl(template, options = {}, locals = {}) + options[:default_content_type] = :json + render :yajl, template, options, locals + end + + def rabl(template, options = {}, locals = {}) + Rabl.register! + render :rabl, template, options, locals + end + + # Calls the given block for every possible template file in views, + # named name.ext, where ext is registered on engine. + def find_template(views, name, engine) + yield ::File.join(views, "#{name}.#{@preferred_extension}") + + Tilt.default_mapping.extensions_for(engine).each do |ext| + yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension + end + end + + private + + # logic shared between builder and nokogiri + def render_ruby(engine, template, options = {}, locals = {}, &block) + options, template = template, nil if template.is_a?(Hash) + template = Proc.new { block } if template.nil? + render engine, template, options, locals + end + + def render(engine, data, options = {}, locals = {}, &block) + # merge app-level options + engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} + options.merge!(engine_options) { |key, v1, v2| v1 } + + # extract generic options + locals = options.delete(:locals) || locals || {} + views = options.delete(:views) || settings.views || "./views" + layout = options[:layout] + layout = false if layout.nil? && options.include?(:layout) + eat_errors = layout.nil? + layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false) + layout = @default_layout if layout.nil? or layout == true + layout_options = options.delete(:layout_options) || {} + content_type = options.delete(:default_content_type) + content_type = options.delete(:content_type) || content_type + layout_engine = options.delete(:layout_engine) || engine + scope = options.delete(:scope) || self + exclude_outvar = options.delete(:exclude_outvar) + options.delete(:layout) + + # set some defaults + options[:outvar] ||= '@_out_buf' unless exclude_outvar + options[:default_encoding] ||= settings.default_encoding + + # compile and render template + begin + layout_was = @default_layout + @default_layout = false + template = compile_template(engine, data, options, views) + output = template.render(scope, locals, &block) + ensure + @default_layout = layout_was + end + + # render layout + if layout + options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). + merge!(layout_options) + catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } + end + + output.extend(ContentTyped).content_type = content_type if content_type + output + end + + def compile_template(engine, data, options, views) + eat_errors = options.delete :eat_errors + template_cache.fetch engine, data, options, views do + template = Tilt[engine] + raise "Template engine not found: #{engine}" if template.nil? + + case data + when Symbol + body, path, line = settings.templates[data] + if body + body = body.call if body.respond_to?(:call) + template.new(path, line.to_i, options) { body } + else + found = false + @preferred_extension = engine.to_s + find_template(views, data, template) do |file| + path ||= file # keep the initial path rather than the last one + if found = File.exist?(file) + path = file + break + end + end + throw :layout_missing if eat_errors and not found + template.new(path, 1, options) + end + when Proc, String + body = data.is_a?(String) ? Proc.new { data } : data + caller = settings.caller_locations.first + path = options[:path] || caller[0] + line = options[:line] || caller[1] + template.new(path, line.to_i, options, &body) + else + raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." + end + end + end + end + + # Base class for all Sinatra applications and middleware. + class Base + include Rack::Utils + include Helpers + include Templates + + URI_INSTANCE = URI::Parser.new + + attr_accessor :app, :env, :request, :response, :params + attr_reader :template_cache + + def initialize(app = nil) + super() + @app = app + @template_cache = Tilt::Cache.new + @pinned_response = nil # whether a before! filter pinned the content-type + yield self if block_given? + end + + # Rack call interface. + def call(env) + dup.call!(env) + end + + def call!(env) # :nodoc: + @env = env + @params = IndifferentHash.new + @request = Request.new(env) + @response = Response.new + template_cache.clear if settings.reload_templates + + invoke { dispatch! } + invoke { error_block!(response.status) } unless @env['sinatra.error'] + + unless @response['Content-Type'] + if Array === body && body[0].respond_to?(:content_type) + content_type body[0].content_type + elsif default = settings.default_content_type + content_type default + end + end + + @response.finish + end + + # Access settings defined with Base.set. + def self.settings + self + end + + # Access settings defined with Base.set. + def settings + self.class.settings + end + + def options + warn "Sinatra::Base#options is deprecated and will be removed, " \ + "use #settings instead." + settings + end + + # Exit the current block, halts any further processing + # of the request, and returns the specified response. + def halt(*response) + response = response.first if response.length == 1 + throw :halt, response + end + + # Pass control to the next matching route. + # If there are no more matching routes, Sinatra will + # return a 404 response. + def pass(&block) + throw :pass, block + end + + # Forward the request to the downstream app -- middleware only. + def forward + fail "downstream app not set" unless @app.respond_to? :call + status, headers, body = @app.call env + @response.status = status + @response.body = body + @response.headers.merge! headers + nil + end + + private + + # Run filters defined on the class and all superclasses. + # Accepts an optional block to call after each filter is applied. + def filter!(type, base = settings) + filter! type, base.superclass if base.superclass.respond_to?(:filters) + base.filters[type].each do |args| + result = process_route(*args) + yield result if block_given? + end + end + + # Run routes defined on the class and all superclasses. + def route!(base = settings, pass_block = nil) + if routes = base.routes[@request.request_method] + routes.each do |pattern, conditions, block| + @response.delete_header('Content-Type') unless @pinned_response + + returned_pass_block = process_route(pattern, conditions) do |*args| + env['sinatra.route'] = "#{@request.request_method} #{pattern}" + route_eval { block[*args] } + end + + # don't wipe out pass_block in superclass + pass_block = returned_pass_block if returned_pass_block + end + end + + # Run routes defined in superclass. + if base.superclass.respond_to?(:routes) + return route!(base.superclass, pass_block) + end + + route_eval(&pass_block) if pass_block + route_missing + end + + # Run a route block and throw :halt with the result. + def route_eval + throw :halt, yield + end + + # If the current request matches pattern and conditions, fill params + # with keys and call the given block. + # Revert params afterwards. + # + # Returns pass block. + def process_route(pattern, conditions, block = nil, values = []) + route = @request.path_info + route = '/' if route.empty? and not settings.empty_path_info? + route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/') + return unless params = pattern.params(route) + + params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes + force_encoding(params) + @params = @params.merge(params) if params.any? + + regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} ) + if regexp_exists + captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c } + values += captures + @params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty? + else + values += params.values.flatten + end + + catch(:pass) do + conditions.each { |c| throw :pass if c.bind(self).call == false } + block ? block[self, values] : yield(self, values) + end + rescue + @env['sinatra.error.params'] = @params + raise + ensure + params ||= {} + params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params'] + end + + # No matching route was found or all routes passed. The default + # implementation is to forward the request downstream when running + # as middleware (@app is non-nil); when no downstream app is set, raise + # a NotFound exception. Subclasses can override this method to perform + # custom route miss logic. + def route_missing + if @app + forward + else + raise NotFound, "#{request.request_method} #{request.path_info}" + end + end + + # Attempt to serve static files from public directory. Throws :halt when + # a matching file is found, returns nil otherwise. + def static!(options = {}) + return if (public_dir = settings.public_folder).nil? + path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" + return unless valid_path?(path) + + path = File.expand_path(path) + return unless File.file?(path) + + env['sinatra.static_file'] = path + cache_control(*settings.static_cache_control) if settings.static_cache_control? + send_file path, options.merge(:disposition => nil) + end + + # Run the block with 'throw :halt' support and apply result to the response. + def invoke + res = catch(:halt) { yield } + + res = [res] if Integer === res or String === res + if Array === res and Integer === res.first + res = res.dup + status(res.shift) + body(res.pop) + headers(*res) + elsif res.respond_to? :each + body res + end + nil # avoid double setting the same response tuple twice + end + + # Dispatch a request with error handling. + def dispatch! + # Avoid passing frozen string in force_encoding + @params.merge!(@request.params).each do |key, val| + next unless val.respond_to?(:force_encoding) + val = val.dup if val.frozen? + @params[key] = force_encoding(val) + end + + invoke do + static! if settings.static? && (request.get? || request.head?) + filter! :before do + @pinned_response = !@response['Content-Type'].nil? + end + route! + end + rescue ::Exception => boom + invoke { handle_exception!(boom) } + ensure + begin + filter! :after unless env['sinatra.static_file'] + rescue ::Exception => boom + invoke { handle_exception!(boom) } unless @env['sinatra.error'] + end + end + + # Error handling during requests. + def handle_exception!(boom) + if error_params = @env['sinatra.error.params'] + @params = @params.merge(error_params) + end + @env['sinatra.error'] = boom + + if boom.respond_to? :http_status + status(boom.http_status) + elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 + status(boom.code) + else + status(500) + end + + status(500) unless status.between? 400, 599 + + if server_error? + dump_errors! boom if settings.dump_errors? + raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler + elsif not_found? + headers['X-Cascade'] = 'pass' if settings.x_cascade? + end + + if res = error_block!(boom.class, boom) || error_block!(status, boom) + return res + end + + if not_found? || bad_request? + if boom.message && boom.message != boom.class.name + body boom.message + else + content_type 'text/html' + body '

    ' + (not_found? ? 'Not Found' : 'Bad Request') + '

    ' + end + end + + return unless server_error? + raise boom if settings.raise_errors? or settings.show_exceptions? + error_block! Exception, boom + end + + # Find an custom error block for the key(s) specified. + def error_block!(key, *block_params) + base = settings + while base.respond_to?(:errors) + next base = base.superclass unless args_array = base.errors[key] + args_array.reverse_each do |args| + first = args == args_array.first + args += [block_params] + resp = process_route(*args) + return resp unless resp.nil? && !first + end + end + return false unless key.respond_to? :superclass and key.superclass < Exception + error_block!(key.superclass, *block_params) + end + + def dump_errors!(boom) + msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") + @env['rack.errors'].puts(msg) + end + + class << self + CALLERS_TO_IGNORE = [ # :nodoc: + /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code + /lib\/tilt.*\.rb$/, # all tilt code + /^\(.*\)$/, # generated code + /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks + /active_support/, # active_support require hacks + /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks + /= 1.9.2 + /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files + ] + + # contrary to what the comment said previously, rubinius never supported this + if defined?(RUBY_IGNORE_CALLERS) + warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" + CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) + end + + attr_reader :routes, :filters, :templates, :errors + + # Removes all routes, filters, middleware and extension hooks from the + # current class (not routes/filters/... defined by its superclass). + def reset! + @conditions = [] + @routes = {} + @filters = {:before => [], :after => []} + @errors = {} + @middleware = [] + @prototype = nil + @extensions = [] + + if superclass.respond_to?(:templates) + @templates = Hash.new { |hash, key| superclass.templates[key] } + else + @templates = {} + end + end + + # Extension modules registered on this class and all superclasses. + def extensions + if superclass.respond_to?(:extensions) + (@extensions + superclass.extensions).uniq + else + @extensions + end + end + + # Middleware used in this class and all superclasses. + def middleware + if superclass.respond_to?(:middleware) + superclass.middleware + @middleware + else + @middleware + end + end + + # Sets an option to the given value. If the value is a proc, + # the proc will be called every time the option is accessed. + def set(option, value = (not_set = true), ignore_setter = false, &block) + raise ArgumentError if block and !not_set + value, not_set = block, false if block + + if not_set + raise ArgumentError unless option.respond_to?(:each) + option.each { |k,v| set(k, v) } + return self + end + + if respond_to?("#{option}=") and not ignore_setter + return __send__("#{option}=", value) + end + + setter = proc { |val| set option, val, true } + getter = proc { value } + + case value + when Proc + getter = value + when Symbol, Integer, FalseClass, TrueClass, NilClass + getter = value.inspect + when Hash + setter = proc do |val| + val = value.merge val if Hash === val + set option, val, true + end + end + + define_singleton("#{option}=", setter) + define_singleton(option, getter) + define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" + self + end + + # Same as calling `set :option, true` for each of the given options. + def enable(*opts) + opts.each { |key| set(key, true) } + end + + # Same as calling `set :option, false` for each of the given options. + def disable(*opts) + opts.each { |key| set(key, false) } + end + + # Define a custom error handler. Optionally takes either an Exception + # class, or an HTTP status code to specify which errors should be + # handled. + def error(*codes, &block) + args = compile! "ERROR", /.*/, block + codes = codes.flat_map(&method(:Array)) + codes << Exception if codes.empty? + codes << Sinatra::NotFound if codes.include?(404) + codes.each { |c| (@errors[c] ||= []) << args } + end + + # Sugar for `error(404) { ... }` + def not_found(&block) + error(404, &block) + end + + # Define a named template. The block must return the template source. + def template(name, &block) + filename, line = caller_locations.first + templates[name] = [block, filename, line.to_i] + end + + # Define the layout template. The block must return the template source. + def layout(name = :layout, &block) + template name, &block + end + + # Load embedded templates from the file; uses the caller's __FILE__ + # when no file is specified. + def inline_templates=(file = nil) + file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file + + begin + io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) + app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) + rescue Errno::ENOENT + app, data = nil + end + + if data + if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m + encoding = $2 + else + encoding = settings.default_encoding + end + lines = app.count("\n") + 1 + template = nil + force_encoding data, encoding + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*\S)\s*$/ + template = force_encoding(String.new, encoding) + templates[$1.to_sym] = [template, file, lines] + elsif template + template << line + end + end + end + end + + # Lookup or register a mime type in Rack's mime registry. + def mime_type(type, value = nil) + return type if type.nil? + return type.to_s if type.to_s.include?('/') + type = ".#{type}" unless type.to_s[0] == ?. + return Rack::Mime.mime_type(type, nil) unless value + Rack::Mime::MIME_TYPES[type] = value + end + + # provides all mime types matching type, including deprecated types: + # mime_types :html # => ['text/html'] + # mime_types :js # => ['application/javascript', 'text/javascript'] + def mime_types(type) + type = mime_type type + type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] + end + + # Define a before filter; runs before all requests within the same + # context as route handlers and may access/modify the request and + # response. + def before(path = /.*/, **options, &block) + add_filter(:before, path, **options, &block) + end + + # Define an after filter; runs after all requests within the same + # context as route handlers and may access/modify the request and + # response. + def after(path = /.*/, **options, &block) + add_filter(:after, path, **options, &block) + end + + # add a filter + def add_filter(type, path = /.*/, **options, &block) + filters[type] << compile!(type, path, block, **options) + end + + # Add a route condition. The route is considered non-matching when the + # block returns false. + def condition(name = "#{caller.first[/`.*'/]} condition", &block) + @conditions << generate_method(name, &block) + end + + def public=(value) + warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead" + set(:public_folder, value) + end + + def public_dir=(value) + self.public_folder = value + end + + def public_dir + public_folder + end + + # Defining a `GET` handler also automatically defines + # a `HEAD` handler. + def get(path, opts = {}, &block) + conditions = @conditions.dup + route('GET', path, opts, &block) + + @conditions = conditions + route('HEAD', path, opts, &block) + end + + def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end + def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end + def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end + def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end + def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end + def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end + def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end + def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end + + # Makes the methods defined in the block and in the Modules given + # in `extensions` available to the handlers and templates + def helpers(*extensions, &block) + class_eval(&block) if block_given? + prepend(*extensions) if extensions.any? + end + + # Register an extension. Alternatively take a block from which an + # extension will be created and registered on the fly. + def register(*extensions, &block) + extensions << Module.new(&block) if block_given? + @extensions += extensions + extensions.each do |extension| + extend extension + extension.registered(self) if extension.respond_to?(:registered) + end + end + + def development?; environment == :development end + def production?; environment == :production end + def test?; environment == :test end + + # Set configuration options for Sinatra and/or the app. + # Allows scoping of settings for certain environments. + def configure(*envs) + yield self if envs.empty? || envs.include?(environment.to_sym) + end + + # Use the specified Rack middleware + def use(middleware, *args, &block) + @prototype = nil + @middleware << [middleware, args, block] + end + + # Stop the self-hosted server if running. + def quit! + return unless running? + # Use Thin's hard #stop! if available, otherwise just #stop. + running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop + $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages? + set :running_server, nil + set :handler_name, nil + end + + alias_method :stop!, :quit! + + # Run the Sinatra app as a self-hosted server using + # Puma, Mongrel, or WEBrick (in that order). If given a block, will call + # with the constructed handler once we have taken the stage. + def run!(options = {}, &block) + return if running? + set options + handler = detect_rack_handler + handler_name = handler.name.gsub(/.*::/, '') + server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} + server_settings.merge!(:Port => port, :Host => bind) + + begin + start_server(handler, server_settings, handler_name, &block) + rescue Errno::EADDRINUSE + $stderr.puts "== Someone is already performing on port #{port}!" + raise + ensure + quit! + end + end + + alias_method :start!, :run! + + # Check whether the self-hosted server is running or not. + def running? + running_server? + end + + # The prototype instance used to process requests. + def prototype + @prototype ||= new + end + + # Create a new instance without middleware in front of it. + alias new! new unless method_defined? :new! + + # Create a new instance of the class fronted by its middleware + # pipeline. The object is guaranteed to respond to #call but may not be + # an instance of the class new was called on. + def new(*args, &bk) + instance = new!(*args, &bk) + Wrapper.new(build(instance).to_app, instance) + end + + # Creates a Rack::Builder instance with all the middleware set up and + # the given +app+ as end point. + def build(app) + builder = Rack::Builder.new + setup_default_middleware builder + setup_middleware builder + builder.run app + builder + end + + def call(env) + synchronize { prototype.call(env) } + end + + # Like Kernel#caller but excluding certain magic entries and without + # line / method information; the resulting array contains filenames only. + def caller_files + cleaned_caller(1).flatten + end + + # Like caller_files, but containing Arrays rather than strings with the + # first element being the file, and the second being the line. + def caller_locations + cleaned_caller 2 + end + + private + + # Starts the server by running the Rack Handler. + def start_server(handler, server_settings, handler_name) + # Ensure we initialize middleware before startup, to match standard Rack + # behavior, by ensuring an instance exists: + prototype + # Run the instance we created: + handler.run(self, **server_settings) do |server| + unless suppress_messages? + $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" + end + + setup_traps + set :running_server, server + set :handler_name, handler_name + server.threaded = settings.threaded if server.respond_to? :threaded= + + yield server if block_given? + end + end + + def suppress_messages? + handler_name =~ /cgi/i || quiet + end + + def setup_traps + if traps? + at_exit { quit! } + + [:INT, :TERM].each do |signal| + old_handler = trap(signal) do + quit! + old_handler.call if old_handler.respond_to?(:call) + end + end + + set :traps, false + end + end + + # Dynamically defines a method on settings. + def define_singleton(name, content = Proc.new) + singleton_class.class_eval do + undef_method(name) if method_defined? name + String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) + end + end + + # Condition for matching host name. Parameter might be String or Regexp. + def host_name(pattern) + condition { pattern === request.host } + end + + # Condition for matching user agent. Parameter should be Regexp. + # Will set params[:agent]. + def user_agent(pattern) + condition do + if request.user_agent.to_s =~ pattern + @params[:agent] = $~[1..-1] + true + else + false + end + end + end + alias_method :agent, :user_agent + + # Condition for matching mimetypes. Accepts file extensions. + def provides(*types) + types.map! { |t| mime_types(t) } + types.flatten! + condition do + if type = response['Content-Type'] + types.include? type or types.include? type[/^[^;]+/] + elsif type = request.preferred_type(types) + params = (type.respond_to?(:params) ? type.params : {}) + content_type(type, params) + true + else + false + end + end + end + + def route(verb, path, options = {}, &block) + enable :empty_path_info if path == "" and empty_path_info.nil? + signature = compile!(verb, path, block, **options) + (@routes[verb] ||= []) << signature + invoke_hook(:route_added, verb, path, block) + signature + end + + def invoke_hook(name, *args) + extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } + end + + def generate_method(method_name, &block) + define_method(method_name, &block) + method = instance_method method_name + remove_method method_name + method + end + + def compile!(verb, path, block, **options) + # Because of self.options.host + host_name(options.delete(:host)) if options.key?(:host) + # Pass Mustermann opts to compile() + route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze + + options.each_pair { |option, args| send(option, *args) } + + pattern = compile(path, route_mustermann_opts) + method_name = "#{verb} #{path}" + unbound_method = generate_method(method_name, &block) + conditions, @conditions = @conditions, [] + wrapper = block.arity != 0 ? + proc { |a, p| unbound_method.bind(a).call(*p) } : + proc { |a, p| unbound_method.bind(a).call } + + [ pattern, conditions, wrapper ] + end + + def compile(path, route_mustermann_opts = {}) + Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts)) + end + + def setup_default_middleware(builder) + builder.use ExtendedRack + builder.use ShowExceptions if show_exceptions? + builder.use Rack::MethodOverride if method_override? + builder.use Rack::Head + setup_logging builder + setup_sessions builder + setup_protection builder + end + + def setup_middleware(builder) + middleware.each { |c,a,b| builder.use(c, *a, &b) } + end + + def setup_logging(builder) + if logging? + setup_common_logger(builder) + setup_custom_logger(builder) + elsif logging == false + setup_null_logger(builder) + end + end + + def setup_null_logger(builder) + builder.use Rack::NullLogger + end + + def setup_common_logger(builder) + builder.use Sinatra::CommonLogger + end + + def setup_custom_logger(builder) + if logging.respond_to? :to_int + builder.use Rack::Logger, logging + else + builder.use Rack::Logger + end + end + + def setup_protection(builder) + return unless protection? + options = Hash === protection ? protection.dup : {} + options = { + img_src: "'self' data:", + font_src: "'self'" + }.merge options + + protect_session = options.fetch(:session) { sessions? } + options[:without_session] = !protect_session + + options[:reaction] ||= :drop_session + + builder.use Rack::Protection, options + end + + def setup_sessions(builder) + return unless sessions? + options = {} + options[:secret] = session_secret if session_secret? + options.merge! sessions.to_hash if sessions.respond_to? :to_hash + builder.use session_store, options + end + + def detect_rack_handler + servers = Array(server) + servers.each do |server_name| + begin + return Rack::Handler.get(server_name.to_s) + rescue LoadError, NameError + end + end + fail "Server handler (#{servers.join(',')}) not found." + end + + def inherited(subclass) + subclass.reset! + subclass.set :app_file, caller_files.first unless subclass.app_file? + super + end + + @@mutex = Mutex.new + def synchronize(&block) + if lock? + @@mutex.synchronize(&block) + else + yield + end + end + + # used for deprecation warnings + def warn(message) + super message + "\n\tfrom #{cleaned_caller.first.join(':')}" + end + + # Like Kernel#caller but excluding certain magic entries + def cleaned_caller(keep = 3) + caller(1). + map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. + reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } + end + end + + # Force data to specified encoding. It defaults to settings.default_encoding + # which is UTF-8 by default + def self.force_encoding(data, encoding = default_encoding) + return if data == settings || data.is_a?(Tempfile) + if data.respond_to? :force_encoding + data.force_encoding(encoding).encode! + elsif data.respond_to? :each_value + data.each_value { |v| force_encoding(v, encoding) } + elsif data.respond_to? :each + data.each { |v| force_encoding(v, encoding) } + end + data + end + + def force_encoding(*args) settings.force_encoding(*args) end + + reset! + + set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym + set :raise_errors, Proc.new { test? } + set :dump_errors, Proc.new { !test? } + set :show_exceptions, Proc.new { development? } + set :sessions, false + set :session_store, Rack::Session::Cookie + set :logging, false + set :protection, true + set :method_override, false + set :use_code, false + set :default_encoding, "utf-8" + set :x_cascade, true + set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } + settings.add_charset << /^text\// + set :mustermann_opts, {} + set :default_content_type, 'text/html' + + # explicitly generating a session secret eagerly to play nice with preforking + begin + require 'securerandom' + set :session_secret, SecureRandom.hex(64) + rescue LoadError, NotImplementedError + # SecureRandom raises a NotImplementedError if no random device is available + set :session_secret, "%064x" % Kernel.rand(2**256-1) + end + + class << self + alias_method :methodoverride?, :method_override? + alias_method :methodoverride=, :method_override= + end + + set :run, false # start server via at-exit hook? + set :running_server, nil + set :handler_name, nil + set :traps, true + set :server, %w[HTTP webrick] + set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } + set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) + set :quiet, false + + ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + + if ruby_engine == 'macruby' + server.unshift 'control_tower' + else + server.unshift 'reel' + server.unshift 'puma' + server.unshift 'mongrel' if ruby_engine.nil? + server.unshift 'thin' if ruby_engine != 'jruby' + server.unshift 'trinidad' if ruby_engine == 'jruby' + end + + set :absolute_redirects, true + set :prefixed_redirects, false + set :empty_path_info, nil + set :strict_paths, true + + set :app_file, nil + set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } + set :views, Proc.new { root && File.join(root, 'views') } + set :reload_templates, Proc.new { development? } + set :lock, false + set :threaded, true + + set :public_folder, Proc.new { root && File.join(root, 'public') } + set :static, Proc.new { public_folder && File.exist?(public_folder) } + set :static_cache_control, false + + error ::Exception do + response.status = 500 + content_type 'text/html' + '

    Internal Server Error

    ' + end + + configure :development do + get '/__sinatra__/:image.png' do + filename = __dir__ + "/images/#{params[:image].to_i}.png" + content_type :png + send_file filename + end + + error NotFound do + content_type 'text/html' + + if self.class == Sinatra::Application + code = <<-RUBY.gsub(/^ {12}/, '') + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + RUBY + else + code = <<-RUBY.gsub(/^ {12}/, '') + class #{self.class} + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + end + RUBY + + file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') + code = "# in #{file}\n#{code}" unless file.empty? + end + + (<<-HTML).gsub(/^ {10}/, '') + + + + + + +

    Sinatra doesn’t know this ditty.

    + +
    + Try this: +
    #{Rack::Utils.escape_html(code)}
    +
    + + + HTML + end + end + end + + # Execution context for classic style (top-level) applications. All + # DSL methods executed on main are delegated to this class. + # + # The Application class should not be subclassed, unless you want to + # inherit all settings, routes, handlers, and error pages from the + # top-level. Subclassing Sinatra::Base is highly recommended for + # modular applications. + class Application < Base + set :logging, Proc.new { !test? } + set :method_override, true + set :run, Proc.new { !test? } + set :app_file, nil + + def self.register(*extensions, &block) #:nodoc: + added_methods = extensions.flat_map(&:public_instance_methods) + Delegator.delegate(*added_methods) + super(*extensions, &block) + end + end + + # Sinatra delegation mixin. Mixing this module into an object causes all + # methods to be delegated to the Sinatra::Application class. Used primarily + # at the top-level. + module Delegator #:nodoc: + def self.delegate(*methods) + methods.each do |method_name| + define_method(method_name) do |*args, &block| + return super(*args, &block) if respond_to? method_name + Delegator.target.send(method_name, *args, &block) + end + private method_name + end + end + + delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, + :template, :layout, :before, :after, :error, :not_found, :configure, + :set, :mime_type, :enable, :disable, :use, :development?, :test?, + :production?, :helpers, :settings, :register + + class << self + attr_accessor :target + end + + self.target = Application + end + + class Wrapper + def initialize(stack, instance) + @stack, @instance = stack, instance + end + + def settings + @instance.settings + end + + def helpers + @instance + end + + def call(env) + @stack.call(env) + end + + def inspect + "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" + end + end + + # Create a new Sinatra application; the block is evaluated in the class scope. + def self.new(base = Base, &block) + base = Class.new(base) + base.class_eval(&block) if block_given? + base + end + + # Extend the top-level DSL with the modules provided. + def self.register(*extensions, &block) + Delegator.target.register(*extensions, &block) + end + + # Include the helper modules provided in Sinatra's request context. + def self.helpers(*extensions, &block) + Delegator.target.helpers(*extensions, &block) + end + + # Use the middleware for classic applications. + def self.use(*args, &block) + Delegator.target.use(*args, &block) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/404.png b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/404.png new file mode 100644 index 0000000000..f16a914ff2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/404.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/500.png b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/500.png new file mode 100644 index 0000000000..e08b17d9e6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/images/500.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/indifferent_hash.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/indifferent_hash.rb new file mode 100644 index 0000000000..3043a5a949 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/indifferent_hash.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true +$stderr.puts <:foo and "foo" are + # considered to be the same. + # + # rgb = Sinatra::IndifferentHash.new + # + # rgb[:black] = '#000000' # symbol assignment + # rgb[:black] # => '#000000' # symbol retrieval + # rgb['black'] # => '#000000' # string retrieval + # + # rgb['white'] = '#FFFFFF' # string assignment + # rgb[:white] # => '#FFFFFF' # symbol retrieval + # rgb['white'] # => '#FFFFFF' # string retrieval + # + # Internally, symbols are mapped to strings when used as keys in the entire + # writing interface (calling e.g. []=, merge). This mapping + # belongs to the public interface. For example, given: + # + # hash = Sinatra::IndifferentHash.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 = Sinatra::IndifferentHash.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 Sinatra. + class IndifferentHash < Hash + def self.[](*args) + new.merge!(Hash[*args]) + end + + def initialize(*args) + args.map!(&method(:convert_value)) + + super(*args) + end + + def default(*args) + args.map!(&method(:convert_key)) + + super(*args) + end + + def default=(value) + super(convert_value(value)) + end + + def assoc(key) + super(convert_key(key)) + end + + def rassoc(value) + super(convert_value(value)) + end + + def fetch(key, *args) + args.map!(&method(:convert_value)) + + super(convert_key(key), *args) + end + + def [](key) + super(convert_key(key)) + end + + def []=(key, value) + super(convert_key(key), convert_value(value)) + end + + alias_method :store, :[]= + + def key(value) + super(convert_value(value)) + end + + def key?(key) + super(convert_key(key)) + end + + alias_method :has_key?, :key? + alias_method :include?, :key? + alias_method :member?, :key? + + def value?(value) + super(convert_value(value)) + end + + alias_method :has_value?, :value? + + def delete(key) + super(convert_key(key)) + end + + def dig(key, *other_keys) + super(convert_key(key), *other_keys) + end if method_defined?(:dig) # Added in Ruby 2.3 + + def fetch_values(*keys) + keys.map!(&method(:convert_key)) + + super(*keys) + end if method_defined?(:fetch_values) # Added in Ruby 2.3 + + def slice(*keys) + keys.map!(&method(:convert_key)) + + self.class[super(*keys)] + end if method_defined?(:slice) # Added in Ruby 2.5 + + def values_at(*keys) + keys.map!(&method(:convert_key)) + + super(*keys) + end + + def merge!(*other_hashes) + other_hashes.each do |other_hash| + if other_hash.is_a?(self.class) + super(other_hash) + else + other_hash.each_pair do |key, value| + key = convert_key(key) + value = yield(key, self[key], value) if block_given? && key?(key) + self[key] = convert_value(value) + end + end + end + + self + end + + alias_method :update, :merge! + + def merge(*other_hashes, &block) + dup.merge!(*other_hashes, &block) + end + + def replace(other_hash) + super(other_hash.is_a?(self.class) ? other_hash : self.class[other_hash]) + end + + if method_defined?(:transform_values!) # Added in Ruby 2.4 + def transform_values(&block) + dup.transform_values!(&block) + end + + def transform_values! + super + super(&method(:convert_value)) + end + end + + if method_defined?(:transform_keys!) # Added in Ruby 2.5 + def transform_keys(&block) + dup.transform_keys!(&block) + end + + def transform_keys! + super + super(&method(:convert_key)) + end + end + + private + + def convert_key(key) + key.is_a?(Symbol) ? key.to_s : key + end + + def convert_value(value) + case value + when Hash + value.is_a?(self.class) ? value : self.class[value] + when Array + value.map(&method(:convert_value)) + else + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/main.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/main.rb new file mode 100644 index 0000000000..e4231c30f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/main.rb @@ -0,0 +1,54 @@ +module Sinatra + ParamsConfig = {} + + if ARGV.any? + require 'optparse' + parser = OptionParser.new { |op| + op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) } + op.on('-s server', 'specify rack server/handler') { |val| ParamsConfig[:server] = val } + op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true } + op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true } + op.on('-e env', 'set the environment (default is development)') do |val| + ENV['RACK_ENV'] = val + ParamsConfig[:environment] = val.to_sym + end + op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val| + ParamsConfig[:bind] = val + end + } + begin + parser.parse!(ARGV.dup) + rescue => evar + ParamsConfig[:optparse_error] = evar + end + end + + require 'sinatra/base' + + class Application < Base + + # we assume that the first file that requires 'sinatra' is the + # app_file. all other path related options are calculated based + # on this path by default. + set :app_file, caller_files.first || $0 + + set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } + + if run? && ARGV.any? + error = ParamsConfig.delete(:optparse_error) + raise error if error + ParamsConfig.each { |k, v| set k, v } + end + end + + remove_const(:ParamsConfig) + at_exit { Application.run! if $!.nil? && Application.run? } +end + +# include would include the module in Object +# extend only extends the `main` object +extend Sinatra::Delegator + +class Rack::Builder + include Sinatra::Delegator +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/show_exceptions.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/show_exceptions.rb new file mode 100644 index 0000000000..de468c0a4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/show_exceptions.rb @@ -0,0 +1,362 @@ +# frozen_string_literal: true + +require 'rack/show_exceptions' + +module Sinatra + # Sinatra::ShowExceptions catches all exceptions raised from the app it + # wraps. It shows a useful backtrace with the sourcefile and clickable + # context, the whole Rack environment and the request data. + # + # Be careful when you use this on public-facing sites as it could reveal + # information helpful to attackers. + class ShowExceptions < Rack::ShowExceptions + @@eats_errors = Object.new + def @@eats_errors.flush(*) end + def @@eats_errors.puts(*) end + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + rescue Exception => e + errors, env["rack.errors"] = env["rack.errors"], @@eats_errors + + if prefers_plain_text?(env) + content_type = "text/plain" + body = dump_exception(e) + else + content_type = "text/html" + body = pretty(env, e) + end + + env["rack.errors"] = errors + + [ + 500, + { + "Content-Type" => content_type, + "Content-Length" => body.bytesize.to_s + }, + [body] + ] + end + + def template + TEMPLATE + end + + private + + def bad_request?(e) + Sinatra::BadRequest === e + end + + def prefers_plain_text?(env) + !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && + [/curl/].index { |item| item =~ env["HTTP_USER_AGENT"] } + end + + def frame_class(frame) + if frame.filename =~ %r{lib/sinatra.*\.rb} + "framework" + elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || + frame.filename =~ %r{/bin/(\w+)\z} + "system" + else + "app" + end + end + +TEMPLATE = ERB.new <<-HTML # :nodoc: + + + + + <%=h exception.class %> at <%=h path %> + + + + + + +
    + + +
    +

    BACKTRACE

    +

    (expand)

    + +
    + +
      + + <% id = 1 %> + <% frames.each do |frame| %> + <% if frame.context_line && frame.context_line != "#" %> + +
    • + <%=h frame.filename %> in + <%=h frame.function %> +
    • + +
    • + <% if frame.pre_context %> +
        + <% frame.pre_context.each do |line| %> +
      1. <%=h line %>
      2. + <% end %> +
      + <% end %> + +
        +
      1. <%= + h frame.context_line %>
      2. +
      + + <% if frame.post_context %> +
        + <% frame.post_context.each do |line| %> +
      1. <%=h line %>
      2. + <% end %> +
      + <% end %> +
      +
    • + + <% end %> + + <% id += 1 %> + <% end %> + +
    +
    + + <% unless bad_request?(exception) %> +
    +

    GET

    + <% if req.GET and not req.GET.empty? %> + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No GET data.

    + <% end %> +
    +
    + +
    +

    POST

    + <% if req.POST and not req.POST.empty? %> + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No POST data.

    + <% end %> +
    +
    + <% end %> + +
    + + <% unless req.cookies.empty? %> + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No cookie data.

    + <% end %> +
    +
    + +
    +

    Rack ENV

    + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val %>
    +
    +
    + +

    You're seeing this error because you have +enabled the show_exceptions setting.

    +
    + + +HTML + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/version.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/version.rb new file mode 100644 index 0000000000..d229a497ed --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/version.rb @@ -0,0 +1,3 @@ +module Sinatra + VERSION = '2.1.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/sinatra.gemspec b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/sinatra.gemspec new file mode 100644 index 0000000000..db5e10134d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/sinatra.gemspec @@ -0,0 +1,55 @@ +version = File.read(File.expand_path("../VERSION", __FILE__)).strip + +Gem::Specification.new 'sinatra', version do |s| + s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." + s.summary = "Classy web-development dressed in a DSL" + s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] + s.email = "sinatrarb@googlegroups.com" + s.homepage = "http://sinatrarb.com/" + s.license = 'MIT' + s.files = Dir['README*.md', 'lib/**/*', 'examples/*'] + [ + ".yardopts", + "AUTHORS.md", + "CHANGELOG.md", + "CONTRIBUTING.md", + "Gemfile", + "LICENSE", + "MAINTENANCE.md", + "Rakefile", + "SECURITY.md", + "sinatra.gemspec", + "VERSION"] + s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } + s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' + s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc --encoding=UTF-8] + + if s.respond_to?(:metadata) + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra', + 'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md', + 'homepage_uri' => 'http://sinatrarb.com/', + 'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues', + 'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb', + 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra' + } + else + msg = "RubyGems 2.0 or newer is required to protect against public "\ + "gem pushes. You can update your rubygems version by running:\n\n"\ + "gem install rubygems-update\n"\ + "update_rubygems\n"\ + "gem update --system" + raise <<-EOF +RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: + gem install rubygems-update + update_rubygems: + gem update --system +EOF + end + + s.required_ruby_version = '>= 2.3.0' + + s.add_dependency 'rack', '~> 2.2' + s.add_dependency 'tilt', '~> 2.0' + s.add_dependency 'rack-protection', version + s.add_dependency 'mustermann', '~> 1.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/.yardopts b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/.yardopts new file mode 100644 index 0000000000..60e00e9a35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/.yardopts @@ -0,0 +1,5 @@ +--readme README.md +--title 'Sinatra API Documentation' +--charset utf-8 +--markup markdown +'lib/**/*.rb' - '*.md' diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/AUTHORS.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/AUTHORS.md new file mode 100644 index 0000000000..925eb91ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/AUTHORS.md @@ -0,0 +1,81 @@ +Sinatra was designed and developed by Blake Mizerany in California. + +### Current Team + +* **Konstantin Haase** (maintainer) +* **Zachary Scott** +* **Kashyap Kondamudi** +* **Ashley Williams** +* **Trevor Bramble** +* **Kunpei Sakai** + +### Alumni + +* **Blake Mizerany** (creator) +* **Ryan Tomayko** +* **Simon Rozet** +* **Katrina Owen** + +### Thanks + +Sinatra would not have been possible without strong company backing. +In the past, financial and emotional support have been provided mainly by +[Heroku](http://heroku.com), [GitHub](https://github.com) and +[Engine Yard](http://www.engineyard.com/), and is now taken care of by +[Travis CI](http://travis-ci.com/). + +Special thanks to the following extraordinary individuals, without whom +Sinatra would not be possible: + +* [Ryan Tomayko](http://tomayko.com/) (rtomayko) for constantly fixing + whitespace errors __60d5006__ +* [Ezra Zygmuntowicz](http://brainspl.at/) (ezmobius) for initial help and + letting Blake steal some of merbs internal code. +* [Chris Schneider](http://gittr.com) (cschneid) for The Book, the blog, + [irclogger.com](http://irclogger.com/sinatra/), and a bunch of useful + patches. +* [Markus Prinz](http://nuclearsquid.com/) (cypher) for patches over the + years, caring about the README, and hanging in there when times were rough. +* [Erik Kastner](http://metaatem.net/) (kastner) for fixing `MIME_TYPES` under + Rack 0.5. +* [Ben Bleything](http://blog.bleything.net/) (bleything) for caring about HTTP + status codes and doc fixes. +* [Igal Koshevoy](http://twitter.com/igalko) (igal) for root path detection under + Thin/Passenger. +* [Jon Crosby](http://joncrosby.me/) (jcrosby) for coffee breaks, doc fixes, and + just because, man. +* [Karel Minarik](https://github.com/karmi) (karmi) for screaming until the + website came back up. +* [Jeremy Evans](http://code.jeremyevans.net/) (jeremyevans) for unbreaking + optional path params (twice!) +* [The GitHub guys](https://github.com/) for stealing Blake's table. +* [Nickolas Means](http://nmeans.org/) (nmeans) for Sass template support. +* [Victor Hugo Borja](https://github.com/vic) (vic) for splat'n routes specs and + doco. +* [Avdi Grimm](http://avdi.org/) (avdi) for basic RSpec support. +* [Jack Danger Canty](http://jåck.com/) for a more accurate root directory + and for making me watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just + now. +* Mathew Walker for making escaped paths work with static files. +* Millions of Us for having the problem that led to Sinatra's conception. +* [Songbird](http://getsongbird.com/) for the problems that helped Sinatra's + future become realized. +* [Rick Olson](http://techno-weenie.net/) (technoweenie) for the killer plug + at RailsConf '08. +* Steven Garcia for the amazing custom artwork you see on 404's and 500's +* [Pat Nakajima](http://patnakajima.com/) (nakajima) for fixing non-nested + params in nested params Hash's. +* Gabriel Andretta for having people wonder whether our documentation is + actually in English or in Spanish. +* Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang, + Mickael Riga, Bernhard Essl, Janos Hardi, Kouhei Yanagita and + "burningTyger" for willingly translating whatever ends up in the README. +* [Wordy](https://wordy.com/) for proofreading our README. **73e137d** +* cactus for digging through code and specs, multiple times. +* Nicolás Sanguinetti (foca) for strong demand of karma and shaping + helpers/register. + +And last but not least: + +* [Frank Sinatra](http://www.sinatra.com/) (chairman of the board) for having so much class he + deserves a web-framework named after him. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CHANGELOG.md new file mode 100644 index 0000000000..06048f86ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CHANGELOG.md @@ -0,0 +1,1639 @@ +## 2.2.3 / 2022-11-25 + +* Fix: Escape filename in the Content-Disposition header. [#1841](https://github.com/sinatra/sinatra/pull/1841) by Kunpei Sakai + +* Fix: fixed ReDoS for Rack::Protection::IPSpoofing. [#1823](https://github.com/sinatra/sinatra/pull/1823) by @ooooooo-q + +## 2.2.2 / 2022-07-23 + +* Update mustermann dependency to version 2. + +## 2.2.1 / 2022-07-15 + +* Fix JRuby regression by using ruby2_keywords for delegation. [#1750](https://github.com/sinatra/sinatra/pull/1750) by Patrik Ragnarsson + +* Add JRuby to CI. [#1755](https://github.com/sinatra/sinatra/pull/1755) by Karol Bucek + +## 2.2.0 / 2022-02-15 + +* Breaking change: Add #select, #reject and #compact methods to Sinatra::IndifferentHash. If hash keys need to be converted to symbols, call #to_h to get a Hash instance first. #1711 by Olivier Bellone + +* Handle EOFError raised by Rack and return Bad Request 400 status. [#1743](https://github.com/sinatra/sinatra/pull/1743) by tamazon + +* Update README.es.md with removal of Thin. [#1630](https://github.com/sinatra/sinatra/pull/1630) by Espartaco Palma + +* Minor refactors in `base.rb`. [#1640](https://github.com/sinatra/sinatra/pull/1640) by ceclinux + +* Fixed typos in german README.md. [#1648](https://github.com/sinatra/sinatra/pull/1648) by Juri + +* Add escaping to the static 404 page. [#1645](https://github.com/sinatra/sinatra/pull/1645) by Chris Gavin + +* Remove `detect_rack_handler` method. [#1652](https://github.com/sinatra/sinatra/pull/1652) by ceclinux + +* Respect content type set in superclass before filter. Fixes [#1647](https://github.com/sinatra/sinatra/issues/1647) [#1649](https://github.com/sinatra/sinatra/pull/1649) by Jordan Owens + +* Update README.ja.md with removal of Thin. [#1629](https://github.com/sinatra/sinatra/pull/1629) by Ryuichi KAWAMATA + +* *Revert "Use prepend instead of include for helpers.* [#1662](https://github.com/sinatra/sinatra/pull/1662) by namusyaka + +* Various minor fixes to README.md. [#1663](https://github.com/sinatra/sinatra/pull/1663) by Yanis Zafirópulos + +* Document when `dump_errors` is enabled. Fixes [#1664](https://github.com/sinatra/sinatra/issues/1664) [#1665](https://github.com/sinatra/sinatra/pull/1665) by Patrik Ragnarsson + +* Update README.pt-br.md. [#1668](https://github.com/sinatra/sinatra/pull/1668) by Vitor Oliveira + +* Fix usage of inherited `Sinatra::Base` classes keyword arguments. Fixes [#1669](https://github.com/sinatra/sinatra/issues/1669) [#1670](https://github.com/sinatra/sinatra/pull/1670) by Cadu Ribeiro + +* Reduce RDoc generation time by not including every README. Fixes [#1578](https://github.com/sinatra/sinatra/issues/1578) [#1671](https://github.com/sinatra/sinatra/pull/1671) by Eloy Pérez + +* Add support for per form csrf tokens. Fixes [#1616](https://github.com/sinatra/sinatra/issues/1616) [#1653](https://github.com/sinatra/sinatra/pull/1653) by Jordan Owens + +* Update MAINTENANCE.md with the `stable` branch status. [#1681](https://github.com/sinatra/sinatra/pull/1681) by Fredrik Rubensson + +* Validate expanded path matches `public_dir` when serving static files. [#1683](https://github.com/sinatra/sinatra/pull/1683) by cji-stripe + +* Fix Delegator to pass keyword arguments for Ruby 3.0. [#1684](https://github.com/sinatra/sinatra/pull/1684) by andrewtblake + +* Fix use with keyword arguments for Ruby 3.0. [#1701](https://github.com/sinatra/sinatra/pull/1701) by Robin Wallin + +* Fix memory leaks for proc template. Fixes [#1704](https://github.com/sinatra/sinatra/issues/1714) [#1719](https://github.com/sinatra/sinatra/pull/1719) by Slevin + +* Remove unnecessary `test_files` from the gemspec. [#1712](https://github.com/sinatra/sinatra/pull/1712) by Masataka Pocke Kuwabara + +### CI + +* Use latest JRuby 9.2.16.0 on CI. [#1682](https://github.com/sinatra/sinatra/pull/1682) by Olle Jonsson + +* Switch CI from travis to Github actions. [#1691](https://github.com/sinatra/sinatra/pull/1691) by namusyaka + +* Skip the Slack action if `secrets.SLACK_WEBHOOK` is not set. [#1705](https://github.com/sinatra/sinatra/pull/1705) by Robin Wallin + +* Small CI improvements. [#1703](https://github.com/sinatra/sinatra/pull/1703) by Robin Wallin + +* Drop auto-generated boilerplate comments from CI configuration file. [#1728](https://github.com/sinatra/sinatra/pull/1728) by Olle Jonsson + +### sinatra-contrib + +* Do not raise when key is an enumerable. [#1619](https://github.com/sinatra/sinatra/pull/1619) by Ulysse Buonomo + +### Rack protection + +* Fix broken `origin_whitelist` option. Fixes [#1641](https://github.com/sinatra/sinatra/issues/1641) [#1642](https://github.com/sinatra/sinatra/pull/1642) by Takeshi YASHIRO + +## 2.1.0 / 2020-09-05 + +* Fix additional Ruby 2.7 keyword warnings [#1586](https://github.com/sinatra/sinatra/pull/1586) by Stefan Sundin + +* Drop Ruby 2.2 support [#1455](https://github.com/sinatra/sinatra/pull/1455) by Eloy Pérez + +* Add Rack::Protection::ReferrerPolicy [#1291](https://github.com/sinatra/sinatra/pull/1291) by Stefan Sundin + +* Add `default_content_type` setting. Fixes [#1238](https://github.com/sinatra/sinatra/pull/1238) [#1239](https://github.com/sinatra/sinatra/pull/1239) by Mike Pastore + +* Allow `set :` in sinatra-namespace [#1255](https://github.com/sinatra/sinatra/pull/1255) by Christian Höppner + +* Use prepend instead of include for helpers. Fixes [#1213](https://github.com/sinatra/sinatra/pull/1213) [#1214](https://github.com/sinatra/sinatra/pull/1214) by Mike Pastore + +* Fix issue with passed routes and provides Fixes [#1095](https://github.com/sinatra/sinatra/pull/1095) [#1606](https://github.com/sinatra/sinatra/pull/1606) by Mike Pastore, Jordan Owens + +* Add QuietLogger that excludes pathes from Rack::CommonLogger [1250](https://github.com/sinatra/sinatra/pull/1250) by Christoph Wagner + +* Sinatra::Contrib dependency updates. Fixes [#1207](https://github.com/sinatra/sinatra/pull/1207) [#1411](https://github.com/sinatra/sinatra/pull/1411) by Mike Pastore + +* Allow CSP to fallback to default-src. Fixes [#1484](https://github.com/sinatra/sinatra/pull/1484) [#1490](https://github.com/sinatra/sinatra/pull/1490) by Jordan Owens + +* Replace `origin_whitelist` with `permitted_origins`. Closes [#1620](https://github.com/sinatra/sinatra/issues/1620) [#1625](https://github.com/sinatra/sinatra/pull/1625) by rhymes + +* Use Rainbows instead of thin for async/stream features. Closes [#1624](https://github.com/sinatra/sinatra/issues/1624) [#1627](https://github.com/sinatra/sinatra/pull/1627) by Ryuichi KAWAMATA + +* Enable EscapedParams if passed via settings. Closes [#1615](https://github.com/sinatra/sinatra/issues/1615) [#1632](https://github.com/sinatra/sinatra/issues/1632) by Anders Bälter + +* Support for parameters in mime types. Fixes [#1141](https://github.com/sinatra/sinatra/issues/1141) by John Hope + +* Handle null byte when serving static files [#1574](https://github.com/sinatra/sinatra/issues/1574) by Kush Fanikiso + +* Improve development support and documentation and source code by Olle Jonsson, Pierre-Adrien Buisson, Shota Iguchi + +## 2.0.8.1 / 2020-01-02 + +* Allow multiple hashes to be passed in `merge` and `merge!` for `Sinatra::IndifferentHash` [#1572](https://github.com/sinatra/sinatra/pull/1572) by Shota Iguchi + +## 2.0.8 / 2020-01-01 + +* Lookup Tilt class for template engine without loading files [#1558](https://github.com/sinatra/sinatra/pull/1558). Fixes [#1172](https://github.com/sinatra/sinatra/issues/1172) by Jordan Owens + +* Add request info in NotFound exception [#1566](https://github.com/sinatra/sinatra/pull/1566) by Stefan Sundin + +* Add `.yaml` support in `Sinatra::Contrib::ConfigFile` [#1564](https://github.com/sinatra/sinatra/issues/1564). Fixes [#1563](https://github.com/sinatra/sinatra/issues/1563) by Emerson Manabu Araki + +* Remove only routing parameters from @params hash [#1569](https://github.com/sinatra/sinatra/pull/1569). Fixes [#1567](https://github.com/sinatra/sinatra/issues/1567) by Jordan Owens, Horacio + +* Support `capture` and `content_for` with Hamlit [#1580](https://github.com/sinatra/sinatra/pull/1580) by Takashi Kokubun + +* Eliminate warnings of keyword parameter for Ruby 2.7.0 [#1581](https://github.com/sinatra/sinatra/pull/1581) by Osamtimizer + +## 2.0.7 / 2019-08-22 + +* Fix a regression [#1560](https://github.com/sinatra/sinatra/pull/1560) by Kunpei Sakai + +## 2.0.6 / 2019-08-21 + +* Fix an issue setting environment from command line option [#1547](https://github.com/sinatra/sinatra/pull/1547), [#1554](https://github.com/sinatra/sinatra/pull/1554) by Jordan Owens, Kunpei Sakai + +* Support pandoc as a new markdown renderer [#1533](https://github.com/sinatra/sinatra/pull/1533) by Vasiliy + +* Remove outdated code for tilt 1.x [#1532](https://github.com/sinatra/sinatra/pull/1532) by Vasiliy + +* Remove an extra logic for `force_encoding` [#1527](https://github.com/sinatra/sinatra/pull/1527) by Jordan Owens + +* Avoid multiple errors even if `params` contains special values [#1526](https://github.com/sinatra/sinatra/pull/1527) by Kunpei Sakai + +* Support `bundler/inline` with `require 'sinatra'` integration [#1520](https://github.com/sinatra/sinatra/pull/1520) by Kunpei Sakai + +* Avoid `TypeError` when params contain a key without a value on Ruby < 2.4 [#1516](https://github.com/sinatra/sinatra/pull/1516) by Samuel Giddins + +* Improve development support and documentation and source code by Olle Jonsson, Basavanagowda Kanur, Yuki MINAMIYA + +## 2.0.5 / 2018-12-22 + +* Avoid FrozenError when params contains frozen value [#1506](https://github.com/sinatra/sinatra/pull/1506) by Kunpei Sakai + +* Add support for Erubi [#1494](https://github.com/sinatra/sinatra/pull/1494) by @tkmru + +* `IndifferentHash` monkeypatch warning improvements [#1477](https://github.com/sinatra/sinatra/pull/1477) by Mike Pastore + +* Improve development support and documentation and source code by Anusree Prakash, Jordan Owens, @ceclinux and @krororo. + +### sinatra-contrib + +* Add `flush` option to `content_for` [#1225](https://github.com/sinatra/sinatra/pull/1225) by Shota Iguchi + +* Drop activesupport dependency from sinatra-contrib [#1448](https://github.com/sinatra/sinatra/pull/1448) + +* Update `yield_content` to append default to ERB template buffer [#1500](https://github.com/sinatra/sinatra/pull/1500) by Jordan Owens + +### rack-protection + +* Don't track the Accept-Language header by default [#1504](https://github.com/sinatra/sinatra/pull/1504) by Artem Chistyakov + +## 2.0.4 / 2018-09-15 + +* Don't blow up when passing frozen string to `send_file` disposition [#1137](https://github.com/sinatra/sinatra/pull/1137) by Andrew Selder + +* Fix ubygems LoadError [#1436](https://github.com/sinatra/sinatra/pull/1436) by Pavel Rosický + +* Unescape regex captures [#1446](https://github.com/sinatra/sinatra/pull/1446) by Jordan Owens + +* Slight performance improvements for IndifferentHash [#1427](https://github.com/sinatra/sinatra/pull/1427) by Mike Pastore + +* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider + +## 2.0.3 / 2018-06-09 + +* Fix the backports gem regression [#1442](https://github.com/sinatra/sinatra/issues/1442) by Marc-André Lafortune + +## 2.0.2 / 2018-06-05 + +* Escape invalid query parameters [#1432](https://github.com/sinatra/sinatra/issues/1432) by Kunpei Sakai + * The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627). + +* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](https://github.com/sinatra/sinatra/issues/1431) by Arpit Chauhan + +* Add xml content-types to valid html_types for Rack::Protection [#1413](https://github.com/sinatra/sinatra/issues/1413) by Reenan Arbitrario + +* Encode route parameters using :default_encoding setting [#1412](https://github.com/sinatra/sinatra/issues/1412) by Brian m. Carlson + +* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](https://github.com/sinatra/sinatra/issues/1244) by John Hope + +* Add Sinatra::IndifferentHash#slice [#1405](https://github.com/sinatra/sinatra/issues/1405) by Shota Iguchi + +* Remove status code 205 from drop body response [#1398](https://github.com/sinatra/sinatra/issues/1398) by Shota Iguchi + +* Ignore empty captures from params [#1390](https://github.com/sinatra/sinatra/issues/1390) by Shota Iguchi + +* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien + +## 2.0.1 / 2018-02-17 + +* Repair nested namespaces, by avoiding prefix duplication [#1322](https://github.com/sinatra/sinatra/issues/1322). Fixes [#1310](https://github.com/sinatra/sinatra/issues/1310) by Kunpei Sakai + +* Add pattern matches to values for Mustermann::Concat [#1333](https://github.com/sinatra/sinatra/issues/1333). Fixes [#1332](https://github.com/sinatra/sinatra/issues/1332) by Dawa Ometto + +* Ship the VERSION file with the gem, to allow local unpacking [#1338](https://github.com/sinatra/sinatra/issues/1338) by Olle Jonsson + +* Fix issue with custom error handler on bad request [#1351](https://github.com/sinatra/sinatra/issues/1351). Fixes [#1350](https://github.com/sinatra/sinatra/issues/1350) by Jordan Owens + +* Override Rack::ShowExceptions#pretty to set custom template [#1377](https://github.com/sinatra/sinatra/issues/1377). Fixes [#1376](https://github.com/sinatra/sinatra/issues/1376) by Jordan Owens + +* Enhanced path validation in Windows [#1379](https://github.com/sinatra/sinatra/issues/1379) by Orange Tsai from DEVCORE + * The patch fixes [CVE-2018-7212](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7212) + +* Improve development support and documentation by Faheel Ahmad, Shota Iguchi, Olle Jonsson, Manabu Niseki, John Hope, Horacio, Ice-Storm, GraniteRock, Raman Skaskevich, Carlos Azuaje, 284km, Dan Rice and Zachary Scott + +## 2.0.0 / 2017-04-10 + + * Use Mustermann for patterns [#1086](https://github.com/sinatra/sinatra/issues/1086) by Konstantin Haase + + * Server now provides `-q` flag for quiet mode, which disables start/stop messages [#1153](https://github.com/sinatra/sinatra/issues/1153) by Vasiliy. + + * Session middleware can now be specified with `:session_store` setting [#1161](https://github.com/sinatra/sinatra/issues/1161) by Jordan Owens. + + * `APP_ENV` is now preferred and recommended over `RACK_ENV` for setting environment [#984](https://github.com/sinatra/sinatra/issues/984) by Damien Mathieu. + + * Add Reel support [#793](https://github.com/sinatra/sinatra/issues/793) by Patricio Mac Adden. + + * Make route params available during error handling [#895](https://github.com/sinatra/sinatra/issues/895) by Jeremy Evans. + + * Unify `not_found` and `error` 404 behavior [#896](https://github.com/sinatra/sinatra/issues/896) by Jeremy Evans. + + * Enable Ruby 2.3 `frozen_string_literal` feature [#1076](https://github.com/sinatra/sinatra/issues/1076) by Vladimir Kochnev. + + * Add Sinatra::ShowExceptions::TEMPLATE and patched Rack::ShowExceptions to prefer Sinatra template by Zachary Scott. + + * Sinatra::Runner is used internally for integration tests [#840](https://github.com/sinatra/sinatra/issues/840) by Nick Sutterer. + + * Fix case-sensitivity issue in `uri` method [#889](https://github.com/sinatra/sinatra/issues/889) by rennex. + + * Use `Rack::Utils.status_code` to allow `status` helper to use symbol as well as numeric codes [#968](https://github.com/sinatra/sinatra/issues/968) by Tobias H. Michaelsen. + + * Improved error handling for invalid params through Rack [#1070](https://github.com/sinatra/sinatra/issues/1070) by Jordan Owens. + + * Ensure template is cached only once [#1021](https://github.com/sinatra/sinatra/issues/1021) by Patrik Rak. + + * Rack middleware is initialized at server runtime rather than after receiving first request [#1205](https://github.com/sinatra/sinatra/issues/1205) by Itamar Turner-Trauring. + + * Improve Session Secret documentation to encourage better security practices [#1218](https://github.com/sinatra/sinatra/issues/1218) by Glenn Rempe + + * Exposed global and per-route options for Mustermann route parsing [#1233](https://github.com/sinatra/sinatra/issues/1233) by Mike Pastore + + * Use same `session_secret` for classic and modular apps in development [#1245](https://github.com/sinatra/sinatra/issues/1245) by Marcus Stollsteimer + + * Make authenticity token length a fixed value of 32 [#1181](https://github.com/sinatra/sinatra/issues/1181) by Jordan Owens + + * Modernize Rack::Protection::ContentSecurityPolicy with CSP Level 2 and 3 Directives [#1202](https://github.com/sinatra/sinatra/issues/1202) by Glenn Rempe + + * Adds preload option to Rack:Protection:StrictTransport [#1209](https://github.com/sinatra/sinatra/issues/1209) by Ed Robinson + + * Improve BadRequest logic. Raise and handle exceptions if status is 400 [#1212](https://github.com/sinatra/sinatra/issues/1212) by Mike Pastore + + * Make Rack::Test a development dependency [#1232](https://github.com/sinatra/sinatra/issues/1232) by Mike Pastore + + * Capture exception messages of raised NotFound and BadRequest [#1210](https://github.com/sinatra/sinatra/issues/1210) by Mike Pastore + + * Add explicit set method to contrib/cookies to override cookie settings [#1240](https://github.com/sinatra/sinatra/issues/1240) by Andrew Allen + + * Avoid executing filters even if prefix matches with other namespace [#1253](https://github.com/sinatra/sinatra/issues/1253) by namusyaka + + * Make `#has_key?` also indifferent in access, can accept String or Symbol [#1262](https://github.com/sinatra/sinatra/issues/1262) by Stephen Paul Weber + + * Add `allow_if` option to bypass json csrf protection [#1265](https://github.com/sinatra/sinatra/issues/1265) by Jordan Owens + + * rack-protection: Bundle StrictTransport, CookieTossing, and CSP [#1267](https://github.com/sinatra/sinatra/issues/1267) by Mike Pastore + + * Add `:strict_paths` option for managing trailing slashes [#1273](https://github.com/sinatra/sinatra/issues/1273) by namusyaka + + * Add full IndifferentHash implementation to params [#1279](https://github.com/sinatra/sinatra/issues/1279) by Mike Pastore + +## 1.4.8 / 2017-01-30 + + * Fix the deprecation warning from Ruby about Fixnum. [#1235](https://github.com/sinatra/sinatra/issues/1235) by Akira Matsuda + +## 1.4.7 / 2016-01-24 + + * Add Ashley Williams, Trevor Bramble, and Kashyap Kondamudi to team Sinatra. + + * Correctly handle encoded colons in routes. (Jeremy Evans) + + * Rename CHANGES to CHANGELOG.md and update Rakefile. [#1043](https://github.com/sinatra/sinatra/issues/1043) (Eliza Sorensen) + + * Improve documentation. [#941](https://github.com/sinatra/sinatra/issues/941), [#1069](https://github.com/sinatra/sinatra/issues/1069), [#1075](https://github.com/sinatra/sinatra/issues/1075), [#1025](https://github.com/sinatra/sinatra/issues/1025), [#1052](https://github.com/sinatra/sinatra/issues/1052) (Many great folks) + + * Introduce `Sinatra::Ext` to workaround Rack 1.6 bug to fix Ruby 1.8.7 + support. [#1080](https://github.com/sinatra/sinatra/issues/1080) (Zachary Scott) + + * Add CONTRIBUTING guide. [#987](https://github.com/sinatra/sinatra/issues/987) (Katrina Owen) + + +## 1.4.6 / 2015-03-23 + + * Improve tests and documentation. (Darío Hereñú, Seiichi Yonezawa, kyoendo, + John Voloski, Ferenc-, Renaud Martinet, Christian Haase, marocchino, + huoxito, Damir Svrtan, Amaury Medeiros, Jeremy Evans, Kashyap, shenqihui, + Ausmarton Fernandes, kami, Vipul A M, Lei Wu, 7stud, Taylor Shuler, + namusyaka, burningTyger, Cornelius Bock, detomastah, hakeda, John Hope, + Ruben Gonzalez, Andrey Deryabin, attilaolah, Anton Davydov, Nikita Penzin, + Dyego Costa) + + * Remove duplicate require of sinatra/base. (Alexey Muranov) + + * Escape HTML in 404 error page. (Andy Brody) + + * Refactor to method call in `Stream#close` and `#callback`. (Damir Svrtan) + + * Depend on latest version of Slim. (Damir Svrtan) + + * Fix compatibility with Tilt version 2. (Yegor Timoschenko) + + * Fix compatibility issue with Rack `pretty` method from ShowExceptions. + (Kashyap) + + * Show date in local time in exception messages. (tayler1) + + * Fix logo on error pages when using Ruby 1.8. (Jeremy Evans) + + * Upgrade test suite to Minitest version 5 and fix Ruby 2.2 compatibility. + (Vipul A M) + +## 1.4.5 / 2014-04-08 + + * Improve tests and documentation. (Seiichi Yonezawa, Mike Gehard, Andrew + Deitrick, Matthew Nicholas Bradley, GoGo tanaka, Carlos Lazo, Shim Tw, + kyoendo, Roman Kuznietsov, Stanislav Chistenko, Ryunosuke SATO, Ben Lewis, + wuleicanada, Patricio Mac Adden, Thais Camilo) + + * Fix Ruby warnings. (Vipul A M, Piotr Szotkowski) + + * Fix template cache memory leak. (Scott Holden) + + * Work around UTF-8 bug in JRuby. (namusyaka) + + * Don't set charset for JSON mime-type (Sebastian Borrazas) + + * Fix bug in request.accept? that might trigger a NoMethodError. (sbonami) + +## 1.4.4 / 2013-10-21 + + * Allow setting layout to false specifically for a single rendering engine. + (Matt Wildig) + + * Allow using wildcard in argument passed to `request.accept?`. (wilkie) + + * Treat missing Accept header like wild card. (Patricio Mac Adden) + + * Improve tests and documentation. (Darío Javier Cravero, Armen P., michelc, + Patricio Mac Adden, Matt Wildig, Vipul A M, utenmiki, George Timoschenko, + Diogo Scudelletti) + + * Fix Ruby warnings. (Vipul A M, Patricio Mac Adden) + + * Improve self-hosted server started by `run!` method or in classic mode. + (Tobias Bühlmann) + + * Reduce objects allocated per request. (Vipul A M) + + * Drop unused, undocumented options hash from Sinatra.new. (George Timoschenko) + + * Keep Content-Length header when response is a `Rack::File` or when streaming. + (Patricio Mac Adden, George Timoschenko) + + * Use reel if it's the only server available besides webrick. (Tobias Bühlmann) + + * Add `disable :traps` so setting up signal traps for self hosted server can be + skipped. (George Timoschenko) + + * The `status` option passed to `send_file` may now be a string. (George + Timoschenko) + + * Reduce file size of dev mode images for 404 and 500 pages. (Francis Go) + +## 1.4.3 / 2013-06-07 + + * Running a Sinatra file directly or via `run!` it will now ignore an + empty $PORT env variable. (noxqsgit) + + * Improve documentation. (burningTyger, Patricio Mac Adden, + Konstantin Haase, Diogo Scudelletti, Dominic Imhof) + + * Expose matched pattern as env["sinatra.route"]. (Aman Gupta) + + * Fix warning on Ruby 2.0. (Craig Little) + + * Improve running subset of tests in isolation. (Viliam Pucik) + + * Reorder private/public methods. (Patricio Mac Adden) + + * Loosen version dependency for rack, so it runs with Rails 3.2. + (Konstantin Haase) + + * Request#accept? now returns true instead of a truthy value. (Alan Harris) + +## 1.4.2 / 2013-03-21 + + * Fix parsing error for case where both the pattern and the captured part + contain a dot. (Florian Hanke, Konstantin Haase) + + * Missing Accept header is treated like */*. (Greg Denton) + + * Improve documentation. (Patricio Mac Adden, Joe Bottigliero) + +## 1.4.1 / 2013-03-15 + + * Make delegated methods available in config.ru (Konstantin Haase) + +## 1.4.0 / 2013-03-15 + + * Add support for LINK and UNLINK requests. (Konstantin Haase) + + * Add support for Yajl templates. (Jamie Hodge) + + * Add support for Rabl templates. (Jesse Cooke) + + * Add support for Wlang templates. (Bernard Lambeau) + + * Add support for Stylus templates. (Juan David Pastas, Konstantin Haase) + + * You can now pass a block to ERb, Haml, Slim, Liquid and Wlang templates, + which will be used when calling `yield` in the template. (Alexey Muranov) + + * When running in classic mode, no longer include Sinatra::Delegator in Object, + instead extend the main object only. (Konstantin Haase) + + * Improved route parsing: "/:name.?:format?" with "/foo.png" now matches to + {name: "foo", format: "png"} instead of {name: "foo.png"}. (Florian Hanke) + + * Add :status option support to send_file. (Konstantin Haase) + + * The `provides` condition now respects an earlier set content type. + (Konstantin Haase) + + * Exception#code is only used when :use_code is enabled. Moreover, it will + be ignored if the value is not between 400 and 599. You should use + Exception#http_status instead. (Konstantin Haase) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + + * Sinatra::Base.new now returns a Sinatra::Wrapper instance, exposing + `#settings` and `#helpers`, yet going through the middleware stack on + `#call`. It also implements a nice `#inspect`, so it plays nice with + Rails' `rake routes`. (Konstantin Haase) + + * In addition to WebRick, Thin and Mongrel, Sinatra will now automatically pick + up Puma, Trinidad, ControlTower or Net::HTTP::Server when installed. The + logic for picking the server has been improved and now depends on the Ruby + implementation used. (Mark Rada, Konstantin Haase, Patricio Mac Adden) + + * "Sinatra doesn't know this ditty" pages now show the app class when running + a modular application. This helps detecting where the response came from when + combining multiple modular apps. (Konstantin Haase) + + * When port is not set explicitly, use $PORT env variable if set and only + default to 4567 if not. Plays nice with foreman. (Konstantin Haase) + + * Allow setting layout on a per engine basis. (Zachary Scott, Konstantin Haase) + + * You can now use `register` directly in a classic app. (Konstantin Haase) + + * `redirect` now accepts URI or Addressable::URI instances. (Nicolas + Sanguinetti) + + * Have Content-Disposition header also include file name for `inline`, not + just for `attachment`. (Konstantin Haase) + + * Better compatibility to Rack 1.5. (James Tucker, Konstantin Haase) + + * Make route parsing regex more robust. (Zoltan Dezso, Konstantin Haase) + + * Improve Accept header parsing, expose parameters. (Pieter van de Bruggen, + Konstantin Haase) + + * Add `layout_options` render option. Allows you, amongst other things, to + render a layout from a different folder. (Konstantin Haase) + + * Explicitly setting `layout` to `nil` is treated like setting it to `false`. + (richo) + + * Properly escape attributes in Content-Type header. (Pieter van de Bruggen) + + * Default to only serving localhost in development mode. (Postmodern) + + * Setting status code to 404 in error handler no longer triggers not_found + handler. (Konstantin Haase) + + * The `protection` option now takes a `session` key for force + disabling/enabling session based protections. (Konstantin Haase) + + * Add `x_cascade` option to disable `X-Cascade` header on missing route. + (Konstantin Haase) + + * Improve documentation. (Kashyap, Stanislav Chistenko, Zachary Scott, + Anthony Accomazzo, Peter Suschlik, Rachel Mehl, ymmtmsys, Anurag Priyam, + burningTyger, Tony Miller, akicho8, Vasily Polovnyov, Markus Prinz, + Alexey Muranov, Erik Johnson, Vipul A M, Konstantin Haase) + + * Convert documentation to Markdown. (Kashyap, Robin Dupret, burningTyger, + Vasily Polovnyov, Iain Barnett, Giuseppe Capizzi, Neil West) + + * Don't set not_found content type to HTML in development mode with custom + not_found handler. (Konstantin Haase) + + * Fix mixed indentation for private methods. (Robin Dupret) + + * Recalculate Content-Length even if hard coded if body is reset. Relevant + mostly for error handlers. (Nathan Esquenazi, Konstantin Haase) + + * Plus sign is once again kept as such when used for URL matches. (Konstantin + Haase) + + * Take views option into account for template caching. (Konstantin Haase) + + * Consistent use of `headers` instead of `header` internally. (Patricio Mac Adden) + + * Fix compatibility to RDoc 4. (Bohuslav Kabrda) + + * Make chat example work with latest jQuery. (loveky, Tony Miller) + + * Make tests run without warnings. (Patricio Mac Adden) + + * Make sure value returned by `mime_type` is a String or nil, even when a + different object is passed in, like an AcceptEntry. (Konstantin Haase) + + * Exceptions in `after` filter are now handled like any other exception. + (Nathan Esquenazi) + +## 1.3.6 (backport release) / 2013-03-15 + +Backported from 1.4.0: + + * Take views option into account for template caching. (Konstantin Haase) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + +## 1.3.5 / 2013-02-25 + + * Fix for RubyGems 2.0 (Uchio KONDO) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + +## 1.3.4 / 2013-01-26 + + * Improve documentation. (Kashyap, Stanislav Chistenko, Konstantin Haase, + ymmtmsys, Anurag Priyam) + + * Adjustments to template system to work with Tilt edge. (Konstantin Haase) + + * Fix streaming with latest Rack release. (Konstantin Haase) + + * Fix default content type for Sinatra::Response with latest Rack release. + (Konstantin Haase) + + * Fix regression where + was no longer treated like space. (Ross Boucher) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + +## 1.3.3 / 2012-08-19 + + * Improved documentation. (burningTyger, Konstantin Haase, Gabriel Andretta, + Anurag Priyam, michelc) + + * No longer modify the load path. (Konstantin Haase) + + * When keeping a stream open, set up callback/errback correctly to deal with + clients closing the connection. (Konstantin Haase) + + * Fix bug where having a query param and a URL param by the same name would + concatenate the two values. (Konstantin Haase) + + * Prevent duplicated log output when application is already wrapped in a + `Rack::CommonLogger`. (Konstantin Haase) + + * Fix issue where `Rack::Link` and Rails were preventing indefinite streaming. + (Konstantin Haase) + + * No longer cause warnings when running Ruby with `-w`. (Konstantin Haase) + + * HEAD requests on static files no longer report a Content-Length of 0, but + instead the proper length. (Konstantin Haase) + + * When protecting against CSRF attacks, drop the session instead of refusing + the request. (Konstantin Haase) + +## 1.3.2 / 2011-12-30 + + * Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it, + too. (Konstantin Haase) + + * Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`. + (Konstantin Haase) + + * Route specific params are now available in the block passed to #stream. + (Konstantin Haase) + + * Fix bug where rendering a second template in the same request, after the + first one raised an exception, skipped the default layout. (Nathan Baum) + + * Fix bug where parameter escaping got enabled when disabling a different + protection. (Konstantin Haase) + + * Fix regression: Filters without a pattern may now again manipulate the params + hash. (Konstantin Haase) + + * Added examples directory. (Konstantin Haase) + + * Improved documentation. (Gabriel Andretta, Markus Prinz, Erick Zetta, Just + Lest, Adam Vaughan, Aleksander Dąbrowski) + + * Improved MagLev support. (Tim Felgentreff) + +## 1.3.1 / 2011-10-05 + + * Support adding more than one callback to the stream object. (Konstantin + Haase) + + * Fix for infinite loop when streaming on 1.9.2 with Thin from a modular + application (Konstantin Haase) + +## 1.3.0 / 2011-09-30 + + * Added `stream` helper method for easily creating streaming APIs, Server + Sent Events or even WebSockets. See README for more on that topic. + (Konstantin Haase) + + * If a HTTP 1.1 client is redirected from a different verb than GET, use 303 + instead of 302 by default. You may still pass 302 explicitly. Fixes AJAX + redirects in Internet Explorer 9 (to be fair, everyone else is doing it + wrong and IE is behaving correct). (Konstantin Haase) + + * Added support for HTTP PATCH requests. (Konstantin Haase) + + * Use rack-protection to defend against common opportunistic attacks. + (Josh Lane, Jacob Burkhart, Konstantin Haase) + + * Support for Creole templates, Creole is a standardized wiki markup, + supported by many wiki implementations. (Konstanin Haase) + + * The `erubis` method has been deprecated. If Erubis is available, Sinatra + will automatically use it for rendering ERB templates. `require 'erb'` + explicitly to prevent that behavior. (Magnus Holm, Ryan Tomayko, Konstantin + Haase) + + * Patterns now match against the escaped URLs rather than the unescaped + version. This makes Sinatra confirm with RFC 2396 section 2.2 and RFC 2616 + section 3.2.3 (escaped reserved characters should not be treated like the + unescaped version), meaning that "/:name" will also match `/foo%2Fbar`, but + not `/foo/bar`. To avoid incompatibility, pattern matching has been + adjusted. Moreover, since we do no longer need to keep an unescaped version + of path_info around, we handle all changes to `env['PATH_INFO']` correctly. + (Konstantin Haase) + + * `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in + modular applications. (Konstantin Haase) + + * Set up `Rack::Logger` or `Rack::NullLogger` depending on whether logging + was enabled or not. Also, expose that logger with the `logger` helper + method. (Konstantin Haase) + + * The sessions setting may be an options hash now. (Konstantin Haase) + + * Important: Ruby 1.8.6 support has been dropped. This version also depends + on at least Rack 1.3.0. This means that it is incompatible with Rails prior + to 3.1.0. Please use 1.2.x if you require an earlier version of Ruby or + Rack, which we will continue to supply with bug fixes. (Konstantin Haase) + + * Renamed `:public` to `:public_folder` to avoid overriding Ruby's built-in + `public` method/keyword. `set(:public, ...)` is still possible but shows a + warning. (Konstantin Haase) + + * It is now possible to use a different target class for the top level DSL + (aka classic style) than `Sinatra::Application` by setting + `Delegator.target`. This was mainly introduced to ease testing. (Konstantin + Haase) + + * Error handlers defined for an error class will now also handle subclasses + of that class, unless more specific error handlers exist. (Konstantin + Haase) + + * Error handling respects Exception#code, again. (Konstantin Haase) + + * Changing a setting will merge hashes: `set(:x, :a => 1); set(:x :b => 2)` + will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to + avoid this behavior. (Konstantin Haase) + + * Added `request.accept?` and `request.preferred_type` to ease dealing with + `Accept` headers. (Konstantin Haase) + + * Added `:static_cache_control` setting to automatically set cache control + headers to static files. (Kenichi Nakamura) + + * Added `informal?`, `success?`, `redirect?`, `client_error?`, + `server_error?` and `not_found?` helper methods to ease dealing with status + codes. (Konstantin Haase) + + * Uses SecureRandom to generate default session secret. (Konstantin Haase) + + * The `attachment` helper will set Content-Type (if it hasn't been set yet) + depending on the supplied file name. (Vasiliy Ermolovich) + + * Conditional requests on `etag` helper now work properly for unsafe HTTP + methods. (Matthew Schinckel, Konstantin Haase) + + * The `last_modified` helper does not stop execution and change the status code + if the status code is something different than 200. (Konstantin Haase) + + * Added support for If-Unmodified-Since header. (Konstantin Haase) + + * `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew + Armenia) + + * `Sinatra::Base.run!` takes a block allowing access to the Rack handler. + (David Waite) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Exception objects are now passed to error handlers. (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * Middleware setup is now distributed across multiple methods, allowing + Sinatra extensions to easily hook into the setup process. (Konstantin + Haase) + + * Internal refactoring and minor performance improvements. (Konstantin Haase) + + * Move Sinatra::VERSION to separate file, so it can be checked without + loading Sinatra. (Konstantin Haase) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + +## 1.2.9 (backports release) / 2013-03-15 + +IMPORTANT: THIS IS THE LAST 1.2.x RELEASE, PLEASE UPGRADE. + + * Display EOL warning when loading Sinatra. (Konstantin Haase) + + * Improve documentation. (Anurag Priyam, Konstantin Haase) + + * Do not modify the load path. (Konstantin Haase) + + * Display deprecation warning if RUBY_IGNORE_CALLERS is used. (Konstantin Haase) + + * Add backports library so we can still run on Ruby 1.8.6. (Konstantin Haase) + +## 1.2.8 (backports release) / 2011-12-30 + +Backported from 1.3.2: + +* Fix bug where rendering a second template in the same request after the + first one raised an exception skipped the default layout (Nathan Baum) + +## 1.2.7 (backports release) / 2011-09-30 + +Custom changes: + + * Fix Ruby 1.8.6 issue with Accept header parsing. (Konstantin Haase) + +Backported from 1.3.0: + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Fix uninitialized instance variable warning. (David Kellum) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + +## 1.2.6 / 2011-05-01 + + * Fix broken delegation, backport delegation tests from Sinatra 1.3. + (Konstantin Haase) + +## 1.2.5 / 2011-04-30 + + * Restore compatibility with Ruby 1.8.6. (Konstantin Haase) + +## 1.2.4 / 2011-04-30 + + * Sinatra::Application (classic style) does not use a session secret in + development mode, so sessions are not invalidated after every request when + using Shotgun. (Konstantin Haase) + + * The request object was shared between multiple Sinatra instances in the + same middleware chain. This caused issues if any non-sinatra routing + happened in-between two of those instances, or running a request twice + against an application (described in the README). The caching was reverted. + See GH[#239](https://github.com/sinatra/sinatra/issues/239) and GH[#256](https://github.com/sinatra/sinatra/issues/256) for more infos. (Konstantin Haase) + + * Fixes issues where the top level DSL was interfering with method_missing + proxies. This issue surfaced when Rails 3 was used with older Sass versions + and Sinatra >= 1.2.0. (Konstantin Haase) + + * Sinatra::Delegator.delegate is now able to delegate any method names, even + those containing special characters. This allows better integration into + other programming languages on Rubinius (probably on the JVM, too), like + Fancy. (Konstantin Haase) + + * Remove HEAD request logic and let Rack::Head handle it instead. (Paolo + "Nusco" Perrotta) + +## 1.2.3 / 2011-04-13 + + * This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2, + however, if you want to use a newer Tilt version, you have to upgrade to at + least this version of Sinatra. (Konstantin Haase) + + * Helpers dealing with time, like `expires`, handle objects that pretend to be + numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase) + +## 1.2.2 / 2011-04-08 + + * The `:provides => :js` condition now matches both `application/javascript` + and `text/javascript`. The `:provides => :xml` condition now matches both + `application/xml` and `text/xml`. The `Content-Type` header is set + accordingly. If the client accepts both, the `application/*` version is + preferred, since the `text/*` versions are deprecated. (Konstantin Haase) + + * The `provides` condition now handles wildcards in `Accept` headers correctly. + Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`. + (Konstantin Haase) + + * When parsing `Accept` headers, `Content-Type` preferences are honored + according to RFC 2616 section 14.1. (Konstantin Haase) + + * URIs passed to the `url` helper or `redirect` may now use any schema to be + identified as absolute URIs, not only `http` or `https`. (Konstantin Haase) + + * Handles `Content-Type` strings that already contain parameters correctly in + `content_type` (example: `content_type "text/plain; charset=utf-16"`). + (Konstantin Haase) + + * If a route with an empty pattern is defined (`get("") { ... }`) requests with + an empty path info match this route instead of "/". (Konstantin Haase) + + * In development environment, when running under a nested path, the image URIs + on the error pages are set properly. (Konstantin Haase) + +## 1.2.1 / 2011-03-17 + + * Use a generated session secret when using `enable :sessions`. (Konstantin + Haase) + + * Fixed a bug where the wrong content type was used if no content type was set + and a template engine was used with a different engine for the layout with + different default content types, say Less embedded in Slim. (Konstantin + Haase) + + * README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé, + Gregor Schmidt) + +## 1.2.0 / 2011-03-03 + + * Added `slim` rendering method for rendering Slim templates. (Steve + Hodgkiss) + + * The `markaby` rendering method now allows passing a block, making inline + usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase) + + * All render methods now take a `:layout_engine` option, allowing to use a + layout in a different template language. Even more useful than using this + directly (`erb :index, :layout_engine => :haml`) is setting this globally for + a template engine that otherwise does not support layouts, like Markdown or + Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase) + + * Before and after filters now support conditions, both with and without + patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase) + + * Added a `url` helper method which constructs absolute URLs. Copes with + reverse proxies and Rack handlers correctly. Aliased to `to`, so you can + write `redirect to('/foo')`. (Konstantin Haase) + + * If running on 1.9, patterns for routes and filters now support named + captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`. + (Steve Price) + + * All rendering methods now take a `:scope` option, which renders them in + another context. Note that helpers and instance variables will be + unavailable if you use this feature. (Paul Walker) + + * The behavior of `redirect` can now be configured with `absolute_redirects` + and `prefixed_redirects`. (Konstantin Haase) + + * `send_file` now allows overriding the Last-Modified header, which defaults + to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase) + + * You can use your own template lookup method by defining `find_template`. + This allows, among other things, using more than one views folder. + (Konstantin Haase) + + * Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel + Andretta, Konstantin Haase) + + * Improved error handling. (cactus, Konstantin Haase) + + * Skip missing template engines in tests correctly. (cactus) + + * Sinatra now ships with a Gemfile for development dependencies, since it eases + supporting different platforms, like JRuby. (Konstantin Haase) + +## 1.1.4 (backports release) / 2011-04-13 + + * Compatible with Tilt 1.3. (Konstantin Haase) + +## 1.1.3 / 2011-02-20 + + * Fixed issues with `user_agent` condition if the user agent header is missing. + (Konstantin Haase) + + * Fix some routing tests that have been skipped by accident (Ross A. Baker) + + * Fix rendering issues with Builder and Nokogiri (Konstantin Haase) + + * Replace last_modified helper with better implementation. (cactus, + Konstantin Haase) + + * Fix issue with charset not being set when using `provides` condition. + (Konstantin Haase) + + * Fix issue with `render` not picking up all alternative file extensions for + a rendering engine - it was not possible to register ".html.erb" without + tricks. (Konstantin Haase) + +## 1.1.2 / 2010-10-25 + +Like 1.1.1, but with proper CHANGES file. + +## 1.1.1 / 2010-10-25 + + * README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov) + and Portuguese (Luciano Sousa). + + * Nested templates without a `:layout` option can now be used from the layout + template without causing an infinite loop. (Konstantin Haase) + + * Inline templates are now encoding aware and can therefore be used with + unicode characters on Ruby 1.9. Magic comments at the beginning of the file + will be honored. (Konstantin Haase) + + * Default `app_file` is set correctly when running with bundler. Using + bundler caused Sinatra not to find the `app_file` and therefore not to find + the `views` folder on it's own. (Konstantin Haase) + + * Better handling of Content-Type when using `send_file`: If file extension + is unknown, fall back to `application/octet-stream` and do not override + content type if it has already been set, except if `:type` is passed + explicitly (Konstantin Haase) + + * Path is no longer cached if changed between handlers that do pattern + matching. This means you can change `request.path_info` in a pattern + matching before filter. (Konstantin Haase) + + * Headers set by cache_control now always set max_age as an Integer, making + sure it is compatible with RFC2616. (Konstantin Haase) + + * Further improved handling of string encodings on Ruby 1.9, templates now + honor default_encoding and URLs support unicode characters. (Konstantin + Haase) + +## 1.1.0 / 2010-10-24 + + * Before and after filters now support pattern matching, including the + ability to use captures: "before('/user/:name') { |name| ... }". This + avoids manual path checking. No performance loss if patterns are avoided. + (Konstantin Haase) + + * It is now possible to render SCSS files with the `scss` method, which + behaves exactly like `sass` except for the different file extension and + assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase) + + * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`, + `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown, + Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates. + (Konstantin Haase) + + * Now supports byte-range requests (the HTTP_RANGE header) for static files. + Multi-range requests are not supported, however. (Jens Alfke) + + * You can now use #settings method from class and top level for convenience. + (Konstantin Haase) + + * Setting multiple values now no longer relies on #to_hash and therefore + accepts any Enumerable as parameter. (Simon Rozet) + + * Nested templates default the `layout` option to `false` rather than `true`. + This eases the use of partials. If you wanted to render one haml template + embedded in another, you had to call `haml :partial, {}, :layout => false`. + As you almost never want the partial to be wrapped in the standard layout + in this situation, you now only have to call `haml :partial`. Passing in + `layout` explicitly is still possible. (Konstantin Haase) + + * If a the return value of one of the render functions is used as a response + body and the content type has not been set explicitly, Sinatra chooses a + content type corresponding to the rendering engine rather than just using + "text/html". (Konstantin Haase) + + * README is now available in Chinese (Wu Jiang), French (Mickael Riga), + German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos + Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese + README has been updated (Kouhei Yanagita). + + * It is now possible to access Sinatra's template_cache from the outside. + (Nick Sutterer) + + * The `last_modified` method now also accepts DateTime instances and makes + sure the header will always be set to a string. (Konstantin Haase) + + * 599 now is a legal status code. (Steve Shreeve) + + * This release is compatible with Ruby 1.9.2. Sinatra was trying to read + non existent files Ruby added to the call stack. (Shota Fukumori, + Konstantin Haase) + + * Prevents a memory leak on 1.8.6 in production mode. Note, however, that + this is due to a bug in 1.8.6 and request will have the additional overhead + of parsing templates again on that version. It is recommended to use at + least Ruby 1.8.7. (Konstantin Haase) + + * Compares last modified date correctly. `last_modified` was halting only + when the 'If-Modified-Since' header date was equal to the time specified. + Now, it halts when is equal or later than the time specified (Gabriel + Andretta). + + * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra + application under a subpath in Rails 3, the PATH_INFO is not prefixed with + a slash and no routes did match. (José Valim) + + * Better handling of encodings in 1.9, defaults params encoding to UTF-8. + (Konstantin Haase) + + * `show_exceptions` handling is now triggered after custom error handlers, if + it is set to `:after_handlers`, thus not disabling those handler in + development mode. (pangel, Konstantin Haase) + + * Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino) + + * `send_file` now always respects the `:type` option if set. Previously it + was discarded if no matching mime type was found, which made it impossible + to directly pass a mime type. (Konstantin Haase) + + * `redirect` always redirects to an absolute URI, even if a relative URI was + passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe + Garcia Ballester, Anthony Williams) + + * Broken examples for using Erubis, Haml and Test::Unit in README have been + fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden) + + * Sinatra now handles SIGTERM correctly. (Patrick Collison) + + * Fixes an issue with inline templates in modular applications that manually + call `run!`. (Konstantin Haase) + + * Spaces after inline template names are now ignored (Konstantin Haase) + + * It's now possible to use Sinatra with different package management + systems defining a custom require. (Konstantin Haase) + + * Lighthouse has been dropped in favor of GitHub issues. + + * Tilt is now a dependency and therefore no longer ships bundled with + Sinatra. (Ryan Tomayko, Konstantin Haase) + + * Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported. + (Konstantin Haase) + +## 1.0 / 2010-03-23 + + * It's now possible to register blocks to run after each request using + after filters. After filters run at the end of each request, after + routes and error handlers. (Jimmy Schementi) + + * Sinatra now uses Tilt for rendering + templates. This adds support for template caching, consistent + template backtraces, and support for new template engines, like + mustache and liquid. (Ryan Tomayko) + + * ERB, Erubis, and Haml templates are now compiled the first time + they're rendered instead of being string eval'd on each invocation. + Benchmarks show a 5x-10x improvement in render time. This also + reduces the number of objects created, decreasing pressure on Ruby's + GC. (Ryan Tomayko) + + * New 'settings' method gives access to options in both class and request + scopes. This replaces the 'options' method. (Chris Wanstrath) + + * New boolean 'reload_templates' setting controls whether template files + are reread from disk and recompiled on each request. Template read/compile + is cached by default in all environments except development. (Ryan Tomayko) + + * New 'erubis' helper method for rendering ERB template with Erubis. The + erubis gem is required. (Dylan Egan) + + * New 'cache_control' helper method provides a convenient way of + setting the Cache-Control response header. Takes a variable number + of boolean directives followed by a hash of value directives, like + this: cache_control :public, :must_revalidate, :max_age => 60 + (Ryan Tomayko) + + * New 'expires' helper method is like cache_control but takes an + integer number of seconds or Time object: + expires 300, :public, :must_revalidate + (Ryan Tomayko) + + * New request.secure? method for checking for an SSL connection. + (Adam Wiggins) + + * Sinatra apps can now be run with a `-o ` argument to specify + the address to bind to. (Ryan Tomayko) + + * Rack::Session::Cookie is now added to the middleware pipeline when + running in test environments if the :sessions option is set. + (Simon Rozet) + + * Route handlers, before filters, templates, error mappings, and + middleware are now resolved dynamically up the inheritance hierarchy + when needed instead of duplicating the superclass's version when + a new Sinatra::Base subclass is created. This should fix a variety + of issues with extensions that need to add any of these things + to the base class. (Ryan Tomayko) + + * Exception error handlers always override the raise_errors option now. + Previously, all exceptions would be raised outside of the application + when the raise_errors option was enabled, even if an error handler was + defined for that exception. The raise_errors option now controls + whether unhandled exceptions are raised (enabled) or if a generic 500 + error is returned (disabled). (Ryan Tomayko) + + * The X-Cascade response header is set to 'pass' when no matching route + is found or all routes pass. (Josh Peek) + + * Filters do not run when serving static files anymore. (Ryan Tomayko) + + * pass takes an optional block to be used as the route handler if no + subsequent route matches the request. (Blake Mizerany) + +The following Sinatra features have been obsoleted (removed entirely) in +the 1.0 release: + + * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test` + module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`, + `put_it`, `delete_it`, and `head_it` helper methods. The + [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should + be used instead. + + * Test framework specific libraries (`sinatra/test/spec`, + `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See + http://www.sinatrarb.com/testing.html for instructions on setting up a + testing environment under each of these frameworks. + + * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead. + `Sinatra::Base` acts more like `Sinatra::Default` in development mode. + For example, static file serving and sexy development error pages are + enabled by default. + + * Auto-requiring template libraries in the `erb`, `builder`, `haml`, + and `sass` methods is obsolete due to thread-safety issues. You must + require the template libraries explicitly in your app. + + * The `:views_directory` option to rendering methods is obsolete; use + `:views` instead. + + * The `:haml` and `:sass` options to rendering methods are obsolete. + Template engine options should be passed in the second Hash argument + instead. + + * The `use_in_file_templates` method is obsolete. Use + `enable :inline_templates` or `set :inline_templates, 'path/to/file'` + + * The 'media_type' helper method is obsolete. Use 'mime_type' instead. + + * The 'mime' main and class method is obsolete. Use 'mime_type' instead. + + * The request-level `send_data` method is no longer supported. + + * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer + supported. This may effect extensions written for versions prior to 0.9.2. + See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html) + for the officially supported extensions API. + + * The `set_option` and `set_options` methods are obsolete; use `set` + instead. + + * The `:env` setting (`settings.env`) is obsolete; use `:environment` + instead. + + * The request level `stop` method is obsolete; use `halt` instead. + + * The request level `entity_tag` method is obsolete; use `etag` + instead. + + * The request level `headers` method (HTTP response headers) is obsolete; + use `response['Header-Name']` instead. + + * `Sinatra.application` is obsolete; use `Sinatra::Application` instead. + + * Using `Sinatra.application = nil` to reset an application is obsolete. + This should no longer be necessary. + + * Using `Sinatra.default_options` to set base configuration items is + obsolete; use `Sinatra::Base.set(key, value)` instead. + + * The `Sinatra::ServerError` exception is obsolete. All exceptions raised + within a request are now treated as internal server errors and result in + a 500 response status. + + * The `:methodoverride' option to enable/disable the POST _method hack is + obsolete; use `:method_override` instead. + +## 0.9.2 / 2009-05-18 + + * This version is compatible with Rack 1.0. [Rein Henrichs] + + * The development-mode unhandled exception / error page has been + greatly enhanced, functionally and aesthetically. The error + page is used when the :show_exceptions option is enabled and an + exception propagates outside of a route handler or before filter. + [Simon Rozet / Matte Noble / Ryan Tomayko] + + * Backtraces that move through templates now include filenames and + line numbers where possible. [#51 / S. Brent Faulkner] + + * All templates now have an app-level option for setting default + template options (:haml, :sass, :erb, :builder). The app-level + option value must be a Hash if set and is merged with the + template options specified to the render method (Base#haml, + Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko] + + * The method signature for all template rendering methods has + been unified: "def engine(template, options={}, locals={})". + The options Hash now takes the generic :views, :layout, and + :locals options but also any template-specific options. The + generic options are removed before calling the template specific + render method. Locals may be specified using either the + :locals key in the options hash or a second Hash option to the + rendering method. [#191 / Ryan Tomayko] + + * The receiver is now passed to "configure" blocks. This + allows for the following idiom in top-level apps: + configure { |app| set :foo, app.root + '/foo' } + [TJ Holowaychuck / Ryan Tomayko] + + * The "sinatra/test" lib is deprecated and will be removed in + Sinatra 1.0. This includes the Sinatra::Test module and + Sinatra::TestHarness class in addition to all the framework + test helpers that were deprecated in 0.9.1. The Rack::Test + lib should be used instead: http://gitrdoc.com/brynary/rack-test + [#176 / Simon Rozet] + + * Development mode source file reloading has been removed. The + "shotgun" (http://rtomayko.github.com/shotgun/) program can be + used to achieve the same basic functionality in most situations. + Passenger users should use the "tmp/always_restart.txt" + file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko] + + * Auto-requiring template libs in the erb, builder, haml, and + sass methods is deprecated due to thread-safety issues. You must + require the template libs explicitly in your app file. [Simon Rozet] + + * A new Sinatra::Base#route_missing method was added. route_missing + is sent when no route matches the request or all route handlers + pass. The default implementation forwards the request to the + downstream app when running as middleware (i.e., "@app" is + non-nil), or raises a NotFound exception when no downstream app + is defined. Subclasses can override this method to perform custom + route miss logic. [Jon Crosby] + + * A new Sinatra::Base#route_eval method was added. The method + yields to the block and throws :halt with the result. Subclasses + can override this method to tap into the route execution logic. + [TJ Holowaychuck] + + * Fix the "-x" (enable request mutex / locking) command line + argument. Passing -x now properly sets the :lock option. + [S. Brent Faulkner, Ryan Tomayko] + + * Fix writer ("foo=") and predicate ("foo?") methods in extension + modules not being added to the registering class. + [#172 / Pat Nakajima] + + * Fix in-file templates when running alongside activesupport and + fatal errors when requiring activesupport before sinatra + [#178 / Brian Candler] + + * Fix various issues running on Google AppEngine. + [Samuel Goebert, Simon Rozet] + + * Fix in-file templates __END__ detection when __END__ exists with + other stuff on a line [Yoji Shidara] + +## 0.9.1.1 / 2009-03-09 + + * Fix directory traversal vulnerability in default static files + route. See [#177] for more info. + +## 0.9.1 / 2009-03-01 + + * Sinatra now runs under Ruby 1.9.1 [#61] + + * Route patterns (splats, :named, or Regexp captures) are now + passed as arguments to the block. [#140] + + * The "helpers" method now takes a variable number of modules + along with the normal block syntax. [#133] + + * New request-level #forward method for middleware components: passes + the env to the downstream app and merges the response status, headers, + and body into the current context. [#126] + + * Requests are now automatically forwarded to the downstream app when + running as middleware and no matching route is found or all routes + pass. + + * New simple API for extensions/plugins to add DSL-level and + request-level methods. Use Sinatra.register(mixin) to extend + the DSL with all public methods defined in the mixin module; + use Sinatra.helpers(mixin) to make all public methods defined + in the mixin module available at the request level. [#138] + See http://www.sinatrarb.com/extensions.html for details. + + * Named parameters in routes now capture the "." character. This makes + routes like "/:path/:filename" match against requests like + "/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt". + Previously, the route would not match at all. + + * Added request-level "redirect back" to redirect to the referring + URL. + + * Added a new "clean_trace" option that causes backtraces dumped + to rack.errors and displayed on the development error page to + omit framework and core library backtrace lines. The option is + enabled by default. [#77] + + * The ERB output buffer is now available to helpers via the @_out_buf + instance variable. + + * It's now much easier to test sessions in unit tests by passing a + ":session" option to any of the mock request methods. e.g., + get '/', {}, :session => { 'foo' => 'bar' } + + * The testing framework specific files ('sinatra/test/spec', + 'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated. + See http://sinatrarb.com/testing.html for instructions on setting up + a testing environment with these frameworks. + + * The request-level #send_data method from Sinatra 0.3.3 has been added + for compatibility but is deprecated. + + * Fix :provides causing crash on any request when request has no + Accept header [#139] + + * Fix that ERB templates were evaluated twice per "erb" call. + + * Fix app-level middleware not being run when the Sinatra application is + run as middleware. + + * Fixed some issues with running under Rack's CGI handler caused by + writing informational stuff to stdout. + + * Fixed that reloading was sometimes enabled when starting from a + rackup file [#110] + + * Fixed that "." in route patterns erroneously matched any character + instead of a literal ".". [#124] + +## 0.9.0.4 / 2009-01-25 + + * Using halt with more than 1 args causes ArgumentError [#131] + * using halt in a before filter doesn't modify response [#127] + * Add deprecated Sinatra::EventContext to unbreak plugins [#130] + * Give access to GET/POST params in filters [#129] + * Preserve non-nested params in nested params hash [#117] + * Fix backtrace dump with Rack::Lint [#116] + +## 0.9.0.3 / 2009-01-21 + + * Fall back on mongrel then webrick when thin not found. [#75] + * Use :environment instead of :env in test helpers to + fix deprecation warnings coming from framework. + * Make sinatra/test/rspec work again [#113] + * Fix app_file detection on windows [#118] + * Fix static files with Rack::Lint in pipeline [#121] + +## 0.9.0.2 / 2009-01-18 + + * Halting a before block should stop processing of routes [#85] + * Fix redirect/halt in before filters [#85] + +## 0.9.0 / 2009-01-18 + + * Works with and requires Rack >= 0.9.1 + + * Multiple Sinatra applications can now co-exist peacefully within a + single process. The new "Sinatra::Base" class can be subclassed to + establish a blank-slate Rack application or middleware component. + Documentation on using these features is forth-coming; the following + provides the basic gist: http://gist.github.com/38605 + + * Parameters with subscripts are now parsed into a nested/recursive + Hash structure. e.g., "post[title]=Hello&post[body]=World" yields + params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. + + * Regular expressions may now be used in route pattens; captures are + available at "params[:captures]". + + * New ":provides" route condition takes an array of mime types and + matches only when an Accept request header is present with a + corresponding type. [cypher] + + * New request-level "pass" method; immediately exits the current block + and passes control to the next matching route. + + * The request-level "body" method now takes a block; evaluation is + deferred until an attempt is made to read the body. The block must + return a String or Array. + + * New "route conditions" system for attaching rules for when a route + matches. The :agent and :host route options now use this system. + + * New "dump_errors" option controls whether the backtrace is dumped to + rack.errors when an exception is raised from a route. The option is + enabled by default for top-level apps. + + * Better default "app_file", "root", "public", and "views" location + detection; changes to "root" and "app_file" automatically cascade to + other options that depend on them. + + * Error mappings are now split into two distinct layers: exception + mappings and custom error pages. Exception mappings are registered + with "error(Exception)" and are run only when the app raises an + exception. Custom error pages are registered with "error(status_code)", + where "status_code" is an integer, and are run any time the response + has the status code specified. It's also possible to register an error + page for a range of status codes: "error(500..599)". + + * In-file templates are now automatically imported from the file that + requires 'sinatra'. The use_in_file_templates! method is still available + for loading templates from other files. + + * Sinatra's testing support is no longer dependent on Test::Unit. Requiring + 'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness + class, which can be used with any test framework. The 'sinatra/test/unit', + 'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files + can be required to setup a framework-specific testing environment. See the + README for more information. + + * Added support for Bacon (test framework). The 'sinatra/test/bacon' file + can be required to setup Sinatra test helpers on Bacon::Context. + + * Deprecated "set_option" and "set_options"; use "set" instead. + + * Deprecated the "env" option ("options.env"); use "environment" instead. + + * Deprecated the request level "stop" method; use "halt" instead. + + * Deprecated the request level "entity_tag" method; use "etag" instead. + Both "entity_tag" and "etag" were previously supported. + + * Deprecated the request level "headers" method (HTTP response headers); + use "response['Header-Name']" instead. + + * Deprecated "Sinatra.application"; use "Sinatra::Application" instead. + + * Deprecated setting Sinatra.application = nil to reset an application. + This should no longer be necessary. + + * Deprecated "Sinatra.default_options"; use + "Sinatra::Default.set(key, value)" instead. + + * Deprecated the "ServerError" exception. All Exceptions are now + treated as internal server errors and result in a 500 response + status. + + * Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it" + test helper methods. Use "get", "post", "put", "delete", and "head", + respectively, instead. + + * Removed Event and EventContext classes. Applications are defined in a + subclass of Sinatra::Base; each request is processed within an + instance. + +## 0.3.3 / 2009-01-06 + + * Pin to Rack 0.4.0 (this is the last release on Rack 0.4) + + * Log unhandled exception backtraces to rack.errors. + + * Use RACK_ENV environment variable to establish Sinatra + environment when given. Thin sets this when started with + the -e argument. + + * BUG: raising Sinatra::NotFound resulted in a 500 response + code instead of 404. + + * BUG: use_in_file_templates! fails with CR/LF [#45] + + * BUG: Sinatra detects the app file and root path when run under + thin/passenger. + +## 0.3.2 + + * BUG: Static and send_file read entire file into String before + sending. Updated to stream with 8K chunks instead. + + * Rake tasks and assets for building basic documentation website. + See http://sinatra.rubyforge.org + + * Various minor doc fixes. + +## 0.3.1 + + * Unbreak optional path parameters [jeremyevans] + +## 0.3.0 + + * Add sinatra.gemspec w/ support for github gem builds. Forks can now + enable the build gem option in github to get free username-sinatra.gem + builds: gem install username-sinatra.gem --source=http://gems.github.com/ + + * Require rack-0.4 gem; removes frozen rack dir. + + * Basic RSpec support; require 'sinatra/test/rspec' instead of + 'sinatra/test/spec' to use. [avdi] + + * before filters can modify request environment vars used for + routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting + type functionality. + + * In-file templates now uses @@ instead of ## as template separator. + + * Top-level environment test predicates: development?, test?, production? + + * Top-level "set", "enable", and "disable" methods for tweaking + app options. [rtomayko] + + * Top-level "use" method for building Rack middleware pipelines + leading to app. See README for usage. [rtomayko] + + * New "reload" option - set false to disable reloading in development. + + * New "host" option - host/ip to bind to [cschneid] + + * New "app_file" option - override the file to reload in development + mode [cschneid] + + * Development error/not_found page cleanup [sr, adamwiggins] + + * Remove a bunch of core extensions (String#to_param, String#from_param, + Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass) + + * Various grammar and formatting fixes to README; additions on + community and contributing [cypher] + + * Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api + + * Specs, documentation and fixes for splat'n routes [vic] + + * Fix whitespace errors across all source files. [rtomayko] + + * Fix streaming issues with Mongrel (body not closed). [bmizerany] + + * Fix various issues with environment not being set properly (configure + blocks not running, error pages not registering, etc.) [cypher] + + * Fix to allow locals to be passed to ERB templates [cschneid] + + * Fix locking issues causing random errors during reload in development. + + * Fix for escaped paths not resolving static files [Matthew Walker] + +## 0.2.1 + + * File upload fix and minor tweaks. + +## 0.2.0 + + * Initial gem release of 0.2 codebase. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CONTRIBUTING.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CONTRIBUTING.md new file mode 100644 index 0000000000..9f4cbc58c1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contribute + +Want to show Sinatra some love? Help out by contributing! + +## Found a bug? + +Log it in our [issue tracker][ghi] or send a note to the [mailing list][ml]. +Be sure to include all relevant information, like the versions of Sinatra and +Ruby you're using. A [gist](http://gist.github.com/) of the code that caused +the issue as well as any error messages are also very helpful. + +## Need help? + +The [Sinatra mailing list][ml] has over 900 subscribers, many of which are happy +to help out newbies or talk about potential feature additions. You can also +drop by the [#sinatra](irc://chat.freenode.net/#sinatra) channel on +[irc.freenode.net](http://freenode.net). + +## Have a patch? + +Bugs and feature requests that include patches are much more likely to +get attention. Here are some guidelines that will help ensure your patch +can be applied as quickly as possible: + +1. **Use [Git](http://git-scm.com) and [GitHub](http://github.com):** + The easiest way to get setup is to fork the + [sinatra/sinatra repo](http://github.com/sinatra/sinatra/). + Or, the [sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/), + if the patch is doc related. + +2. **Write unit tests:** If you add or modify functionality, it must + include unit tests. If you don't write tests, we have to, and this + can hold up acceptance of the patch. + +3. **Mind the `README`:** If the patch adds or modifies a major feature, + modify the `README.md` file to reflect that. Again, if you don't + update the `README`, we have to, and this holds up acceptance. + +4. **Push it:** Once you're ready, push your changes to a topic branch + and add a note to the ticket with the URL to your branch. Or, say + something like, "you can find the patch on johndoe/foobranch". We also + gladly accept GitHub [pull requests](http://help.github.com/pull-requests/). + +__NOTE:__ _We will take whatever we can get._ If you prefer to attach diffs in +emails to the mailing list, that's fine; but do know that _someone_ will need +to take the diff through the process described above and this can hold things +up considerably. + +## Want to write docs? + +The process for contributing to Sinatra's website, documentation or the book +is the same as contributing code. We use Git for versions control and GitHub to +track patch requests. + +* [The sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/) + is where the website sources are managed. There are almost always people in + `#sinatra` that are happy to discuss, apply, and publish website patches. + +* [The Book](http://sinatra-org-book.herokuapp.com/) has its own [Git + repository](http://github.com/sinatra/sinatra-book/) and build process but is + managed the same as the website and project codebase. + +* [Sinatra Recipes](http://recipes.sinatrarb.com/) is a community + project where anyone is free to contribute ideas, recipes and tutorials. Which + also has its own [Git repository](http://github.com/sinatra/sinatra-recipes). + +* [The Introduction](http://www.sinatrarb.com/intro.html) is generated from + Sinatra's [README file](http://github.com/sinatra/sinatra/blob/master/README.md). + +* If you want to help translating the documentation, the README is already + available in + [Japanese](http://github.com/sinatra/sinatra/blob/master/README.ja.md), + [German](http://github.com/sinatra/sinatra/blob/master/README.de.md), + [Chinese](https://github.com/sinatra/sinatra/blob/master/README.zh.md), + [Russian](https://github.com/sinatra/sinatra/blob/master/README.ru.md), + [European](https://github.com/sinatra/sinatra/blob/master/README.pt-pt.md) and + [Brazilian](https://github.com/sinatra/sinatra/blob/master/README.pt-br.md) + Portuguese, + [French](https://github.com/sinatra/sinatra/blob/master/README.fr.md), + [Spanish](https://github.com/sinatra/sinatra/blob/master/README.es.md), + [Korean](https://github.com/sinatra/sinatra/blob/master/README.ko.md), and + [Hungarian](https://github.com/sinatra/sinatra/blob/master/README.hu.md). + The translations tend to fall behind the English version. Translations into + other languages would also be appreciated. + +## Looking for something to do? + +If you'd like to help out but aren't sure how, pick something that looks +interesting from the [issues][ghi] list and hack on. Make sure to leave a +comment on the ticket noting that you're investigating (a simple "Taking…" is +fine). + +[ghi]: http://github.com/sinatra/sinatra/issues +[ml]: http://groups.google.com/group/sinatrarb/topics "Sinatra Mailing List" + +* ["Help Wanted"](https://github.com/sinatra/sinatra/labels/help%20wanted): Anyone willing to pitch in is open to contribute to this ticket as they see fit (will try to add context / summarize or ask for requirements) + +* ["Good First Issue"](https://github.com/sinatra/sinatra/labels/good%20first%20issue): Potential first time contributors should start here + +* ["Wishlist"](https://github.com/sinatra/sinatra/labels/Wishlist): All the things I wish we had but have no time for diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Gemfile b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Gemfile new file mode 100644 index 0000000000..386ba7def9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Gemfile @@ -0,0 +1,79 @@ +# Why use bundler? +# Well, not all development dependencies install on all rubies. Moreover, `gem +# install sinatra --development` doesn't work, as it will also try to install +# development dependencies of our dependencies, and those are not conflict free. +# So, here we are, `bundle install`. +# +# If you have issues with a gem: `bundle install --without-coffee-script`. + +RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE +source 'https://rubygems.org' unless ENV['QUICK'] +gemspec + +gem 'rake' +gem 'rack', '~> 2.0' +gem 'rack-test', '>= 0.6.2' +gem "minitest", "~> 5.0" +gem 'yard' + +gem "rack-protection", path: "rack-protection" +gem "sinatra-contrib", path: "sinatra-contrib" + +gem "twitter-text", "1.14.7" + +if RUBY_ENGINE == 'jruby' + gem 'nokogiri', '!= 1.5.0' + gem 'puma', '~> 5' +end + +if RUBY_ENGINE == 'jruby' || RUBY_ENGINE == 'ruby' + gem "activesupport", "~> 5.1.6" +end + +if RUBY_ENGINE == "ruby" + gem 'less', '~> 2.0' + gem 'therubyracer' + gem 'redcarpet' + gem 'wlang', '>= 3.0.1' + gem 'bluecloth' + gem 'rdiscount' + gem 'RedCloth' + gem 'puma', '~> 5' + gem 'yajl-ruby' + gem 'nokogiri' + gem 'rainbows' + gem 'eventmachine' + gem 'slim', '~> 2.0' + gem 'coffee-script', '>= 2.0' + gem 'kramdown' + gem 'maruku' + gem 'creole' + gem 'wikicloth' + gem 'markaby' + gem 'radius' + gem 'asciidoctor' + gem 'liquid' + gem 'stylus' + gem 'rabl' + gem 'builder' + gem 'erubi' + gem 'erubis' + gem 'haml', '>= 3.0' + gem 'sass' + gem 'reel-rack' + gem 'celluloid', '~> 0.16.0' + gem 'commonmarker', '~> 0.20.0' + gem 'pandoc-ruby', '~> 2.0.2' + gem 'simplecov', require: false +end + +if RUBY_ENGINE == "rbx" + gem 'json' + gem 'rubysl' + gem 'rubysl-test-unit' + gem 'erubi' +end + +platforms :jruby do + gem 'json' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/LICENSE new file mode 100644 index 0000000000..c53f830ff5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2007, 2008, 2009 Blake Mizerany +Copyright (c) 2010-2017 Konstantin Haase +Copyright (c) 2015-2017 Zachary Scott + +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.0.0/gems/sinatra-2.2.4/MAINTENANCE.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/MAINTENANCE.md new file mode 100644 index 0000000000..79951c6492 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/MAINTENANCE.md @@ -0,0 +1,29 @@ +# Sinatra maintenance + +## Versions + +### Releases + +The next major version of Sinatra will be released from the master branch. Each version will be tagged so it will be possible to branch of should there be a need for bug fixes and other updates. + +## Issues + +### New features + +New features will only be added to the master branch and will not be made available in point releases. + +### Bug fixes + +Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. + +### Security issues + +The current release series will receive patches and new versions in case of a security issue. + +### Severe security issues + +For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. + +### Unsupported Release Series + +When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. We may provide back-ports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.de.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.de.md new file mode 100644 index 0000000000..83461102a8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.de.md @@ -0,0 +1,3239 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](http://travis-ci.org/sinatra/sinatra) + +*Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter +Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 2.0 +Vorabausgabe).* + +Sinatra ist eine +[DSL](https://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das +schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand +ermöglicht: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hallo Welt!' +end +``` + +Sinatra-Gem installieren: + +```shell +gem install sinatra +``` + +und im gleichen Verzeichnis ausführen: + +```shell +ruby myapp.rb +``` + +Die Seite kann nun unter [http://localhost:4567](http://localhost:4567) +aufgerufen werden. + +Es wird empfohlen `gem install thin` auszuführen, Sinatra wird dann +diesen Server verwenden. + +## Inhalt + +- [Sinatra](#sinatra) + * [Inhalt](#inhalt) + * [Routen](#routen) + + [Bedingungen](#bedingungen) + + [Rückgabewerte](#rückgabewerte) + + [Eigene Routen-Muster](#eigene-routen-muster) + * [Statische Dateien](#statische-dateien) + * [Views/Templates](#views-templates) + + [Direkte Templates](#direkte-templates) + + [Verfügbare Templatesprachen](#verfügbare-templatesprachen) + - [Haml Templates](#haml-templates) + - [Erb Templates](#erb-templates) + - [Builder Templates](#builder-templates) + - [Nokogiri Templates](#nokogiri-templates) + - [Sass Templates](#sass-templates) + - [SCSS Templates](#scss-templates) + - [Less Templates](#less-templates) + - [Liquid Templates](#liquid-templates) + - [Markdown Templates](#markdown-templates) + - [Textile Templates](#textile-templates) + - [RDoc Templates](#rdoc-templates) + - [AsciiDoc Templates](#asciidoc-templates) + - [Radius Templates](#radius-templates) + - [Markaby Templates](#markaby-templates) + - [RABL Templates](#rabl-templates) + - [Slim Templates](#slim-templates) + - [Creole Templates](#creole-templates) + - [MediaWiki Templates](#mediawiki-templates) + - [CoffeeScript Templates](#coffeescript-templates) + - [Stylus Templates](#stylus-templates) + - [Yajl Templates](#yajl-templates) + - [WLang Templates](#wlang-templates) + + [Auf Variablen in Templates zugreifen](#auf-variablen-in-templates-zugreifen) + + [Templates mit `yield` und verschachtelte Layouts](#templates-mit--yield--und-verschachtelte-layouts) + + [Inline-Templates](#inline-templates) + + [Benannte Templates](#benannte-templates) + + [Dateiendungen zuordnen](#dateiendungen-zuordnen) + + [Eine eigene Template-Engine hinzufügen](#eine-eigene-template-engine-hinzuf-gen) + + [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden) + * [Filter](#filter) + * [Helfer](#helfer) + + [Sessions verwenden](#sessions-verwenden) + - [Sitzungseinstellungen](#sitzungseinstellungen) + - [Eigene Sitzungs-Middleware auswählen](#eigene-sitzungs-middleware-ausw-hlen) + + [Anhalten](#anhalten) + + [Weiterspringen](#weiterspringen) + + [Eine andere Route ansteuern](#eine-andere-route-ansteuern) + + [Body, Status-Code und Header setzen](#body--status-code-und-header-setzen) + + [Response-Streams](#response-streams) + + [Logger](#logger) + + [Mime-Types](#mime-types) + + [URLs generieren](#urls-generieren) + + [Browser-Umleitung](#browser-umleitung) + + [Cache einsetzen](#cache-einsetzen) + + [Dateien versenden](#dateien-versenden) + + [Das Request-Objekt](#das-request-objekt) + + [Anhänge](#anhänge) + + [Umgang mit Datum und Zeit](#umgang-mit-datum-und-zeit) + + [Nachschlagen von Template-Dateien](#nachschlagen-von-template-dateien) + + [Konfiguration](#konfiguration) + - [Einstellung des Angriffsschutzes](#einstellung-des-angriffsschutzes) + - [Mögliche Einstellungen](#m-gliche-einstellungen) + * [Umgebungen](#umgebungen) + * [Fehlerbehandlung](#fehlerbehandlung) + + [Nicht gefunden](#nicht-gefunden) + + [Fehler](#fehler) + * [Rack-Middleware](#rack-middleware) + * [Testen](#testen) + * [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatra--base---middleware--bibliotheken-und-modulare-anwendungen) + + [Modularer vs. klassischer Stil](#modularer-vs-klassischer-stil) + + [Eine modulare Applikation bereitstellen](#eine-modulare-applikation-bereitstellen) + + [Eine klassische Anwendung mit einer config.ru verwenden](#eine-klassische-anwendung-mit-einer-configru-verwenden) + + [Wann sollte eine config.ru-Datei verwendet werden?](#wann-sollte-eine-configru-datei-verwendet-werden-) + + [Sinatra als Middleware nutzen](#sinatra-als-middleware-nutzen) + + [Dynamische Applikationserstellung](#dynamische-applikationserstellung) + * [Geltungsbereich und Bindung](#geltungsbereich-und-bindung) + + [Anwendungs- oder Klassen-Scope](#anwendungs--oder-klassen-scope) + + [Anfrage- oder Instanz-Scope](#anfrage--oder-instanz-scope) + + [Delegation-Scope](#delegation-scope) + * [Kommandozeile](#kommandozeile) + + [Multi-threading](#multi-threading) + * [Systemanforderungen](#systemanforderungen) + * [Der neuste Stand (The Bleeding Edge)](#der-neuste-stand--the-bleeding-edge-) + + [Mit Bundler](#mit-bundler) + + [Eigenes Repository](#eigenes-repository) + + [Gem erstellen](#gem-erstellen) + * [Versions-Verfahren](#versions-verfahren) + * [Mehr](#mehr) + +## Routen + +In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster +definiert. Jeder dieser Routen wird ein Ruby-Block zugeordnet: + +```ruby +get '/' do + .. zeige etwas .. +end + +post '/' do + .. erstelle etwas .. +end + +put '/' do + .. update etwas .. +end + +delete '/' do + .. entferne etwas .. +end + +options '/' do + .. zeige, was wir können .. +end + +link '/' do + .. verbinde etwas .. +end + +unlink '/' do + .. trenne etwas .. +end +``` + +Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. +Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt. + +Routen mit angehängtem Schrägstrich unterscheiden sich von Routen ohne: + +```ruby +get '/foo' do + # wird nicht bei "GET /foo/" aufgerufen +end +``` + +Die Muster der Routen können benannte Parameter beinhalten, die über den +`params`-Hash zugänglich gemacht werden: + +```ruby +get '/hallo/:name' do + # passt auf "GET /hallo/foo" und "GET /hallo/bar" + # params['name'] ist dann 'foo' oder 'bar' + "Hallo #{params['name']}!" +end +``` + +Man kann auf diese auch mit Block-Parametern zugreifen: + +```ruby +get '/hallo/:name' do |n| + # n entspricht hier params['name'] + "Hallo #{n}!" +end +``` + +Routen-Muster können auch mit sog. Splat- oder Wildcard-Parametern über das +`params['splat']`-Array angesprochen werden: + +```ruby +get '/sag/*/zu/*' do + # passt z.B. auf /sag/hallo/zu/welt + params['splat'] # => ["hallo", "welt"] +end + +get '/download/*.*' do + # passt auf /download/pfad/zu/datei.xml + params['splat'] # => ["pfad/zu/datei", "xml"] +end +``` + +Oder mit Block-Parametern: + +```ruby +get '/download/*.*' do |pfad, endung| + [pfad, endung] # => ["Pfad/zu/Datei", "xml"] +end +``` + +Routen mit regulären Ausdrücken sind auch möglich: + +```ruby +get /\/hallo\/([\w]+)/ do + "Hallo, #{params['captures'].first}!" +end +``` + +Und auch hier können Block-Parameter genutzt werden: + +```ruby +get %r{/hallo/([\w]+)} do |c| + # erkennt "GET /hallo/frank" oder "GET /sag/hallo/frank" usw. + "Hallo, #{c}!" +end +``` + +Routen-Muster können auch mit optionalen Parametern ausgestattet werden: + +```ruby +get '/posts/:format?' do + # passt auf "GET /posts/" sowie jegliche Erweiterung + # wie "GET /posts/json", "GET /posts/xml" etc. +end +``` + +Routen können auch den query-Parameter verwenden: + +```ruby +get '/posts' do + # passt zu "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # verwendet title- und author-Variablen. Der query-Parameter ist für + # die /post-Route optional +end +``` + +Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert +(siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem +Abgleich mit den Routen modifiziert wird. + +Die Mustermann-Optionen können für eine gegebene Route angepasst werden, +indem man der Route den `:mustermann_opts`-Hash mitgibt: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # Passt genau auf /posts mit explizitem Anchoring + "Wenn Dein Anchor-Muster passt, darfst Du klatschen!" +end +``` + +Das sieht zwar genauso aus wie eine Bedingung, ist es aber nicht. Diese +Option wird mit dem globalen `:mustermann_opts`-Hash zusammengeführt +(siehe weiter unten). + +### Bedingungen + +An Routen können eine Vielzahl von Bedingungen geknüpft werden, die erfüllt +sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine +Einschränkung des User-Agents über die interne Bedingung `:agent`: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Du verwendest Songbird Version #{params['agent'][0]}" +end +``` + +Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: + +```ruby +get '/foo' do + # passt auf andere Browser +end +``` + +Andere verfügbare Bedingungen sind `:host_name` und `:provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Adminbereich, Zugriff verweigert!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` durchsucht den Accept-Header der Anfrage + +Eigene Bedingungen können relativ einfach hinzugefügt werden: + +```ruby +set(:wahrscheinlichkeit) { |value| condition { rand <= value } } + +get '/auto_gewinnen', :wahrscheinlichkeit => 0.1 do + "Du hast gewonnen!" +end + +get '/auto_gewinnen' do + "Tut mir leid, verloren." +end +``` + +Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet +werden: + +```ruby +set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mein/account/", :auth => [:user, :admin] do + "Mein Account" +end + +get "/nur/admin/", :auth => :admin do + "Nur Admins dürfen hier rein!" +end +``` + +### Rückgabewerte + +Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body +festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware, +weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den +vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings +auch andere Werte akzeptiert. + +Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um +einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt: + +* Ein Array mit drei Elementen: `[Status (Integer), Headers (Hash), + Response-Body (antwortet auf #each)]`. +* Ein Array mit zwei Elementen: `[Status (Integer), Response-Body (antwortet + auf #each)]`. +* Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen + Block nur mit Strings als Übergabewerten aufruft. +* Ein Integer, das den Status-Code festlegt. + +Damit lässt sich relativ einfach Streaming implementieren: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming +direkt in die Route integriert. + +### Eigene Routen-Muster + +Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für +String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. +Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene +Routen-Muster erstellt werden: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch +einfacher: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Oder unter Verwendung eines negativen look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Statische Dateien + +Statische Dateien werden im `./public`-Ordner erwartet. Es ist möglich, +einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Zu beachten ist, dass der Ordnername `public` nicht Teil der URL ist. +Die Datei `./public/css/style.css` ist unter +`http://example.com/css/style.css` zu finden. + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +## Views/Templates + +Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils +einen String zurückgibt: + +```ruby +get '/' do + erb :index +end +``` + +Dieses Beispiel rendert `views/index.erb`. + +Anstelle eines Templatenamens kann man auch direkt die Templatesprache +verwenden: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates nehmen ein zweites Argument an, den Options-Hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` +(Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) + +Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Für alle Templates können auch Einstellungen, die für alle Routen gelten, +festgelegt werden: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Optionen, die an die Rendermethode weitergegeben werden, überschreiben die +Einstellungen, die mit `set` festgelegt wurden. + +Einstellungen: + +
    +
    locals
    +
    Liste von lokalen Variablen, die an das Dokument weitergegeben werden. + Praktisch für Partials: + + erb "<%= foo %>", :locals => {:foo => "bar"}
    + +
    default_encoding
    +
    Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt + auf settings.default_encoding.
    + +
    views
    +
    Ordner, aus dem die Templates geladen werden. Voreingestellt auf + settings.views.
    + +
    layout
    +
    Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht + (true oderfalse). Ist es ein Symbol, dann legt es fest, + welches Template als Layout verwendet wird: + + erb :index, :layout => !request.xhr?
    + +
    content_type
    +
    Content-Typ den das Template ausgibt. Voreinstellung hängt von der + Templatesprache ab.
    + +
    scope
    +
    Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb + der App-Instanz. Wird Scope geändert, sind Instanzvariablen und + Helfermethoden nicht verfügbar.
    + +
    layout_engine
    +
    Legt fest, welcher Renderer für das Layout verantwortlich ist. Hilfreich + für Sprachen, die sonst keine Templates unterstützen. Voreingestellt auf + den Renderer, der für das Template verwendet wird: + + set :rdoc, :layout_engine => :erb
    + +
    layout_options
    +
    Besondere Einstellungen, die nur für das Rendering verwendet werden: + + set :rdoc, :layout_options => { :views => 'views/layouts' }
    +
    + +Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis +befinden. Es kann jedoch ein anderer Ordner festgelegt werden: + +```ruby +set :views, settings.root + '/templates' +``` + +Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen +werden muss, auch dann, wenn sie sich in einem Unterordner befinden: + +```ruby +haml :'unterverzeichnis/template' +``` + +Wird einer Rendering-Methode ein String übergeben, wird dieser direkt +gerendert. + +### Direkte Templates + +```ruby +get '/' do + haml '%div.title Hallo Welt' +end +``` + +Hier wird der String direkt gerendert. + +Optional kann `:path` und `:line` für einen genaueren Backtrace +übergeben werden, wenn mit dem vorgegebenen String ein Pfad im +Dateisystem oder eine Zeilennummer verbunden ist: + +```ruby +get '/' do + haml '%div.title Hallo Welt', :path => 'examples/datei.haml', :line => 3 +end +``` + +### Verfügbare Templatesprachen + +Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche +verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu +Beginn ein `'require'`: + +```ruby +require 'rdiscount' # oder require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Abhängigkeithaml
    Dateierweiterung.haml
    Beispielhaml :index, :format => :html5
    + + +#### Erb Templates + + + + + + + + + + + + + + +
    Abhängigkeiterubis oder erb + (Standardbibliothek von Ruby)
    Dateierweiterungen.erb, .rhtml oder .erubis (nur Erubis)
    Beispielerb :index
    + + +#### Builder Templates + + + + + + + + + + + + + + +
    Abhängigkeitbuilder
    Dateierweiterung.builder
    Beispielbuilder { |xml| xml.em "Hallo" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Abhängigkeitnokogiri
    Dateierweiterung.nokogiri
    Beispielnokogiri { |xml| xml.em "Hallo" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Sass Templates + + + + + + + + + + + + + + +
    Abhängigkeitsass
    Dateierweiterung.sass
    Beispielsass :stylesheet, :style => :expanded
    + + +#### SCSS Templates + + + + + + + + + + + + + + +
    Abhängigkeitsass
    Dateierweiterung.scss
    Beispielscss :stylesheet, :style => :expanded
    + + +#### Less Templates + + + + + + + + + + + + + + +
    Abhängigkeitless
    Dateierweiterung.less
    Beispielless :stylesheet
    + + +#### Liquid Templates + + + + + + + + + + + + + + +
    Abhängigkeitliquid
    Dateierweiterung.liquid
    Beispielliquid :index, :locals => { :key => 'Wert' }
    + +Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Markdown Templates + + + + + + + + + + + + + + +
    AbhängigkeitEine der folgenden Bibliotheken: + RDiscount, + RedCarpet, + BlueCloth, + kramdown oder + maruku +
    Dateierweiterungen.markdown, .mkd und .md
    Beispielmarkdown :index, :layout_engine => :erb
    + +Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Markdown üblicherweise in Kombination +mit anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => markdown(:einfuehrung) } +``` + +Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= markdown(:Grüße) +``` + +Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht +in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### Textile Templates + + + + + + + + + + + + + + +
    AbhängigkeitRedCloth
    Dateierweiterung.textile
    Beispieltextile :index, :layout_engine => :erb
    + +Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => textile(:einfuehrung) } +``` + +Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= textile(:Grüße) +``` + +Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht +in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### RDoc Templates + + + + + + + + + + + + + + +
    Abhängigkeitrdoc
    Dateierweiterung.rdoc
    Beispieltextile :README, :layout_engine => :erb
    + +Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => rdoc(:einfuehrung) } +``` + +Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= rdoc(:Grüße) +``` + +Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in +RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    AbhängigkeitAsciidoctor
    Dateierweiterungen.asciidoc, .adoc und .ad
    Beispielasciidoc :README, :layout_engine => :erb
    + +Da man aus dem AsciiDoc-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Radius Templates + + + + + + + + + + + + + + +
    Abhängigkeitradius
    Dateierweiterung.radius
    Beispielradius :index, :locals => { :key => 'Wert' }
    + +Da man aus dem Radius-Template heraus keine Ruby-Methoden aufrufen kann, wird +man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt. + +#### Markaby Templates + + + + + + + + + + + + + + +
    Abhängigkeitmarkaby
    Dateierweiterung.mab
    Beispielmarkaby { h1 "Willkommen!" }
    + +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### RABL Templates + + + + + + + + + + + + + + +
    Abhängigkeitrabl
    Dateierweiterung.rabl
    Beispielrabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    Abhängigkeitslim
    Dateierweiterung.slim
    Beispielslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    Abhängigkeitcreole
    Dateierweiterung.creole
    Beispielcreole :wiki, :layout_engine => :erb
    + +Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => creole(:einfuehrung) } +``` + +Beachte, dass man die `creole`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= creole(:Grüße) +``` + +Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts +nicht in Creole geschrieben werden. Es ist aber möglich, einen Renderer +für die Templates zu verwenden und einen anderen für das Layout, indem +die `:layout_engine`-Option verwendet wird. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    AbhängigkeitWikiCloth
    Dateierweiterungen.mediawiki und .mw
    Beispielmediawiki :wiki, :layout_engine => :erb
    + +Da man aus dem Mediawiki-Template heraus keine Ruby-Methoden aufrufen +und auch keine locals verwenden kann, wird man Mediawiki üblicherweise +in Kombination mit anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Beachte: Man kann die `mediawiki`-Methode auch aus anderen Templates +heraus aufrufen: + +```ruby +%h1 Grüße von Haml! +%p= mediawiki(:greetings) +``` + +Da man Ruby nicht von MediaWiki heraus aufrufen kann, können auch +Layouts nicht in MediaWiki geschrieben werden. Es ist aber möglich, +einen Renderer für die Templates zu verwenden und einen anderen für das +Layout, indem die `:layout_engine`-Option verwendet wird. + +#### CoffeeScript Templates + + + + + + + + + + + + + +
    Abhängigkeitcoffee-script + und eine Möglichkeit JavaScript auszuführen. +
    Dateierweiterung.coffee
    Beispielcoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Abhängigkeit + + Stylus + und eine Möglichkeit + + JavaScript auszuführen + . +
    Dateierweiterung.styl
    Beispielstylus :index
    + +Um Stylus-Templates ausführen zu können, müssen `stylus` und `stylus/tilt` +zuerst geladen werden: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Abhängigkeityajl-ruby
    Dateierweiterung.yajl
    Beispiel + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende +Json-Variable wird mit Hilfe von `#to_json` umgewandelt: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt +verwendet werden: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    Abhängigkeitwlang
    Dateierweiterung.wlang
    Beispielwlang :index, :locals => { :key => 'value' }
    + +Ruby-Methoden in Wlang aufzurufen entspricht nicht den idiomatischen Vorgaben +von Wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die +Wlang und `yield` verwenden, werden aber trotzdem unterstützt. + +Rendert den eingebetteten Template-String. + +### Auf Variablen in Templates zugreifen + +Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen +in Routen sind auch direkt im Template verfügbar: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Oder durch einen expliziten Hash von lokalen Variablen: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen +Templates eingesetzt. + +### Templates mit `yield` und verschachtelte Layouts + +Ein Layout ist üblicherweise ein Template, das ein `yield` aufruft. Ein +solches Template kann entweder wie oben beschrieben über die `:template` +Option verwendet werden oder mit einem Block gerendert werden: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Dieser Code entspricht weitestgehend `erb :index, :layout => :post`. + +Blöcke an Render-Methoden weiterzugeben ist besonders bei verschachtelten +Layouts hilfreich: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Der gleiche Effekt kann auch mit weniger Code erreicht werden: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Zur Zeit nehmen folgende Renderer Blöcke an: `erb`, `haml`, `liquid`, `slim ` +und `wlang`. + +Das gleich gilt auch für die allgemeine `render` Methode. + +### Inline-Templates + +Templates können auch am Ende der Datei definiert werden: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hallo Welt +``` + +Anmerkung: Inline-Templates, die in der Datei definiert sind, die `require +'sinatra'` aufruft, werden automatisch geladen. Um andere Inline-Templates in +weiteren Dateien aufzurufen, muss explizit `enable :inline_templates` +verwendet werden. + +### Benannte Templates + +Templates können auch mit der Top-Level `template`-Methode definiert werden: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hallo Welt!' +end + +get '/' do + haml :index +end +``` + +Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf +verwendet. Durch `:layout => false` kann das Ausführen individuell nach Route +verhindert werden, oder generell für ein Template, z.B. Haml via: +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? + # !request.xhr? prüft, ob es sich um einen asynchronen Request handelt. + # wenn nicht, dann verwende ein Layout (negiert durch !) +end +``` + +### Dateiendungen zuordnen + +Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` +genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt +werden soll, lässt sich dies wie folgt bewerkstelligen: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Eine eigene Template-Engine hinzufügen + +Zu allererst muss die Engine bei Tilt registriert und danach eine +Rendering-Methode erstellt werden: + +```ruby +Tilt.register :mtt, MeineTolleTemplateEngine + +helpers do + def mtt(*args) render(:mtt, *args) end +end + +get '/' do + mtt :index +end +``` + +Dieser Code rendert `./views/application.mtt`. Siehe +[github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über +Tilt zu erfahren. + +### Eigene Methoden zum Aufsuchen von Templates verwenden + +Um einen eigenen Mechanismus zum Aufsuchen von Templates zu +implementieren, muss `#find_template` definiert werden: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filter + +Before-Filter werden vor jedem Request in demselben Kontext, wie danach die +Routen, ausgeführt. So können etwa Request und Antwort geändert werden. +Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet +werden: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After-Filter werden nach jedem Request in demselben Kontext ausgeführt und +können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte +Instanzvariablen können in After-Filtern verwendet werden: + +```ruby +after do + puts response.status +end +``` + +Achtung: Wenn statt der body-Methode ein einfacher String verwendet +wird, ist der Response-body im after-Filter noch nicht verfügbar, da +er erst nach dem Durchlaufen des after-Filters erzeugt wird. + +Filter können optional auch mit einem Muster ausgestattet werden, das auf den +Request-Pfad passen muss, damit der Filter ausgeführt wird: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt +werden: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helfer + +Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden +definiert, die in Routen und Templates verwendet werden können: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Ebenso können Helfer auch in einem eigenen Modul definiert werden: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +Das Ergebnis ist das gleiche, wie beim Einbinden in die +Anwendungs-Klasse. + +### Sessions verwenden +Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. +Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet +werden: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +Um die Sicherheit zu erhöhen, werden Daten mit einem geheimen +Sitzungsschlüssel unter Verwendung von `HMAC-SHA1` in einem Cookie signiert. +Der Sitzungsschlüssel sollte optimalerweise ein kryptografisch zufällig +erzeugter Wert mit angemessener Länge sein, für den `HMAC-SHA1` größer +oder gleich 65 Bytes ist (256 Bits, 64 Hex-Zeichen). Es wird empfohlen, +keinen Schlüssel zu verwenden, dessen Zufälligkeit weniger als 32 Bytes +entspricht (also 256 Bits, 64 Hex-Zeichen). Es ist deshalb **wirklich +wichtig**, dass nicht einfach irgendetwas als Schlüssel verwendet wird, +sondern ein sicherer Zufallsgenerator die Zeichenkette erstellt. Menschen sind +nicht besonders gut darin, zufällige Zeichenfolgen zu erstellen. + +Sinatra generiert automatisch einen zufälligen, 32 Byte langen +Schlüssel. Da jedoch bei jedem Neustart der Schlüssel ebenfalls neu generiert +wird, ist es sinnvoll einen eigenen Schlüssel festzulegen, damit er über alle +Anwendungsinstanzen hinweg geteilt werden kann. + +Aus praktikablen und Sicherheitsgründen wird +[empfohlen](https://12factor.net/config), dass ein sicherer Zufallswert +erzeugt und in einer Umgebungsvariable abgelegt wird, damit alle +Anwendungsinstanzen darauf zugreifen können. Dieser Sitzungsschlüssel +sollte in regelmäßigen Abständen erneuert werden. Zum Erzeugen von 64 +Byte starken Schlüsseln sind hier ein paar Beispiele vorgestellt: + +**Sitzungsschlüssel erzeugen** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8afi...usw...ec0f262ac +``` + +**Sitzungsschlüssel erzeugen (Bonuspunkte)** + +Um den systemweiten Zufallszahlengenerator zu verwenden, kann das +[sysrandom gem](https://github.com/cryptosphere/sysrandom) installiert +werden, anstelle von Zufallszahlen aus dem Userspace, auf die MRI zur +Zeit standardmäßig zugreift: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Sitzungsschlüssel-Umgebungsvariable** + +Wird eine `SESSION_SECRET`-Umgebungsvariable persistent gesetzt, kann +Sinatra darauf zugreifen. Da die folgende Methode von System zu System +variieren kann, ist dies als Beispiel zu verstehen: + +```bash +$ echo "export SESSION_SECRET=99ae8af...etc...ec0f262ac" >> ~/.bashrc +``` + +**Anwendungseinstellung für Sitzungsschlüssel** + +Die Anwendung sollte unabhängig von der `SESSION_SECRET`-Umgebungsvariable +auf einen sicheren zufälligen Schlüssel zurückgreifen. + +Auch hier sollte das +[sysrandom gem](https://github.com/cryptosphere/sysrandom) verwendet +werden: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Sitzungseinstellungen + +Im Options-Hash können weitere Einstellungen abgelegt werden: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Um Sitzungsdaten über mehrere Anwendungen und Subdomains hinweg zu +teilen, muss die Domain mit einem `*.*` vor der Domain ausgestattet +werden: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Eigene Sitzungs-Middleware auswählen + +Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter +Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten +höheren, teilweise überflüssigen Traffic. Es kann daher eine beliebige +Rack-Session Middleware verwendet werden. Folgende Methoden stehen zur +Verfügung: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Oder Sitzungen werden mit einem Options-Hash ausgestattet: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Eine weitere Methode ist der Verzicht auf `enable :session` und +stattdessen die Verwendung einer beliebigen anderen Middleware. + +Dabei ist jedoch zu beachten, dass der reguläre sitzungsbasierte +Sicherungsmechanismus **nicht automatisch aktiviert wird**. + +Die dazu benötigte Rack-Middleware muss explizit eingebunden werden: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Mehr dazu unter [Einstellung des Angiffsschutzes](https://github.com/sinatra/sinatra/blob/master/README.de.md#einstellung-des-angriffsschutzes). + +### Anhalten + +Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: + +```ruby +halt +``` + +Der Status kann beim Stoppen mit angegeben werden: + +```ruby +halt 410 +``` + +Oder auch den Response-Body: + +```ruby +halt 'Hier steht der Body' +``` + +Oder beides: + +```ruby +halt 401, 'verschwinde!' +``` + +Sogar mit Headern: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'Rache' +``` + +Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: + +```ruby +halt erb(:error) +``` + +### Weiterspringen + +Eine Route kann mittels `pass` zu der nächsten passenden Route springen: + +```ruby +get '/raten/:wer' do + pass unless params['wer'] == 'Frank' + 'Du hast mich!' +end + +get '/raten/*' do + 'Du hast mich nicht!' +end +``` + +Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route +gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster +gefunden wird. + +### Eine andere Route ansteuern + +Wenn nicht zu einer anderen Route gesprungen werden soll, sondern nur das +Ergebnis einer anderen Route gefordert wird, kann `call` für einen internen +Request verwendet werden: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht +werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die +`/foo` und `/bar` zugreifen können. + +Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine +Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` +verwendet werden. + +Weitere Informationen zu `call` finden sich in den Rack-Spezifikationen. + +### Body, Status-Code und Header setzen + +Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit +einem Returnwert in der Route zu setzen. In manchen Situationen kann es +jedoch sein, dass der Body an anderer Stelle während der Ausführung gesetzt +werden soll. Dafür kann man die Helfer-Methode `body` einsetzen. Ist sie +gesetzt, kann sie zu einem späteren Zeitpunkt aufgerufen werden: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom +Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming +einsetzen, siehe auch "Rückgabewerte"). + +Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + halt "Ich bin ein Teekesselchen" +end +``` + +Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne +Argumente den aktuellen Wert aus. + +### Response-Streams + +In manchen Situationen sollen Daten bereits an den Client zurückgeschickt +werden, bevor ein vollständiger Response bereit steht. Manchmal will man die +Verbindung auch erst dann beenden und Daten so lange an den Client +zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die +`stream`-Helfer-Methode, die es einem erspart, eigene Lösungen zu schreiben: + +```ruby +get '/' do + stream do |out| + out << "Das ist ja mal wieder fanta -\n" + sleep 0.5 + out << " (bitte warten …) \n" + sleep 1 + out << "- stisch!\n" + end +end +``` + +Damit lassen sich Streaming-APIs realisieren, sog. +[Server Sent Events](https://w3c.github.io/eventsource/), die als Basis für +[WebSockets](https://en.wikipedia.org/wiki/WebSocket) dienen. Ebenso können +sie verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von +langsamen Ressourcen abhängig ist. + +Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl +nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die +Applikation verwendet wird. Einige Server unterstützen +Streaming nicht oder nur teilweise. Sollte der Server Streaming nicht +unterstützen, wird ein vollständiger Response-Body zurückgeschickt, sobald der +an `stream` weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert +Streaming z.B. überhaupt nicht. + +Ist der optionale Parameter `keep_open` aktiviert, wird beim gestreamten +Objekt `close` nicht aufgerufen und es ist einem überlassen dies an einem +beliebigen späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei +Event-gesteuerten Serven wie Thin oder Rainbows möglich, andere Server werden +trotzdem den Stream beenden: + +```ruby +# Durchgehende Anfrage (long polling) + +set :server, :thin +connections = [] + +get '/subscribe' do + # Client-Registrierung beim Server, damit Events mitgeteilt werden können + stream(:keep_open) do |out| + connections << out + # tote Verbindungen entfernen + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # Den Client über eine neue Nachricht in Kenntnis setzen + # notify client that a new message has arrived + out << params['message'] << "\n" + + # Den Client zur erneuten Verbindung auffordern + out.close + end + + # Rückmeldung + "Mitteiling erhalten" +end +``` + +Es ist ebenfalls möglich, dass der Client die Verbindung schließt, während in +den Socket geschrieben wird. Deshalb ist es sinnvoll, vor einem +Schreibvorgang `out.closed?` zu prüfen. + +### Logger + +Im Geltungsbereich eines Request stellt die `logger` Helfer-Methode eine +`Logger` Instanz zur Verfügung: + +```ruby +get '/' do + logger.info "es passiert gerade etwas" + # ... +end +``` + +Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten +Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt +zurück. In den Routen und Filtern muss man sich also nicht weiter darum +kümmern. + +Beachte, dass das Loggen standardmäßig nur für `Sinatra::Application` +voreingestellt ist. Wird über `Sinatra::Base` vererbt, muss es erst aktiviert +werden: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Damit auch keine Middleware das Logging aktivieren kann, muss die `logging` +Einstellung auf `nil` gesetzt werden. Das heißt aber auch, dass `logger` in +diesem Fall `nil` zurückgeben wird. Üblicherweise wird das eingesetzt, wenn +ein eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was +in `env['rack.logger']` eingetragen ist. + +### Mime-Types + +Wenn `send_file` oder statische Dateien verwendet werden, kann es vorkommen, +dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit `mime_type` +per Dateiendung: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Es kann aber auch der `content_type`-Helfer verwendet werden: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLs generieren + +Zum Generieren von URLs sollte die `url`-Helfer-Methode genutzen werden, so +z.B. beim Einsatz von Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen. + +Diese Methode ist ebenso über das Alias `to` zu erreichen (siehe Beispiel +unten). + +### Browser-Umleitung + +Eine Browser-Umleitung kann mithilfe der `redirect`-Helfer-Methode erreicht +werden: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Weitere Parameter werden wie Argumente der `halt`-Methode behandelt: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'Hier bist du falsch' +``` + +Ebenso leicht lässt sich ein Schritt zurück mit dem Alias `redirect back` +erreichen: + +```ruby +get '/foo' do + "mach was" +end + +get '/bar' do + mach_was + redirect back +end +``` + +Um Argumente an einen Redirect weiterzugeben, können sie entweder dem Query +übergeben: + +```ruby +redirect to('/bar?summe=42') +``` + +oder eine Session verwendet werden: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache einsetzen + +Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein +ordentliches HTTP-Caching. + +Der Cache-Control-Header lässt sich ganz einfach einstellen: + +```ruby +get '/' do + cache_control :public + "schon gecached!" +end +``` + +Profitipp: Caching im before-Filter aktivieren + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Bei Verwendung der `expires`-Helfermethode zum Setzen des gleichnamigen +Headers, wird `Cache-Control` automatisch eigestellt: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Um alles richtig zu machen, sollten auch `etag` oder `last_modified` verwendet +werden. Es wird empfohlen, dass diese Helfer aufgerufen werden *bevor* die +eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client +eine aktuelle Version im Cache vorhält: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +ebenso ist es möglich einen +[schwachen ETag](https://de.wikipedia.org/wiki/HTTP_ETag) zu verwenden: + +```ruby +etag @article.sha1, :weak +``` + +Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür +notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy +Cache-Lösungen bietet sich z.B. +[rack-cache](https://github.com/rtomayko/rack-cache) an: + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein If-Match +oder ein If-None-Match Header auf `*` gesetzt wird in Abhängigkeit davon, ob +die Resource bereits existiert. Sinatra geht davon aus, dass Ressourcen bei +sicheren Anfragen (z.B. bei get oder Idempotenten Anfragen wie put) bereits +existieren, wobei anderen Ressourcen (besipielsweise bei post), als neue +Ressourcen behandelt werden. Dieses Verhalten lässt sich mit der +`:new_resource` Option ändern: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Soll das schwache ETag trotzdem verwendet werden, verwendet man die `:kind` +Option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Dateien versenden + +Um den Inhalt einer Datei als Response zurückzugeben, kann die +`send_file`-Helfer-Methode verwendet werden: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Für `send_file` stehen einige Hash-Optionen zur Verfügung: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +
    +
    filename
    +
    Dateiname als Response. + Standardwert ist der eigentliche Dateiname.
    + +
    last_modified
    +
    Wert für den Last-Modified-Header, Standardwert ist mtime der + Datei.
    + +
    type
    +
    Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, + von der Dateiendung abgeleitet.
    + +
    disposition
    +
    Verwendet für Content-Disposition. Mögliche Werte sind: nil + (Standard), :attachment und :inline.
    + +
    length
    +
    Content-Length-Header. Standardwert ist die Dateigröße.
    +
    Status
    +
    Zu versendender Status-Code. Nützlich, wenn eine statische Datei + als Fehlerseite zurückgegeben werden soll. Wenn der Rack-Handler es + unterstützt, dann können auch andere Methoden außer Streaming vom + Ruby-Prozess verwendet werden. Wird diese Helfermethode verwendet, + dann wird Sinatra sich automatisch um die Range-Anfrage kümmern.
    +
    + +### Das Request-Objekt + +Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus +zugegriffen werden (d.h. Filter, Routen, Fehlerbehandlung): + +```ruby +# App läuft unter http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # Request-Body des Client (siehe unten) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # Länge des request.body + request.media_type # Medientypus von request.body + request.host # "example.com" + request.get? # true (ähnliche Methoden für andere Verben) + request.form_data? # false + request["irgendein_param"] # Wert von einem Parameter; [] ist die Kurzform für den params Hash + request.referrer # Der Referrer des Clients oder '/' + request.user_agent # User-Agent (verwendet in der :agent Bedingung) + request.cookies # Hash des Browser-Cookies + request.xhr? # Ist das hier ein Ajax-Request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-Adresse des Clients + request.secure? # false (true wenn SSL) + request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) + request.env # vollständiger env-Hash von Rack übergeben +end +``` + +Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch +schreibbar: + +```ruby +before { request.path_info = "/" } + +get "/" do + "Alle Anfragen kommen hier an!" +end +``` + +Der `request.body` ist ein IO- oder StringIO-Objekt: + +```ruby +post "/api" do + request.body.rewind # falls schon jemand davon gelesen hat + daten = JSON.parse request.body.read + "Hallo #{daten['name']}!" +end +``` + +### Anhänge + +Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser +angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: + +```ruby +get '/' do + attachment + "Speichern!" +end +``` + +Ebenso kann ein Dateiname als Parameter hinzugefügt werden: + +```ruby +get '/' do + attachment "info.txt" + "Speichern!" +end +``` + +### Umgang mit Datum und Zeit + +Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert +ein Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und +ähnliche Klassen konvertieren: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "noch Zeit" +end +``` + +Diese Methode wird intern für `expires`, `last_modiefied` und ihresgleichen +verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem +Verhalten erweitern, indem man `time_for` in der eigenen Applikation +überschreibt: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "Hallo" +end +``` + +### Nachschlagen von Template-Dateien + +Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum +Rendern aufzufinden: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "könnte diese hier sein: #{file}" +end +``` + +Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann +sie nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum +Beispiel dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Ein anderes Beispiel wäre, verschiedene Verzeichnisse für verschiedene Engines +zu verwenden: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt +werden! + +Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich +existiert. Es wird lediglich der angegebene Block aufgerufen und nach allen +möglichen Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` +`block` verwendet, sobald eine Datei gefunden wurde. Ebenso werden +Template-Pfade samt Inhalten gecached, solange nicht im Entwicklungsmodus +gearbeitet wird. Das sollte im Hinterkopf behalten werden, wenn irgendwelche +verrückten Methoden zusammengebastelt werden. + +### Konfiguration + +Wird einmal beim Starten in jedweder Umgebung ausgeführt: + +```ruby +configure do + # setze eine Option + set :option, 'wert' + + # setze mehrere Optionen + set :a => 1, :b => 2 + + # das gleiche wie `set :option, true` + enable :option + + # das gleiche wie `set :option, false` + disable :option + + # dynamische Einstellungen mit Blöcken + set(:css_dir) { File.join(views, 'css') } +end +``` + +Läuft nur, wenn die Umgebung (`APP_ENV`-Umgebungsvariable) auf `:production` +gesetzt ist: + +```ruby +configure :production do + ... +end +``` + +Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: + +```ruby +configure :production, :test do + ... +end +``` + +Diese Einstellungen sind über `settings` erreichbar: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +#### Einstellung des Angriffsschutzes + +Sinatra verwendet +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme), um die +Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung +lässt sich selbstverständlich deaktivieren, der damit verbundene +Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen +Risiken. + +```ruby +disable :protection +``` + +Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` +einen Hash mit Optionen hinzu: + +```ruby +set :protection, :except => :path_traversal +``` + +Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere +Schutzmechanismen zu deaktivieren: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Standardmäßig setzt Sinatra einen sitzungbasierten Schutz nur dann ein, +wenn `:sessions` aktiviert wurde (siehe oben). Manchmal kann es aber +auch sein, dass Sitzungen außerhalb von Sinatra eingerichtet werden, +z.B. über eine config.ru oder einer zusätzlichen +`Rack::Builder`-Instance. In diesen Situationen kann eine +sitzungbasierte Sicherung eingesetzt werden mit Hilfe der +`:session`-Option: + +```ruby +set :protection, session => true +``` + +#### Mögliche Einstellungen + +
    +
    absolute_redirects
    +
    + Wenn ausgeschaltet, wird Sinatra relative Redirects zulassen. + Jedoch ist Sinatra dann nicht mehr mit RFC 2616 (HTTP 1.1) + konform, das nur absolute Redirects zulässt. +
    +
    + Sollte eingeschaltet werden, wenn die Applikation hinter einem + Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. + Beachte, dass die url-Helfer-Methode nach wie vor + absolute URLs erstellen wird, es sei denn, es wird als zweiter + Parameter false angegeben. +
    +
    Standardmäßig nicht aktiviert.
    + +
    add_charset
    +
    + Mime-Types werden hier automatisch der Helfer-Methode + content_type zugeordnet. Es empfielt sich, Werte + hinzuzufügen statt sie zu überschreiben: settings.add_charset + << "application/foobar" +
    + +
    app_file
    +
    + Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, + Inline-, View- und öffentliche Verzeichnis des Projekts festzustellen. +
    + +
    bind
    +
    + IP-Adresse, an die gebunden wird (Standardwert: 0.0.0.0 + oder localhost). Wird nur für den eingebauten Server + verwendet. +
    + +
    default_encoding
    +
    + Das Encoding, falls keines angegeben wurde. Standardwert ist + "utf-8". +
    + +
    dump_errors
    +
    Fehler im Log anzeigen.
    + +
    environment
    +
    + Momentane Umgebung. Standardmäßig auf ENV['APP_ENV'] oder + "development" eingestellt, soweit ersteres nicht vorhanden. +
    + +
    logging
    +
    Den Logger verwenden.
    + +
    lock
    +
    + Jeder Request wird gelocked. Es kann nur ein Request pro Ruby-Prozess + gleichzeitig verarbeitet werden. +
    +
    + Eingeschaltet, wenn die Applikation threadsicher ist. Standardmäßig + nicht aktiviert. +
    + +
    method_override
    +
    + Verwende _method, um put/delete-Formulardaten in Browsern zu + verwenden, die dies normalerweise nicht unterstützen. +
    + +
    mustermann_opts
    +
    + Ein Hash mit Standardeinstellungen, der an Mustermann.new beim + Kompilieren der Routen übergeben wird. +
    + +
    port
    +
    Port für die Applikation. Wird nur im internen Server verwendet.
    + +
    prefixed_redirects
    +
    + Entscheidet, ob request.script_name in Redirects + eingefügt wird oder nicht, wenn kein absoluter Pfad angegeben ist. + Auf diese Weise verhält sich redirect '/foo' so, als wäre + es ein redirect to('/foo'). Standardmäßig nicht + aktiviert. +
    + +
    protection
    +
    + Legt fest, ob der Schutzmechanismus für häufig Vorkommende Webangriffe + auf Webapplikationen aktiviert wird oder nicht. Weitere Informationen im + vorhergehenden Abschnitt. +
    + +
    public_folder
    +
    + Das öffentliche Verzeichnis, aus dem Daten zur Verfügung gestellt + werden können. Wird nur dann verwendet, wenn statische Daten zur + Verfügung gestellt werden können (s.u. static Option). + Leitet sich von der app_file Einstellung ab, wenn nicht + gesetzt. +
    + +
    quiet
    +
    + Deaktiviert Logs, die beim Starten und Beenden von Sinatra + ausgegeben werden. false ist die Standardeinstellung. +
    + +
    public_dir
    +
    Alias für public_folder, s.o.
    + +
    reload_templates
    +
    + Legt fest, ob Templates für jede Anfrage neu generiert werden. Im + development-Modus aktiviert. +
    + +
    root
    +
    + Wurzelverzeichnis des Projekts. Leitet sich von der app_file + Einstellung ab, wenn nicht gesetzt. +
    + +
    raise_errors
    +
    + Einen Ausnahmezustand aufrufen. Beendet die Applikation. Ist + automatisch aktiviert, wenn die Umgebung auf "test" + eingestellt ist. Ansonsten ist diese Option deaktiviert. +
    + +
    run
    +
    + Wenn aktiviert, wird Sinatra versuchen, den Webserver zu starten. Nicht + verwenden, wenn Rackup oder anderes verwendet werden soll. +
    + +
    running
    +
    Läuft der eingebaute Server? Diese Einstellung nicht ändern!
    + +
    server
    +
    + Server oder Liste von Servern, die als eingebaute Server zur + Verfügung stehen. Die Reihenfolge gibt die Priorität vor, die + Voreinstellung hängt von der verwendenten Ruby Implementierung ab. +
    + +
    sessions
    +
    + Sessions auf Cookiebasis mittels + Rack::Session::Cookie aktivieren. Für weitere Infos bitte + in der Sektion ‘Sessions verwenden’ nachschauen. +
    + +
    session_store
    +
    + Die verwendete Rack Sitzungs-Middleware. Verweist standardmäßig + auf Rack::Session::Cookie. Für weitere Infos bitte + in der Sektion ‘Sessions verwenden’ nachschauen. +
    + +
    show_exceptions
    +
    + Bei Fehlern einen Stacktrace im Browser anzeigen. Ist automatisch + aktiviert, wenn die Umgebung auf "development" + eingestellt ist. Ansonsten ist diese Option deaktiviert. +
    +
    + Kann auch auf :after_handler gestellt werden, um eine + anwendungsspezifische Fehlerbehandlung auszulösen, bevor der + Fehlerverlauf im Browser angezeigt wird. +
    + +
    static
    +
    + Entscheidet, ob Sinatra statische Dateien zur Verfügung stellen + soll oder nicht. Sollte nicht aktiviert werden, wenn ein Server + verwendet wird, der dies auch selbstständig erledigen kann. + Deaktivieren wird die Performance erhöhen. Standardmäßig + aktiviert. +
    + +
    static_cache_control
    +
    + Wenn Sinatra statische Daten zur Verfügung stellt, können mit + dieser Einstellung die Cache-Control Header zu den + Responses hinzugefügt werden. Die Einstellung verwendet dazu die + cache_control Helfer-Methode. Standardmäßig deaktiviert. + Ein Array wird verwendet, um mehrere Werte gleichzeitig zu + übergeben: set :static_cache_control, [:public, :max_age => + 300] +
    + +
    threaded
    +
    + Wird es auf true gesetzt, wird Thin aufgefordert + EventMachine.defer zur Verarbeitung des Requests einzusetzen. +
    + +
    traps
    +
    Legt fest, wie Sinatra mit System-Signalen umgehen soll.
    + +
    views
    +
    + Verzeichnis der Views. Leitet sich von der app_file Einstellung + ab, wenn nicht gesetzt. +
    + +
    x_cascade
    +
    + Einstellung, ob der X-Cascade Header bei fehlender Route gesetzt + wird oder nicht. Standardeinstellung ist true. +
    +
    + +## Umgebungen + +Es gibt drei voreingestellte Umgebungen in Sinatra: `"development"`, +`"production"` und `"test"`. Umgebungen können über die `APP_ENV` +Umgebungsvariable gesetzt werden. Die Standardeinstellung ist `"development"`. +In diesem Modus werden alle Templates zwischen Requests neu geladen. Dazu gibt +es besondere Fehlerseiten für 404 Stati und Fehlermeldungen. In `"production"` +und `"test"` werden Templates automatisch gecached. + +Um die Anwendung in einer anderen Umgebung auszuführen, kann man die +`APP_ENV`-Umgebungsvariable setzen: + +```shell +APP_ENV=production ruby my_app.rb +``` + +In der Anwendung kann man die die Methoden `development?`, `test?` und +`production?` verwenden, um die aktuelle Umgebung zu erfahren: + + +```ruby +get '/' do + if settings.development? + "development!" + else + "nicht development!" + end +end +``` + +## Fehlerbehandlung + +Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, +dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. + +### Nicht gefunden + +Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 +ist, wird der `not_found`-Handler ausgeführt: + +```ruby +not_found do + 'Seite kann nirgendwo gefunden werden.' +end +``` + +### Fehler + +Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem +Routen-Block oder in einem Filter geworfen wurde. In der +`development`-Umgebung wird es nur dann funktionieren, wenn die +`:show_exceptions`-Option auf `:after_handler` eingestellt wurde: + +```ruby +set :show_exceptions, :after_handler +``` + +Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden: + +```ruby +error do + 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].message +end +``` + +Benutzerdefinierte Fehler: + +```ruby +error MeinFehler do + 'Au weia, ' + env['sinatra.error'].message +end +``` + +Dann, wenn das passiert: + +```ruby +get '/' do + raise MeinFehler, 'etwas Schlimmes ist passiert' +end +``` + +bekommt man dieses: + +```shell +Au weia, etwas Schlimmes ist passiert +``` + +Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden: + +```ruby +error 403 do + 'Zugriff verboten' +end + +get '/geheim' do + 403 +end +``` + +Oder ein Status-Code-Bereich: + +```ruby +error 400..510 do + 'Hallo?' +end +``` + +Sinatra setzt verschiedene `not_found`- und `error`-Handler in der +Development-Umgebung ein, um hilfreiche Debugging-Informationen und +Stack-Traces anzuzeigen. + +## Rack-Middleware + +Sinatra baut auf [Rack](http://rack.github.io/) auf, einem minimalistischen +Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features +für Entwickler ist der Support von Middlewares, die zwischen den Server und +die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort +überwachen und/oder manipulieren können. + +Sinatra macht das Erstellen von Middleware-Verkettungen mit der +Top-Level-Methode `use` zu einem Kinderspiel: + +```ruby +require 'sinatra' +require 'meine_middleware' + +use Rack::Lint +use MeineMiddleware + +get '/hallo' do + 'Hallo Welt' +end +``` + +Die Semantik von `use` entspricht der gleichnamigen Methode der +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder)-DSL +(meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die +`use`-Methode mehrere/verschiedene Argumente und auch Blöcke +entgegennimmt: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'geheim' +end +``` + +Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, +URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet +viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So +muss `use` häufig nicht explizit verwendet werden. + +Hilfreiche Middleware gibt es z.B. hier: +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +oder im [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testen + +Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework +geschrieben werden. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +wird empfohlen: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hallo Welt!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hallo Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Du verwendest Songbird!", last_response.body + end +end +``` + +Hinweis: Wird Sinatra modular verwendet, muss +`Sinatra::Application` mit dem Namen der Applikations-Klasse +ersetzt werden. + +## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen + +Das Definieren einer Top-Level-Anwendung funktioniert gut für +Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie +Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder +auch Sinatra-Erweiterungen geschrieben werden sollen. + +Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie +sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, +Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins +Spiel: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hallo Welt!' + end +end +``` + +Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der +Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei +Veränderungen zu `Sinatra::Base` konvertiert werden: + +* Die Datei sollte `require 'sinatra/base'` anstelle von `require + 'sinatra'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden + in den Top-Level-Namespace importiert. +* Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in + einer Subklasse von `Sinatra::Base` definiert werden. + +`Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per +Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe +[Optionen und Konfiguration](http://www.sinatrarb.com/configuration.html) für +Details über mögliche Optionen. + +Damit eine App sich ähnlich wie eine klassische App verhält, kann man +auch eine Subclass von `Sinatra::Application` erstellen: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modularer vs. klassischer Stil + +Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil +einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein +Grund, eine modulare Applikation zu erstellen. + +Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer +modularen ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. +Sollen mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen +werden. Dabei ist es kein Problem klassische und modulare Anwendungen +miteinander zu vermischen. + +Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet +werden: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SzenarioClassicModularModular
    app_fileSinatra ladende DateiSinatra::Base subklassierende DateiSinatra::Application subklassierende Datei
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Eine modulare Applikation bereitstellen + +Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über +`run!`: + +```ruby +# mein_app.rb +require 'sinatra/base' + +class MeinApp < Sinatra::Base + # ... Anwendungscode hierhin ... + + # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird + run! if app_file == $0 +end +``` + +Starte mit: + +```shell +ruby mein_app.rb +``` + +Oder über eine `config.ru`-Datei, die es erlaubt, einen beliebigen +Rack-Handler zu verwenden: + +```ruby +# config.ru (mit rackup starten) +require './mein_app' +run MeineApp +``` + +Starte: + +```shell +rackup -p 4567 +``` + +### Eine klassische Anwendung mit einer config.ru verwenden + +Schreibe eine Anwendungsdatei: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hallo Welt!' +end +``` + +sowie eine dazugehörige `config.ru`-Datei: + +```ruby +require './app' +run Sinatra::Application +``` + +### Wann sollte eine config.ru-Datei verwendet werden? + +Anzeichen dafür, dass eine `config.ru`-Datei gebraucht wird: + +* Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, + Heroku, ...). +* Es gibt mehr als nur eine Subklasse von `Sinatra::Base`. +* Sinatra soll als Middleware verwendet werden, nicht als Endpunkt. + +**Es gibt keinen Grund, eine `config.ru`-Datei zu verwenden, nur weil eine +Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung +mit modularem Stil benötigt, um eine `config.ru`-Datei zu verwenden.** + +### Sinatra als Middleware nutzen + +Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es +kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden +beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich +nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere +Rack-Anwendung sein (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # Middleware wird vor Filtern ausgeführt + use LoginScreen + + before do + unless session['user_name'] + halt "Zugriff verweigert, bitte einloggen." + end + end + + get('/') { "Hallo #{session['user_name']}." } +end +``` + +### Dynamische Applikationserstellung + +Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, +ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit +`Sinatra.new` erreichen: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hallo" } } +my_app.run! +``` + +Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden +oder Sinatra in einer Bibliothek Verwendung findet. + +Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Geltungsbereich und Bindung + +Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur +Verfügung stehen. + +### Anwendungs- oder Klassen-Scope + +Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die +Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um +`Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit +angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur +Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die +`session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt +wird. + +Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungsscope! +set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ich bin nicht mehr im Anwendungs-Scope! + end +end +``` + +Im Anwendungs-Scope befindet man sich: + +* Innerhalb der Anwendungs-Klasse +* In Methoden, die von Erweiterungen definiert werden +* Im Block, der an `helpers` übergeben wird +* In Procs und Blöcken, die an `set` übergeben werden +* Der an `Sinatra.new` übergebene Block + +Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: + +* Über das Objekt, das an den `configure`-Block übergeben wird (`configure { + |c| ... }`). +* `settings` aus den anderen Scopes heraus. + +### Anfrage- oder Instanz-Scope + +Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse +erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus +kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder +`haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den +Anwendungs-Scope zugegriffen werden: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungs-Scope! + get '/neue_route/:name' do + # Anfrage-Scope für '/neue_route/:name' + @value = 42 + + settings.get "/#{params['name']}" do + # Anfrage-Scope für "/#{params['name']}" + @value # => nil (nicht dieselbe Anfrage) + end + + "Route definiert!" + end +end +``` + +Im Anfrage-Scope befindet man sich: + +* In get, head, post, put, delete, options, patch, link und unlink Blöcken +* In before und after Filtern +* In Helfer-Methoden +* In Templates/Views + +### Delegation-Scope + +Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope +weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, +da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als +delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf +die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es +gibt ein anderes `self`). Weitere Delegationen können mit +`Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. + +Im Delegation-Scop befindet man sich: + +* Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. +* In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. + +Schau am besten im Code nach: Hier ist [Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 +) definiert und wird in den [globalen Namespace +eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb). + +## Kommandozeile + +Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER] +``` + +Die Optionen sind: + +``` +-h # Hilfe +-p # Port setzen (Standard ist 4567) +-h # Host setzen (Standard ist 0.0.0.0) +-e # Umgebung setzen (Standard ist development) +-s # Rack-Server/Handler setzen (Standard ist thin) +-q # den lautlosen Server-Modus einschalten (Standard ist aus) +-x # Mutex-Lock einschalten (Standard ist aus) +``` + +### Multi-threading + +_Paraphrasiert von [dieser Antwort auf StackOverflow][so-answer] von +Konstantin_ + +Sinatra erlegt kein Nebenläufigkeitsmodell auf, sondern überlässt dies dem +selbst gewählten Rack-Proxy (Server), so wie Thin, Puma oder WEBrick. +Sinatra selbst ist Thread-sicher, somit ist es kein Problem wenn der +Rack-Proxy ein anderes Threading-Modell für Nebenläufigkeit benutzt. +Das heißt, dass wenn der Server gestartet wird, dass man die korrekte +Aufrufsmethode benutzen sollte für den jeweiligen Rack-Proxy. +Das folgende Beispiel ist eine Veranschaulichung eines mehrprozessigen +Thin Servers: + +``` ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +Um den Server zu starten, führt man das folgende Kommando aus: + +``` shell +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## Systemanforderungen + +Die folgenden Versionen werden offiziell unterstützt: + +
    +
    Ruby 2.3
    +
    + 2.3 wird vollständig unterstützt. Es gibt derzeit keine Pläne die + offizielle Unterstützung zu beenden +
    + +
    Rubinius
    +
    + Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird + empfohlen, den Puma Server zu + installieren (gem install puma) +
    + +
    JRuby
    +
    + Aktuelle JRuby Versionen werden offiziell unterstützt. Es wird empfohlen, + keine C-Erweiterungen zu verwenden und als Server Trinidad zu verwenden + (gem install trinidad). +
    +
    + +Versionen vor Ruby 2.2.2 werden ab Sinatra 2.0 nicht länger unterstützt. + +Nachfolgende Ruby-Versionen werden regelmäßig auf Unterstützung geprüft. + +Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von +Sinatra unterstützt, funktionieren aber normalerweise: + +* Ruby Enterprise Edition +* Ältere Versionen von JRuby und Rubinius +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 und 1.9.1 (wird aber nicht empfohlen) + +Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, +wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen +Implementierung liegt. + +Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head +(zukünftige Versionen von MRI) mit eingebunden. Es kann davon ausgegangen +werden, dass Sinatra MRI auch weiterhin vollständig unterstützen wird. + +Sinatra sollte auf jedem Betriebssystem laufen, das einen funktionierenden +Ruby-Interpreter aufweist. + +Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= +2.2. + +## Der neuste Stand (The Bleeding Edge) + +Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. +Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, +die so installiert werden: + +```shell +gem install sinatra --pre +``` + +### Mit Bundler + +Wenn die Applikation mit der neuesten Version von Sinatra und +[Bundler](http://bundler.io) genutzt werden soll, empfehlen wir den +nachfolgenden Weg. + +Soweit Bundler noch nicht installiert ist: + +```shell +gem install bundler +``` + +Anschließend wird eine `Gemfile`-Datei im Projektverzeichnis mit folgendem +Inhalt erstellt: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# evtl. andere Abhängigkeiten +gem 'haml' # z.B. wenn du Haml verwendest... +``` + +Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, +direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem +Gemfile von Sinatra hinzugefügt. + +Jetzt kannst du deine Applikation starten: + +```shell +bundle exec ruby myapp.rb +``` + +## Versions-Verfahren + +Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. +SemVer und SemVerTag. + +## Mehr + +* [Projekt-Website](http://www.sinatrarb.com/) - Ergänzende Dokumentation, + News und Links zu anderen Ressourcen. +* [Mitmachen](http://www.sinatrarb.com/contributing.html) - Einen Fehler + gefunden? Brauchst du Hilfe? Hast du einen Patch? +* [Issue-Tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing-Liste](http://groups.google.com/group/sinatrarb) +* IRC [#sinatra](irc://chat.freenode.net/#sinatra) auf + http://freenode.net Es gibt dort auch immer wieder deutschsprachige + Entwickler, die gerne weiterhelfen. +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack and see + [here](https://sinatra-slack.herokuapp.com/) for an invite. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Kochbuch Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der + Community +* API Dokumentation für die + [aktuelle Version](http://www.rubydoc.info//gems/sinatra) oder für + [HEAD](http://www.rubydoc.info/github/sinatra/sinatra) auf + http://rubydoc.info +* [CI Server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.es.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.es.md new file mode 100644 index 0000000000..d0a5931922 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.es.md @@ -0,0 +1,3231 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](http://travis-ci.org/sinatra/sinatra) + +Sinatra es un +[DSL](https://es.wikipedia.org/wiki/Lenguaje_específico_del_dominio) para +crear aplicaciones web rápidamente en Ruby con un mínimo esfuerzo: + +```ruby +# miapp.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Instala la gema: + +```shell +gem install sinatra +ruby miapp.rb +``` + +Y corre la aplicación: +```shell +gem install sinatra +ruby miapp.rb +``` + +Ver en [http://localhost:4567](http://localhost:4567). + +El código que cambiaste no tendra efecto hasta que reinicies el servidor. +Por favor reinicia el servidor cada vez que cambies tu código o usa [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Se recomienda ejecutar `gem install puma`, porque Sinatra lo utilizará si está disponible. + + +## Tabla de Contenidos + +* [Sinatra](#sinatra) + * [Tabla de Contenidos](#tabla-de-contenidos) + * [Rutas](#rutas) + * [Condiciones](#condiciones) + * [Valores de Retorno](#valores-de-retorno) + * [Comparadores de Rutas Personalizados](#comparadores-de-rutas-personalizados) + * [Archivos Estáticos](#archivos-estáticos) + * [Vistas / Plantillas](#vistas--plantillas) + * [Plantillas Literales](#plantillas-literales) + * [Lenguajes de Plantillas Disponibles](#lenguajes-de-plantillas-disponibles) + * [Plantillas Haml](#plantillas-haml) + * [Plantillas Erb](#plantillas-erb) + * [Plantillas Builder](#plantillas-builder) + * [Plantillas Nokogiri](#plantillas-nokogiri) + * [Plantillas Sass](#plantillas-sass) + * [Plantillas SCSS](#plantillas-scss) + * [Plantillas Less](#plantillas-less) + * [Plantillas Liquid](#plantillas-liquid) + * [Plantillas Markdown](#plantillas-markdown) + * [Plantillas Textile](#plantillas-textile) + * [Plantillas RDoc](#plantillas-rdoc) + * [Plantillas AsciiDoc](#plantillas-asciidoc) + * [Plantillas Radius](#plantillas-radius) + * [Plantillas Markaby](#plantillas-markaby) + * [Plantillas RABL](#plantillas-rabl) + * [Plantillas Slim](#plantillas-slim) + * [Plantillas Creole](#plantillas-creole) + * [Plantillas MediaWiki](#mediawiki-templates) + * [Plantillas CofeeScript](#plantillas-coffeescript) + * [Plantillas Stylus](#plantillas-stylus) + * [Plantillas Yajl](#plantillas-yajl) + * [Plantillas Wlang](#plantillas-wlang) + * [Accediendo Variables en Plantillas](#accediendo-a-variables-en-plantillas) + * [Plantillas con `yield` y `layout` anidado](#plantillas-con-yield-y-layout-anidado) + * [Plantillas Inline](#plantillas-inline) + * [Plantillas Nombradas](#plantillas-nombradas) + * [Asociando Extensiones de Archivo](#asociando-extensiones-de-archivo) + * [Añadiendo Tu Propio Motor de Plantillas](#añadiendo-tu-propio-motor-de-plantillas) + * [Usando Lógica Personalizada para la Búsqueda en Plantillas](#usando-lógica-personalizada-para-la-búsqueda-en-plantillas) + * [Filtros](#filtros) + * [Helpers](#helpers) + * [Usando Sesiones](#usando-sesiones) + * [Secreto de Sesión](#secreto-de-sesión) + * [Configuración de Sesión](#configuración-de-sesión) + * [Escogiendo tu propio Middleware de Sesión](#escogiendo-tu-propio-middleware-de-sesión) + * [Interrupcion](#interrupción) + * [Paso](#paso) + * [Desencadenando Otra Ruta](#desencadenando-otra-ruta) + * [Configurando el Cuerpo, Código de Estado y los Encabezados](#configurando-el-cuerpo-código-de-estado-y-los-encabezados) + * [Streaming De Respuestas](#streaming-de-respuestas) + * [Logging](#logging) + * [Tipos Mime](#tipos-mime) + * [Generando URLs](#generando-urls) + * [Redirección del Navegador](#redirección-del-navegador) + * [Control del Cache](#control-del-cache) + * [Enviando Archivos](#enviando-archivos) + * [Accediendo al Objeto Request](#accediendo-al-objeto-request) + * [Archivos Adjuntos](#archivos-adjuntos) + * [Fecha y Hora](#fecha-y-hora) + * [Buscando los Archivos de las Plantillas](#buscando-los-archivos-de-las-plantillas) + * [Configuración](#configuración) + * [Configurando la Protección Contra Ataques](#configurando-la-protección-contra-ataques) + * [Configuraciones Disponibles](#configuraciones-disponibles) + * [Entornos](#entornos) + * [Manejo de Errores](#manejo-de-errores) + * [Not Found](#not-found) + * [Error](#error) + * [Rack Middleware](#rack-middleware) + * [Pruebas](#pruebas) + * [Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares](#sinatrabase---middleware-librerías-y-aplicaciones-modulares) + * [Estilo Modular vs Estilo Clásico](#estilo-modular-vs-clásico) + * [Sirviendo una Aplicación Modular](#sirviendo-una-aplicación-modular) + * [Usando una Aplicación de Estilo Clásico con config.ru](#usando-una-aplicación-clásica-con-un-archivo-configru) + * [¿Cuándo usar config.ru?](#cuándo-usar-configru) + * [Utilizando Sinatra como Middleware](#utilizando-sinatra-como-middleware) + * [Creación Dinámica de Aplicaciones](#creación-dinámica-de-aplicaciones) + * [Ámbitos y Ligaduras (Scopes and Binding)](#Ámbitos-y-ligaduras) + * [Alcance de una Aplicación/Clase](#Ámbito-de-aplicaciónclase) + * [Alcance de una Solicitud/Instancia](#Ámbito-de-peticióninstancia) + * [Alcance de Delegación](#Ámbito-de-delegación) + * [Línea de comandos](#línea-de-comandos) + * [Multi-threading](#multi-threading) + * [Requerimientos](#requerimientos) + * [A la Vanguardia](#a-la-vanguardia) + * [Usando bundler](#usando-bundler) + * [Versionado](#versionado) + * [Lecturas Recomendadas](#lecturas-recomendadas) + +## Rutas + +En Sinatra, una ruta es un método HTTP junto a un patrón de un URL. +Cada ruta está asociada a un bloque: + +```ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. crear algo .. +end + +put '/' do + .. reemplazar algo .. +end + +patch '/' do + .. modificar algo .. +end + +delete '/' do + .. aniquilar algo .. +end + +options '/' do + .. informar algo .. +end + +link '/' do + .. afiliar a algo .. +end + +unlink '/' do + .. separar algo .. +end + +``` + +Las rutas son comparadas en el orden en el que son definidas. La primera ruta +que coincide con la petición es invocada. + +Las rutas con barras al final son distintas a las que no tienen: + + +```ruby +get '/foo' do + # no es igual que "GET /foo/" +end +``` + +Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a +través del hash `params`: + + +```ruby +get '/hola/:nombre' do + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + "Hola #{params['nombre']}!" +end +``` + +También puede acceder a los parámetros nombrados usando parámetros de bloque: + +```ruby +get '/hola/:nombre' do |n| + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + # n almacena params['nombre'] + "Hola #{n}!" +end +``` + +Los patrones de ruta también pueden incluir parámetros splat (o wildcard), +accesibles a través del arreglo `params['splat']`: + +```ruby +get '/decir/*/al/*' do + # coincide con /decir/hola/al/mundo + params['splat'] # => ["hola", "mundo"] +end + +get '/descargar/*.*' do + # coincide con /descargar/path/al/archivo.xml + params['splat'] # => ["path/al/archivo", "xml"] +end +``` + +O, con parámetros de bloque: + +```ruby +get '/descargar/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Rutas con Expresiones Regulares: + +```ruby +get /\/hola\/([\w]+)/ do + "Hola, #{params['captures'].first}!" +end +``` + +O con un parámetro de bloque: + +```ruby +get %r{/hola/([\w]+)} do |c| + "Hola, #{c}!" +end +``` + +Los patrones de ruta pueden contener parámetros opcionales: + +```ruby +get '/posts/:formato?' do + # coincide con "GET /posts/" y además admite cualquier extensión, por + # ejemplo, "GET /posts/json", "GET /posts/xml", etc. +end +``` + +Las rutas también pueden usar parámetros de consulta: + +```ruby +get '/posts' do + # es igual que "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # usa las variables title y author; la consulta es opcional para la ruta /posts +end +``` + +A propósito, a menos que desactives la protección para el ataque *path +traversal* (ver más [abajo](#configurando-la-protección-contra-ataques)), el path de la petición puede ser modificado +antes de que se compare con los de tus rutas. + +Puedes perzonalizar las opciones de [Mustermann](https://github.com/sinatra/mustermann) usadas para una ruta pasando +el hash `:mustermann_opts`: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # es exactamente igual a /posts, con anclaje explícito + "¡Si igualas un patrón anclado aplaude!" +end +``` + +## Condiciones + +Las rutas pueden incluir una variedad de condiciones de selección, como por +ejemplo el user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Estás usando la versión de Songbird #{params['agent'][0]}" +end + +get '/foo' do + # Coincide con navegadores que no sean songbird +end +``` + +Otras condiciones disponibles son `host_name` y `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Área de Administración, Acceso denegado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` busca el encabezado Accept de la solicitud + + +Puede definir sus propias condiciones fácilmente: + +```ruby +set(:probabilidad) { |valor| condition { rand <= valor } } + +get '/gana_un_auto', :probabilidad => 0.1 do + "Ganaste!" +end + +get '/gana_un_auto' do + "Lo siento, perdiste." +end +``` + +Para una condición que toma multiples valores usa splat: + +```ruby +set(:autorizar) do |*roles| # <- mira el splat + condition do + unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol } + redirect "/iniciar_sesion/", 303 + end + end +end + +get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do + "Detalles de mi cuenta" +end + +get "/solo/administradores/", :autorizar => :administrador do + "Únicamente para administradores!" +end +``` + +## Valores de Retorno + +El valor de retorno de un bloque de ruta determina por lo menos el cuerpo de la respuesta +transmitida al cliente HTTP o por lo menos al siguiente middleware en la pila de Rack. +Lo más común es que sea un string, como en los ejemplos anteriores. +Sin embargo, otros valores también son aceptados. + +Puedes retornar cualquier objeto que sea una respuesta Rack válida, +un objeto que represente el cuerpo de una respuesta Rack o un código +de estado HTTP: + +* Un arreglo con tres elementos: `[estado (Integer), cabeceras (Hash), cuerpo de + la respuesta (responde a #each)]` +* Un arreglo con dos elementos: `[estado (Integer), cuerpo de la respuesta + (responde a #each)]` +* Un objeto que responde a `#each` y que le pasa únicamente strings al bloque + dado +* Un Integer representando el código de estado + +De esa manera, por ejemplo, podemos fácilmente implementar un ejemplo de streaming: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +También puedes usar el `stream` helper ([descrito abajo](#streaming-de-respuestas)) +para reducir el código repetitivo e incrustar la lógica de stream a la ruta + +## Comparadores de Rutas Personalizados + +Como se mostró anteriormente, Sinatra permite utilizar strings y expresiones +regulares para definir las rutas. Sin embargo, no termina ahí.Puedes +definir tus propios comparadores muy fácilmente: + +```ruby +class TodoMenosElPatron + Match = Struct.new(:captures) + + def initialize(excepto) + @excepto = excepto + @capturas = Match.new([]) + end + + def match(str) + @capturas unless @excepto === str + end +end + +def cualquiera_menos(patron) + TodoMenosElPatron.new(patron) +end + +get cualquiera_menos("/index") do + # ... +end +``` + +Tenga en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado +similar puede conseguirse más sencillamente: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +O, usando un look ahead negativo: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Archivos Estáticos + +Los archivos estáticos son servidos desde el directorio público +`./public`. Puede especificar una ubicación diferente ajustando la +opción `:public_folder`: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Note que el nombre del directorio público no está incluido en la URL. Por +ejemplo, el archivo `./public/css/style.css` se accede a través de +`http://ejemplo.com/css/style.css`. + +Use la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` (Ver mas [abajo](#control-del-cache)). + +## Vistas / Plantillas + +Cada lenguaje de plantilla se expone a través de un método de renderizado que +lleva su nombre. Estos métodos simplemente devuelven un string: + +```ruby +get '/' do + erb :index +end +``` + +Renderiza `views/index.erb`. + +En lugar del nombre de la plantilla puedes proporcionar directamente el +contenido de la misma: + +```ruby +get '/' do + codigo = "<%= Time.now %>" + erb codigo +end +``` + +Los métodos de renderizado, aceptan además un segundo argumento, el hash de +opciones: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Renderiza `views/index.erb` incrustado en `views/post.erb` (por +defecto, la plantilla `:index` es incrustada en `views/layout.erb` siempre y +cuando este último archivo exista). + +Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado +de la plantilla: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Además, puede definir las opciones para un lenguaje de plantillas de forma +general: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Las opciones pasadas al método de renderizado tienen precedencia sobre las +definidas mediante `set`. + +Opciones disponibles: + +
    + +
    locals
    +
    + Lista de variables locales pasadas al documento. Resultan muy útiles cuando + se combinan con parciales. + Ejemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + Encoding utilizado cuando el de un string es dudoso. Por defecto toma el + valor de settings.default_encoding. +
    + +
    views
    +
    + Directorio desde donde se cargan las vistas. Por defecto toma el valor de + settings.views. +
    + +
    layout
    +
    + Si es true o false indica que se debe usar, o no, un layout, + respectivamente. También puede ser un Symbol que especifique qué plantilla + usar. Ejemplo: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type que produce la plantilla. El valor por defecto depende de cada + lenguaje de plantillas. +
    + +
    scope
    +
    + Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia + de la aplicación. Ten en cuenta que si cambiás esta opción las variables de + instancia y los helpers van a dejar de estar disponibles. +
    + +
    layout_engine
    +
    + Motor de renderizado de plantillas que usa para el layout. Resulta + conveniente para lenguajes que no soportan layouts. Por defecto toma el valor + del motor usado para renderizar la plantilla. + Ejemplo: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Opciones especiales usadas únicamente para renderizar el layout. Ejemplo: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Se asume que las plantillas están ubicadas directamente bajo el directorio `./views`. +Para usar un directorio diferente: + +```ruby +set :views, settings.root + '/templates' +``` + +Es importante acordarse que siempre tienes que referenciar a las plantillas con +símbolos, incluso cuando se encuentran en un subdirectorio (en este caso +tienes que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Esto es debido +a que los métodos de renderización van a renderizar directamente cualquier string que +se les pase como argumento. + +### Plantillas Literales + +```ruby +get '/' do + haml '%div.titulo Hola Mundo' +end +``` + +Renderiza el string de la plantilla. Opcionalmente puedes especificar +`:path` y `:line` para un backtrace más claro si hay una ruta del sistema +de archivos o una línea asociada con ese string + +```ruby +get '/' do + haml '%div.titulo Hola Mundo', :path => 'ejemplos/archivo.haml', :line => 3 +end +``` + +### Lenguajes de Plantillas Disponibles + +Algunos lenguajes tienen varias implementaciones. Para especificar que +implementación usar (y para ser thread-safe), deberías requerirla antes de +usarla: + +```ruby +require 'rdiscount' # o require 'bluecloth' +get('/') { markdown :index } +``` + +#### Plantillas Haml + + + + + + + + + + + + + + +
    Dependenciashaml
    Expresiones de Archivo.haml
    Ejemplohaml :index, :format => :html5
    + +#### Plantillas Erb + + + + + + + + + + + + + + +
    Dependencias + erubis + o erb (incluida en Ruby) +
    Extensiones de Archivo.erb, .rhtml o .erubis (solamente con Erubis)
    Ejemploerb :index
    + +#### Plantillas Builder + + + + + + + + + + + + + + +
    Dependencias + builder +
    Extensiones de Archivo.builder
    Ejemplobuilder { |xml| xml.em "hola" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas Nokogiri + + + + + + + + + + + + + + +
    Dependenciasnokogiri
    Extensiones de Archivo.nokogiri
    Ejemplonokogiri { |xml| xml.em "hola" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas Sass + + + + + + + + + + + + + + +
    Dependenciassass
    Extensiones de Archivo.sass
    Ejemplosass :stylesheet, :style => :expanded
    + +#### Plantillas SCSS + + + + + + + + + + + + + + +
    Dependenciassass
    Extensiones de Archivo.scss
    Ejemploscss :stylesheet, :style => :expanded
    + +#### Plantillas Less + + + + + + + + + + + + + + +
    Dependenciasless
    Extensiones de Archivo.less
    Ejemploless :stylesheet
    + +#### Plantillas Liquid + + + + + + + + + + + + + + +
    Dependenciasliquid
    Extensiones de Archivo.liquid
    Ejemploliquid :index, :locals => { :clave => 'valor' }
    + +Como no va a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla Liquid, casi siempre va a querer pasarle locales. + +#### Plantillas Markdown + + + + + + + + + + + + + + +
    Dependencias + RDiscount, + RedCarpet, + BlueCloth, + kramdown o + maruku +
    Extensiones de Archivo.markdown, .mkd y .md
    Ejemplomarkdown :index, :layout_engine => :erb
    + +No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => markdown(:introduccion) } +``` + +Tenga en cuenta que también puedes llamar al método `markdown` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= markdown(:saludos) +``` + +Como no puede utilizar Ruby desde Markdown, no puede usar layouts escritos en +Markdown. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas Textile + + + + + + + + + + + + + + +
    DependenciasRedCloth
    Extensiones de Archivo.textile
    Ejemplotextile :index, :layout_engine => :erb
    + +No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => textile(:introduccion) } +``` + +Ten en cuenta que también puedes llamar al método `textile` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= textile(:saludos) +``` + +Como no puedes utilizar Ruby desde Textile, no puedes usar layouts escritos en +Textile. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas RDoc + + + + + + + + + + + + + + +
    DependenciasRDoc
    Extensiones de Archivo.rdoc
    Ejemplordoc :README, :layout_engine => :erb
    + +No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => rdoc(:introduccion) } +``` + +Ten en cuenta que también puedes llamar al método `rdoc` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= rdoc(:saludos) +``` + +Como no puedes utilizar Ruby desde RDoc, no puedes usar layouts escritos en RDoc. +De todos modos, es posible usar un motor de renderizado para el layout distinto +al de la plantilla pasando la opción `:layout_engine`. + +#### Plantillas AsciiDoc + + + + + + + + + + + + + + +
    DependenciaAsciidoctor
    Extensiones de Archivo.asciidoc, .adoc and .ad
    Ejemploasciidoc :README, :layout_engine => :erb
    + +Desde que no se puede utilizar métodos de Ruby desde una +plantilla AsciiDoc, casi siempre va a querer pasarle locales. + +#### Plantillas Radius + + + + + + + + + + + + + + +
    DependenciasRadius
    Extensiones de Archivo.radius
    Ejemploradius :index, :locals => { :clave => 'valor' }
    + +Desde que no se puede utilizar métodos de Ruby (excepto por `yield`) de una +plantilla Radius, casi siempre se necesita pasar locales. + +#### Plantillas Markaby + + + + + + + + + + + + + + +
    DependenciasMarkaby
    Extensiones de Archivo.mab
    Ejemplomarkaby { h1 "Bienvenido!" }
    + +También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)). + +#### Plantillas RABL + + + + + + + + + + + + + + +
    DependenciasRabl
    Extensiones de Archivo.rabl
    Ejemplorabl :index
    + +#### Plantillas Slim + + + + + + + + + + + + + + +
    DependenciasSlim Lang
    Extensiones de Archivo.slim
    Ejemploslim :index
    + +#### Plantillas Creole + + + + + + + + + + + + + + +
    DependenciasCreole
    Extensiones de Archivo.creole
    Ejemplocreole :wiki, :layout_engine => :erb
    + +No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +```ruby +erb :resumen, :locals => { :texto => cerole(:introduccion) } +``` + +Debe tomar en cuenta que también puede llamar al método `creole` desde otras +plantillas: + +```ruby +%h1 Hola Desde Haml! +%p= creole(:saludos) +``` + +Como no puedes utilizar Ruby desde Creole, no puedes usar layouts escritos en +Creole. De todos modos, es posible usar un motor de renderizado para el layout +distinto al de la plantilla pasando la opción `:layout_engine`. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependenciaWikiCloth
    Extension de Archivo.mediawiki and .mw
    Ejemplomediawiki :wiki, :layout_engine => :erb
    + +No es posible llamar métodos desde el markup de MediaWiki, ni pasar locales al mismo. +Por lo tanto usualmente lo usarás en combinación con otro motor de renderizado: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Nota que también puedes llamar al método `mediawiki` desde dentro de otras plantillas: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Debido a que no puedes llamar a Ruby desde MediaWiki, no puedes usar los diseños escritos en MediaWiki. +De todas maneras, es posible usar otro motor de renderizado para esa plantilla pasando la opción :layout_engine. + +#### Plantillas CoffeeScript + + + + + + + + + + + + + + +
    Dependencias + + CoffeeScript + y un + + mecanismo para ejecutar javascript + +
    Extensiones de Archivo.coffee
    Ejemplocoffee :index
    + +#### Plantillas Stylus + + + + + + + + + + + + + + +
    Dependencias + + Stylus + y un + + mecanismo para ejecutar javascript + +
    Extensiones de Archivo.styl
    Ejemplostylus :index
    + +Antes de poder usar las plantillas de Stylus, necesitas cargar `stylus` y `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Plantillas Yajl + + + + + + + + + + + + + + +
    Dependenciasyajl-ruby
    Extensiones de Archivo.yajl
    Ejemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +El contenido de la plantilla se evalúa como código Ruby, y la variable `json` es convertida a JSON mediante `#to_json`. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Las opciones `:callback` y `:variable` se pueden utilizar para decorar el objeto renderizado: + +```ruby +var contenido = {"foo":"bar","baz":"qux"}; +present(contenido); +``` + +#### Plantillas WLang + + + + + + + + + + + + + + +
    Dependenciaswlang
    Extensiones de Archivo.wlang
    Ejemplowlang :index, :locals => { :clave => 'valor' }
    + +Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla WLang, casi siempre vas a querer pasarle locales. + +### Accediendo a Variables en Plantillas + +Las plantillas son evaluadas dentro del mismo contexto que los manejadores de +ruta. Las variables de instancia asignadas en los manejadores de ruta son +accesibles directamente por las plantillas: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nombre' +end +``` + +O es posible especificar un Hash de variables locales explícitamente: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.nombre', :locals => { :bar => foo } +end +``` + +Esto es usado típicamente cuando se renderizan plantillas como parciales desde +adentro de otras plantillas. + +### Plantillas con `yield` y `layout` anidado + +Un layout es usualmente una plantilla que llama a `yield`. +Dicha plantilla puede ser usada tanto a travé de la opción `:template` +como describimos arriba, o puede ser rederizada con un bloque como a +continuación: + +```ruby +erb :post, :layout => false do + erb :index +end +``` +Este código es principalmente equivalente a `erb :index, :layout => :post`. + +Pasar bloques a métodos de renderizado es la forma mas útil de crear layouts anidados: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Esto también se puede hacer en menos líneas de código con: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Actualmente, los siguientes métodos de renderizado aceptan un bloque: `erb`, `haml`, +`liquid`, `slim `, `wlang`. También el método general de `render` acepta un bloque. + +### Plantillas Inline + +Las plantillas pueden ser definidas al final del archivo fuente: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.titulo Hola Mundo +``` + +NOTA: Únicamente las plantillas inline definidas en el archivo fuente que +requiere Sinatra son cargadas automáticamente. Llamá `enable +:inline_templates` explícitamente si tienes plantillas inline en otros +archivos fuente. + +### Plantillas Nombradas + +Las plantillas también pueden ser definidas usando el método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.titulo Hola Mundo!' +end + +get '/' do + haml :index +end +``` + +Si existe una plantilla con el nombre "layout", va a ser usada cada vez que +una plantilla es renderizada.Puedes desactivar los layouts individualmente +pasando `:layout => false` o globalmente con +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Asociando Extensiones de Archivo + +Para asociar una extensión de archivo con un motor de renderizado, usa +`Tilt.register`. Por ejemplo, si quieres usar la extensión `tt` para +las plantillas Textile, puedes hacer lo siguiente: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Añadiendo Tu Propio Motor de Plantillas + +Primero, registra tu motor con Tilt, y después, creá tu método de renderizado: + +```ruby +Tilt.register :mipg, MiMotorDePlantilla + +helpers do + def mypg(*args) render(:mypg, *args) end +end + +get '/' do + mypg :index +end +``` + +Renderiza `./views/index.mypg`. Mirá https://github.com/rtomayko/tilt +para aprender más de Tilt. + +### Usando Lógica Personalizada para la Búsqueda en Plantillas + +Para implementar tu propio mecanismo de búsqueda de plantillas puedes +escribir tu propio método `#find_template` + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filtros + +Los filtros `before` son evaluados antes de cada petición dentro del mismo +contexto que las rutas. Pueden modificar la petición y la respuesta. Las +variables de instancia asignadas en los filtros son accesibles por las rutas y +las plantillas: + +```ruby +before do + @nota = 'Hey!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Hey!' + params['splat'] #=> 'bar/baz' +end +``` + +Los filtros `after` son evaluados después de cada petición dentro del mismo +contexto y también pueden modificar la petición y la respuesta. Las variables +de instancia asignadas en los filtros `before` y en las rutas son accesibles por +los filtros `after`: + +```ruby +after do + puts response.status +end +``` + +Nota: A menos que uses el método `body` en lugar de simplemente devolver un +string desde una ruta, el cuerpo de la respuesta no va a estar disponible en +un filtro after, debido a que todavía no se ha generado. + +Los filtros aceptan un patrón opcional, que cuando está presente causa que los +mismos sean evaluados únicamente si el path de la petición coincide con ese +patrón: + +```ruby +before '/protegido/*' do + autenticar! +end + +after '/crear/:slug' do |slug| + session[:ultimo_slug] = slug +end +``` + +Al igual que las rutas, los filtros también pueden aceptar condiciones: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'ejemplo.com' do + # ... +end +``` + +## Helpers + +Usá el método top-level `helpers` para definir métodos ayudantes (helpers) que +pueden ser utilizados dentro de los manejadores de rutas y las plantillas: + +```ruby +helpers do + def bar(nombre) + "#{nombre}bar" + end +end + +get '/:nombre' do + bar(params['nombre']) +end +``` + +Por cuestiones organizativas, puede resultar conveniente organizar los métodos +helpers en distintos módulos: + +```ruby +module FooUtils + def foo(nombre) "#{nombre}foo" end +end + +module BarUtils + def bar(nombre) "#{nombre}bar" end +end + +helpers FooUtils, BarUtils +``` + +El efecto de utilizar `helpers` de esta manera es el mismo que resulta de +incluir los módulos en la clase de la aplicación. + +### Usando Sesiones + +Una sesión es usada para mantener el estado a través de distintas peticiones. +Cuando están activadas, proporciona un hash de sesión para cada sesión de usuario: + +```ruby +enable :sessions + +get '/' do + "valor = " << session[:valor].inspect +end + +get '/:valor' do + session[:valor] = params['valor'] +end +``` + +#### Secreto de Sesión + +Para mejorar la seguridad, los datos de la sesión en la cookie se firman con un secreto usando `HMAC-SHA1`. El secreto de esta sesión debería ser de manera óptima +un valor aleatorio criptográficamente seguro de una longitud adecuada para +`HMAC-SHA1` que es mayor o igual que 64 bytes (512 bits, 128 hex caracteres). +Se le aconsejará que no use un secreto que sea inferior a 32 +bytes de aleatoriedad (256 bits, 64 caracteres hexadecimales). +Por lo tanto, es **muy importante** que no solo invente el secreto, +sino que use un generador de números aleatorios para crearlo. +Los humanos somos extremadamente malos generando valores aleatorios + +De forma predeterminada, un secreto de sesión aleatorio seguro de 32 bytes se genera para usted por +Sinatra, pero cambiará con cada reinicio de su aplicación. Si tienes varias +instancias de tu aplicación y dejas que Sinatra genere la clave, cada instancia +tendría una clave de sesión diferente y probablemente no es lo que quieres. + +Para una mejor seguridad y usabilidad es +[recomendado](https://12factor.net/config) que genere un secreto de sesión +aleatorio seguro y se guarde en las variables de entorno en cada host que ejecuta +su aplicación para que todas las instancias de su aplicación compartan el mismo +secreto. Debería rotar periódicamente esta sesión secreta a un nuevo valor. +Aquí hay algunos ejemplos de cómo puede crear un secreto de 64 bytes y configurarlo: + +**Generación de Secreto de Sesión** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Generación de Secreto de Sesión (Puntos Extras)** + +Usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) para preferir +el uso de el sistema RNG para generar valores aleatorios en lugar de +espacio de usuario `OpenSSL` que MRI Ruby tiene por defecto: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Secreto de Sesión en Variable de Entorno** + +Establezca una variable de entorno `SESSION_SECRET` para Sinatra en el valor que +generaste. Haz que este valor sea persistente durante los reinicios de su host. El +método para hacer esto variará a través de los sistemas operativos, esto es para +propósitos ilustrativos solamente: + + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Configuración de la Aplicación y su Secreto de Sesión** + +Configura tu aplicación a prueba de fallas si la variable de entorno +`SESSION_SECRET` no esta disponible + +Para puntos extras usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) acá tambien: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Configuración de Sesión + +Si desea configurarlo más, también puede almacenar un hash con opciones +en la configuración `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Para compartir su sesión en otras aplicaciones en subdominios de foo.com, agregue el prefijo +dominio con un *.* como este en su lugar: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Escogiendo tu Propio Middleware de Sesión + +Tenga en cuenta que `enable :sessions` en realidad almacena todos los datos en una cookie. +Esto no siempre podria ser lo que quieres (almacenar muchos datos aumentará su +tráfico, por ejemplo). Puede usar cualquier middleware de sesión proveniente de Rack +para hacerlo, se puede utilizar uno de los siguientes métodos: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +O para configurar sesiones con un hash de opciones: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Otra opción es **no** llamar a `enable :sessions`, sino +su middleware de elección como lo haría con cualquier otro middleware. + +Es importante tener en cuenta que al usar este método, la protección +de sesiones **no estará habilitada por defecto**. + +También será necesario agregar el middleware de Rack para hacer eso: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Mira '[Configurando la protección contra ataques](#configurando-la-protección-contra-ataques)' para mas información. + +### Interrupción + +Para detener inmediatamente una petición dentro de un filtro o una ruta debes usar: + +```ruby +halt +``` + +También puedes especificar el estado: + +```ruby +halt 410 +``` + +O el cuerpo: + +```ruby +halt 'esto va a ser el cuerpo' +``` + +O los dos: + +```ruby +halt 401, 'salí de acá!' +``` + +Con cabeceras: + +```ruby +halt 402, { 'Content-Type' => 'text/plain' }, 'venganza' +``` + +Obviamente, es posible utilizar `halt` con una plantilla: + +```ruby +halt erb(:error) +``` + +### Paso + +Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con +la petición usando `pass`: + +```ruby +get '/adivina/:quien' do + pass unless params['quien'] == 'Franco' + 'Adivinaste!' +end + +get '/adivina/*' do + 'Erraste!' +end +``` + +Se sale inmediatamente del bloque de la ruta y se le pasa el control a la +siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve 404. + +### Desencadenando Otra Ruta + +A veces, `pass` no es lo que quieres, sino obtener el +resultado de la llamada a otra ruta. Simplemente use `call` para lograr esto: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Nota que en el ejemplo anterior, es conveniente mover `"bar"` a un +helper, y llamarlo desde `/foo` y `/bar`. Así, vas a simplificar +las pruebas y a mejorar el rendimiento. + +Si quieres que la petición se envíe a la misma instancia de la aplicación en +lugar de otra, usá `call!` en lugar de `call`. + +En la especificación de Rack puedes encontrar más información sobre +`call`. + +### Configurando el Cuerpo, Código de Estado y los Encabezados + +Es posible, y se recomienda, asignar el código de estado y el cuerpo de una +respuesta con el valor de retorno de una ruta. Sin embargo, en algunos +escenarios puede que sea conveniente asignar el cuerpo en un punto arbitrario +del flujo de ejecución con el método helper `body`. A partir de ahí, puedes usar ese +mismo método para acceder al cuerpo de la respuesta: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +También es posible pasarle un bloque a `body`, que será ejecutado por el Rack +handler (puedes usar esto para implementar streaming, mira ["Valores de Retorno"](#valores-de-retorno)). + +De manera similar, también puedes asignar el código de estado y encabezados: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +También, al igual que `body`, `status` y `headers` sin agregarles argumentos pueden usarse +para acceder a sus valores actuales. + +### Streaming De Respuestas + +A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no +terminaste de generar su cuerpo. También es posible que, en algunos casos, +quieras seguir enviando información hasta que el cliente cierre la conexión. +Cuando esto ocurra, el helper `stream` te va a ser de gran ayuda: + +```ruby +get '/' do + stream do |out| + out << "Esto va a ser legen -\n" + sleep 0.5 + out << " (esperalo) \n" + sleep 1 + out << "- dario!\n" + end +end +``` + +Puedes implementar APIs de streaming, +[Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) y puede ser usado +como base para [WebSockets](https://es.wikipedia.org/wiki/WebSockets). También +puede ser usado para incrementar el throughput si solo una parte del contenido +depende de un recurso lento. + +Hay que tener en cuenta que el comportamiento del streaming, especialmente el +número de peticiones concurrentes, depende del servidor web utilizado para +alojar la aplicación. Puede que algunos servidores no soporten streaming +directamente, así el cuerpo de la respuesta será enviado completamente de una +vez cuando el bloque pasado a `stream` finalice su ejecución. Si estás usando +Shotgun, el streaming no va a funcionar. + +Cuando se pasa `keep_open` como parámetro, no se va a enviar el mensaje +`close` al objeto de stream. Permite que tu lo cierres en el punto de ejecución +que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es +posible sólo en servidores que soporten eventos, como Rainbows. El +resto de los servidores van a cerrar el stream de todos modos: + +```ruby + +# config.ru +require 'sinatra/base' + +class App < Sinatra::Base + connections = [] + + get '/subscribe' do + # registrar a un cliente interesado en los eventos del servidor + stream(:keep_open) do |out| + connections << out + # purgar conexiones muertas + connections.reject!(&:closed?) + end + end + + post '/:message' do + connections.each do |out| + # notificar al cliente que ha llegado un nuevo mensaje + out << params['message'] << "\n" + + # indicar al cliente para conectarse de nuevo + out.close + end + + # reconocer + "message received" + end +end + +run App +``` + +```ruby +# rainbows.conf + +Rainbows! do + use :EventMachine +end +``` + +Ejecute: + +```shell +rainbows -c rainbows.conf +``` + +También es posible que el cliente cierre la conexión cuando intenta +escribir en el socket. Debido a esto, se recomienda verificar con +`out.closed?` antes de intentar escribir. + +### Logging + +En el ámbito de la petición, el helper `logger` (registrador) expone +una instancia de `Logger`: + +```ruby +get '/' do + logger.info "cargando datos" + # ... +end +``` + +Este logger tiene en cuenta la configuración de logueo de tu Rack +handler. Si el logueo está desactivado, este método va a devolver un +objeto que se comporta como un logger pero que en realidad no hace +nada. Así, no vas a tener que preocuparte por esta situación. + +Ten en cuenta que el logueo está habilitado por defecto únicamente +para `Sinatra::Application`. Si heredaste de +`Sinatra::Base`, probablemente quieras habilitarlo manualmente: + +```ruby +class MiApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Para evitar que se inicialice cualquier middleware de logging, configurá +`logging` a `nil`. Ten en cuenta que, cuando hagas esto, `logger` va a +devolver `nil`. Un caso común es cuando quieres usar tu propio logger. Sinatra +va a usar lo que encuentre en `env['rack.logger']`. + +### Tipos Mime + +Cuando usás `send_file` o archivos estáticos tal vez tengas tipos mime +que Sinatra no entiende. Usá `mime_type` para registrarlos a través de la +extensión de archivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +También lo puedes usar con el helper `content_type`: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generando URLs + +Para generar URLs deberías usar el método `url`. Por ejemplo, en Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes. + +Este método también puede invocarse mediante su alias `to` (mirá un ejemplo +a continuación). + +### Redirección del Navegador + +Puedes redireccionar al navegador con el método `redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Cualquier parámetro adicional se utiliza de la misma manera que los argumentos +pasados a `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'te confundiste de lugar, compañero' +``` + +También puedes redireccionar fácilmente de vuelta hacia la página desde donde +vino el usuario con `redirect back`: + +```ruby +get '/foo' do + "hacer algo" +end + +get '/bar' do + hacer_algo + redirect back +end +``` + +Para pasar argumentos con una redirección, puedes agregarlos a la cadena de +búsqueda: + +```ruby +redirect to('/bar?suma=42') +``` + +O usar una sesión: + +```ruby +enable :sessions + +get '/foo' do + session[:secreto] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secreto] +end +``` + +### Control del Cache + +Asignar tus encabezados correctamente es el cimiento para realizar un cacheo +HTTP correcto. + +Puedes asignar el encabezado Cache-Control fácilmente: + +```ruby +get '/' do + cache_control :public + "cachealo!" +end +``` + +Pro tip: configurar el cacheo en un filtro `before`: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si estás usando el helper `expires` para definir el encabezado correspondiente, +`Cache-Control` se va a definir automáticamente: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar cachés adecuadamente, deberías considerar usar `etag` o +`last_modified`. Es recomendable que llames a estos asistentes *antes* de hacer +cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si +el cliente ya tiene la versión actual en su caché: + +```ruby +get '/articulo/:id' do + @articulo = Articulo.find params['id'] + last_modified @articulo.updated_at + etag @articulo.sha1 + erb :articulo +end +``` + +También es posible usar una +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @articulo.sha1, :weak +``` + +Estos helpers no van a cachear nada por vos, sino que van a facilitar la +información necesaria para poder hacerlo. Si estás buscando soluciones rápidas +de cacheo con proxys reversos, mirá +[rack-cache](https://github.com/rtomayko/rack-cache): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hola" +end +``` + +Usá la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` a archivos estáticos (ver la sección de configuración +para más detalles). + +De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las +cabeceras If-Match o If-None-Match se le asigna el valor `*` cuando el +recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get) +y potentes (como put) que el recurso existe, mientras que para el resto +(como post) asume que no. Puedes cambiar este comportamiento con la opción +`:new_resource`: + +```ruby +get '/crear' do + etag '', :new_resource => true + Articulo.create + erb :nuevo_articulo +end +``` + +Si quieres seguir usando una weak ETag, indicalo con la opción `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Archivos + +Para enviar archivos, puedes usar el método `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Además acepta un par de opciones: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Estas opciones son: + +
    +
    filename
    +
    Nombre del archivo devuelto, por defecto es el nombre real del archivo.
    + +
    last_modified
    +
    Valor para el encabezado Last-Modified, por defecto toma el mtime del archivo.
    + +
    type
    +
    El Content-Type que se va a utilizar, si no está presente se intenta + adivinar a partir de la extensión del archivo.
    + +
    disposition
    +
    + Se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los + siguientes valores: nil (por defecto), :attachment y :inline +
    + +
    length
    +
    Encabezado Content-Length, por defecto toma el tamaño del archivo
    + +
    status
    +
    + Código de estado a enviar. Útil cuando se envía un archivo estático como un error página. Si es compatible con el controlador de Rack, otros medios que no sean la transmisión del proceso de Ruby será utilizado. Si usas este método de ayuda, Sinatra manejará automáticamente las solicitudes de rango. +
    +
    + +### Accediendo al objeto Request + +El objeto de la petición entrante puede ser accedido desde el nivel de la +petición (filtros, rutas y manejadores de errores) a través del método +`request`: + +```ruby +# app corriendo en http://ejemplo.com/ejemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # cuerpo de la petición enviado por el cliente (ver más abajo) + request.scheme # "http" + request.script_name # "/ejemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # longitud de request.body + request.media_type # tipo de medio de request.body + request.host # "ejemplo.com" + request.get? # true (hay métodos análogos para los otros verbos) + request.form_data? # false + request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA + request.referrer # la referencia del cliente o '/' + request.user_agent # user agent (usado por la condición :agent) + request.cookies # hash de las cookies del navegador + request.xhr? # es una petición ajax? + request.url # "http://ejemplo.com/ejemplo/foo" + request.path # "/ejemplo/foo" + request.ip # dirección IP del cliente + request.secure? # false (sería true sobre ssl) + request.forwarded? # true (si se está corriendo atrás de un proxy reverso) + request.env # hash de entorno directamente entregado por Rack +end +``` + +Algunas opciones, como `script_name` o `path_info` pueden +también ser escritas: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas las peticiones llegan acá" +end +``` + +El objeto `request.body` es una instancia de IO o StringIO: + +```ruby +post "/api" do + request.body.rewind # en caso de que alguien ya lo haya leído + datos = JSON.parse request.body.read + "Hola #{datos['nombre']}!" +end +``` + +### Archivos Adjuntos + +Puedes usar el helper `attachment` para indicarle al navegador que +almacene la respuesta en el disco en lugar de mostrarla en pantalla: + +```ruby +get '/' do + attachment + "guardalo!" +end +``` + +También puedes pasarle un nombre de archivo: + +```ruby +get '/' do + attachment "info.txt" + "guardalo!" +end +``` + +### Fecha y Hora + +Sinatra pone a tu disposición el helper `time_for`, que genera un objeto `Time` +a partir del valor que recibe como argumento. Este valor puede ser un +`String`, pero también es capaz de convertir objetos `DateTime`, `Date` y de +otras clases similares: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "todavía hay tiempo" +end +``` + +Este método es usado internamente por métodos como `expires` y `last_modified`, +entre otros. Por lo tanto, es posible extender el comportamiento de estos +métodos sobreescribiendo `time_for` en tu aplicación: + +```ruby +helpers do + def time_for(value) + case value + when :ayer then Time.now - 24*60*60 + when :mañana then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ayer + expires :mañana + "hola" +end +``` + +### Buscando los Archivos de las Plantillas + +El helper `find_template` se utiliza para encontrar los archivos de las +plantillas que se van a renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |archivo| + puts "podría ser #{archivo}" +end +``` + +Si bien esto no es muy útil, lo interesante es que puedes sobreescribir este +método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para +poder utilizar más de un directorio de vistas: + +```ruby +set :views, ['vistas', 'plantillas'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Otro ejemplo consiste en usar directorios diferentes para los distintos motores +de renderizado: + +```ruby +set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:defecto] + super(folder, name, engine, &block) + end +end +``` + +¡Es muy fácil convertir estos ejemplos en una extensión y compartirla! + +Notá que `find_template` no verifica si un archivo existe realmente, sino +que llama al bloque que recibe para cada path posible. Esto no representa un +problema de rendimiento debido a que `render` va a usar `break` ni bien +encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y +su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno +tener en cuenta lo anterior si escribís un método extraño. + +## Configuración + +Ejecutar una vez, en el inicio, en cualquier entorno: + +```ruby +configure do + # asignando una opción + set :opcion, 'valor' + + # asignando varias opciones + set :a => 1, :b => 2 + + # atajo para `set :opcion, true` + enable :opcion + + # atajo para `set :opcion, false` + disable :opcion + + # también puedes tener configuraciones dinámicas usando bloques + set(:css_dir) { File.join(views, 'css') } +end +``` + +Ejecutar únicamente cuando el entorno (la variable de entorno APP_ENV) es +`:production`: + +```ruby +configure :production do + ... +end +``` + +Ejecutar cuando el entorno es `:production` o `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Puedes acceder a estas opciones utilizando el método `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configurando la Protección Contra Ataques + +Sinatra usa [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +para defender a tu aplicación de los ataques más comunes. Si por algún motivo, +quieres desactivar esta funcionalidad, puedes hacerlo como se indica a +continuación (ten en cuenta que tu aplicación va a quedar expuesta a un +montón de vulnerabilidades bien conocidas): + +```ruby +disable :protection +``` + +También es posible desactivar una única capa de defensa: + +```ruby +set :protection, :except => :path_traversal +``` + +O varias: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +### Configuraciones Disponibles + +
    +
    absolute_redirects
    +
    + Si está deshabilitada, Sinatra va a permitir + redirecciones relativas, sin embargo, como consecuencia + de esto, va a dejar de cumplir con el RFC 2616 (HTTP + 1.1), que solamente permite redirecciones absolutas. +
    +
    + Activalo si tu apliación está corriendo atrás de un proxy + reverso que no se ha configurado adecuadamente. Notá que + el helper url va a seguir produciendo URLs absolutas, a + menos que le pasés false como segundo parámetro. +
    +
    Deshabilitada por defecto.
    + + +
    add_charset
    +
    + Tipos mime a los que el helper content_type les + añade automáticamente el charset. En general, no deberías + asignar directamente esta opción, sino añadirle los charsets + que quieras: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path del archivo principal de la aplicación, se utiliza + para detectar la raíz del proyecto, el directorio de las + vistas y el público, así como las plantillas inline. +
    + +
    bind
    +
    + Dirección IP que utilizará el servidor integrado (por + defecto: 0.0.0.0 o localhost + si su `environment` está configurado para desarrollo). +
    + +
    default_encoding
    +
    + Encoding utilizado cuando el mismo se desconoce (por + defecto "utf-8"). +
    + +
    dump_errors
    +
    + Mostrar errores en el log. +
    + +
    environment
    +
    + Entorno actual, por defecto toma el valor de + ENV['APP_ENV'], o "development" si no + está disponible. +
    + +
    logging
    +
    + Define si se utiliza el logger. +
    + +
    lock
    +
    + Coloca un lock alrededor de cada petición, procesando + solamente una por proceso. +
    +
    + Habilitá esta opción si tu aplicación no es thread-safe. + Se encuentra deshabilitada por defecto. +
    + +
    method_override
    +
    + Utiliza el parámetro _method para permtir + formularios put/delete en navegadores que no los + soportan. +
    + +
    mustermann_opts
    +
    + Un hash predeterminado de opciones para pasar a Mustermann.new + al compilar las rutas. +
    + +
    port
    +
    + Puerto en el que escuchará el servidor integrado. +
    + +
    prefixed_redirects
    +
    + Define si inserta request.script_name en las + redirecciones cuando no se proporciona un path absoluto. + De esta manera, cuando está habilitada, + redirect '/foo' se comporta de la misma manera + que redirect to('/foo'). Se encuentra + deshabilitada por defecto. +
    + +
    protection
    +
    + Define si se habilitan o no las protecciones de ataques web. + Ver la sección de protección encima. +
    + +
    public_folder
    +
    + Lugar del directorio desde donde se sirven los archivos + públicos. Solo se utiliza cuando se sirven archivos + estáticos (ver la opción static). Si no + está presente, se infiere del valor de la opción + app_file. +
    + +
    quiet
    +
    + Inhabilita los logs generados por los comandos de inicio y detención de Sinatra. + false por defecto. +
    + +
    reload_templates
    +
    + Define Si se vuelven a cargar plantillas entre las solicitudes o no. Habilitado en + modo de desarrollo. +
    + +
    root
    +
    + Lugar del directorio raíz del proyecto. Si no está + presente, se infiere del valor de la opción + app_file. +
    + +
    raise_errors
    +
    + Elevar excepciones (detiene la aplicación). Se + encuentra activada por defecto cuando el valor de + environment es "test". En caso + contrario estará desactivada. +
    + +
    run
    +
    + Cuando está habilitada, Sinatra se va a encargar de + iniciar el servidor web, no la habilites cuando estés + usando rackup o algún otro medio. +
    + +
    running
    +
    + Indica si el servidor integrado está ejecutándose, ¡no + cambiés esta configuración!. +
    + +
    server
    +
    + Servidor, o lista de servidores, para usar como servidor + integrado. El orden indica su prioridad, por defecto depende + de la implementación de Ruby. +
    + +
    server_settings
    +
    + Si está utilizando un servidor web WEBrick, presumiblemente para su entorno de desarrollo, puede pasar un hash de opciones a server_settings , como SSLEnable o SSLVerifyClient . Sin embargo, los servidores web como Puma no son compatibles, por lo que puede establecer server_settings definiéndolo como un método cuando llame a configure . +
    + +
    sessions
    +
    + Habilita el soporte de sesiones basadas en cookies a + través de Rack::Session::Cookie. Ver la + sección 'Usando Sesiones' para más información. +
    + +
    session_store
    +
    +Define el middleware de sesión Rack utilizado. Predeterminado a +Rack::Session::Cookie. Consulte la sección 'Uso de sesiones' para obtener más información. +información. +
    + +
    show_exceptions
    +
    + Muestra un stack trace en el navegador cuando ocurre una + excepción. Se encuentra activada por defecto cuando el + valor de environment es "development". + En caso contrario estará desactivada. +
    +
    + También se puede establecer en :after_handler para activar la aplicación especificada + que hara el manejo de errores antes de mostrar un stack trace en el navegador. +
    + +
    static
    +
    +
    Define si Sinatra debe servir los archivos estáticos.
    +
    Deshabilitar cuando se utiliza un servidor capaz de hacer esto por su cuenta.
    +
    La desactivación aumentará el rendimiento.
    +
    + Habilitado por defecto en estilo clásico, deshabilitado para aplicaciones modulares. +
    + + +
    static_cache_control
    +
    + Cuando Sinatra está sirviendo archivos estáticos, y + esta opción está habilitada, les va a agregar encabezados + Cache-Control a las respuestas. Para esto + utiliza el helper cache_control. Se encuentra + deshabilitada por defecto. Notar que es necesario + utilizar un array cuando se asignan múltiples valores: + set :static_cache_control, [:public, :max_age => 300]. +
    + +
    threaded
    +
    +Si se establece en true , le dirá al servidor que use + EventMachine.defer para procesar la solicitud. +
    + +
    traps
    +
    Define si Sinatra debería manejar las señales del sistema.
    + +
    views
    +
    + Path del directorio de las vistas. Si no está presente, + se infiere del valor de la opción app_file. +
    +
    x_cascade
    +
    + Establece o no el encabezado de X-Cascade si no hay una coincidencia de ruta. + verdadero por defecto. +
    +
    + +## Entornos + +Existen tres entornos (`environments`) predefinidos: `development`, +`production` y `test`. El entorno por defecto es +`development` y tiene algunas particularidades: + +* Se recargan las plantillas entre una petición y la siguiente, a diferencia +de `production` y `test`, donde se cachean. +* Se instalan manejadores de errores `not_found` y `error` +especiales que muestran un stack trace en el navegador cuando son disparados. + +Para utilizar alguno de los otros entornos puede asignarse el valor +correspondiente a la variable de entorno `APP_ENV`: +```shell +APP_ENV=production ruby my_app.rb +``` + +Los métodos `development?`, `test?` y `production?` te permiten conocer el +entorno actual. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Manejo de Errores + +Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas +y los filtros `before`, lo que significa que puedes usar, por ejemplo, +`haml`, `erb`, `halt`, etc. + +### Not Found + +Cuando se eleva una excepción `Sinatra::NotFound`, o el código de +estado de la respuesta es 404, el manejador `not_found` es invocado: + +```ruby +not_found do + 'No existo' +end +``` + +### Error + +El manejador `error` es invocado cada vez que una excepción es elevada +desde un bloque de ruta o un filtro. El objeto de la excepción se puede +obtener de la variable Rack `sinatra.error`: + +```ruby +error do + 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].message +end +``` + +Errores personalizados: + +```ruby +error MiErrorPersonalizado do + 'Lo que pasó fue...' + env['sinatra.error'].message +end +``` + +Entonces, si pasa esto: + +```ruby +get '/' do + raise MiErrorPersonalizado, 'algo malo' +end +``` + +Obtienes esto: + +``` + Lo que pasó fue... algo malo +``` + +También, puedes instalar un manejador de errores para un código de estado: + +```ruby +error 403 do + 'Acceso prohibido' +end + +get '/secreto' do + 403 +end +``` + +O un rango: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra instala manejadores `not_found` y `error` especiales +cuando se ejecuta dentro del entorno de desarrollo "development" y se muestran +en tu navegador para que tengas información adicional sobre el error. + +## Rack Middleware + +Sinatra corre sobre [Rack](http://rack.github.io/), una interfaz minimalista +que es un estándar para frameworks webs escritos en Ruby. Una de las +características más interesantes de Rack para los desarrolladores de aplicaciones +es el soporte de "middleware" -- componentes que se ubican entre el servidor y +tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para +proporcionar varios tipos de funcionalidades comunes. + +Sinatra hace muy sencillo construir tuberías de Rack middleware a través del +método top-level `use`: + +```ruby +require 'sinatra' +require 'mi_middleware_personalizado' + +use Rack::Lint +use MiMiddlewarePersonalizado + +get '/hola' do + 'Hola Mundo' +end +``` + +La semántica de `use` es idéntica a la definida para el DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) (más +frecuentemente usado en archivos rackup). Por ejemplo, el método `use` +acepta argumentos múltiples/variables así como bloques: + +```ruby +use Rack::Auth::Basic do |nombre_de_usuario, password| + nombre_de_usuario == 'admin' && password == 'secreto' +end +``` + +Rack es distribuido con una variedad de middleware estándar para logging, +debugging, enrutamiento URL, autenticación y manejo de sesiones. Sinatra +usa muchos de estos componentes automáticamente de acuerdo a su configuración +para que usualmente no tengas que usarlas (con `use`) explícitamente. + +Puedes encontrar middleware útil en +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +o en la [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Pruebas + +Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando +cualquier framework o librería de pruebas basada en Rack. Se recomienda usar +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'mi_app_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MiAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_mi_defecto + get '/' + assert_equal 'Hola Mundo!', last_response.body + end + + def test_con_parametros + get '/saludar', :name => 'Franco' + assert_equal 'Hola Frank!', last_response.body + end + + def test_con_entorno_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Estás usando Songbird!", last_response.body + end +end +``` + +Nota: Si está utilizando Sinatra en el estilo modular, reemplace +`Sinatra::Application` anterior con el nombre de clase de su aplicación. + +## Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares + +Definir tu aplicación en el nivel superior funciona bien para micro-aplicaciones +pero trae inconvenientes considerables a la hora de construir componentes +reutilizables como Rack middleware, Rails metal, librerías simples con un +componente de servidor o incluso extensiones de Sinatra. El DSL de alto nivel +asume una configuración apropiada para micro-aplicaciones (por ejemplo, un +único archivo de aplicación, los directorios `./public` y +`./views`, logging, página con detalles de excepción, etc.). Ahí es +donde `Sinatra::Base` entra en el juego: + +```ruby +require 'sinatra/base' + +class MiApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hola Mundo!' + end +end +``` + +Las subclases de `Sinatra::Base` tienen disponibles exactamente los +mismos métodos que los provistos por el DSL de top-level. La mayoría de las +aplicaciones top-level se pueden convertir en componentes +`Sinatra::Base` con dos modificaciones: + +* Tu archivo debe requerir `sinatra/base` en lugar de `sinatra`; de otra + manera, todos los métodos del DSL de sinatra son importados dentro del + espacio de nombres principal. +* Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación + en una subclase de `Sinatra::Base`. + +`Sinatra::Base` es una pizarra en blanco. La mayoría de las opciones están +desactivadas por defecto, incluyendo el servidor incorporado. Mirá +[Opciones y Configuraciones](http://www.sinatrarb.com/configuration.html) +para detalles sobre las opciones disponibles y su comportamiento. +Si quieres un comportamiento más similar +a cuando defines tu aplicación en el nivel superior (también conocido como Clásico) +estilo), puede subclase `Sinatra::Aplicación` + +```ruby +require 'sinatra/base' + +class MiAplicacion < Sinatra::Application + get '/' do + 'Hola Mundo!' + end +end +``` + +### Estilo Modular vs. Clásico + +Contrariamente a la creencia popular, no hay nada de malo con el estilo clásico. +Si se ajusta a tu aplicación, no es necesario que la cambies a una modular. + +La desventaja de usar el estilo clásico en lugar del modular consiste en que +solamente puedes tener una aplicación Sinatra por proceso Ruby. Si tienes +planificado usar más, cambiá al estilo modular. Al mismo tiempo, ten en +cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos +clásico y modular. + +A continuación se detallan las diferencias (sútiles) entre las configuraciones +de ambos estilos: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConfiguraciónClásicaModularModular
    app_filearchivo que carga sinatraarchivo con la subclase de Sinatra::Basearchivo con la subclase Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Sirviendo una Aplicación Modular + +Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla +activamente con `run!`: + +```ruby +# mi_app.rb +require 'sinatra/base' + +class MiApp < Sinatra::Base + # ... código de la app ... + + # iniciar el servidor si el archivo fue ejecutado directamente + run! if app_file == $0 +end +``` + +Iniciar con: + +```shell +ruby mi_app.rb +``` + +O, con un archivo `config.ru`, que permite usar cualquier handler Rack: + +```ruby +# config.ru +require './mi_app' +run MiApp +``` + +Después ejecutar: + +```shell +rackup -p 4567 +``` + +### Usando una Aplicación Clásica con un Archivo config.ru + +Escribí el archivo de tu aplicación: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Y el `config.ru` correspondiente: + +```ruby +require './app' +run Sinatra::Application +``` + +### ¿Cuándo usar config.ru? + +Indicadores de que probablemente quieres usar `config.ru`: + +* Quieres realizar el deploy con un handler Rack distinto (Passenger, Unicorn, + Heroku, ...). +* Quieres usar más de una subclase de `Sinatra::Base`. +* Quieres usar Sinatra únicamente para middleware, pero no como un endpoint. + +No hay necesidad de utilizar un archivo `config.ru` exclusivamente +porque tienes una aplicación modular, y no necesitás una aplicación modular para +iniciarla con `config.ru`. + +### Utilizando Sinatra como Middleware + +Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, +cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack +como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier +aplicación basada en Rack (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class PantallaDeLogin < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['nombre'] == 'admin' && params['password'] == 'admin' + session['nombre_de_usuario'] = params['nombre'] + else + redirect '/login' + end + end +end + +class MiApp < Sinatra::Base + # el middleware se ejecutará antes que los filtros + use PantallaDeLogin + + before do + unless session['nombre_de_usuario'] + halt "Acceso denegado, por favor iniciá sesión." + end + end + + get('/') { "Hola #{session['nombre_de_usuario']}." } +end +``` + +### Creación Dinámica de Aplicaciones + +Puede que en algunas ocasiones quieras crear nuevas aplicaciones en +tiempo de ejecución sin tener que asignarlas a una constante. Para +esto tienes `Sinatra.new`: + +```ruby +require 'sinatra/base' +mi_app = Sinatra.new { get('/') { "hola" } } +mi_app.run! +``` + +Acepta como argumento opcional una aplicación desde la que se +heredará: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MisHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Construir aplicaciones de esta forma resulta especialmente útil para +testear extensiones Sinatra o para usar Sinatra en tus librerías. + +Por otro lado, hace extremadamente sencillo usar Sinatra como +middleware: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run ProyectoRails::Application +``` + +## Ámbitos y Ligaduras + +El ámbito en el que te encuentras determina que métodos y variables están +disponibles. + +### Ámbito de Aplicación/Clase + +Cada aplicación Sinatra es una subclase de `Sinatra::Base`. Si estás +usando el DSL de top-level (`require 'sinatra'`), entonces esta clase es +`Sinatra::Application`, de otra manera es la subclase que creaste +explícitamente. Al nivel de la clase tienes métodos como `get` o `before`, pero +no puedes acceder a los objetos `request` o `session`, ya que hay una única +clase de la aplicación para todas las peticiones. + +Las opciones creadas utilizando `set` son métodos al nivel de la clase: + +```ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ya no estoy en el ámbito de la aplicación! + end +end +``` + +Tienes la ligadura al ámbito de la aplicación dentro de: + +* El cuerpo de la clase de tu aplicación +* Métodos definidos por extensiones +* El bloque pasado a `helpers` +* Procs/bloques usados como el valor para `set` +* El bloque pasado a `Sinatra.new` + +Este ámbito puede alcanzarse de las siguientes maneras: + +* A través del objeto pasado a los bloques de configuración (`configure { |c| ...}`) +* Llamando a `settings` desde dentro del ámbito de la petición + +### Ámbito de Petición/Instancia + +Para cada petición entrante, una nueva instancia de la clase de tu aplicación +es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este +ámbito puedes acceder a los objetos `request` y `session` o llamar a los métodos +de renderización como `erb` o `haml`. Puedes acceder al ámbito de la aplicación +desde el ámbito de la petición utilizando `settings`: + +```ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + get '/definir_ruta/:nombre' do + # Ámbito de petición para '/definir_ruta/:nombre' + @valor = 42 + + settings.get("/#{params['nombre']}") do + # Ámbito de petición para "/#{params['nombre']}" + @valor # => nil (no es la misma petición) + end + + "Ruta definida!" + end +end +``` + +Tienes la ligadura al ámbito de la petición dentro de: + +* bloques pasados a get, head, post, put, delete, options, patch, link y unlink +* filtros before/after +* métodos helpers +* plantillas/vistas + +### Ámbito de Delegación + +El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier +manera, no se comporta 100% como el ámbito de clase porque no tienes la ligadura +de la clase: únicamente métodos marcados explícitamente para delegación están +disponibles y no compartís variables/estado con el ámbito de clase (léase: +tienes un `self` diferente). Puedes agregar delegaciones de método llamando a +`Sinatra::Delegator.delegate :nombre_del_metodo`. + +Tienes és la ligadura al ámbito de delegación dentro de: + +* La ligadura del top-level, si hiciste `require "sinatra"` +* Un objeto extendido con el mixin `Sinatra::Delegator` + +Hechale un vistazo al código: acá está el +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +que [extiende el objeto main](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Línea de Comandos + +Las aplicaciones Sinatra pueden ser ejecutadas directamente: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Las opciones son: + +``` +-h # ayuda +-p # asigna el puerto (4567 es usado por defecto) +-o # asigna el host (0.0.0.0 es usado por defecto) +-e # asigna el entorno (development es usado por defecto) +-s # especifica el servidor/manejador rack (puma es usado por defecto) +-q # activar el modo silecioso para el servidor (está desactivado por defecto) +-x # activa el mutex lock (está desactivado por defecto) +``` + +### Multi-threading + +_Basado en [esta respuesta en StackOverflow](http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) escrita por Konstantin_ + +Sinatra no impone ningún modelo de concurrencia, sino que lo deja en manos del +handler Rack que se esté usando (Puma o WEBrick). Sinatra en sí mismo es +thread-safe, así que no hay problema en que el Rack handler use un modelo de +concurrencia basado en hilos. + +Esto significa que, cuando estemos arrancando el servidor, tendríamos que +especificar la opción adecuada para el handler Rack específico. En este ejemplo +vemos cómo arrancar un servidor Rainbows multihilo: + +```ruby +# config.ru + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "¡Hola, Mundo!" + end +end + +run App +``` + +```ruby +# rainbows.conf + +# El configurador de Rainbows está basado en Unicorn. + +Rainbows! do + use :ThreadSpawn +end +``` + +Para arrancar el servidor, el comando sería: + +```shell +rainbows -c rainbows.conf +``` + +## Requerimientos + +Las siguientes versiones de Ruby son soportadas oficialmente: + +
    +
    Ruby 2.3
    +
    + 2.3 Es totalmente compatible y recomendado. Actualmente no hay planes + soltar el apoyo oficial para ello. +
    + +
    Rubinius
    +
    + Rubinius es oficialmente compatible (Rubinius> = 2.x). Se recomienda instalar la gema puma + gem install puma. +
    + +
    JRuby
    +
    + La última versión estable de JRuby es oficialmente compatible. No lo es + recomienda usar extensiones C con JRuby. Se recomienda instalar la gema trinidad + gem install trinidad . +
    +
    + +Las versiones de Ruby anteriores a 2.2.2 ya no son compatibles con Sinatra 2.0 . + +Siempre le prestamos atención a las nuevas versiones de Ruby. + +Las siguientes implementaciones de Ruby no se encuentran soportadas +oficialmente. De cualquier manera, pueden ejecutar Sinatra: + +* Versiones anteriores de JRuby y Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev e IronRuby +* Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los uses) + +No ser soportada oficialmente, significa que si las cosas se rompen +ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino +el suyo. + +También ejecutamos nuestro CI contra ruby-head (futuras versiones de MRI), pero +no puede garantizar nada, ya que se mueve constantemente. Esperar próxima +2.x versiones para ser totalmente compatibles. + +Sinatra debería trabajar en cualquier sistema operativo compatible la implementación de Ruby +elegida + +Si ejecuta MacRuby, debe `gem install control_tower`. + +Sinatra actualmente no funciona con Cardinal, SmallRuby, BlueRuby o cualquier +versión de Ruby anterior a 2.2. + +## A la Vanguardia + +Si quieres usar el código de Sinatra más reciente, sientete libre de ejecutar +tu aplicación sobre la rama master, en general es bastante estable. + +También liberamos prereleases de vez en cuando, así, puedes hacer: + +```shell +gem install sinatra --pre +``` + +Para obtener algunas de las últimas características. + +### Usando Bundler + +Esta es la manera recomendada para ejecutar tu aplicación sobre la última +versión de Sinatra usando [Bundler](http://bundler.io). + +Primero, instala Bundler si no lo hiciste todavía: + +```shell +gem install bundler +``` + +Después, en el directorio de tu proyecto, creá un archivo `Gemfile`: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# otras dependencias +gem 'haml' # por ejemplo, si usás haml +``` + +Ten en cuenta que tienes que listar todas las dependencias directas de tu +aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) +porque Bundler las agrega directamente. + +Ahora puedes arrancar tu aplicación así: + +```shell +bundle exec ruby miapp.rb +``` + +## Versionado + +Sinatra utiliza el [Versionado Semántico](http://semver.org/), +siguiendo las especificaciones SemVer y SemVerTag. + +## Lecturas Recomendadas + +* [Sito web del proyecto](http://www.sinatrarb.com/) - Documentación + adicional, noticias, y enlaces a otros recursos. +* [Contribuyendo](http://www.sinatrarb.com/contributing) - ¿Encontraste un + error?. ¿Necesitas ayuda?. ¿Tienes un parche?. +* [Seguimiento de problemas](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Lista de Correo](http://groups.google.com/group/sinatrarb/topics) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) en http://freenode.net +* [Sinatra & Friends](https://sinatrarb.slack.com) en Slack y revisa + [acá](https://sinatra-slack.herokuapp.com/) Para una invitación. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutorial (en inglés). +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Recetas contribuidas + por la comunidad (en inglés). +* Documentación de la API para la + [última versión liberada](http://www.rubydoc.info/gems/sinatra) o para la + [rama de desarrollo actual](http://www.rubydoc.info/github/sinatra/sinatra) + en http://www.rubydoc.info/ +* [Servidor de CI](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.fr.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.fr.md new file mode 100644 index 0000000000..68c0e0aa35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.fr.md @@ -0,0 +1,3111 @@ +# Sinatra +*Attention : Ce document correspond à la traduction de la version anglaise et +il n'est peut-être plus à jour.* + +Sinatra est un [DSL](https://fr.wikipedia.org/wiki/Langage_dédié) pour +créer rapidement et facilement des applications web en Ruby : + +```ruby +# mon_application.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Installez la gem Sinatra : + +```shell +gem install sinatra +``` + +Puis lancez votre programme : + +```shell +ruby mon_application.rb +``` + +Le résultat est visible sur : [http://localhost:4567](http://localhost:4567) + +Le code que vous avez modifié ne sera pas pris en compte tant que vous ne +redémarrerez pas le serveur. Pensez à redémarrer le serveur à chaque +modification ou utilisez +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Il est recommandé d'exécuter également `gem install thin`, pour que +Sinatra utilise le server Thin quand il est disponible. + +## Table des matières + +* [Sinatra](#sinatra) + * [Table des matières](#table-des-matières) + * [Routes](#routes) + * [Conditions](#conditions) + * [Valeurs de retour](#valeurs-de-retour) + * [Masques de route spécifiques](#masques-de-route-spécifiques) + * [Fichiers statiques](#fichiers-statiques) + * [Vues / Templates](#vues--templates) + * [Templates littéraux](#templates-littéraux) + * [Langages de template disponibles](#langages-de-template-disponibles) + * [Templates Haml](#templates-haml) + * [Templates Erb](#templates-erb) + * [Templates Builder](#templates-builder) + * [Templates Nokogiri](#templates-nokogiri) + * [Templates Sass](#templates-sass) + * [Templates SCSS](#templates-scss) + * [Templates Less](#templates-less) + * [Templates Liquid](#templates-liquid) + * [Templates Markdown](#templates-markdown) + * [Templates Textile](#templates-textile) + * [Templates RDoc](#templates-rdoc) + * [Templates Asciidoc](#templates-asciidoc) + * [Templates Radius](#templates-radius) + * [Templates Markaby](#templates-markaby) + * [Templates RABL](#templates-rabl) + * [Templates Slim](#templates-slim) + * [Templates Creole](#templates-creole) + * [Templates MediaWiki](#templates-mediawiki) + * [Templates CoffeeScript](#templates-coffeescript) + * [Templates Stylus](#templates-stylus) + * [Templates Yajl](#templates-yajl) + * [Templates WLang](#templates-wlang) + * [Accéder aux variables dans un Template](#accéder-aux-variables-dans-un-template) + * [Templates avec `yield` et layouts imbriqués](#templates-avec-yield-et-layouts-imbriqués) + * [Templates dans le fichier source](#templates-dans-le-fichier-source) + * [Templates nommés](#templates-nommés) + * [Associer des extensions de fichier](#associer-des-extensions-de-fichier) + * [Ajouter son propre moteur de rendu](#ajouter-son-propre-moteur-de-rendu) + * [Utiliser des règles personnalisées pour la recherche de templates](#utiliser-des-règles-personnalisées-pour-la-recherche-de-templates) + * [Filtres](#filtres) + * [Helpers](#helpers) + * [Utiliser les sessions](#utiliser-les-sessions) + * [Halt](#halt) + * [Passer](#passer) + * [Déclencher une autre route](#déclencher-une-autre-route) + * [Définir le corps, le code retour et les en-têtes](#définir-le-corps-le-code-retour-et-les-en-têtes) + * [Faire du streaming](#faire-du-streaming) + * [Journalisation (Logging)](#journalisation-logging) + * [Types Mime](#types-mime) + * [Former des URLs](#former-des-urls) + * [Redirection du navigateur](#redirection-du-navigateur) + * [Contrôle du cache](#contrôle-du-cache) + * [Envoyer des fichiers](#envoyer-des-fichiers) + * [Accéder à l'objet requête](#accéder-à-lobjet-requête) + * [Fichiers joints](#fichiers-joints) + * [Gérer Date et Time](#gérer-date-et-time) + * [Chercher les fichiers de templates](#chercher-les-fichiers-de-templates) + * [Configuration](#configuration) + * [Se protéger des attaques](#se-protéger-des-attaques) + * [Paramètres disponibles](#paramètres-disponibles) + * [Environnements](#environnements) + * [Gérer les erreurs](#gérer-les-erreurs) + * [NotFound](#notfound) + * [Error](#error) + * [Les Middlewares Rack](#les-middlewares-rack) + * [Tester](#tester) + * [Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires](#sinatrabase---les-middlewares-bibliothèques-et-applications-modulaires) + * [Style modulaire vs. style classique](#style-modulaire-vs-style-classique) + * [Servir une application modulaire](#servir-une-application-modulaire) + * [Utiliser une application de style classique avec un fichier config.ru](#utiliser-une-application-de-style-classique-avec-un-fichier-configru) + * [Quand utiliser un fichier config.ru ?](#quand-utiliser-un-fichier-configru-) + * [Utiliser Sinatra comme Middleware](#utiliser-sinatra-comme-middleware) + * [Création dynamique d'applications](#création-dynamique-dapplications) + * [Contextes et Binding](#contextes-et-binding) + * [Contexte de l'application/classe](#contexte-de-lapplicationclasse) + * [Contexte de la requête/instance](#contexte-de-la-requêteinstance) + * [Le contexte de délégation](#le-contexte-de-délégation) + * [Ligne de commande](#ligne-de-commande) + * [Multi-threading](#multi-threading) + * [Configuration nécessaire](#configuration-nécessaire) + * [Essuyer les plâtres](#essuyer-les-plâtres) + * [Installer avec Bundler](#installer-avec-bundler) + * [Faire un clone local](#faire-un-clone-local) + * [Installer globalement](#installer-globalement) + * [Versions](#versions) + * [Mais encore](#mais-encore) + +## Routes + +Dans Sinatra, une route est une méthode HTTP couplée à un masque (pattern) +URL. Chaque route est associée à un bloc : + +```ruby +get '/' do + .. montrer quelque chose .. +end + +post '/' do + .. créer quelque chose .. +end + +put '/' do + .. remplacer quelque chose .. +end + +patch '/' do + .. changer quelque chose .. +end + +delete '/' do + .. effacer quelque chose .. +end + +options '/' do + .. paramétrer quelque chose .. +end + +link '/' do + .. relier quelque chose .. +end + +unlink '/' do + .. séparer quelque chose .. +end +``` + +Les routes sont évaluées dans l'ordre où elles ont été définies. La première +route qui correspond à la requête est appelée. + +Les routes se terminant par un slash sont différentes de celles qui n'en +comportent pas : + +```ruby +get '/foo' do + # Ne correspond pas à "GET /foo/" +end +``` + +Les masques peuvent inclure des paramètres nommés, accessibles par +l'intermédiaire du hash `params` : + +```ruby +get '/bonjour/:nom' do + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + "Bonjour #{params['nom']} !" +end +``` + +Vous pouvez aussi accéder aux paramètres nommés directement grâce aux +paramètres du bloc comme ceci : + +```ruby +get '/bonjour/:nom' do |n| + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + # n contient params['nom'] + "Bonjour #{n} !" +end +``` + +Une route peut contenir un `splat` (caractère joker), accessible par +l'intermédiaire du tableau `params['splat']` : + +```ruby +get '/dire/*/a/*' do + # répond à /dire/bonjour/a/monde + params['splat'] # => ["bonjour", "monde"] +end + +get '/telecharger/*.*' do + # répond à /telecharger/chemin/vers/fichier.xml + params['splat'] # => ["chemin/vers/fichier", "xml"] +end +``` + +Ou par l'intermédiaire des paramètres du bloc : + +```ruby +get '/telecharger/*.*' do |chemin, ext| + [chemin, ext] # => ["path/to/file", "xml"] +end +``` + +Une route peut aussi être définie par une expression régulière : + +```ruby +get /\/bonjour\/([\w]+)/ do + "Bonjour, #{params['captures'].first} !" +end +``` + +Là encore on peut utiliser les paramètres de bloc : + +```ruby +get %r{/bonjour/([\w]+)} do |c| + # répond à "GET /meta/bonjour/monde", "GET /bonjour/monde/1234" etc. + "Bonjour, #{c} !" +end +``` + +Les routes peuvent aussi comporter des paramètres optionnels : + +```ruby +get '/articles/:format?' do + # répond à "GET /articles/" ou avec une extension "GET /articles/json", "GET /articles/xml" etc... +end +``` + +Ainsi que des paramètres d'URL : + +```ruby +get '/articles' do + # répond à "GET /articles?titre=foo&auteur=bar" + titre = params['titre'] + auteur = params['auteur'] + # utilise les variables titre et auteur qui sont des paramètres d'URL optionnels pour la route /articles +end +``` + +À ce propos, à moins d'avoir désactivé la protection contre les attaques par +"path transversal" (voir plus loin), l'URL demandée peut avoir été modifiée +avant d'être comparée à vos routes. + +Vous pouvez personnaliser les options [Mustermann](https://github.com/sinatra/mustermann#readme) +utilisées pour une route donnée en fournissant un hash `:mustermann_opts` : + +```ruby +get '\A/articles\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # répond exactement à /articles, avec un ancrage explicite + "Si tu réponds à un pattern ancré tape dans tes mains ! +end +``` + +Cela ressemble à une [condition](#conditions), mais ce n'en est pas une ! +Ces options seront mergées dans le hash global `:mustermann_opts` décrit +[plus bas](#paramètres-disponibles). + +## Conditions + +Les routes peuvent définir toutes sortes de conditions, comme par exemple le +"user agent" : + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Vous utilisez Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Correspond à tous les autres navigateurs +end +``` + +Les autres conditions disponibles sont `host_name` et `provides` : + +```ruby +get '/', :host_name => /^admin\./ do + "Zone Administrateur, Accès refusé !" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` se base sur l'en-tête `Accept` de la requête. + +Vous pouvez facilement définir vos propres conditions : + +```ruby +set(:chance) { |valeur| condition { rand <= valeur } } + +get '/gagner_une_voiture', :chance => 0.1 do + "Vous avez gagné !" +end + +get '/gagner_une_voiture' do + "Désolé, vous avez perdu." +end +``` + +Utilisez un `splat` (caractère joker) dans le cas d'une condition qui prend +plusieurs valeurs : + +```ruby +set(:auth) do |*roles| # <- ici on utilise un splat + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mon/compte/", :auth => [:user, :admin] do + "Informations sur votre compte" +end + +get "/reserve/aux/admins/", :auth => :admin do + "Seuls les administrateurs sont acceptés ici !" +end +``` + +## Valeurs de retour + +La valeur renvoyée par le bloc correspondant à une route constitue le corps de +la réponse qui sera transmise au client HTTP ou du moins au prochain `middleware` +dans la pile Rack. Le plus souvent, il s'agit d'une chaîne de caractères, +comme dans les exemples précédents. Cependant, d'autres valeurs sont +acceptées. + +Vous pouvez renvoyer n'importe quel objet qu'il s'agisse d'une réponse Rack +valide, d'un corps de réponse Rack ou d'un code statut HTTP : + +* Un tableau de 3 éléments : `[code statut (Integer), en-têtes (Hash), corps + de la réponse (répondant à #each)]` +* Un tableau de 2 élements : `[code statut (Integer), corps de la réponse + (répondant à #each)]` +* Un objet qui répond à `#each` et qui ne transmet que des chaînes de + caractères au bloc fourni +* Un Integer représentant le code statut + +Ainsi, on peut facilement implémenter un exemple de streaming : + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Vous pouvez aussi utiliser le helper `stream` (présenté un peu plus loin) pour +éviter les répétitions et intégrer le traitement relatif au streaming dans le bloc +de code de la route. + +## Masques de route spécifiques + +Comme cela a été vu auparavant, Sinatra offre la possibilité d'utiliser des +masques sous forme de chaines de caractères ou des expressions régulières +pour définir les routes. Mais il est possible de faire bien plus. Vous pouvez +facilement définir vos propres masques : + +```ruby +class MasqueToutSauf + Masque = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Masque.new([]) + end + + def match(str) + @caputres unless @except === str + end +end + +def tout_sauf(masque) + MasqueToutSauf.new(masque) +end + +get tout_sauf("/index") do + # ... +end +``` + +Notez que l'exemple ci-dessus est plus compliqué qu'il ne devrait et peut être implémenté de la façon suivante : + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou bien en utilisant cette expression regulière : + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Fichiers statiques + +Les fichiers du dossier `./public` sont servis de façon statique. Vous pouvez spécifier un autre dossier avec le paramètre `:public_folder` : + +```ruby +set :public_folder, __dir__ + '/statique' +``` + +Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier +`./public/css/style.css` sera accessible à l'URL : +`http://exemple.com/css/style.css`. + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête `Cache-Control` (voir plus bas). + +## Vues / Templates + +Chaque langage de template est disponible via sa propre méthode de rendu, +lesquelles renvoient tout simplement une chaîne de caractères. + +```ruby +get '/' do + erb :index +end +``` + +Ceci génère la vue `views/index.erb`. + +Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer +le contenu du template : + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Les méthodes de templates acceptent un hash d'options comme second argument : + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Ceci génèrera la vue `views/index.erb` en l'intégrant au *layout* `views/post.erb` (`views/layout.erb` est la valeur par défaut si ce fichier existe). + +Toute option que Sinatra ne comprend pas sera passée au moteur de rendu : + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Vous pouvez également définir les options de chaque langage de template de façon +générale : + +```ruby +set :haml, :format => html5 + +get '/' do + haml :index +end +``` + +Les arguments passés à la méthode de rendu prennent le pas sur les options définies au moyen de `set`. + +Options disponibles : + +
    +
    locals
    +
    + Liste de variables locales passées au document. Pratique pour les vues + partielles. + Exemple : erb "<%= foo %>", :locals => {:foo => "bar"}. +
    + +
    default_encoding
    +
    + Encodage de caractères à utiliser en cas d'incertitude. Par défaut + settings.default_encoding. +
    + +
    views
    +
    + Dossier de vues dans lequel chercher les templates. Par défaut + settings.views. +
    + +
    layout
    +
    + S'il faut ou non utiliser un layout (true ou false). + Ou indique le template à utiliser lorsque c'est un symbole. Exemple : + erb :index, :layout => !request.xhr?. +
    + +
    content_type
    +
    + Content-Type que le template génère. La valeur par défaut dépend du langage de template. +
    + +
    scope
    +
    + Contexte dans lequel effectuer le rendu du template. Par défaut il s'agit + de l'instance de l'application. Si vous changez cela, les variables + d'instance et les méthodes utilitaires ne seront pas disponibles. +
    + +
    layout_engine
    +
    + Moteur de rendu à utiliser pour le layout. Utile pour les langages ne + supportant pas les layouts. Il s'agit par défaut du moteur utilisé pour + le rendu du template. Exemple : set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Options spécifiques à la génération du layout. Exemple : set :rdoc, + :layout_options => { :views => 'views/layouts' } +
    +
    + +Les templates sont supposés se trouver directement dans le dossier +`./views`. Pour utiliser un dossier de vues différent : + +```ruby +set :views, settings.root + '/templates' +``` + +Il est important de se souvenir que les templates sont toujours référencés +sous forme de symboles, même lorsqu'ils sont dans un sous-répertoire (dans +ce cas, utilisez `:'sous_repertoire/template'`). Il faut utiliser +un symbole car les méthodes de rendu évaluent le contenu des chaînes de +caractères au lieu de les considérer comme un chemin vers un fichier. + +### Templates littéraux + +```ruby +get '/' do + haml '%div.title Bonjour le monde' +end +``` + +Utilisera la chaine de caractères comme template pour générer la réponse. +Vous pouvez spécifier un `:path` et `:line` optionnels pour une trace plus +claire s'il existe un chemin dans le système de fichiers ou une ligne +associés à cette chaîne de caractères : + +```ruby +get '/' do + haml '%div.title Bonjour le monde', :path => 'exemples/fichier.haml', :line => 3 +end +``` + +### Langages de template disponibles + +Certains langages ont plusieurs implémentations. Pour préciser l'implémentation +à utiliser (et garantir l'aspect thread-safe), vous devez simplement l'avoir +chargée au préalable : + +```ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Templates Haml + + + + + + + + + + + + + + +
    Dépendanceshaml
    Extensions de fichier.haml
    Exemplehaml :index, :format => :html5
    + +#### Templates Erb + + + + + + + + + + + + + + +
    Dépendances + erubis + ou erb (inclus avec Ruby) +
    Extensions de fichier.erb, .rhtml ou .erubis (Erubis seulement)
    Exempleerb :index
    + +#### Templates Builder + + + + + + + + + + + + + + +
    Dépendances + builder +
    Extensions de fichier.builder
    Exemplebuilder { |xml| xml.em "salut" }
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Nokogiri + + + + + + + + + + + + + + +
    Dépendancesnokogiri
    Extensions de fichier.nokogiri
    Exemplenokogiri { |xml| xml.em "salut" } +
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Sass + + + + + + + + + + + + + + +
    Dépendancessass
    Extensions de fichier.sass
    Exemplesass :stylesheet, :style => :expanded
    + +#### Templates SCSS + + + + + + + + + + + + + + +
    Dépendancessass
    Extensions de fichier.scss
    Exemplescss :stylesheet, :style => :expanded

    +
    + +#### Templates Less + + + + + + + + + + + + + + +
    Dépendancesless
    Extensions de fichier.less
    Exempleless :stylesheet +
    + +#### Templates Liquid + + + + + + + + + + + + + + +
    Dépendancesliquid
    Extensions de fichier.liquid
    Exempleliquid :index, :locals => { :key => 'value' }
    + +Comme vous ne pouvez appeler de méthodes Ruby (autres que `yield`) +dans un template Liquid, vous aurez sûrement à lui passer des variables +locales. + +#### Templates Markdown + + + + + + + + + + + + + + + +

    Dépendances

    + Au choix : + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    Extensions de fichier.markdown, .mkd et .md
    Exemplemarkdown :index, :layout_engine => :erb
    + +Il n’est pas possible d’appeler des méthodes depuis markdown, ni de +lui passer de variables locales. Par conséquent, il sera souvent utilisé +en combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `markdown` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= markdown(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthode Ruby depuis Markdown, vous ne +pouvez pas utiliser de layouts écrits en Markdown. Toutefois, il +est possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Textile + + + + + + + + + + + + + + +
    DépendancesRedCloth
    Extensions de fichier.textile
    Exempletextile :index, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes depuis textile, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => textile(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `textile` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= textile(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthode Ruby depuis Textile, vous ne pouvez +pas utiliser de layouts écrits en Textile. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates RDoc + + + + + + + + + + + + + + +
    DépendancesRDoc
    Extensions de fichier.rdoc
    Exemplerdoc :README, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes Ruby depuis rdoc, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => rdoc(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `rdoc` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= rdoc(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis RDoc, vous ne pouvez +pas utiliser de layouts écrits en RDoc. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Asciidoc + + + + + + + + + + + + + + +
    DépendancesAsciidoctor
    Extensions de fichier.asciidoc, .adoc and .ad
    Exempleasciidoc :README, :layout_engine => :erb
    + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template +AsciiDoc, vous aurez sûrement à lui passer des variables locales. + +#### Templates Radius + + + + + + + + + + + + + + +
    DépendancesRadius
    Extensions de fichier.radius
    Exempleradius :index, :locals => { :key => 'value' }
    + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template +Radius, vous aurez sûrement à lui passer des variables locales. + +#### Templates Markaby + + + + + + + + + + + + + + +
    DépendancesMarkaby
    Extensions de fichier.mab
    Exemplemarkaby { h1 "Bienvenue !" }
    + +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates RABL + + + + + + + + + + + + + + +
    DépendancesRabl
    Extensions de fichier.rabl
    Exemplerabl :index
    + +#### Templates Slim + + + + + + + + + + + + + + +
    DépendancesSlim Lang
    Extensions de fichier.slim
    Exempleslim :index
    + +#### Templates Creole + + + + + + + + + + + + + + +
    DépendancesCreole
    Extensions de fichier.creole
    Exemplecreole :wiki, :layout_engine => :erb
    + +Il n'est pas possible d'appeler de méthodes Ruby depuis creole, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :accueil, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `creole` depuis un autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= creole(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis Creole, vous ne pouvez +pas utiliser de layouts écrits en Creole. Toutefois, il est possible +d'utiliser un moteur de rendu différent pour le template et pour le layout +en utilisant l'option `:layout_engine`. + +#### Templates MediaWiki + + + + + + + + + + + + + + +
    DépendancesWikiCloth
    Extensions de fichier.mediawiki and .mw
    Exemplemediawiki :wiki, :layout_engine => :erb
    + +Il n’est pas possible d’appeler de méthodes Ruby depuis Mediawiki, ni de lui +passer de variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `mediawiki` depuis un +autre template : + +```ruby +%h1 Bonjour depuis Haml ! +%p= mediawiki(:bienvenue) +``` + +Comme vous ne pouvez pas appeler de méthodes Ruby depuis MediaWiki, vous ne pouvez +pas utiliser de layouts écrits en MediaWiki. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates CoffeeScript + + + + + + + + + + + + + + +
    Dépendances + + CoffeeScript + + et un + + moyen d'exécuter javascript + +
    Extensions de fichier.coffee
    Exemplecoffee :index
    + +#### Templates Stylus + + + + + + + + + + + + + + +
    Dépendances + + Stylus + + et un + + moyen d'exécuter javascript + +
    Extensions de fichier.styl
    Exemplestylus :index
    + +Avant de pouvoir utiliser des templates Stylus, vous devez auparavant charger +`stylus` et `stylus/tilt` : + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemple +end +``` + +#### Templates Yajl + + + + + + + + + + + + + + +
    Dépendances + yajl-ruby +
    Extensions de fichier.yajl
    Exempleyajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'ressource'

    +
    + +La source du template est évaluée en tant que chaine Ruby, puis la +variable json obtenue est convertie avec #to_json. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Les options `:callback` et `:variable` peuvent être utilisées pour décorer +l’objet retourné. + +```ruby +var ressource = {"foo":"bar","baz":"qux"}; present(ressource); +``` + +#### Templates WLang + + + + + + + + + + + + + + +
    Dépendanceswlang
    Extensions de fichier.wlang
    Exemplewlang :index, :locals => { :key => 'value' }
    + +L’appel de code ruby au sein des templates n’est pas idiomatique en wlang. +L’écriture de templates sans logique est encouragée, via le passage de variables +locales. Il est néanmoins possible d’écrire un layout en wlang et d’y utiliser +`yield`. + +### Accéder aux variables dans un Template + +Un template est évalué dans le même contexte que l'endroit d'où il a été +appelé (gestionnaire de route). Les variables d'instance déclarées dans le +gestionnaire de route sont directement accessibles dans le template : + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nom' +end +``` + +Alternativement, on peut passer un hash contenant des variables locales : + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nom', :locals => { :foo => foo } +end +``` + +Ceci est généralement nécessaire lorsque l'on veut utiliser un template depuis un autre template (partiel) et qu'il faut donc adapter le nom des variables. + +### Templates avec `yield` et layouts imbriqués + +En général, un layout est un simple template qui appelle `yield`. Ce genre de +template peut s'utiliser via l'option `:template` comme décrit précédemment ou +peut être rendu depuis un bloc : + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Ce code est plus ou moins équivalent à `erb :index, :layout => :post`. + +Le fait de passer des blocs aux méthodes de rendu est particulièrement utile +pour gérer des templates imbriqués : + +```ruby +erb :layout_principal, :layout => false do + erb :layout_admin do + erb :utilisateur + end +end +``` + +Ou plus brièvement : + +```ruby +erb :layout_admin, :layout => :layout_principal do + erb :utilisateur +end +``` + +Actuellement, les méthodes de rendu qui acceptent un bloc sont : `erb`, `haml`, +`liquid`, `slim ` et `wlang`. La méthode générale `render` accepte elle aussi +un bloc. + + +### Templates dans le fichier source + +Des templates peuvent être définis dans le fichier source comme ceci : + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Bonjour le monde ! +``` + +NOTE : Les templates du fichier source qui contient `require 'sinatra'` +sont automatiquement chargés. Si vous avez des templates dans d'autres +fichiers source, il faut explicitement les déclarer avec +`enable :inline_templates`. + + +### Templates nommés + +Les templates peuvent aussi être définis grâce à la méthode de haut niveau `template` : + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Bonjour le monde !' +end + +get '/' do + haml :index +end +``` + +Si un template nommé "layout" existe, il sera utilisé à chaque fois qu'un +template sera affiché. Vous pouvez désactivez les layouts au cas par cas en +passant `:layout => false` ou bien les désactiver par défaut au moyen +de `set :haml, :layout => false` : + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associer des extensions de fichier + +Pour associer une extension de fichier avec un moteur de rendu, utilisez +`Tilt.register`. Par exemple, si vous désirez utiliser l'extension +de fichier `tt` pour les templates Textile, vous pouvez faire comme suit : + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Ajouter son propre moteur de rendu + +En premier lieu, déclarez votre moteur de rendu avec Tilt, ensuite créez +votre méthode de rendu : + +```ruby +Tilt.register :monmoteur, MonMerveilleuxMoteurDeRendu + +helpers do + def monmoteur(*args) render(:monmoteur, *args) end +end + +get '/' do + monmoteur :index +end +``` + +Utilisera `./views/index.monmoteur`. Voir [le projet Github](https://github.com/rtomayko/tilt) pour en savoir plus sur Tilt. + +### Utiliser des règles personnalisées pour la recherche de templates + +Pour implémenter votre propre mécanisme de recherche de templates, vous +pouvez écrire votre propre méthode `#find_template` : + +```ruby +configure do + set :views, [ './vues/a', './vues/b' ] +end + +def find_template(vues, nom, moteur, &bloc) + Array(vues).each do |v| + super(v, nom, moteur, &bloc) + end +end +``` + +## Filtres + +Les filtres `before` sont exécutés avant chaque requête, dans le même contexte +que les routes, et permettent de modifier la requête et sa réponse. Les +variables d'instance déclarées dans les filtres sont accessibles au niveau +des routes et des templates : + +```ruby +before do + @note = 'Coucou !' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Coucou !' + params['splat'] #=> 'bar/baz' +end +``` + +Les filtres `after` sont exécutés après chaque requête à l'intérieur du même +contexte et permettent de modifier la requête et sa réponse. Les variables +d'instance déclarées dans les filtres `before` ou les routes sont accessibles +au niveau des filtres `after` : + +```ruby +after do + puts response.status +end +``` + +Note : Le corps de la réponse n'est pas disponible au niveau du filtre `after` +car il ne sera généré que plus tard (sauf dans le cas où vous utilisez la +méthode `body` au lieu de simplement renvoyer une chaine depuis vos routes). + +Les filtres peuvent être associés à un masque, ce qui permet de limiter leur +exécution aux cas où la requête correspond à ce masque : + +```ruby +before '/secret/*' do + authentification! +end + +after '/faire/:travail' do |travail| + session['dernier_travail'] = travail +end +``` + +Tout comme les routes, les filtres acceptent également des conditions : + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Utilisez la méthode de haut niveau `helpers` pour définir des méthodes +qui seront accessibles dans vos gestionnaires de route et dans vos templates : + +```ruby +helpers do + def bar(nom) + "#{nom}bar" + end +end + +get '/:nom' do + bar(params['nom']) +end +``` + +Vous pouvez aussi définir les méthodes helper dans un module séparé : + +```ruby +module FooUtils + def foo(nom) "#{nom}foo" end +end + +module BarUtils + def bar(nom) "#{nom}bar" end +end + +helpers FooUtils, BarUtils +``` + +Cela a le même résultat que d'inclure les modules dans la classe de +l'application. + +### Utiliser les sessions + +Les sessions sont utilisées pour conserver un état entre les requêtes. Une fois +activées, vous avez un hash de session par session utilisateur : + +```ruby +enable :sessions + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:valeur' do + session['valeur'] = params['valeur'] +end +``` + +Notez que `enable :sessions` enregistre en fait toutes les données dans +un cookie. Ce n'est pas toujours ce que vous voulez (enregistrer beaucoup de +données va augmenter le traffic par exemple). Vous pouvez utiliser n'importe +quel middleware Rack de session afin d'éviter cela. N'utilisez **pas** +`enable :sessions` dans ce cas mais chargez le middleware de votre +choix comme vous le feriez pour n'importe quel autre middleware : + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:valeur' do + session['valeur'] = params['valeur'] +end +``` + +Pour renforcer la sécurité, les données de session dans le cookie sont signées +avec une clé secrète de session. Une clé secrète est générée pour vous au +hasard par Sinatra. Toutefois, comme cette clé change à chaque démarrage de +votre application, vous pouvez définir cette clé vous-même afin que toutes +les instances de votre application la partage : + +```ruby +set :session_secret, 'super secret' +``` + +Si vous souhaitez avoir plus de contrôle, vous pouvez également enregistrer un +hash avec des options lors de la configuration de `sessions` : + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Pour que les différents sous-domaines de foo.com puissent partager une session, +vous devez précéder le domaine d'un *.* (point) : + +```ruby +set :sessions, :domain => '.foo.com' +``` + + +### Halt + +Pour arrêter immédiatement la requête dans un filtre ou un gestionnaire de +route : + +```ruby +halt +``` + +Vous pouvez aussi passer le code retour ... + +```ruby +halt 410 +``` + +Ou le texte ... + +```ruby +halt 'Ceci est le texte' +``` + +Ou les deux ... + +```ruby +halt 401, 'Partez !' +``` + +Ainsi que les en-têtes ... + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +Bien sûr il est possible de combiner un template avec `halt` : + +```ruby +halt erb(:erreur) +``` + +### Passer + +Une route peut passer le relais aux autres routes qui correspondent également +avec `pass` : + +```ruby +get '/devine/:qui' do + pass unless params['qui'] == 'Frank' + "Tu m'as eu !" +end + +get '/devine/*' do + 'Manqué !' +end +``` + +On sort donc immédiatement de ce gestionnaire et on continue à chercher, +dans les masques suivants, le prochain qui correspond à la requête. +Si aucun des masques suivants ne correspond, un code 404 est retourné. + +### Déclencher une autre route + +Parfois, `pass` n'est pas ce que vous recherchez, au lieu de cela vous +souhaitez obtenir le résultat d'une autre route. Pour cela, utilisez +simplement `call` : + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Notez que dans l'exemple ci-dessus, vous faciliterez les tests et améliorerez +la performance en déplaçant simplement `"bar"` dans un helper +utilisé à la fois par `/foo` et `/bar`. + +Si vous souhaitez que la requête soit envoyée à la même instance de +l'application plutôt qu'à une copie, utilisez `call!` au lieu de +`call`. + +Lisez la spécification Rack si vous souhaitez en savoir plus sur +`call`. + +### Définir le corps, le code retour et les en-têtes + +Il est possible et recommandé de définir le code retour et le corps de la +réponse au moyen de la valeur de retour d'un bloc définissant une route. +Quoiqu'il en soit, dans certains cas vous pourriez avoir besoin de définir +le coprs de la réponse à un moment arbitraire de l'exécution. Vous pouvez le +faire au moyen de la méthode `body`. Si vous faites ainsi, vous pouvez alors +utiliser cette même méthode pour accéder au corps de la réponse : + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Il est également possible de passer un bloc à `body`, qui sera exécuté par le +gestionnaire Rack (ceci peut être utilisé pour implémenter un streaming, +voir "Valeurs de retour"). + +Pareillement au corps de la réponse, vous pouvez également définir le code +retour et les en-têtes : + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "Je suis une théière !" +end +``` + +Comme pour `body`, `headers` et `status` peuvent être utilisés sans arguments +pour accéder à leurs valeurs. + +### Faire du streaming + +Il y a des cas où vous voulez commencer à renvoyer des données pendant que +vous êtes en train de générer le reste de la réponse. Dans les cas les plus +extrèmes, vous souhaitez continuer à envoyer des données tant que le client +n'abandonne pas la connexion. Vous pouvez alors utiliser le helper `stream` +pour éviter de créer votre propre système : + +```ruby +get '/' do + stream do |out| + out << "Ca va être hallu -\n" + sleep 0.5 + out << " (attends la suite) \n" + sleep 1 + out << "- cinant !\n" + end +end +``` + +Cela permet d'implémenter des API de streaming ou de +[Server Sent Events](https://w3c.github.io/eventsource/) et peut servir de +base pour des [WebSockets](https://en.wikipedia.org/wiki/WebSocket). Vous +pouvez aussi l'employer pour augmenter le débit quand une partie du contenu +provient d'une ressource lente. + +Le fonctionnement du streaming, notamment le nombre de requêtes simultanées, +dépend énormément du serveur web utilisé. Certains ne prennent pas du tout en +charge le streaming. Lorsque le serveur ne gère pas le streaming, la partie +body de la réponse sera envoyée au client en une seule fois, après +l'exécution du bloc passé au helper `stream`. Le streaming ne +fonctionne pas du tout avec Shotgun. + +En utilisant le helper `stream` avec le paramètre `keep_open`, il n'appelera +pas la méthode `close` du flux, vous laissant la possibilité de le fermer à +tout moment au cours de l'exécution. Ceci ne fonctionne qu'avec les serveurs +evented (ie non threadés) tels que Thin et Rainbows. Les autres serveurs +fermeront malgré tout le flux : + +```ruby +# interrogation prolongée + +set :server, :thin +connexions = [] + +get '/souscrire' do + # abonne un client aux évènements du serveur + stream(:keep_open) do |out| + connexions << out + # purge les connexions abandonnées + connexions.reject!(&:closed?) + end +end + +post '/message' do + connexions.each do |out| + # prévient le client qu'un nouveau message est arrivé + out << params['message'] << "\n" + + # indique au client de se connecter à nouveau + out.close + end + + # compte-rendu + "message reçu" +end +``` + +Il est aussi possible pour le client de fermer la connexion en essayant +d'écrire sur le socket. Pour cette raison, il est recommandé de vérifier +`out.closed?` avant d'essayer d'y écrire. + +### Journalisation (Logging) + +Dans le contexte de la requête, la méthode utilitaire `logger` expose une +instance de `Logger` : + +```ruby +get '/' do + logger.info "chargement des données" + # ... +end +``` + +Ce logger va automatiquement prendre en compte les paramètres de +configuration pour la journalisation de votre gestionnaire Rack. Si la +journalisation est désactivée, cette méthode renverra un objet factice et +vous n'avez pas à vous en inquiéter dans vos routes en le filtrant. + +Notez que la journalisation est seulement activée par défaut pour +`Sinatra::Application`, donc si vous héritez de `>Sinatra::Base`, +vous aurez à l'activer vous-même : + +```ruby +class MonApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Si vous souhaitez utiliser votre propre logger, vous devez définir le paramètre +`logging` à `nil` pour être certain qu'aucun middleware de logging ne sera +installé (notez toutefois que `logger` renverra alors `nil`). Dans ce cas, +Sinatra utilisera ce qui sera présent dans `env['rack.logger']`. + +### Types Mime + +Quand vous utilisez `send_file` ou des fichiers statiques, vous +pouvez rencontrer des types mime que Sinatra ne connaît pas. Utilisez +`mime_type` pour les déclarer par extension de fichier : + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Vous pouvez également les utiliser avec la méthode `content_type` : + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Former des URLs + +Pour former des URLs, vous devriez utiliser la méthode `url`, par exemple en +Haml : + +```ruby +%a{:href => url('/foo')} foo +``` + +Cela prend en compte les proxy inverse et les routeurs Rack, s'ils existent. + +Cette méthode est également disponible sous l'alias `to` (voir ci-dessous +pour un exemple). + +### Redirection du navigateur + +Vous pouvez déclencher une redirection du navigateur avec la méthode +`redirect` : + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Tout paramètre additionnel sera utilisé comme argument pour la méthode +`halt` : + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'mauvais endroit mon pote' +``` + +Vous pouvez aussi rediriger vers la page dont l'utilisateur venait au moyen de +`redirect back` : + +```ruby +get '/foo' do + "faire quelque chose" +end + +get '/bar' do + faire_quelque_chose + redirect back +end +``` + +Pour passer des arguments à une redirection, ajoutez-les soit à la requête : + +```ruby +redirect to('/bar?sum=42') +``` + +Ou bien utilisez une session : + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### Contrôle du cache + +Définissez correctement vos en-têtes à la base pour un bon cache HTTP. + +Vous pouvez facilement définir l'en-tête Cache-Control de la manière suivante : + +```ruby +get '/' do + cache_control :public + "met le en cache !" +end +``` + +Conseil de pro : définir le cache dans un filtre before : + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si vous utilisez la méthode `expires` pour définir l'en-tête correspondant, +`Cache-Control` sera alors défini automatiquement : + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Pour utiliser correctement le cache, vous devriez utiliser `etag` ou +`last_modified`. Il est recommandé d'utiliser ces méthodes *avant* de faire +d'importantes modifications, car elles vont immédiatement déclencher la réponse +si le client a déjà la version courante dans son cache : + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Il est également possible d'utiliser un +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) : + +```ruby +etag @article.sha1, :weak +``` + +Ces méthodes ne sont pas chargées de mettre des données en cache, mais elles +fournissent les informations nécessaires pour le cache de votre navigateur. Si vous êtes à la +recherche d'une solution rapide pour un reverse-proxy de cache, essayez +[rack-cache](https://github.com/rtomayko/rack-cache) : + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête `Cache-Control` (voir plus loin). + +D'après la RFC 2616, votre application devrait se comporter différement lorsque +l'en-tête If-Match ou If-None-Match est défini à `*` en tenant compte du +fait que la ressource demandée existe déjà ou pas. Sinatra considère que les +requêtes portant sur des ressources sûres (tel que get) ou idempotentes (tel que +put) existent déjà et pour les autres ressources (par exemple dans le cas +de requêtes post) qu'il s'agit de nouvelles ressources. Vous pouvez modifier ce +comportement en passant une option `:new_resource` : + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :nouvel_article +end +``` + +Si vous souhaitez avoir un ETag faible, utilisez l'option `:kind` : + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Envoyer des fichiers + +Pour envoyer des fichiers, vous pouvez utiliser la méthode `send_file` : + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Quelques options sont également acceptées : + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Les options sont : + +
    +
    filename
    +
    + le nom du fichier dans la réponse, par défaut le nom du fichier envoyé. +
    + +
    type
    +
    + type de contenu à utiliser, deviné à partir de l’extension de fichier si + absent +
    + +
    disposition
    +
    + utilisé pour Content-Disposition, les valeurs possibles étant : nil + (par défaut), :attachment et :inline +
    + +
    length
    +
    en-tête Content-Length, par défaut la taille du fichier
    + +
    status
    +
    + code état à renvoyer. Utile quand un fichier statique sert de page d’erreur. + Si le gestionnaire Rack le supporte, d'autres moyens que le streaming via le + processus Ruby seront utilisés. Si vous utilisez cette méthode, Sinatra gérera + automatiquement les requêtes de type range. +
    +
    + +### Accéder à l'objet requête + +L'objet correspondant à la requête envoyée peut être récupéré dans le contexte +de la requête (filtres, routes, gestionnaires d'erreur) au moyen de la méthode +`request` : + +```ruby +# application tournant à l'adresse http://exemple.com/exemple +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # vrai + request.preferred_type(t) # 'text/html' + request.body # corps de la requête envoyée par le client + # (voir ci-dessous) + request.scheme # "http" + request.script_name # "/exemple" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # taille de request.body + request.media_type # type de média pour request.body + request.host # "exemple.com" + request.get? # vrai (méthodes similaires pour les autres + # verbes HTTP) + request.form_data? # faux + request["UN_ENTETE"] # valeur de l'en-tête UN_ENTETE + request.referrer # référant du client ou '/' + request.user_agent # user agent (utilisé par la condition :agent) + request.cookies # tableau contenant les cookies du navigateur + request.xhr? # requête AJAX ? + request.url # "http://exemple.com/exemple/foo" + request.path # "/exemple/foo" + request.ip # adresse IP du client + request.secure? # faux + request.forwarded? # vrai (si on est derrière un proxy inverse) + request.env # tableau brut de l'environnement fourni par Rack +end +``` + +Certaines options, telles que `script_name` ou `path_info` +peuvent également être modifiées : + +```ruby +before { request.path_info = "/" } + +get "/" do + "toutes les requêtes arrivent ici" +end +``` + +`request.body` est un objet IO ou StringIO : + +```ruby +post "/api" do + request.body.rewind # au cas où il a déjà été lu + donnees = JSON.parse request.body.read + "Bonjour #{donnees['nom']} !" +end +``` + +### Fichiers joints + +Vous pouvez utiliser la méthode `attachment` pour indiquer au navigateur que +la réponse devrait être stockée sur le disque plutôt qu'affichée : + + +```ruby +get '/' do + attachment + "enregistre-le !" +end +``` + +Vous pouvez également lui passer un nom de fichier : + +```ruby +get '/' do + attachment "info.txt" + "enregistre-le !" +end +``` + +### Gérer Date et Time + +Sinatra fourni un helper `time_for` pour convertir une valeur donnée en +objet `Time`. Il peut aussi faire la conversion à partir d'objets `DateTime`, +`Date` ou de classes similaires : + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "encore temps" +end +``` + +Cette méthode est utilisée en interne par `expires`, `last_modified` et +consorts. Par conséquent, vous pouvez très facilement étendre le +fonctionnement de ces méthodes en surchargeant le helper `time_for` dans +votre application : + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "salut" +end +``` + +### Chercher les fichiers de templates + +La méthode `find_template` est utilisée pour trouver les fichiers de +templates à générer : + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "pourrait être #{file}" +end +``` + +Ce n'est pas très utile. En revanche, il est utile de pouvoir surcharger +cette méthode afin de définir son propre mécanisme de recherche. Par exemple, +vous pouvez utiliser plus d'un répertoire de vues : + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(vues, nom, moteur, &bloc) + Array(vues).each { |v| super(v, nom, moteur, &bloc) } + end +end +``` + +Un autre exemple est d'utiliser des répertoires différents pour des moteurs +de rendu différents : + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(vues, nom, moteur, &bloc) + _, dossier = vues.detect { |k,v| moteur == Tilt[k] } + dossier ||= vues[:default] + super(dossier, nom, moteur, &bloc) + end +end +``` + +Vous pouvez également écrire cela dans une extension et la partager avec +d'autres ! + +Notez que `find_template` ne vérifie pas que le fichier existe mais +va plutôt exécuter le bloc pour tous les chemins possibles. Cela n'induit pas +de problème de performance dans le sens où `render` va utiliser `break` dès +qu'un fichier sera trouvé. De plus, l'emplacement des templates (et leur +contenu) est mis en cache si vous n'êtes pas en mode développement. Vous +devez garder cela en tête si vous écrivez une méthode vraiment dingue. + +## Configuration + +Lancé une seule fois au démarrage de tous les environnements : + +```ruby +configure do + # définir un paramètre + set :option, 'valeur' + + # définir plusieurs paramètres + set :a => 1, :b => 2 + + # équivalent à "set :option, true" + enable :option + + # équivalent à "set :option, false"" + disable :option + + # vous pouvez également avoir des paramètres dynamiques avec des blocs + set(:css_dir) { File.join(views, 'css') } +end +``` + +Lancé si l'environnement (variable d'environnement APP_ENV) est `:production` : + +```ruby + configure :production do + ... + end +``` + +Lancé si l'environnement est `:production` ou `:test` : + +```ruby + configure :production, :test do + ... + end +``` + +Vous pouvez accéder à ces paramètres via `settings` : + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Se protéger des attaques + +Sinatra utilise [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +pour protéger votre application contre les principales attaques opportunistes. +Vous pouvez très simplement désactiver cette fonctionnalité (ce qui exposera +votre application à beaucoup de vulnerabilités courantes) : + +```ruby +disable :protection +``` + +Pour désactiver seulement un type de protection, vous pouvez définir `protection` +avec un hash d'options : + +```ruby +set :protection, :except => :path_traversal +``` + +Vous pouvez également lui passer un tableau pour désactiver plusieurs types de +protection : + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Par défaut, il faut que `:sessions` soit activé pour que Sinatra mette en place +un système de protection au niveau de la session. Dans le cas où vous gérez +vous même les sessions, vous devez utiliser l'option `:session` pour que cela +soit le cas : + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### Paramètres disponibles + +
    +
    absolute_redirects
    +
    Si désactivé, Sinatra permettra les redirections relatives. Toutefois, + Sinatra ne sera plus conforme à la RFC 2616 (HTTP 1.1), qui n’autorise + que les redirections absolues.

    + + Activez si votre application tourne derrière un proxy inverse qui n’a + pas été correctement configuré. Notez que la méthode url + continuera de produire des URLs absolues, sauf si vous lui passez + false comme second argument.

    + +

    Désactivé par défaut.

    + +
    add_charset
    +

    types mime pour lesquels la méthode content_type va + automatiquement ajouter l’information du charset.

    + +

    Vous devriez lui ajouter des valeurs plutôt que de l’écraser :

    + +
    settings.add_charset >> "application/foobar"
    + +
    app_file
    +

    chemin pour le fichier de l’application principale, utilisé pour + détecter la racine du projet, les dossiers public et vues, et les + templates en ligne.

    + +
    bind
    +
    adresse IP sur laquelle se brancher (par défaut : 0.0.0.0). Utiliser + seulement pour le serveur intégré.
    + +
    default_encoding
    +
    encodage à utiliser si inconnu (par défaut "utf-8")
    + +
    dump_errors
    +
    afficher les erreurs dans le log. +
    + +
    environment
    +
    environnement courant, par défaut ENV['APP_ENV'], ou + "development" si absent.
    + +
    logging
    +
    utiliser le logger.
    + +
    lock
    +

    Place un lock autour de chaque requête, n’exécutant donc + qu’une seule requête par processus Ruby.

    + +

    Activé si votre application n’est pas thread-safe. Désactivé + par défaut.

    + +
    method_override
    +
    utilise la magie de _method afin de permettre des formulaires + put/delete dans des navigateurs qui ne le permettent pas. + +
    +
    port
    +
    port à écouter. Utiliser seulement pour le serveur intégré.
    + +
    mustermann_opts
    +
    + Un hash d'options à passer à Mustermann.new lors de la compilation + des chemins de routes +
    + +
    prefixed_redirects
    +
    si oui ou non request.script_name doit être inséré dans les + redirections si un chemin non absolu est utilisé. Ainsi, redirect + '/foo' se comportera comme redirect to('/foo'). Désactivé + par défaut.
    + +
    protection
    +
    défini s’il faut activer ou non la protection contre les attaques web. + Voir la section protection précédente.
    + +
    public_dir
    +
    alias pour public_folder. Voir ci-dessous.
    + +
    public_folder
    +
    chemin pour le dossier à partir duquel les fichiers publics sont servis. + Utilisé seulement si les fichiers statiques doivent être servis (voir le + paramètre static). Si non défini, il découle du paramètre + app_file.
    + +
    quiet
    +
    + Désactive les journaux (logs) générés par les commandes start et stop + de Sinatra. false par défaut. +
    + +
    reload_templates
    +
    si oui ou non les templates doivent être rechargés entre les requêtes. + Activé en mode développement.
    + +
    root
    +
    chemin pour le dossier racine du projet. Si non défini, il découle du + paramètre app_file.
    + +
    raise_errors
    +
    soulever les erreurs (ce qui arrêtera l’application). Désactivé par + défaut sauf lorsque environment est défini à + "test".
    + +
    run
    +
    si activé, Sinatra s’occupera de démarrer le serveur, ne pas activer si + vous utiliser rackup ou autres.
    + +
    running
    +
    est-ce que le serveur intégré est en marche ? ne changez pas ce + paramètre !
    + +
    server
    +
    serveur ou liste de serveurs à utiliser pour le serveur intégré. Par + défaut [‘thin’, ‘mongrel’, ‘webrick’], l’ordre indiquant la + priorité.
    + +
    server_settings
    +
    + Si vous utilisez un serveur Webrick, sans doute pour votre environnement de + développement, vous pouvez passer des options à server_settings, + comme SSLEnable ou SSLVerifyClient. Cependant, les + serveurs comme Puma et Thin ne le permettent pas, et vous pouvez donc + définir server_settings en tant que méthode lorsque vous appelez + configure. +
    + +
    sessions
    +
    active le support des sessions basées sur les cookies, en utilisant + Rack::Session::Cookie. Reportez-vous à la section ‘Utiliser les + sessions’ pour plus d’informations.
    + +
    session_store
    +
    + Le middleware Rack utilisé pour les sessions. Rack::Session::Cookie + par défaut. Voir la section 'Utiliser les sessions' pour plus de détails. +
    + +
    show_exceptions
    +
    affiche la trace de l’erreur dans le navigateur lorsqu’une exception se + produit. Désactivé par défaut sauf lorsque environment est + défini à "development".
    + +
    static
    +
    Si oui ou non Sinatra doit s’occuper de servir les fichiers statiques. + Désactivez si vous utilisez un serveur capable de le gérer lui même. Le + désactiver augmentera la performance. Activé par défaut pour le style + classique, désactivé pour le style modulaire.
    + +
    static_cache_control
    +
    A définir quand Sinatra rend des fichiers statiques pour ajouter les + en-têtes Cache-Control. Utilise le helper cache_control. + Désactivé par défaut. Utiliser un array explicite pour définir des + plusieurs valeurs : set :static_cache_control, [:public, :max_age => + 300]
    + +
    threaded
    +
    à définir à true pour indiquer à Thin d’utiliser + EventMachine.defer pour traiter la requête.
    + +
    traps
    +
    Indique si Sinatra doit gérer les signaux système.
    + +
    views
    +
    chemin pour le dossier des vues. Si non défini, il découle du paramètre + app_file.
    + +
    x_cascade
    +
    + Indique s'il faut ou non définir le header X-Cascade lorsqu'aucune route + ne correspond. Défini à true par défaut. +
    +
    + +## Environnements + +Il existe trois environnements prédéfinis : `"development"`, +`"production"` et `"test"`. Les environnements peuvent être +sélectionné via la variable d'environnement `APP_ENV`. Sa valeur par défaut +est `"development"`. Dans ce mode, tous les templates sont rechargés à +chaque requête. Des handlers spécifiques pour `not_found` et +`error` sont installés pour vous permettre d'avoir une pile de trace +dans votre navigateur. En mode `"production"` et `"test"` les +templates sont mis en cache par défaut. + +Pour exécuter votre application dans un environnement différent, définissez la +variable d'environnement `APP_ENV` : + +``` shell +APP_ENV=production ruby my_app.rb +``` + +Vous pouvez utiliser une des méthodes `development?`, `test?` et `production?` +pour déterminer quel est l'environnement en cours : + +```ruby +get '/' do + if settings.development? + "développement !" + else + "pas en développement !" + end +end +``` + +## Gérer les erreurs + +Les gestionnaires d'erreur s'exécutent dans le même contexte que les routes ou +les filtres, ce qui veut dire que vous avez accès (entre autres) aux bons +vieux `haml`, `erb`, `halt`, etc. + +### NotFound + +Quand une exception Sinatra::NotFound est soulevée, ou que le code +retour est 404, le gestionnaire not_found est invoqué : + +```ruby +not_found do + 'Pas moyen de trouver ce que vous cherchez' +end +``` + +### Error + +Le gestionnaire `error` est invoqué à chaque fois qu'une exception est +soulevée dans une route ou un filtre. Notez qu'en développement, il ne +sera exécuté que si vous définissez l'option show exceptions à +`:after_handler` : + +```ruby +set :show_exceptions, :after_handler +``` + +L'objet exception est accessible via la +variable Rack `sinatra.error` : + +```ruby +error do + 'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].message +end +``` + +Erreur personnalisée : + +```ruby +error MonErreurSurMesure do + 'Oups ! Il est arrivé...' + env['sinatra.error'].message +end +``` + +Donc si cette erreur est soulevée : + +```ruby +get '/' do + raise MonErreurSurMesure, 'quelque chose de mal' +end +``` + +La réponse sera : + +``` +Oups ! Il est arrivé... quelque chose de mal +``` + +Alternativement, vous pouvez avoir un gestionnaire d'erreur associé à un code +particulier : + +```ruby +error 403 do + 'Accès interdit' +end + +get '/secret' do + 403 +end +``` + +Ou un intervalle : + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installe pour vous quelques gestionnaires `not_found` et +`error` génériques lorsque vous êtes en environnement de +`development`. + +## Les Middlewares Rack + +Sinatra fonctionne avec [Rack](http://rack.github.io/), une interface standard +et minimale pour les web frameworks Ruby. Un des points forts de Rack est le +support de ce que l'on appelle des "middlewares" -- composants qui viennent se +situer entre le serveur et votre application, et dont le but est de +visualiser/manipuler la requête/réponse HTTP, et d'offrir diverses +fonctionnalités classiques. + +Sinatra permet d'utiliser facilement des middlewares Rack via la méthode de +haut niveau `use` : + +```ruby +require 'sinatra' +require 'mon_middleware_perso' + +use Rack::Lint +use MonMiddlewarePerso + +get '/bonjour' do + 'Bonjour le monde' +end +``` + +La sémantique de `use` est identique à celle définie dans le DSL de +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(le plus souvent utilisé dans un fichier `rackup`). Par exemple, la méthode +`use` accepte divers arguments ainsi que des blocs : + +```ruby +use Rack::Auth::Basic do |identifiant, mot_de_passe| + identifiant == 'admin' && mot_de_passe == 'secret' +end +``` + +Rack est distribué avec de nombreux middlewares standards pour loguer, débuguer, +faire du routage URL, de l'authentification ou gérer des sessions. Sinatra gère +plusieurs de ces composants automatiquement via son système de configuration, ce +qui vous dispense de faire un `use` pour ces derniers. + +Vous trouverez d'autres middlewares intéressants sur +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readm), +ou en consultant le [wiki de Rack](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Tester + +Les tests pour Sinatra peuvent être écrit avec n'importe quelle bibliothèque +basée sur Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) est +recommandé : + +```ruby +require 'mon_application_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MonTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_ma_racine + get '/' + assert_equal 'Bonjour le monde !', last_response.body + end + + def test_avec_des_parametres + get '/rencontrer', :nom => 'Frank' + assert_equal 'Salut Frank !', last_response.body + end + + def test_avec_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Vous utilisez Songbird !", last_response.body + end +end +``` + +Note : si vous utilisez le style modulaire de Sinatra, remplacez +`Sinatra::Application` par le nom de la classe de votre application. + +## Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires + +Définir votre application au niveau supérieur fonctionne bien dans le cas des +micro-applications mais présente pas mal d'inconvénients pour créer des +composants réutilisables sous forme de middlewares Rack, de Rails metal, de +simples librairies avec un composant serveur ou même d'extensions Sinatra. Le +niveau supérieur suppose une configuration dans le style des micro-applications +(une application d'un seul fichier, des répertoires `./public` et +`./views`, des logs, une page d'erreur, etc...). C'est là que +`Sinatra::Base` prend tout son intérêt : + +```ruby +require 'sinatra/base' + +class MonApplication < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Bonjour le monde !' + end +end +``` + +Les méthodes de la classe `Sinatra::Base` sont parfaitement identiques à +celles disponibles via le DSL de haut niveau. Il suffit de deux modifications +pour transformer la plupart des applications de haut niveau en un composant +`Sinatra::Base` : + +* Votre fichier doit charger `sinatra/base` au lieu de `sinatra`, sinon toutes + les méthodes du DSL Sinatra seront importées dans l'espace de nom principal. +* Les gestionnaires de routes, la gestion d'erreur, les filtres et les options + doivent être placés dans une classe héritant de `Sinatra::Base`. + +`Sinatra::Base` est une page blanche. La plupart des options sont +désactivées par défaut, y compris le serveur intégré. Reportez-vous à +[Options et Configuration](http://www.sinatrarb.com/configuration.html) +pour plus d'informations sur les options et leur fonctionnement. Si vous +souhaitez un comportement plus proche de celui obtenu lorsque vous définissez +votre application au niveau supérieur (aussi connu sous le nom de style +Classique), vous pouvez créer une classe héritant de `Sinatra::Application`. + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Bonjour le monde !' + end +end +``` + +### Style modulaire vs. style classique + +Contrairement aux idées reçues, il n'y a rien de mal à utiliser le style +classique. Si c'est ce qui convient pour votre application, vous n'avez +aucune raison de passer à une application modulaire. + +Le principal inconvénient du style classique sur le style modulaire est que vous +ne pouvez avoir qu'une application par processus Ruby. Si vous pensez en +utiliser plus, passez au style modulaire. Et rien ne vous empêche de mixer style +classique et style modulaire. + +Si vous passez d'un style à l'autre, souvenez-vous des quelques différences +mineures en ce qui concerne les paramètres par défaut : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ParamètreClassiqueModulaireModulaire
    app_filefichier chargeant sinatrafichier héritant de Sinatra::Basefichier héritant de Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Servir une application modulaire + +Il y a deux façons de faire pour démarrer une application modulaire, démarrez +avec `run!` : + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... code de l'application ici ... + + # démarre le serveur si ce fichier est directement exécuté + run! if app_file == $0 +end +``` + +Démarrez ensuite avec : + +```shell +ruby my_app.rb +``` + +Ou alors avec un fichier `config.ru`, qui permet d'utiliser n'importe +quel gestionnaire Rack : + +```ruby +# config.ru +require './my_app' +run MyApp +``` + +Exécutez : + +```shell +rackup -p 4567 +``` + +### Utiliser une application de style classique avec un fichier config.ru + +Ecrivez votre application : + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Et un fichier `config.ru` correspondant : + +```ruby +require './app' +run Sinatra::Application +``` + +### Quand utiliser un fichier config.ru ? + +Quelques cas où vous devriez utiliser un fichier `config.ru` : + +* Vous souhaitez déployer avec un autre gestionnaire Rack (Passenger, Unicorn, + Heroku, ...). +* Vous souhaitez utiliser plus d'une sous-classe de `Sinatra::Base`. +* Vous voulez utiliser Sinatra comme un middleware, non en tant que + endpoint. + +**Il n'est pas nécessaire de passer par un fichier `config.ru` pour la +seule raison que vous êtes passé au style modulaire, et vous n'avez pas besoin +de passer au style modulaire pour utiliser un fichier `config.ru`.** + +### Utiliser Sinatra comme Middleware + +Non seulement Sinatra peut utiliser d'autres middlewares Rack, il peut +également être à son tour utilisé au-dessus de n'importe quel endpoint Rack +en tant que middleware. Cet endpoint peut très bien être une autre +application Sinatra, ou n'importe quelle application basée sur Rack +(Rails/Ramaze/Camping/...) : + +```ruby +require 'sinatra/base' + +class EcranDeConnexion < Sinatra::Base + enable :sessions + + get('/connexion') { haml :connexion } + + post('/connexion') do + if params['nom'] = 'admin' && params['motdepasse'] = 'admin' + session['nom_utilisateur'] = params['nom'] + else + redirect '/connexion' + end + end +end + +class MonApp < Sinatra::Base + # le middleware sera appelé avant les filtres + use EcranDeConnexion + + before do + unless session['nom_utilisateur'] + halt "Accès refusé, merci de vous connecter." + end + end + + get('/') { "Bonjour #{session['nom_utilisateur']}." } +end +``` + +### Création dynamique d'applications + +Il se peut que vous ayez besoin de créer une nouvelle application à l'exécution +sans avoir à les assigner à une constante, vous pouvez le faire grâce à +`Sinatra.new` : + +```ruby +require 'sinatra/base' +mon_app = Sinatra.new { get('/') { "salut" } } +mon_app.run! +``` + +L'application dont elle hérite peut être passé en argument optionnel : + +```ruby +# config.ru +require 'sinatra/base' + +controleur = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controleur) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controleur) { get('/') { 'b' } } +end +``` + +C'est notamment utile pour tester des extensions pour Sinatra ou bien pour +utiliser Sinatra dans votre propre bibliothèque. + +Cela permet également d'utiliser très facilement Sinatra comme middleware : + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Contextes et Binding + +Le contexte dans lequel vous êtes détermine les méthodes et variables +disponibles. + +### Contexte de l'application/classe + +Une application Sinatra correspond à une sous-classe de `Sinatra::Base`. Il +s'agit de `Sinatra::Application` si vous utilisez le DSL de haut niveau +(`require 'sinatra'`). Sinon c'est la sous-classe que vous avez définie. Dans +le contexte de cette classe, vous avez accès aux méthodes telles que `get` ou +`before`, mais pas aux objets `request` ou `session` étant donné que toutes +les requêtes sont traitées par une seule classe d'application. + +Les options définies au moyen de `set` deviennent des méthodes de classe : + +```ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Eh, je ne suis plus dans le contexte de l'application ! + end +end +``` + +Vous avez le binding du contexte de l'application dans : + +* Le corps de la classe d'application +* Les méthodes définies par les extensions +* Le bloc passé à `helpers` +* Les procs/blocs utilisés comme argument pour `set` +* Le bloc passé à `Sinatra.new` + +Vous pouvez atteindre ce contexte (donc la classe) de la façon suivante : + +* Via l'objet passé dans les blocs `configure` (`configure { |c| ... }`) +* En utilisant `settings` dans le contexte de la requête + +### Contexte de la requête/instance + +Pour chaque requête traitée, une nouvelle instance de votre classe +d'application est créée et tous vos gestionnaires sont exécutés dans ce +contexte. Depuis celui-ci, vous pouvez accéder aux objets `request` et +`session` ou faire appel aux fonctions de rendu telles que `erb` ou `haml`. +Vous pouvez accéder au contexte de l'application depuis le contexte de la +requête au moyen de `settings` : + +```ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + get '/ajouter_route/:nom' do + # Contexte de la requête pour '/ajouter_route/:nom' + @value = 42 + + settings.get("/#{params['nom']}") do + # Contexte de la requête pour "/#{params['nom']}" + @value # => nil (on est pas au sein de la même requête) + end + + "Route ajoutée !" + end +end +``` + +Vous avez le binding du contexte de la requête dans : + +* les blocs get, head, post, put, delete, options, patch, link et unlink +* les filtres before et after +* les méthodes utilitaires (définies au moyen de `helpers`) +* les vues et templates + +### Le contexte de délégation + +Le contexte de délégation se contente de transmettre les appels de méthodes au +contexte de classe. Toutefois, il ne se comporte pas à 100% comme le contexte +de classe car vous n'avez pas le binding de la classe : seules les méthodes +spécifiquement déclarées pour délégation sont disponibles et il n'est pas +possible de partager de variables/états avec le contexte de classe +(comprenez : `self` n'est pas le même). Vous pouvez ajouter des délégations de +méthode en appelant `Sinatra::Delegator.delegate :method_name`. + +Vous avez le binding du contexte de délégation dans : + +* Le binding de haut niveau, si vous avez utilisé `require "sinatra"` +* Un objet qui inclut le module `Sinatra::Delegator` + +Pour vous faire une idée, vous pouvez jeter un coup d'oeil au +[mixin Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +qui [étend l'objet principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Ligne de commande + +Les applications Sinatra peuvent être lancées directement : + +```shell +ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR] +``` + +Avec les options : + +``` +-h # aide +-p # déclare le port (4567 par défaut) +-o # déclare l'hôte (0.0.0.0 par défaut) +-e # déclare l'environnement (development par défaut) +-s # déclare le serveur/gestionnaire à utiliser (thin par défaut) +-x # active le mutex lock (off par défaut) +``` + +### Multi-threading + +_Cette partie est basée sur [une réponse StackOverflow][so-answer] de Konstantin._ + +Sinatra n'impose pas de modèle de concurrence. Sinatra est thread-safe, vous pouvez +donc utiliser n'importe quel gestionnaire Rack, comme Thin, Puma ou WEBrick en mode +multi-threaded. + +Cela signifie néanmoins qu'il vous faudra spécifier les paramètres correspondant au +gestionnaire Rack utilisé lors du démarrage du serveur. + +L'exemple ci-dessous montre comment vous pouvez exécuter un serveur Thin de manière +multi-threaded: + +``` +# app.rb +require 'sinatra/base' + +classe App < Sinatra::Base + get '/' do + 'Bonjour le monde !' + end +end + +App.run! +``` + +Pour démarrer le serveur, exécuter la commande suivante: + +``` +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## Configuration nécessaire + +Les versions suivantes de Ruby sont officiellement supportées : + +
    +
    Ruby 2.2
    +
    + 2.2 est totalement supporté et recommandé. L'abandon de son support + officiel n'est pas à l'ordre du jour. +
    + +
    Rubinius
    +
    + Rubinius est officiellement supporté (Rubinius >= 2.x). Un gem install + puma est recommandé. +
    + +
    JRuby
    +
    + La dernière version stable de JRuby est officiellement supportée. Il est + déconseillé d'utiliser des extensions C avec JRuby. Un gem install + trinidad est recommandé. +
    +
    + +Les versions antérieures à 2.2.2 ne sont plus supportées depuis Sinatra 2.0. + +Nous gardons également un oeil sur les versions Ruby à venir. + +Les implémentations Ruby suivantes ne sont pas officiellement supportées mais +sont malgré tout connues pour permettre de faire fonctionner Sinatra : + +* Versions plus anciennes de JRuby et Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation) + +Le fait de ne pas être officiellement supporté signifie que si quelque chose +ne fonctionne pas sur cette plateforme uniquement alors c'est un problème de la +plateforme et pas un bug de Sinatra. + +Nous lançons également notre intégration continue (CI) avec ruby-head (la +future 2.1.0), mais nous ne pouvont rien garantir étant donné les évolutions +continuelles. La version 2.1.0 devrait être totalement supportée. + +Sinatra devrait fonctionner sur n'importe quel système d'exploitation +supporté par l'implémentation Ruby choisie. + +Si vous utilisez MacRuby, vous devriez `gem install control_tower`. + +Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, BlueRuby ou +toute version de Ruby antérieure à 1.8.7 à l'heure actuelle. + +## Essuyer les plâtres + +Si vous souhaitez tester la toute dernière version de Sinatra, n'hésitez pas +à faire tourner votre application sur la branche master, celle-ci devrait être +stable. + +Pour cela, la méthode la plus simple est d'installer une gem de prerelease que +nous publions de temps en temps : + +```shell +gem install sinatra --pre +``` +Ce qui permet de bénéficier des toutes dernières fonctionnalités. + +### Installer avec Bundler + +Il est cependant conseillé de passer par [Bundler](http://bundler.io) pour +faire tourner votre application avec la dernière version de Sinatra. + +Pour commencer, installez bundler si nécessaire : + +```shell +gem install bundler +``` + +Ensuite, créez un fichier `Gemfile` dans le dossier de votre projet : + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# autres dépendances +gem 'haml' # si par exemple vous utilisez haml +gem 'activerecord', '~> 3.0' # au cas où vous auriez besoin de ActiveRecord 3.x +``` + +Notez que vous devez lister toutes les dépendances de votre application dans +ce fichier `Gemfile`. Les dépendances directes de Sinatra (Rack et Tilt) seront +automatiquement téléchargées et ajoutées par Bundler. + +Vous pouvez alors lancer votre application de la façon suivante : + +```shell +bundle exec ruby myapp.rb +``` + +## Versions + +Sinatra se conforme aux [versions sémantiques](http://semver.org/), aussi bien +SemVer que SemVerTag. + +## Mais encore + +* [Site internet](http://www.sinatrarb.com/) - Plus de documentation, + de news, et des liens vers d'autres ressources. +* [Contribuer](http://www.sinatrarb.com/contributing) - Vous avez trouvé un + bug ? Besoin d'aide ? Vous avez un patch ? +* [Suivi des problèmes](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](http://groups.google.com/group/sinatrarb/topics) +* IRC : [#sinatra](irc://chat.freenode.net/#sinatra) sur http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutoriels et recettes +* [Sinatra Recipes](http://recipes.sinatrarb.com/) trucs et astuces rédigés par + la communauté +* Documentation API de la [dernière version](http://www.rubydoc.info/gems/sinatra) + ou du [HEAD courant](http://www.rubydoc.info/github/sinatra/sinatra) sur + http://www.rubydoc.info/ +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.hu.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.hu.md new file mode 100644 index 0000000000..f6c8239d2b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.hu.md @@ -0,0 +1,728 @@ +# Sinatra +*Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű +változatnak, és lehet, hogy nem naprakész.* + +A Sinatra egy [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) +webalkalmazások Ruby nyelven történő fejlesztéséhez, minimális +energiabefektetéssel: + +```ruby + # myapp.rb + require 'sinatra' + get '/' do + 'Helló Világ!' + end +``` + +Telepítsd a gem-et és indítsd el az alkalmazást a következőképpen: + +```ruby + sudo gem install sinatra + ruby myapp.rb +``` + +Az alkalmazás elérhető lesz itt: [http://localhost:4567](http://localhost:4567) + +## Útvonalak (routes) + +A Sinatrában az útvonalat egy HTTP metódus és egy URL-re illeszkedő minta +párosa alkotja. Minden egyes útvonalhoz tartozik egy blokk: + +```ruby + get '/' do + .. megjelenítünk valamit .. + end + + post '/' do + .. létrehozunk valamit .. + end + + put '/' do + .. frissítünk valamit .. + end + + delete '/' do + .. törlünk valamit .. + end +``` + +Az útvonalak illeszkedését a rendszer a definiálásuk sorrendjében +ellenőrzi. Sorrendben mindig az első illeszkedő útvonalhoz tartozó metódus kerül +meghívásra. + +Az útvonalminták tartalmazhatnak paramétereket is, melyeket a `params` +hash-ből érhetünk el: + +```ruby + get '/hello/:name' do + # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra + # ekkor params['name'] értéke 'foo' vagy 'bar' lesz + "Helló #{params['name']}!" + end +``` + +A kulcsszavas argumentumokat (named parameters) blokk paraméterek útján +is el tudod érni: + +```ruby + get '/hello/:name' do |n| + "Helló #{n}!" + end +``` + +Az útvonalmintákban szerepelhetnek joker paraméterek is, melyeket a +`params['splat']` tömbön keresztül tudunk elérni. + +```ruby + get '/say/*/to/*' do + # illeszkedik a /say/hello/to/world mintára + params['splat'] # => ["hello", "world"] + end + + get '/download/*.*' do + # illeszkedik a /download/path/to/file.xml mintára + params['splat'] # => ["path/to/file", "xml"] + end +``` + +Reguláris kifejezéseket is felvehetünk az útvonalba: + +```ruby + get /\/hello\/([\w]+)/ do + "Helló, #{params['captures'].first}!" + end +``` + +Vagy blokk paramétereket: + +```ruby + get %r{/hello/([\w]+)} do |c| + "Helló, #{c}!" + end +``` + +Az útvonalak azonban számos egyéb illeszkedési feltétel szerint is +tervezhetők, így például az user agent karakterláncot alapul véve: + +```ruby + get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "A Songbird #{params['agent'][0]} verzióját használod" + end + + get '/foo' do + # illeszkedik az egyéb user agentekre + end +``` + +## Statikus állományok + +A statikus fájlok kiszolgálása a `./public` könyvtárból +történik, de természetesen más könyvtárat is megadhatsz erre a célra, +mégpedig a :public_folder kapcsoló beállításával: + + set :public_folder, __dir__ + '/static' + +Fontos megjegyezni, hogy a nyilvános könyvtár neve nem szerepel az URL-ben. +A ./public/css/style.css fájl az +`http://example.com/css/style.css` URL-en lesz elérhető. + +## Nézetek és Sablonok + +A sablonfájlokat rendszerint a `./views` könyvtárba helyezzük, de +itt is lehetőség nyílik egyéb könyvtár használatára: + + set :views, __dir__ + '/templates' + +Nagyon fontos észben tartani, hogy a sablononkra mindig szimbólumokkal +hivatkozunk, még akkor is, ha egyéb (ebben az esetben a +:'subdir/template') könyvtárban tároljuk őket. A renderelő +metódusok minden, nekik közvetlenül átadott karakterláncot megjelenítenek. + +### Haml sablonok + +HAML sablonok rendereléséhez szükségünk lesz a haml gem-re vagy könyvtárra: + +```ruby + # Importáljuk be a haml-t az alkalmazásba + require 'haml' + + get '/' do + haml :index + end +``` + +Ez szépen lerendereli a `./views/index.haml` sablont. + +A [Haml kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml + + get '/' do + haml :index, :haml_options => {:format => :html4 } # immár felülírva + end +``` + +### Erb sablonok + + # Importáljuk be az erb-t az alkalmazásba + +```ruby + require 'erb' + + get '/' do + erb :index + end +``` + +Ez a `./views/index.erb` sablont fogja lerenderelni. + +### Builder sablonok + +Szükségünk lesz a builder gem-re vagy könyvtárra a builder sablonok +rendereléséhez: + + # Importáljuk be a builder-t az alkalmazásba + +```ruby + require 'builder' + + get '/' do + builder :index + end +``` + +Ez pedig a `./views/index.builder` állományt fogja renderelni. + +### Sass sablonok + +Sass sablonok használatához szükség lesz a haml gem-re vagy könyvtárra: + + # Be kell importálni a haml, vagy a sass könyvtárat + +```ruby + require 'sass' + + get '/stylesheet.css' do + sass :stylesheet + end +``` + +Így a `./views/stylesheet.sass` fájl máris renderelhető. + +A [Sass kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested + + get '/stylesheet.css' do + sass :stylesheet, :sass_options => {:style => :expanded } # felülírva + end +``` + +### Beágyazott sablonok + +```ruby + get '/' do + haml '%div.title Helló Világ' + end +``` + +Lerendereli a beágyazott sablon karakerláncát. + +### Változók elérése a sablonokban + +A sablonok ugyanabban a kontextusban kerülnek kiértékelésre, mint az +útvonal metódusok (route handlers). Az útvonal metódusokban megadott +változók közvetlenül elérhetőek lesznek a sablonokban: + +```ruby + get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' + end +``` + +De megadhatod egy lokális változókat tartalmazó explicit hash-ben is: + +```ruby + get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } + end +``` + +Ezt leginkább akkor érdemes megtenni, ha partial-eket akarunk renderelni +valamely más sablonból. + +### Fájlon belüli sablonok + +Sablonokat úgy is megadhatunk, hogy egyszerűen az alkalmazás fájl +végére begépeljük őket: + +```ruby + require 'rubygems' + require 'sinatra' + + get '/' do + haml :index + end + + __END__ + + @@ layout + %html + = yield + + @@ index + %div.title Helló Világ!!!!! +``` + +Megjegyzés: azok a fájlon belüli sablonok, amelyek az alkalmazás fájl végére +kerülnek és függnek a sinatra könyvtártól, automatikusan betöltődnek. +Ha ugyanezt más alkalmazásfájlban is szeretnéd megtenni, hívd meg +a use_in_file_templates! metódust az adott fájlban. + +### Kulcsszavas sablonok + +Sablonokat végül a felsőszintű template metódussal is +definiálhatunk: + +```ruby + template :layout do + "%html\n =yield\n" + end + + template :index do + '%div.title Helló Világ!' + end + + get '/' do + haml :index + end +``` + +Ha létezik "layout" nevű sablon, akkor az minden esetben meghívódik, amikor +csak egy sablon renderelésre kerül. A layoutokat ki lehet kapcsolni a +`:layout => false` meghívásával. + +```ruby + get '/' do + haml :index, :layout => !request.xhr? + end +``` + +## Helperek + +Használd a felső szintű helpers metódust azokhoz a helper +függvényekhez, amiket az útvonal metódusokban és a sablonokban akarsz +használni: + +```ruby + helpers do + def bar(name) + "#{name}bar" + end + end + + get '/:name' do + bar(params['name']) + end +``` + +## Szűrők (filters) + +Az előszűrők (before filter) az adott hívás kontextusában minden egyes +kérés alkalmával kiértékelődnek, így módosíthatják a kérést és a +választ egyaránt. A szűrőkbe felvett példányváltozók elérhetőek lesznek +az útvonalakban és a sablonokban is: + +```ruby + before do + @note = 'Csá!' + request.path_info = '/foo/bar/baz' + end + + get '/foo/*' do + @note #=> 'Szeva!' + params['splat'] #=> 'bar/baz' + end +``` + +Az utószűrők az egyes kérések után, az adott kérés kontextusában kerülnek +kiértékelésre, így ezek is képesek módosítani a kérést és a választ egyaránt. +Az előszűrőkben és úvonalakban létrehozott példányváltozók elérhetőek lesznek +az utószűrők számára: + +```ruby + after do + puts response.status + end +``` + +## Megállítás + +Egy kérés szűrőben vagy útvonalban történő azonnal blokkolásához +használd a következő parancsot: + + halt + +A megállításkor egy blokktörzset is megadhatsz ... + + halt 'ez fog megjelenni a törzsben' + +Vagy állítsd be a HTTP státuszt és a törzset is egyszerre ... + + halt 401, 'menj innen!' + +## Passzolás + +Az útvonalak továbbadhatják a végrehajtást egy másik útvonalnak +a `pass` függvényhívással: + +```ruby + get '/guess/:who' do + pass unless params['who'] == 'Frici' + "Elkaptál!" + end + + get '/guess/*' do + "Elhibáztál!" + end +``` + +Az útvonal blokkja azonnal kilép és átadja a vezérlést a következő +illeszkedő útvonalnak. Ha nem talál megfelelő útvonalat, a Sinatra +egy 404-es hibával tér vissza. + +## Beállítások + +Csak indításkor, de minden környezetre érvényesen fusson le: + +```ruby + configure do + ... + end +``` + +Csak akkor fusson le, ha a környezet (a APP_ENV környezeti változóban) +`:production`-ra van állítva: + +```ruby + configure :production do + ... + end +``` + +Csak akkor fusson le, ha a környezet :production vagy :test: + +```ruby + configure :production, :test do + ... + end +``` + +## Hibakezelés + +A hibakezelők ugyanabban a kontextusban futnak le, mint az útvonalak és +előszűrők, ezért számukra is elérhetőek mindazok a könyvtárak, amelyek +az utóbbiak rendelkezésére is állnak; így például a `haml`, +az `erb`, a `halt` stb. + +### Nem található + +Amikor a `Sinatra::NotFound` kivétel fellép, vagy a válasz HTTP +státuszkódja 404-es, mindig a `not_found` metódus hívódik meg. + +```ruby + not_found do + 'Sehol sem találom, amit keresel' + end +``` + +### Hiba + +Az `error` metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy +előszűrő kivételt vált ki. A kivétel objektum lehívható a +`sinatra.error` Rack változótól: + +```ruby + error do + 'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].message + end +``` + +Egyéni hibakezelés: + +```ruby + error MyCustomError do + 'Szóval az van, hogy...' + env['sinatra.error'].message + end +``` + +És amikor fellép: + +```ruby + get '/' do + raise MyCustomError, 'valami nem stimmel!' + end +``` + +Ez fog megjelenni: + + Szóval az van, hogy... valami nem stimmel! + +A Sinatra speciális `not_found` és `error` hibakezelőket +használ, amikor a futtatási környezet fejlesztői módba van kapcsolva. + +## Mime típusok + +A `send_file` metódus használatakor, vagy statikus fájlok +kiszolgálásakor előfordulhat, hogy a Sinatra nem ismeri fel a fájlok +mime típusát. Ilyenkor használd a +mime_type+ kapcsolót a fájlkiterjesztés +bevezetéséhez: + +```ruby + mime_type :foo, 'text/foo' +``` + +## Rack Middleware + +A Sinatra egy Ruby keretrendszerek számára kifejlesztett egyszerű és szabványos +interfészre, a [Rack](http://rack.github.io/) -re épül. A Rack fejlesztői +szempontból egyik legérdekesebb jellemzője, hogy támogatja az úgynevezett +"middleware" elnevezésű komponenseket, amelyek beékelődnek a szerver és az +alkalmazás közé, így képesek megfigyelni és/vagy módosítani a HTTP +kéréseket és válaszokat. Segítségükkel különféle, egységesen működő +funkciókat építhetünk be rendszerünkbe. + +A Sinatra keretrendszerben gyerekjáték a Rack middleware-ek behúzása a +`use` metódus segítségével: + +```ruby + require 'sinatra' + require 'my_custom_middleware' + + use Rack::Lint + use MyCustomMiddleware + + get '/hello' do + 'Helló Világ' + end +``` + +A `use` metódus szemantikája megegyezik a +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL-ben +használt +use+ metóduséval (az említett DSL-t leginkább rackup állományokban +használják). Hogy egy példát említsünk, a `use` metódus elfogad +változókat és blokkokat egyaránt, akár kombinálva is ezeket: + +```ruby + use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'titkos' + end +``` + +A Rack terjesztéssel egy csomó alap middleware komponens is érkezik, +amelyekkel a naplózás, URL útvonalak megadása, autentikáció és +munkamenet-kezelés könnyen megvalósítható. A Sinatra ezek közül elég +sokat automatikusan felhasznál a beállításoktól függően, így ezek +explicit betöltésével (+use+) nem kell bajlódnod. + +## Tesztelés + +Sinatra teszteket bármely Rack alapú tesztelő könyvtárral vagy +keretrendszerrel készíthetsz. Mi a [Rack::Test](http://gitrdoc.com/brynary/rack-test) +könyvtárat ajánljuk: + +```ruby + require 'my_sinatra_app' + require 'rack/test' + + class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Helló Világ!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frici' + assert_equal 'Helló Frici!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbird-öt használsz!", last_response.body + end + end +``` + +Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok +a 0.9.2-es kiadástól kezdve elavultnak számítanak. + +## Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások + +Az alkalmazást felső szinten építeni megfelelhet mondjuk egy kisebb +app esetén, ám kifejezetten károsnak bizonyulhat olyan komolyabb, +újra felhasználható komponensek készítésekor, mint például egy Rack +middleware, Rails metal, egyszerűbb kiszolgáló komponenssel bíró +könyvtárak vagy éppen Sinatra kiterjesztések. A felső szintű DSL +bepiszkítja az Objektum névteret, ráadásul kisalkalmazásokra szabott +beállításokat feltételez (így például egyetlen alkalmazásfájl, +`./public` +és `./views` könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). +Itt jön a képbe a Sinatra::Base osztály: + +```ruby + require 'sinatra/base' + + class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Helló Világ!' + end + end +``` + +A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként +vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül +használhatod (`use`) vagy futtathatod (`run`) az osztályodat egy rackup +konfigurációs állományban (`config.ru`), vagy egy szerverkomponenst +tartalmazó könyvtár vezérlésekor: + +```ruby + MyApp.run! :host => 'localhost', :port => 9090 +``` + +A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső +szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű +alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben: + +* A fájlban nem a `sinatra`, hanem a `sinatra/base` osztályt kell + beimportálni, mert egyébként az összes Sinatra DSL metódus a fő + névtérbe kerül. +* Az alkalmazás útvonalait, hibakezelőit, szűrőit és beállításait + a Sinatra::Base osztály gyermekosztályaiban kell megadni. + +A `Sinatra::Base` osztály igazából egy üres lap: a legtöbb funkció +alapból ki van kapcsolva, beleértve a beépített szervert is. A +beállításokkal és az egyes kapcsolók hatásával az +[Options and Configuration](http://www.sinatrarb.com/configuration.html) lap +foglalkozik. + +Széljegyzet: A Sinatra felső szintű DSL-je egy egyszerű delegációs +rendszerre épül. A Sinatra::Application osztály - a Sinatra::Base egy +speciális osztályaként - fogadja az összes :get, :put, :post, +:delete, :before, :error, :not_found, :configure és :set üzenetet, +ami csak a felső szintre beérkezik. Érdemes utánanézned a kódban, +miképp [kerül be](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25) +a [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064) +a fő névtérbe. + +## Parancssori lehetőségek + +Sinatra alkalmazásokat közvetlenül futtathatunk: + +``` + ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER] +``` + +Az alábbi kapcsolókat ismeri fel a rendszer: + + -h # segítség + -p # a port beállítása (alapértelmezés szerint ez a 4567-es) + -e # a környezet beállítása (alapértelmezés szerint ez a development) + -s # a rack szerver/handler beállítása (alapértelmezetten ez a thin) + -x # a mutex lock bekapcsolása (alapértelmezetten ki van kapcsolva) + +## Több szálon futtatás + +_Parafrázis [Konstantin StackOverflow válasza][so-answer] alapján_ + +A Sinatra nem szabja meg az konkurenciakezelés módját, hanem az alatta működő +Rack kezelőre (szerverre) hagyja ezt a feladatot, ami például a Thin, a Puma, +vagy a WEBrick. A Sinatra önmagában szálbiztos, tehát semmilyen probléma sem +adódik, ha a Rack kezelő többszálú konkurenciamodellt használ. Ezek szerint +szerverindításkor meg kell adni a Rack szervernek megfelelő indítási módot. +A következő példa egy többszálú Thin szerver indítását mutatja be. + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +A szerverindítás parancsa a következő lenne: + +``` shell +thin --threaded start +``` + +[so-answer]: http://stackoverflow.com/a/6282999/1725341 + +## Fejlesztői változat + +Ha a Sinatra legfrissebb, fejlesztői változatát szeretnéd használni, +készíts egy helyi másolatot és indítsd az alkalmazásodat úgy, +hogy a `sinatra/lib` könyvtár elérhető legyen a +`LOAD_PATH`-on: + +``` + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb +``` + +De hozzá is adhatod a sinatra/lib könyvtárat a LOAD_PATH-hoz +az alkalmazásodban: + +```ruby + $LOAD_PATH.unshift __dir__ + '/sinatra/lib' + require 'rubygems' + require 'sinatra' + + get '/about' do + "A következő változatot futtatom " + Sinatra::VERSION + end +``` + +A Sinatra frissítését később így végezheted el: + +``` + cd myproject/sinatra + git pull +``` + +## További információk + +* [A projekt weboldala](http://www.sinatrarb.com/) - Kiegészítő dokumentáció, + hírek, hasznos linkek +* [Közreműködés](http://www.sinatrarb.com/contributing.html) - Hibát találtál? + Segítségre van szükséged? Foltot küldenél be? +* [Lighthouse](http://sinatra.lighthouseapp.com) - Hibakövetés és kiadások +* [Twitter](https://twitter.com/sinatra) +* [Levelezőlista](http://groups.google.com/group/sinatrarb) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) a http://freenode.net címen diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ja.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ja.md new file mode 100644 index 0000000000..33902102d9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ja.md @@ -0,0 +1,2844 @@ +# Sinatra + +*注) +本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照してください。* + +Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るための[DSL](https://ja.wikipedia.org/wiki/メインページドメイン固有言語)です。 + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +gemをインストールし、 + +```shell +gem install sinatra +``` + +次のように実行します。 + +```shell +ruby myapp.rb +``` + +[http://localhost:4567](http://localhost:4567) を開きます。 + +コードを変更しても、サーバを再起動しないと変更が有効になりません。 +コードを変更するたびにサーバを再起動するか、[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader)を使ってください。 + +PumaがあればSinatraはこれを利用するので、`gem install puma`することをお薦めします。 + +## 目次 + +* [Sinatra](#sinatra) + * [目次](#目次) + * [ルーティング(Routes)](#ルーティングroutes) + * [条件(Conditions)](#条件conditions) + * [戻り値(Return Values)](#戻り値return-values) + * [カスタムルーティングマッチャー(Custom Route Matchers)](#カスタムルーティングマッチャーcustom-route-matchers) + * [静的ファイル(Static Files)](#静的ファイルstatic-files) + * [ビュー / テンプレート(Views / Templates)](#ビュー--テンプレートviews--templates) + * [リテラルテンプレート(Literal Templates)](#リテラルテンプレートliteral-templates) + * [利用可能なテンプレート言語](#利用可能なテンプレート言語) + * [Haml テンプレート](#haml-テンプレート) + * [Erb テンプレート](#erb-テンプレート) + * [Builder テンプレート](#builder-テンプレート) + * [Nokogiri テンプレート](#nokogiri-テンプレート) + * [Sass テンプレート](#sass-テンプレート) + * [SCSS テンプレート](#scss-テンプレート) + * [Less テンプレート](#less-テンプレート) + * [Liquid テンプレート](#liquid-テンプレート) + * [Markdown テンプレート](#markdown-テンプレート) + * [Textile テンプレート](#textile-テンプレート) + * [RDoc テンプレート](#rdoc-テンプレート) + * [AsciiDoc テンプレート](#asciidoc-テンプレート) + * [Radius テンプレート](#radius-テンプレート) + * [Markaby テンプレート](#markaby-テンプレート) + * [RABL テンプレート](#rabl-テンプレート) + * [Slim テンプレート](#slim-テンプレート) + * [Creole テンプレート](#creole-テンプレート) + * [MediaWiki テンプレート](#mediawiki-テンプレート) + * [CoffeeScript テンプレート](#coffeescript-テンプレート) + * [Stylus テンプレート](#stylus-テンプレート) + * [Yajl テンプレート](#yajl-テンプレート) + * [WLang テンプレート](#wlang-テンプレート) + * [テンプレート内での変数へのアクセス](#テンプレート内での変数へのアクセス) + * [`yield`を伴うテンプレートとネストしたレイアウト](#yieldを伴うテンプレートとネストしたレイアウト) + * [インラインテンプレート(Inline Templates)](#インラインテンプレートinline-templates) + * [名前付きテンプレート(Named Templates)](#名前付きテンプレートnamed-templates) + * [ファイル拡張子の関連付け](#ファイル拡張子の関連付け) + * [オリジナルテンプレートエンジンの追加](#オリジナルテンプレートエンジンの追加) + * [カスタムロジックを使用したテンプレートの探索](#カスタムロジックを使用したテンプレートの探索) + * [フィルタ(Filters)](#フィルタfilters) + * [ヘルパー(Helpers)](#ヘルパーhelpers) + * [セッションの使用](#セッションの使用) + * [セッションミドルウェアの選択](#セッションミドルウェアの選択) + * [停止(Halting)](#停止halting) + * [パッシング(Passing)](#パッシングpassing) + * [別ルーティングの誘発](#別ルーティングの誘発) + * [ボディ、ステータスコードおよびヘッダの設定](#ボディステータスコードおよびヘッダの設定) + * [ストリーミングレスポンス(Streaming Responses)](#ストリーミングレスポンスstreaming-responses) + * [ロギング(Logging)](#ロギングlogging) + * [MIMEタイプ(Mime Types)](#mimeタイプmime-types) + * [URLの生成](#urlの生成) + * [ブラウザリダイレクト(Browser Redirect)](#ブラウザリダイレクトbrowser-redirect) + * [キャッシュ制御(Cache Control)](#キャッシュ制御cache-control) + * [ファイルの送信](#ファイルの送信) + * [リクエストオブジェクトへのアクセス](#リクエストオブジェクトへのアクセス) + * [アタッチメント(Attachments)](#アタッチメントattachments) + * [日付と時刻の取り扱い](#日付と時刻の取り扱い) + * [テンプレートファイルの探索](#テンプレートファイルの探索) + * [コンフィギュレーション(Configuration)](#コンフィギュレーションconfiguration) + * [攻撃防御に対する設定](#攻撃防御に対する設定) + * [利用可能な設定](#利用可能な設定) + * [環境設定(Environments)](#環境設定environments) + * [エラーハンドリング(Error Handling)](#エラーハンドリングerror-handling) + * [Not Found](#not-found) + * [エラー(Error)](#エラーerror) + * [Rackミドルウェア(Rack Middleware)](#rackミドルウェアrack-middleware) + * [テスト(Testing)](#テストtesting) + * [Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ](#sinatrabase---ミドルウェアライブラリおよびモジュラーアプリ) + * [モジュラースタイル vs クラッシックスタイル](#モジュラースタイル-vs-クラッシックスタイル) + * [モジュラーアプリケーションの提供](#モジュラーアプリケーションの提供) + * [config.ruを用いたクラッシックスタイルアプリケーションの使用](#configruを用いたクラッシックスタイルアプリケーションの使用) + * [config.ruはいつ使うのか?](#configruはいつ使うのか) + * [Sinatraのミドルウェアとしての利用](#sinatraのミドルウェアとしての利用) + * [動的なアプリケーションの生成](#動的なアプリケーションの生成) + * [スコープとバインディング(Scopes and Binding)](#スコープとバインディングscopes-and-binding) + * [アプリケーション/クラスのスコープ](#アプリケーションクラスのスコープ) + * [リクエスト/インスタンスのスコープ](#リクエストインスタンスのスコープ) + * [デリゲートスコープ](#デリゲートスコープ) + * [コマンドライン](#コマンドライン) + * [マルチスレッド](#マルチスレッド) + * [必要環境](#必要環境) + * [最新開発版](#最新開発版) + * [Bundlerを使う場合](#bundlerを使う場合) + * [直接組み込む場合](#直接組み込む場合) + * [グローバル環境にインストールする場合](#グローバル環境にインストールする場合) + * [バージョニング(Versioning)](#バージョニングversioning) + * [参考文献](#参考文献) + +## ルーティング(Routes) + +Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。 +ルーティングはブロックに結び付けられています。 + +```ruby +get '/' do + .. 何か見せる .. +end + +post '/' do + .. 何か生成する .. +end + +put '/' do + .. 何か更新する .. +end + +patch '/' do + .. 何か修正する .. +end + +delete '/' do + .. 何か削除する .. +end + +options '/' do + .. 何か満たす .. +end + +link '/' do + .. 何かリンクを張る .. +end + +unlink '/' do + .. 何かアンリンクする .. +end +``` + +ルーティングは定義された順番にマッチします。 +リクエストに最初にマッチしたルーティングが呼び出されます。 + +トレイリングスラッシュを付けたルートは、そうでないルートと異なったものになります。 + +```ruby + get '/foo' do + # Does not match "GET /foo/" + end +``` + +ルーティングのパターンは名前付きパラメータを含むことができ、 +`params`ハッシュで取得できます。 + +```ruby +get '/hello/:name' do + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + "Hello #{params['name']}!" +end +``` + +また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。 + +```ruby +get '/hello/:name' do |n| + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + # n が params['name'] を保持 + "Hello #{n}!" +end +``` + +ルーティングパターンはアスタリスク(すなわちワイルドカード)を含むこともでき、 +`params['splat']` で取得できます。 + +```ruby +get '/say/*/to/*' do + # /say/hello/to/world にマッチ + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml にマッチ + params['splat'] # => ["path/to/file", "xml"] +end +``` + +ここで、ブロックパラメータを使うこともできます。 + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +ルーティングを正規表現にマッチさせることもできます。 + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +ここでも、ブロックパラメータが使えます。 + +```ruby +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +ルーティングパターンは、オプショナルパラメータを取ることもできます。 + +```ruby +get '/posts/:format?' do + # "GET /posts/" と "GET /posts/json", "GET /posts/xml" の拡張子などにマッチ +end +``` + +ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、 +ルーティングにマッチする前にリクエストパスが修正される可能性があります。 + +## 条件(Conditions) + +ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。 + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbirdのバージョン #{params['agent'][0]}を使ってます。" +end + +get '/foo' do + # Songbird以外のブラウザにマッチ +end +``` + +ほかに`host_name`と`provides`条件が利用可能です。 + +```ruby +get '/', :host_name => /^admin\./ do + "Adminエリアです。アクセスを拒否します!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +独自の条件を定義することも簡単にできます。 + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "あなたの勝ちです!" +end + +get '/win_a_car' do + "残念、あなたの負けです。" +end +``` + +複数の値を取る条件には、アスタリスクを使います。 + +```ruby +set(:auth) do |*roles| # <- ここでアスタリスクを使う + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "アカウントの詳細" +end + +get "/only/admin/", :auth => :admin do + "ここは管理者だけ!" +end +``` + +## 戻り値(Return Values) + +ルーティングブロックの戻り値は、HTTPクライアントまたはRackスタックでの次のミドルウェアに渡されるレスポンスボディを決定します。 + +これは大抵の場合、上の例のように文字列ですが、それ以外の値も使用することができます。 + +Rackレスポンス、Rackボディオブジェクト、HTTPステータスコードのいずれかとして妥当なオブジェクトであればどのようなオブジェクトでも返すことができます。 + +* 3つの要素を含む配列: + `[ステータス(Integer), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]` +* 2つの要素を含む配列: + `[ステータス(Integer), レスポンスボディ(#eachに応答する)]` +* `#each`に応答するオブジェクト。通常はそのまま何も返さないが、 +与えられたブロックに文字列を渡す。 +* ステータスコードを表現する整数(Integer) + +これにより、例えばストリーミングを簡単に実装することができます。 + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +後述する`stream`ヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。 + +## カスタムルーティングマッチャー(Custom Route Matchers) + +先述のようにSinatraはルーティングマッチャーとして、文字列パターンと正規表現を使うことをビルトインでサポートしています。しかしこれに留まらず、独自のマッチャーを簡単に定義することもできるのです。 + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +ノート: この例はオーバースペックであり、以下のようにも書くことができます。 + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +または、否定先読みを使って: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 静的ファイル(Static Files) + +静的ファイルは`./public`ディレクトリから配信されます。 +`:public_folder`オプションを指定することで別の場所を指定することができます。 + +```ruby +set :public_folder, __dir__ + '/static' +``` + +ノート: この静的ファイル用のディレクトリ名はURL中に含まれません。 +例えば、`./public/css/style.css`は`http://example.com/css/style.css`でアクセスできます。 + +`Cache-Control`の設定をヘッダーへ追加するには`:static_cache_control`の設定(下記参照)を加えてください。 + +## ビュー / テンプレート(Views / Templates) + +各テンプレート言語はそれ自身のレンダリングメソッドを通して展開されます。それらのメソッドは単に文字列を返します。 + +```ruby +get '/' do + erb :index +end +``` + +これは、`views/index.erb`をレンダリングします。 + +テンプレート名を渡す代わりに、直接そのテンプレートの中身を渡すこともできます。 + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。 + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +これは、`views/post.erb`内に埋め込まれた`views/index.erb`をレンダリングします(デフォルトは`views/layout.erb`があればそれになります)。 + +Sinatraが理解できないオプションは、テンプレートエンジンに渡されることになります。 + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +テンプレート言語ごとにオプションをセットすることもできます。 + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +レンダリングメソッドに渡されたオプションは`set`で設定されたオプションを上書きします。 + +利用可能なオプション: + +
    +
    locals
    +
    + ドキュメントに渡されるローカルのリスト。パーシャルに便利。 + 例: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + 文字エンコーディングが確実でない場合に指定。デフォルトは、settings.default_encoding。 +
    + +
    views
    +
    + テンプレートを読み出すビューのディレクトリ。デフォルトは、settings.views。 +
    + +
    layout
    +
    + レイアウトを使うかの指定(true または false)。値がシンボルの場合は、使用するテンプレートが指定される。例: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + テンプレートが生成するContent-Type。デフォルトはテンプレート言語ごとに異なる。 +
    + +
    scope
    +
    + テンプレートをレンダリングするときのスコープ。デフォルトは、アプリケーションのインスタンス。これを変更した場合、インスタンス変数およびヘルパーメソッドが利用できなくなる。 +
    + +
    layout_engine
    +
    + レイアウトをレンダリングするために使用するテンプレートエンジン。レイアウトをサポートしない言語で有用。デフォルトはテンプレートに使われるエンジン。例: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + レイアウトをレンダリングするときだけに使う特別なオプション。例: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +テンプレートは`./views`ディレクトリ下に配置されています。 +他のディレクトリを使用する場合の例: + +```ruby +set :views, settings.root + '/templates' +``` + +テンプレートの参照は、テンプレートがサブディレクトリ内にある場合でも常にシンボルで指定することを覚えておいてください。 +(これは`:'subdir/template'`または`'subdir/template'.to_sym`のように指定することを意味します。) +レンダリングメソッドにシンボルではなく文字列を渡してしまうと、そのまま文字列として出力してしまいます。 + +### リテラルテンプレート(Literal Templates) + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +これはテンプレート文字列をレンダリングしています。 +テンプレート文字列に関連するファイルパスや行数を`:path`や`:line`オプションで指定することで、バックトレースを明確にすることができます。 + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### 利用可能なテンプレート言語 + +いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。 + +```ruby +require 'rdiscount' # または require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml テンプレート + + + + + + + + + + + + + + +
    依存haml
    ファイル拡張子.haml
    haml :index, :format => :html5
    + +#### Erb テンプレート + + + + + + + + + + + + + + +
    依存 + erubi + または erubis + または erb (Rubyに同梱) +
    ファイル拡張子.erb, .rhtml または .erubi (Erubiだけ) または.erubis (Erubisだけ)
    erb :index
    + +#### Builder テンプレート + + + + + + + + + + + + + + +
    依存 + builder +
    ファイル拡張子.builder
    builder { |xml| xml.em "hi" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### Nokogiri テンプレート + + + + + + + + + + + + + + +
    依存nokogiri
    ファイル拡張子.nokogiri
    nokogiri { |xml| xml.em "hi" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### Sass テンプレート + + + + + + + + + + + + + + +
    依存sass
    ファイル拡張子.sass
    sass :stylesheet, :style => :expanded
    + +#### Scss テンプレート + + + + + + + + + + + + + + +
    依存sass
    ファイル拡張子.scss
    scss :stylesheet, :style => :expanded
    + +#### Less テンプレート + + + + + + + + + + + + + + +
    依存less
    ファイル拡張子.less
    less :stylesheet
    + +#### Liquid テンプレート + + + + + + + + + + + + + + +
    依存liquid
    ファイル拡張子.liquid
    liquid :index, :locals => { :key => 'value' }
    + +LiquidテンプレートからRubyのメソッド(`yield`を除く)を呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Markdown テンプレート + + + + + + + + + + + + + + +
    依存 + 次の何れか: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    ファイル拡張子.markdown, .mkd and .md
    markdown :index, :layout_engine => :erb
    + +Markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +ノート: 他のテンプレート内で`markdown`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### Textile テンプレート + + + + + + + + + + + + + + +
    依存RedCloth
    ファイル拡張子.textile
    textile :index, :layout_engine => :erb
    + +Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +ノート: 他のテンプレート内で`textile`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +TextileからはRubyを呼ぶことができないので、Textileで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### RDoc テンプレート + + + + + + + + + + + + + + +
    依存RDoc
    ファイル拡張子.rdoc
    rdoc :README, :layout_engine => :erb
    + +RDocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +ノート: 他のテンプレート内で`rdoc`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDocからはRubyを呼ぶことができないので、RDocで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### AsciiDoc テンプレート + + + + + + + + + + + + + + +
    依存Asciidoctor
    ファイル拡張子.asciidoc, .adoc and .ad
    asciidoc :README, :layout_engine => :erb
    + +AsciiDocテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Radius テンプレート + + + + + + + + + + + + + + +
    依存Radius
    ファイル拡張子.radius
    radius :index, :locals => { :key => 'value' }
    + +RadiusテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Markaby テンプレート + + + + + + + + + + + + + + +
    依存Markaby
    ファイル拡張子.mab
    markaby { h1 "Welcome!" }
    + +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### RABL テンプレート + + + + + + + + + + + + + + +
    依存Rabl
    ファイル拡張子.rabl
    rabl :index
    + +#### Slim テンプレート + + + + + + + + + + + + + + +
    依存Slim Lang
    ファイル拡張子.slim
    slim :index
    + +#### Creole テンプレート + + + + + + + + + + + + + + +
    依存Creole
    ファイル拡張子.creole
    creole :wiki, :layout_engine => :erb
    + +Creoleからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +ノート: 他のテンプレート内で`creole`メソッドを呼び出せます。 + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +CreoleからはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### MediaWiki テンプレート + + + + + + + + + + + + + + +
    依存WikiCloth
    ファイル拡張子.mediawiki および .mw
    mediawiki :wiki, :layout_engine => :erb
    + +MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。 + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +ノート: 他のテンプレートから部分的に`mediawiki`メソッドを呼び出すことも可能です。 + +#### CoffeeScript テンプレート + + + + + + + + + + + + + + +
    依存 + + CoffeeScript + および + + JavaScriptの起動方法 + +
    ファイル拡張子.coffee
    coffee :index
    + +#### Stylus テンプレート + + + + + + + + + + + + + + +
    依存 + + Stylus + および + + JavaScriptの起動方法 + +
    ファイル拡張子.styl
    stylus :index
    + +Stylusテンプレートを使えるようにする前に、まず`stylus`と`stylus/tilt`を読み込む必要があります。 + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl テンプレート + + + + + + + + + + + + + + +
    依存yajl-ruby
    ファイル拡張子.yajl
    + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +テンプレートのソースはRubyの文字列として評価され、その結果のJSON変数は`#to_json`を使って変換されます。 + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`および`:variable`オプションは、レンダリングされたオブジェクトを装飾するために使うことができます。 + +```ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +#### WLang テンプレート + + + + + + + + + + + + + + +
    依存wlang
    ファイル拡張子.wlang
    wlang :index, :locals => { :key => 'value' }
    + +WLang内でのRubyメソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトは`yield`をサポートしています。 + +### テンプレート内での変数へのアクセス + +テンプレートはルーティングハンドラと同じコンテキストの中で評価されます。ルーティングハンドラでセットされたインスタンス変数はテンプレート内で直接使うことができます。 + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +また、ローカル変数のハッシュで明示的に指定することもできます。 + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +これは他のテンプレート内で部分テンプレートとして表示する典型的な手法です。 + +### `yield`を伴うテンプレートとネストしたレイアウト + +レイアウトは通常、`yield`を呼ぶ単なるテンプレートに過ぎません。 +そのようなテンプレートは、既に説明した`:template`オプションを通して使われるか、または次のようなブロックを伴ってレンダリングされます。 + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +このコードは、`erb :index, :layout => :post`とほぼ等価です。 + +レンダリングメソッドにブロックを渡すスタイルは、ネストしたレイアウトを作るために最も役立ちます。 + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +これはまた次のより短いコードでも達成できます。 + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +現在、次のレンダリングメソッドがブロックを取れます: `erb`, `haml`, +`liquid`, `slim `, `wlang`。 +また汎用の`render`メソッドもブロックを取れます。 + +### インラインテンプレート(Inline Templates) + +テンプレートはソースファイルの最後で定義することもできます。 + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world!!!!! +``` + +ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合には`enable :inline_templates`を明示的に呼んでください。 + +### 名前付きテンプレート(Named Templates) + +テンプレートはトップレベルの`template`メソッドで定義することもできます。 + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +「layout」という名前のテンプレートが存在する場合は、そのテンプレートファイルは他のテンプレートがレンダリングされる度に使用されます。`:layout => false`で個別に、または`set :haml, :layout => false`でデフォルトとして、レイアウトを無効にすることができます。 + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### ファイル拡張子の関連付け + +任意のテンプレートエンジンにファイル拡張子を関連付ける場合は、`Tilt.register`を使います。例えば、Textileテンプレートに`tt`というファイル拡張子を使いたい場合は、以下のようにします。 + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### オリジナルテンプレートエンジンの追加 + +まず、Tiltでそのエンジンを登録し、次にレンダリングメソッドを作ります。 + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +これは、`./views/index.myat`をレンダリングします。Tiltについての詳細は、https://github.com/rtomayko/tilt を参照してください。 + +### カスタムロジックを使用したテンプレートの探索 + +オリジナルテンプレートの検索メカニズムを実装するためには、`#find_template`メソッドを実装します。 + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## フィルタ(Filters) + +beforeフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの前に評価され、それによってリクエストとレスポンスを変更可能にします。フィルタ内でセットされたインスタンス変数はルーティングとテンプレートからアクセスすることができます。 + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +afterフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの後に評価され、それによってこれもリクエストとレスポンスを変更可能にします。beforeフィルタとルーティング内でセットされたインスタンス変数はafterフィルタからアクセスすることができます。 + +```ruby +after do + puts response.status +end +``` + +ノート: `body`メソッドを使わずにルーティングから文字列を返すだけの場合、その内容はafterフィルタでまだ利用できず、その後に生成されることになります。 + +フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。 + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +ルーティング同様、フィルタもまた条件を取ることができます。 + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## ヘルパー(Helpers) + +トップレベルの`helpers`メソッドを使用してルーティングハンドラやテンプレートで使うヘルパーメソッドを定義できます。 + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +あるいは、ヘルパーメソッドをモジュール内で個別に定義することもできます。 + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。 + +### セッションの使用 + +セッションはリクエスト間での状態維持のために使用されます。セッションを有効化すると、ユーザセッションごとに一つのセッションハッシュが与えられます。 + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +ノート: `enable :sessions`は実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合は`enable :sessions`を呼ばずに、選択したミドルウェアを他のミドルウェアのときと同じようにして取り込んでください。 + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +セキュリティ向上のため、クッキー内のセッションデータはセッション秘密鍵(session secret)で署名されます。Sinatraによりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。 + +```ruby +set :session_secret, 'super secret' +``` + +更に、設定変更をしたい場合は、`sessions`の設定においてオプションハッシュを保持することもできます。 + +```ruby +set :sessions, :domain => 'foo.com' +``` + +foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に *.* を付けます。 + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### セッションミドルウェアの選択 + +`enable :sessions`とすることで、クッキー内の全てのデータを実際に保存してしまうことに注意してください。 +これは、あなたが望む挙動ではない(例えば、大量のデータを保存することでトラフィックが増大してしまう)かもしれません。 +あなたは、次のいずれかの方法によって、任意のRackセッションミドルウェアを使用することができます。 + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +オプションのハッシュを設定するためには、次のようにします。 + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +他の方法は`enable :sessions`を**しない**で、他のミドルウェアの選択と同様にあなた自身でミドルウェアを選択することです。 + +この方法を選択する場合は、セッションベースの保護は**デフォルトで有効にならない**ということに注意することが重要です。 + +これを満たすためのRackミドルウェアを追加することが必要になります。 + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +より詳しい情報は、「攻撃防御に対する設定」の項を参照してください。 + +### 停止(Halting) + +フィルタまたはルーティング内で直ちにリクエストを止める場合 + +```ruby +halt +``` + +この際、ステータスを指定することもできます。 + +```ruby +halt 410 +``` + +body部を指定することも、 + +```ruby +halt 'ここにbodyを書く' +``` + +ステータスとbody部を指定することも、 + +```ruby +halt 401, '立ち去れ!' +``` + +ヘッダを付けることもできます。 + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ' +``` + +もちろん、テンプレートを`halt`に結びつけることも可能です。 + +```ruby +halt erb(:error) +``` + +### パッシング(Passing) + +ルーティングは`pass`を使って次のルーティングに飛ばすことができます。 + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + "見つかっちゃった!" +end + +get '/guess/*' do + "はずれです!" +end +``` + +ルーティングブロックからすぐに抜け出し、次にマッチするルーティングを実行します。マッチするルーティングが見当たらない場合は404が返されます。 + +### 別ルーティングの誘発 + +`pass`を使ってルーティングを飛ばすのではなく、他のルーティングを呼んだ結果を得たいという場合があります。 +これは`call`を使用することで実現できます。 + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +ノート: 先の例において、テストを楽にしパフォーマンスを改善するには、`"bar"`を単にヘルパーに移し、`/foo`および`/bar`から使えるようにしたほうが良いです。 + +リクエストが、その複製物でない同じアプリケーションのインスタンスに送られるようにしたいときは、`call`に代えて`call!`を使ってください。 + +`call`についての詳細はRackの仕様を参照してください。 + +### ボディ、ステータスコードおよびヘッダの設定 + +ステータスコードおよびレスポンスボディを、ルーティングブロックの戻り値にセットすることが可能であり、これは推奨されています。しかし、あるケースでは実行フローの任意のタイミングでボディをセットしたくなるかもしれません。`body`ヘルパーメソッドを使えばそれができます。そうすると、それ以降、ボディにアクセスするためにそのメソッドを使うことができるようになります。 + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +また、`body`にはブロックを渡すことができ、これはRackハンドラにより実行されることになります(これはストリーミングを実装するのに使われます。"戻り値"の項を参照してください。) + +ボディと同様に、ステータスコードおよびヘッダもセットできます。 + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +引数を伴わない`body`、`headers`、`status`などは、それらの現在の値にアクセスするために使えます。 + +### ストリーミングレスポンス(Streaming Responses) + +レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。`stream`ヘルパーを使えば、独自ラッパーを作る必要はありません。 + +```ruby +get '/' do + stream do |out| + out << "それは伝 -\n" + sleep 0.5 + out << " (少し待つ) \n" + sleep 1 + out << "- 説になる!\n" + end +end +``` + +これはストリーミングAPI、[Server Sent Events](https://w3c.github.io/eventsource/)の実装を可能にし、[WebSockets](https://en.wikipedia.org/wiki/WebSocket)の土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。 + +ノート: ストリーミングの挙動、特に並行リクエスト(concurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。いくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディは`stream`に渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。 + +オプション引数が`keep_open`にセットされている場合、ストリームオブジェクト上で`close`は呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。 + +```ruby +# config.ru +require 'sinatra/base' + +class App < Sinatra::Base + connections = [] + + get '/subscribe' do + # register a client's interest in server events + # サーバイベントにおけるクライアントの関心を登録 + stream(:keep_open) do |out| + connections << out + # 死んでいるコネクションを排除 + connections.reject!(&:closed?) + end + end + + post '/:message' do + connections.each do |out| + # クライアントへ新規メッセージ到着の通知 + out << params['message'] << "\n" + + # クライアントへの再接続の指示 + out.close + end + + # 肯定応答 + "message received" + end +end + +run App +``` + +```ruby +# rainbows.conf +Rainbows! do + use :EventMachine +end +```` + +次のように起動します。 + +```shell +rainbows -c rainbows.conf +``` + +クライアントはソケットに書き込もうとしている接続を閉じることも可能です。そのため、記述しようとする前に`out.closed?`をチェックすることを勧めます。 + +### ロギング(Logging) + +リクエストスコープにおいて、`logger`ヘルパーは`Logger`インスタンスを作り出します。 + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。 + +ノート: ロギングは、`Sinatra::Application`に対してのみデフォルトで有効にされているので、`Sinatra::Base`を継承している場合は、ユーザがこれを有効化する必要があります。 + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +ロギングミドルウェアが設定されてしまうのを避けるには、`logging`設定を`nil`にセットします。しかしこの場合、`logger`が`nil`を返すことを忘れないように。よくあるユースケースは、オリジナルのロガーをセットしたいときです。Sinatraは、とにかく`env['rack.logger']`で見つかるものを使います。 + +### MIMEタイプ(Mime Types) + +`send_file`か静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は `mime_type` を使ってファイル拡張子毎に登録してください。 + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +これは`content_type`ヘルパーで利用することができます: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLの生成 + +URLを生成するためには`url`ヘルパーメソッドが使えます。Hamlではこのようにします。 + +```ruby +%a{:href => url('/foo')} foo +``` + +これはリバースプロキシおよびRackルーティングを、それらがあれば考慮に入れます。 + +このメソッドには`to`というエイリアスがあります(以下の例を参照)。 + +### ブラウザリダイレクト(Browser Redirect) + +`redirect` ヘルパーメソッドを使うことで、ブラウザをリダイレクトさせることができます。 + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +他に追加されるパラメータは、`halt`に渡される引数と同様に取り扱われます。 + +```ruby +redirect to('/bar'), 303 +redirect 'https://www.google.com/', 'wrong place, buddy' +``` + +また、`redirect back`を使えば、簡単にユーザが来たページへ戻るリダイレクトを作れます。 + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +redirectに引数を渡すには、それをクエリーに追加するか、 + +```ruby +redirect to('/bar?sum=42') +``` + +または、セッションを使います。 + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### キャッシュ制御(Cache Control) + +ヘッダを正しく設定することが、適切なHTTPキャッシングのための基礎となります。 + +キャッシュ制御ヘッダ(Cache-Control header)は、次のように簡単に設定できます。 + +```ruby +get '/' do + cache_control :public + "キャッシュしました!" +end +``` + +ヒント: キャッシングをbeforeフィルタ内で設定します。 + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires`ヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。 + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +キャッシュを適切に使うために、`etag`または`last_modified`を使うことを検討してください。これらのヘルパーを、重い仕事をさせる *前* に呼ぶことを推奨します。そうすれば、クライアントが既にキャッシュに最新版を持っている場合はレスポンスを直ちに破棄するようになります。 + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +また、[weak ETag](https://ja.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)を使うこともできます。 + +```ruby +etag @article.sha1, :weak +``` + +これらのヘルパーは、キャッシングをしてくれませんが、必要な情報をキャッシュに与えてくれます。もし手早いリバースプロキシキャッシングの解決策をお探しなら、 [rack-cache](https://github.com/rtomayko/rack-cache)を試してください。 + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +`:static_cache_control`設定(以下を参照)を、キャッシュ制御ヘッダ情報を静的ファイルに追加するために使ってください。 + +RFC 2616によれば、アプリケーションは、If-MatchまたはIf-None-Matchヘッダが`*`に設定されている場合には、要求されたリソースが既に存在するか否かに応じて、異なる振る舞いをすべきとなっています。Sinatraは、getのような安全なリクエストおよびputのような冪等なリクエストは既に存在しているものとして仮定し、一方で、他のリソース(例えば、postリクエスト)は新たなリソースとして取り扱われるよう仮定します。この振る舞いは、`:new_resource`オプションを渡すことで変更できます。 + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +ここでもWeak ETagを使いたい場合は、`:kind`オプションを渡してください。 + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### ファイルの送信 + +ファイルを送信するには、`send_file`ヘルパーメソッドを使います。 + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +これはオプションを取ることもできます。 + +```ruby +send_file 'foo.png', :type => :jpg +``` + +オプション一覧 + +
    +
    filename
    +
    ファイル名。デフォルトは実際のファイル名。
    + +
    last_modified
    +
    Last-Modifiedヘッダの値。デフォルトはファイルのmtime。
    + +
    type
    +
    コンテンツの種類。設定がない場合、ファイル拡張子から類推される。
    + +
    disposition
    +
    + Content-Dispositionに使われる。許容値: nil (デフォルト)、 + :attachment および :inline +
    + +
    length
    +
    Content-Lengthヘッダ。デフォルトはファイルサイズ。
    + +
    status
    +
    + 送られるステータスコード。静的ファイルをエラーページとして送るときに便利。 + + Rackハンドラでサポートされている場合は、Rubyプロセスからのストリーミング以外の手段が使われる。このヘルパーメソッドを使うと、Sinatraは自動で範囲リクエスト(range requests)を扱う。 +
    +
    + +### リクエストオブジェクトへのアクセス + +受信するリクエストオブジェクトは、`request`メソッドを通じてリクエストレベル(フィルタ、ルーティング、エラーハンドラ)からアクセスすることができます。 + +```ruby +# アプリケーションが http://example.com/example で動作している場合 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # クライアントによって送信されたリクエストボディ(下記参照) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.bodyの長さ + request.media_type # request.bodyのメディアタイプ + request.host # "example.com" + request.get? # true (他の動詞にも同種メソッドあり) + request.form_data? # false + request["some_param"] # some_param変数の値。[]はパラメータハッシュのショートカット + request.referrer # クライアントのリファラまたは'/' + request.user_agent # ユーザエージェント (:agent 条件によって使用される) + request.cookies # ブラウザクッキーのハッシュ + request.xhr? # Ajaxリクエストかどうか + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # クライアントのIPアドレス + request.secure? # false (sslではtrueになる) + request.forwarded? # true (リバースプロキシの裏で動いている場合) + request.env # Rackによって渡された生のenvハッシュ +end +``` + +`script_name`や`path_info`などのオプションは次のように利用することもできます。 + +```ruby +before { request.path_info = "/" } + +get "/" do + "全てのリクエストはここに来る" +end +``` + +`request.body`はIOまたはStringIOのオブジェクトです。 + +```ruby +post "/api" do + request.body.rewind # 既に読まれているときのため + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### アタッチメント(Attachments) + +`attachment`ヘルパーを使って、レスポンスがブラウザに表示されるのではなく、ディスクに保存されることをブラウザに対し通知することができます。 + +```ruby +get '/' do + attachment + "保存しました!" +end +``` + +ファイル名を渡すこともできます。 + +```ruby +get '/' do + attachment "info.txt" + "保存しました!" +end +``` + +### 日付と時刻の取り扱い + +Sinatraは`time_for`ヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまた`DateTime`、`Date`および類似のクラスを変換できます。 + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "まだ時間がある" +end +``` + +このメソッドは、`expires`、`last_modified`といった種類のものの内部で使われています。そのため、アプリケーションにおいて、`time_for`をオーバーライドすることでそれらのメソッドの挙動を簡単に拡張できます。 + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### テンプレートファイルの探索 + +`find_template`ヘルパーは、レンダリングのためのテンプレートファイルを見つけるために使われます。 + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +この例はあまり有益ではありません。しかし、このメソッドを、独自の探索機構で働くようオーバーライドするなら有益になります。例えば、複数のビューディレクトリを使えるようにしたいときがあります。 + + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +他の例としては、異なるエンジン用の異なるディレクトリを使う場合です。 + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +これをエクステンションとして書いて、他の人と簡単に共有することもできます! + +ノート: `find_template`はファイルが実際に存在するかのチェックをしませんが、与えられたブロックをすべての可能なパスに対し呼び出します。これがパフォーマンス上の問題にはならないのは、`render`はファイルを見つけると直ちに`break`を使うからです。また、テンプレートの場所(および内容)は、developmentモードでの起動でない限りはキャッシュされます。このことは、複雑なメソッド(a really crazy method)を書いた場合は記憶しておく必要があります。 + +## コンフィギュレーション(Configuration) + +どの環境でも起動時に1回だけ実行されます。 + +```ruby +configure do + # 1つのオプションをセット + set :option, 'value' + + # 複数のオプションをセット + set :a => 1, :b => 2 + + # `set :option, true`と同じ + enable :option + + # `set :option, false`と同じ + disable :option + + # ブロックを使って動的な設定をすることもできます。 + set(:css_dir) { File.join(views, 'css') } +end +``` + +環境設定(`APP_ENV`環境変数)が`:production`に設定されている時だけ実行する方法: + +```ruby +configure :production do + ... +end +``` + +環境設定が`:production`か`:test`に設定されている時だけ実行する方法: + +```ruby +configure :production, :test do + ... +end +``` + +設定したオプションには`settings`からアクセスできます: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 攻撃防御に対する設定 + +Sinatraは[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)を使用することで、アプリケーションを一般的な日和見的攻撃から守っています。これは簡単に無効化できます(が、アプリケーションに大量の一般的な脆弱性を埋め込むことになってしまいます)。 + +```ruby +disable :protection +``` + +ある1つの防御を無効にするには、`protection`にハッシュでオプションを指定します。 + +```ruby +set :protection, :except => :path_traversal +``` + +配列を渡すことで、複数の防御を無効にすることもできます。 + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +デフォルトでSinatraは、`:sessions`が有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、`:session`オプションを渡すことにより、セッションベースの防御を設定することができます。 + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 利用可能な設定 + +
    +
    absolute_redirects
    +
    + 無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。 +
    +
    + アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。 +
    +
    デフォルトは無効。
    + +
    add_charset
    +
    + Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。 + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。 +
    + +
    bind
    +
    バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
    + +
    default_encoding
    +
    不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
    + +
    dump_errors
    +
    ログにおけるエラーの表示。
    + +
    environment
    +
    + 現在の環境。デフォルトはENV['APP_ENV']、それが無い場合は"development"。 +
    + +
    logging
    +
    ロガーの使用。
    + +
    lock
    +
    + 各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。 +
    +
    アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
    + +
    method_override
    +
    + put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。 +
    + +
    port
    +
    待ち受けポート。ビルトインサーバのみで有効。
    + +
    prefixed_redirects
    +
    + 絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。 +
    + +
    protection
    +
    Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
    + +
    public_dir
    +
    public_folderのエイリアス。以下を参照。
    + +
    public_folder
    +
    + publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。 +
    + +
    reload_templates
    +
    + リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。 +
    + +
    root
    +
    + プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。 +
    + +
    raise_errors
    +
    + 例外発生の設定(アプリケーションは止まる)。environment"test"に設定されているときはデフォルトは有効。それ以外は無効。 +
    + +
    run
    +
    + 有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。 +
    + +
    running
    +
    ビルトインサーバが稼働中か?この設定を変更しないこと!
    + +
    server
    +
    + ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。 +
    + +
    sessions
    +
    + Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。 +
    + +
    show_exceptions
    +
    + 例外発生時にブラウザにスタックトレースを表示する。environment"development"に設定されているときは、デフォルトで有効。それ以外は無効。 +
    +
    + また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。 +
    + +
    static
    +
    Sinatraが静的ファイルの提供を取り扱うかの設定。
    +
    その取り扱いができるサーバを使う場合は無効。
    +
    無効化でパフォーマンスは改善する
    +
    + クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。 +
    + +
    static_cache_control
    +
    + Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。 +
    +
    + 複数の値をセットするときは明示的に配列を使う: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + trueに設定されているときは、サーバにリクエストを処理するためにEventMachine.deferを使うことを通知する。 +
    + +
    views
    +
    + ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。 +
    + +
    x_cascade
    +
    + マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue。 +
    +
    + +## 環境設定(Environments) + +3種類の既定環境、`"development"`、`"production"`および`"test"`があります。環境は、`APP_ENV`環境変数を通して設定できます。デフォルト値は、`"development"`です。`"development"`環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別の`not_found`および`error`ハンドラがブラウザにスタックトレースを表示します。`"production"`および`"test"`環境においては、テンプレートはデフォルトでキャッシュされます。 + +異なる環境を走らせるには、`APP_ENV`環境変数を設定します。 + +```shell +APP_ENV=production ruby my_app.rb +``` + +既定メソッド、`development?`、`test?`および`production?`を、現在の環境設定を確認するために使えます。 + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## エラーハンドリング(Error Handling) + +エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、`haml`、`erb`、`halt`といった便利なものが全て使えることを意味します。 + +### 未検出(Not Found) + +`Sinatra::NotFound`例外が発生したとき、またはレスポンスのステータスコードが404のときに、`not_found`ハンドラが発動します。 + +```ruby +not_found do + 'ファイルが存在しません' +end +``` + +### エラー(Error) + +`error`ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。 +しかし、環境設定がdevelopmentの場合は`:after_handler`を設定している場合のみ発動するようになります。 + +```ruby +set :show_exceptions, :after_handler +``` + +例外オブジェクトはRack変数`sinatra.error`から取得できます。 + +```ruby +error do + 'エラーが発生しました。 - ' + env['sinatra.error'].message +end +``` + +エラーをカスタマイズする場合は、 + +```ruby +error MyCustomError do + 'エラーメッセージ...' + env['sinatra.error'].message +end +``` + +と書いておいて、下記のように呼び出します。 + +```ruby +get '/' do + raise MyCustomError, '何かがまずかったようです' +end +``` + +そうするとこうなります。 + +``` +エラーメッセージ... 何かがまずかったようです +``` + +あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。 + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +範囲指定もできます。 + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatraを開発環境の下で実行している場合は、特別な`not_found`および`error`ハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。 + +## Rackミドルウェア(Rack Middleware) + +SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースである[Rack](https://rack.github.io/)上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。 + +Sinatraはトップレベルの`use`メソッドを通して、Rackミドルウェアパイプラインの構築を楽にします。 + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`の文法は、[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder)DSLで定義されているそれ(rackupファイルで最もよく使われる)と同じです。例えば `use`メソッドは複数の引数、そしてブロックも取ることができます。 + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rackは、ロギング、デバッギング、URLルーティング、認証、セッション管理など、多様な標準的ミドルウェアを共に配布されています。Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、通常、それらを`use`で明示的に指定する必要はありません。 + +便利なミドルウェアを以下で見つけられます。 + +[rack](https://github.com/rack/rack/tree/master/lib/rack)、 +[rack-contrib](https://github.com/rack/rack-contrib#readm)、 +または[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)。 + +## テスト(Testing) + +SinatraでのテストはRackベースのテストライブラリまたはフレームワークを使って書くことができます。[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)をお薦めします。 + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbirdを使ってます!", last_response.body + end +end +``` + +ノート: モジュラースタイルでSinatraを使う場合は、上記`Sinatra::Application`をアプリケーションのクラス名に置き換えてください。 + +## Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ + +軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、`./public`および`./views`ディレクトリ、ロギング、例外詳細ページなど)を仮定しています。そこで`Sinatra::Base`の出番です。 + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base`のサブクラスで利用できるメソッドは、トップレベルDSLで利用できるものと全く同じです。ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することで`Sinatra::Base`コンポーネントに変えることができます。 + +* `sinatra`の代わりに`sinatra/base`を読み込む + (そうしない場合、SinatraのDSLメソッドの全てがmainの名前空間にインポートされます) +* ルーティング、エラーハンドラ、フィルタ、オプションを`Sinatra::Base`のサブクラスに書く + +`Sinatra::Base`はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルトで無効になっています。利用可能なオプションとその挙動の詳細については[Configuring Settings](http://www.sinatrarb.com/configuration.html)(英語)をご覧ください。 + +もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、`Sinatra::Application`をサブクラス化させてください。 + +```ruby +require "sinatra/base" + +class MyApp < Sinatra::Application + get "/" do + 'Hello world!' + end +end +``` + +### モジュラースタイル vs クラッシックスタイル + +一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。それがそのアプリケーションに合っているのであれば、モジュラーアプリケーションに移行する必要はありません。 + +モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。複数が必要な場合はモジュラースタイルに移行してください。モジュラースタイルとクラッシックスタイルを混合できないということはありません。 + +一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    設定クラッシックモジュラーモジュラー
    app_filesinatraを読み込むファイルSinatra::Baseをサブクラス化したファイルSinatra::Applicationをサブクラス化したファイル
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### モジュラーアプリケーションの提供 + +モジュラーアプリケーションを開始、つまり`run!`を使って開始させる二種類のやり方があります。 + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... アプリケーションのコードを書く ... + + # Rubyファイルが直接実行されたらサーバを立ち上げる + run! if app_file == $0 +end +``` + +として、次のように起動するか、 + +```shell +ruby my_app.rb +``` + +または、Rackハンドラを使えるようにする`config.ru`ファイルを書いて、 + +```ruby +# config.ru (rackupで起動) +require './my_app' +run MyApp +``` + +起動します。 + +```shell +rackup -p 4567 +``` + +### config.ruを用いたクラッシックスタイルアプリケーションの使用 + +アプリケーションファイルと、 + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +対応する`config.ru`を書きます。 + +```ruby +require './app' +run Sinatra::Application +``` + +### config.ruはいつ使うのか? + +`config.ru`ファイルは、以下の場合に適しています。 + +* 異なるRackハンドラ(Passenger, Unicorn, Herokuなど)でデプロイしたいとき +* `Sinatra::Base`の複数のサブクラスを使いたいとき +* Sinatraをミドルウェアとして利用し、エンドポイントとしては利用しないとき + +**モジュラースタイルに移行したという理由だけで、`config.ru`に移行する必要はなく、`config.ru`で起動するためにモジュラースタイルを使う必要はありません。** + +### Sinatraのミドルウェアとしての利用 + +Sinatraは他のRackミドルウェアを利用することができるだけでなく、 +全てのSinatraアプリケーションは、それ自体ミドルウェアとして別のRackエンドポイントの前に追加することが可能です。 + +このエンドポイントには、別のSinatraアプリケーションまたは他のRackベースのアプリケーション(Rails/Ramaze/Camping/…)が用いられるでしょう。 + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] = 'admin' and params['password'] = 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # ミドルウェアはbeforeフィルタの前に実行される + use LoginScreen + + before do + unless session['user_name'] + halt "アクセスは拒否されました。ログインしてください。" + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 動的なアプリケーションの生成 + +新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。`Sinatra.new`を使えばそれができます。 + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +これは省略できる引数として、それが継承するアプリケーションを取ります。 + +```ruby +# config.ru (rackupで起動) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +これは特にSinatraのextensionをテストするときや、Sinatraを自身のライブラリで利用する場合に役立ちます。 + +これはまた、Sinatraをミドルウェアとして利用することを極めて簡単にします。 + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## スコープとバインディング(Scopes and Binding) + +現在のスコープはどのメソッドや変数が利用可能かを決定します。 + +### アプリケーション/クラスのスコープ + +全てのSinatraアプリケーションはSinatra::Baseのサブクラスに相当します。 +もしトップレベルDSLを利用しているならば(`require 'sinatra'`)このクラスはSinatra::Applicationであり、 +そうでなければ、あなたが明示的に作成したサブクラスです。 +クラスレベルでは`get`や`before`のようなメソッドを持っています。 +しかし`request`や`session`オブジェクトには、全てのリクエストに対する単一のアプリケーションクラスがあるだけなので、アクセスできません。 + +`set`によって作られたオプションはクラスレベルのメソッドです。 + +```ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # もうアプリケーションスコープの中にいないよ! + end +end +``` + +次の場所ではアプリケーションスコープバインディングを持ちます。 + +* アプリケーションクラス本体 +* 拡張によって定義されたメソッド +* `helpers`に渡されたブロック +* `set`の値として使われるProcまたはブロック +* `Sinatra.new`に渡されたブロック + +このスコープオブジェクト(クラス)は次のように利用できます。 + +* configureブロックに渡されたオブジェクト経由(`configure { |c| ... }`) +* リクエストスコープの中での`settings` + +### リクエスト/インスタンスのスコープ + +やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 +このスコープの内側からは`request`や`session`オブジェクトにアクセスすることができ、`erb`や`haml`のようなレンダリングメソッドを呼び出すことができます。 +リクエストスコープの内側からは、`settings`ヘルパーによってアプリケーションスコープにアクセスすることができます。 + +```ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + get '/define_route/:name' do + # '/define_route/:name'のためのリクエストスコープ + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"のためのリクエストスコープ + @value # => nil (not the same request) + end + + "ルーティングが定義された!" + end +end +``` + +次の場所ではリクエストスコープバインディングを持ちます。 + +* get/head/post/put/delete/options/patch/link/unlink ブロック +* before/after フィルタ +* helper メソッド +* テンプレート/ビュー + +### デリゲートスコープ + +デリゲートスコープは、単にクラススコープにメソッドを転送します。 +しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません。 +委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注: +異なった`self`を持っています)。 +`Sinatra::Delegator.delegate :method_name`を呼び出すことによってデリゲートするメソッドを明示的に追加することができます。 + +次の場所ではデリゲートスコープを持ちます。 + +* もし`require "sinatra"`しているならば、トップレベルバインディング +* `Sinatra::Delegator` mixinでextendされたオブジェクト + +コードをご覧ください: ここでは [Sinatra::Delegator +mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)は[mainオブジェクトにextendされています](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。 + +## コマンドライン + +Sinatraアプリケーションは直接実行できます。 + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +オプション: + +``` +-h # ヘルプ +-p # ポート指定(デフォルトは4567) +-o # ホスト指定(デフォルトは0.0.0.0) +-e # 環境を指定 (デフォルトはdevelopment) +-s # rackserver/handlerを指定 (デフォルトはpuma) +-x # mutex lockを付ける (デフォルトはoff) +``` + +### マルチスレッド + +_この[StackOverflow](https://stackoverflow.com/a/6282999/5245129) +のKonstantinによる回答を言い換えています。_ + +Sinatraでは同時実行モデルを負わせることはできませんが、根本的な部分であるやPuma、WEBrickのようなRackハンドラ(サーバー)部分に委ねることができます。 +Sinatra自身はスレッドセーフであり、もしRackハンドラが同時実行モデルのスレッドを使用していても問題はありません。 +つまり、これはサーバーを起動させる時、特定のRackハンドラに対して正しい起動処理を特定することが出来ます。 +この例はRainbowsサーバーをマルチスレッドで起動する方法のデモです。 + +```ruby +# config.ru + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +run App +``` + +```ruby +# rainbows.conf + +# RainbowsのコンフィギュレータはUnicornのものをベースにしています。 +Rainbows! do + use :ThreadSpawn +end +``` + +次のようなコマンドでサーバを起動します。 + +``` +rainbows -c rainbows.conf +``` + +## 必要環境 + +次のRubyバージョンが公式にサポートされています。 + +
    +
    Ruby 1.8.7
    +
    + 1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。 +
    + +
    Ruby 1.9.2
    +
    + 1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。 +
    + +
    Ruby 1.9.3
    +
    + 1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。 +
    + +
    Ruby 2.0.0
    +
    + 2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。 +
    + +
    Rubinius
    +
    + Rubiniusは公式にサポートされています(Rubinius >= 2.x)。 + gem install pumaすることが推奨されています。 +
    + +
    JRuby
    +
    + JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。 + gem install trinidadすることが推奨されています。 +
    +
    + +開発チームは常に最新となるRubyバージョンに注視しています。 + +次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。 + +* JRubyとRubiniusの古いバージョン +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません) + +公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。 + +開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。 + +Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。 + +MacRubyを使う場合は、`gem install control_tower`してください。 + +Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。 + +## 最新開発版 + +Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、 + +```shell +gem install sinatra --pre +``` + +すれば、最新の機能のいくつかを利用できます。 + +### Bundlerを使う場合 + +最新のSinatraでアプリケーションを動作させたい場合には、[Bundler](https://bundler.io)を使うのがお薦めのやり方です。 + +まず、Bundlerがなければそれをインストールします。 + +```shell +gem install bundler +``` + +そして、プロジェクトのディレクトリで、`Gemfile`を作ります。 + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 他の依存ライブラリ +gem 'haml' # Hamlを使う場合 +gem 'activerecord', '~> 3.0' # ActiveRecord 3.xが必要かもしれません +``` + +ノート: `Gemfile`にアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。 + +これで、以下のようにしてアプリケーションを起動することができます。 + +```shell +bundle exec ruby myapp.rb +``` + +### 直接組み込む場合 + +ローカルにクローンを作って、`sinatra/lib`ディレクトリを`$LOAD_PATH`に追加してアプリケーションを起動します。 + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +追ってSinatraのソースを更新する方法。 + +```shell +cd myapp/sinatra +git pull +``` + +### グローバル環境にインストールする場合 + +Sinatraのgemを自身でビルドすることもできます。 + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +gemをルートとしてインストールする場合は、最後のステップはこうなります。 + +```shell +sudo rake install +``` + +## バージョニング(Versioning) + +Sinatraは、[Semantic Versioning](https://semver.org/)におけるSemVerおよびSemVerTagの両方に準拠しています。 + +## 参考文献 + +* [プロジェクトサイト](http://www.sinatrarb.com/) - ドキュメント、ニュース、他のリソースへのリンクがあります。 +* [プロジェクトに参加(貢献)する](http://www.sinatrarb.com/contributing.html) - バグレポート パッチの送信、サポートなど +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [メーリングリスト](https://groups.google.com/group/sinatrarb/topics) +* http://freenode.net上のIRC: [#sinatra](irc://chat.freenode.net/#sinatra) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) クックブック、チュートリアル +* [Sinatra Recipes](http://recipes.sinatrarb.com/) コミュニティによるレシピ集 +* http://www.rubydoc.info/上のAPIドキュメント: [最新版(latest release)用](http://www.rubydoc.info/gems/sinatra)または[現在のHEAD用](http://www.rubydoc.info/github/sinatra/sinatra) +* [CIサーバ](https://travis-ci.org/sinatra/sinatra) +* [Greenbear Laboratory Rack日本語マニュアル](http://route477.net/w/RackReferenceJa.html) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ko.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ko.md new file mode 100644 index 0000000000..4ed635368b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ko.md @@ -0,0 +1,2967 @@ +# Sinatra + +*주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있습니다.* + +Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게 +해 주는 [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)입니다. + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +아래의 명령어로 젬을 설치합니다. + +```shell +gem install sinatra +``` + +아래의 명령어로 실행합니다. + +```shell +ruby myapp.rb +``` + +[http://localhost:4567](http://localhost:4567) 를 확인해 보세요. + +`gem install thin`도 함께 실행하기를 권장합니다. +thin이 설치되어 있을 경우 Sinatra는 thin을 통해 실행합니다. + +## 목차 + +* [Sinatra](#sinatra) + * [목차](#목차) + * [라우터(Routes)](#라우터routes) + * [조건(Conditions)](#조건conditions) + * [반환값(Return Values)](#반환값return-values) + * [커스텀 라우터 매처(Custom Route Matchers)](#커스텀-라우터-매처custom-route-matchers) + * [정적 파일(Static Files)](#정적-파일static-files) + * [뷰 / 템플릿(Views / Templates)](#뷰--템플릿views--templates) + * [리터럴 템플릿(Literal Templates)](#리터럴-템플릿literal-templates) + * [가능한 템플릿 언어들(Available Template Languages)](#가능한-템플릿-언어들available-template-languages) + * [Haml 템플릿](#haml-템플릿) + * [Erb 템플릿](#erb-템플릿) + * [Builder 템플릿](#builder-템플릿) + * [Nokogiri 템플릿](#nokogiri-템플릿) + * [Sass 템플릿](#sass-템플릿) + * [SCSS 템플릿](#scss-템플릿) + * [Less 템플릿](#less-템플릿) + * [Liquid 템플릿](#liquid-템플릿) + * [Markdown 템플릿](#markdown-템플릿) + * [Textile 템플릿](#textile-템플릿) + * [RDoc 템플릿](#rdoc-템플릿) + * [AsciiDoc 템플릿](#asciidoc-템플릿) + * [Radius 템플릿](#radius-템플릿) + * [Markaby 템플릿](#markaby-템플릿) + * [RABL 템플릿](#rabl-템플릿) + * [Slim 템플릿](#slim-템플릿) + * [Creole 템플릿](#creole-템플릿) + * [MediaWiki 템플릿](#mediawiki-템플릿) + * [CoffeeScript 템플릿](#coffeescript-템플릿) + * [Stylus 템플릿](#stylus-템플릿) + * [Yajl 템플릿](#yajl-템플릿) + * [WLang 템플릿](#wlang-템플릿) + * [템플릿에서 변수에 접근하기](#템플릿에서-변수에-접근하기) + * [템플릿에서의 `yield` 와 중첩 레이아웃](#템플릿에서의-yield-와-중첩-레이아웃) + * [인라인 템플릿](#인라인-템플릿) + * [이름을 가지는 템플릿(Named Templates)](#이름을-가지는-템플릿named-templates) + * [파일 확장자 연결하기](#파일-확장자-연결하기) + * [나만의 고유한 템플릿 엔진 추가하기](#나만의-고유한-템플릿-엔진-추가하기) + * [템플릿 검사를 위한 커스텀 로직 사용하기](#템플릿-검사를-위한-커스텀-로직-사용하기) + * [필터(Filters)](#필터filters) + * [헬퍼(Helpers)](#헬퍼helpers) + * [세션(Sessions) 사용하기](#세션sessions-사용하기) + * [중단하기(Halting)](#중단하기halting) + * [넘기기(Passing)](#넘기기passing) + * [다른 라우터 부르기(Triggering Another Route)](#다른-라우터-부르기triggering-another-route) + * [본문, 상태 코드 및 헤더 설정하기](#본문-상태-코드-및-헤더-설정하기) + * [응답 스트리밍(Streaming Responses)](#응답-스트리밍streaming-responses) + * [로깅(Logging)](#로깅logging) + * [마임 타입(Mime Types)](#마임-타입mime-types) + * [URL 생성하기](#url-생성하기) + * [브라우저 재지정(Browser Redirect)](#브라우저-재지정browser-redirect) + * [캐시 컨트롤(Cache Control)](#캐시-컨트롤cache-control) + * [파일 전송하기(Sending Files)](#파일-전송하기sending-files) + * [요청 객체에 접근하기(Accessing the Request Object)](#요청-객체에-접근하기accessing-the-request-object) + * [첨부(Attachments)](#첨부attachments) + * [날짜와 시간 다루기](#날짜와-시간-다루기) + * [템플릿 파일 참조하기](#템플릿-파일-참조하기) + * [설정(Configuration)](#설정configuration) + * [공격 방어 설정하기(Configuring attack protection)](#공격-방어-설정하기configuring-attack-protection) + * [가능한 설정들(Available Settings)](#가능한-설정들available-settings) + * [환경(Environments)](#환경environments) + * [에러 처리(Error Handling)](#에러-처리error-handling) + * [찾을 수 없음(Not Found)](#찾을-수-없음not-found) + * [에러](#에러) + * [Rack 미들웨어(Rack Middleware)](#rack-미들웨어rack-middleware) + * [테스팅(Testing)](#테스팅testing) + * [Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps)](#sinatrabase---미들웨어middleware-라이브러리libraries-그리고-모듈-앱modular-apps) + * [모듈(Modular) vs. 전통적 방식(Classic Style)](#모듈modular-vs-전통적-방식classic-style) + * [모듈 애플리케이션(Modular Application) 제공하기](#모듈-애플리케이션modular-application-제공하기) + * [config.ru로 전통적 방식의 애플리케이션 사용하기](#configru로-전통적-방식의-애플리케이션-사용하기) + * [언제 config.ru를 사용할까?](#언제-configru를-사용할까) + * [Sinatra를 미들웨어로 사용하기](#sinatra를-미들웨어로-사용하기) + * [동적인 애플리케이션 생성(Dynamic Application Creation)](#동적인-애플리케이션-생성dynamic-application-creation) + * [범위(Scopes)와 바인딩(Binding)](#범위scopes와-바인딩binding) + * [애플리케이션/클래스 범위](#애플리케이션클래스-범위) + * [요청/인스턴스 범위](#요청인스턴스-범위) + * [위임 범위(Delegation Scope)](#위임-범위delegation-scope) + * [명령행(Command Line)](#명령행command-line) + * [다중 스레드(Multi-threading)](#다중-스레드multi-threading) + * [요구사항(Requirement)](#요구사항requirement) + * [최신(The Bleeding Edge)](#최신the-bleeding-edge) + * [Bundler를 사용하여](#bundler를-사용하여) + * [직접 하기(Roll Your Own)](#직접-하기roll-your-own) + * [전역으로 설치(Install Globally)](#전역으로-설치install-globally) + * [버저닝(Versioning)](#버저닝versioning) + * [더 읽을 거리(Further Reading)](#더-읽을-거리further-reading) + +## 라우터(Routes) + +Sinatra에서, 라우터(route)는 URL-매칭 패턴과 쌍을 이루는 HTTP 메서드입니다. +각각의 라우터는 블록과 연결됩니다. + +```ruby +get '/' do + .. 무언가 보여주기(show) .. +end + +post '/' do + .. 무언가 만들기(create) .. +end + +put '/' do + .. 무언가 대체하기(replace) .. +end + +patch '/' do + .. 무언가 수정하기(modify) .. +end + +delete '/' do + .. 무언가 없애기(annihilate) .. +end + +options '/' do + .. 무언가 주기(appease) .. +end + +link '/' do + .. 무언가 관계맺기(affiliate) .. +end + +unlink '/' do + .. 무언가 격리하기(separate) .. +end +``` + +라우터는 정의된 순서에 따라 매치되고 요청에 대해 가장 먼저 매칭된 라우터가 호출됩니다. + +라우터 패턴에는 이름을 가진 매개변수가 포함될 수 있으며, `params` 해시로 접근할 수 있습니다. + +```ruby +get '/hello/:name' do + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + "Hello #{params['name']}!" +end +``` + +또한 블록 매개변수를 통하여도 이름을 가진 매개변수에 접근할 수 있습니다. + +```ruby +get '/hello/:name' do |n| + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + # n 에는 params['name']가 저장 + "Hello #{n}!" +end +``` + +라우터 패턴에는 스플랫(splat, 또는 와일드카드)도 매개변수도 포함될 수 있으며, 이럴 경우 `params['splat']` 배열을 통해 접근할 수 있습니다. + +```ruby +get '/say/*/to/*' do + # /say/hello/to/world와 매치 + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml과 매치 + params['splat'] # => ["path/to/file", "xml"] +end +``` + +블록 매개변수로도 접근할 수 있습니다. + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +라우터는 정규표현식으로 매치할 수 있습니다. + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +블록 매개변수로도 사용가능합니다. + +```ruby +get %r{/hello/([\w]+)} do |c| + # "GET /meta/hello/world", "GET /hello/world/1234" 등과 매치 + "Hello, #{c}!" +end +``` + +라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있습니다. + +```ruby +get '/posts/:format?' do + # "GET /posts/" 는 물론 "GET /posts/json", "GET /posts/xml" 와 같은 어떤 확장자와도 매칭 +end +``` + +쿼리 파라메터로도 이용가능 합니다. + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +한편, 경로 탐색 공격 방지(path traversal attack protection, 아래 참조)를 비활성화시키지 않았다면, +요청 경로는 라우터와 매칭되기 이전에 수정될 수 있습니다. + +### 조건(Conditions) + +라우터는 사용자 에이전트(user agent)같은 다양한 매칭 조건을 포함할 수 있습니다. + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbird 버전 #{params['agent'][0]}을 사용하는군요!" +end + +get '/foo' do + # songbird 브라우저가 아닌 경우 매치 +end +``` + +다른 가능한 조건에는 `host_name`과 `provides`가 있습니다. + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides`는 request의 허용된 해더를 검색합니다. + +사용자 정의 조건도 쉽게 만들 수 있습니다. + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "내가 졌소!" +end + +get '/win_a_car' do + "미안해서 어쩌나." +end +``` + +여러 값을 받는 조건에는 스플랫(splat)을 사용합니다. + +```ruby +set(:auth) do |*roles| # <- 이게 스플랫 + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "내 계정 정보" +end + +get "/only/admin/", :auth => :admin do + "관리자 외 접근불가!" +end +``` + +### 반환값(Return Values) + +라우터 블록의 반환 값은 HTTP 클라이언트로 전달되는 응답 본문만을 결정하거나, 또는 Rack 스택에서 다음 번 미들웨어만를 결정합니다. +위의 예제에서 볼 수 있지만 대부분의 경우 이 반환값은 문자열입니다.하지만 다른 값도 허용됩니다. + +유효한 Rack 응답, Rack 본문 객체 또는 HTTP 상태 코드가 되는 어떠한 객체라도 반환할 수 있습니다. + +* 세 요소를 가진 배열: `[상태 (Integer), 헤더 (Hash), 응답 본문 (#each에 반응)]` +* 두 요소를 가진 배열: `[상태 (Integer), 응답 본문 (#each에 반응)]` +* `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체 +* 상태 코드를 의미하는 Integer + +이것을 이용한 예를 들자면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있습니다. + +```ruby +class Stream + def each +100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +`stream` 헬퍼 메서드(아래 참조)를 사용하면 이런 번거로움을 줄이고 스트리밍 로직을 라우터 속에 포함 시킬 수도 있습니다. + +### 커스텀 라우터 매처(Custom Route Matchers) + +위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있습니다. +하지만, 그게 끝은 아닙니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있습니다. + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +사실 위의 예제는 조금 과하게 작성된 면이 있습니다. 간단하게 표현할 수도 있어요. + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +또는 거꾸로 탐색(negative look ahead)할 수도 있습니다. + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 정적 파일(Static Files) + +정적 파일들은 `./public` 디렉터리에서 제공됩니다. 위치를 다른 곳으로 +변경하려면 `:public_folder` 옵션을 지정하면 됩니다. + +```ruby +set :public_folder, __dir__ + '/static' +``` + +public 디렉터리명은 URL에 포함되지 않는다는 점에 주의하세요. +`./public/css/style.css` 파일은 아마 `http://example.com/css/style.css` 로 접근할 수 있을 것입니다. + +`Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하면 됩니다. + +## 뷰 / 템플릿(Views / Templates) + +템플릿 언어들은 각각의 렌더링 메서드를 통해 표출됩니다. +이들 메서드는 문자열을 반환할 뿐입니다. + +```ruby +get '/' do + erb :index +end +``` + +이 구문은 `views/index.erb`를 렌더합니다. + +템플릿 이름 대신 템플릿의 내용을 직접 넘길 수도 있습니다. + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +템플릿은 두 번째 인자로 옵션값의 해시를 받습니다. + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +이 구문은 `views/post.erb` 속에 내장된 `views/index.erb`를 렌더합니다. +(`views/layout.erb`파일이 존재할 경우 기본값은 `views/layout.erb`입니다.) + +Sinatra가 이해하지 못하는 모든 옵션값들은 템플릿 엔진으로 전달됩니다. + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +옵션값은 템플릿 언어별로 전역적으로 설정할 수도 있습니다. + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +render 메서드에서 전달된 옵션값들은 `set`을 통해 설정한 옵션값보다 우선합니다. + +가능한 옵션값들은 다음과 같습니다. + +
    +
    locals
    +
    + 문서로 전달되는 local 목록. 파셜과 함께 사용하기 좋음. + 예제: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + 불확실한 경우에 사용할 문자열 인코딩. + 기본값은 settings.default_encoding. +
    + +
    views
    +
    + 템플릿을 로드할 뷰 폴더. + 기본값은 settings.views. +
    + +
    layout
    +
    + 레이아웃을 사용할지 여부 (true 또는 false), 만약 + 이 값이 심볼일 경우, 사용할 템플릿을 지정. 예제: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + 템플릿이 생성하는 Content-Type, 기본값은 템플릿 언어에 의존. +
    + +
    scope
    +
    + 템플릿을 렌더링하는 범위. 기본값은 어플리케이션 인스턴스. + 만약 이 값을 변경하면, 인스턴스 변수와 헬퍼 메서드들을 사용할 수 없게 됨. +
    + +
    layout_engine
    +
    + 레이아웃 렌더링에 사용할 템플릿 엔진. 레이아웃을 지원하지 않는 언어인 경우에 유용. + 기본값은 템플릿에서 사용하는 엔진. 예제: set :rdoc, :layout_engine => :erb +
    +
    + + +템플릿은 `./views` 디렉터리에 있는 것으로 가정됩니다. 뷰 디렉터리를 +다른 곳으로 하고 싶으시면 이렇게 하세요. + +```ruby +set :views, settings.root + '/templates' +``` + +템플릿은 언제나 심볼로 참조되어야 한다는 것에 주의하세요. +템플릿이 하위 디렉터리에 위치한 경우(그럴 경우에는 `:'subdir/template'`을 +사용)에도 예외는 없습니다. 반드시 심볼이어야 하는 이유는, 문자열을 넘기면 +렌더링 메서드가 전달된 문자열을 직접 렌더하기 때문입니다. + +### 리터럴 템플릿(Literal Templates) + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +템플릿 문자열을 렌더합니다. + +### 가능한 템플릿 언어들(Available Template Languages) + +일부 언어는 여러 개의 구현이 있습니다. (스레드에 안전하게 thread-safe) 어느 구현을 +사용할지 저정하려면, 먼저 require 하기만 하면 됩니다. + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml 템플릿 + + + + + + + + + + + + + + +
    의존성haml
    파일 확장자.haml
    예제haml :index, :format => :html5
    + +#### Erb 템플릿 + + + + + + + + + + + + + + +
    의존성erubis 또는 erb (루비 속에 포함)
    파일 확장자.erb, .rhtml, .erubis (Erubis만 해당)
    예제erb :index
    + +#### Builder 템플릿 + + + + + + + + + + + + + + +
    의존성builder
    파일 확장자.builder
    예제builder { |xml| xml.em "hi" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Nokogiri 템플릿 + + + + + + + + + + + + + + +
    의존성nokogiri
    파일 확장자.nokogiri
    예제nokogiri { |xml| xml.em "hi" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Sass 템플릿 + + + + + + + + + + + + + + +
    의존성sass
    파일 확장자.sass
    예제sass :stylesheet, :style => :expanded
    + +#### SCSS 템플릿 + + + + + + + + + + + + + + +
    의존성sass
    파일 확장자.scss
    예제scss :stylesheet, :style => :expanded
    + +#### Less 템플릿 + + + + + + + + + + + + + + +
    의존성less
    파일 확장자.less
    예제less :stylesheet
    + +#### Liquid 템플릿 + + + + + + + + + + + + + + +
    의존성liquid
    파일 확장자.liquid
    예제liquid :index, :locals => { :key => 'value' }
    + +Liquid 템플릿에서는 루비 메서드(`yield` 제외)를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markdown 템플릿 + + + + + + + + + + + + + + +
    의존성 + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku + 중 아무거나 +
    파일 확장.markdown, .mkd, .md
    예제markdown :index, :layout_engine => :erb
    + +Markdown에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +다른 템플릿 속에서 `markdown` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 안녕 Haml! +%p= markdown(:greetings) +``` + +Markdown에서 루비를 호출할 수 없기 때문에, Markdown으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### Textile 템플릿 + + + + + + + + + + + + + + +
    의존성RedCloth
    파일 확장자.textile
    예제textile :index, :layout_engine => :erb
    + +Textile에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +다른 템플릿 속에서 `textile` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 안녕 Haml! +%p= textile(:greetings) +``` + +Textile에서 루비를 호출할 수 없기 때문에, Textile으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### RDoc 템플릿 + + + + + + + + + + + + + + +
    의존성rdoc
    파일 확장자.rdoc
    예제rdoc :README, :layout_engine => :erb
    + +RDoc에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +다른 템플릿 속에서 `rdoc` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDoc에서 루비를 호출할 수 없기 때문에, RDoc으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### AsciiDoc 템플릿 + + + + + + + + + + + + + + +
    의존성Asciidoctor
    파일 확장자.asciidoc, .adoc and .ad
    예제asciidoc :README, :layout_engine => :erb
    + +AsciiDoc 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Radius 템플릿 + + + + + + + + + + + + + + +
    의존성radius
    파일 확장자.radius
    예제radius :index, :locals => { :key => 'value' }
    + +Radius 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markaby 템플릿 + + + + + + + + + + + + + + +
    의존성markaby
    파일확장.mab
    예제markaby { h1 "Welcome!" }
    + +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### RABL 템플릿 + + + + + + + + + + + + + + +
    의존성rabl
    파일 확장자.rabl
    예제rabl :index
    + +#### Slim 템플릿 + + + + + + + + + + + + + + +
    의존성slim
    파일 확장자.slim
    예제slim :index
    + +#### Creole 템플릿 + + + + + + + + + + + + + + +
    의존성creole
    파일 확장자.creole
    예제creole :wiki, :layout_engine => :erb
    + +Creole에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +다른 템플릿 속에서 `creole` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Creole에서 루비를 호출할 수 없기 때문에, Creole으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### MediaWiki 템플릿 + + + + + + + + + + + + + + +
    의존성WikiCloth
    파일 확장자.mediawiki and .mw
    예제mediawiki :wiki, :layout_engine => :erb
    + +MediaWiki 마크업에서는 메서드 호출 뿐 아니라 locals 전달도 불가능합니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +다른 템플릿 속에서 `mediawiki` 메서드를 호출할 수도 있습니다. + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +MediaWiki에서 루비를 호출할 수 없기 때문에, MediaWiki으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### CoffeeScript 템플릿 + + + + + + + + + + + + + + +
    의존성 + + CoffeeScript + 와 + + 자바스크립트 실행법 + +
    파일 확장자.coffee
    예제coffee :index
    + +#### Stylus 템플릿 + + + + + + + + + + + + + + +
    의존성 + + Stylus + 와 + + 자바스크립트 실행법 + +
    파일 확장자.styl
    예제stylus :index
    + +Stylus 템플릿을 사용가능하게 하려면, 먼저 `stylus`와 `stylus/tilt`를 로드 +해야합니다. + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl 템플릿 + + + + + + + + + + + + + + +
    의존성yajl-ruby
    파일 확장자.yajl
    예제 + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 `#to_json`으로 변환됩니다. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`과 `:variable` 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있습니다. + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang 템플릿 + + + + + + + + + + + + + + +
    의존성WLang
    파일 확장자.wlang
    예제wlang :index, :locals => { :key => 'value' }
    + +WLang 템플릿에서는 루비 메서드를 사용하는게 일반적이지 않기 +때문에, 거의 대부분의 경우 locals를 전달합니다. 그래도 +WLang으로 쓰여진 레이아웃과 `yield`는 지원합니다. + +### 템플릿에서 변수에 접근하기 + +템플릿은 라우터 핸들러와 같은 맥락(context)에서 평가됩니다. 라우터 +핸들러에서 설정한 인스턴스 변수들은 템플릿에서 직접 접근 가능합니다. + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +명시적으로 로컬 변수의 해시를 지정할 수도 있습니다. + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +이 방법은 주로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할 +때 사용됩니다. + +### 템플릿에서의 `yield` 와 중첩 레이아웃 + +레이아웃은 보통 `yield`만 호출하는 템플릿입니다. +위에 설명된 `:template` 옵션을 통해 템플릿을 사용하거나, +다음 예제처럼 블록으로 렌더링 할 수 있습니다. + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +위 코드는 `erb :index, :layout => :post`와 대부분 동일합니다. + +렌더링 메서드에 블록 넘기기는 중첩 레이아웃을 만들때 유용합니다. + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +위의 코드도 줄일 수 있습니다. + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +현재, `erb`, `haml`, `liquid`, `slim `, `wlang`는 블럭을 지원합니다. +일반적인 `render` 메소드도 블럭을 지원합니다. + +### 인라인 템플릿 + +템플릿은 소스 파일의 마지막에서 정의할 수도 있습니다. + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html += yield + +@@ index +%div.title Hello world. +``` + +참고: sinatra를 require한 소스 파일에 정의된 인라인 템플릿은 자동으로 +로드됩니다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로 +`enable :inline_templates`을 호출하면 됩니다. + +### 이름을 가지는 템플릿(Named Templates) + +템플릿은 톱 레벨(top-level)에서 `template`메서드로도 정의할 수 있습니다. + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +"layout"이라는 이름의 템플릿이 존재하면, 템플릿이 렌더될 때마다 사용됩니다. +레이아웃을 비활성화할 때는 `:layout => false`를 전달하여 개별적으로 +비활성시키거나 `set :haml, :layout => false`으로 기본값을 비활성으로 둘 수 +있습니다. + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### 파일 확장자 연결하기 + +어떤 파일 확장자를 특정 템플릿 엔진과 연결하려면, `Tilt.register`를 사용하면 +됩니다. 예를 들어, `tt`라는 파일 확장자를 Textile 템플릿과 연결하고 싶다면, +다음과 같이 하면 됩니다. + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### 나만의 고유한 템플릿 엔진 추가하기 + +우선, Tilt로 여러분 엔진을 등록하고, 렌더링 메서드를 생성합니다. + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +위 코드는 `./views/index.myat` 를 렌더합니다. +Tilt에 대한 더 자세한 내용은 https://github.com/rtomayko/tilt 참조하세요. + +### 템플릿 검사를 위한 커스텀 로직 사용하기 + +고유한 템플릿 룩업을 구현하기 위해서는 `#find_template` 메서드를 만드셔야 합니다. + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## 필터(Filters) + +사전 필터(before filter)는 라우터와 동일한 맥락에서 매 요청 전에 평가되며 +요청과 응답을 변형할 수 있습니다. 필터에서 설정된 인스턴스 변수들은 라우터와 +템플릿에서 접근 가능합니다. + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +사후 필터(after filter)는 라우터와 동일한 맥락에서 매 요청 이후에 평가되며 +마찬가지로 요청과 응답을 변형할 수 있습니다. 사전 필터와 라우터에서 설정된 +인스턴스 변수들은 사후 필터에서 접근 가능합니다. + +```ruby +after do + puts response.status +end +``` + +참고: 만약 라우터에서 `body` 메서드를 사용하지 않고 그냥 문자열만 반환한 +경우라면, body는 나중에 생성되는 탓에, 아직 사후 필터에서 사용할 수 없을 +것입니다. + +필터는 패턴을 취할 수도 있으며, 이 경우 요청 경로가 그 패턴과 매치할 +경우에만 필터가 평가될 것입니다. + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +라우터와 마찬가지로, 필터 역시 조건을 취할 수 있습니다. + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## 헬퍼(Helpers) + +톱-레벨의 `helpers` 메서드를 사용하여 라우터 핸들러와 템플릿에서 사용할 헬퍼 +메서드들을 정의할 수 있습니다. + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +또는, 헬퍼 메서드는 별도의 모듈 속에 정의할 수도 있습니다. + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +이 것은 모듈을 애플리케이션 클래스에 포함(include)시킨 것과 같습니다. + +### 세션(Sessions) 사용하기 + +세션은 요청 동안에 상태를 유지하기 위해 사용합니다. +세션이 활성화되면, 사용자 세션 당 세션 해시 하나씩을 갖게 됩니다. + +```ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +`enable :sessions`은 실은 모든 데이터를 쿠키 속에 저장하는 것에 주의하세요. +이 방식이 바람직하지 않을 수도 있습니다. (예를 들어, 많은 양의 데이터를 +저장하게 되면 트래픽이 늘어납니다). +이런 경우에는 랙 세션 미들웨어(Rack session middleware)를 사용할 수 있습니다. +`enable :sessions`을 호출하지 **않는** 대신에, 선택한 미들웨어를 다른 +미들웨어들처럼 포함시키면 됩니다. + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +보안 강화을 위해서, 쿠키 속의 세션 데이터는 세션 시크릿(secret)으로 +사인(sign)됩니다. Sinatra는 무작위 시크릿을 생성합니다. 하지만, 이 +시크릿은 애플리케이션 시작 시마다 변경되기 때문에, 애플리케이션의 +모든 인스턴스들이 공유할 시크릿을 직접 만들 수도 있습니다. + +```ruby +set :session_secret, 'super secret' +``` + +조금 더 세부적인 설정이 필요하다면, `sessions` 설정에서 옵션이 있는 +해시를 저장할 수도 있습니다. + +```ruby +set :sessions, :domain => 'foo.com' +``` + +세션을 다른 foo.com의 서브도메인 들과 공유하기 원한다면, 다음에 나오는 +것 처럼 도메인 앞에 *.*을 붙이셔야 합니다. + +```ruby +set :sessions, :domain => '.foo.com' +``` + +### 중단하기(Halting) + +필터나 라우터에서 요청을 즉각 중단하고 싶을 때 사용하합니다. + +```ruby +halt +``` + +중단할 때 상태를 지정할 수도 있습니다. + +```ruby +halt 410 +``` + +본문을 넣을 수도 있습니다. + +```ruby +halt 'this will be the body' +``` + +둘 다 할 수도 있습니다. + +```ruby +halt 401, 'go away!' +``` + +헤더를 추가할 경우에는 다음과 같이 하면 됩니다. + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +당연히 `halt`와 템플릿은 같이 사용할 수 있습니다. + +```ruby +halt erb(:error) +``` + +### 넘기기(Passing) + +라우터는 `pass`를 사용하여 다음 번 매칭되는 라우터로 처리를 넘길 수 있습니다. + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +이 때 라우터 블록에서 즉각 빠져나오게 되고 제어는 다음 번 매칭되는 라우터로 +넘어갑니다. 만약 매칭되는 라우터를 찾지 못하면, 404가 반환됩니다. + +### 다른 라우터 부르기(Triggering Another Route) + +때로는 `pass`가 아니라, 다른 라우터를 호출한 결과를 얻고 싶을 때도 +있습니다. 이럴때는 간단하게 `call`을 사용하면 됩니다. + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do +"bar" +end +``` + +위 예제의 경우, `"bar"`를 헬퍼로 옮겨 `/foo`와 `/bar` 모두에서 사용하도록 +하면 테스팅을 쉽게 하고 성능을 높일 수 있습니다. + +요청의 사본이 아닌 바로 그 인스턴스로 보내지도록 하고 싶다면, +`call` 대신 `call!`을 사용하면 됩니다. + +`call`에 대한 더 자세한 내용은 Rack 명세를 참고하세요. + +### 본문, 상태 코드 및 헤더 설정하기 + +라우터 블록의 반환값과 함께 상태 코드(status code)와 응답 본문(response body)을 +설정할수 있고 권장됩니다. 하지만, 경우에 따라서는 본문을 실행 흐름 중의 임의 +지점에서 설정해야 할때도 있습니다. 이런 경우 `body` 헬퍼 메서드를 사용하면 +됩니다. 이렇게 하면, 그 순간부터 본문에 접근할 때 그 메서드를 사용할 수가 있습니다. + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +`body`로 블록을 전달하는 것도 가능하며, 이 블록은 랙(Rack) 핸들러에 의해 +실행됩니다. (이 방법은 스트리밍을 구현할 때 사용할 수 있습니다. "값 +반환하기"를 참고하세요). + +본문와 마찬가지로, 상태코드와 헤더도 설정할 수 있습니다. + +```ruby +get '/foo' do + status 418 + headers \ +"Allow" => "BREW, POST, GET, PROPFIND, WHEN", +"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +`body`처럼, `header`와 `status`도 매개변수 없이 사용하여 현재 값을 +액세스할 수 있습니다. + +### 응답 스트리밍(Streaming Responses) + +응답 본문의 일정 부분을 계속 생성하는 가운데 데이터를 내보내기 시작하고 +싶을 경우가 있습니다. 극단적인 예제로, 클라이언트가 접속을 끊기 전까지 +계속 데이터를 내보내고 싶을 경우도 있죠. 여러분만의 래퍼(wrapper)를 +만들지 않으려면 `stream` 헬퍼를 사용하면 됩니다. + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +이렇게 스트리밍 API나 [서버 발송 이벤트Server Sent +Events](https://w3c.github.io/eventsource/)를 구현할 수 있고, 이 방법은 +[WebSockets](https://en.wikipedia.org/wiki/WebSocket)을 위한 기반으로 사용됩니다. +이 방법은 일부 콘텐츠가 느린 자원에 의존하는 경우에 스로풋(throughtput)을 +높이기 위해 사용되기도 합니다. + +스트리밍 동작, 특히 동시 요청의 수는 애플리케이션을 서빙하는 웹서버에 크게 +의존합니다. 일부의 경우 아예 스트리밍을 지원하지 조차 않습니다. 만약 서버가 +스트리밍을 지원하지 않는다면, 본문은 `stream` 으로 전달된 블록이 수행을 마친 +후에 한꺼번에 반환됩니다. 이런 한번에 쏘는 샷건같은 방식으로는 스트리밍은 +움직이지 않습니다. + +선택적 매개변수 `keep_open`이 설정되어 있다면, 스트림 객체에서 `close`를 +호출하지 않을 것이고, 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수 +있습니다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동하고 +다른 서버들은 여전히 스트림을 닫습니다. + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +### 로깅(Logging) + +요청 스코프(request scope) 내에서, `Logger`의 인스턴스인 `logger` +헬퍼를 사용할 수 있습니다. + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +이 로거는 자동으로 Rack 핸들러에서 설정한 로그설정을 참고합니다. +만약 로깅이 비활성상태라면, 이 메서드는 더미(dummy) 객체를 반환하기 때문에, +라우터나 필터에서 이 부분에 대해 걱정할 필요는 없습니다. + +로깅은 `Sinatra::Application`에서만 기본으로 활성화되어 있음에 유의합시다. +만약 `Sinatra::Base`로부터 상속받은 경우라면 직접 활성화시켜 줘야 합니다. + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +로깅 미들웨어를 사용하지 않으려면, `logging` 설정을 `nil`로 두면 됩니다. +하지만, 이 경우 주의할 것은 `logger`는 `nil`을 반환하는 것입니다. +통상적인 유스케이스는 여러분만의 로거를 사용하고자 할 경우일 것입니다. +Sinatra는 `env['rack.logger']`에서 찾은 로거를 사용할 것입니다. + +### 마임 타입(Mime Types) + +`send_file`이나 정적인 파일을 사용할 때에 Sinatra가 인식하지 못하는 +마임 타입이 있을 수 있습니다. 이 경우 `mime_type`을 사용하여 파일 +확장자를 등록합니다. + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +`content_type` 헬퍼로 쓸 수도 있습니다. + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URL 생성하기 + +URL을 생성할때 `url` 헬퍼 메서드를 사용합니다. 예를 들어 Haml에서는 이렇게 +합니다. + +```ruby +%a{:href => url('/foo')} foo +``` + +이것은 리버스 프록시(reverse proxies)와 Rack 라우터가 있다면 참고합니다. + +이 메서드는 `to`라는 별칭으로도 사용할 수 있습니다. (아래 예제 참조) + +### 브라우저 재지정(Browser Redirect) + +`redirect` 헬퍼 메서드를 사용하여 브라우저를 리다이렉트 시킬 수 있습니다. + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +다른 부가적인 매개변수들은 `halt`에 전달하는 인자들과 비슷합니다. + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +`redirect back`을 사용하면 쉽게 사용자가 왔던 페이지로 다시 돌아가게 +할 수 있습니다. + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +리다이렉트와 함께 인자를 전달하려면, 쿼리로 붙이거나, + +```ruby +redirect to('/bar?sum=42') +``` + +세션을 사용하면 됩니다. + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### 캐시 컨트롤(Cache Control) + +헤더를 정확하게 설정하는 것은 적절한 HTTP 캐싱의 기본입니다. + +Cache-Control 헤더를 다음과 같이 간단하게 설정할 수 있습니다. + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +프로 팁: 캐싱은 사전 필터에서 설정하세요. + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires` 헬퍼를 사용하여 그에 상응하는 헤더를 설정한다면, +`Cache-Control`이 자동으로 설정됩니다. + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +캐시를 잘 사용하려면, `etag` 또는 `last_modified`을 사용해 보세요. +무거운 작업을 하기 *전*에 이들 헬퍼를 호출하길 권장합니다. 이렇게 하면, +클라이언트 캐시에 현재 버전이 이미 들어 있을 경우엔 즉각 응답을 +뿌릴(flush) 것입니다. + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +[약한 ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)를 +사용할 수 도 있습니다. + +```ruby +etag @article.sha1, :weak +``` + +이들 헬퍼는 어떠한 캐싱도 하지 않으며, 대신 캐시에 필요한 정보를 제공합니다. +손쉬운 리버스 프록시(reverse-proxy) 캐싱 솔루션을 찾고 있다면, +[rack-cache](https://github.com/rtomayko/rack-cache)를 써보세요. + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +정적 파일에 `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` +설정(아래 참조)을 쓰세요. + +RFC 2616에 따르면 If-Match 또는 If-None-Match 헤더가 `*`로 설정된 경우 요청한 +리소스(resource)가 이미 존재하느냐 여부에 따라 다르게 취급해야 한다고 되어 +있습니다. Sinatra는 (get 처럼) 안전하거나 (put 처럼) 멱등인 요청에 대한 리소스는 +이미 존재한다고 가정하지만, 다른 리소스(예를 들면 post 요청 같은)의 경우는 +새 리소스로 취급합니다. 이 행동은 `:new_resource` 옵션을 전달하여 변경할 수 있습니다. + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +약한 ETag를 사용하고자 한다면, `:kind`으로 전달합시다. + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### 파일 전송하기(Sending Files) + +응답(response)으로 파일의 컨탠츠를 리턴하려면, `send_file` 헬퍼 메서드를 사용하면 됩니다. + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +이 메서드는 몇 가지 옵션을 받습니다. + +```ruby +send_file 'foo.png', :type => :jpg +``` + +옵션들: + +
    +
    filename
    +
    응답에서 사용되는 파일명. 기본값은 실제 파일명.
    + +
    last_modified
    +
    Last-Modified 헤더값. 기본값은 파일의 mtime.
    + +
    type
    +
    Content-Type 헤더값. 없으면 파일 확장자로부터 유추.
    + +
    disposition
    +
    + Content-Disposition 헤더값. 가능한 값들: nil (기본값), + :attachment:inline +
    + +
    length
    +
    Content-Length 헤더값, 기본값은 파일 크기.
    + +
    status
    +
    + 전송할 상태 코드. 오류 페이지로 정적 파일을 전송할 경우에 유용. + + Rack 핸들러가 지원할 경우, Ruby 프로세스로부터의 스트리밍이 아닌 + 다른 수단이 사용가능함. 이 헬퍼 메서드를 사용하게 되면, Sinatra는 + 자동으로 범위 요청(range request)을 처리함. +
    +
    + + +### 요청 객체에 접근하기(Accessing the Request Object) + +들어오는 요청 객에는 요청 레벨(필터, 라우터, 오류 핸들러)에서 `request` +메서드를 통해 접근 가능합니다. + +```ruby +# http://example.com/example 상에서 실행 중인 앱 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # 클라이언트로부터 전송된 요청 본문 (아래 참조) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body의 길이 + request.media_type # request.body의 미디어 유형 + request.host # "example.com" + request.get? # true (다른 동사에 대해 유사한 메서드 있음) + request.form_data? # false + request["SOME_HEADER"] # SOME_HEADER 헤더의 값 + request.referrer # 클라이언트의 리퍼러 또는 '/' + request.user_agent # 사용자 에이전트 (:agent 조건에서 사용됨) + request.cookies # 브라우저 쿠키의 해시 + request.xhr? # 이게 ajax 요청인가요? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 클라이언트 IP 주소 + request.secure? # false (ssl 접속인 경우 true) + request.forwarded? # true (리버스 프록시 하에서 작동 중이라면) + request.env # Rack에 의해 처리되는 로우(raw) env 해시 +end +``` + +`script_name`, `path_info`같은 일부 옵션들은 이렇게 쓸 수도 있습니다. + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body`는 IO 객체이거나 StringIO 객체입니다. + +```ruby +post "/api" do + request.body.rewind # 누군가 이미 읽은 경우 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### 첨부(Attachments) + +`attachment` 헬퍼를 사용하여 응답이 브라우저에 표시하는 대신 +디스크에 저장되어야 함을 블라우저에게 알릴 수 있습니다. + +```ruby +get '/' do + attachment + "store it!" +end +``` + +파일명을 전달할 수도 있습니다. + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### 날짜와 시간 다루기 + +Sinatra는 `time_for_` 헬퍼 메서드를 제공합니다. 이 메서드는 +주어진 값으로부터 Time 객체를 생성한다. `DateTime`, `Date` 같은 +비슷한 클래스들도 변환됩니다. + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +이 메서드는 내부적으로 `expires` 나 `last_modified` 같은 곳에서 사용됩니다. +따라서 여러분은 애플리케이션에서 `time_for`를 오버라이딩하여 이들 메서드의 +동작을 쉽게 확장할 수 있습니다. + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### 템플릿 파일 참조하기 + +`find_template`는 렌더링할 템플릿 파일을 찾는데 사용됩니다. + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +이것만으로는 그렇게 유용하지는 않습니다만, 이 메서드를 오버라이드하여 여러분만의 +참조 메커니즘에서 가로채게 하면 유용해집니다. 예를 들어, 하나 이상의 뷰 디렉터리를 +사용하고자 한다면 이렇게 하세요. + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +다른 예제는 각 엔진마다 다른 디렉터리를 사용할 경우입니다. + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +여러분은 이것을 간단하게 확장(extension)으로 만들어 다른 사람들과 공유할 수 있다! + +`find_template`은 그 파일이 실제 존재하는지 검사하지 않음에 유의합니다. +모든 가능한 경로에 대해 주어진 블록을 호출할 뿐입니다. 이것은 성능 문제는 +되지 않습니다. 왜냐하면 `render`는 파일이 발견되는 즉시 `break`하기 때문입니다. +또한, 템플릿 위치(그리고 콘텐츠)는 개발 모드에서 실행 중이 아니라면 캐시될 것입니다. +정말로 멋진 메세드를 작성하고 싶다면 이 점을 명심하세요. + +## 설정(Configuration) + +모든 환경에서, 시작될 때, 한번만 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure do + # 옵션 하나 설정 + set :option, 'value' + + # 여러 옵션 설정 + set :a => 1, :b => 2 + + # `set :option, true`와 동일 + enable :option + + # `set :option, false`와 동일 + disable :option + + # 블록으로 동적인 설정을 할 수도 있음 + set(:css_dir) { File.join(views, 'css') } +end +``` + +환경(APP_ENV 환경 변수)이 `:production`일 때만 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure :production do + ... +end +``` + +환경이 `:production` 또는 `:test`일 때 실행되게 하려면 이렇게 하면 됩니다. + +```ruby +configure :production, :test do + ... +end +``` + +이 옵션들은 `settings`를 통해 접근 가능합니다. + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 공격 방어 설정하기(Configuring attack protection) + +Sinatra는 [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)을 사용하여 +일반적이고 일어날 수 있는 공격에 대비합니다. 이 모듈은 간단하게 비활성시킬 수 있습니다. +(하지만 애플리케이션에 엄청나게 많은 취약성을 야기합니다.) + +```ruby +disable :protection +``` + +하나의 방어층만 스킵하려면, 옵션 해시에 `protection`을 설정하면 됩니다. + +```ruby +set :protection, :except => :path_traversal +``` + +배열로 넘김으로써 방어층 여러 개를 비활성화할 수 있습니다. + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +기본적으로 `:sessions`가 활성 중일 때만 Sinatra는 방어층을 설정합니다. +때로는 자신만의 세션을 설정할 때도 있습니다. 이런 경우 `:session` 옵션을 +넘겨줌으로써 세션을 기반으로한 방어층을 설정 할 수 있습니다. + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 가능한 설정들(Available Settings) + +
    +
    absolute_redirects
    +
    + 만약 비활성이면, Sinatra는 상대경로 리다이렉트를 허용할 것이지만, + 이렇게 되면 Sinatra는 더 이상 오직 절대경로 리다이렉트만 허용하고 있는 + RFC 2616(HTTP 1.1)에 위배됨. +
    +
    + 적정하게 설정되지 않은 리버스 프록시 하에서 앱을 실행 중이라면 활성화시킬 것. + rul 헬퍼는, 만약 두 번째 매개변수로 false를 전달하지만 않는다면, + 여전히 절대경로 URL을 생성할 것임에 유의. +
    +
    기본값은 비활성.
    + +
    add_charset
    +
    + content_type가 문자셋 정보에 자동으로 추가하게 될 마임(mime) 타입. + 이 옵션은 오버라이딩하지 말고 추가해야 함. + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + 메인 애플리케이션 파일의 경로. 프로젝트 루트, 뷰, public 폴더, + 인라인 템플릿을 파악할 때 사용됨. +
    + +
    bind
    +
    바인드할 IP 주소(기본값: 0.0.0.0 이나 + `environment`가 개발로 설정 되어있으면 localhost). 오직 + 빌트인(built-in) 서버에서만 사용됨.
    + +
    default_encoding
    +
    인코딩을 알 수 없을 때 인코딩(기본값은 "utf-8").
    + +
    dump_errors
    +
    로그안의 에러 출력.
    + +
    environment
    +
    + 현재 환경, 기본값은 ENV['APP_ENV'] ENV에 없을 경우엔 "development". +
    + +
    logging
    +
    로거(logger) 사용.
    + +
    lock
    +
    + Ruby 프로세스 당 요청을 동시에 할 경우에만 매 요청에 걸쳐 잠금(lock)을 설정. +
    +
    앱이 스레드에 안전(thread-safe)하지 않으면 활성화시킬 것. 기본값은 비활성.
    + +
    method_override
    +
    + put/delete를 지원하지 않는 브라우저에서 put/delete 폼을 허용하는 + _method 꼼수 사용. +
    + +
    port
    +
    접속 포트. 빌트인 서버에서만 사용됨.
    + +
    prefixed_redirects
    +
    + 절대경로가 주어지지 않은 리다이렉트에 request.script_name를 + 삽입할지 여부를 결정. 활성화 하면 redirect '/foo'는 + redirect to('/foo')처럼 동작. 기본값은 비활성. +
    + +
    protection
    +
    웹 공격 방어를 활성화시킬 건지 여부. 위의 보안 섹션 참조.
    + +
    public_dir
    +
    public_folder의 별칭. 밑을 참조.
    + +
    public_folder
    +
    + public 파일이 제공될 폴더의 경로. + static 파일 제공이 활성화된 경우만 사용됨(아래 static참조). + 만약 설정이 없으면 app_file로부터 유추됨. +
    + +
    reload_templates
    +
    + 요청 간에 템플릿을 리로드(reload)할 건지 여부. 개발 모드에서는 활성됨. +
    + +
    root
    +
    + 프로젝트 루트 디렉터리 경로. 설정이 없으면 app_file 설정으로부터 유추됨. +
    + +
    raise_errors
    +
    + 예외 발생(애플리케이션은 중단됨). + 기본값은 environment"test"인 경우는 활성, 그렇지 않으면 비활성. +
    + +
    run
    +
    + 활성화되면, Sinatra가 웹서버의 시작을 핸들링. + rackup 또는 다른 도구를 사용하는 경우라면 활성화시키지 말 것. +
    + +
    running
    +
    빌트인 서버가 실행 중인가? 이 설정은 변경하지 말 것!
    + +
    server
    +
    + 빌트인 서버로 사용할 서버 또는 서버 목록. + 기본값은 루비구현에 따라 다르며 순서는 우선순위를 의미. +
    + +
    sessions
    +
    + Rack::Session::Cookie를 사용한 쿠키 기반 세션 활성화. + 보다 자세한 정보는 '세션 사용하기' 참조. +
    + +
    show_exceptions
    +
    + 예외 발생 시에 브라우저에 스택 추적을 보임. + 기본값은 environment"development"인 + 경우는 활성, 나머지는 비활성. +
    +
    + :after_handler를 설정함으로써 브라우저에서 + 스택 트레이스를 보여주기 전에 앱에 특화된 에러 핸들링을 + 할 수도 있음. +
    + +
    static
    +
    Sinatra가 정적(static) 파일을 핸들링할 지 여부를 설정.
    +
    이 기능이 가능한 서버를 사용하는 경우라면 비활성시킬 것.
    +
    비활성시키면 성능이 올라감.
    +
    + 기본값은 전통적 방식에서는 활성, 모듈 앱에서는 비활성. +
    + +
    static_cache_control
    +
    + Sinatra가 정적 파일을 제공하는 경우, 응답에 Cache-Control 헤더를 + 추가할 때 설정. cache_control 헬퍼를 사용. + 기본값은 비활성. +
    +
    + 여러 값을 설정할 경우는 명시적으로 배열을 사용할 것: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + true로 설정하면, Thin이 요청을 처리하는데 있어 + EventMachine.defer를 사용하도록 함. +
    + +
    views
    +
    + 뷰 폴더 경로. 설정하지 않은 경우 app_file로부터 유추됨. +
    + +
    x_cascade
    +
    + 라우트를 찾지못했을 때의 X-Cascade 해더를 설정여부. + 기본값은 true +
    +
    + + +## 환경(Environments) + +3가지의 미리 정의된 `environments` `"development"`, `"production"`, `"test"` +가 있습니다. 환경은 `APP_ENV` 환경 변수를 통해서도 설정됩니다. 기본값은 +`"development"`입니다. `"development"` 모드에서는 모든 템플릿들은 요청 간에 +리로드됩니다. 또, `"development"` 모드에서는 특별한 `not_found` 와 `error` +핸들러가 브라우저에서 스택 트레이스를 볼 수 있게합니다. +`"production"`과 `"test"`에서는 기본적으로 템플릿은 캐시됩니다. + +다른 환경으로 실행시키려면 `APP_ENV` 환경 변수를 사용하세요. + +```shell +APP_ENV=production ruby my_app.rb +``` + +현재 설정된 환경이 무엇인지 검사하기 위해서는 준비된 `development?`, `test?`, +`production?` 메서드를 사용할 수 있습니다. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## 에러 처리(Error Handling) + +예외 핸들러는 라우터 및 사전 필터와 동일한 맥락에서 실행됩니다. +이 말인즉, `haml`, `erb`, `halt`같은 이들이 제공하는 모든 것들을 사용할 수 +있다는 뜻입니다. + +### 찾을 수 없음(Not Found) + +`Sinatra::NotFound` 예외가 발생하거나 또는 응답의 상태 코드가 404라면, +`not_found` 핸들러가 호출됩니다. + +```ruby +not_found do + '아무 곳에도 찾을 수 없습니다.' +end +``` + +### 에러 + +`error` 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출됩니다. +하지만 개발 환경에서는 예외 확인 옵션을 `:after_handler`로 설정되어 있을 경우에만 +실행됨을 주의하세요. + +```ruby +set :show_exceptions, :after_handler +``` + +예외 객체는 Rack 변수 `sinatra.error`로부터 얻을 수 있습니다. + +```ruby +error do + '고약한 오류가 발생했군요 - ' + env['sinatra.error'].message +end +``` + +사용자 정의 오류는 이렇게 정의합니다. + +```ruby +error MyCustomError do + '무슨 일이 생겼나면요...' + env['sinatra.error'].message +end +``` + +그런 다음, 이 오류가 발생하면 이렇게 처리합니다. + +```ruby +get '/' do + raise MyCustomError, '안좋은 일' +end +``` + +결과는 이렇습니다. + +``` +무슨 일이 생겼냐면요... 안좋은 일 +``` + +상태 코드에 대해 오류 핸들러를 설치할 수도 있습니다. + +```ruby +error 403 do + '액세스가 금지됨' +end + +get '/secret' do + 403 +end +``` + +범위로 지정할 수도 있습니다. + +```ruby +error 400..510 do + '어이쿠' +end +``` + +Sinatra는 개발 환경에서 동작할 때 브라우저에 괜찮은 스택 트레이스와 추가적인 +디버그 정보를 보여주기 위해 특별한 `not_found` 와 `error` 핸들러를 설치합니다. + +## Rack 미들웨어(Middleware) + +Sinatra는 [Rack](http://rack.github.io/) 위에서 동작하며, Rack은 루비 웹 +프레임워크를 위한 최소한의 표준 인터페이스입니다. Rack이 애플리케이션 개발자들에게 +제공하는 가장 흥미로운 기능은 "미들웨어(middleware)"에 대한 지원입니다. +여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을 +모니터링하거나/조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트입니다. + +Sinatra는 톱레벨의 `use` 메서드를 사용하여 Rack 미들웨어의 파이프라인을 만드는 일을 +식은 죽 먹기로 만듭니다. + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`문법은 [Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(rackup 파일에서 가장 많이 사용)에서 정의한 것과 동일합니다. 예를 들어, `use` 메서드는 +블록이나 여러 개의/가변적인 인자도 받을 수 있습니다. + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack은 로깅, 디버깅, URL 라우팅, 인증, 그리고 세센 핸들링을 위한 다양한 표준 +미들웨어로 분산되어 있습니다. Sinatra는 설정에 기반하여 이들 컴포넌트들 중 +많은 것들을 자동으로 사용하며, 따라서 여러분은 일반적으로는 `use`를 명시적으로 +사용할 필요가 없을 것입니다. + +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) +에서 유용한 미들웨어들을 찾을 수 있습니다. + +## 테스팅(Testing) + +Sinatra 테스트는 많은 Rack 기반 테스팅 라이브러리, 프레임워크를 사용하여 작성가능합니다. +그 중 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)를 권장합니다. + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +주의: Sinatra를 모듈러 방식으로 사용한다면, `Sinatra::Application` +를 앱에서 사용하는 클래스 이름으로 바꾸세요. + +## Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps) + +톱레벨에서 앱을 정의하는 것은 마이크로 앱(micro-app) 수준에서는 잘 동작하지만, +Rack 미들웨어나, Rails 메탈(metal) 또는 서버 컴포넌트를 갖는 간단한 라이브러리, +또는 더 나아가 Sinatra 익스텐션(extension) 같은 재사용 가능한 컴포넌트들을 구축할 +경우에는 심각한 약점이 있습니다. 톱레벨은 마이크로 앱 스타일의 설정을 가정하는 것 +입니다. (즉, 하나의 단일 애플리케이션 파일과 `./public` 및 `./views` 디렉터리, +로깅, 예외 상세 페이지 등등). 이 곳에서 `Sinatra::Base`가 필요합니다. + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base` 서브클래스에서 사용가능한 메서드들은 톱레벨 DSL로 접근 가능한 것들과 +동일합니다. 대부분의 톱레벨 앱들은 다음 두 가지만 수정하면 `Sinatra::Base` 컴포넌트로 +변환 가능합니다. + +* 파일은 `sinatra`가 아닌 `sinatra/base`를 require해야 합니다. + 그렇지 않으면 모든 Sinatra의 DSL 메서드들이 메인 네임스페이스에 불러지게 + 됩니다. +* 앱의 라우터, 예외 핸들러, 필터, 옵션은 `Sinatra::Base`의 서브클래스에 두어야 + 합니다. + +`Sinatra::Base`는 백지상태(blank slate)입니다. 빌트인 서버를 비롯한 대부분의 옵션들이 +기본값으로 꺼져 있습니다. 가능한 옵션들과 그 작동에 대한 상세는 [옵션과 +설정](http://www.sinatrarb.com/configuration.html)을 참조하세요. + +### 모듈(Modular) vs. 전통적 방식(Classic Style) + +일반적인 믿음과는 반대로, 전통적 방식에 잘못된 부분은 없습니다. 여러분 애플리케이션에 +맞다면, 모듈 애플리케이션으로 전환할 필요는 없습니다. + +모듈 방식이 아닌 전통적 방식을 사용할 경우 생기는 주된 단점은 루비 프로세스 당 +하나의 Sinatra 애플리케이션만 사용할 수 있다는 점입니다. 만약 하나 이상을 사용할 +계획이라면 모듈 방식으로 전환하세요. 모듈 방식과 전통적 방식을 섞어쓰지 못할 +이유는 없습니다. + +방식을 전환할 경우에는, 기본값 설정의 미묘한 차이에 유의해야 합니다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    설정전통적 방식모듈
    app_filesinatra를 로딩하는 파일Sinatra::Base를 서브클래싱한 파일
    run$0 == app_filefalse
    logging truefalse
    method_overridetruefalse
    inline_templatestruefalse
    statictrueFile.exist?(public_folder)
    + +### 모듈 애플리케이션(Modular Application) 제공하기 + +모듈 앱을 시작하는 두 가지 일반적인 옵션이 있습니다. +`run!`으로 능동적으로 시작하는 방법은 이렇습니다. + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... 여기에 앱 코드가 온다 ... + + # 루비 파일이 직접 실행될 경우에 서버를 시작 + run! if app_file == $0 +end +``` + +이렇게 시작할 수도 있습니다. + +```shell +ruby my_app.rb +``` + +`config.ru`와 함께 사용할수도 있습니다. 이 경우는 어떠한 Rack 핸들러도 사용할 수 있도록 +허용 합다. + +```ruby +# config.ru +require './my_app' +run MyApp +``` + +실행은 이렇게 합니다. + +```shell +rackup -p 4567 +``` + +### config.ru로 전통적 방식의 애플리케이션 사용하기 + +앱 파일을 다음과 같이 작성합니다. + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +대응하는 `config.ru`는 다음과 같이 작성합니다. + +```ruby +require './app' +run Sinatra::Application +``` + +### 언제 config.ru를 사용할까? + +`config.ru`는 다음 경우에 권장 됩니다. + +* 다른 Rack 핸들러(Passenger, Unicorn, Heroku, ...)로 배포하고자 할 때. +* 하나 이상의 `Sinatra::Base` 서브클래스를 사용하고자 할 때. +* Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때. + +**모듈 방식으로 전환했다는 이유만으로 `config.ru`로 전환할 필요는 없으며, +또한 `config.ru`를 사용한다고 해서 모듈 방식을 사용해야 하는 것도 아닙니다.** + +### Sinatra를 미들웨어로 사용하기 + +Sinatra에서 다른 Rack 미들웨어를 사용할 수 있을 뿐 아니라, +어떤 Sinatra 애플리케이션에서도 순차로 어떠한 Rack 종착점 앞에 미들웨어로 +추가될 수 있습니다. 이 종착점은 다른 Sinatra 애플리케이션이 될 수도 있고, +또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/...)이 될 수도 +있습니다. + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 미들웨어는 사전 필터보다 앞서 실행됨 + use LoginScreen + + before do +unless session['user_name'] + halt "접근 거부됨, 로그인 하세요." +end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 동적인 애플리케이션 생성(Dynamic Application Creation) + +어떤 상수에 할당하지 않고 런타임에서 새 애플리케이션들을 생성하려면, +`Sinatra.new`를 쓰면 됩니다. + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +선택적 인자로 상속할 애플리케이션을 받을 수 있습니다. + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +이 방법은 Sintra 익스텐션을 테스팅하거나 또는 여러분의 라이브러리에서 Sinatra를 +사용할 경우에 특히 유용합니다. + +이 방법은 Sinatra를 미들웨어로 사용하는 것을 아주 쉽게 만들어 주기도 합니다. + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## 범위(Scopes)와 바인딩(Binding) + +현재 어느 범위에 있느냐가 어떤 메서드와 변수를 사용할 수 있는지를 결정합니다. + +### 애플리케이션/클래스 범위 + +모든 Sinatra 애플리케이션은 `Sinatra::Base`의 서브클래스에 대응됩니다. +만약 톱레벨 DSL (`require 'sinatra'`)을 사용한다면, 이 클래스는 +`Sinatra::Application`이며, 그렇지 않을 경우라면 여러분이 명시적으로 생성한 +그 서브클래스가 됩니다. 클래스 레벨에서는 `get` 이나 `before` 같은 메서드들을 +가지나, `request` 객체나 `session` 에는 접근할 수 없습니다. 왜냐면 모든 요청에 +대해 애플리케이션 클래스는 오직 하나이기 때문입니다. + +`set`으로 생성한 옵션들은 클래스 레벨의 메서드들입니다. + +```ruby +class MyApp < Sinatra::Base + # 저기요, 저는 애플리케이션 범위에 있다구요! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요! + end +end +``` + +애플리케이션 범위에는 이런 것들이 있습니다. + +* 애플리케이션 클래스 본문 +* 확장으로 정의된 메서드 +* `helpers`로 전달된 블록 +* `set`의 값으로 사용된 Procs/blocks +* `Sinatra.new`로 전달된 블록 + +범위 객체 (클래스)는 다음과 같이 접근할 수 있습니다. + +* configure 블록으로 전달된 객체를 통해(`configure { |c| ... }`) +* 요청 범위 내에서 `settings` + +### 요청/인스턴스 범위 + +매 요청마다, 애플리케이션 클래스의 새 인스턴스가 생성되고 모든 핸들러 블록은 +그 범위 내에서 실행됩니다. 범위 내에서 여러분은 `request` 와 `session` 객체에 +접근하거나 `erb` 나 `haml` 같은 렌더링 메서드를 호출할 수 있습니다. 요청 범위 +내에서 `settings` 헬퍼를 통해 애플리케이션 범위에 접근 가능합니다. + +```ruby +class MyApp < Sinatra::Base + # 이봐요, 전 애플리케이션 범위에 있다구요! + get '/define_route/:name' do + # '/define_route/:name'의 요청 범위 + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"의 요청 범위 + @value # => nil (동일한 요청이 아님) + end + + "라우터가 정의됨!" + end +end +``` + +요청 범위에는 이런 것들이 있습니다. + +* get/head/post/put/delete/options 블록 +* before/after 필터 +* 헬퍼(helper) 메서드 +* 템플릿/뷰 + +### 위임 범위(Delegation Scope) + +위임 범위(delegation scope)는 메서드를 단순히 클래스 범위로 보냅니다(forward). +하지만 클래스 바인딩을 갖지 않기에 완전히 클래스 범위처럼 동작하지는 않습니다. +오직 명시적으로 위임(delegation) 표시된 메서드들만 사용 가능하고, +또한 클래스 범위와 변수/상태를 공유하지 않습니다 (유의: `self`가 다름). +`Sinatra::Delegator.delegate :method_name`을 호출하여 메서드 위임을 명시적으로 +추가할 수 있습니다. + +위임 범위에는 이런 것들이 있습니다. + +* 톱레벨 바인딩, `require "sinatra"`를 한 경우 +* `Sinatra::Delegator` 믹스인으로 확장된 객체 + +직접 코드를 살펴보길 바랍니다. +[Sinatra::Delegator 믹스인](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +은 [메인 객체를 확장한 것](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)입니다. + +## 명령행(Command Line) + +Sinatra 애플리케이션은 직접 실행할 수 있습니다. + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +옵션들은 다음과 같습니다. + +``` +-h # 도움말 +-p # 포트 설정 (기본값은 4567) +-o # 호스트 설정 (기본값은 0.0.0.0) +-e # 환경 설정 (기본값은 development) +-s # rack 서버/핸들러 지정 (기본값은 thin) +-x # mutex 잠금 켜기 (기본값은 off) +``` + +### 다중 스레드(Multi-threading) + +_Konstantin의 [StackOverflow의 답변][so-answer]에서 가져왔습니다_ + +시나트라는 동시성 모델을 전혀 사용하지 않지만, Thin, Puma, WEBrick 같은 +기저의 Rack 핸들러(서버)는 사용합니다. 시나트라 자신은 스레드에 안전하므로 +랙 핸들러가 동시성 스레드 모델을 사용한다고해도 문제가 되지는 않습니다. +이는 서버를 시작할 때, 서버에 따른 정확한 호출 방법을 사용했을 때의 +이야기입니다. 밑의 예제는 다중 스레드 Thin 서버를 시작하는 방법입니다. + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +서버를 시작하는 명령어는 다음과 같습니다. + +```shell +thin --threaded start +``` + + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## 요구사항(Requirement) + +다음의 루비 버전은 공식적으로 지원됩니다. +
    +
    Ruby 1.8.7
    +
    + 1.8.7은 완전하게 지원되지만, 꼭 그래야할 특별한 이유가 없다면, + 1.9.2로 업그레이드하거나 또는 JRuby나 Rubinius로 전환할 것을 권장합니다. + 1.8.7에 대한 지원은 Sinatra 2.0 이전에는 중단되지 않을 것입니다. + Ruby 1.8.6은 더이상 지원하지 않습니다. +
    + +
    Ruby 1.9.2
    +
    + 1.9.2는 완전하게 지원됩니다. 1.9.2p0은, Sinatra를 실행했을 때 세그먼트 오류가 + 발생할수 있으므로 쓰지 마세요. 공식 지원은 Sinatra 1.5 이전에는 중단되지 않을 + 것입니다. +
    + +
    Ruby 1.9.3
    +
    + 1.9.3은 완전하게 지원되고 권장합니다. 이전 버전에서 1.9.3으로 전환할 경우 모든 + 세션이 무효화되므로 주의하세요. 1.9.3에 대한 지원은 Sinatra 2.0 이전에는 + 중단되지 않을 것입니다. +
    + +
    Ruby 2.x
    +
    + 2.x은 완전하게 지원되고 권장합니다. 현재 공식 지원 중지 계획은 없습니다. +
    + +
    Rubinius
    +
    + Rubinius는 공식적으로 지원됩니다. (Rubinius >= 2.x) + gem install puma를 권장합니다. +
    + +
    JRuby
    +
    + JRuby의 마지막 안정판은 공식적으로 지원됩니다. C 확장을 JRuby와 사용하는 + 것은 권장되지 않습니다. + gem install trinidad를 권장합니다. +
    +
    + +새로 나오는 루비 버전도 주시하고 있습니다. + +다음 루비 구현체들은 공식적으로 지원하지 않지만 +여전히 Sinatra를 실행할 수 있는 것으로 알려져 있습니다. + +* JRuby와 Rubinius 예전 버전 +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 및 1.9.1 (이 버전들은 사용하지 말 것을 권합니다) + +공식적으로 지원하지 않는다는 것의 의미는 무언가가 그 플랫폼에서만 잘못 동작하고, +지원되는 플랫폼에서는 정상적으로 동작할 경우, 우리의 문제가 아니라 그 플랫폼의 문제로 +간주한다는 뜻입니다. + +또한 우리는 CI를 ruby-head (MRI의 이후 릴리즈) 브랜치에 맞춰 실행하지만, +계속해서 변하고 있기 때문에 아무 것도 보장할 수는 없습니다. +앞으로 나올 2.x가 완전히 지원되길 기대합시다. + +Sinatra는 선택한 루비 구현체가 지원하는 어떠한 운영체제에서도 작동해야 +합니다. + +MacRuby를 사용한다면, gem install control_tower 를 실행해 주세요. + +현재 Cardinal, SmallRuby, BlueRuby 또는 1.8.7 이전의 루비 버전에서는 +Sinatra를 실행할 수 없을 것입니다. + +## 최신(The Bleeding Edge) + +Sinatra의 가장 최근 코드를 사용하고자 한다면, 애플리케이션을 마스터 브랜치에 맞춰 +실행하면 되므로 부담가지지 마세요. 하지만 덜 안정적일 것입니다. + +주기적으로 사전배포(prerelease) 젬을 푸시하기 때문에, 최신 기능들을 얻기 위해 +다음과 같이 할 수도 있습니다. + +```shell +gem install sinatra --pre +``` + +### Bundler를 사용하여 + +여러분 애플리케이션을 최신 Sinatra로 실행하고자 한다면, +[Bundler](http://bundler.io)를 사용할 것을 권장합니다. + +우선, 아직 설치하지 않았다면 bundler를 설치합니다. + +```shell +gem install bundler +``` + +그런 다음, 프로젝트 디렉터리에서, `Gemfile`을 만듭니다. + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 다른 의존관계들 +gem 'haml' # 예를 들어, haml을 사용한다면 +gem 'activerecord', '~> 3.0' # 아마도 ActiveRecord 3.x도 필요할 것 +``` + +`Gemfile`안에 애플리케이션의 모든 의존성을 적어야 합니다. +하지만, Sinatra가 직접적인 의존관계에 있는 것들(Rack과 Tilt)은 +Bundler가 자동으로 찾아서 추가할 것입니다. + +이제 앱을 실행할 수 있습니다. + +```shell +bundle exec ruby myapp.rb +``` + +### 직접 하기(Roll Your Own) + +로컬 클론(clone)을 생성한 다음 `$LOAD_PATH`에 `sinatra/lib` 디렉터리를 주고 +여러분 앱을 실행합니다. + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +이후에 Sinatra 소스를 업데이트하려면 이렇게 하세요. + +```shell +cd myapp/sinatra +git pull +``` + +### 전역으로 설치(Install Globally) + +젬을 직접 빌드할 수 있습니다. + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 합니다. + +```shell +sudo rake install +``` + +## 버저닝(Versioning) + +Sinatra는 [시맨틱 버저닝Semantic Versioning](http://semver.org/) +[(번역)](http://surpreem.com/archives/380)의 SemVer, +SemVerTag를 준수합니다. + +## 더 읽을 거리(Further Reading) + +* [프로젝트 웹사이트](http://www.sinatrarb.com/) - 추가 문서들, 뉴스, + 그리고 다른 리소스들에 대한 링크. +* [기여하기](http://www.sinatrarb.com/contributing) - 버그를 찾았나요? + 도움이 필요한가요? 패치를 하셨나요? +* [이슈 트래커](https://github.com/sinatra/sinatra/issues) +* [트위터](https://twitter.com/sinatra) +* [메일링 리스트](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) http://freenode.net +* 슬랙의 [Sinatra & Friends](https://sinatrarb.slack.com)입니다. + [여기](https://sinatra-slack.herokuapp.com/)에서 가입가능합니다. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 튜토리얼 +* [Sinatra Recipes](http://recipes.sinatrarb.com/) 커뮤니티가 만드는 레시피 +* http://www.rubydoc.info/에 있는 [최종 릴리스](http://www.rubydoc.info/gems/sinatra) + 또는 [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra)에 대한 API 문서 +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.malayalam.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.malayalam.md new file mode 100644 index 0000000000..422be089f0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.malayalam.md @@ -0,0 +1,3141 @@ +# സിനാട്ര + +[![gem വേർഷൻ ](https://badge.fury.io/rb/sinatra.svg)](http://badge.fury.io/rb/sinatra) +[![ബിൽഡ് സ്റ്റാറ്റസ്](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) +[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver) + + വെബ് അപ്പ്ലിക്കേഷൻസ് എളുപ്പത്തിൽ ഉണ്ടാക്കാനുള്ള ഒരു ലൈബ്രറി [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) ആണ്.: + +```റൂബി +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +gem ഇൻസ്റ്റാൾ ചെയ്യുവാൻ: + +```shell +gem install sinatra +``` + +റൺ ചെയ്യുവാൻ : + +```shell / ഷെൽ +ruby myapp.rb +``` + +View at: [http://localhost:4567](http://localhost:4567) + + സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാതെ നിങ്ങളുടെ കോഡ് ചേഞ്ച് കാണാൻ സാധിക്കുകയില്ല +കോഡ് ചേഞ്ച് ചെയ്യുമ്പോൾ സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാൻ മറക്കരുത് +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +എപ്പോഴും `gem install thin`,എന്ന് റൺ ചെയ്യുക , ഇത് ഏറ്റവും പുതിയ അപ്ലിക്കേഷൻ സെലക്ട് ചെയ്യാൻ നമ്മളെ സഹായിക്കും . + +## ഉള്ളടക്കം + +* [സിനാട്ര](#sinatra) + * [ഉള്ളടക്കം](#table-of-contents) + * [റൂട്സ്](#routes) + * [കണ്ടിഷൻസ്](#conditions) + * [റിട്ടേൺ വാല്യൂസ്](#return-values) + * [കസ്റ്റമ് റൂട്ട് മടീച്ചേഴ്സ് ](#custom-route-matchers) + * [സ്റ്റാറ്റിക് files](#static-files) + * [വ്യൂസ് / ടെംപ്ലേറ്റ്സ്](#views--templates) + * [ലിറ്ററൽ ടെംപ്ലേറ്റ്സ് ](#literal-templates) + * [ലഭ്യമായ ടെംപ്ലേറ്റ്സ് ഭാഷകൾ ](#available-template-languages) + * [Haml ടെംപ്ലേറ്റ്സ്](#haml-templates) + * [Erb ടെംപ്ലേറ്റ്സ്](#erb-templates) + * [Builder ടെംപ്ലേറ്റ്സ്](#builder-templates) + * [nokogiri ടെംപ്ലേറ്റ്സ്](#nokogiri-templates) + * [Sass ടെംപ്ലേറ്റ്സ്](#sass-templates) + * [SCSS ടെംപ്ലേറ്റ്സ്](#scss-templates) + * [Less ടെംപ്ലേറ്റ്സ്](#less-templates) + * [Liquid ടെംപ്ലേറ്റ്സ്](#liquid-templates) + * [Markdown ടെംപ്ലേറ്റ്സ്](#markdown-templates) + * [Textile ടെംപ്ലേറ്റ്സ്](#textile-templates) + * [RDoc ടെംപ്ലേറ്റ്സ്](#rdoc-templates) + * [AsciiDoc ടെംപ്ലേറ്റ്സ്](#asciidoc-templates) + * [Radius ടെംപ്ലേറ്റ്സ്](#radius-templates) + * [Markaby ടെംപ്ലേറ്റ്സ്](#markaby-templates) + * [RABL ടെംപ്ലേറ്റ്സ്](#rabl-templates) + * [Slim ടെംപ്ലേറ്റ്സ്](#slim-templates) + * [Creole ടെംപ്ലേറ്റ്സ്](#creole-templates) + * [MediaWiki ടെംപ്ലേറ്റ്സ്](#mediawiki-templates) + * [CoffeeScript ടെംപ്ലേറ്റ്സ്](#coffeescript-templates) + * [Stylus ടെംപ്ലേറ്റ്സ്](#stylus-templates) + * [Yajl ടെംപ്ലേറ്റ്സ്](#yajl-templates) + * [WLang ടെംപ്ലേറ്റ്സ്](#wlang-templates) + * [വാരിയബിൾസിനെ എടുക്കാൻ സഹായിക്കുന്ന ടെംപ്ലേറ്റ്സ്](#accessing-variables-in-templates) + * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) + * [Inline ടെംപ്ലേറ്റ്സ്](#inline-templates) + * [പേരുള്ള ടെംപ്ലേറ്റ്സ്](#named-templates) + * [Associating File Extensions](#associating-file-extensions) + * [നിങ്ങളുടെ സ്വന്തം ടെമ്പ്ലേറ്റ് എങ്ങിനെ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു ](#adding-your-own-template-engine) + * [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup) + * [Filters](#filters) + * [Helpers](#helpers) + * [സെഷൻസ് ഉപയോഗിക്കുന്നു ](#using-sessions) + * [രഹസ്യമായി സെഷൻസ് സംരക്ഷിക്കുക ](#session-secret-security) + * [Session Config](#session-config) + * [സെഷൻ middlewate തിരഞ്ഞെടുക്കുക](#choosing-your-own-session-middleware) + * [ഹാൾട് ചെയ്യുക ](#halting) + * [Passing](#passing) + * [മറ്റൊരു റൂട്ട് ട്രിഗർ ചെയ്യുക ](#triggering-another-route) + * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) + * [Streaming Responses](#streaming-responses) + * [Logging](#logging) + * [Mime Types](#mime-types) + * [ URLs Generating](#generating-urls) + * [Browser റീഡിറക്ട് ചെയ്യുക ](#browser-redirect) + * [Cache Control](#cache-control) + * [Sending Files](#sending-files) + * [Accessing the Request Object](#accessing-the-request-object) + * [അറ്റാച്മെന്റ്സ് ](#attachments) + * [ദിവസവും സമയവും ഡീൽ ചെയ്യക ](#dealing-with-date-and-time) + * [Template Files നോക്കുന്നു ](#looking-up-template-files) + * [Configuration](#configuration) + * [Configuring attack protection](#configuring-attack-protection) + * [Available Settings](#available-settings) + * [Environments](#environments) + * [ കൈകാര്യം ചെയ്യുക ](#error-handling) + * [കണ്ടെത്താൻ ആയില്ല ](#not-found) + * [തെറ്റ്](#error) + * [Rack Middleware](#rack-middleware) + * [ടെസ്റ്റ് ചെയ്യുക ](#testing) + * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) + * [Modular vs. Classic Style](#modular-vs-classic-style) + * [Serving a Modular Application](#serving-a-modular-application) + * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) + * [When to use a config.ru?](#when-to-use-a-configru) + * [Using Sinatra as Middleware](#using-sinatra-as-middleware) + * [Dynamic Application Creation](#dynamic-application-creation) + * [Scopes and Binding](#scopes-and-binding) + * [Application/Class Scope](#applicationclass-scope) + * [Request/Instance Scope](#requestinstance-scope) + * [Delegation Scope](#delegation-scope) + * [Command Line](#command-line) + * [Multi-threading](#multi-threading) + * [ആവശ്യങ്ങൾ ](#requirement) + * [The Bleeding Edge](#the-bleeding-edge) + * [With Bundler](#with-bundler) + * [വേർഷൻ ചെയ്യുക ](#versioning) + * [Further Reading](#further-reading) + +## Routes + +In Sinatra, a route is an HTTP method paired with a URL-matching pattern. +Each route is associated with a block: + +```റൂബി +get '/' do + .. show something .. +end + +post '/' do + .. create something .. +end + +put '/' do + .. replace something .. +end + +patch '/' do + .. modify something .. +end + +delete '/' do + .. annihilate something .. +end + +options '/' do + .. appease something .. +end + +link '/' do + .. affiliate something .. +end + +unlink '/' do + .. separate something .. +end +``` + +റൂട്സ് മാച്ച് ചെയ്യാനാ രീതിയില് ആണ് അത് നിർവചിക്കുന്നത് . ഏത് റിക്വസ്റ്റ് ആണോ റൂട്ട് ആയി ചേരുന്നത് ആ റൂട്ട് ആണ് വിളിക്കപെടുക . + +ട്രെയ്ലറിങ് സ്ലാഷ്‌സ് ഉള്ള റൂട്സ് അത് ഇല്ലാത്തതിൽ നിന്ന് വ്യത്യാസം ഉള്ളത് ആണ് : + +```ruby +get '/foo' do + # Does not match "GET /foo/" +end +``` + +Route patterns may include named parameters, accessible via the +`params` hash: + +```ruby +get '/hello/:name' do + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + "Hello #{params['name']}!" +end +``` + +```ruby +get '/hello/:name' do |n| + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + # n stores params['name'] + "Hello #{n}!" +end +``` +റൂട്ട് പാട്ടേഴ്സിൽ പേരുള്ള splat ഉണ്ടാകാറുണ്ട് അതിനെ 'params['splat']' array ഉപയോഗിച്ച ഉപയോഗപ്പെടുത്താവുന്നത് ആണ് + + + +```ruby +get '/say/*/to/*' do + # matches /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # matches /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Or with block parameters: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +റെഗുലർ expressions : + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +ബ്ലോക്ക് പരാമീറ്റർസ് ഉള്ള റൂട്ട് മാച്ചിങ് : + +```ruby +get %r{/hello/([\w]+)} do |c| + # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc. + "Hello, #{c}!" +end +``` + + + +```ruby +get '/posts/:format?' do + # matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc +end +``` +റൂട്ട് പാറ്റെൺസ് ഇത് query പരാമീറ്റർസ് ഉണ്ടാകാം : + + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` +അതുപോലെ നിങ്ങൾ പാത ട്രവേഴ്സല് അറ്റാച്ച് പ്രൊട്ടക്ഷൻ (#configuring-attack-protection) ഡിസബിലെ ചെയ്തട്ടില്ലെങ്കിൽ റെക്‌സ് പാത മോഡിഫിയ ചെയ്യണത്തിനു മുൻപ് അത് മാച്ച് ചെയ്യപ്പെടും + + +You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme) +options used for a given route by passing in a `:mustermann_opts` hash: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` +ഇത് [condition](#conditions), പോലെ തോന്നുമെങ്കിലും ഇ ഒപ്റേൻസ് global `:mustermann_opts` ആയി മെർജ് ചെയ്യപ്പെട്ടിരിക്കുന്നു + +## കണ്ടിഷൻസ് + +യൂസർ അഗെന്റ്റ് പോലുള്ള മാച്ചിങ് റൂട്സ് ഇത് അടങ്ങി ഇരിക്കുന്നു +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Matches non-songbird browsers +end +``` + + ഇതുപോലുള്ള വേറെ കണ്ടിഷൻസ് ആണ് host_name , provides +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides ` ആക്‌സെപ്റ് ഹെൽഡർസ് നെ അന്വഷിക്കുന്നു + + നിങ്ങളുടെ കണ്ടിഷൻസ് ഇനി എളുപ്പത്തിൽ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +splat ഉപയോഗിച്ച പലതരത്തിൽ ഉള്ള കണ്ടിഷൻസ് ഉണ്ടാക്കാൻ സാധിക്കുന്നു : + +```ruby +set(:auth) do |*roles| # <- notice the splat here + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +## Return Values + + + + +റൂട്ട് ബ്ലോക്കിന്റെ റിട്ടേൺ വാല്യൂ HTTP client യിലേക്ക് കടത്തിവിടുന്ന രേസ്പോൻസ് ബോഡിയെ തീരുമാനിക്കുന്നു. സാധാരണയായി ഇത് ഒരു സ്ട്രിംഗ് ആണ്. പക്ഷെ മറ്റു വാല്യൂകളെയും ഇത് സ്വീകരിക്കും + +* മൂന്ന് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), headers (Hash), response + body (responds to #each)]` +* രണ്ട് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), response body (responds to + #each)]` +* An object that responds to `#each` and passes nothing but strings to + the given block +* Integer സ്റ്റാറ്റസ് കോഡിനെ കാണിക്കുന്നു + +ഇത് നമക്ക് സ്ട്രീമിംഗ് ഉദാഹരണങ്ങൾ ഉണ്ടാക്കാം +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +`stream` helper' ഉപയോഗിച്ച([described below](#streaming-responses))റൂട്ട് ഇലെ ബോയ്ലർ പ്ലേറ്റ്സ് ഇനി കുറക്കാം + +## Custom Route Matchers + +മുകളിൽ കാണിച്ചിരിക്കുന്ന പോലെ , സിനാട്ര ഉപയോഗിച്ച String patterns, regular expressions കൈകാര്യം ചെയ്യാം മാത്രമല്ല നിങ്ങളുടെ സ്വന്തം matchers ഉം ഉണ്ടാക്കാം +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +ഇതിനെ ഇങ്ങനെയും കാണിക്കാം + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Or, using negative look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Static Files + +സ്റ്റാറ്റിക് ഫിലെസ് `./public` എന്ന ഡയറക്ടറി ഇത് ആണ് ഉണ്ടാകുക +നിങ്ങൾക്ക് `:public_folder` വഴി വേറെ പാത ഉണ്ടാക്കാം + + +```ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +URL ഇളിൽ ഡയറക്ടറി പാത ഉണ്ടാകില്ല . A file +`./public/css/style.css` is made available as +`http://example.com/css/style.css`. + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info. + +## Views / Templates + +എല്ലാ ടെമ്പ്ലേറ്റ് ഭാഷയും അതിന്റെ സ്വതം റെൻഡറിങ് മെതോഡിൽ ആണ് പുറത്തു കാണപ്പെടുക. ഇത് ഒരു സ്ട്രിംഗ് ഇനി റിട്ടേൺ ചെയ്യും + +```ruby +get '/' do + erb :index +end +``` + +This renders `views/index.erb`. + +ടെമ്പ്ലേറ്റ് ഇന്റെ പേരിനു പകരം നിങ്ങൾക്ക് ടെപ്ലേറ്റ് ഇന്റെ കോൺടെന്റ് കടത്തി വിടാം + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +ടെമ്പ്ലേറ്റ് മറ്റൊരു അർജുമെന്റിനെ കടത്തി വിടുന്നു +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +This will render `views/index.erb` embedded in the +`views/post.erb` (default is `views/layout.erb`, if it exists). + +സിനാട്ര ക്ക് മനസ്സിലാകാത്ത ടെമ്പ്ലേറ്റ് ഇനി ടെമ്പ്ലേറ്റ് എന്ജിനിലേക്ക് കടത്തി വിടും : + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +നിങ്ങൾക്ക് ഓപ്ഷണൽ ആയി ലാംഗ്വേജ് ജനറലിൽ സെറ്റ് ചെയ്യാൻ കഴിയും : + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +ഉപയോഗിച്ച റെൻഡർ മെതോഡിൽ ഒപ്റേൻസ് പാസ് ചെയ്യാൻ പാട്ടും `set`. + +Available Options: + +
    +
    locals
    +
    + List of locals passed to the document. Handy with partials. + Example: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding to use if uncertain. Defaults to + settings.default_encoding. +
    + +
    views
    +
    + Views folder to load templates from. Defaults to settings.views. +
    + +
    layout
    +
    + Whether to use a layout (true or false). If it's a + Symbol, specifies what template to use. Example: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type the template produces. Default depends on template language. +
    + +
    scope
    +
    + Scope to render template under. Defaults to the application + instance. If you change this, instance variables and helper methods + will not be available. +
    + +
    layout_engine
    +
    + Template engine to use for rendering the layout. Useful for + languages that do not support layouts otherwise. Defaults to the + engine used for the template. Example: set :rdoc, :layout_engine + => :erb +
    + +
    layout_options
    +
    + Special options only used for rendering the layout. Example: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Templates are assumed to be located directly under the `./views` +directory. To use a different views directory: + +```ruby +set :views, settings.root + '/templates' +``` + + +One important thing to remember is that you always have to reference +templates with symbols, even if they're in a subdirectory (in this case, +use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a +symbol because otherwise rendering methods will render any strings +passed to them directly. + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Renders the template string. You can optionally specify `:path` and +`:line` for a clearer backtrace if there is a filesystem path or line +associated with that string: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Available Template Languages + +Some languages have multiple implementations. To specify what implementation +to use (and to be thread-safe), you should simply require it first: + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependencyhaml
    File Extension.haml
    Examplehaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependency + erubis + or erb (included in Ruby) +
    File Extensions.erb, .rhtml or .erubis (Erubis only)
    Exampleerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependency + builder +
    File Extension.builder
    Examplebuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependencynokogiri
    File Extension.nokogiri
    Examplenokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.sass
    Examplesass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.scss
    Examplescss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependencyless
    File Extension.less
    Exampleless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependencyliquid
    File Extension.liquid
    Exampleliquid :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods (except for `yield`) from a Liquid +template, you almost always want to pass locals to it. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependency + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    File Extensions.markdown, .mkd and .md
    Examplemarkdown :index, :layout_engine => :erb
    + +It is not possible to call methods from Markdown, nor to pass locals to it. +You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Note that you may also call the `markdown` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Since you cannot call Ruby from Markdown, you cannot use layouts written in +Markdown. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### Textile Templates + + + + + + + + + + + + + + +
    DependencyRedCloth
    File Extension.textile
    Exampletextile :index, :layout_engine => :erb
    + +It is not possible to call methods from Textile, nor to pass locals to +it. You therefore will usually use it in combination with another +rendering engine: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Note that you may also call the `textile` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Since you cannot call Ruby from Textile, you cannot use layouts written in +Textile. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependencyRDoc
    File Extension.rdoc
    Examplerdoc :README, :layout_engine => :erb
    + +It is not possible to call methods from RDoc, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Note that you may also call the `rdoc` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Since you cannot call Ruby from RDoc, you cannot use layouts written in +RDoc. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    DependencyAsciidoctor
    File Extension.asciidoc, .adoc and .ad
    Exampleasciidoc :README, :layout_engine => :erb
    + +Since you cannot call Ruby methods directly from an AsciiDoc template, you +almost always want to pass locals to it. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependencyRadius
    File Extension.radius
    Exampleradius :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods directly from a Radius template, you +almost always want to pass locals to it. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependencyMarkaby
    File Extension.mab
    Examplemarkaby { h1 "Welcome!" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependencyRabl
    File Extension.rabl
    Examplerabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependencySlim Lang
    File Extension.slim
    Exampleslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependencyCreole
    File Extension.creole
    Examplecreole :wiki, :layout_engine => :erb
    + +It is not possible to call methods from Creole, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note that you may also call the `creole` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Since you cannot call Ruby from Creole, you cannot use layouts written in +Creole. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependencyWikiCloth
    File Extension.mediawiki and .mw
    Examplemediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass +locals to it. You therefore will usually use it in combination with +another rendering engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Since you cannot call Ruby from MediaWiki, you cannot use layouts written in +MediaWiki. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependency + + CoffeeScript + and a + + way to execute javascript + +
    File Extension.coffee
    Examplecoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependency + + Stylus + and a + + way to execute javascript + +
    File Extension.styl
    Examplestylus :index
    + +Before being able to use Stylus templates, you need to load `stylus` and +`stylus/tilt` first: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependencyyajl-ruby
    File Extension.yajl
    Example + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + + +The template source is evaluated as a Ruby string, and the +resulting json variable is converted using `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +The `:callback` and `:variable` options can be used to decorate the rendered +object: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    DependencyWLang
    File Extension.wlang
    Examplewlang :index, :locals => { :key => 'value' }
    + +Since calling ruby methods is not idiomatic in WLang, you almost always +want to pass locals to it. Layouts written in WLang and `yield` are +supported, though. + +### Accessing Variables in Templates + +Templates are evaluated within the same context as route handlers. Instance +variables set in route handlers are directly accessible by templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Or, specify an explicit Hash of local variables: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +This is typically used when rendering templates as partials from within +other templates. + +### Templates with `yield` and nested layouts + +A layout is usually just a template that calls `yield`. +Such a template can be used either through the `:template` option as +described above, or it can be rendered with a block as follows: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +This code is mostly equivalent to `erb :index, :layout => :post`. + +Passing blocks to rendering methods is most useful for creating nested +layouts: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +This can also be done in fewer lines of code with: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Currently, the following rendering methods accept a block: `erb`, `haml`, +`liquid`, `slim `, `wlang`. Also the general `render` method accepts a block. + +### Inline Templates + +Templates may be defined at the end of the source file: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +NOTE: Inline templates defined in the source file that requires sinatra are +automatically loaded. Call `enable :inline_templates` explicitly if you +have inline templates in other source files. + +### Named Templates + +Templates may also be defined using the top-level `template` method: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +If a template named "layout" exists, it will be used each time a template +is rendered. You can individually disable layouts by passing +`:layout => false` or disable them by default via +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associating File Extensions + +To associate a file extension with a template engine, use +`Tilt.register`. For instance, if you like to use the file extension +`tt` for Textile templates, you can do the following: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adding Your Own Template Engine + +First, register your engine with Tilt, then create a rendering method: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renders `./views/index.myat`. Learn more about +[Tilt](https://github.com/rtomayko/tilt#readme). + +### Using Custom Logic for Template Lookup + +To implement your own template lookup mechanism you can write your +own `#find_template` method: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filters + +Before filters are evaluated before each request within the same context +as the routes will be and can modify the request and response. Instance +variables set in filters are accessible by routes and templates: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After filters are evaluated after each request within the same context +as the routes will be and can also modify the request and response. +Instance variables set in before filters and routes are accessible by +after filters: + +```ruby +after do + puts response.status +end +``` + +Note: Unless you use the `body` method rather than just returning a +String from the routes, the body will not yet be available in the after +filter, since it is generated later on. + +Filters optionally take a pattern, causing them to be evaluated only if the +request path matches that pattern: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Like routes, filters also take conditions: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Use the top-level `helpers` method to define helper methods for use in +route handlers and templates: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Alternatively, helper methods can be separately defined in a module: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +The effect is the same as including the modules in the application class. + +### Using Sessions + +A session is used to keep state during requests. If activated, you have one +session hash per user session: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### Session Secret Security + +To improve security, the session data in the cookie is signed with a session +secret using `HMAC-SHA1`. This session secret should optimally be a +cryptographically secure random value of an appropriate length which for +`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex +characters). You would be advised not to use a secret that is less than 32 +bytes of randomness (256 bits, 64 hex characters). It is therefore **very +important** that you don't just make the secret up, but instead use a secure +random number generator to create it. Humans are extremely bad at generating +random values. + +By default, a 32 byte secure random session secret is generated for you by +Sinatra, but it will change with every restart of your application. If you +have multiple instances of your application, and you let Sinatra generate the +key, each instance would then have a different session key which is probably +not what you want. + +For better security and usability it's +[recommended](https://12factor.net/config) that you generate a secure random +secret and store it in an environment variable on each host running your +application so that all of your application instances will share the same +secret. You should periodically rotate this session secret to a new value. +Here are some examples of how you might create a 64 byte secret and set it: + +**Session Secret Generation** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Generation (Bonus Points)** + +Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to +prefer use of system RNG facilities to generate random values instead of +userspace `OpenSSL` which MRI Ruby currently defaults to: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Environment Variable** + +Set a `SESSION_SECRET` environment variable for Sinatra to the value you +generated. Make this value persistent across reboots of your host. Since the +method for doing this will vary across systems this is for illustrative +purposes only: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Session Secret App Config** + +Setup your app config to fail-safe to a secure random secret +if the `SESSION_SECRET` environment variable is not available. + +For bonus points use the [sysrandom +gem](https://github.com/cryptosphere/sysrandom#readme) here as well: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Session Config + +If you want to configure it further, you may also store a hash with options +in the `sessions` setting: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +To share your session across other apps on subdomains of foo.com, prefix the +domain with a *.* like this instead: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Choosing Your Own Session Middleware + +Note that `enable :sessions` actually stores all data in a cookie. This +might not always be what you want (storing lots of data will increase your +traffic, for instance). You can use any Rack session middleware in order to +do so, one of the following methods can be used: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Or to set up sessions with a hash of options: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Another option is to **not** call `enable :sessions`, but instead pull in +your middleware of choice as you would any other middleware. + +It is important to note that when using this method, session based +protection **will not be enabled by default**. + +The Rack middleware to do that will also need to be added: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +See '[Configuring attack protection](#configuring-attack-protection)' for more information. + +### Halting + +To immediately stop a request within a filter or route use: + +```ruby +halt +``` + +You can also specify the status when halting: + +```ruby +halt 410 +``` + +Or the body: + +```ruby +halt 'this will be the body' +``` + +Or both: + +```ruby +halt 401, 'go away!' +``` + +With headers: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +It is of course possible to combine a template with `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +A route can punt processing to the next matching route using `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +The route block is immediately exited and control continues with the next +matching route. If no matching route is found, a 404 is returned. + +### Triggering Another Route + +Sometimes `pass` is not what you want, instead you would like to get the +result of calling another route. Simply use `call` to achieve this: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note that in the example above, you would ease testing and increase +performance by simply moving `"bar"` into a helper used by both `/foo` and +`/bar`. + +If you want the request to be sent to the same application instance rather +than a duplicate, use `call!` instead of `call`. + +Check out the Rack specification if you want to learn more about `call`. + +### Setting Body, Status Code and Headers + +It is possible and recommended to set the status code and response body with +the return value of the route block. However, in some scenarios you might +want to set the body at an arbitrary point in the execution flow. You can do +so with the `body` helper method. If you do so, you can use that method from +there on to access the body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +It is also possible to pass a block to `body`, which will be executed by the +Rack handler (this can be used to implement streaming, [see "Return Values"](#return-values)). + +Similar to the body, you can also set the status code and headers: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Like `body`, `headers` and `status` with no arguments can be used to access +their current values. + +### Streaming Responses + +Sometimes you want to start sending out data while still generating parts of +the response body. In extreme examples, you want to keep sending data until +the client closes the connection. You can use the `stream` helper to avoid +creating your own wrapper: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +This allows you to implement streaming APIs, +[Server Sent Events](https://w3c.github.io/eventsource/), and can be used as +the basis for [WebSockets](https://en.wikipedia.org/wiki/WebSocket). It can +also be used to increase throughput if some but not all content depends on a +slow resource. + +Note that the streaming behavior, especially the number of concurrent +requests, highly depends on the web server used to serve the application. +Some servers might not even support streaming at all. If the server does not +support streaming, the body will be sent all at once after the block passed +to `stream` finishes executing. Streaming does not work at all with Shotgun. + +If the optional parameter is set to `keep_open`, it will not call `close` on +the stream object, allowing you to close it at any later point in the +execution flow. This only works on evented servers, like Thin and Rainbows. +Other servers will still close the stream: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +It's also possible for the client to close the connection when trying to +write to the socket. Because of this, it's recommended to check +`out.closed?` before trying to write. + +### Logging + +In the request scope, the `logger` helper exposes a `Logger` instance: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +This logger will automatically take your Rack handler's logging settings into +account. If logging is disabled, this method will return a dummy object, so +you do not have to worry about it in your routes and filters. + +Note that logging is only enabled for `Sinatra::Application` by default, so +if you inherit from `Sinatra::Base`, you probably want to enable it yourself: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +To avoid any logging middleware to be set up, set the `logging` setting to +`nil`. However, keep in mind that `logger` will in that case return `nil`. A +common use case is when you want to set your own logger. Sinatra will use +whatever it will find in `env['rack.logger']`. + +### Mime Types + +When using `send_file` or static files you may have mime types Sinatra +doesn't understand. Use `mime_type` to register them by file extension: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +You can also use it with the `content_type` helper: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generating URLs + +For generating URLs you should use the `url` helper method, for instance, in +Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +It takes reverse proxies and Rack routers into account, if present. + +This method is also aliased to `to` (see [below](#browser-redirect) for an example). + +### Browser Redirect + +You can trigger a browser redirect with the `redirect` helper method: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Any additional parameters are handled like arguments passed to `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +You can also easily redirect back to the page the user came from with +`redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +To pass arguments with a redirect, either add them to the query: + +```ruby +redirect to('/bar?sum=42') +``` + +Or use a session: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache Control + +Setting your headers correctly is the foundation for proper HTTP caching. + +You can easily set the Cache-Control header like this: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Pro tip: Set up caching in a before filter: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +If you are using the `expires` helper to set the corresponding header, +`Cache-Control` will be set automatically for you: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +To properly use caches, you should consider using `etag` or `last_modified`. +It is recommended to call those helpers *before* doing any heavy lifting, as +they will immediately flush a response if the client already has the current +version in its cache: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +It is also possible to use a +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +These helpers will not do any caching for you, but rather feed the necessary +information to your cache. If you are looking for a quick +reverse-proxy caching solution, try +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info to static files. + +According to RFC 2616, your application should behave differently if the +If-Match or If-None-Match header is set to `*`, depending on whether the +resource requested is already in existence. Sinatra assumes resources for +safe (like get) and idempotent (like put) requests are already in existence, +whereas other resources (for instance post requests) are treated as new +resources. You can change this behavior by passing in a `:new_resource` +option: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +If you still want to use a weak ETag, pass in a `:kind` option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Sending Files + +To return the contents of a file as the response, you can use the `send_file` +helper method: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +It also takes options: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +The options are: + +
    +
    filename
    +
    File name to be used in the response, + defaults to the real file name.
    +
    last_modified
    +
    Value for Last-Modified header, defaults to the file's mtime.
    + +
    type
    +
    Value for Content-Type header, guessed from the file extension if + missing.
    + +
    disposition
    +
    + Value for Content-Disposition header, possible values: nil + (default), :attachment and :inline +
    + +
    length
    +
    Value for Content-Length header, defaults to file size.
    + +
    status
    +
    + Status code to be sent. Useful when sending a static file as an error + page. If supported by the Rack handler, other means than streaming + from the Ruby process will be used. If you use this helper method, + Sinatra will automatically handle range requests. +
    +
    + +### Accessing the Request Object + +The incoming request object can be accessed from request level (filter, +routes, error handlers) through the `request` method: + +```ruby +# app running on http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # request body sent by the client (see below) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # length of request.body + request.media_type # media type of request.body + request.host # "example.com" + request.get? # true (similar methods for other verbs) + request.form_data? # false + request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. + request.referrer # the referrer of the client or '/' + request.user_agent # user agent (used by :agent condition) + request.cookies # hash of browser cookies + request.xhr? # is this an ajax request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # client IP address + request.secure? # false (would be true over ssl) + request.forwarded? # true (if running behind a reverse proxy) + request.env # raw env hash handed in by Rack +end +``` + +Some options, like `script_name` or `path_info`, can also be written: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +The `request.body` is an IO or StringIO object: + +```ruby +post "/api" do + request.body.rewind # in case someone already read it + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Attachments + +You can use the `attachment` helper to tell the browser the response should +be stored on disk rather than displayed in the browser: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +You can also pass it a file name: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Dealing with Date and Time + +Sinatra offers a `time_for` helper method that generates a Time object from +the given value. It is also able to convert `DateTime`, `Date` and similar +classes: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +This method is used internally by `expires`, `last_modified` and akin. You +can therefore easily extend the behavior of those methods by overriding +`time_for` in your application: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Looking Up Template Files + +The `find_template` helper is used to find template files for rendering: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +This is not really useful. But it is useful that you can actually override +this method to hook in your own lookup mechanism. For instance, if you want +to be able to use more than one view directory: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Another example would be using different directories for different engines: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +You can also easily wrap this up in an extension and share with others! + +Note that `find_template` does not check if the file really exists but +rather calls the given block for all possible paths. This is not a +performance issue, since `render` will use `break` as soon as a file is +found. Also, template locations (and content) will be cached if you are not +running in development mode. You should keep that in mind if you write a +really crazy method. + +## Configuration + +Run once, at startup, in any environment: + +```ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +``` + +Run only when the environment (`APP_ENV` environment variable) is set to +`:production`: + +```ruby +configure :production do + ... +end +``` + +Run when the environment is set to either `:production` or `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +You can access those options via `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configuring attack protection + +Sinatra is using +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) to +defend your application against common, opportunistic attacks. You can +easily disable this behavior (which will open up your application to tons +of common vulnerabilities): + +```ruby +disable :protection +``` + +To skip a single defense layer, set `protection` to an options hash: + +```ruby +set :protection, :except => :path_traversal +``` +You can also hand in an array in order to disable a list of protections: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +By default, Sinatra will only set up session based protection if `:sessions` +have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up +sessions "outside" of the Sinatra app, such as in the config.ru or with a +separate `Rack::Builder` instance. In that case you can still set up session +based protection by passing the `:session` option: + +```ruby +set :protection, :session => true +``` + +### Available Settings + +
    +
    absolute_redirects
    +
    + If disabled, Sinatra will allow relative redirects, however, Sinatra + will no longer conform with RFC 2616 (HTTP 1.1), which only allows + absolute redirects. +
    +
    + Enable if your app is running behind a reverse proxy that has not been + set up properly. Note that the url helper will still produce + absolute URLs, unless you pass in false as the second + parameter. +
    +
    Disabled by default.
    + +
    add_charset
    +
    + Mime types the content_type helper will automatically add the + charset info to. You should add to it rather than overriding this + option: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path to the main application file, used to detect project root, views + and public folder and inline templates. +
    + +
    bind
    +
    + IP address to bind to (default: 0.0.0.0 or + localhost if your `environment` is set to development). Only + used for built-in server. +
    + +
    default_encoding
    +
    Encoding to assume if unknown (defaults to "utf-8").
    + +
    dump_errors
    +
    Display errors in the log.
    + +
    environment
    +
    + Current environment. Defaults to ENV['APP_ENV'], or + "development" if not available. +
    + +
    logging
    +
    Use the logger.
    + +
    lock
    +
    + Places a lock around every request, only running processing on request + per Ruby process concurrently. +
    +
    Enabled if your app is not thread-safe. Disabled by default.
    + +
    method_override
    +
    + Use _method magic to allow put/delete forms in browsers that + don't support it. +
    + +
    mustermann_opts
    +
    + A default hash of options to pass to Mustermann.new when compiling routing + paths. +
    + +
    port
    +
    Port to listen on. Only used for built-in server.
    + +
    prefixed_redirects
    +
    + Whether or not to insert request.script_name into redirects + if no absolute path is given. That way redirect '/foo' would + behave like redirect to('/foo'). Disabled by default. +
    + +
    protection
    +
    + Whether or not to enable web attack protections. See protection section + above. +
    + +
    public_dir
    +
    Alias for public_folder. See below.
    + +
    public_folder
    +
    + Path to the folder public files are served from. Only used if static + file serving is enabled (see static setting below). Inferred + from app_file setting if not set. +
    + +
    quiet
    +
    + Disables logs generated by Sinatra's start and stop commands. + false by default. +
    + +
    reload_templates
    +
    + Whether or not to reload templates between requests. Enabled in + development mode. +
    + +
    root
    +
    + Path to project root folder. Inferred from app_file setting + if not set. +
    + +
    raise_errors
    +
    + Raise exceptions (will stop application). Enabled by default when + environment is set to "test", disabled otherwise. +
    + +
    run
    +
    + If enabled, Sinatra will handle starting the web server. Do not + enable if using rackup or other means. +
    + +
    running
    +
    Is the built-in server running now? Do not change this setting!
    + +
    server
    +
    + Server or list of servers to use for built-in server. Order indicates + priority, default depends on Ruby implementation. +
    + +
    server_settings
    +
    + If you are using a WEBrick web server, presumably for your development + environment, you can pass a hash of options to server_settings, + such as SSLEnable or SSLVerifyClient. However, web + servers such as Puma and Thin do not support this, so you can set + server_settings by defining it as a method when you call + configure. +
    + +
    sessions
    +
    + Enable cookie-based sessions support using + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    session_store
    +
    + The Rack session middleware used. Defaults to + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    show_exceptions
    +
    + Show a stack trace in the browser when an exception happens. Enabled by + default when environment is set to "development", + disabled otherwise. +
    +
    + Can also be set to :after_handler to trigger app-specified + error handling before showing a stack trace in the browser. +
    + +
    static
    +
    Whether Sinatra should handle serving static files.
    +
    Disable when using a server able to do this on its own.
    +
    Disabling will boost performance.
    +
    + Enabled by default in classic style, disabled for modular apps. +
    + +
    static_cache_control
    +
    + When Sinatra is serving static files, set this to add + Cache-Control headers to the responses. Uses the + cache_control helper. Disabled by default. +
    +
    + Use an explicit array when setting multiple values: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + If set to true, will tell Thin to use + EventMachine.defer for processing the request. +
    + +
    traps
    +
    Whether Sinatra should handle system signals.
    + +
    views
    +
    + Path to the views folder. Inferred from app_file setting if + not set. +
    + +
    x_cascade
    +
    + Whether or not to set the X-Cascade header if no route matches. + Defaults to true. +
    +
    + +## Environments + +There are three predefined `environments`: `"development"`, +`"production"` and `"test"`. Environments can be set through the +`APP_ENV` environment variable. The default value is `"development"`. +In the `"development"` environment all templates are reloaded between +requests, and special `not_found` and `error` handlers display stack +traces in your browser. In the `"production"` and `"test"` environments, +templates are cached by default. + +To run different environments, set the `APP_ENV` environment variable: + +```shell +APP_ENV=production ruby my_app.rb +``` + +You can use predefined methods: `development?`, `test?` and `production?` to +check the current environment setting: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Error Handling + +Error handlers run within the same context as routes and before filters, +which means you get all the goodies it has to offer, like `haml`, `erb`, +`halt`, etc. + +### Not Found + +When a `Sinatra::NotFound` exception is raised, or the response's status +code is 404, the `not_found` handler is invoked: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +The `error` handler is invoked any time an exception is raised from a route +block or a filter. But note in development it will only run if you set the +show exceptions option to `:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +The exception object can be obtained from the `sinatra.error` Rack variable: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Custom errors: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Then, if this happens: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +You get this: + +``` +So what happened was... something bad +``` + +Alternatively, you can install an error handler for a status code: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Or a range: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installs special `not_found` and `error` handlers when +running under the development environment to display nice stack traces +and additional debugging information in your browser. + +## Rack Middleware + +Sinatra rides on [Rack](https://rack.github.io/), a minimal standard +interface for Ruby web frameworks. One of Rack's most interesting +capabilities for application developers is support for "middleware" -- +components that sit between the server and your application monitoring +and/or manipulating the HTTP request/response to provide various types +of common functionality. + +Sinatra makes building Rack middleware pipelines a cinch via a top-level +`use` method: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +The semantics of `use` are identical to those defined for the +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(most frequently used from rackup files). For example, the `use` method +accepts multiple/variable args as well as blocks: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack is distributed with a variety of standard middleware for logging, +debugging, URL routing, authentication, and session handling. Sinatra uses +many of these components automatically based on configuration so you +typically don't have to `use` them explicitly. + +You can find useful middleware in +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testing + +Sinatra tests can be written using any Rack-based testing library or +framework. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +is recommended: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Note: If you are using Sinatra in the modular style, replace +`Sinatra::Application` above with the class name of your app. + +## Sinatra::Base - Middleware, Libraries, and Modular Apps + +Defining your app at the top-level works well for micro-apps but has +considerable drawbacks when building reusable components such as Rack +middleware, Rails metal, simple libraries with a server component, or even +Sinatra extensions. The top-level assumes a micro-app style configuration +(e.g., a single application file, `./public` and `./views` +directories, logging, exception detail page, etc.). That's where +`Sinatra::Base` comes into play: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +The methods available to `Sinatra::Base` subclasses are exactly the same +as those available via the top-level DSL. Most top-level apps can be +converted to `Sinatra::Base` components with two modifications: + +* Your file should require `sinatra/base` instead of `sinatra`; + otherwise, all of Sinatra's DSL methods are imported into the main + namespace. +* Put your app's routes, error handlers, filters, and options in a subclass + of `Sinatra::Base`. + +`Sinatra::Base` is a blank slate. Most options are disabled by default, +including the built-in server. See [Configuring +Settings](http://www.sinatrarb.com/configuration.html) for details on +available options and their behavior. If you want behavior more similar +to when you define your app at the top level (also known as Classic +style), you can subclass `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modular vs. Classic Style + +Contrary to common belief, there is nothing wrong with the classic +style. If it suits your application, you do not have to switch to a +modular application. + +The main disadvantage of using the classic style rather than the modular +style is that you will only have one Sinatra application per Ruby +process. If you plan to use more than one, switch to the modular style. +There is no reason you cannot mix the modular and the classic styles. + +If switching from one style to the other, you should be aware of +slightly different default settings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SettingClassicModularModular
    app_filefile loading sinatrafile subclassing Sinatra::Basefile subclassing Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Serving a Modular Application + +There are two common options for starting a modular app, actively +starting with `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +``` + +Start with: + +```shell +ruby my_app.rb +``` + +Or with a `config.ru` file, which allows using any Rack handler: + +```ruby +# config.ru (run with rackup) +require './my_app' +run MyApp +``` + +Run: + +```shell +rackup -p 4567 +``` + +### Using a Classic Style Application with a config.ru + +Write your app file: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +And a corresponding `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### When to use a config.ru? + +A `config.ru` file is recommended if: + +* You want to deploy with a different Rack handler (Passenger, Unicorn, + Heroku, ...). +* You want to use more than one subclass of `Sinatra::Base`. +* You want to use Sinatra only for middleware, and not as an endpoint. + +**There is no need to switch to a `config.ru` simply because you +switched to the modular style, and you don't have to use the modular +style for running with a `config.ru`.** + +### Using Sinatra as Middleware + +Not only is Sinatra able to use other Rack middleware, any Sinatra +application can in turn be added in front of any Rack endpoint as +middleware itself. This endpoint could be another Sinatra application, +or any other Rack-based application (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # middleware will run before filters + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Dynamic Application Creation + +Sometimes you want to create new applications at runtime without having to +assign them to a constant. You can do this with `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +It takes the application to inherit from as an optional argument: + +```ruby +# config.ru (run with rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +This is especially useful for testing Sinatra extensions or using Sinatra in +your own library. + +This also makes using Sinatra as middleware extremely easy: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Scopes and Binding + +The scope you are currently in determines what methods and variables are +available. + +### Application/Class Scope + +Every Sinatra application corresponds to a subclass of `Sinatra::Base`. +If you are using the top-level DSL (`require 'sinatra'`), then this +class is `Sinatra::Application`, otherwise it is the subclass you +created explicitly. At class level you have methods like `get` or +`before`, but you cannot access the `request` or `session` objects, as +there is only a single application class for all requests. + +Options created via `set` are methods at class level: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, I'm no longer in the application scope! + end +end +``` + +You have the application scope binding inside: + +* Your application class body +* Methods defined by extensions +* The block passed to `helpers` +* Procs/blocks used as value for `set` +* The block passed to `Sinatra.new` + +You can reach the scope object (the class) like this: + +* Via the object passed to configure blocks (`configure { |c| ... }`) +* `settings` from within the request scope + +### Request/Instance Scope + +For every incoming request, a new instance of your application class is +created, and all handler blocks run in that scope. From within this scope you +can access the `request` and `session` objects or call rendering methods like +`erb` or `haml`. You can access the application scope from within the request +scope via the `settings` helper: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + get '/define_route/:name' do + # Request scope for '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Request scope for "/#{params['name']}" + @value # => nil (not the same request) + end + + "Route defined!" + end +end +``` + +You have the request scope binding inside: + +* get, head, post, put, delete, options, patch, link and unlink blocks +* before and after filters +* helper methods +* templates/views + +### Delegation Scope + +The delegation scope just forwards methods to the class scope. However, it +does not behave exactly like the class scope, as you do not have the class +binding. Only methods explicitly marked for delegation are available, and you +do not share variables/state with the class scope (read: you have a different +`self`). You can explicitly add method delegations by calling +`Sinatra::Delegator.delegate :method_name`. + +You have the delegate scope binding inside: + +* The top level binding, if you did `require "sinatra"` +* An object extended with the `Sinatra::Delegator` mixin + +Have a look at the code for yourself: here's the +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Command Line + +Sinatra applications can be run directly: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Options are: + +``` +-h # help +-p # set the port (default is 4567) +-o # set the host (default is 0.0.0.0) +-e # set the environment (default is development) +-s # specify rack server/handler (default is thin) +-q # turn on quiet mode for server (default is off) +-x # turn on the mutex lock (default is off) +``` + +### Multi-threading + +_Paraphrasing from +[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129) +by Konstantin_ + +Sinatra doesn't impose any concurrency model, but leaves that to the +underlying Rack handler (server) like Thin, Puma or WEBrick. Sinatra +itself is thread-safe, so there won't be any problem if the Rack handler +uses a threaded model of concurrency. This would mean that when starting +the server, you'd have to specify the correct invocation method for the +specific Rack handler. The following example is a demonstration of how +to start a multi-threaded Thin server: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +To start the server, the command would be: + +```shell +thin --threaded start +``` + +## Requirement + +The following Ruby versions are officially supported: +
    +
    Ruby 2.2
    +
    + 2.2 is fully supported and recommended. There are currently no plans to + drop official support for it. +
    + +
    Rubinius
    +
    + Rubinius is officially supported (Rubinius >= 2.x). It is recommended to + gem install puma. +
    + +
    JRuby
    +
    + The latest stable release of JRuby is officially supported. It is not + recommended to use C extensions with JRuby. It is recommended to + gem install trinidad. +
    +
    + +Versions of Ruby prior to 2.2.2 are no longer supported as of Sinatra 2.0. + +We also keep an eye on upcoming Ruby versions. + +The following Ruby implementations are not officially supported but still are +known to run Sinatra: + +* Older versions of JRuby and Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those) + +Not being officially supported means if things only break there and not on a +supported platform, we assume it's not our issue but theirs. + +We also run our CI against ruby-head (future releases of MRI), but we +can't guarantee anything, since it is constantly moving. Expect upcoming +2.x releases to be fully supported. + +Sinatra should work on any operating system supported by the chosen Ruby +implementation. + +If you run MacRuby, you should `gem install control_tower`. + +Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +Ruby version prior to 2.2. + +## The Bleeding Edge + +If you would like to use Sinatra's latest bleeding-edge code, feel free +to run your application against the master branch, it should be rather +stable. + +We also push out prerelease gems from time to time, so you can do a + +```shell +gem install sinatra --pre +``` + +to get some of the latest features. + +### With Bundler + +If you want to run your application with the latest Sinatra, using +[Bundler](https://bundler.io) is the recommended way. + +First, install bundler, if you haven't: + +```shell +gem install bundler +``` + +Then, in your project directory, create a `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# other dependencies +gem 'haml' # for instance, if you use haml +``` + +Note that you will have to list all your application's dependencies in +the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, +however, be automatically fetched and added by Bundler. + +Now you can run your app like this: + +```shell +bundle exec ruby myapp.rb +``` + +## Versioning + +Sinatra follows [Semantic Versioning](https://semver.org/), both SemVer and +SemVerTag. + +## Further Reading + +* [Project Website](http://www.sinatrarb.com/) - Additional documentation, + news, and links to other resources. +* [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need + help? Have a patch? +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net) +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack + ([get an invite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed + recipes +* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra) + or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on + [RubyDoc](http://www.rubydoc.info/) +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.md new file mode 100644 index 0000000000..b575ecf4be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.md @@ -0,0 +1,3203 @@ +# Sinatra + +[![Gem Version](https://badge.fury.io/rb/sinatra.svg)](https://badge.fury.io/rb/sinatra) +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) +[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver) + +Sinatra is a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for +quickly creating web applications in Ruby with minimal effort: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Install the gem: + +```shell +gem install sinatra +``` + +And run with: + +```shell +ruby myapp.rb +``` + +View at: [http://localhost:4567](http://localhost:4567) + +The code you changed will not take effect until you restart the server. +Please restart the server every time you change or use +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +It is recommended to also run `gem install puma`, which Sinatra will +pick up if available. + +## Table of Contents + +* [Sinatra](#sinatra) + * [Table of Contents](#table-of-contents) + * [Routes](#routes) + * [Conditions](#conditions) + * [Return Values](#return-values) + * [Custom Route Matchers](#custom-route-matchers) + * [Static Files](#static-files) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Available Template Languages](#available-template-languages) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Accessing Variables in Templates](#accessing-variables-in-templates) + * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) + * [Inline Templates](#inline-templates) + * [Named Templates](#named-templates) + * [Associating File Extensions](#associating-file-extensions) + * [Adding Your Own Template Engine](#adding-your-own-template-engine) + * [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup) + * [Filters](#filters) + * [Helpers](#helpers) + * [Using Sessions](#using-sessions) + * [Session Secret Security](#session-secret-security) + * [Session Config](#session-config) + * [Choosing Your Own Session Middleware](#choosing-your-own-session-middleware) + * [Halting](#halting) + * [Passing](#passing) + * [Triggering Another Route](#triggering-another-route) + * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) + * [Streaming Responses](#streaming-responses) + * [Logging](#logging) + * [Mime Types](#mime-types) + * [Generating URLs](#generating-urls) + * [Browser Redirect](#browser-redirect) + * [Cache Control](#cache-control) + * [Sending Files](#sending-files) + * [Accessing the Request Object](#accessing-the-request-object) + * [Attachments](#attachments) + * [Dealing with Date and Time](#dealing-with-date-and-time) + * [Looking Up Template Files](#looking-up-template-files) + * [Configuration](#configuration) + * [Configuring attack protection](#configuring-attack-protection) + * [Available Settings](#available-settings) + * [Environments](#environments) + * [Error Handling](#error-handling) + * [Not Found](#not-found) + * [Error](#error) + * [Rack Middleware](#rack-middleware) + * [Testing](#testing) + * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) + * [Modular vs. Classic Style](#modular-vs-classic-style) + * [Serving a Modular Application](#serving-a-modular-application) + * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) + * [When to use a config.ru?](#when-to-use-a-configru) + * [Using Sinatra as Middleware](#using-sinatra-as-middleware) + * [Dynamic Application Creation](#dynamic-application-creation) + * [Scopes and Binding](#scopes-and-binding) + * [Application/Class Scope](#applicationclass-scope) + * [Request/Instance Scope](#requestinstance-scope) + * [Delegation Scope](#delegation-scope) + * [Command Line](#command-line) + * [Multi-threading](#multi-threading) + * [Requirement](#requirement) + * [The Bleeding Edge](#the-bleeding-edge) + * [With Bundler](#with-bundler) + * [Versioning](#versioning) + * [Further Reading](#further-reading) + +## Routes + +In Sinatra, a route is an HTTP method paired with a URL-matching pattern. +Each route is associated with a block: + +```ruby +get '/' do + .. show something .. +end + +post '/' do + .. create something .. +end + +put '/' do + .. replace something .. +end + +patch '/' do + .. modify something .. +end + +delete '/' do + .. annihilate something .. +end + +options '/' do + .. appease something .. +end + +link '/' do + .. affiliate something .. +end + +unlink '/' do + .. separate something .. +end +``` + +Routes are matched in the order they are defined. The first route that +matches the request is invoked. + +Routes with trailing slashes are different from the ones without: + +```ruby +get '/foo' do + # Does not match "GET /foo/" +end +``` + +Route patterns may include named parameters, accessible via the +`params` hash: + +```ruby +get '/hello/:name' do + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + "Hello #{params['name']}!" +end +``` + +You can also access named parameters via block parameters: + +```ruby +get '/hello/:name' do |n| + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + # n stores params['name'] + "Hello #{n}!" +end +``` + +Route patterns may also include splat (or wildcard) parameters, accessible +via the `params['splat']` array: + +```ruby +get '/say/*/to/*' do + # matches /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # matches /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Or with block parameters: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Route matching with Regular Expressions: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +Or with a block parameter: + +```ruby +get %r{/hello/([\w]+)} do |c| + # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc. + "Hello, #{c}!" +end +``` + +Route patterns may have optional parameters: + +```ruby +get '/posts/:format?' do + # matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc +end +``` + +Routes may also utilize query parameters: + +```ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +By the way, unless you disable the path traversal attack protection (see +[below](#configuring-attack-protection)), the request path might be modified before +matching against your routes. + +You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme) +options used for a given route by passing in a `:mustermann_opts` hash: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` + +It looks like a [condition](#conditions), but it isn't one! These options will +be merged into the global `:mustermann_opts` hash described +[below](#available-settings). + +## Conditions + +Routes may include a variety of matching conditions, such as the user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Matches non-songbird browsers +end +``` + +Other available conditions are `host_name` and `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` searches the request's Accept header. + +You can easily define your own conditions: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +For a condition that takes multiple values use a splat: + +```ruby +set(:auth) do |*roles| # <- notice the splat here + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +## Return Values + +The return value of a route block determines at least the response body +passed on to the HTTP client or at least the next middleware in the +Rack stack. Most commonly, this is a string, as in the above examples. +But other values are also accepted. + +You can return an object that would either be a valid Rack response, Rack +body object or HTTP status code: + +* An Array with three elements: `[status (Integer), headers (Hash), response + body (responds to #each)]` +* An Array with two elements: `[status (Integer), response body (responds to + #each)]` +* An object that responds to `#each` and passes nothing but strings to + the given block +* A Integer representing the status code + +That way we can, for instance, easily implement a streaming example: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +You can also use the `stream` helper method ([described below](#streaming-responses)) to reduce +boilerplate and embed the streaming logic in the route. + +## Custom Route Matchers + +As shown above, Sinatra ships with built-in support for using String +patterns and regular expressions as route matches. However, it does not +stop there. You can easily define your own matchers: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note that the above example might be over-engineered, as it can also be +expressed as: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Or, using negative look ahead: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Static Files + +Static files are served from the `./public` directory. You can specify +a different location by setting the `:public_folder` option: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Note that the public directory name is not included in the URL. A file +`./public/css/style.css` is made available as +`http://example.com/css/style.css`. + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info. + +## Views / Templates + +Each template language is exposed via its own rendering method. These +methods simply return a string: + +```ruby +get '/' do + erb :index +end +``` + +This renders `views/index.erb`. + +Instead of a template name, you can also just pass in the template content +directly: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates take a second argument, the options hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +This will render `views/index.erb` embedded in the +`views/post.erb` (default is `views/layout.erb`, if it exists). + +Any options not understood by Sinatra will be passed on to the template +engine: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +You can also set options per template language in general: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Options passed to the render method override options set via `set`. + +Available Options: + +
    +
    locals
    +
    + List of locals passed to the document. Handy with partials. + Example: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding to use if uncertain. Defaults to + settings.default_encoding. +
    + +
    views
    +
    + Views folder to load templates from. Defaults to settings.views. +
    + +
    layout
    +
    + Whether to use a layout (true or false). If it's a + Symbol, specifies what template to use. Example: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type the template produces. Default depends on template language. +
    + +
    scope
    +
    + Scope to render template under. Defaults to the application + instance. If you change this, instance variables and helper methods + will not be available. +
    + +
    layout_engine
    +
    + Template engine to use for rendering the layout. Useful for + languages that do not support layouts otherwise. Defaults to the + engine used for the template. Example: set :rdoc, :layout_engine + => :erb +
    + +
    layout_options
    +
    + Special options only used for rendering the layout. Example: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Templates are assumed to be located directly under the `./views` +directory. To use a different views directory: + +```ruby +set :views, settings.root + '/templates' +``` + + +One important thing to remember is that you always have to reference +templates with symbols, even if they're in a subdirectory (in this case, +use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a +symbol because otherwise rendering methods will render any strings +passed to them directly. + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Renders the template string. You can optionally specify `:path` and +`:line` for a clearer backtrace if there is a filesystem path or line +associated with that string: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Available Template Languages + +Some languages have multiple implementations. To specify what implementation +to use (and to be thread-safe), you should simply require it first: + +```ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependencyhaml
    File Extension.haml
    Examplehaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependency + erubi + or erubis + or erb (included in Ruby) +
    File Extensions.erb, .rhtml or .erubi (Erubi only) + or .erubis (Erubis only)
    Exampleerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependency + builder +
    File Extension.builder
    Examplebuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependencynokogiri
    File Extension.nokogiri
    Examplenokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.sass
    Examplesass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependencysass
    File Extension.scss
    Examplescss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependencyless
    File Extension.less
    Exampleless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependencyliquid
    File Extension.liquid
    Exampleliquid :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods (except for `yield`) from a Liquid +template, you almost always want to pass locals to it. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependency + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku + commonmarker + pandoc +
    File Extensions.markdown, .mkd and .md
    Examplemarkdown :index, :layout_engine => :erb
    + +It is not possible to call methods from Markdown, nor to pass locals to it. +You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Note that you may also call the `markdown` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Since you cannot call Ruby from Markdown, you cannot use layouts written in +Markdown. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### Textile Templates + + + + + + + + + + + + + + +
    DependencyRedCloth
    File Extension.textile
    Exampletextile :index, :layout_engine => :erb
    + +It is not possible to call methods from Textile, nor to pass locals to +it. You therefore will usually use it in combination with another +rendering engine: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Note that you may also call the `textile` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Since you cannot call Ruby from Textile, you cannot use layouts written in +Textile. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependencyRDoc
    File Extension.rdoc
    Examplerdoc :README, :layout_engine => :erb
    + +It is not possible to call methods from RDoc, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Note that you may also call the `rdoc` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Since you cannot call Ruby from RDoc, you cannot use layouts written in +RDoc. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    DependencyAsciidoctor
    File Extension.asciidoc, .adoc and .ad
    Exampleasciidoc :README, :layout_engine => :erb
    + +Since you cannot call Ruby methods directly from an AsciiDoc template, you +almost always want to pass locals to it. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependencyRadius
    File Extension.radius
    Exampleradius :index, :locals => { :key => 'value' }
    + +Since you cannot call Ruby methods directly from a Radius template, you +almost always want to pass locals to it. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependencyMarkaby
    File Extension.mab
    Examplemarkaby { h1 "Welcome!" }
    + +It also takes a block for inline templates (see [example](#inline-templates)). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependencyRabl
    File Extension.rabl
    Examplerabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependencySlim Lang
    File Extension.slim
    Exampleslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependencyCreole
    File Extension.creole
    Examplecreole :wiki, :layout_engine => :erb
    + +It is not possible to call methods from Creole, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note that you may also call the `creole` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Since you cannot call Ruby from Creole, you cannot use layouts written in +Creole. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    DependencyWikiCloth
    File Extension.mediawiki and .mw
    Examplemediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass +locals to it. You therefore will usually use it in combination with +another rendering engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other +templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Since you cannot call Ruby from MediaWiki, you cannot use layouts written in +MediaWiki. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependency + + CoffeeScript + and a + + way to execute javascript + +
    File Extension.coffee
    Examplecoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependency + + Stylus + and a + + way to execute javascript + +
    File Extension.styl
    Examplestylus :index
    + +Before being able to use Stylus templates, you need to load `stylus` and +`stylus/tilt` first: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependencyyajl-ruby
    File Extension.yajl
    Example + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + + +The template source is evaluated as a Ruby string, and the +resulting json variable is converted using `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +The `:callback` and `:variable` options can be used to decorate the rendered +object: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    DependencyWLang
    File Extension.wlang
    Examplewlang :index, :locals => { :key => 'value' }
    + +Since calling ruby methods is not idiomatic in WLang, you almost always +want to pass locals to it. Layouts written in WLang and `yield` are +supported, though. + +### Accessing Variables in Templates + +Templates are evaluated within the same context as route handlers. Instance +variables set in route handlers are directly accessible by templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Or, specify an explicit Hash of local variables: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +This is typically used when rendering templates as partials from within +other templates. + +### Templates with `yield` and nested layouts + +A layout is usually just a template that calls `yield`. +Such a template can be used either through the `:template` option as +described above, or it can be rendered with a block as follows: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +This code is mostly equivalent to `erb :index, :layout => :post`. + +Passing blocks to rendering methods is most useful for creating nested +layouts: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +This can also be done in fewer lines of code with: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Currently, the following rendering methods accept a block: `erb`, `haml`, +`liquid`, `slim `, `wlang`. Also, the general `render` method accepts a block. + +### Inline Templates + +Templates may be defined at the end of the source file: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +NOTE: Inline templates defined in the source file that requires Sinatra are +automatically loaded. Call `enable :inline_templates` explicitly if you +have inline templates in other source files. + +### Named Templates + +Templates may also be defined using the top-level `template` method: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +If a template named "layout" exists, it will be used each time a template +is rendered. You can individually disable layouts by passing +`:layout => false` or disable them by default via +`set :haml, :layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associating File Extensions + +To associate a file extension with a template engine, use +`Tilt.register`. For instance, if you like to use the file extension +`tt` for Textile templates, you can do the following: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adding Your Own Template Engine + +First, register your engine with Tilt, then create a rendering method: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renders `./views/index.myat`. Learn more about +[Tilt](https://github.com/rtomayko/tilt#readme). + +### Using Custom Logic for Template Lookup + +To implement your own template lookup mechanism you can write your +own `#find_template` method: + +```ruby +configure do + set :views, [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filters + +Before filters are evaluated before each request within the same context +as the routes will be and can modify the request and response. Instance +variables set in filters are accessible by routes and templates: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After filters are evaluated after each request within the same context +as the routes will be and can also modify the request and response. +Instance variables set in before filters and routes are accessible by +after filters: + +```ruby +after do + puts response.status +end +``` + +Note: Unless you use the `body` method rather than just returning a +String from the routes, the body will not yet be available in the after +filter, since it is generated later on. + +Filters optionally take a pattern, causing them to be evaluated only if the +request path matches that pattern: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Like routes, filters also take conditions: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Use the top-level `helpers` method to define helper methods for use in +route handlers and templates: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Alternatively, helper methods can be separately defined in a module: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +The effect is the same as including the modules in the application class. + +### Using Sessions + +A session is used to keep state during requests. If activated, you have one +session hash per user session: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### Session Secret Security + +To improve security, the session data in the cookie is signed with a session +secret using `HMAC-SHA1`. This session secret should optimally be a +cryptographically secure random value of an appropriate length which for +`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex +characters). You would be advised not to use a secret that is less than 32 +bytes of randomness (256 bits, 64 hex characters). It is therefore **very +important** that you don't just make the secret up, but instead use a secure +random number generator to create it. Humans are extremely bad at generating +random values. + +By default, a 32 byte secure random session secret is generated for you by +Sinatra, but it will change with every restart of your application. If you +have multiple instances of your application, and you let Sinatra generate the +key, each instance would then have a different session key which is probably +not what you want. + +For better security and usability it's +[recommended](https://12factor.net/config) that you generate a secure random +secret and store it in an environment variable on each host running your +application so that all of your application instances will share the same +secret. You should periodically rotate this session secret to a new value. +Here are some examples of how you might create a 64-byte secret and set it: + +**Session Secret Generation** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Generation (Bonus Points)** + +Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to +use the system RNG facilities to generate random values instead of +userspace `OpenSSL` which MRI Ruby currently defaults to: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Session Secret Environment Variable** + +Set a `SESSION_SECRET` environment variable for Sinatra to the value you +generated. Make this value persistent across reboots of your host. Since the +method for doing this will vary across systems this is for illustrative +purposes only: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Session Secret App Config** + +Set up your app config to fail-safe to a secure random secret +if the `SESSION_SECRET` environment variable is not available. + +For bonus points use the [sysrandom +gem](https://github.com/cryptosphere/sysrandom#readme) here as well: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Session Config + +If you want to configure it further, you may also store a hash with options +in the `sessions` setting: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +To share your session across other apps on subdomains of foo.com, prefix the +domain with a *.* like this instead: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Choosing Your Own Session Middleware + +Note that `enable :sessions` actually stores all data in a cookie. This +might not always be what you want (storing lots of data will increase your +traffic, for instance). You can use any Rack session middleware in order to +do so, one of the following methods can be used: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Or to set up sessions with a hash of options: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Another option is to **not** call `enable :sessions`, but instead pull in +your middleware of choice as you would any other middleware. + +It is important to note that when using this method, session based +protection **will not be enabled by default**. + +The Rack middleware to do that will also need to be added: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +See '[Configuring attack protection](#configuring-attack-protection)' for more information. + +### Halting + +To immediately stop a request within a filter or route use: + +```ruby +halt +``` + +You can also specify the status when halting: + +```ruby +halt 410 +``` + +Or the body: + +```ruby +halt 'this will be the body' +``` + +Or both: + +```ruby +halt 401, 'go away!' +``` + +With headers: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +It is of course possible to combine a template with `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +A route can punt processing to the next matching route using `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +The route block is immediately exited and control continues with the next +matching route. If no matching route is found, a 404 is returned. + +### Triggering Another Route + +Sometimes `pass` is not what you want, instead, you would like to get the +result of calling another route. Simply use `call` to achieve this: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note that in the example above, you would ease testing and increase +performance by simply moving `"bar"` into a helper used by both `/foo` and +`/bar`. + +If you want the request to be sent to the same application instance rather +than a duplicate, use `call!` instead of `call`. + +Check out the Rack specification if you want to learn more about `call`. + +### Setting Body, Status Code, and Headers + +It is possible and recommended to set the status code and response body with +the return value of the route block. However, in some scenarios, you might +want to set the body at an arbitrary point in the execution flow. You can do +so with the `body` helper method. If you do so, you can use that method from +thereon to access the body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +It is also possible to pass a block to `body`, which will be executed by the +Rack handler (this can be used to implement streaming, [see "Return Values"](#return-values)). + +Similar to the body, you can also set the status code and headers: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a teapot!" +end +``` + +Like `body`, `headers` and `status` with no arguments can be used to access +their current values. + +### Streaming Responses + +Sometimes you want to start sending out data while still generating parts of +the response body. In extreme examples, you want to keep sending data until +the client closes the connection. You can use the `stream` helper to avoid +creating your own wrapper: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +This allows you to implement streaming APIs, +[Server Sent Events](https://w3c.github.io/eventsource/), and can be used as +the basis for [WebSockets](https://en.wikipedia.org/wiki/WebSocket). It can +also be used to increase throughput if some but not all content depends on a +slow resource. + +Note that the streaming behavior, especially the number of concurrent +requests, highly depends on the webserver used to serve the application. +Some servers might not even support streaming at all. If the server does not +support streaming, the body will be sent all at once after the block passed +to `stream` finishes executing. Streaming does not work at all with Shotgun. + +If the optional parameter is set to `keep_open`, it will not call `close` on +the stream object, allowing you to close it at any later point in the +execution flow. This only works on evented servers, like Rainbows. +Other servers will still close the stream: + +```ruby +# config.ru +require 'sinatra/base' + +class App < Sinatra::Base + connections = [] + + get '/subscribe', provides: 'text/event-stream' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end + end + + post '/' do + connections.each do |out| + # notify client that a new message has arrived + out << "data: #{params[:msg]}\n\n" + + # indicate client to connect again + out.close + end + + 204 # response without entity body + end +end + +run App +``` + +```ruby +# rainbows.conf +Rainbows! do + use :EventMachine +end +```` + +Run: + +```shell +rainbows -c rainbows.conf +``` + +It's also possible for the client to close the connection when trying to +write to the socket. Because of this, it's recommended to check +`out.closed?` before trying to write. + +### Logging + +In the request scope, the `logger` helper exposes a `Logger` instance: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +This logger will automatically take your Rack handler's logging settings into +account. If logging is disabled, this method will return a dummy object, so +you do not have to worry about it in your routes and filters. + +Note that logging is only enabled for `Sinatra::Application` by default, so +if you inherit from `Sinatra::Base`, you probably want to enable it yourself: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +To avoid any logging middleware to be set up, set the `logging` option to +`nil`. However, keep in mind that `logger` will in that case return `nil`. A +common use case is when you want to set your own logger. Sinatra will use +whatever it will find in `env['rack.logger']`. + +### Mime Types + +When using `send_file` or static files you may have mime types Sinatra +doesn't understand. Use `mime_type` to register them by file extension: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +You can also use it with the `content_type` helper: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generating URLs + +For generating URLs you should use the `url` helper method, for instance, in +Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +It takes reverse proxies and Rack routers into account - if present. + +This method is also aliased to `to` (see [below](#browser-redirect) for an example). + +### Browser Redirect + +You can trigger a browser redirect with the `redirect` helper method: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Any additional parameters are handled like arguments passed to `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +You can also easily redirect back to the page the user came from with +`redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +To pass arguments with a redirect, either add them to the query: + +```ruby +redirect to('/bar?sum=42') +``` + +Or use a session: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache Control + +Setting your headers correctly is the foundation for proper HTTP caching. + +You can easily set the Cache-Control header like this: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Pro tip: Set up caching in a before filter: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +If you are using the `expires` helper to set the corresponding header, +`Cache-Control` will be set automatically for you: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +To properly use caches, you should consider using `etag` or `last_modified`. +It is recommended to call those helpers *before* doing any heavy lifting, as +they will immediately flush a response if the client already has the current +version in its cache: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +It is also possible to use a +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +These helpers will not do any caching for you, but rather feed the necessary +information to your cache. If you are looking for a quick +reverse-proxy caching solution, try +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Use the `:static_cache_control` setting (see [below](#cache-control)) to add +`Cache-Control` header info to static files. + +According to RFC 2616, your application should behave differently if the +If-Match or If-None-Match header is set to `*`, depending on whether the +resource requested is already in existence. Sinatra assumes resources for +safe (like get) and idempotent (like put) requests are already in existence, +whereas other resources (for instance post requests) are treated as new +resources. You can change this behavior by passing in a `:new_resource` +option: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +If you still want to use a weak ETag, pass in a `:kind` option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Sending Files + +To return the contents of a file as the response, you can use the `send_file` +helper method: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +It also takes options: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +The options are: + +
    +
    filename
    +
    File name to be used in the response, + defaults to the real file name.
    +
    last_modified
    +
    Value for Last-Modified header, defaults to the file's mtime.
    + +
    type
    +
    Value for Content-Type header, guessed from the file extension if + missing.
    + +
    disposition
    +
    + Value for Content-Disposition header, possible values: nil + (default), :attachment and :inline +
    + +
    length
    +
    Value for Content-Length header, defaults to file size.
    + +
    status
    +
    + Status code to be sent. Useful when sending a static file as an error + page. If supported by the Rack handler, other means than streaming + from the Ruby process will be used. If you use this helper method, + Sinatra will automatically handle range requests. +
    +
    + +### Accessing the Request Object + +The incoming request object can be accessed from request level (filter, +routes, error handlers) through the `request` method: + +```ruby +# app running on http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # request body sent by the client (see below) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # length of request.body + request.media_type # media type of request.body + request.host # "example.com" + request.get? # true (similar methods for other verbs) + request.form_data? # false + request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. + request.referrer # the referrer of the client or '/' + request.user_agent # user agent (used by :agent condition) + request.cookies # hash of browser cookies + request.xhr? # is this an ajax request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # client IP address + request.secure? # false (would be true over ssl) + request.forwarded? # true (if running behind a reverse proxy) + request.env # raw env hash handed in by Rack +end +``` + +Some options, like `script_name` or `path_info`, can also be written: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +The `request.body` is an IO or StringIO object: + +```ruby +post "/api" do + request.body.rewind # in case someone already read it + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Attachments + +You can use the `attachment` helper to tell the browser the response should +be stored on disk rather than displayed in the browser: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +You can also pass it a file name: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Dealing with Date and Time + +Sinatra offers a `time_for` helper method that generates a Time object from +the given value. It is also able to convert `DateTime`, `Date` and similar +classes: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +This method is used internally by `expires`, `last_modified` and akin. You +can therefore easily extend the behavior of those methods by overriding +`time_for` in your application: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Looking Up Template Files + +The `find_template` helper is used to find template files for rendering: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +This is not really useful. But it is useful that you can actually override +this method to hook in your own lookup mechanism. For instance, if you want +to be able to use more than one view directory: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Another example would be using different directories for different engines: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +You can also easily wrap this up in an extension and share it with others! + +Note that `find_template` does not check if the file really exists but +rather calls the given block for all possible paths. This is not a +performance issue, since `render` will use `break` as soon as a file is +found. Also, template locations (and content) will be cached if you are not +running in development mode. You should keep that in mind if you write a +really crazy method. + +## Configuration + +Run once, at startup, in any environment: + +```ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +``` + +Run only when the environment (`APP_ENV` environment variable) is set to +`:production`: + +```ruby +configure :production do + ... +end +``` + +Run when the environment is set to either `:production` or `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +You can access those options via `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configuring attack protection + +Sinatra is using +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) to +defend your application against common, opportunistic attacks. You can +easily disable this behavior (which will open up your application to tons +of common vulnerabilities): + +```ruby +disable :protection +``` + +To skip a single defense layer, set `protection` to an options hash: + +```ruby +set :protection, :except => :path_traversal +``` +You can also hand in an array in order to disable a list of protections: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +By default, Sinatra will only set up session based protection if `:sessions` +have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up +sessions "outside" of the Sinatra app, such as in the config.ru or with a +separate `Rack::Builder` instance. In that case, you can still set up session +based protection by passing the `:session` option: + +```ruby +set :protection, :session => true +``` + +### Available Settings + +
    +
    absolute_redirects
    +
    + If disabled, Sinatra will allow relative redirects, however, Sinatra + will no longer conform with RFC 2616 (HTTP 1.1), which only allows + absolute redirects. +
    +
    + Enable if your app is running behind a reverse proxy that has not been + set up properly. Note that the url helper will still produce + absolute URLs, unless you pass in false as the second + parameter. +
    +
    Disabled by default.
    + +
    add_charset
    +
    + Mime types the content_type helper will automatically add the + charset info to. You should add to it rather than overriding this + option: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Path to the main application file, used to detect project root, views + and public folder and inline templates. +
    + +
    bind
    +
    + IP address to bind to (default: 0.0.0.0 or + localhost if your `environment` is set to development). Only + used for built-in server. +
    + +
    default_content_type
    +
    + Content-Type to assume if unknown (defaults to "text/html"). Set + to nil to not set a default Content-Type on every response; when + configured so, you must set the Content-Type manually when emitting content + or the user-agent will have to sniff it (or, if nosniff is enabled + in Rack::Protection::XSSHeader, assume application/octet-stream). +
    + +
    default_encoding
    +
    Encoding to assume if unknown (defaults to "utf-8").
    + +
    dump_errors
    +
    Display errors in the log. Enabled by default unless environment is "test".
    + +
    environment
    +
    + Current environment. Defaults to ENV['APP_ENV'], or + "development" if not available. +
    + +
    logging
    +
    Use the logger.
    + +
    lock
    +
    + Places a lock around every request, only running processing on request + per Ruby process concurrently. +
    +
    Enabled if your app is not thread-safe. Disabled by default.
    + +
    method_override
    +
    + Use _method magic to allow put/delete forms in browsers that + don't support it. +
    + +
    mustermann_opts
    +
    + A default hash of options to pass to Mustermann.new when compiling routing + paths. +
    + +
    port
    +
    Port to listen on. Only used for built-in server.
    + +
    prefixed_redirects
    +
    + Whether or not to insert request.script_name into redirects + if no absolute path is given. That way redirect '/foo' would + behave like redirect to('/foo'). Disabled by default. +
    + +
    protection
    +
    + Whether or not to enable web attack protections. See protection section + above. +
    + +
    public_dir
    +
    Alias for public_folder. See below.
    + +
    public_folder
    +
    + Path to the folder public files are served from. Only used if static + file serving is enabled (see static setting below). Inferred + from app_file setting if not set. +
    + +
    quiet
    +
    + Disables logs generated by Sinatra's start and stop commands. + false by default. +
    + +
    reload_templates
    +
    + Whether or not to reload templates between requests. Enabled in + development mode. +
    + +
    root
    +
    + Path to project root folder. Inferred from app_file setting + if not set. +
    + +
    raise_errors
    +
    + Raise exceptions (will stop application). Enabled by default when + environment is set to "test", disabled otherwise. +
    + +
    run
    +
    + If enabled, Sinatra will handle starting the web server. Do not + enable if using rackup or other means. +
    + +
    running
    +
    Is the built-in server running now? Do not change this setting!
    + +
    server
    +
    + Server or list of servers to use for built-in server. Order indicates + priority, default depends on Ruby implementation. +
    + +
    server_settings
    +
    + If you are using a WEBrick web server, presumably for your development + environment, you can pass a hash of options to server_settings, + such as SSLEnable or SSLVerifyClient. However, web + servers such as Puma do not support this, so you can set + server_settings by defining it as a method when you call + configure. +
    + +
    sessions
    +
    + Enable cookie-based sessions support using + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    session_store
    +
    + The Rack session middleware used. Defaults to + Rack::Session::Cookie. See 'Using Sessions' section for more + information. +
    + +
    show_exceptions
    +
    + Show a stack trace in the browser when an exception happens. Enabled by + default when environment is set to "development", + disabled otherwise. +
    +
    + Can also be set to :after_handler to trigger app-specified + error handling before showing a stack trace in the browser. +
    + +
    static
    +
    Whether Sinatra should handle serving static files.
    +
    Disable when using a server able to do this on its own.
    +
    Disabling will boost performance.
    +
    + Enabled by default in classic style, disabled for modular apps. +
    + +
    static_cache_control
    +
    + When Sinatra is serving static files, set this to add + Cache-Control headers to the responses. Uses the + cache_control helper. Disabled by default. +
    +
    + Use an explicit array when setting multiple values: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + If set to true, will tell server to use + EventMachine.defer for processing the request. +
    + +
    traps
    +
    Whether Sinatra should handle system signals.
    + +
    views
    +
    + Path to the views folder. Inferred from app_file setting if + not set. +
    + +
    x_cascade
    +
    + Whether or not to set the X-Cascade header if no route matches. + Defaults to true. +
    +
    + +## Environments + +There are three predefined `environments`: `"development"`, +`"production"` and `"test"`. Environments can be set through the +`APP_ENV` environment variable. The default value is `"development"`. +In the `"development"` environment all templates are reloaded between +requests, and special `not_found` and `error` handlers display stack +traces in your browser. In the `"production"` and `"test"` environments, +templates are cached by default. + +To run different environments, set the `APP_ENV` environment variable: + +```shell +APP_ENV=production ruby my_app.rb +``` + +You can use predefined methods: `development?`, `test?` and `production?` to +check the current environment setting: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Error Handling + +Error handlers run within the same context as routes and before filters, +which means you get all the goodies it has to offer, like `haml`, `erb`, +`halt`, etc. + +### Not Found + +When a `Sinatra::NotFound` exception is raised, or the response's status +code is 404, the `not_found` handler is invoked: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +The `error` handler is invoked any time an exception is raised from a route +block or a filter. But note in development it will only run if you set the +show exceptions option to `:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +The exception object can be obtained from the `sinatra.error` Rack variable: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Custom errors: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Then, if this happens: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +You get this: + +``` +So what happened was... something bad +``` + +Alternatively, you can install an error handler for a status code: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Or a range: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installs special `not_found` and `error` handlers when +running under the development environment to display nice stack traces +and additional debugging information in your browser. + +## Rack Middleware + +Sinatra rides on [Rack](https://rack.github.io/), a minimal standard +interface for Ruby web frameworks. One of Rack's most interesting +capabilities for application developers is support for "middleware" -- +components that sit between the server and your application monitoring +and/or manipulating the HTTP request/response to provide various types +of common functionality. + +Sinatra makes building Rack middleware pipelines a cinch via a top-level +`use` method: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +The semantics of `use` are identical to those defined for the +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(most frequently used from rackup files). For example, the `use` method +accepts multiple/variable args as well as blocks: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack is distributed with a variety of standard middleware for logging, +debugging, URL routing, authentication, and session handling. Sinatra uses +many of these components automatically based on configuration so you +typically don't have to `use` them explicitly. + +You can find useful middleware in +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testing + +Sinatra tests can be written using any Rack-based testing library or +framework. +[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames) +is recommended: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Note: If you are using Sinatra in the modular style, replace +`Sinatra::Application` above with the class name of your app. + +## Sinatra::Base - Middleware, Libraries, and Modular Apps + +Defining your app at the top-level works well for micro-apps but has +considerable drawbacks when building reusable components such as Rack +middleware, Rails metal, simple libraries with a server component, or even +Sinatra extensions. The top-level assumes a micro-app style configuration +(e.g., a single application file, `./public` and `./views` +directories, logging, exception detail page, etc.). That's where +`Sinatra::Base` comes into play: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +The methods available to `Sinatra::Base` subclasses are exactly the same +as those available via the top-level DSL. Most top-level apps can be +converted to `Sinatra::Base` components with two modifications: + +* Your file should require `sinatra/base` instead of `sinatra`; + otherwise, all of Sinatra's DSL methods are imported into the main + namespace. +* Put your app's routes, error handlers, filters, and options in a subclass + of `Sinatra::Base`. + +`Sinatra::Base` is a blank slate. Most options are disabled by default, +including the built-in server. See [Configuring +Settings](http://www.sinatrarb.com/configuration.html) for details on +available options and their behavior. If you want behavior more similar +to when you define your app at the top level (also known as Classic +style), you can subclass `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modular vs. Classic Style + +Contrary to common belief, there is nothing wrong with the classic +style. If it suits your application, you do not have to switch to a +modular application. + +The main disadvantage of using the classic style rather than the modular +style is that you will only have one Sinatra application per Ruby +process. If you plan to use more than one, switch to the modular style. +There is no reason you cannot mix the modular and classic styles. + +If switching from one style to the other, you should be aware of +slightly different default settings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SettingClassicModularModular
    app_filefile loading sinatrafile subclassing Sinatra::Basefile subclassing Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Serving a Modular Application + +There are two common options for starting a modular app, actively +starting with `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +``` + +Start with: + +```shell +ruby my_app.rb +``` + +Or with a `config.ru` file, which allows using any Rack handler: + +```ruby +# config.ru (run with rackup) +require './my_app' +run MyApp +``` + +Run: + +```shell +rackup -p 4567 +``` + +### Using a Classic Style Application with a config.ru + +Write your app file: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +And a corresponding `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### When to use a config.ru? + +A `config.ru` file is recommended if: + +* You want to deploy with a different Rack handler (Passenger, Unicorn, + Heroku, ...). +* You want to use more than one subclass of `Sinatra::Base`. +* You want to use Sinatra only for middleware, and not as an endpoint. + +**There is no need to switch to a `config.ru` simply because you +switched to the modular style, and you don't have to use the modular +style for running with a `config.ru`.** + +### Using Sinatra as Middleware + +Not only is Sinatra able to use other Rack middleware, any Sinatra +application can, in turn, be added in front of any Rack endpoint as +middleware itself. This endpoint could be another Sinatra application, +or any other Rack-based application (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # middleware will run before filters + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Dynamic Application Creation + +Sometimes you want to create new applications at runtime without having to +assign them to a constant. You can do this with `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +It takes the application to inherit from as an optional argument: + +```ruby +# config.ru (run with rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +This is especially useful for testing Sinatra extensions or using Sinatra in +your own library. + +This also makes using Sinatra as middleware extremely easy: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Scopes and Binding + +The scope you are currently in determines what methods and variables are +available. + +### Application/Class Scope + +Every Sinatra application corresponds to a subclass of `Sinatra::Base`. +If you are using the top-level DSL (`require 'sinatra'`), then this +class is `Sinatra::Application`, otherwise it is the subclass you +created explicitly. At the class level, you have methods like `get` or +`before`, but you cannot access the `request` or `session` objects, as +there is only a single application class for all requests. + +Options created via `set` are methods at class level: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, I'm no longer in the application scope! + end +end +``` + +You have the application scope binding inside: + +* Your application class body +* Methods defined by extensions +* The block passed to `helpers` +* Procs/blocks used as a value for `set` +* The block passed to `Sinatra.new` + +You can reach the scope object (the class) like this: + +* Via the object passed to configure blocks (`configure { |c| ... }`) +* `settings` from within the request scope + +### Request/Instance Scope + +For every incoming request, a new instance of your application class is +created, and all handler blocks run in that scope. From within this scope you +can access the `request` and `session` objects or call rendering methods like +`erb` or `haml`. You can access the application scope from within the request +scope via the `settings` helper: + +```ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + get '/define_route/:name' do + # Request scope for '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Request scope for "/#{params['name']}" + @value # => nil (not the same request) + end + + "Route defined!" + end +end +``` + +You have the request scope binding inside: + +* get, head, post, put, delete, options, patch, link and unlink blocks +* before and after filters +* helper methods +* templates/views + +### Delegation Scope + +The delegation scope just forwards methods to the class scope. However, it +does not behave exactly like the class scope, as you do not have the class +binding. Only methods explicitly marked for delegation are available, and you +do not share variables/state with the class scope (read: you have a different +`self`). You can explicitly add method delegations by calling +`Sinatra::Delegator.delegate :method_name`. + +You have the delegate scope binding inside: + +* The top-level binding, if you did `require "sinatra"` +* An object extended with the `Sinatra::Delegator` mixin + +Have a look at the code for yourself: here's the +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Command Line + +Sinatra applications can be run directly: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Options are: + +``` +-h # help +-p # set the port (default is 4567) +-o # set the host (default is 0.0.0.0) +-e # set the environment (default is development) +-s # specify rack server/handler (default is puma) +-q # turn on quiet mode for server (default is off) +-x # turn on the mutex lock (default is off) +``` + +### Multi-threading + +_Paraphrasing from +[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129) +by Konstantin_ + +Sinatra doesn't impose any concurrency model but leaves that to the +underlying Rack handler (server) like Puma or WEBrick. Sinatra +itself is thread-safe, so there won't be any problem if the Rack handler +uses a threaded model of concurrency. This would mean that when starting +the server, you'd have to specify the correct invocation method for the +specific Rack handler. The following example is a demonstration of how +to start a multi-threaded Rainbows server: + +```ruby +# config.ru + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +run App +``` + +```ruby +# rainbows.conf + +# Rainbows configurator is based on Unicorn. +Rainbows! do + use :ThreadSpawn +end +``` + +To start the server, the command would be: + +```shell +rainbows -c rainbows.conf +``` + +## Requirement + +The following Ruby versions are officially supported: +
    +
    Ruby 2.3
    +
    + 2.3 is fully supported and recommended. There are currently no plans to + drop official support for it. +
    + +
    Rubinius
    +
    + Rubinius is officially supported (Rubinius >= 2.x). It is recommended to + gem install puma. +
    + +
    JRuby
    +
    + The latest stable release of JRuby is officially supported. It is not + recommended to use C extensions with JRuby. It is recommended to + gem install trinidad. +
    +
    + +Versions of Ruby before 2.3 are no longer supported as of Sinatra 2.1.0. + +We also keep an eye on upcoming Ruby versions. + +The following Ruby implementations are not officially supported but still are +known to run Sinatra: + +* Older versions of JRuby and Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those) + +Not being officially supported means if things only break there and not on a +supported platform, we assume it's not our issue but theirs. + +We also run our CI against ruby-head (future releases of MRI), but we +can't guarantee anything, since it is constantly moving. Expect upcoming +2.x releases to be fully supported. + +Sinatra should work on any operating system supported by the chosen Ruby +implementation. + +If you run MacRuby, you should `gem install control_tower`. + +Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +Ruby version prior to 2.2. + +## The Bleeding Edge + +If you would like to use Sinatra's latest bleeding-edge code, feel free +to run your application against the master branch, it should be rather +stable. + +We also push out prerelease gems from time to time, so you can do a + +```shell +gem install sinatra --pre +``` + +to get some of the latest features. + +### With Bundler + +If you want to run your application with the latest Sinatra, using +[Bundler](https://bundler.io) is the recommended way. + +First, install bundler, if you haven't: + +```shell +gem install bundler +``` + +Then, in your project directory, create a `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# other dependencies +gem 'haml' # for instance, if you use haml +``` + +Note that you will have to list all your application's dependencies in +the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, +however, be automatically fetched and added by Bundler. + +Now you can run your app like this: + +```shell +bundle exec ruby myapp.rb +``` + +## Versioning + +Sinatra follows [Semantic Versioning](https://semver.org/), both SemVer and +SemVerTag. + +## Further Reading + +* [Project Website](http://www.sinatrarb.com/) - Additional documentation, + news, and links to other resources. +* [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need + help? Have a patch? +* [Issue tracker](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net) +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack + ([get an invite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed + recipes +* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra) + or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on + [RubyDoc](http://www.rubydoc.info/) +* [CI server](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-br.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-br.md new file mode 100644 index 0000000000..52dfbec106 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-br.md @@ -0,0 +1,3787 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Alguns dos trechos de código a seguir utilizam caracteres UTF-8. Então, caso +esteja utilizando uma versão de ruby inferior à `2.0.0`, adicione o encoding +no início de seus arquivos: + +```ruby +# encoding: utf-8 +``` + +Sinatra é uma +[DSL](https://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar aplicações web em Ruby com o mínimo de esforço e rapidez: + +```ruby +# minha_app.rb +require 'sinatra' + +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem: + +```shell +gem install sinatra +``` + +Em seguida execute: + +```shell +ruby minha_app.rb +``` + +Acesse em: [http://localhost:4567](http://localhost:4567) + +Códigos alterados só terão efeito após você reiniciar o servidor. +Por favor, reinicie o servidor após qualquer mudança ou use +[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +É recomendado também executar `gem install thin`. Caso esta gem esteja +disponível, o Sinatra irá utilizá-la. + +## Conteúdo + +* [Sinatra](#sinatra) + * [Conteúdo](#conteúdo) + * [Rotas](#rotas) + * [Condições](#condições) + * [Valores Retornados](#valores-retornados) + * [Validadores de rota personalizados](#validadores-de-rota-personalizados) + * [Arquivos estáticos](#arquivos-estáticos) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Linguagens de template disponíveis](#linguagens-de-template-disponíveis) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Acessando Variáveis nos Templates](#acessando-variáveis-nos-templates) + * [Templates com `yield` e layouts aninhados](#templates-com-yield-e-layouts-aninhados) + * [Templates Inline](#templates-inline) + * [Templates Nomeados](#templates-nomeados) + * [Associando extensões de arquivos](#associando-extensões-de-arquivos) + * [Adicionando seu Próprio Engine de Template](#adicionando-seu-próprio-engine-de-template) + * [Customizando lógica para encontrar templates](#customizando-lógica-para-encontrar-templates) + * [Filtros](#filtros) + * [Helpers](#helpers) + * [Utilizando Sessões](#utilizando-sessões) + * [Segurança Secreta da Sessão](#segurança-secreta-da-sessão) + * [Configuração da Sessão](#configuração-da-sessão) + * [Escolhande Seu Próprio Middleware de Sessão](#escolhendo-middleware-de-sessão) + * [Halting](#halting) + * [Passing](#passing) + * [Desencadeando Outra Rota](#desencadeando-outra-rota) + * [Definindo Corpo, Código de Status e Cabeçalhos](#definindo-corpo-codigo-de-status-cabeçalhos) + * [Transmitindo Respostas](#transmitindo-respostas) + * [Usando Logs](#usando-logs) + * [Tipos Mime](#tipos-mime) + * [Gerando URLS](#gerando-urls) + * [Redirecionamento do Navegador](#redirecionamento-do-navagador) + * [Controle de Cache](#controle-de-cache) + * [Enviando Arquivos](#enviando-arquivos) + * [Acessando o Objeto de Requisição](#acessando-o-objeto-de-requisição) + * [Anexos](#anexos) + * [Trabalhando com Data e Hora](#trabalhando-com-data-e-hora) + * [Procurando Arquivos de Modelo](#procurando-arquivos-de-modelo) + * [Configuração](#configuração) + * [Configurando proteção a ataques](#configurando-proteção-a-ataques) + * [Configurações Disponíveis](#configurações-disponíveis) + * [Ambientes](#ambientes) + * [Tratamento de Erros](#tratamento-de-erros) + * [Não Encontrado](#não-encontrado) + * [Erro](#erro) + * [Rack Middleware](#rack-middleware) + * [Testando](#testando) + * [Sinatra::Base - Middleware, Bibliotecas e Aplicações Modulares](#sinatrabase-middleware-bibliotecas-e-aplicações-modulares) + * [Modular vs. Estilo Clássico](#modular-vs-estilo-clássico) + * [Servindo uma Aplicação Modular](#servindo-uma-aplicação-modular) + * [Usando uma Aplicação de Estilo Clássico com um config.ru](#usando-uma-aplicação-de-estilo-clássico-com-um-configru) + * [Quando usar um config.ru?](#quando-usar-um-configru) + * [Usando Sinatra como Middleware](#usando-sinatra-como-middleware) + * [Criação de Aplicações Dinâmicas](#criação-de-aplicações-dinamicas) + * [Escopos e Ligação](#escopos-e-ligação) + * [Escopo de Aplicação/Classe](#escopo-de-aplicação-classe) + * [Escopo de Instância/Requisição](#escopo-de-instância-requisição) + * [Escopo de Delegação](#escopo-de-delegação) + * [Linha de comando](#linha-de-comando) + * [Multi-threading](#multi-threading) + * [Requerimentos](#requerimentos) + * [A última versão](#a-última-versão) + * [Com Bundler](#com-bundler) + * [Versionando](#versionando) + * [Mais](#mais) + +## Rotas + +No Sinatra, uma rota é um método HTTP emparelhado com um padrão de URL. +Cada rota possui um bloco de execução: + +```ruby +get '/' do + .. mostrando alguma coisa .. +end + +post '/' do + .. criando alguma coisa .. +end + +put '/' do + .. atualizando alguma coisa .. +end + +patch '/' do + .. modificando alguma coisa .. +end + +delete '/' do + .. removendo alguma coisa .. +end + +options '/' do + .. estabelecendo alguma coisa .. +end + +link '/' do + .. associando alguma coisa .. +end + +unlink '/' do + .. separando alguma coisa .. +end +``` + +As rotas são interpretadas na ordem em que são definidas. A primeira +rota encontrada responde a requisição. + +Rotas com barras à direita são diferentes das que não contém as barras: + +```ruby +get '/foo' do + # Não é o mesmo que "GET /foo/" +end +``` + +Padrões de rota podem conter parâmetros nomeados, acessíveis por meio do +hash `params`: + +```ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Você também pode acessar parâmetros nomeados por meio dos parâmetros de +um bloco: + +```ruby +get '/ola/:nome' do |n| + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + # n guarda o valor de params['nome'] + "Olá #{n}!" +end +``` + +Padrões de rota também podem conter parâmetros splat (curinga), +acessível por meio do array `params['splat']`: + +```ruby +get '/diga/*/para/*' do + # corresponde a /diga/ola/para/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/caminho/do/arquivo.xml + params['splat'] # => ["caminho/do/arquivo", "xml"] +end +``` + +Ou com parâmetros de um bloco: + +```ruby +get '/download/*.*' do |caminho, ext| + [caminho, ext] # => ["caminho/do/arquivo", "xml"] +end +``` + +Rotas podem casar com expressões regulares: + +```ruby +get /\/ola\/([\w]+)/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com parâmetros de um bloco: + +```ruby +get %r{/ola/([\w]+)} do |c| + # corresponde a "GET /meta/ola/mundo", "GET /ola/mundo/1234" etc. + "Olá, #{c}!" +end +``` + +Padrões de rota podem contar com parâmetros opcionais: + +```ruby +get '/posts/:formato?' do + # corresponde a "GET /posts/" e qualquer extensão "GET /posts/json", "GET /posts/xml", etc. +end +``` + +Rotas também podem utilizar query strings: + +```ruby +get '/posts' do + # corresponde a "GET /posts?titulo=foo&autor=bar" + titulo = params['titulo'] + autor = params['autor'] + # utiliza as variáveis titulo e autor; a query é opcional para a rota /posts +end +``` + +A propósito, a menos que você desative a proteção contra ataques (veja +[abaixo](#configurando-proteção-a-ataques)), o caminho solicitado pode ser +alterado antes de concluir a comparação com as suas rotas. + +Você pode customizar as opções usadas do +[Mustermann](https://github.com/sinatra/mustermann#readme) para uma +rota passando `:mustermann_opts` num hash: + +```ruby +get '\A/posts\z', :musterman_opts => { :type => regexp, :check_anchors => false } do + # corresponde a /posts exatamente, com ancoragem explícita + "Se você combinar um padrão ancorado bata palmas!" +end +``` + +Parece com uma [condição](#condições) mas não é! Essas opções serão +misturadas no hash global `:mustermann_opts` descrito +[abaixo](#configurações-disponíveis) + +## Condições + +Rotas podem incluir uma variedade de condições, tal como o `user agent`: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está usando o Songbird versão #{params['agent'][0]}" +end + +get '/foo' do + # Correspondente a navegadores que não sejam Songbird +end +``` + +Outras condições disponíveis são `host_name` e `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Área administrativa. Acesso negado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` procura o cabeçalho Accept das requisições + +Você pode facilmente definir suas próprias condições: + +```ruby +set(:probabilidade) { |valor| condition { rand <= valor } } + +get '/ganha_um_carro', :probabilidade => 0.1 do + "Você ganhou!" +end + +get '/ganha_um_carro' do + "Sinto muito, você perdeu." +end +``` + +Use splat, para uma condição que leva vários valores: + +```ruby +set(:auth) do |*roles| # <- observe o splat aqui + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/minha/conta/", :auth => [:usuario, :administrador] do + "Detalhes da sua conta" +end + +get "/apenas/administrador/", :auth => :administrador do + "Apenas administradores são permitidos aqui!" +end +``` + +## Retorno de valores + +O valor de retorno do bloco de uma rota determina pelo menos o corpo da +resposta passado para o cliente HTTP, ou pelo menos o próximo middleware +na pilha Rack. Frequentemente, isto é uma `string`, tal como nos +exemplos acima. Entretanto, outros valores também são aceitos. + +Você pode retornar uma resposta válida ou um objeto para o Rack, sendo +eles de qualquer tipo de objeto que queira. Além disso, é possível +retornar um código de status HTTP. + +* Um array com três elementos: `[status (Integer), cabeçalho (Hash), + corpo da resposta (responde à #each)]` + +* Um array com dois elementos: `[status (Integer), corpo da resposta + (responde à #each)]` + +* Um objeto que responda à `#each` sem passar nada, mas, sim, `strings` + para um dado bloco + +* Um objeto `Integer` representando o código de status + +Dessa forma, podemos implementar facilmente um exemplo de streaming: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Você também pode usar o método auxiliar `stream` +([descrito abaixo](#respostas-de-streaming)) para reduzir códigos +boilerplate e para incorporar a lógica de streaming (transmissão) na rota. + +## Validadores de Rota Personalizados + +Como apresentado acima, a estrutura do Sinatra conta com suporte +embutido para uso de padrões de String e expressões regulares como +validadores de rota. No entanto, ele não pára por aí. Você pode +facilmente definir os seus próprios validadores: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note que o exemplo acima pode ser robusto e complicado em excesso. Pode +também ser implementado como: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou, usando algo mais denso à frente: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do diretório +`./public`. Você pode especificar um local diferente pela opção +`:public_folder` + +```ruby +set :public_folder, __dir__ + '/estatico' +``` + +Note que o nome do diretório público não é incluído na URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://exemplo.com/css/style.css`. + +Use a configuração `:static_cache_control` (veja [abaixo](#controle-de-cache)) +para adicionar a informação `Cache-Control` no cabeçalho. + +## Views / Templates + +Cada linguagem de template é exposta através de seu próprio método de +renderização. Estes métodos simplesmente retornam uma string: + +```ruby +get '/' do + erb :index +end +``` + +Isto renderiza `views/index.rb` + +Ao invés do nome do template, você também pode passar direto o conteúdo do +template: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates também aceitam como um segundo argumento, um hash de opções: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Isto irá renderizar a `views/index.erb` inclusa dentro da `views/post.erb` +(o padrão é a `views/layout.erb`, se existir). + +Qualquer opção não reconhecida pelo Sinatra será passada adiante para o engine +de template: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Você também pode definir opções padrões para um tipo de template: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Opções passadas para o método de renderização sobrescreve as opções definitas +através do método `set`. + +Opções disponíveis: + +
    +
    locals
    +
    + Lista de locais passado para o documento. Conveniente para *partials* + Exemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + String encoding para ser utilizada em caso de incerteza. o padrão é + settings.default_encoding. +
    + +
    views
    +
    + Diretório de onde os templates são carregados. O padrão é + settings.views. +
    + +
    layout
    +
    + Para definir quando utilizar ou não um + layout (true ou false). E se for um + Symbol, especifica qual template usar. Exemplo: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + O *Content-Type* que o template produz. O padrão depende + da linguagem de template utilizada. +
    + +
    scope
    +
    + Escopo em que o template será renderizado. Padrão é a + instância da aplicação. Se você mudar isto as variáveis + de instância métodos auxiliares não serão + disponibilizados. +
    + +
    layout_engine
    +
    + A engine de template utilizada para renderizar seu layout. + Útil para linguagens que não suportam templates de outra + forma. O padrão é a engine do template utilizado. Exemplo: + set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Opções especiais utilizadas apenas para renderizar o + layout. Exemplo: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +É pressuposto que os templates estarão localizados diretamente sob o +diretório `./views`. Para usar um diretório diferente: + +```ruby +set :views, settings.root + '/templates' +``` + +Uma coisa importante para se lembrar é que você sempre deve +referenciar os templates utilizando *symbols*, mesmo que +eles estejam em um subdiretório (neste caso use: +`:'subdir/template'` or `'subdir/template'.to_sym`). Você deve +utilizar um *symbol* porque senão o método de renderização irá +renderizar qualquer outra string que você passe diretamente +para ele + +### Literal Templates + +```ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza um template string. Você pode opcionalmente especificar `path` +e `:line` para um backtrace mais claro se existir um caminho do sistema de +arquivos ou linha associada com aquela string. + +```ruby +get '/' do + haml '%div.title Olá Mundo', :path => 'exemplos/arquivo.haml', :line => 3 +end +``` + +### Linguagens de template disponíveis + +Algumas linguagens possuem múltiplas implementações. Para especificar qual +implementação deverá ser utilizada (e para ser *thread-safe*), você deve +requerê-la primeiro: + +```ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
    Dependênciahaml
    Extensão do Arquivo.haml
    Exemplohaml :index, :format => :html5
    + +#### Erb Templates + + + + + + + + + + + + + + +
    Dependência + erubis + or erb (included in Ruby) +
    Extensão dos Arquivos.erb, .rhtml or .erubis (Erubis only)
    Exemploerb :index
    + +#### Builder Templates + + + + + + + + + + + + + + +
    Dependência + + builder + +
    Extensão do Arquivo.builder
    Exemplobuilder { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see exemplo). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
    Dependêncianokogiri
    Extensão do Arquivo.nokogiri
    Exemplonokogiri { |xml| xml.em "hi" }
    + +It also takes a block for inline templates (see exemplo). + +#### Sass Templates + + + + + + + + + + + + + + +
    Dependênciasass
    Extensão do Arquivo.sass
    Exemplosass :stylesheet, :style => :expanded
    + +#### SCSS Templates + + + + + + + + + + + + + + +
    Dependênciasass
    Extensão do Arquivo.scss
    Exemploscss :stylesheet, :style => :expanded
    + +#### Less Templates + + + + + + + + + + + + + + +
    Dependêncialess
    Extensão do Arquivo.less
    Exemploless :stylesheet
    + +#### Liquid Templates + + + + + + + + + + + + + + +
    Dependência + liquid +
    Extensão do Arquivo.liquid
    Exemploliquid :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby (exceto pelo método `yield`) pelo template +Liquid, você quase sempre precisará passar o `locals` para ele. + +#### Markdown Templates + + + + + + + + + + + + + + +
    Dependência + Anyone of: + + RDiscount + , + + RedCarpet + , + + BlueCloth + , + + kramdown + , + + maruku + +
    Extensão do Arquivos.markdown, .mkd and .md
    Exemplomarkdown :index, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => markdown(:introducao) } +``` + +Note que você também pode chamar o método `markdown` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= markdown(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Markdown, você não +pode utilizar um layout escrito em Markdown. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + + + + + + + + + + + + + + +
    DependênciaRedCloth
    Extensão do Arquivo.textile
    Exemplotextile :index, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => textile(:introducao) } +``` + +Note que você também pode chamar o método `textile` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= textile(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Textile, você não +pode utilizar um layout escrito em Textile. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### RDoc Templates + + + + + + + + + + + + + + +
    DependênciaRDoc
    Extensão do Arquivo.rdoc
    Exemplordoc :README, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => rdoc(:introducao) } +``` + +Note que você também pode chamar o método `rdoc` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= rdoc(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo RDoc, você não +pode utilizar um layout escrito em RDoc. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
    Dependência + Asciidoctor +
    Extensão do Arquivo.asciidoc, .adoc and .ad
    Exemploasciidoc :README, :layout_engine => :erb
    + +Já que você não pode chamar o Ruby pelo template AsciiDoc, +você quase sempre precisará passar o `locals` para ele. + +#### Radius Templates + + + + + + + + + + + + + + +
    DependênciaRadius
    Extensão do Arquivo.radius
    Exemploradius :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby pelo template Radius, +você quase sempre precisará passar o `locals` para ele. + +#### Markaby Templates + + + + + + + + + + + + + + +
    DependênciaMarkaby
    Extensão do Arquivo.mab
    Exemplomarkaby { h1 "Welcome!" }
    + +Este também recebe um bloco para templates (veja o exemplo). + +#### RABL Templates + + + + + + + + + + + + + + +
    DependênciaRabl
    Extensão do Arquivo.rabl
    Exemplorabl :index
    + +#### Slim Templates + + + + + + + + + + + + + + +
    DependênciaSlim Lang
    Extensão do Arquivo.slim
    Exemploslim :index
    + +#### Creole Templates + + + + + + + + + + + + + + +
    DependênciaCreole
    Extensão do Arquivo.creole
    Exemplocreole :wiki, :layout_engine => :erb
    + +Não é possível chamar métodos por este template, nem passar *locals* para o +mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note que você também pode chamar o método `creole` dentro de outros templates: + +```ruby +%h1 Olá do Haml! +%p= creole(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Creole, você não +pode utilizar um layout escrito em Creole. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
    Dependência + + WikiCloth + +
    Extensão do Arquivo.mediawiki and .mw
    Exemplomediawiki :wiki, :layout_engine => :erb
    + +It is not possible to call methods from MediaWiki markup, nor to pass locals to +it. You therefore will usually use it in combination with another rendering +engine: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other templates: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Já que você não pode chamar o Ruby pelo MediaWiki, você não +pode utilizar um layout escrito em MediaWiki. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
    Dependência + + CoffeeScript + and a + + way to execute javascript + +
    Extensão do Arquivo.coffee
    Exemplocoffee :index
    + +#### Stylus Templates + + + + + + + + + + + + + + +
    Dependência + + Stylus + and a + + way to execute javascript + +
    Extensão do Arquivo.styl
    Exemplostylus :index
    + +Antes que você possa utilizar o template Stylus primeiro você deve carregar +`stylus` e `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemplo +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
    Dependência + + yajl-ruby + +
    Extensão do Arquivo.yajl
    Exemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +O código-fonte do template é executado como uma string Ruby e a variável +resultante em json é convertida utilizando `#to_json`: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +O `:callback` e `:variable` são opções que podem ser utilizadas para o objeto +de renderização: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
    Dependência + WLang +
    Extensão do Arquivo.wlang
    Exemplowlang :index, :locals => { :key => 'value' }
    + +Já que você não pode chamar o Ruby (exceto pelo método +`yield`) pelo template WLang, +você quase sempre precisará passar o `locals` para ele. + +## Acessando Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto como manipuladores de +rota. Variáveis de instância definidas em manipuladores de rota são +diretamente acessadas por templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito de variáveis locais: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizando quando renderizamos templates como +partials dentro de outros templates. + +### Templates com `yield` e layouts aninhados + +Um layout geralmente é apenas um template que executa `yield`. +Tal template pode ser utilizado pela opção `:template` descrita acima ou pode +ser renderizado através de um bloco, como a seguir: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Este código é quase equivalente a `erb :index, :layout => :post` + +Passando blocos para os métodos de renderização é útil para criar layouts +aninhados: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Também pode ser feito com menos linhas de código: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Atualmente os métodos listados aceitam blocos: `erb`, `haml`, +`liquid`, `slim `, `wlang`. E o método geral `render` também aceita blocos. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo. +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo Sinatra. Digite `enable :inline_templates` explicitamente se +você tem templates inline em outros arquivos fonte. + +### Templates Nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado toda vez +que um template for renderizado. Você pode desabilitar layouts passando +`:layout => false` ou desabilita-los por padrão +via `set :haml, :layout => false` + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associando Extensões de Arquivos + +Para associar uma extensão de arquivo com um engine de template use o método +`Tilt.register`. Por exemplo, se você quiser usar a extensão `tt` para os +templates Textile você pode fazer o seguinte: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adicionando seu Próprio Engine de Template + +Primeiro registre seu engine utilizando o Tilt, e então crie um método de +renderização: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renderize `./views/index.myat`. Aprenda mais sobre o +Tilt [aqui](https://github.com/rtomayko/tilt#readme). + +### Customizando Lógica para Encontrar Templates + +Para implementar sua própria lógica para busca de templates você pode escrever +seu próprio método `#find_template` + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância definidas nos filtros são acessadas através de rotas e +templates: + +```ruby +before do + @nota = 'Oi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Oi!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do mesmo contexto da +requisição e também podem modificar a requisição e a resposta. Variáveis de +instância definidas nos filtros before e rotas são acessadas através dos +filtros after: + +```ruby +after do + puts response.status +end +``` + +Nota: A não ser que você use o método `body` ao invés de apenas retornar uma +String das rotas, o corpo ainda não estará disponível no filtro after, uma vez +que é gerado depois. + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Como rotas, filtros também aceitam condições: + +```ruby +before :agent => /Songbird/ do + #... +end + +after '/blog/*', :host_name => 'exemplo.com' do + #... +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +```ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +Alternativamente, métodos auxiliares podem ser definidos separadamente em +módulos: + +```ruby +module FooUtils + def foo(nome) "#{nome}foo" end +end + +module BarUtils + def bar(nome) "#{nome}bar" end +end + +helpers FooUtils, BarUtils +``` + +O efeito é o mesmo que incluir os módulos na classe da aplicação. + +### Utilizando Sessões + + Uma sessão é usada para manter o estado durante requisições. Se ativada, você + terá disponível um hash de sessão para cada sessão de usuário: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` +#### Segredo Seguro da Sessão + +Para melhorar a segurança, os dados da sessão no cookie são assinado com uma +segredo de sessão usando `HMAC-SHA1`. Esse segredo de sessão deve ser, de +preferência, um valor criptograficamente randômico, seguro, de um comprimento +apropriado no qual `HMAC-SHA1` é maior ou igual a 64 bytes (512 bits, 128 +caracteres hexadecimais). Você será avisado para não usar uma chave secreta +menor que 32 bytes de randomicidade (256 bits, 64 caracteres hexadecimais). +Portanto, é **muito importante** que você não invente o segredo, mas use um +gerador de números aleatórios seguro para cria-lo. Humanos são extremamente +ruins em gerar números aleatórios. + +Por padrão, um segredo de sessão aleatório seguro de 32 bytes é gerada para +você pelo Sinatra, mas ele mudará toda vez que você reiniciar sua aplicação. Se +você tiver múltiplas instâncias da sua aplicação e você deixar que o Sinatra +gere a chave, cada instância teria uma chave de sessão diferente, o que +certamente não é o que você quer. + +Para melhor segurança e usabilidade é +[recomendado](https://12factor.net/config) que você gere um segredo randômico +secreto e salve-o em uma variável de ambiente em cada host rodando sua +aplicação, assim todas as instâncias da sua aplicação irão compartilhar o mesmo +segredo. Você deve, periodicamente, mudar esse segredo de sessão para um novo +valor. Abaixo, são mostrados alguns exemplos de como você pode criar um segredo +de 64 bytes e usa-lo: + +**Gerando Segredo de Sessão** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Gerando Segredo de Sessão (Pontos adicionais)** + +Use preferencialmente a +[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) para utilizar +as facilidades do sistema RNG para gerar valores aleatórios ao invés +do `OpenSSL` no qual o MRI Ruby padroniza para: + +```text +$ gem install sysrandom +Building native extensions. This could take a while... +Sucessfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Segredo de Sessão numa Variável de Ambiente** + +Defina uma variável de ambiente `SESSION_SECRET` para o Sinatra com o valor que +você gerou. Salve esse valor entre as reinicializações do seu host. Já que a +forma de fazer isso irá variar entre os sistemas operacionais, o exemplo abaixo +serve apenas para fins ilustrativos: + +```bash +# echo "export SESSION_SECRET = 99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Configurando o Segredo de Sessão na Aplicação** + +Configure sua aplicação para uma falha de segredo seguro aleatório se a +variável de ambiente `SESSION_SECRET` não estiver disponível. + +Como ponto adicional use a +[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) da seguinte +forma: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securearandom' +set :session_secret, ENV.fecth(`SESSION_SECRET`) { SecureRandom.hex(64) } +``` + +#### Configuração de Sessão + +Se você deseja configurar isso adicionalmente, você pode salvar um hash com +opções na definição de `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Para compartilhar sua sessão com outras aplicações no subdomínio de foo.com, +adicione um *.* antes do domínio como no exemplo abaixo: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Escolhendo Seu Próprio Middleware de Sessão + +Perceba que `enable :sessions` na verdade guarda todos seus dados num cookie. +Isto pode não ser o que você deseja sempre (armazenar muitos dados irá aumentar +seu tráfego, por exemplo). Você pode usar qualquer middleware de sessão Rack +para fazer isso, um dos seguintes métodos pode ser usado: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Ou definir as sessões com um hash de opções: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Outra opção é **não** usar `enable :sessions`, mas ao invés disso trazer seu +middleware escolhido como você faria com qualquer outro middleware. + +É importante lembrar que usando esse método, a proteção baseada na sessão +**não estará habilitada por padrão**. + +Para o middleware Rack fazer isso, será preciso que isso também seja adicionado: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` +Veja '[Configurando proteção a ataques](#configurando-proteção-a-ataques)' para +mais informações. + +### Parando + +Para parar imediatamente uma requisição com um filtro ou rota utilize: + +```ruby +halt +``` + +Você também pode especificar o status quando parar: + +```ruby +halt 410 +``` + +Ou o corpo: + +```ruby +halt 'isso será o corpo' +``` + +Ou ambos: + +```ruby +halt 401, 'vá embora!' +``` + +Com cabeçalhos: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +Também é obviamente possível combinar um template com o `halt`: + +```ruby +halt erb(:error) +``` + +### Passing + +Uma rota pode processar aposta para a próxima rota correspondente usando +`pass`: + +```ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Você me pegou!' +end + +get '/adivinhar/*' do + 'Você falhou!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota compatível. Se nenhuma rota compatível for encontrada, um +404 é retornado. + +### Desencadeando Outra Rota + +As vezes o `pass` não é o que você quer, ao invés dele talvez você queira obter +o resultado chamando outra rota. Simplesmente utilize o método `call` neste +caso: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note que no exemplo acima você ganharia performance e facilitaria os testes ao +simplesmente mover `"bar"` para um método auxiliar usado por ambos `/foo` +e `/bar`. + +Se você quer que a requisição seja enviada para a mesma instância da aplicação +no lugar de uma duplicada, use `call!` no lugar de `call`. + +Veja a especificação do Rack se você quer aprender mais sobre o `call`. + +### Definindo Corpo, Código de Status e Cabeçalhos + +É possível e recomendado definir o código de status e o corpo da resposta com o +valor retornado do bloco da rota. Entretanto, em alguns cenários você pode +querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode +fazer isso com o método auxiliar `body`. Se você fizer isso, poderá usar esse +método de agora em diante para acessar o body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Também é possível passar um bloco para `body`, que será executado pelo +manipulador Rack (isso pode ser usado para implementar transmissão, +[veja "Retorno de Valores"](#retorno-de-valores)). + +Similar ao corpo, você pode também definir o código de status e cabeçalhos: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN" + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "Eu sou um bule de chá!" +end +``` + +Assim como `body`, `headers` e `status` sem argumentos podem ser usados para +acessar seus valores atuais. + +### Transmitindo Respostas + +As vezes você quer começar a mandar dados enquanto está gerando partes do corpo +da resposta. Em exemplos extremos, você quer continuar enviando dados até o +cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para +evitar criar seu próprio empacotador: + +```ruby +get '/' do + stream do |out| + out << "Isso será len -\n" + sleep 0.5 + out << " Aguarde \n" + sleep 1 + out << " dário!\n" + end +end +``` + +Isso permite você implementar APIs de Transmissão, +[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode +ser usado como a base para +[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também +para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender +de um recurso lento. + +Perceba que o comportamento da transmissão, especialmente o número de +requisições concorrentes, depende altamente do servidor web usado para servir a +aplicação. Alguns servidores podem até mesmo não suportar transmissão de +maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado +completamente após que o bloco passado para `stream` terminar de executar. +Transmissão não funciona de nenhuma maneira com Shotun. + +Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no +objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no +fluxo de execução. Isso funciona apenas em servidores orientados a eventos, +como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão: + +```ruby +# long polling + +set :server, :thin +conexoes = [] + +get '/assinar' do + # registra o interesse de um cliente em servidores de eventos + stream(:keep_open) do |saida| + conexoes << saida + # retire conexões mortas + conexoes.reject!(&:closed?) + end +end + +post '/:mensagem' do + conexoes.each do |saida| + # notifica o cliente que uma nova mensagem chegou + saida << params['mensagem'] << "\n" + + # indica ao cliente para se conectar novamente + saida.close + end + + # confirma + "mensagem recebida" +end +``` + +Também é possível para o cliente fechar a conexão quando está tentando escrever +para o socket. Devido a isso, é recomendado checar `out.closed?` antes de +tentar escrever. + +### Usando Logs + +No escopo da requisição, o método auxiliar `logger` expõe uma instância +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Esse logger irá automaticamente botar as configurações de log do manipulador +Rack na sua conta. Se a produção de logs estiver desabilitada, esse método +retornará um objeto dummy, então você não terá que se preocupar com suas rotas +e filtros. + +Perceba que a produção de logs está habilitada apenas para +`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`, +você provavelmente irá querer habilitar: + +```ruby + class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end + end +``` + +Para evitar que qualquer middleware de logs seja configurado, defina a +configuração `logging` como `nil`. Entretanto, tenha em mente que `logger` +retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir +seu próprio logger. Sinatra irá usar qualquer um que ele achar +em `env['rack.logger']` + +### Tipos Mime + +Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos +mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão +do arquivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Você pode utilizar também com o método auxiliar `content_type`: + +```ruby +get '/' do + content-type :foo + "foo foo foo" +end +``` + +### Gerando URLs + +Para gerar URLs você deve usar o método auxiliar `url` no Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Isso inclui proxies reversos e rotas Rack, se presentes. + +Esse método é também apelidado para `to` (veja +[abaixo](#redirecionamento-do-browser) para um exemplo). + +### Redirecionamento do Browser + +Você pode lançar um redirecionamento no browser com o método auxiliar +`redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Quaisquer parâmetros adicionais são interpretados como argumentos passados +ao `halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'lugar errado, amigo' +``` + +Você pode também facilmente redirecionar para a página da qual o usuário veio +com `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Para passar argumentos com um redirecionamento, adicione-os a query: + +```ruby +redirect to('/bar?sum=42') +``` + +Ou use uma sessão: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Controle de Cache + +Definir sues cabeçalhos corretamente é o principal passo para uma correta cache +HTTP. + +Você pode facilmente definir o cabeçalho de Cache-Control como: + +```ruby +get '/' do + cache_control :public + "guarde isso!" +end +``` + +Dica profissional: Configure a cache em um filtro anterior: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Se você está usando o método auxiliar `expires` para definir seu cabeçalho +correspondente, `Cache-Control` irá ser definida automaticamente para você: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar propriciamente caches, você deve considerar usar `etag` ou +`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer +qualquer tipo de processamento pesado, já que eles irão imediatamente retornar +uma resposta se o cliente já possui a versão atual na sua cache: + +```ruby +get "/artigo/:id" do + @artigo = Artigo.find params['id'] + last_modified @artigo.updated_at + etag @artigo.sha1 + erb :artigo +end +``` + +Também é possível usar uma +[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` +Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas +irão alimentar as informações necessárias para sua cache. Se você está +pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "olá" +end +``` + +Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache)) +para adicionar o cabeçalho de informação `Cache-Control` para arquivos +estáticos. + +De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o +cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o +recurso requisitado já existe. Sinatra assume que recursos para requisições +seguras (como get) e idempotentes (como put) já existem, enquanto que para +outros recursos (por exemplo requisições post) são tratados como novos +recursos. Você pode mudar esse comportamento passando em uma opção +`:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Artigo.create + erb :novo_artigo +end +``` + +Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Arquivos + +Para retornar os conteúdos de um arquivo como as resposta, você pode usar o +método auxiliar `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Também aceita opções: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +As opções são: + +
    +
    filename
    +
    Nome do arquivo a ser usado na respota, + o padrão é o nome do arquivo reak
    +
    last_modified
    +
    Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do + arquivo.
    + +
    type
    +
    Valor do cabeçalho Content-Type, extraído da extensão do arquivo se + inexistente.
    + +
    disposition
    +
    + Valor do cabeçalho Content-Disposition, valores possíveis: nil + (default), :attachment and :inline +
    + +
    length
    +
    Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do + arquivo.
    + +
    status
    +
    + Código de status a ser enviado. Útil quando está se enviando um arquivo + estático como uma página de erro. Se suportado pelo handler do Rack, + outros meios além de transmissão do processo do Ruby serão usados. + So você usar esse método auxiliar, o Sinatra irá automaticamente lidar com + requisições de alcance. +
    +
    + +### Acessando o Objeto da Requisição + +O objeto vindo da requisição pode ser acessado do nível de requisição (filtros, +rotas, manipuladores de erro) através do método `request`: + +```ruby +# app rodando em http://exemplo.com/exemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # corpo da requisição enviado pelo cliente (veja abaixo) + request.scheme # "http" + request.script_name # "/exemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # tamanho do request.body + request.media_type # tipo de mídia of request.body + request.host # "exemplo.com" + request.get? # true (método similar para outros tipos de requisição) + request.form_data? # false + request["algum_ param"] # valor do parâmetro 'algum_param'. [] é um atalho para o hash de parâmetros + request.referrer # a referência do cliente ou '/' + request.user_agent # agente de usuário (usado por :agent condition) + request.cookies # hash dos cookies do browser + request.xhr? # isto é uma requisição ajax? + request.url # "http://exemplo.com/exemplo/foo" + request.path # "/exemplo/foo" + request.ip # endereço de IP do cliente + request.secure? # false (seria true se a conexão fosse ssl) + request.forwarded? # true (se está rodando por um proxy reverso) + request.env # raw env hash handed in by Rack +end +``` + +Algumas opções, como `script_name` ou `path_info, podem ser escritas como: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas requisições acabam aqui" +end +``` +`request.body` é uma ES ou um objeto StringIO: + +```ruby +post "/api" do + request.body.rewind # em caso de algo já ter lido + data = JSON.parse request.body.read + "Oi #{data['nome']}!" +end +``` + +### Anexos + +Você pode usar o método auxiliar `attachment` para dizer ao navegador que a +resposta deve ser armazenada no disco no lugar de ser exibida no browser: + +```ruby +get '/' do + attachment "info.txt" + "salve isso!" +end +``` + +### Trabalhando com Data e Hora + +O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do +valor dado. É também possível converter `DateTime`, `Date` e classes similares: + + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "continua no tempo" +end +``` + +Esse método é usado internamente por `expires`, `last_modified` e akin. Você +pode portanto facilmente estender o comportamento desses métodos sobrescrevendo +`time_for` na sua aplicação: + +```ruby +helpers do + def time_for(valor) + case valor + when :ontem then Time.now - 24*60*60 + when :amanha then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ontem + expires :amanha + "oi" +end +``` + +### Pesquisando por Arquivos de Template + +O método auxiliar `find_template` é usado para encontrar arquivos de template +para renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |arquivo| + puts "pode ser #{arquivo}" +end +``` + +Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever +esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se +você quer ser capaz de usar mais de um diretório de view: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Outro exemplo seria utilizando diretórios diferentes para motores (engines) +diferentes: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras +pessoas! + +Perceba que `find_template` não verifica se o arquivo realmente existe. Ao +invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não +significa um problema de perfomance, já que `render` irá usar `break` assim que +o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates +serão guardados na cache se você não estiver rodando no modo de +desenvolvimento. Você deve se lembrar disso se você escrever um método +realmente maluco. + +## Configuração + +É possível e recomendado definir o código de status e o corpo da resposta com o +valor retornado do bloco da rota. Entretanto, em alguns cenários você pode +querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode +fazer isso com o método auxiliar `body`. Se você fizer isso, poderá usar esse +método de agora em diante para acessar o body: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Também é possível passar um bloco para `body`, que será executado pelo +manipulador Rack (isso pode ser usado para implementar transmissão, +[veja "Retorno de Valores"](#retorno-de-valores)). + +Similar ao corpo, você pode também definir o código de status e cabeçalhos: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN" + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "Eu sou um bule de chá!" +end +``` + +Assim como `body`, `headers` e `status` sem argumentos podem ser usados para +acessar seus valores atuais. + +### Transmitindo Respostas + +As vezes você quer começar a mandar dados enquanto está gerando partes do corpo +da resposta. Em exemplos extremos, você quer continuar enviando dados até o +cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para +evitar criar seu próprio empacotador: + +```ruby +get '/' do + stream do |out| + out << "Isso será len -\n" + sleep 0.5 + out << " Aguarde \n" + sleep 1 + out << " dário!\n" + end +end +``` + +Isso permite você implementar APIs de Transmissão, +[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode +ser usado como a base para +[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também +para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender +de um recurso lento. + +Perceba que o comportamento da transmissão, especialmente o número de +requisições concorrentes, depende altamente do servidor web usado para servir a +aplicação. Alguns servidores podem até mesmo não suportar transmissão de +maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado +completamente após que o bloco passado para `stream` terminar de executar. +Transmissão não funciona de nenhuma maneira com Shotun. + +Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no +objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no +fluxo de execução. Isso funciona apenas em servidores orientados a eventos, +como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão: + +```ruby +# long polling + +set :server, :thin +conexoes = [] + +get '/assinar' do + # registra o interesse de um cliente em servidores de eventos + stream(:keep_open) do |saida| + conexoes << saida + # retire conexões mortas + conexoes.reject!(&:closed?) + end +end + +post '/:messagem' do + conexoes.each do |saida| + # notifica o cliente que uma nova mensagem chegou + saida << params['messagem'] << "\n" + + # indica ao cliente para se conectar novamente + saida.close + end + + # confirma + "messagem recebida" +end +``` + +Também é possível para o cliente fechar a conexão quando está tentando escrever +para o socket. Devido a isso, é recomendado checar `out.closed?` antes de +tentar escrever. + +### Usando Logs + +No escopo da requisição, o método auxiliar `logger` expõe uma instância +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Esse logger irá automaticamente botar as configurações de log do manipulador +Rack na sua conta. Se a produção de logs estiver desabilitada, esse método +retornará um objeto dummy, então você não terá que se preocupar com suas rotas +e filtros. + +Perceba que a produção de logs está habilitada apenas para +`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`, +você provavelmente irá querer habilitar: + +```ruby + class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end + end +``` + +Para evitar que qualquer middleware de logs seja configurado, defina a +configuração `logging` como `nil`. Entretanto, tenha em mente que `logger` +retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir +seu próprio logger. Sinatra irá usar qualquer um que ele achar em +`env['rack.logger']` + +### Tipos Mime + +Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos +Mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão +do arquivo: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Você pode utilizar também com o método auxiliar `content_type`: + +```ruby +get '/' do + content-type :foo + "foo foo foo" +end +``` + +### Gerando URLs + +Para gerar URLs você deve usar o método auxiliar `url` no Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Isso inclui proxies reversos e rotas Rack, se presentes. + +Esse método é também apelidado para `to` (veja +[abaixo](#redirecionamento-do-browser) para um exemplo). + +### Redirecionamento do Browser + +Você pode lançar um redirecionamento no browser com o método auxiliar +`redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Quaisquer parâmetros adicionais são interpretados como argumentos passados ao +`halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'lugar errado, amigo' +``` + +Você pode também facilmente redirecionar para a página da qual o usuário veio +com `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Para passar argumentos com um redirecionamento, adicione-os a query: + +```ruby +redirect to('/bar?sum=42') +``` + +Ou use uma sessão: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Controle de Cache + +Definir sues cabeçalhos corretamente é o principal passo para uma correta cache +HTTP. + +Você pode facilmente definir o cabeçalho de Cache-Control como: + +```ruby +get '/' do + cache_control :public + "guarde isso!" +end +``` + +Dica profissional: Configure a cache em um filtro anterior: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Se você está usando o método auxiliar `expires` para definir seu cabeçalho +correspondente, `Cache-Control` irá ser definida automaticamente para você: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar propriciamente caches, você deve considerar usar `etag` ou +`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer +qualquer tipo de processamento pesado, já que eles irão imediatamente retornar +uma resposta se o cliente já possui a versão atual na sua cache: + +```ruby +get "/artigo/:id" do + @artigo = Artigo.find params['id'] + last_modified @artigo.updated_at + etag @artigo.sha1 + erb :artigo +end +``` + +Também é possível usar uma +[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` +Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas +irão alimentar as informações necessárias para sua cache. Se você está +pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente +[rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "olá" +end +``` + +Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache)) +para adicionar o cabeçalho de informação `Cache-Control` para arquivos +estáticos. + +De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o +cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o +recurso requisitado já existe. Sinatra assume que recursos para requisições +seguras (como get) e idempotentes (como put) já existem, enquanto que para +outros recursos (por exemplo requisições post) são tratados como novos +recursos. Você pode mudar esse comportamento passando em uma opção +`:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Artigo.create + erb :novo_artigo +end +``` + +Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Arquivos + +Para retornar os conteúdos de um arquivo como as resposta, você pode usar o +método auxiliar `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Também aceita opções: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +As opções são: + +
    +
    filename
    +
    Nome do arquivo a ser usado na respota, + o padrão é o nome do arquivo reak
    +
    last_modified
    +
    Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do + arquivo.
    + +
    type
    +
    Valor do cabeçalho Content-Type, extraído da extensão do arquivo se + inexistente.
    + +
    disposition
    +
    + Valor do cabeçalho Content-Disposition, valores possíveis: nil + (default), :attachment and :inline +
    + +
    length
    +
    Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do + arquivo.
    + +
    status
    +
    + Código de status a ser enviado. Útil quando está se enviando um arquivo + estático como uma página de erro. Se suportado pelo handler do Rack, + outros meios além de transmissão do processo do Ruby serão usados. + So você usar esse método auxiliar, o Sinatra irá automaticamente lidar + com requisições de alcance. +
    +
    + +### Acessando o Objeto da Requisção + +O objeto vindo da requisição pode ser acessado do nível de requisição (filtros, +rotas, manipuladores de erro) através do método `request`: + +```ruby +# app rodando em http://exemplo.com/exemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # corpo da requisição enviado pelo cliente (veja abaixo) + request.scheme # "http" + request.script_name # "/exemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # tamanho do request.body + request.media_type # tipo de mídia of request.body + request.host # "exemplo.com" + request.get? # true (metodo similar para outros tipos de requisição) + request.form_data? # false + request["algum_ param"] # valor do parâmetro 'algum_param'. [] é um atalho para o hash de parâmetros + request.referrer # a referência do cliente ou '/' + request.user_agent # agente de usuário (usado por :agent condition) + request.cookies # hash dos cookies do browser + request.xhr? # isto é uma requisição ajax? + request.url # "http://exemplo.com/exemplo/foo" + request.path # "/exemplo/foo" + request.ip # endereço de IP do cliente + request.secure? # false (seria true se a conexão fosse ssl) + request.forwarded? # true (se está rodando por um proxy reverso) + request.env # raw env hash handed in by Rack +end +``` + +Algumas opções, como `script_name` ou `path_info, podem ser escritas como: + +```ruby +before { request.path_info = "/" } + +get "/" do + "todas requisições acabam aqui" +end +``` +`request.body` é uma ES ou um objeto StringIO: + +```ruby +post "/api" do + request.body.rewind # em caso de algo já ter lido + data = JSON.parse request.body.read + "Oi #{data['nome']}!" +end +``` + +### Anexos + +Você pode usar o método auxiliar `attachment` para dizer ao navegador que a +reposta deve ser armazenada no disco no lugar de ser exibida no browser: + +```ruby +get '/' do + attachment "info.txt" + "salve isso!" +end +``` + +### Trabalhando com Data e Hora + +O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do +valor dado. É também possível converter `DateTime`, `Date` e classes similares: + + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "continua no tempo" +end +``` + +Esse método é usado internamente por `expires`, `last_modified` e akin. Você +pode portanto facilmente estender o comportamento desses métodos sobrescrevendo +`time_for` na sua aplicação: + +```ruby +helpers do + def time_for(valor) + case valor + when :ontem then Time.now - 24*60*60 + when :amanha then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ontem + expires :amanha + "oi" +end +``` + +### Pesquisando por Arquivos de Template + +O método auxiliar `find_template` é usado para encontrar arquivos de template +para renderizar: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |arquivo| + puts "pode ser #{arquivo}" +end +``` + +Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever +esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se +você quer ser capaz de usar mais de um diretório de view: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Outro exemplo seria utilizando diretórios diferentes para motores (engines) +diferentes: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras +pessoas! + +Perceba que `find_template` não verifica se o arquivo realmente existe. Ao +invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não +significa um problema de perfomance, já que `render` irá usar `break` assim que +o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates +serão guardados na cache se você não estiver rodando no modo de +desenvolvimento. Você deve se lembrar disso se você escrever um método +realmente maluco. + +## Configuração + +Rode uma vez, na inicialização, em qualquer ambiente: + +```ruby +configure do + ... +end +``` + +```ruby +configure do + # configurando uma opção + set :option, 'value' + + # configurando múltiplas opções + set :a => 1, :b => 2 + + # o mesmo que `set :option, true` + enable :option + + # o mesmo que `set :option, false` + disable :option + + # você pode também ter configurações dinâmicas com blocos + set(:css_dir) { File.join(views, 'css') } +end +``` + +Rode somente quando o ambiente (`APP_ENV` variável de ambiente) é definida para +`:production`: + +```ruby +configure :production do + ... +end +``` + +Rode quando o ambiente é definido para `:production` ou `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Você pode acessar essas opções por meio de `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configurando proteção a ataques + +O Sinatra está usando +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +para defender sua aplicação contra ataques oportunistas comuns. Você pode +facilmente desabilitar esse comportamento (o que irá abrir sua aplicação à +toneladas de vulnerabilidades comuns): + +```ruby +disable :protection +``` + +Para pular uma única camada de defesa, defina `protection` como um hash de +opções: + +```ruby +set :protection, :except => :path_traversal +``` + +Você também pode definir em um array, visando desabilitar uma lista de +proteções: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Por padrão, o Sinatra irá configurar apenas sessões com proteção se `:sessions` +tiver sido habilitado. Veja '[Utilizando Sessões](#utilizando-sessões)'. As +vezes você pode querer configurar sessões "fora" da aplicação do Sinatra, como +em config.ru ou com uma instância de `Rack::Builder` separada. Nesse caso, você +pode continuar configurando uma sessão com proteção passando a opção `:session`: + +```ruby +set :protection, :session => true +``` + +### Configurações Disponíveis + +
    +
    absolute_redirects
    +
    + Se desabilitada, o Sinatra irá permitir redirecionamentos relativos, + entretanto, isso não estará conforme a RFC 2616 (HTTP 1.1), que permite + apenas redirecionamentos absolutos. +
    +
    + Habilite se sua aplicação estiver rodando antes de um proxy reverso que + não foi configurado corretamente. Note que o método auxiliar url + irá continuar produzindo URLs absolutas, a não ser que você passe + false como segundo parâmetro. +
    +
    Desabilitado por padrão.
    + +
    add_charset
    +
    + Para tipos Mime o método auxiliar content_type irá + automaticamente adicionar a informação de codificação. Você deve adicionar + isto no lugar de sobrescrever essa opção: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + Caminho para o arquivo principal da aplicação, usado para detectar a raíz + do projeto, views e pastas públicas e templates inline. +
    + +
    bind
    +
    + Endereço IP a ser ligado (padrão: 0.0.0.0 ou localhost + se seu ambiente está definido como desenvolvimento). Usado apenas para + servidor embutido. +
    + +
    default_encoding
    +
    Codificação assumida caso a mesma seja desconhecida (padrão corresponde + a "utf-8").
    + +
    dump_errors
    +
    Exibe erros no log.
    + +
    environment
    +
    + Ambiente atual. O padrão é ENV['APP_ENV'], ou + "development" se o primeiro não estiver disponível. +
    + +
    logging
    +
    Usa o logger.
    + +
    lock
    +
    + Coloca um bloqueio em torno de cada requisição, executando apenas + processamento sob requisição por processo Ruby simultaneamente. +
    +
    Habilitado se sua aplicação não for 'thread-safe'. Desabilitado por + padrão.
    + +
    method_override
    +
    + Use a mágica _method para permitir formulários put/delete em + navegadores que não oferecem suporte à essas operações. +
    + +
    mustermann_opts
    +
    + Um hash de opções padrão para passar a Mustermann.new quando se está + compilado os caminho de roteamento. +
    + +
    port
    +
    Porta a ser escutada. Usado apenas para servidores embutidos.
    + +
    prefixed_redirects
    +
    + Inserir ou não inserir request.script_name nos redirecionamentos + se nenhum caminho absoluto for dado. Dessa forma redirect '/foo' + irá se comportar como redirect to('/foo'). +
    +
    Desabilitado por padrão.
    + +
    protection
    +
    + Habilitar ou não proteções a ataques web. Veja a sessão de proteção acima. +
    + +
    public_dir
    +
    Apelido para public_folder. Veja abaixo.
    + +
    public_folder
    +
    + Caminho para o diretório de arquivos públicos. Usado apenas se a exibição + de arquivos estáticos estiver habilitada (veja a configuração + static abaixo). Deduzido da configuração app_file se + não for definido. +
    + +
    quiet
    +
    + Desabilita logs gerados pelos comandos de inicio e parada do Sinatra. + false por padrão. +
    + +
    reload_templates
    +
    + Se deve ou não recarregar templates entre as requisições. Habilitado no + modo de desenvolvimento. +
    + +
    root
    +
    + Caminho para o diretório raíz do projeto. Deduzido da configuração + app_file se não for definido. +
    + +
    raise_errors
    +
    + Lança exceções (irá para a aplicação). Habilitado por padrão quando o + ambiente está definido para "test, desabilitado em caso + contrário. +
    + +
    run
    +
    + Se habilitado, o Sinatra irá lidar com o início do servidor web. Não + habilite se estiver usando rackup ou outros meios. +
    + +
    running
    +
    É o servidor embutido que está rodando agora? Não mude essa + configuração!
    + +
    server
    +
    + Servidor ou listas de servidores para usar o servidor embutido. A ordem + indica prioridade, por padrão depende da implementação do Ruby +
    + +
    server_settings
    +
    + Se você estiver usando um servidor web WEBrick, presumidamente para seu + ambiente de desenvolvimento, você pode passar um hash de opções para + server_settings, tais como SSLEnable ou + SSLVerifyClient. Entretanto, servidores web como Puma e Thin não + suportam isso, então você pode definir server_settings como um + método quando chamar configure. +
    + +
    sessions
    +
    + Habilita o suporte a sessões baseadas em cookie usando + Rack::Session::Cookie. Veja a seção 'Usando Sessões' para mais + informações. +
    + +
    session_store
    +
    + O middleware de sessão Rack usado. O padrão é + Rack::Session::Cookie. Veja a sessão 'Usando Sessões' para mais + informações. +
    + +
    show_exceptions
    +
    + Mostra um relatório de erros no navegador quando uma exceção ocorrer. + Habilitado por padrão quando o ambiente é definido como + "development", desabilitado caso contrário. +
    +
    + Pode também ser definido para :after_handler para disparar um + manipulador de erro específico da aplicação antes de mostrar um relatório + de erros no navagador. +
    + +
    static
    +
    + Define se o Sinatra deve lidar com o oferecimento de arquivos + estáticos. +
    +
    + Desabilitado quando está utilizando um servidor capaz de fazer isso + sozinho. +
    +
    Desabilitar irá aumentar a perfomance
    +
    + Habilitado por padrão no estilo clássico, desabilitado para aplicações + modulares. +
    + +
    static_cache_control
    +
    + Quando o Sinatra está oferecendo arquivos estáticos, definir isso irá + adicionar cabeçalhos Cache-Control nas respostas. Usa o método + auxiliar cache-control. Desabilitado por padrão. +
    +
    + Use um array explícito quando estiver definindo múltiplos valores: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + Se estiver definido como true, irá definir que o Thin use + EventMachine.defer para processar a requisição. +
    + +
    traps
    +
    Define se o Sinatra deve lidar com sinais do sistema.
    + +
    views
    +
    + Caminho para o diretório de views. Deduzido da configuração + app_file se não estiver definido. +
    + +
    x_cascade
    +
    + Se deve ou não definir o cabeçalho X-Cascade se nenhuma rota combinar. + Definido como padrão true +
    +
    + +## Ambientes + +Existem três `environments` (ambientes) pré-definidos: `"development"` +(desenvolvimento), `"production"` (produção) e `"test"` (teste). Ambientes +podem ser definidos através da variável de ambiente `APP_ENV`. O valor padrão é +`"development"`. No ambiente `"development"` todos os templates são +recarregados entre as requisições e manipuladores especiais como `not_found` e +`error` exibem relatórios de erros no seu navegador. Nos ambientes de +`"production"` e `"test"`, os templates são guardados em cache por padrão. + +Para rodar diferentes ambientes, defina a variável de ambiente `APP_ENV`: + +```shell +APP_ENV=production ruby minha_app.rb +``` + +Você pode usar métodos pré-definidos: `development?`, `test?` e `production?` +para checar a configuração atual de ambiente: + +```ruby +get '/' do + if settings.development? + "desenvolvimento!" + else + "não está em desenvolvimento!" + end +end +``` + +## Tratamento de Erros + +Manipuladores de erros rodam dentro do mesmo contexto como rotas e filtros +before, o que significa que você pega todos os "presentes" que eles têm para +oferecer, como `haml`, `erb`, `halt`, etc. + +### Não Encontrado + +Quando uma exceção `Sinatra::NotFound` é lançada, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +```ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado toda a vez que uma exceção é lançada a +partir de um bloco de rota ou um filtro. Note que em desenvolvimento, ele irá +rodar apenas se você tiver definido a opção para exibir exceções em +`:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +O objeto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +```ruby +error do + 'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros customizados: + +```ruby +error MeuErroCustomizado do + 'Então que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +```ruby +get '/' do + raise MeuErroCustomizado, 'alguma coisa ruim' +end +``` + +Você receberá isso: + +``` +Então que aconteceu foi... alguma coisa ruim +```` + +Alternativamente, você pode instalar um manipulador de erro para um código +de status: + +```ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um alcance: + +```ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra instala os manipuladores especiais `not_found` e `error` +quando roda sobre o ambiente de desenvolvimento para exibir relatórios de erros +bonitos e informações adicionais de "debug" no seu navegador. + +## Rack Middleware + +O Sinatra roda no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Um das capacidades mais +interessantes do Rack para desenvolver aplicativos é suporte a +“middleware” – componentes que ficam entre o servidor e sua aplicação +monitorando e/ou manipulando o request/response do HTTP para prover +vários tipos de funcionalidades comuns. + +O Sinatra faz construtores pipelines do middleware Rack facilmente em um +nível superior utilizando o método `use`: + +```ruby +require 'sinatra' +require 'meu_middleware_customizado' + +use Rack::Lint +use MeuMiddlewareCustomizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis bem como blocos: + +```ruby +use Rack::Auth::Basic do |usuario, senha| + usuario == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuído com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão. Sinatra +utilizada muitos desses componentes automaticamente baseando sobre +configuração, então, tipicamente você não tem `use` explicitamente. + +Você pode achar middlewares utéis em +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +ou em [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +```ruby +require 'minha_aplicacao_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parâmetros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Se você está usando o Sinatra no estilo modular, substitua +`Sinatra::Application' acima com o nome da classe da sua aplicação + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação em um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis inconvenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: uma simples +arquivo de aplicação, diretórios `./public` e `./views`, logs, página de +detalhes de exceção, etc.). É onde o `Sinatra::Base` entra em jogo: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Ola mundo!' + end +end +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +* Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; caso +contrário, todos os métodos DSL do Sinatra são importados para o espaço de +nomes principal. + +* Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções +numa subclasse de `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desabilitadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. Se você quer +comportamento mais similar à quando você definiu sua aplicação em nível mais +alto (também conhecido como estilo Clássico), você pode usar subclasses de +`Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Application + get '/' do + 'Olá mundo!' + end +end +``` + +### Estilo Clássico vs. Modular + +Ao contrário da crença comum, não há nada de errado com o estilo clássico. Se +encaixa com sua aplicação, você não tem que mudar para uma aplicação modular. + +As desvantagens principais de usar o estilo clássico no lugar do estilo modular +é que você ira ter apenas uma aplicação Sinatra por processo Ruby. Se seu plano +é usar mais de uma, mude para o estilo modular. Não há nenhum impedimento para +você misturar os estilos clássico e modular. + +Se vai mudar de um estilo para outro, você deve tomar cuidado com algumas +configurações diferentes: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConfiguraçãoClássicoModularModular
    app_filearquivo carregando sinatraarquivo usando subclasse Sinatra::Basearquivo usando subclasse Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Servindo uma Aplicação Modular: + +Existem duas opções comuns para começar uma aplicação modular, ativamente +começando com `run!`: + +```ruby +# minha_app.rb +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + # ... código da aplicação aqui ... + + # inicie o servidor se o arquivo ruby foi executado diretamente + run! if app_file == $0 +end +``` + +Inicie com with: + +```shell +ruby minha_app.rb +``` + +Ou com um arquivo `config.ru`, que permite você usar qualquer manipulador Rack: + +```ruby +# config.ru (roda com rackup) +require './minha_app' +run MinhaApp +``` + +Rode: + +```shell +rackup -p 4567 +``` + +### Usando uma Aplicação de Estilo Clássico com um config.ru: + +Escreva o arquivo da sua aplicação: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Olá mundo!' +end +``` + +E um `config.ru` correspondente: + +```ruby +require './app' +run Sinatra::Application +``` + +### Quando usar um config.ru? + +Um arquivo `config.ru` é recomendado se: + +* Você quer lançar com um manipulador Rack diferente (Passenger, Unicorn, +Heroku, ...). + +* Você quer usar mais de uma subclasse de `Sinatra::Base`. +* Você quer usar Sinatra apenas como middleware, mas não como um "endpoint". + +**Não há necessidade de mudar para um `config.ru` simplesmente porque você mudou para o estilo modular, e você não tem que usar o estilo modular para rodar com um `config.ru`.** + +### Usando Sinatra como Middleware + +O Sinatra não é capaz apenas de usar outro middleware Rack, qualquer aplicação +Sinatra pode ser adicionada na frente de qualquer "endpoint" Rack como +middleware. Esse endpoint pode ser outra aplicação Sinatra, ou qualquer outra +aplicação baseada em Rack (Rails/Hanami/Roda/...): + +```ruby +require 'sinatra/base' + +class TelaLogin < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['nome'] == 'admin' && params['senha'] == 'admin' + session['nome_usuario'] = params['nome'] + else + redirect '/login' + end + end +end + +class MinhaApp < Sinatra::Base + # middleware irá rodar filtros before + use TelaLogin + + before do + unless session['nome_usuario'] + halt "Acesso negado, por favor login." + end + end + + get('/') { "Olá #{session['nome_usuario']}." } +end +``` + +### Criação de Aplicações Dinâmicas + +Às vezes, você quer criar novas aplicações em tempo de execução sem ter que +associa-las a uma constante. Você pode fazer isso com `Sinatra.new`: + +```ruby +require 'sinatra/base' +minha_app = Sinatra.new { get('/') { "oi" } } +minha_app.run! +``` + +Isso leva a aplicação à herdar como um argumento opcional: + +```ruby +# config.ru (roda com rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MeusHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Isso é especialmente útil para testar extensões do Sinatra ou usar Sinatra na +sua própria biblioteca. + +Isso também faz o uso do Sinatra como middleware extremamente fácil: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +# Escopos e Ligação + +O escopo que você está atualmente determina quais métodos e varáveis está +disponíveis. + +### Escopo de Aplicação/Classe + +Toda aplicação Sinatra corresponde à um subclasse de `Sinatra::Base`. Se você +está utilizando a DSL de nível mais alto (`require 'sinatra`), então esta +classe é `Sinatra::Application`, caso contrário é a subclasse que você criou +explicitamente. No nível de classe você tem métodos como `get` ou `before`, mas +você não pode acessar os objetos `request` ou `session`, como existe apenas uma +única classe de aplicativo para todas as solicitações. + +Opções criadas via `set` são métodos a nível de classe: + +```ruby +class MinhaApp < Sinatra::Base + # Hey, eu estou no escopo da aplicação! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, eu não estou mais no escopo da aplicação! + end +end +``` + +Você tem a ligação ao escopo da aplicação dentro: + +* Do corpo da classe da sua aplicação +* De métodos definidos por extensões +* Do bloco passado a `helpers` +* De Procs/Blocos usados como valor para `set`` +* Do bloco passado a `Sinatra.new`` + +Você pode atingir o escopo do objeto (a classe) de duas maneiras: + +* Por meio do objeto passado aos blocos "configure" (`configure { |c| ...}`) +* `settings` de dentro do escopo da requisição + +### Escopo de Instância/Requisição + +Para toda requisição que chega, uma nova instância da classe da sua aplicação é +criada e todos blocos de manipulação rodam nesse escopo. Dentro desse escopo +você pode acessar os objetos `request` e `session` ou chamar métodos de +renderização como `erb` ou `haml`. Você pode acessar o escopo da aplicação de +dentro do escopo da requisição através do método auxiliar `settings`: + +```ruby +class MinhaApp < Sinatra::Base + # Hey, eu estou no escopo da aplicação! + get '/define_rota/:nome' do + # Escopo da requisição para '/define_rota/:nome' + @valor = 42 + + settings.get("/#{params['nome']}") do + # Escopo da requisição para "/#{params['nome']}" + @valor # => nil (não é a mesma requisição) + end + + "Rota definida!" + end +end +``` + +Você tem a ligação ao escopo da requisição dentro dos: + +* blocos get, head, post, put, delete, options, patch, link e unlink +* filtros after e before +* métodos "helper" (auxiliares) +* templates/views + +### Escopo de Delegação + +O escopo de delegação apenas encaminha métodos ao escopo da classe. Entretanto, +ele não se comporta exatamente como o escopo da classe já que você não tem a +ligação da classe. Apenas métodos marcados explicitamente para delegação +estarão disponíveis e você não compartilha variáveis/estado com o escopo da +classe (leia: você tem um `self` diferente). Você pode explicitamente adicionar +delegações de métodos chamando `Sinatra::Delegator.delegate :method_name`. + +Você tem a ligação com o escopo delegado dentro: + +* Da ligação de maior nível, se você digitou `require "sinatra"` +* De um objeto estendido com o mixin `Sinatra::Delegator` + +Dê uma olhada no código você mesmo: aqui está [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) em [estendendo o objeto principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Linha de Comando + +Aplicações Sinatra podem ser executadas diretamente: + +```shell +ruby minhaapp.rb [-h] [-x] [-q] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s MANIPULADOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # ativa o bloqueio mutex (padrão é desligado) +``` + +### Multi-threading + +_Parafraseando [esta resposta no StackOverflow](resposta-so) por Konstantin_ + +Sinatra não impõe nenhum modelo de concorrência, mas deixa isso como +responsabilidade do Rack (servidor) subjacente como o Thin, Puma ou WEBrick. +Sinatra por si só é thread-safe, então não há nenhum problema se um Rack +handler usar um modelo de thread de concorrência. Isso significaria que ao +iniciar o servidor, você teria que especificar o método de invocação correto +para o Rack handler específico. Os seguintes exemplos é uma demonstração de +como iniciar um servidor Thin multi-thread: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + 'Olá mundo' + end +end + +App.run! +``` + +Para iniciar o servidor seria: + +```shell +thin --threaded start +``` + +## Requerimentos + +As seguintes versões do Ruby são oficialmente suportadas: +
    +
    Ruby 2.2
    +
    + 2.2 é totalmente suportada e recomendada. Atualmente não existem planos + para para o suporte oficial para ela. +
    + +
    Rubinius
    +
    + Rubinius é oficialmente suportado (Rubinius >= 2.x). É recomendado rodar + gem install puma. +
    + +
    JRuby
    +
    + A última versão estável lançada do JRuby é oficialmente suportada. Não é + recomendado usar extensões em C com o JRuby. É recomendado rodar + gem install trinidad. +
    +
    + +Versões do Ruby antes da 2.2.2 não são mais suportadas pelo Sinatra 2.0. + +Nós também estamos de olhos em versões futuras do Ruby. + +As seguintes implementações do Ruby não são oficialmente suportadas mas sabemos +que rodam o Sinatra: + +* Versões antigas do JRuby e Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 e 1.9.1 (mas nós não recomendamos o uso dessas) + +Não ser oficialmente suportada significa que se algo quebrar e não estiver nas +plataformas suporta, iremos assumir que não é um problema nosso e sim das +plataformas. + +Nós também rodas nossa IC sobre ruby-head (lançamentos futuros do MRI), mas nós +não podemos garantir nada, já que está em constante mudança. Espera-se que +lançamentos futuros da versão 2.x sejam totalmente suportadas. + +Sinatra deve funcionar em qualquer sistema operacional suportado pela +implementação Ruby escolhida. + +Se você rodar MacRuby, você deve rodar `gem install control_tower`. + +O Sinatra atualmente não roda em Cardinal, SmallRuby, BlueRuby ou qualquer +versão do Ruby anterior ao 2.2. + +## A última versão + +Se você gostaria de utilizar o código da última versão do Sinatra, sinta-se +livre para rodar a aplicação com o ramo master, ele deve ser estável. + +Nós também lançamos pré-lançamentos de gems de tempos em tempos, então você +pode fazer: + +```shell +gem install sinatra --pre +``` + +para obter alguma das últimas funcionalidades. + +### Com Bundler + +Se você quer rodar sua aplicação com a última versão do Sinatra usando +[Bundler](https://bundler.io) é recomendado fazer dessa forma. + +Primeiramente, instale o Bundler, se você ainda não tiver: + +```shell +gem install bundler +``` + +Então, no diretório do seu projeto, crie uma `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# outras dependências +gem 'haml' # por exemplo, se você usar haml +``` + +Perceba que você terá que listar todas suas dependências de aplicação no +`Gemfile`. As dependências diretas do Sinatra (Rack e Tilt) irão, entretanto, +ser automaticamente recuperadas e adicionadas pelo Bundler. + +Então você pode rodar sua aplicação assim: + +```shell +bundle exec ruby myapp.rb +``` + +## Versionando + +O Sinatras segue [Versionamento Semântico](https://semver.org/), tanto SemVer +como SemVerTag. + +## Mais + +* [Website do Projeto](http://www.sinatrarb.com/) - Documentação +adicional, novidades e links para outros recursos. +* [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um +bug? Precisa de ajuda? Tem um patch? +* [Acompanhar Problemas](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Lista de Email](http://groups.google.com/group/sinatrarb/topics) +* [Sinatra & Amigos](https://sinatrarb.slack.com) no Slack +([consiga um convite](https://sinatra-slack.herokuapp.com/)) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) - Livro de "Receitas" +* [Sinatra Recipes](http://recipes.sinatrarb.com/) - "Receitas" de +contribuições da comunidade +* Documentação da API para a +[última release](http://www.rubydoc.info/gems/sinatra) +ou para o [HEAD atual](http://www.rubydoc.info/github/sinatra/sinatra) +no [Ruby Doc](http://www.rubydoc.info/) +* [Servidor de CI](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-pt.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-pt.md new file mode 100644 index 0000000000..27a0789adf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.pt-pt.md @@ -0,0 +1,791 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Sinatra é uma +[DSL](https://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar rapidamente aplicações web em Ruby com o mínimo de esforço: + +```ruby +# minhaapp.rb +require 'rubygems' +require 'sinatra' +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem e execute com: + +```shell +sudo gem install sinatra +ruby minhaapp.rb +``` + +Aceda em: [http://localhost:4567](http://localhost:4567) + +## Rotas + +No Sinatra, uma rota é um metodo HTTP associado a uma URL correspondente +padrão. Cada rota é associada a um bloco: + +```ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. criar algo .. +end + +put '/' do + .. atualizar algo .. +end + +delete '/' do + .. apagar algo .. +end +``` + +Rotas são encontradas na ordem em que são definidas. A primeira rota que +é encontrada invoca o pedido. + +Padrões de rota podem incluir parâmetros nomeados, acessíveis através da +hash `params`: + +```ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Pode também aceder a parâmetros nomeados através do bloco de parâmetros: + +```ruby +get '/ola/:nome' do |n| + "Olá #{n}!" +end +``` + +Padrões de rota podem também incluir parâmetros splat (asteriscos), +acessíveis através do array `params['splat']`. + +```ruby +get '/diga/*/ao/*' do + # corresponde a /diga/ola/ao/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/pasta/do/arquivo.xml + params['splat'] # => ["pasta/do/arquivo", "xml"] +end +``` + +Rotas correspondem-se com expressões regulares: + +```ruby +get /\/ola\/([\w]+)/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com um bloco de parâmetro: + +```ruby +get %r{/ola/([\w]+)} do |c| + "Olá, #{c}!" +end +``` + +Rotas podem incluir uma variedade de condições correspondentes, por +exemplo, o agente usuário: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está a utilizar a versão #{params['agent'][0]} do Songbird." +end + +get '/foo' do + # Corresponde a um navegador não Songbird +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do directório +`./public`. Você pode especificar um local diferente através da opção +`:public_folder` + +```ruby +set :public_folder, __dir__ + '/estatico' +``` + +Note que o nome do directório público não é incluido no URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://example.com/css/style.css`. + +## Views / Templates + +Templates presumem-se estar localizados sob o directório `./views`. Para +utilizar um directório de views diferente: + +```ruby +set :views, __dir__ + '/modelo' +``` + +Uma coisa importante a ser lembrada é que você sempre tem as referências +dos templates como símbolos, mesmo se eles estiverem num sub-directório +(nesse caso utilize `:'subdir/template'`). Métodos de renderização irão +processar qualquer string passada directamente para elas. + +### Haml Templates + +A gem/biblioteca haml é necessária para renderizar templates HAML: + +```ruby +# É necessário requerir 'haml' na aplicação. +require 'haml' + +get '/' do + haml :index +end +``` + +Renderiza `./views/index.haml`. + +[Opções +Haml](http://haml.info/docs/yardoc/file.REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +```ruby +set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml + +get '/' do + haml :index, :haml_options => {:format => :html4 } # substituido +end +``` + +### Erb Templates + +```ruby +# É necessário requerir 'erb' na aplicação. +require 'erb' + +get '/' do + erb :index +end +``` + +Renderiza `./views/index.erb` + +### Erubis + +A gem/biblioteca erubis é necessária para renderizar templates erubis: + +```ruby +# É necessário requerir 'erubis' na aplicação. +require 'erubis' + +get '/' do + erubis :index +end +``` + +Renderiza `./views/index.erubis` + +### Builder Templates + +A gem/biblioteca builder é necessária para renderizar templates builder: + +```ruby +# É necessário requerir 'builder' na aplicação. +require 'builder' + +get '/' do + content_type 'application/xml', :charset => 'utf-8' + builder :index +end +``` + +Renderiza `./views/index.builder`. + +### Sass Templates + +A gem/biblioteca sass é necessária para renderizar templates sass: + +```ruby +# É necessário requerir 'haml' ou 'sass' na aplicação. +require 'sass' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet +end +``` + +Renderiza `./views/stylesheet.sass`. + +[Opções +Sass](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +```ruby +set :sass, {:style => :compact } # o estilo padrão do Sass é :nested + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet, :style => :expanded # substituido +end +``` + +### Less Templates + +A gem/biblioteca less é necessária para renderizar templates Less: + +```ruby +# É necessário requerir 'less' na aplicação. +require 'less' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + less :stylesheet +end +``` + +Renderiza `./views/stylesheet.less`. + +### Templates Inline + +```ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza a string, em uma linha, no template. + +### Acedendo a Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto que os manipuladores de +rota. Variáveis de instância definidas em rotas manipuladas são +directamente acedidas por templates: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito para variáveis locais: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizado quando renderizamos templates parciais +(partials) dentro de outros templates. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte(.rb): + +```ruby +require 'rubygems' +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo!!!!! +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo sinatra. Digite \`enable :inline\_templates\` se tem +templates inline no outro arquivo fonte. + +### Templates nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado sempre que +um template for renderizado. Pode desactivar layouts usando +`:layout => false`. + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +```ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância definidas nos filtros são acedidas através de rotas e +templates: + +```ruby +before do + @nota = 'Olá!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Olá!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do contexto da +requisição e também podem modificar o pedido e a resposta. Variáveis de +instância definidas nos filtros before e rotas são acedidas através dos +filtros after: + +```ruby +after do + puts response.status +end +``` + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +```ruby +before '/protected/*' do + autenticar! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +## Halting + +Para parar imediatamente uma requisição dentro de um filtro ou rota +utilize: + +```ruby +halt +``` + +Pode também especificar o status ao parar… + +```ruby +halt 410 +``` + +Ou com um corpo de texto… + +```ruby +halt 'isto será o corpo de texto' +``` + +Ou também… + +```ruby +halt 401, 'vamos embora!' +``` + +Com cabeçalhos… + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +## Passing + +Dentro de uma rota, pode passar para a próxima rota correspondente +usando `pass`: + +```ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Apanhaste-me!' +end + +get '/adivinhar/*' do + 'Falhaste!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um +404 é retornado. + +## Configuração + +Correndo uma vez, na inicialização, em qualquer ambiente: + +```ruby +configure do + ... +end +``` + +Correndo somente quando o ambiente (`APP_ENV` environment variável) é +definido para `:production`: + +```ruby +configure :production do + ... +end +``` + +Correndo quando o ambiente é definido para `:production` ou `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +## Lidar com Erros + +Lida-se com erros no mesmo contexto das rotas e filtros before, o que +signifca que `haml`, `erb`, etc, estão disponíveis. + +### Não Encontrado + +Quando um `Sinatra::NotFound` exception é levantado, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +```ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado sempre que uma exceção é lançada a +partir de um bloco de rota ou um filtro. O objecto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +```ruby +error do + 'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros personalizados: + +```ruby +error MeuErroPersonalizado do + 'O que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +```ruby +get '/' do + raise MeuErroPersonalizado, 'alguma coisa desagradável' +end +``` + +O resultado será: + +``` +O que aconteceu foi...alguma coisa desagradável +``` + +Alternativamente, pode definir um manipulador de erro para um código de +status: + +```ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um range (alcance): + +```ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra define os manipuladores especiais `not_found` e `error` quando +corre no ambiente de desenvolvimento. + +## Mime Types + +Quando utilizamos `send_file` ou arquivos estáticos pode ter mime types +Sinatra não entendidos. Use `mime_type` para os registar por extensão de +arquivos: + +```ruby +mime_type :foo, 'text/foo' +``` + +Pode também utilizar isto com o helper `content_type`: + +```ruby +content_type :foo +``` + +## Middleware Rack + +O Sinatra corre no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Uma das capacidades mais +interessantes do Rack, para desenvolver aplicações, é o suporte de +“middleware” – componentes que residem entre o servidor e a aplicação, +monitorizando e/ou manipulando o pedido/resposta (request/response) HTTP +para providenciar varios tipos de funcionalidades comuns. + +O Sinatra torna a construção de pipelines do middleware Rack fácil a um +nível superior utilizando o método `use`: + +```ruby +require 'sinatra' +require 'meu_middleware_personalizado' + +use Rack::Lint +use MeuMiddlewarePersonalizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis, bem como blocos: + +```ruby +use Rack::Auth::Basic do |utilizador, senha| + utilizador == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuido com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão.Sinatra +utiliza muitos desses componentes automaticamente dependendo da +configuração, por isso, tipicamente nao é necessário utilizar `use` +explicitamente. + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +```ruby +require 'minha_aplicacao_sinatra' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parametros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Os módulos de classe embutidos `Sinatra::Test` e +`Sinatra::TestHarness` são depreciados na versão 0.9.2. + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação a um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis incovenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: um simples arquivo +de aplicação, directórios `./public` e `./views`, logs, página de detalhes +de excepção, etc.). É onde o Sinatra::Base entra em jogo: + +```ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Olá mundo!' + end +end +``` + +A classe MinhaApp é um componente Rack independente que pode utilizar +como um middleware Rack, uma aplicação Rack, ou metal Rails. Pode +utilizar ou executar esta classe com um arquivo rackup `config.ru`; +ou, controlar um componente de servidor fornecendo como biblioteca: + +```ruby +MinhaApp.run! :host => 'localhost', :port => 9090 +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +- Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; + outra coisa, todos os métodos DSL do Sinatra são importados para o + espaço principal. + +- Coloque as rotas da sua aplicação, manipuladores de erro, filtros e + opções na subclasse de um `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desactivadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. + +SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples +sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial +da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, +`:before`, `:error`, `:not_found`, `:configure`, e `:set` messages enviados +para o alto nível. Dê você mesmo uma vista de olhos ao código: aqui está o +[Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) +sendo [incluido dentro de um espaço +principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) + +## Linha de Comandos + +As aplicações Sinatra podem ser executadas directamente: + +```shell +ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # activa o bloqueio (padrão é desligado) +``` + +## A última versão + +Se gostaria de utilizar o código da última versão do Sinatra, crie um +clone local e execute sua aplicação com o directório `sinatra/lib` no +`LOAD_PATH`: + +```shell +cd minhaapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib minhaapp.rb +``` + +Alternativamente, pode adicionar o directório do `sinatra/lib` no +`LOAD_PATH` do seu aplicativo: + +```ruby +$LOAD_PATH.unshift __dir__ + '/sinatra/lib' +require 'rubygems' +require 'sinatra' + +get '/sobre' do + "Estou correndo a versão" + Sinatra::VERSION +end +``` + +Para actualizar o código do Sinatra no futuro: + +```shell +cd meuprojeto/sinatra +git pull +``` + +## Mais + +- [Website do Projeto](http://www.sinatrarb.com/) - Documentação + adicional, novidades e links para outros recursos. + +- [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um + bug? Precisa de ajuda? Tem um patch? + +- [Acompanhar Questões](https://github.com/sinatra/sinatra/issues) + +- [Twitter](https://twitter.com/sinatra) + +- [Lista de Email](http://groups.google.com/group/sinatrarb/topics) + +- [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em + [freenode.net](http://freenode.net) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ru.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ru.md new file mode 100644 index 0000000000..2aa8aa4f03 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.ru.md @@ -0,0 +1,3207 @@ +# Sinatra + +[![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) + +*Внимание: Этот документ является переводом английской версии и может быть +устаревшим* + +Sinatra — это предметно-ориентированный каркас +([DSL](https://ru.wikipedia.org/wiki/Предметно-ориентированный_язык)) +для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Установите gem: + +```shell +gem install sinatra +``` + +и запустите приложение при помощи: + +```shell +ruby myapp.rb +``` + +Оцените результат: [http://localhost:4567](http://localhost:4567) + +Имейте ввиду, что изменения в коде не будут видны до тех пор, пока вы не перезапустите +сервер. Пожалуйста, перезагружайте сервер каждый раз как вносите изменения или добавьте +в проект [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader). + +Рекомендуется также установить Thin сервер (`gem install thin`), который автоматически +работает с Sinatra приложениями. + +## Содержание + +* [Sinatra](#sinatra) + * [Содержание](#Содержание) + * [Маршруты](#Маршруты) + * [Условия](#Условия) + * [Возвращаемые значения](#Возвращаемые-значения) + * [Собственные детекторы совпадений для маршрутов](#Собственные-детекторы-совпадений-для-маршрутов) + * [Статические файлы](#Статические-файлы) + * [Представления / Шаблоны](#Представления--Шаблоны) + * [Буквальные шаблоны](#Буквальные-шаблоны) + * [Доступные шаблонизаторы](#Доступные-шаблонизаторы) + * [Haml шаблоны](#haml-шаблоны) + * [Erb шаблоны](#erb-шаблоны) + * [Builder шаблоны](#builder-шаблоны) + * [Nokogiri шаблоны](#nokogiri-шаблоны) + * [Sass шаблоны](#sass-шаблоны) + * [SCSS шаблоны](#scss-шаблоны) + * [Less шаблоны](#less-шаблоны) + * [Liquid шаблоны](#liquid-шаблоны) + * [Markdown шаблоны](#markdown-шаблоны) + * [Textile шаблоны](#textile-шаблоны) + * [RDoc шаблоны](#rdoc-шаблоны) + * [AsciiDoc шаблоны](#asciidoc-шаблоны) + * [Radius шаблоны](#radius-шаблоны) + * [Markaby шаблоны](#markaby-шаблоны) + * [RABL шаблоны](#rabl-шаблоны) + * [Slim шаблоны](#slim-шаблоны) + * [Creole шаблоны](#creole-шаблоны) + * [MediaWiki шаблоны](#mediawiki-шаблоны) + * [CoffeeScript шаблоны](#coffeescript-шаблоны) + * [Stylus шаблоны](#stylus-шаблоны) + * [Yajl шаблоны](#yajl-шаблоны) + * [WLang шаблоны](#wlang-шаблоны) + * [Доступ к переменным в шаблонах](#Доступ-к-переменным-в-шаблонах) + * [Шаблоны с `yield` и вложенные лэйауты](#Шаблоны-с-yield-и-вложенные-лэйауты) + * [Включённые шаблоны](#Включённые-шаблоны) + * [Именованные шаблоны](#Именованные-шаблоны) + * [Привязка файловых расширений](#Привязка-файловых-расширений) + * [Добавление собственного движка рендеринга](#Добавление-собственного-движка-рендеринга) + * [Использование пользовательской логики для поиска шаблона](#Использование-пользовательской-логики-для-поиска-шаблона) + * [Фильтры](#Фильтры) + * [Методы-помощники](#Методы-помощники) + * [Использование сессий](#Использование-сессий) + * [Безопасность сессии](#Безопасность-сессии) + * [Конфигурация сессии](#Конфигурация-сессии) + * [Выбор вашей собственной "прослойки" сессии](#Выбор-вашей-собственной-прослойки-сессии) + * [Прерывание](#Прерывание) + * [Передача](#Передача) + * [Вызов другого маршрута](#Вызов-другого-маршрута) + * [Установка тела, статус кода и заголовков ответа](#Установка-тела-статус-кода-и-заголовков-ответа) + * [Потоковые ответы](#Потоковые-ответы) + * [Логирование](#Логирование) + * [Mime-типы](#mime-типы) + * [Генерирование URL](#Генерирование-url) + * [Перенаправление (редирект)](#Перенаправление-редирект) + * [Управление кэшированием](#Управление-кэшированием) + * [Отправка файлов](#Отправка-файлов) + * [Доступ к объекту запроса](#Доступ-к-объекту-запроса) + * [Вложения](#Вложения) + * [Работа со временем и датами](#Работа-со-временем-и-датами) + * [Поиск файлов шаблонов](#Поиск-файлов-шаблонов) + * [Конфигурация](#Конфигурация) + * [Настройка защиты от атак](#Настройка-защиты-от-атак) + * [Доступные настройки](#Доступные-настройки) + * [Режим, окружение](#Режим-окружение) + * [Обработка ошибок](#Обработка-ошибок) + * [Not Found](#not-found) + * [Error](#error) + * [Rack "прослойки"](#rack-прослойки) + * [Тестирование](#Тестирование) + * [Sinatra::Base — "прослойки", библиотеки и модульные приложения](#sinatrabase--прослойки-библиотеки-и-модульные-приложения) + * [Модульные приложения против классических](#Модульные-приложения-против-классических) + * [Запуск модульных приложений](#Запуск-модульных-приложений) + * [Запуск классических приложений с config.ru](#Запуск-классических-приложений-с-configru) + * [Когда использовать config.ru?](#Когда-использовать-configru) + * [Использование Sinatra в качестве "прослойки"](#Использование-sinatra-в-качестве-прослойки) + * [Создание приложений "на лету"](#Создание-приложений-на-лету) + * [Области видимости и привязка](#Области-видимости-и-привязка) + * [Область видимости приложения / класса](#Область-видимости-приложения--класса) + * [Область видимости запроса / экземпляра](#Область-видимости-запроса--экземпляра) + * [Область видимости делегирования](#Область-видимости-делегирования) + * [Командная строка](#Командная-строка) + * [Многопоточность](#Многопоточность) + * [Системные требования](#Системные-требования) + * [Самая свежая версия](#Самая-свежая-версия) + * [При помощи Bundler](#При-помощи-bundler) + * [Версии](#Версии) + * [Дальнейшее чтение](#Дальнейшее-чтение) + +## Маршруты + +В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут +связан с блоком кода: + +```ruby +get '/' do + # .. что-то показать .. +end + +post '/' do + # .. что-то создать .. +end + +put '/' do + # .. что-то заменить .. +end + +patch '/' do + # .. что-то изменить .. +end + +delete '/' do + # .. что-то удалить .. +end + +options '/' do + # .. что-то ответить .. +end + +link '/' do + # .. что-то подключить .. +end + +unlink '/' do + # .. что-то отключить .. +end +``` + +Маршруты сверяются с запросом в порядке очерёдности их записи в файле +приложения. Первый же совпавший с запросом маршрут и будет вызван. + +Маршруты с конечным слэшем отличаются от маршрутов без него: + +```ruby +get '/foo' do + # не соответствует "GET /foo/" +end +``` + +Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше +`params`: + +```ruby +get '/hello/:name' do + # соответствует "GET /hello/foo" и "GET /hello/bar", + # где params['name'] - это 'foo' или 'bar' + "Hello #{params['name']}!" +end +``` + +Также можно получить доступ к именованным параметрам через параметры блока: + +```ruby +get '/hello/:name' do |n| + # соответствует "GET /hello/foo" и "GET /hello/bar", + # где params['name'] - это 'foo' или 'bar' + # n хранит params['name'] + "Hello #{n}!" +end +``` + +Шаблоны маршрутов также могут включать в себя splat параметры (или '*' маску, +обозначающую любой символ), доступные в массиве `params['splat']`: + +```ruby +get '/say/*/to/*' do + # соответствует /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # соответствует /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Или с параметрами блока: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Можно также использовать регулярные выражения в качестве шаблонов маршрутов: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +Или с параметром блока: + +```ruby +# Соответствует "GET /meta/hello/world", "GET /hello/world/1234" и т.д. +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +Шаблоны маршрутов могут иметь необязательные параметры: + +```ruby +get '/posts/:format?' do + # соответствует "GET /posts/", "GET /posts/json", "GET /posts/xml" и т.д. +end +``` + +Маршруты также могут использовать параметры запроса: + +```ruby +get '/posts' do + # соответствует "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # используются переменные title и author; запрос не обязателен для маршрута /posts +end +``` + +**Имеейте ввиду**: если вы не отключите защиту от обратного пути в директориях +(_path traversal_, см. ниже), путь запроса может быть изменён до начала +поиска подходящего маршрута. + +Вы можете настроить Mustermann опции, используемые для данного маршрута, путём передачи в `:mustermann_opts` хэш: + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # в точности соответствует /posts, с явной привязкой + "If you match an anchored pattern clap your hands!" +end +``` + +Это похоже на [условие](#Условия), но это не так! Эти опции будут объеденины в глобальный `:mustermann_opts` хэш, описанный +[ниже](#Доступные-настройки). + +### Условия + +Маршруты могут включать в себя различные условия совпадений, такие как, например, +строка агента пользователя (user agent): + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # соответствует не-songbird браузерам +end +``` + +Другими доступными условиями являются `host_name` и `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` ищет заголовок запроса `Accept`. + +Вы можете с лёгкостью задавать собственные условия: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +Ипользуйте splat-оператор (`*`) для условий, которые принимают несколько аргументов: + +```ruby +set(:auth) do |*roles| # <- обратите внимание на звёздочку + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +### Возвращаемые значения + +Возвращаемое значение блока маршрута ограничивается телом ответа, которое +будет передано HTTP клиенту, или, по крайней мере, следующей "прослойке" (middleware) +в Rack стеке. Чаще всего это строка как в примерах выше. Но также приемлемы и +другие значения. + +Вы можете вернуть любой объект, который будет либо корректным Rack ответом, +либо объектом Rack body, либо кодом состояния HTTP: + +* массив с тремя переменными: `[код (Integer), заголовки (Hash), тело ответа + (должно отвечать на #each)]`; +* массив с двумя переменными: `[код (Integer), тело ответа (должно отвечать + на #each)]`; +* объект, отвечающий на `#each`, который передает только строковые типы + данных в этот блок; +* Integer, представляющий код состояния HTTP. + +Таким образом легко можно реализовать, например, потоковую передачу: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Вы также можете использовать вспомогательный метод `stream` (описанный ниже) +для того, чтобы уменьшить количество шаблонного кода и встроить потоковую +логику прямо в маршрут. + +### Собственные детекторы совпадений для маршрутов + +Как показано выше, Sinatra поставляется со встроенной поддержкой строк и +регулярных выражений в качестве шаблонов URL. Но и это ещё не всё. Вы можете +легко определить свои собственные детекторы совпадений (matchers) для +маршрутов: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Обратите внимание на то, что предыдущий пример, возможно, чересчур усложнён, потому что он +может быть реализован следующим образом: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Или с использованием негативной опережающей проверки (отрицательное look-ahead условие): + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## Статические файлы + +Статические файлы раздаются из `./public` директории. Вы можете указать другое +месторасположение при помощи опции `:public_folder`: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +Учтите, что имя директории со статическими файлами не включено в URL. +Например, файл `./public/css/style.css` будет доступен как +`http://example.com/css/style.css`. + +Используйте опцию `:static_cache_control` (см. ниже) для того, чтобы добавить +заголовок `Cache-Control`. + +## Представления / Шаблоны + +Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту +возвращают строку: + +```ruby +get '/' do + erb :index +end +``` + +Данный код отрендерит файл `views/index.erb`. + +Вместо имени шаблона вы так же можете передавать непосредственно само +содержимое шаблона: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Метод рендеринга шаблона принимает в качестве второго аргумента хэш с опциями: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Данный метод отрендерит шаблон `views/index.erb`, который будет вложен в `views/post.erb` +(по умолчанию: `views/layout.erb`, если файл существует). + +Любые опции, которые Sinatra не распознает, будут переданы в шаблонизатор: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Вы также можете глобально задавать опции для шаблонизаторов: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Опции, переданные в метод, переопределяют опции, заданные при помощи `set`. + +Доступные опции: + +
    +
    locals
    +
    + Список локальных переменных, передаваемых в документ. + Пример: erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    + Кодировка, которую следует использовать в том случае, если не удалось + определить оригинальную. По умолчанию: settings.default_encoding. +
    + +
    views
    +
    + Директория с шаблонами. По умолчанию: settings.views. +
    + +
    layout
    +
    + Определяет необходимость использования лэйаута (true или false). + Если же в качестве значения передан символ, то его значение будет интерпретировано + как наименования файла шаблона лэйаута. + Пример: erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    + Content-Type отображенного шаблона. По умолчанию: задаётся шаблонизатором. +
    + +
    scope
    +
    + Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр + приложения. Если вы измените эту опцию, то переменные экземпляра и + методы-помощники станут недоступными в ваших шаблонах. +
    + +
    layout_engine
    +
    + Шаблонизатор, который следует использовать для отображения лэйаута. + Полезная опция для шаблонизаторов, в которых отсутствует поддержка + лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого + шаблона. Пример: set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + Специальные опции, используемые только для рендеринга лэйаута. Пример: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +По умолчанию в качестве пути для хранения шаблонов принята директория `./views`. +Чтобы назначить другую директорию с шаблонами необходимо изменить настройки: + +```ruby +set :views, settings.root + '/templates' +``` + +Важное замечание: вы всегда должны ссылаться на шаблоны при помощи символов +(Symbol), даже тогда, когда они расположены в поддиректории (в этом случае +используйте конструкции вида `:'subdir/template'`). Вы должны использовать +символы в связи с тем, что в ином случае шаблонизаторы попросту отображают +любые строки, переданные им. + +### Буквальные шаблоны + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Отобразит шаблон, содержимое которого передано строкой. Опционально можно +указать дополнительные опции `:path` и `:line` для того, чтобы улучшить бэктрейс. +Делайте это в том случае, если строка определена в некотором файле, к которому +можно указать путь и номер строки, где расположена исходная строка: + +```ruby +get '/' do + haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 +end +``` + +### Доступные шаблонизаторы + +Некоторые языки шаблонов имеют несколько реализаций. Для того, чтобы указать +конкретную реализацию, которую вы хотите использовать, вам следует просто +подключить нужную библиотеку: + +```ruby +require 'rdiscount' # или require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml шаблоны + + + + + + + + + + + + + + +
    Зависимостиhaml
    Расширения файлов.haml
    Примерhaml :index, :format => :html5
    + +#### Erb шаблоны + + + + + + + + + + + + + + +
    Зависимости + erubis + или erb (включён в Ruby) +
    Расширения файлов.erb, .rhtml or .erubis (только Erubis)
    Примерerb :index
    + +#### Builder шаблоны + + + + + + + + + + + + + + +
    Зависимости + builder +
    Расширения файлов.builder
    Примерbuilder { |xml| xml.em "hi" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### Nokogiri шаблоны + + + + + + + + + + + + + + +
    Зависимостиnokogiri
    Расширения файлов.nokogiri
    Примерnokogiri { |xml| xml.em "hi" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### Sass шаблоны + + + + + + + + + + + + + + +
    Зависимостиsass
    Расширения файлов.sass
    Примерsass :stylesheet, :style => :expanded
    + +#### SCSS шаблоны + + + + + + + + + + + + + + +
    Зависимостиsass
    Расширения файлов.scss
    Примерscss :stylesheet, :style => :expanded
    + +#### Less шаблоны + + + + + + + + + + + + + + +
    Зависимостиless
    Расширения файлов.less
    Примерless :stylesheet
    + +#### Liquid шаблоны + + + + + + + + + + + + + + +
    Зависимостиliquid
    Расширения файлов.liquid
    Примерliquid :index, :locals => { :key => 'value' }
    + +В связи с тем, что в Liquid шаблонах невозможно вызывать методы из Ruby +(за исключением `yield`), вам почти всегда понадобиться передавать в шаблон +локальные переменные. + +#### Markdown шаблоны + + + + + + + + + + + + + + +
    Зависимости + Любая из библиотек: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    Расширения файлов.markdown, .mkd and .md
    Примерmarkdown :index, :layout_engine => :erb
    + +В Markdown невозможно вызывать методы или передавать локальные переменные. +По этой причине вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `markdown` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Вы не можете вызывать Ruby код код из Markdown, соответственно вы не можете +использовать лэйауты на Markdown. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### Textile шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRedCloth
    Расширения файлов.textile
    Примерtextile :index, :layout_engine => :erb
    + +В Textile невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать данный шаблон +совместно с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `textile` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Вы не можете вызывать Ruby код код из Textile, соответственно вы не можете +использовать лэйауты на Textile. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### RDoc шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRDoc
    Расширения файлов.rdoc
    Примерrdoc :README, :layout_engine => :erb
    + +В RDoc невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `rdoc` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Вы не можете вызывать Ruby код код из RDoc, соответственно вы не можете использовать +лэйауты на RDoc. Тем не менее, существует возможность использовать один шаблонизатор +для отображения шаблона, а другой для лэйаута при помощи опции +`:layout_engine`. + +#### AsciiDoc шаблоны + + + + + + + + + + + + + + +
    ЗависимостиAsciidoctor
    Расширения файлов.asciidoc, .adoc и .ad
    Примерasciidoc :README, :layout_engine => :erb
    + +Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Radius шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRadius
    Расширения файлов.radius
    Примерradius :index, :locals => { :key => 'value' }
    + +Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Markaby шаблоны + + + + + + + + + + + + + + +
    ЗависимостиMarkaby
    Расширения файлов.mab
    Примерmarkaby { h1 "Welcome!" }
    + +Шаблонизатор также принимает блоки для включённых шаблонов ([см. пример](#Включённые-шаблоны)). + +#### RABL шаблоны + + + + + + + + + + + + + + +
    ЗависимостиRabl
    Расширения файлов.rabl
    Примерrabl :index
    + +#### Slim шаблоны + + + + + + + + + + + + + + +
    ЗависимостиSlim Lang
    Расширения файлов.slim
    Примерslim :index
    + +#### Creole шаблоны + + + + + + + + + + + + + + +
    ЗависимостиCreole
    Расширения файлов.creole
    Примерcreole :wiki, :layout_engine => :erb
    + +В Creole невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать данный шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +обратите внимание на то, что вы можете вызывать метод `creole` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Вы не можете вызывать Ruby код из Creole, соответственно вы не можете +использовать лэйауты на Creole. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### MediaWiki шаблоны + + + + + + + + + + + + + + +
    ЗависимостиWikiCloth
    Расширения файлов.mediawiki и .mw
    Примерmediawiki :wiki, :layout_engine => :erb
    + +В разметке MediaWiki невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Обратите внимание на то, что вы можете вызывать метод `mediawiki` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Вы не можете вызывать Ruby код из MediaWiki, соответственно вы не можете +использовать лэйауты на MediaWiki. Тем не менее, существует возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи +опции `:layout_engine`. + +#### CoffeeScript шаблоны + + + + + + + + + + + + + + +
    Зависимости + + CoffeeScript + и + + способ запускать JavaScript + +
    Расширения файлов.coffee
    Примерcoffee :index
    + +#### Stylus шаблоны + + + + + + + + + + + + + + +
    Зависимости + + Stylus + и + + способ запускать JavaScript + +
    Расширение файла.styl
    Примерstylus :index
    + +Перед тем, как использовать шаблоны Stylus, необходимо сперва подключить +`stylus` и `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl шаблоны + + + + + + + + + + + + + + +
    Зависимостиyajl-ruby
    Расширения файлов.yajl
    Пример + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +Содержимое шаблона интерпретируется как код на Ruby, а результирующая +переменная json затем конвертируется при помощи `#to_json`. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Опции `:callback` и `:variable` используются для "декорирования" итогового +объекта: + +```ruby +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang шаблоны + + + + + + + + + + + + + + +
    ЗависимостиWLang
    Расширения файлов.wlang
    Примерwlang :index, :locals => { :key => 'value' }
    + +Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за +исключением `yield`), то вы почти всегда будете передавать в шаблон локальные +переменные. Лэйауты также могут быть описаны при помощи WLang. + +### Доступ к переменным в шаблонах + +Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. +Переменные экземпляра, установленные в процессе обработки маршрутов, будут +доступны напрямую в шаблонах: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Вы также можете установить их при помощи хэша локальных переменных: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Это обычный подход, применяемый тогда, когда шаблоны рендерятся как части других шаблонов. + +### Шаблоны с `yield` и вложенные лэйауты + +Лэйаут (layout) обычно представляет собой шаблон, который исполняет +`yield`. Такой шаблон может быть использован либо при помощи опции `:template`, +как описано выше, либо при помощи блока: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +Эти инструкции по сути эквивалентны `erb :index, :layout => :post`. + +Передача блоков интерпретирующим шаблоны методам наиболее полезна для +создания вложенных лэйаутов: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +То же самое может быть сделано в более короткой форме: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +В настоящее время следующие методы шаблонизаторов +принимают блок: `erb`, `haml`, `liquid`, `slim `, `wlang`. Кроме того, +общий метод построения шаблонов `render` также принимает блок. + +### Включённые шаблоны + +Шаблоны также могут быть определены в конце исходного файла: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +Обратите внимание: включённые шаблоны, определённые в исходном файле, который подключил +Sinatra, будут загружены автоматически. Вызовите `enable :inline_templates` +напрямую в том случае, если используете включённые шаблоны в других файлах. + +### Именованные шаблоны + +Шаблоны также могут быть определены при помощи метода `template`: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +Если шаблон с именем "layout" существует, то он будет использоваться каждый +раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае при +помощи опции `:layout => false` или отключить его для всего приложения: `set :haml, +:layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Привязка файловых расширений + +Для того, чтобы связать расширение файла с движком рендеринга, используйте +`Tilt.register`. Так, например, вызовите следующий код в том случае, если вы +хотите использовать расширение `tt` для шаблонов Textile: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Добавление собственного движка рендеринга + +Сначала зарегистрируйте собственный движок в Tilt, а затем создайте метод, отвечающий +за рендеринг: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Данный код отрендерит `./views/index.myat`. +Подробнее о [Tilt](https://github.com/rtomayko/tilt#readme). + +### Использование пользовательской логики для поиска шаблона + +Для того, чтобы реализовать собственный механизм поиска шаблона, +необходимо написать метод `#find_template`: + +```ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Фильтры + +`before`-фильтры выполняются перед каждым запросом в том же контексте, что и +маршруты, и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +`after`-фильтры выполняются после каждого запроса в том же контексте +и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в `before`-фильтрах и маршрутах, будут доступны в +`after`-фильтрах: + +```ruby +after do + puts response.status +end +``` + +Обратите внимание: если вы используете метод `body`, а не просто возвращаете строку из +маршрута, то тело ответа не будет доступно в `after`-фильтрах, так как оно +будет сгенерировано позднее. + +Фильтры также могут использовать шаблоны URL и будут выполнены только в том случае, +если путь запроса совпадет с указанным шаблоном: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Как и маршруты, фильтры могут использовать условия: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Методы-помощники + +Используйте высокоуровневый метод `helpers` для того, чтобы определить +методы-помощники, которые могут быть использованы в обработчиках маршрутов +и шаблонах: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Также методы-помощники могут быть заданы в отдельных модулях: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +Эффект равносилен включению модулей в класс приложения. + +### Использование сессий + +Сессия используется для того, чтобы сохранять состояние между запросами. Если +эта опция включена, то у вас будет один хэш сессии на один пользовательский сеанс: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +### Безопасность сессии + +Для того, чтобы повысить безопасность, данные сессии в файле 'cookie' +подписываются ключом сессии с использованием `HMAC-SHA1`. Этот ключ сессии +должен быть оптимальным криптографическим 'secure random' значением соответствующей +длины, которая для `HMAC-SHA1` больше или равна 64 байтам (512 бит, 128 +шестнадцатеричных символов). Не рекомендуется использовать ключ, длина +которого менее 32 байт (256 бит, 64 шестнадцатеричных символа). Поэтому +**очень важно**, чтобы вы не просто составили значение ключа, а использовали +безопасный генератор случайных чисел для его создания. Люди очень плохо +придумывают случайные значения. + +По умолчанию, Sinatra создаёт для вас безопасный случайный ключ сессии из +32 байт, однако он будет меняться при каждом перезапуске приложения. Если у +вас есть несколько экземпляров вашего приложения, и вы доверили Sinatra +генерацию ключа, то каждый экземпляр будет иметь отличный ключ сессии, +что, вероятно, не совсем то, что вам необходимо. + +Для лучшей безопасности и удобства использования +[рекомендуется](https://12factor.net/config) генерировать случайный безопасный +ключ и хранить его в переменной среды на каждом хосте, на котором запущено +приложение, чтобы все экземпляры вашего приложения использовали один и тот +же ключ. Вы должны периодически менять значение ключа сессии на новое. +Вот несколько примеров того, как вы можете создать 64-байтный ключ +и установить его: + +**Генерация ключа сессии** + +```text +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Генерация ключа сессии (бонусные пункты)** + +Используйте [гем 'sysrandom'](https://github.com/cryptosphere/sysrandom#readme). +Предпочтительнее использовать системные средства RNG для генерации случайных +значений вместо пространства пользователя `OpenSSL`, который в настоящее время +по умолчанию используется в MRI Ruby: + +```text +$ gem install sysrandom +Создание собственных расширений. Это может занять некоторое время... +Успешно установлен sysrandom-1.x +1 gem установлен + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +**Переменная среды для ключа сессии** + +Задайте переменной среды `SESSION_SECRET` значение, которое вы +сгенерировали. Данная переменная автоматически будет использована Sinatra. +Сделайте это значение постоянным при перезагрузке вашего +сервера. Поскольку метод для генерации будет различным в разных системах, +то код ниже приведён только в качестве примера: + +```bash +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +**Конфигурация приложения** + +В целях безопасности настройте конфигурацию вашего приложения таким образом, +чтобы оно генерировало случайный безопасный ключ тогда, когда переменная +среды `SESSION_SECRET` не доступна. + +В качестве бонусных пунктов здесь тоже используйте +[гем 'sysrandom'gem](https://github.com/cryptosphere/sysrandom): + +```ruby +require 'securerandom' +# -или- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### Конфигурация сессии + +Если вы хотите больше настроек для сессий, вы можете задать их, передав хэш +опций в параметр `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах +foo.com, добавьте *.* перед доменом: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### Выбор вашей собственной "прослойки" сессии + +Обратите внимание на то, что при использовании `enable :sessions` все данные +сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, +сохранение больших объёмов данных увеличит ваш трафик). В таком случае вы +можете использовать альтернативную Rack "прослойку" (middleware), реализующую +механизм сессий. Для этого используете один из способов ниже: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +Или установите параметры сессии при помощи хэша опций: + +```ruby +set :sessions, :expire_after => 2592000 +set :session_store, Rack::Session::Pool +``` + +Вы также можете **не вызывать** `enable :sessions`, а вместо этого использовать +необходимую вам Rack прослойку так же, как вы это обычно делаете. + +Очень важно обратить внимание на то, что когда вы используете этот метод, +основной способ защиты сессии **не будет включён по умолчанию**. + +Вам также потребуется добавить следующие Rack middleware для этого: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` + +Смотрите раздел ["Настройка защиты от атак"](#Настройка-защиты-от-атак) для более подробной информации. + +### Прерывание + +Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, +используйте следующую команду: + +```ruby +halt +``` + +Можно также указать статус при прерывании: + +```ruby +halt 410 +``` + +Тело: + +```ruby +halt 'this will be the body' +``` + +И то, и другое: + +```ruby +halt 401, 'go away!' +``` + +Можно указать заголовки: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +И, конечно, можно использовать шаблоны с `halt`: + +```ruby +halt erb(:error) +``` + +### Передача + +Маршрут может передать обработку запроса следующему совпадающему маршруту +используя метод `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +Блок маршрута сразу же прерывается, а контроль переходит к следующему +совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на +запрос будет 404. + +### Вызов другого маршрута + +Иногда `pass` не подходит, например, если вы хотите получить результат вызова +другого обработчика маршрута. В таком случае просто используйте `call`: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Обратите внимание на то, что в предыдущем примере можно облегчить тестирование и +повысить производительность, перенеся `"bar"` в метод-помощник, используемый и в +`/foo`, и в `/bar`. + +Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, +а не в его копию, используйте `call!` вместо `call`. + +Если хотите узнать больше о `call`, смотрите спецификацию Rack. + +### Установка тела, статус кода и заголовков ответа + +Хорошим тоном является установка кода состояния HTTP и тела ответа в +возвращаемом значении обработчика маршрута. Тем не менее, в некоторых +ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке +потока исполнения. Вы можете сделать это при помощи метода-помощника `body`. +Если вы задействуете метод `body`, то вы можете использовать его и в +дальнейшем, чтобы получить доступ к телу ответа: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Также можно передать блок в метод `body`, который затем будет вызван +обработчиком Rack (такой подход может быть использован для реализации +потокового ответа, см. ["Возвращаемые значения"](#Возвращаемые-значения)). + +Аналогично вы можете установить код ответа и его заголовки: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Как и `body`, методы `headers` и `status`, вызванные без аргументов, +возвращают свои текущие значения. + +### Потоковые ответы + +Иногда требуется начать отправлять данные клиенту прямо в процессе +генерирования частей этих данных. В особых случаях требуется постоянно +отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете +использовать метод `stream` вместо разработки собственных "обёрток". + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +Это позволяет вам реализовать стриминговые API, +[Server Sent Events](https://w3c.github.io/eventsource/), +и может служить основой для [WebSockets](https://en.wikipedia.org/wiki/WebSocket). +Также такой подход можно использовать для увеличения производительности в том случае, +когда какая-то часть контента (а не весь) зависит от медленного ресурса. + +Обратите внимание на то, что возможности стриминга, особенно количество одновременно +обслуживаемых запросов, очень сильно зависят от используемого веб-сервера. +Некоторые серверы могут и вовсе не поддерживать стриминг. Если сервер не +поддерживает стриминг, то все данные будут отправлены за один раз сразу после +того, как блок, переданный в `stream`, завершится. Стриминг вообще не работает +при использовании Shotgun. + +Если метод используется с параметром `keep_open`, то он не будет вызывать +`close` у объекта потока, что позволит вам закрыть его позже в любом другом +месте. Это работает только с событийными серверами, например, с Thin и +Rainbows. Другие же серверы всё равно будут закрывать поток: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # регистрация клиента в событиях сервера + stream(:keep_open) do |out| + connections << out + # удаление "мёртвых клиентов" + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # уведомить клиента о новом сообщении + out << params['message'] << "\n" + + # указать клиенту на необходимость снова соединиться + out.close + end + + # допуск + "message received" +end +``` + +Также клиент может закрыть соединение при попытке записи в сокет. В связи с +этим рекомендуется выполнить проверку `out.closed?` прежде, чем пытаться произвести запись. + +### Логирование + +В области видимости запроса метод `logger` предоставляет доступ к экземпляру +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Этот логер автоматически учитывает ваши настройки логирования в Rack. Если +логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы +можете смело использовать его в маршрутах и фильтрах. + +Обратите внимание на то, что логирование включено по умолчанию только для +`Sinatra::Application`. Если ваше приложение является подклассом `Sinatra::Base`, то +вы, скорее всего, захотите включить его вручную: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Чтобы избежать использования любой логирующей "прослойки", задайте опции +`logging` значение `nil`. При этом не забывайте, что в такой ситуации +`logger` будет возвращать `nil`. Чаще всего так делают, когда задают свой собственный +логер. Sinatra будет использовать то, что находится в `env['rack.logger']`. + +### Mime-типы + +Когда вы используете `send_file` или статические файлы, у вас могут быть +mime-типы, которые Sinatra не понимает по умолчанию. Используйте `mime_type` +для их регистрации по расширению файла: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Вы также можете использовать это в методе-помощнике `content_type`: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Генерирование URL + +Чтобы сформировать URL, вам следует использовать метод `url`, например, в Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они +присутствуют. + +Наряду с `url` вы можете использовать `to` (смотрите пример ниже). + +### Перенаправление (редирект) + +Вы можете перенаправить браузер пользователя при помощи метода `redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Любые дополнительные параметры используются по аналогии с аргументами метода +`halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +Вы также можете перенаправить пользователя обратно на страницу, с которой он +пришёл, при помощи `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Для того, чтобы передать какие-либо параметры вместе с перенаправлением, +добавьте их в строку запроса: + +```ruby +redirect to('/bar?sum=42') +``` + +либо используйте сессию: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Управление кэшированием + +Установка корректных заголовков — основа правильного HTTP кэширования. + +Вы можете легко выставить заголовок Cache-Control следующим образом: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Совет: задавайте кэширование в `before`-фильтре: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Если вы используете метод `expires` для задания соответствующего заголовка, то +`Cache-Control` будет выставлен автоматически: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Чтобы использовать кэширование правильно, вам стоит подумать о +применении `etag` или `last_modified`. Рекомендуется использовать эти +методы-помощники *до* выполнения ресурсоёмких вычислений, так как они +немедленно отправят ответ клиенту в том случае, если текущая версия +уже присутствует в их кэше: + +```ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Также вы можете использовать +[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +Эти методы-помощники не станут ничего кэшировать, однако они дадут +необходимую информацию для вашего кэша. Если вы ищете лёгкое решение для +кэширования, попробуйте [rack-cache](https://github.com/rtomayko/rack-cache#readme): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок +`Cache-Control` к статическим файлам. + +В соответствии с RFC 2616 ваше приложение должно вести себя по-разному, когда +заголовки If-Match или If-None-Match имеют значение `*`, в зависимости от +того, существует или нет запрашиваемый ресурс. Sinatra предполагает, что +ресурсы, к которым обращаются при помощи безопасных (GET) и идемпотентных (PUT) +методов, уже существуют, а остальные ресурсы (к которым обращаются, например, +при помощи POST) считает новыми. Вы можете изменить данное поведение при помощи +опции `:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Если вы хотите использовать weak ETag, задайте опцию `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Отправка файлов + +Для отправки файлов пользователю вы можете использовать метод `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Этот метод имеет несколько опций: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Возможные опции: + +
    +
    filename
    +
    имя файла, по умолчанию: реальное имя файла.
    + +
    last_modified
    +
    значение для заголовка Last-Modified, по умолчанию: mtime (время + изменения) файла.
    + +
    type
    +
    тип файла, по умолчанию: определяется по расширению файла.
    + +
    disposition
    +
    + используется для заголовка Content-Disposition, возможные значения: nil + (по умолчанию), :attachment и :inline +
    + +
    length
    +
    значения для заголовка Content-Length, по умолчанию: размер файла.
    + +
    status
    +
    + Код ответа. Полезно в том случае, когда отсылается статический файл в качестве + страницы с сообщением об ошибке. Если поддерживается обработчик Rack, будут использоваться + другие средства, кроме потоковой передачи из процесса Ruby. Если вы используете + этот вспомогательный метод, Sinatra автоматически обрабатывает запросы диапазона. +
    +
    + +### Доступ к объекту запроса + +Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, +маршрутах, обработчиках ошибок) при помощи `request` метода: + +```ruby +# приложение запущено на http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # тело запроса, посланное клиентом (см. ниже) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # длина тела запроса + request.media_type # медиатип тела запроса + request.host # "example.com" + request.get? # true (есть аналоги для других методов HTTP) + request.form_data? # false + request["some_param"] # значение параметра some_param. Шорткат для хэша params + request.referrer # источник запроса клиента либо '/' + request.user_agent # user agent (используется для :agent условия) + request.cookies # хэш, содержащий cookies браузера + request.xhr? # является ли запрос ajax запросом? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-адрес клиента + request.secure? # false (true, если запрос сделан через SSL) + request.forwarded? # true (если сервер работает за обратным прокси) + request.env # "сырой" env хэш, полученный Rack +end +``` + +Некоторые опции, такие как `script_name` или `path_info`, доступны для +модификации: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body` является IO или StringIO объектом: + +```ruby +post "/api" do + request.body.rewind # в случае, если кто-то уже прочитал тело запроса + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Вложения + +Вы можете использовать метод `attachment`, чтобы сообщить браузеру о том, +что ответ сервера должен быть сохранён на диск, а не отображён: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +Вы также можете указать имя файла: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Работа со временем и датами + +Sinatra предлагает метод-помощник `time_for`, который из заданного значения +создает объект Time. Он также может конвертировать `DateTime`, `Date` и +схожие классы: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2016') + "still time" +end +``` + +Этот метод используется внутри Sinatra методами `expires`, `last_modified` и +им подобными. Поэтому вы легко можете изменить и дополнить поведение этих методов, +переопределив `time_for` в своём приложении: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Поиск файлов шаблонов + +Для поиска шаблонов и их последующего рендеринга используется метод +`find_template`: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +Это не слишком полезный пример. Зато полезен тот факт, что вы можете +переопределить этот метод, чтобы использовать свой собственный механизм +поиска. Например, если вы хотите, чтобы можно было использовать несколько +директорий с шаблонами: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Другой пример, в котором используются разные директории для движков +рендеринга: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Вы можете легко вынести этот код в расширение и поделиться им с остальными! + +Обратите внимание на тот факт, что `find_template` не проверяет, существует ли +файл на самом деле, а вызывает заданный блок для всех возможных путей. Дело тут не в +производительности, а в том, что `render` вызовет `break` как только файл +будет найден. Содержимое и местонахождение шаблонов будет закэшировано в том случае, +если приложение запущено не в режиме разработки (`set :environment, :development`). +Вы должны помнить об этих нюансах, если пишите по-настоящему "сумасшедший" +метод. + +## Конфигурация + +Этот блок исполняется один раз при старте в любом окружении (environment): + +```ruby +configure do + # задание одной опции + set :option, 'value' + + # устанавливаем несколько опций + set :a => 1, :b => 2 + + # то же самое, что и `set :option, true` + enable :option + + # то же самое, что и `set :option, false` + disable :option + + # у вас могут быть "динамические" опции с блоками + set(:css_dir) { File.join(views, 'css') } +end +``` + +Следующий пример будет выполнен только тогда, когда окружение +(переменная `APP_ENV`) будет задана как `:production`: + +```ruby +configure :production do + ... +end +``` + +Следующий код будет выполнен в том случае, когда окружение +будет задано как `:production` или `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Вы можете получить доступ к этим опциям при помощи метода `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Настройка защиты от атак + +Sinatra использует +[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) для защиты +приложения от простых атак. Вы можете легко выключить эту защиту (что сделает +ваше приложение чрезвычайно уязвимым к большому числу различных уязвимостей): + +```ruby +disable :protection +``` + +Чтобы отключить какой-либо конкретный уровень защиты, передайте хэш опций +в параметр `protection`: + +```ruby +set :protection, :except => :path_traversal +``` + +Вы также можете отключить сразу несколько уровней защиты: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +По умолчанию Sinatra будет устанавливать `session based` защиту только если +включена опция `:sessions`. См. ["Использование сессий""](#Использование-сессий). +Иногда вы захотите настроить сессии "вне" приложения Sinatra, например, в +config.ru или при помощи отдельного экземпляра `Rack::Builder`. В таком случае +вы также можете настроить `session based` защиту, передав опцию `:session`: + +```ruby +set :protection, :session => true +``` + +### Доступные настройки + +
    +
    absolute_redirects
    +
    + если отключено, то Sinatra будет позволять использование относительных + перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP + 1.1), который разрешает только абсолютные перенаправления. +
    +
    + включайте эту опцию, если ваше приложение работает за обратным прокси, + который настроен не совсем корректно. Обратите внимание на тот факт, что + метод url всё равно будет генерировать абсолютные URL в том случае, + если вы не передадите false вторым аргументом. +
    +
    отключено по умолчанию.
    + +
    add_charset
    +
    + mime-типы, к которым метод content_type будет автоматически добавлять + информацию о кодировке. Вам следует добавлять значения к этой опции + вместо её переопределения: settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + путь к главному файлу приложения, используется для нахождения корневой + директории проекта, директорий с шаблонами и статическими файлами, + вложенных шаблонов. +
    + +
    bind
    +
    + используемый IP-адрес (по умолчанию: 0.0.0.0 или + localhost если опция `environment` настроена на "development"). + Используется только встроенным сервером. +
    + +
    default_encoding
    +
    кодировка, если неизвестна (по умолчанию: "utf-8").
    + +
    dump_errors
    +
    отображать ошибки в логе.
    + +
    environment
    +
    + текущее окружение, по умолчанию, значение ENV['APP_ENV'] или + "development", если ENV['APP_ENV'] недоступна. +
    + +
    logging
    +
    использовать логер.
    + +
    lock
    +
    + создаёт блокировку для каждого запроса, которая гарантирует обработку + только одного запроса в текущий момент времени в Ruby процессе. +
    +
    + включайте в том случае, если ваше приложение не потокобезопасно (thread-safe). + Отключено по умолчанию. +
    + +
    method_override
    +
    + использовать "магический" параметр _method для поддержки + PUT/DELETE форм в браузерах, которые не поддерживают данные методы. +
    + +
    mustermann_opts
    +
    + хэш настроек по умолчанию для перехода к Mustermann.new при компиляции + маршрутов маршрутизации. +
    + +
    port
    +
    + порт, на котором будет работать сервер. + Используется только встроенным сервером. +
    + +
    prefixed_redirects
    +
    + добавлять или нет параметр request.script_name к редиректам, если не + задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя + как redirect to('/foo'). Отключено по умолчанию. +
    + +
    protection
    +
    включена или нет защита от атак. Смотрите секцию защиты от атак выше.
    + +
    public_dir
    +
    алиас для public_folder.
    + +
    public_folder
    +
    + путь к общедоступной директории, откуда будут раздаваться файлы. + Используется, только если включена раздача статических файлов + (см. опцию static ниже). Берётся из настройки app_file, + если не установлена. +
    + +
    quiet
    +
    + отключает журналы, созданные командами запуска и остановки Sinatra. + false по умолчанию. +
    + +
    reload_templates
    +
    + перезагружать или нет шаблоны на каждый запрос. Включено в режиме + разработки. +
    + +
    root
    +
    + путь к корневой директории проекта. Берётся из настройки app_file, + если не установлен. +
    + +
    raise_errors
    +
    + выбрасывать исключения (будет останавливать приложение). + По умолчанию включено только в окружении "test". +
    + +
    run
    +
    + если включено, Sinatra будет самостоятельно запускать веб-сервер. Не + включайте, если используете rackup или аналогичные средства. +
    + +
    running
    +
    работает ли сейчас встроенный сервер? Не меняйте эту опцию!
    + +
    server
    +
    + сервер или список серверов, которые следует использовать в качестве + встроенного сервера. Порядок задаёт приоритет, по умолчанию зависит + от реализации Ruby. +
    + +
    server_settings
    +
    + Если вы используете в качестве сервера WEBrick, например для работы в + режиме разработки, то вы можете передать набор опций для server_settings, + таких как SSLEnable или SSLVerifyClient. Тем не менее, такие + серверы как Puma или Thin не поддерживают эти параметры, поэтому вы можете + устанавливать server_settings указав его как метод при вызове + configure. +
    + +
    sessions
    +
    + включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. + Смотрите раздел "Использование сессий" выше. +
    + +
    session_store
    +
    + используемая Rack "прослойка" для сессии. По умолчанию Rack::Session::Cookie. + Смотрите раздел "Использование сессий" выше. +
    + +
    show_exceptions
    +
    + показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию + включено только в окружении "development". +
    +
    + может также быть установлено в :after_handler для запуска специфичной + для приложения обработки ошибок, перед показом трассировки стека в браузере. +
    + +
    static
    +
    указывает, должна ли Sinatra осуществлять раздачу статических файлов.
    +
    Отключите, если используете какой-либо веб-сервер для этой цели.
    +
    Отключение значительно улучшит производительность приложения.
    +
    По умолчанию включено в классических и отключено в модульных приложениях.
    + +
    static_cache_control
    +
    + когда Sinatra раздаёт статические файлы, используйте эту опцию для того, + чтобы добавить им заголовок Cache-Control. Для этого используется + метод-помощник cache_control. По умолчанию отключено. +
    +
    + используйте массив, когда надо задать несколько значений: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + если включено, то Thin будет использовать EventMachine.defer для + обработки запросов. +
    + +
    traps
    +
    указывает, должна ли Sinatra обрабатывать системные сигналы.
    + +
    views
    +
    + путь к директории с шаблонами. Берётся из настройки app_file в том + случае, если не установлен. +
    + +
    x_cascade
    +
    + Указывает, необходимо ли устанавливать заголовок X-Cascade если никакие маршруты не совпадают. + true по умолчанию. +
    +
    + +## Режим, окружение + +Есть 3 предопределённых режима работы приложения (окружения): `"development"`, +`"production"` и `"test"`. Режим может быть задан через переменную окружения `APP_ENV`. +Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны +перезагружаются между запросами, а также задаются специальные обработчики +`not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях +`"production"` и `"test"` шаблоны по умолчанию кэшируются. + +Для запуска приложения в определённом окружении установите переменную +окружения `APP_ENV`: + +```shell +APP_ENV=production ruby my_app.rb +``` + +Вы можете использовать предопределённые методы `development?`, `test?` и +`production?`, чтобы определить текущее окружение. + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Обработка ошибок + +Обработчики ошибок исполняются в том же контексте, что и маршруты и +`before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`, +`halt` и т.д. доступны и им. + +### Not Found + +В случае возникновения исключения `Sinatra::NotFound` или возврата кода ответа 404 +будет вызван обработчик `not_found`: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +Обработчик ошибок `error` будет вызван тогда, когда исключение выброшено из блока +маршрута либо из фильтра. Тем не менее, обратите внимание на то, что в режиме разработки +он будет запускаться только в том случае, если вы установите опцию "show exceptions" +на `: after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +Объект-исключение доступен как переменная `sinatra.error` в Rack: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Пользовательские ошибки: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Тогда, если это возникнет: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +То вы получите: + +``` +So what happened was... something bad +``` + +Также вы можете установить обработчик ошибок для кода состояния HTTP: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Либо набора кодов: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra устанавливает специальные обработчики `not_found` и `error`, когда +приложение запущено в режиме разработки (окружение `:development`) чтобы +отображать информативные трассировки стека и дополнительную информацию об отладке +в вашем браузере. + +## Rack "прослойки" + +Sinatra использует [Rack](https://rack.github.io/) - минимальный стандартный +интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для +разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — +компонентов, находящихся "между" сервером и вашим приложением, которые +отслеживают и/или манипулируют HTTP запросами/ответами для предоставления +различной функциональности. + +Sinatra позволяет очень просто создавать пайплайны из подобных Rack "прослоек" +при помощи метода `use`: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +Семантика `use` идентична той, что определена для +[Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(чаще всего используется в rackup файлах). Так, например, метод `use` принимает как +множественные переменные, так и блоки: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack распространяется с различными стандартными "прослойками" для логирования, +отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra +использует многие из этих компонентов автоматически, основываясь на +конфигурации, чтобы вам не приходилось подключать их при помощи `use` вручную. + +Вы можете найти полезные прослойки в +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +или в [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Тестирование + +Тесты для Sinatra приложений могут быть написаны при помощи любых библиотек или +фреймворков, поддерживающих тестирование Rack. Разработчики гема Sinatra рекомендуют +использовать [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_user_agent + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Примечание: если вы используете Sinatra в модульном стиле, замените +`Sinatra::Application` в примере выше на имя класса вашего приложения. + +## Sinatra::Base — "прослойки", библиотеки и модульные приложения + +Описание вашего приложения на верхнем уровне хорошо работает для микроприложений, +но имеет значительные недостатки при создании многоразовых компонентов, таких как +Rack "прослойка", Rails metal, простые библиотеки с серверным компонентом или +даже расширения Sinatra. Верхний уровень предполагает конфигурацию стиля +микроприложений (например, одиночный файл приложения, `./public` и `./views`, +каталоги, логирование, страницу подробных сведений об исключениях и т.д.). +И тут на помощь приходит `Sinatra::Base`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +Методы, доступные подклассам `Sinatra::Base` идентичны тем, что доступны +приложениям при помощи DSL верхнего уровня. Большинство таких приложений могут быть +конвертированы в `Sinatra::Base` компоненты при помощи двух модификаций: + +* Вы должны подключать `sinatra/base` вместо `sinatra`, иначе все DSL методы, + предоставляемые Sinatra, будут импортированы в глобальное пространство + имён. +* Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс + `Sinatra::Base`. + +`Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный +сервер, по умолчанию отключены. Смотрите +[Опции и конфигурация](http://www.sinatrarb.com/configuration.html) +для детальной информации об опциях и их поведении. Если вы хотите, чтобы +поведение было более похоже на то, когда вы определяете своё приложение +на верхнем уровне (также известно как классический стиль), вы можете +унаследоваться от `Sinatra::Application`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Модульные приложения против классических + +Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего +плохого. Если этот стиль подходит вашему приложению, вы не обязаны +переписывать его в модульное приложение. + +Основным недостатком классического стиля является тот факт, что у вас может +быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете +использовать больше, переключайтесь на модульный стиль. Вы можете смело +смешивать модульный и классический стили. + +Переходя с одного стиля на другой, примите во внимание следующие изменения в +настройках: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ОпцияКлассическийМодульныйМодульный
    app_fileфайл с приложениемфайл с подклассом Sinatra::Baseфайл с подклассом Sinatra::Application
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### Запуск модульных приложений + +Есть два общепринятых способа запускать модульные приложения: запуск напрямую +при помощи `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... здесь код приложения ... + + # запускаем сервер, если исполняется текущий файл + run! if app_file == $0 +end +``` + +Затем: + +```shell +ruby my_app.rb +``` + +Или при помощи конфигурационного файла `config.ru`, который позволяет +использовать любой Rack-совместимый сервер приложений: + +```ruby +# config.ru (запускается при помощи Rackup) +require './my_app' +run MyApp +``` + +Запускаем: + +```shell +rackup -p 4567 +``` + +### Запуск классических приложений с config.ru + +Файл приложения: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +И соответствующий `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### Когда использовать config.ru? + +Использование файла `config.ru` рекомендовано если: + +* вы хотите разворачивать своё приложение на различных Rack-совместимых + серверах (Passenger, Unicorn, Heroku, ...); +* вы хотите использовать более одного подкласса `Sinatra::Base`; +* вы хотите использовать Sinatra только в качестве "прослойки" Rack. + +**Совсем необязательно переходить на использование `config.ru` лишь потому, +что вы стали использовать модульный стиль приложения. И необязательно +использовать модульный стиль, чтобы запускать приложение при помощи +`config.ru`.** + +### Использование Sinatra в качестве "прослойки" + +Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra +приложение само может быть добавлено к любому Rack endpoint в качестве +"прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra +приложение или любое другое приложение, основанное на Rack (Rails/Hamami/Roda/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # "прослойка" будет запущена перед фильтрами + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Создание приложений "на лету" + +Иногда требуется создавать Sinatra приложения "на лету" (например, из другого +приложения), не сохраняя их в константу. Это возможно при помощи `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +Этот метод может принимать аргументом приложение, от которого следует +наследоваться: + +```ruby +# config.ru (запускается при помощи Rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Это особенно полезно для тестирования расширений Sinatra и при использовании +Sinatra внутри вашей библиотеки. + +Это также значительно упрощает использование Sinatra в качестве прослойки: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Области видимости и привязка + +Текущая область видимости определяет методы и переменные, доступные в данный +момент. + +### Область видимости приложения / класса + +Любое Sinatra приложение соответствует подклассу `Sinatra::Base`. Если вы +используете DSL верхнего уровня (`require 'sinatra'`), то этим классом будет +`Sinatra::Application`, иначе это будет подкласс, который вы создали вручную. +На уровне класса вам будут доступны такие методы, как `get` или `before`, но +вы не сможете получить доступ к объектам `request` или `session`, так как +существует только один класс приложения для всех запросов. + +Опции, созданные при помощи `set`, являются методами уровня класса: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Я больше не в области видимости приложения! + end +end +``` + +У вас будет привязка к области видимости приложения внутри: + +* тела вашего класса приложения; +* методов, определённых расширениями; +* блока, переданного в `helpers`; +* блоков, использованных как значения для `set`; +* блока, переданного в `Sinatra.new`. + +Вы можете получить доступ к объекту области видимости (классу приложения) +следующими способами: + +* через объект, переданный блокам конфигурации (`configure { |c| ... }`); +* `settings` внутри области видимости запроса. + +### Область видимости запроса / экземпляра + +Для каждого входящего запроса будет создан новый экземпляр вашего приложения, +и все блоки обработчика будут запущены в этом контексте. В этой области +видимости вам доступны `request` и `session` объекты, а также вызовы методов +рендеринга, такие как `erb` или `haml`. Вы можете получить доступ к области +видимости приложения из контекста запроса используя метод-помощник +`settings`: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + get '/define_route/:name' do + # Область видимости запроса '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Область видимости запроса "/#{params['name']}" + @value # => nil (другой запрос) + end + + "Route defined!" + end +end +``` + +У вас будет привязка к области видимости запроса в: + +* get, head, post, put, delete, options, patch, link и unlink блоках; +* before и after фильтрах; +* методах-помощниках; +* шаблонах/отображениях. + +### Область видимости делегирования + +Область видимости делегирования просто перенаправляет методы в область +видимости класса. Однако, она не полностью ведет себя как область видимости +класса, так как у вас нет привязки к классу. Только методы, явно помеченные +для делегирования, будут доступны, а переменных/состояний области видимости +класса не будет (иначе говоря, у вас будет другой `self` объект). Вы можете +непосредственно добавить методы делегирования, используя +`Sinatra::Delegator.delegate :method_name`. + +У вас будет контекст делегирования внутри: + +* привязки верхнего уровня, если вы сделали `require "sinatra"`; +* объекта, расширенного при помощи `Sinatra::Delegator`. + +Посмотрите сами в код: вот +[примесь Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +[расширяет главный объект](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Командная строка + +Sinatra приложения могут быть запущены напрямую: + +```shell +ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Поддерживаемые опции: + +``` +-h # раздел помощи +-p # указание порта (по умолчанию 4567) +-o # указание хоста (по умолчанию 0.0.0.0) +-e # указание окружения, режима (по умолчанию development) +-s # указание rack сервера/обработчика (по умолчанию thin) +-q # включить тихий режим для сервера (по умолчанию выключен) +-x # включить мьютекс-блокировку (по умолчанию выключена) +``` + +### Многопоточность + +_Данный раздел является перефразированным [ответом пользователя Konstantin](https://stackoverflow.com/a/6282999/5245129) на StackOverflow_ + +Sinatra не навязывает каких-либо моделей параллелизма, но для этих целей можно +использовать любой Rack обработчик (сервер), например Thin, Puma или WEBrick. Сама +по себе Sinatra потокобезопасна, поэтому нет никаких проблем в использовании +поточной модели параллелизма в Rack обработчике. Это означает, что когда +запускается сервер, вы должны указать правильный метод вызова для конкретного +Rack обработчика. Пример ниже показывает, как можно запустить мультипоточный +Thin сервер: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +Для запуска сервере выполните следующую команду: + +```shell +thin --threaded start +``` + +## Системные требования + +Следующие версии Ruby официально поддерживаются: +
    +
    Ruby 2.3
    +
    + Версия 2.3 полностью поддерживается и рекомендуется. В настоящее время нет + планов отказаться от официальной поддержки. +
    + +
    Rubinius
    +
    + Rubinius официально поддерживается (Rubinius >= 2.x). Рекомендуется + выполнить gem install puma. +
    + +
    JRuby
    +
    + Официально поддерживается последний стабильный релиз JRuby. Не + рекомендуется использовать расширений на C в JRuby. Рекомендуется + выполнить gem install trinidad. +
    +
    + +Версии Ruby ниже 2.2.2 более не поддерживаются в Sinatra 2.0. + +Мы также следим за предстоящими к выходу версиями Ruby. + +Следующие реализации Ruby не поддерживаются официально, однако известно, что +на них запускается Sinatra: + +* старые версии JRuby и Rubinius; +* Ruby Enterprise Edition; +* MacRuby, Maglev, IronRuby; +* Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию). + +То, что версия официально не поддерживается, означает, что, если что-то не +работает на этой версии, а на поддерживаемой работает — это не наша проблема, +а их. + +Мы также запускаем наши CI-тесты на ruby-head (будущие версии MRI), но мы не +можем ничего гарантировать, так как ведётся постоянная разработка. +Предполагается, что предстоящие релизы 2.x будут полностью поддерживаться. + +Sinatra должна работать на любой операционной системе, в которой есть одна из +указанных выше версий Ruby. + +Если вы запускаете MacRuby, вы должны выполнить `gem install control_tower`. + +Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой +версии Ruby ниже 2.2. + +## Самая свежая версия + +Если вы хотите использовать самый последний релиз Sinatra, не бойтесь запускать +своё приложение вместе с кодом из master ветки Sinatra, так как она весьма стабильна. + +Мы также время от времени выпускаем предварительные версии, которые вы можете +установить следующим образом: + +```shell +gem install sinatra --pre +``` + +таким образом вы сможете воспользоваться некоторыми самыми последними возможностями. + +### При помощи Bundler + +Если вы хотите запускать своё приложение с последней версией Sinatra, то +рекомендуем использовать [Bundler](http://bundler.io). + +Для начала установите Bundler, если у вас его ещё нет: + +```shell +gem install bundler +``` + +Затем создайте файл `Gemfile` в директории вашего проекта: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => 'sinatra/sinatra' + +# другие зависимости +gem 'haml' # в том случае, если используете haml +``` + +Имейте ввиду, что вам необходимо будет указывать все зависимости вашего приложения +в `Gemfile`. Необходимые зависимости самой библиотеки Sinatra (Rack и Tilt) +будут автоматически скачаны и добавлены Bundler. + +Теперь вы можете запускать своё приложение следующим образом: + +```shell +bundle exec ruby myapp.rb +``` + +## Версии + +Sinatra использует [Semantic Versioning](https://semver.org/): как SemVer, так и +SemVerTag. + +## Дальнейшее чтение + +* [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная + документация, новости и ссылки на другие ресурсы. +* [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили + баг? Нужна помощь? Написали патч? +* [Отслеживание проблем/ошибок](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [Группы рассылки](https://groups.google.com/forum/#!forum/sinatrarb) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) на [Freenode](https://freenode.net) +* [Sinatra и Друзья](https://sinatrarb.slack.com) на Slack, а так же + [ссылка для инвайта](https://sinatra-slack.herokuapp.com/). +* [Sinatra Book](https://github.com/sinatra/sinatra-book) учебник и сборник рецептов +* [Sinatra Recipes](http://recipes.sinatrarb.com/) сборник рецептов +* API документация к [последнему релизу](http://www.rubydoc.info/gems/sinatra) + или [текущему HEAD](http://www.rubydoc.info/github/sinatra/sinatra) на + http://www.rubydoc.info/ +* [Сервер непрерывной интеграции](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.zh.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.zh.md new file mode 100644 index 0000000000..483ab25690 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/README.zh.md @@ -0,0 +1,2934 @@ +# Sinatra + +*注:本文档是英文版的翻译,内容更新有可能不及时。如有不一致的地方,请以英文版为准。* + +Sinatra 是一门基于 +Ruby 的[领域专属语言](https://en.wikipedia.org/wiki/Domain-specific_language),致力于轻松、快速地创建网络应用: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +安装 Sinatra 这个 gem: + +```shell +gem install sinatra +``` + +然后运行 myapp.rb 中的代码: + +```shell +ruby myapp.rb +``` + +在该地址查看: [http://localhost:4567](http://localhost:4567) + +推荐运行 `gem install thin` 安装 Thin。这样,Sinatra 会优先选择 Thin 作为服务器。 + +## 目录 + +* [Sinatra](#sinatra) + * [目录](#目录) + * [路由](#路由) + * [条件](#条件) + * [返回值](#返回值) + * [自定义路由匹配器](#自定义路由匹配器) + * [静态文件](#静态文件) + * [视图 / 模板](#视图--模板) + * [字面量模板](#字面量模板) + * [可选的模板语言](#可选的模板语言) + * [Haml 模板](#haml-模板) + * [Erb 模板](#erb-模板) + * [Builder 模板](#builder-模板) + * [Nokogiri 模板](#nokogiri-模板) + * [Sass 模板](#sass-模板) + * [SCSS 模板](#scss-模板) + * [Less 模板](#less-模板) + * [Liquid 模板](#liquid-模板) + * [Markdown 模板](#markdown-模板) + * [Textile 模板](#textile-模板) + * [RDoc 模板](#rdoc-模板) + * [AsciiDoc 模板](#asciidoc-模板) + * [Radius 模板](#radius-模板) + * [Markaby 模板](#markaby-模板) + * [RABL 模板](#rabl-模板) + * [Slim 模板](#slim-模板) + * [Creole 模板](#creole-模板) + * [MediaWiki 模板](#mediawiki-模板) + * [CoffeeScript 模板](#coffeescript-模板) + * [Stylus 模板](#stylus-模板) + * [Yajl 模板](#yajl-模板) + * [WLang 模板](#wlang-模板) + * [在模板中访问变量](#在模板中访问变量) + * [带 `yield` 的模板和嵌套布局](#带-yield-的模板和嵌套布局) + * [内联模板](#内联模板) + * [具名模板](#具名模板) + * [关联文件扩展名](#关联文件扩展名) + * [添加自定义模板引擎](#添加自定义模板引擎) + * [自定义模板查找逻辑](#自定义模板查找逻辑) + * [过滤器](#过滤器) + * [辅助方法](#辅助方法) + * [使用会话](#使用会话) + * [中断请求](#中断请求) + * [传递请求](#传递请求) + * [触发另一个路由](#触发另一个路由) + * [设置响应主体、状态码和响应首部](#设置响应主体状态码和响应首部) + * [响应的流式传输](#响应的流式传输) + * [日志](#日志) + * [媒体类型](#媒体类型) + * [生成 URL](#生成-url) + * [浏览器重定向](#浏览器重定向) + * [缓存控制](#缓存控制) + * [发送文件](#发送文件) + * [访问请求对象](#访问请求对象) + * [附件](#附件) + * [处理日期和时间](#处理日期和时间) + * [查找模板文件](#查找模板文件) + * [配置](#配置) + * [配置攻击防护](#配置攻击防护) + * [可选的设置](#可选的设置) + * [环境](#环境) + * [错误处理](#错误处理) + * [未找到](#未找到) + * [错误](#错误) + * [Rack 中间件](#rack-中间件) + * [测试](#测试) + * [Sinatra::Base - 中间件、库和模块化应用](#sinatrabase---中间件库和模块化应用) + * [模块化风格 vs. 经典风格](#模块化风格-vs-经典风格) + * [运行一个模块化应用](#运行一个模块化应用) + * [使用 config.ru 运行经典风格的应用](#使用-configru-运行经典风格的应用) + * [何时使用 config.ru?](#何时使用-configru) + * [把 Sinatra 当作中间件使用](#把-sinatra-当作中间件使用) + * [创建动态应用](#创建动态应用) + * [作用域和绑定](#作用域和绑定) + * [应用/类作用域](#应用类作用域) + * [请求/实例作用域](#请求实例作用域) + * [代理作用域](#代理作用域) + * [命令行](#命令行) + * [多线程](#多线程) + * [必要条件](#必要条件) + * [紧跟前沿](#紧跟前沿) + * [通过 Bundler 使用 Sinatra](#通过-bundler-使用-sinatra) + * [使用自己本地的 Sinatra](#使用自己本地的-sinatra) + * [全局安装](#全局安装) + * [版本](#版本) + * [更多资料](#更多资料) + +## 路由 + +在 Sinatra 中,一个路由分为两部分:HTTP 方法和 URL 匹配范式。每个路由都有一个要执行的代码块: + +```ruby +get '/' do + .. 显示内容 .. +end + +post '/' do + .. 创建内容 .. +end + +put '/' do + .. 替换内容 .. +end + +patch '/' do + .. 修改内容 .. +end + +delete '/' do + .. 删除内容 .. +end + +options '/' do + .. 显示命令列表 .. +end + +link '/' do + .. 建立某种联系 .. +end + +unlink '/' do + .. 解除某种联系 .. +end +``` + +路由按照它们定义时的顺序进行匹配。第一个与请求匹配的路由会被调用。 + +路由范式可以包括具名参数,具名参数可以通过 `params` hash 访问: + +```ruby +get '/hello/:name' do + # 匹配 "GET /hello/foo" 和 "GET /hello/bar" + # params['name'] 的值是 'foo' 或者 'bar' + "Hello #{params['name']}!" +end +``` + +也可以通过代码块参数访问具名参数: + +```ruby +get '/hello/:name' do |n| + # 匹配 "GET /hello/foo" 和 "GET /hello/bar" + # params['name'] 的值是 'foo' 或者 'bar' + # n 存储 params['name'] 的值 + "Hello #{n}!" +end +``` + +路由范式也可以包含通配符参数, 参数值可以通过 `params['splat']` 数组访问。 + +```ruby +get '/say/*/to/*' do + # 匹配 "GET /say/hello/to/world" + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # 匹配 "GET /download/path/to/file.xml" + params['splat'] # => ["path/to/file", "xml"] +end +``` + +或者通过代码块参数访问: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +通过正则表达式匹配路由: + +```ruby +get /\/hello\/([\w]+)/ do + "Hello, #{params['captures'].first}!" +end +``` + +或者使用代码块参数: + +```ruby +get %r{/hello/([\w]+)} do |c| + # 匹配 "GET /meta/hello/world"、"GET /hello/world/1234" 等 + "Hello, #{c}!" +end +``` + +路由范式可以包含可选参数: + +```ruby +get '/posts/:format?' do + # 匹配 "GET /posts/" 和任意扩展 "GET /posts/json"、"GET /posts/xml" 等 +end +``` + +路由也可以使用查询参数: + +```ruby +get '/posts' do + # 匹配 "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # 使用 title 和 author 变量;对于 /posts 路由来说,查询字符串是可选的 +end +``` +顺便一提,除非你禁用了路径遍历攻击防护(见下文),请求路径可能在匹配路由前发生改变。 + +你也可以通过`:mustermann_opt`选项定义[Mustermann](https://github.com/sinatra/mustermann)来匹配路由。 + +```ruby +get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do + # matches /posts exactly, with explicit anchoring + "If you match an anchored pattern clap your hands!" +end +``` + +它看起来像一个[条件](https://github.com/sinatra/sinatra/blob/master/README.zh.md#%E6%9D%A1%E4%BB%B6),但实际不是!这些选项将被合并到全局的`mustermann_opts`。 + +### 条件 + +路由可以包含各种匹配条件,比如 user agent: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "你正在使用 Songbird,版本是 #{params['agent'][0]}" +end + +get '/foo' do + # 匹配非 Songbird 浏览器 +end +``` + +其它可以使用的条件有 `host_name` 和 `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "管理员区域,无权进入!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +`provides` 会搜索请求的 Accept 首部字段。 + +也可以轻易地使用自定义条件: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +对于一个需要提供多个值的条件,可以使用 splat: + +```ruby +set(:auth) do |*roles| # <- 注意此处使用了 splat + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +### 返回值 + +路由代码块的返回值至少决定了返回给 +HTTP 客户端的响应主体,或者至少决定了在 +Rack 堆栈中的下一个中间件。大多数情况下,返回值是一个字符串,就像上面的例子中的一样。但是,其它类型的值也是可以接受的。 + +你可以返回任何对象,该对象要么是一个合理的 Rack 响应,要么是一个 Rack body 对象,要么是 HTTP 状态码: + +* 一个包含三个元素的数组: `[状态 (Integer), 响应首部 (Hash), 响应主体 (可以响应 #each 方法)]` +* 一个包含两个元素的数组: `[状态 (Integer), 响应主体 (可以响应 #each 方法)]` +* 一个响应 `#each` 方法,只传回字符串的对象 +* 一个代表状态码的数字 + +例如,我们可以轻松地实现流式传输: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +也可以使用 `stream` 辅助方法(见下文描述)以减少样板代码并在路由中直接使用流式传输。 + +### 自定义路由匹配器 + +如上文所示,Sinatra +本身支持使用字符串和正则表达式作为路由匹配。但不限于此,你可以轻松地定义自己的匹配器: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +上面的例子可能太繁琐了, 因为它也可以用更简单的方式表述: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +或者,使用消极向前查找: + +```ruby +get %r{(?!/index)} do + # ... +end +``` + +## 静态文件 + +静态文件从 `./public` 目录提供服务。可以通过设置`:public_folder` 选项设定一个不同的位置: + +```ruby +set :public_folder, __dir__ + '/static' +``` + +请注意 public 目录名并没有包含在 URL 中。文件 `./public/css/style.css` 可以通过 +`http://example.com/css/style.css` 访问。 + +可以使用 `:static_cache_control` 设置(见下文)添加 `Cache-Control` 首部信息。 + +## 视图 / 模板 + +每一门模板语言都将自身的渲染方法暴露给 +Sinatra 调用。这些渲染方法只是简单地返回字符串。 + +```ruby +get '/' do + erb :index +end +``` + +这段代码会渲染 `views/index.erb` 文件。 + +除了模板文件名,也可以直接传入模板内容: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +渲染方法接受第二个参数,即选项 hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +这段代码会将 `views/index.erb` 嵌入在 `views/post.erb` +布局中并一起渲染(`views/layout.erb` 是默认的布局,如果它存在的话)。 + +任何 Sinatra 不能理解的选项都会传递给模板引擎。 + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +也可以为每种模板语言设置通用的选项: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +在渲染方法中传入的选项会覆盖通过 `set` 设置的通用选项。 + +可用的选项: + +
    +
    locals
    +
    + 传递给模板文档的 locals 对象列表。对于 partials + 很方便。例如:erb "<%= foo %>", :locals => {:foo => "bar"} +
    + +
    default_encoding
    +
    默认的字符编码。默认值为 settings.default_encoding
    + +
    views
    +
    存放模板文件的目录。默认为 settings.views
    + +
    layout
    +
    + 是否使用布局 (truefalse)。 + 如果使用一个符号类型的值,则是用于明确使用的模板。例如: + erb :index, :layout => !request.xhr? +
    + +
    content_type
    +
    由模板生成的 Content-Type。默认值由模板语言决定。
    + +
    scope
    +
    + 渲染模板时的作用域。默认值为应用类的实例对象。如果更改此项,实例变量和辅助方法将不可用。 +
    + +
    layout_engine
    +
    + 渲染布局所使用的模板引擎。用于不支持布局的模板语言。默认值为模板所使用的引擎。例如: + set :rdoc, :layout_engine => :erb +
    + +
    layout_options
    +
    + 渲染布局的特殊选项。例如: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
    +
    + +Sinatra 假定模板文件直接位于 `./views` 目录。要使用不同的视图目录: + +```ruby +set :views, settings.root + '/templates' +``` + + +需要牢记的一点是,你必须通过符号引用模板, 即使它们存放在子目录下 +(在这种情况下,使用 `:'subdir/template'` 或 `'subdir/template'.to_sym`)。 +如果你不使用符号,渲染方法会直接渲染你传入的任何字符串。 + +### 字面量模板 + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +这段代码直接渲染模板字符串。 + +### 可选的模板语言 + +一些语言有多种实现。为了确定使用哪种实现(以及保证线程安全),你应该首先引入该实现: + +```ruby +require 'rdiscount' # 或 require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml 模板 + + + + + + + + + + + + + + +
    依赖项haml
    文件扩展名.haml
    例子haml :index, :format => :html5
    + +#### Erb 模板 + + + + + + + + + + + + + + +
    依赖项 + erubis + 或 erb (Ruby 标准库中已经包含) +
    文件扩展名.erb, .rhtml or .erubis (仅用于 Erubis)
    例子erb :index
    + +#### Builder 模板 + + + + + + + + + + + + + + +
    依赖项 + builder +
    文件扩展名.builder
    例子builder { |xml| xml.em "hi" }
    + +`builder` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### Nokogiri 模板 + + + + + + + + + + + + + + +
    依赖项nokogiri
    文件扩展名.nokogiri
    例子nokogiri { |xml| xml.em "hi" }
    + +`nokogiri` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### Sass 模板 + + + + + + + + + + + + + + +
    依赖项sass
    文件扩展名.sass
    例子sass :stylesheet, :style => :expanded
    + +#### SCSS 模板 + + + + + + + + + + + + + + +
    依赖项sass
    文件扩展名.scss
    例子scss :stylesheet, :style => :expanded
    + +#### Less 模板 + + + + + + + + + + + + + + +
    依赖项less
    文件扩展名.less
    例子less :stylesheet
    + +#### Liquid 模板 + + + + + + + + + + + + + + +
    依赖项liquid
    文件扩展名.liquid
    例子liquid :index, :locals => { :key => 'value' }
    + +因为不能在 Liquid 模板中调用 Ruby 方法(除了 `yield`),你几乎总是需要传递 locals 对象给它。 + +#### Markdown 模板 + + + + + + + + + + + + + + +
    依赖项 + 下列任一: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
    文件扩展名.markdown, .mkd and .md
    例子markdown :index, :layout_engine => :erb
    + +不能在 markdown 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +请注意你也可以在其它模板中调用 markdown 方法: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +因为不能在 Markdown 中使用 Ruby 语言,你不能使用 Markdown 书写的布局。 +不过,使用其它渲染引擎作为模板的布局是可能的,这需要通过传入 `:layout_engine` 选项。 + +#### Textile 模板 + + + + + + + + + + + + + + +
    依赖项RedCloth
    文件扩展名.textile
    例子textile :index, :layout_engine => :erb
    + +不能在 textile 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +请注意你也可以在其他模板中调用 `textile` 方法: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +因为不能在 Textile 中调用 Ruby 方法,你不能用 Textile 书写布局。 +不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### RDoc 模板 + + + + + + + + + + + + + + +
    依赖项RDoc
    文件扩展名.rdoc
    例子rdoc :README, :layout_engine => :erb
    + +不能在 rdoc 中调用 Ruby 方法,也不能传递 locals 给它。 +因此,你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +请注意你也可以在其他模板中调用 `rdoc` 方法: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +因为不能在 RDoc 中调用 Ruby 方法,你不能用 RDoc 书写布局。 +不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### AsciiDoc 模板 + + + + + + + + + + + + + + +
    依赖项Asciidoctor
    文件扩展名.asciidoc, .adoc and .ad
    例子asciidoc :README, :layout_engine => :erb
    + +因为不能在 AsciiDoc 模板中直接调用 Ruby 方法,你几乎总是需要传递 locals 对象给它。 + +#### Radius 模板 + + + + + + + + + + + + + + +
    依赖项Radius
    文件扩展名.radius
    例子radius :index, :locals => { :key => 'value' }
    + +因为不能在 Radius 模板中直接调用 Ruby 方法,你几乎总是可以传递 locals 对象给它。 + +#### Markaby 模板 + + + + + + + + + + + + + + +
    依赖项Markaby
    文件扩展名.mab
    例子markaby { h1 "Welcome!" }
    + +`markaby` 渲染方法也接受一个代码块,用于内联模板(见例子)。 + +#### RABL 模板 + + + + + + + + + + + + + + +
    依赖项Rabl
    文件扩展名.rabl
    例子rabl :index
    + +#### Slim 模板 + + + + + + + + + + + + + + +
    依赖项Slim Lang
    文件扩展名.slim
    例子slim :index
    + +#### Creole 模板 + + + + + + + + + + + + + + +
    依赖项Creole
    文件扩展名.creole
    例子creole :wiki, :layout_engine => :erb
    + +不能在 creole 中调用 Ruby 方法,也不能传递 locals 对象给它。 +因此你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +注意你也可以在其它模板内调用 `creole` 方法: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +因为不能在 Creole 模板文件内调用 Ruby 方法,你不能用 Creole 书写布局文件。 +然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### MediaWiki 模板 + + + + + + + + + + + + + + +
    依赖项WikiCloth
    文件扩展名.mediawiki and .mw
    例子mediawiki :wiki, :layout_engine => :erb
    + +在 MediaWiki 标记文件内不能调用 Ruby 方法,也不能传递 locals 对象给它。 +因此你一般会结合其它的渲染引擎来使用它: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +注意你也可以在其它模板内调用 `mediawiki` 方法: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +因为不能在 MediaWiki 文件内调用 Ruby 方法,你不能用 MediaWiki 书写布局文件。 +然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。 + +#### CoffeeScript 模板 + + + + + + + + + + + + + + +
    依赖项 + + CoffeeScript + 以及一种 + + 执行 JavaScript 的方式 + +
    文件扩展名.coffee
    例子coffee :index
    + +#### Stylus 模板 + + + + + + + + + + + + + + +
    依赖项 + + Stylus + 以及一种 + + 执行 JavaScript 的方式 + +
    文件扩展名.styl
    例子stylus :index
    + +在使用 Stylus 模板之前,你需要先加载 `stylus` 和 `stylus/tilt`: + +```ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl 模板 + + + + + + + + + + + + + + +
    依赖项yajl-ruby
    文件扩展名.yajl
    例子 + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
    + +模板文件的源码作为一个 Ruby 字符串被求值,得到的 json 变量是通过 `#to_json` 方法转换的: + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +可以使用 `:callback` 和 `:variable` 选项装饰被渲染的对象: + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang 模板 + + + + + + + + + + + + + + +
    依赖项WLang
    文件扩展名.wlang
    例子wlang :index, :locals => { :key => 'value' }
    + +因为在 WLang 中调用 Ruby 方法不符合语言习惯,你几乎总是需要传递 locals 给 WLang 木板。 +然而,可以用 WLang 编写布局文件,也可以在 WLang 中使用 `yield` 方法。 + +### 在模板中访问变量 + +模板的求值发生在路由处理器内部的上下文中。模板可以直接访问路由处理器中设置的实例变量。 + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +或者,也可以显式地指定一个由局部变量组成的 locals 哈希: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } +end +``` + +locals 哈希典型的使用情景是在别的模板中渲染 partials。 + +### 带 `yield` 的模板和嵌套布局 + +布局通常就是使用了 `yield` 方法的模板。 +这样的布局文件可以通过上面描述的 `:template` 选项指定,也可以通过下面的代码块渲染: + +```ruby +erb :post, :layout => false do + erb :index +end +``` + +这段代码几乎完全等同于 `erb :index, :layout => :post`。 + +向渲染方法传递代码块对于创建嵌套布局是最有用的: + +```ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +代码行数可以更少: + +```ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +当前,以下的渲染方法接受一个代码块:`erb`、`haml`、`liquid`、`slim ` 和 `wlang`。 +通用的 `render` 方法也接受。 + +### 内联模板 + +模板可以在源文件的末尾定义: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +注意:在引入了 sinatra 的源文件中定义的内联模板会自动载入。 +如果你在其他源文件中也有内联模板,需要显式调用 `enable :inline_templates`。 + +### 具名模板 + +可以使用顶层 `template` 方法定义模板: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +如果存在名为 “layout” 的模板,该模板会在每个模板渲染的时候作为布局使用。 +你可以为渲染方法传送 `:layout => false` 来禁用该次渲染的布局, +也可以设置 `set :haml, :layout => false` 来默认禁用布局。 + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### 关联文件扩展名 + +为了将一个文件扩展名到对应的模版引擎,要使用 `Tilt.register`。 +比如,如果你喜欢使用 `tt` 作为 Textile 模版的扩展名,你可以这样做: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### 添加自定义模板引擎 + +首先,通过 Tilt 注册你自定义的引擎,然后创建一个渲染方法: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +这段代码将会渲染 `./views/index.myat` 文件。 +查看 https://github.com/rtomayko/tilt 以了解更多关于 Tilt 的信息。 + +### 自定义模板查找逻辑 + +要实现自定义的模板查找机制,你可以构建自己的 `#find_template` 方法: + +```ruby +configure do + set :views, [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## 过滤器 + +`before` 过滤器在每个请求之前调用,调用的上下文与请求的上下文相同,并且可以修改请求和响应。 +在过滤器中设置的变量可以被路由和模板访问: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +`after` 过滤器在每个请求之后调用,调用上下文与请求的上下文相同,并且也会修改请求和响应。 +在 `before` 过滤器和路由中设置的实例变量可以被 `after` 过滤器访问: + +```ruby +after do + puts response.status +end +``` + +请注意:除非你显式使用 `body` 方法,而不是在路由中直接返回字符串, +响应主体在 `after` 过滤器是不可访问的, 因为它在之后才会生成。 + +过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +和路由一样,过滤器也可以带有条件: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## 辅助方法 + +使用顶层的 `helpers` 方法来定义辅助方法, 以便在路由处理器和模板中使用: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +也可以在多个分散的模块中定义辅助方法: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +以上代码块与在应用类中包含模块等效。 + +### 使用会话 + +会话用于在请求之间保持状态。如果激活了会话,每一个用户会话都对应一个会话 hash: + +```ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +#### 会话加密 + +为提高安全性,cookie 中的会话数据使用`HMAC-SHA1`进行加密。会话加密的最佳实践应当是像`HMAC-SHA1`这样生成大于或等于64字节 (512 bits, 128 hex characters)的随机值。应当避免使用少于32字节(256 bits, 64 hex characters)的随机值。应当使用生成器来创建安全的密钥,而不是拍脑袋决定。 + +默认情况下,Sinatra会生成一个32字节的密钥,但随着应用程序的每次重新启动,它都会发生改变。如果有多个应用程序的实例,使用Sinatra生成密钥,每个实例将有不同的密钥,这可能不是您想要的。 + +为了更好的安全性和可用性,[建议](https://12factor.net/config)生成安全的随机密钥,并将其存储在运行应用程序的每个主机上的环境变量中,以便所有应用程序实例都将共享相同的密钥。并且应该定期更新会话密钥。下面是一些创建64比特密钥的例子: + +#### 生成密钥 + +```ruby +$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +#### 生成密钥(小贴士) + +MRI Ruby目前认为[sysrandom gem](https://github.com/cryptosphere/sysrandom)使用系统的随机数生成器要比用户态的`OpenSSL`好。 + +```ruby +$ gem install sysrandom +Building native extensions. This could take a while... +Successfully installed sysrandom-1.x +1 gem installed + +$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" +99ae8af...snip...ec0f262ac +``` + +#### 从环境变量使用密钥 + +将Sinatra的SESSION_SECRET环境变量设置为生成的值。在主机的重新启动之间保存这个值。由于这样做的方法会因系统而异,仅供说明之用: +``` +# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc +``` + +#### 应用的密钥配置 + +如果SESSION SECRET环境变量不可用,将把应用的随机密钥设置为不安全的。 + +关于[sysrandom gem](https://github.com/cryptosphere/sysrandom)的更多用法: + +```ruby +require 'securerandom' +# -or- require 'sysrandom/securerandom' +set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) } +``` + +#### 会话配置 + +如果你想进一步配置会话,可以在设置 `sessions` 时提供一个选项 hash 作为第二个参数: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +为了在 foo.com 的子域名间共享会话数据,可以在域名前添加一个 *.*: + +```ruby +set :sessions, :domain => '.foo.com' +``` + +#### 选择你自己的会话中间件 + +请注意 `enable :sessions` 实际将所有的数据保存在一个 cookie 中。 +这可能并不总是你想要的(cookie 中存储大量的数据会增加你的流量)。 +你可以使用任何 Rack session 中间件:要达到此目的,**不要**使用 `enable :sessions`, +而是按照自己的需要引入想使用的中间件: + +```ruby +enable :sessions +set :session_store, Rack::Session::Pool +``` + +另一种选择是不要调用enable:sessions,而是像你想要的其他中间件一样加入你的中间件。 + +重要的是要注意,使用此方法时,默认情况下不会启用基于会话的保护。 + +还需要添加Rack中间件: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 +use Rack::Protection::RemoteToken +use Rack::Protection::SessionHijacking +``` +更多[安全防护配置](https://github.com/sinatra/sinatra#configuring-attack-protection)的信息。 + +### 中断请求 + +要想在过滤器或路由中立即中断一个请求: + +```ruby +halt +``` + +你也可以指定中断时的状态码: + +```ruby +halt 410 +``` + +或者响应主体: + +```ruby +halt 'this will be the body' +``` + +或者同时指定两者: + +```ruby +halt 401, 'go away!' +``` + +也可以指定响应首部: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +当然也可以使用模板: + +``` +halt erb(:error) +``` + +### 传递请求 + +一个路由可以放弃对请求的处理并将处理让给下一个匹配的路由,这要通过 `pass` 实现: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +执行 `pass` 后,控制流从该路由代码块直接退出,并继续前进到下一个匹配的路由。 +如果没有匹配的路由,将返回 404。 + +### 触发另一个路由 + +有些时候,`pass` 并不是你想要的,你希望得到的是调用另一个路由的结果。 +使用 `call` 就可以做到这一点: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +请注意在以上例子中,你只需简单地移动 `"bar"` 到一个被 `/foo` 和 `/bar` 同时使用的辅助方法中, +就可以简化测试和增加性能。 + +如果你希望请求发送到同一个应用,而不是应用副本,应使用 `call!` 而不是 `call`。 + +如果想更多了解关于 `call` 的信息,请查看 Rack 规范。 + +### 设置响应主体、状态码和响应首部 + +推荐在路由代码块的返回值中设定状态码和响应主体。 +但是,在某些场景下你可能想在别处设置响应主体,这时你可以使用 `body` 辅助方法。 +设置之后,你可以在那以后使用该方法访问响应主体: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +也可以传递一个代码块给 `body` 方法, +它会被 Rack 处理器执行(这可以用来实现流式传输,参见“返回值”)。 + +与响应主体类似,你也可以设定状态码和响应首部: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +正如 `body` 方法,不带参数调用 `headers` 和 `status` 方法可以访问它们的当前值。 + +### 响应的流式传输 + +有时你可能想在完全生成响应主体前返回数据。 +更极端的情况是,你希望在客户端关闭连接前一直发送数据。 +为满足这些需求,可以使用 `stream` 辅助方法而不必重新造轮子: + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +`stream` 辅助方法允许你实现流式 API 和 +[服务器端发送事件](https://w3c.github.io/eventsource/), +同时它也是实现 [WebSockets](https://en.wikipedia.org/wiki/WebSocket) 的基础。 +如果你应用的部分(不是全部)内容依赖于访问缓慢的资源,它也可以用来提高并发能力。 + +请注意流式传输,尤其是并发请求数,高度依赖于应用所使用的服务器。 +一些服务器可能根本不支持流式传输。 +如果服务器不支持,传递给 `stream` 方法的代码块执行完毕之后,响应主体会一次性地发送给客户端。 +Shotgun 完全不支持流式传输。 + +如果 `:keep_open` 作为可选参数传递给 `stream` 方法,将不会在流对象上调用 `close` 方法, +这允许你在控制流的下游某处手动关闭。该参数只对事件驱动的服务器(如 Thin 和 Rainbows)生效。 +其它服务器仍会关闭流式传输: + +```ruby +# 长轮询 + +set :server, :thin +connections = [] + +get '/subscribe' do + # 在服务器端的事件中注册客户端 + stream(:keep_open) do |out| + connections << out + # 清除关闭的连接 + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # 通知客户端有条新消息 + out << params['message'] << "\n" + + # 使客户端重新连接 + out.close + end + + # 确认 + "message received" +end +``` + +### 日志 + +在请求作用域下,`logger` 辅助方法会返回一个 `Logger` 类的实例: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +该 `logger` 方法会自动参考 Rack 处理器的日志设置。 +若日志被禁用,该方法会返回一个无关痛痒的对象,所以你完全不必担心这会影响路由和过滤器。 + +注意只有 `Sinatra::Application` 默认开启了日志,若你的应用继承自 `Sinatra::Base`, +很可能需要手动开启: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +为避免使用任何与日志有关的中间件,需要将 `logging` 设置项设为 `nil`。 +然而,在这种情况下,`logger` 辅助方法会返回 `nil`。 +一种常见的使用场景是你想要使用自己的日志工具。 +Sinatra 会使用 `env['rack.logger']` 的值作为日志工具,无论该值是什么。 + +### 媒体类型 + +使用 `send_file` 或者静态文件的时候,Sinatra 可能不会识别你的媒体类型。 +使用 `mime_type` 通过文件扩展名来注册媒体类型: + +```ruby +mime_type :foo, 'text/foo' +``` + +你也可以使用 `content_type` 辅助方法: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### 生成 URL + +为了生成 URL,你应当使用 `url` 辅助方法,例如,在 Haml 中: + +```ruby +%a{:href => url('/foo')} foo +``` + +如果使用了反向代理和 Rack 路由,生成 URL 的时候会考虑这些因素。 + +这个方法还有一个别名 `to` (见下面的例子)。 + +### 浏览器重定向 + +你可以通过 `redirect` 辅助方法触发浏览器重定向: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +其他参数的用法,与 `halt` 相同: + +```ruby +redirect to('/bar'), 303 +redirect 'http://www.google.com/', 'wrong place, buddy' +``` + +用 `redirect back` 可以把用户重定向到原始页面: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +如果想传递参数给 redirect,可以用查询字符串: + +```ruby +redirect to('/bar?sum=42') +``` + +或者使用会话: + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### 缓存控制 + +正确设置响应首部是合理利用 HTTP 缓存的基础。 + +可以这样设定 Cache-Control 首部字段: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +核心提示: 应当在 `before` 过滤器中设定缓存。 + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +如果你使用 `expires` 辅助方法设定响应的响应首部, 会自动设定 `Cache-Control` 字段: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +为了合理使用缓存,你应该考虑使用 `etag` 或 `last_modified` 方法。 +推荐在执行繁重任务*之前*使用这些辅助方法,这样一来, +如果客户端在缓存中已经有相关内容,就会立即得到响应: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +也可以使用 [weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +这些辅助方法并不会为你做任何缓存,而是将必要的信息发送给你的缓存。 +如果你正在寻找快捷的反向代理缓存方案,可以尝试 +[rack-cache](https://github.com/rtomayko/rack-cache): + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +使用 `:statis_cache_control` 设置(见下文)为静态文件添加 `Cache-Control` 首部字段。 + +根据 RFC 2616,如果 If-Match 或 If-None-Match 首部设置为 `*`,根据所请求的资源存在与否, +你的应用应当有不同的行为。 +Sinatra 假设安全请求(如 GET)和幂等性请求(如 PUT)所访问的资源是已经存在的, +而其它请求(如 POST 请求)所访问的资源是新资源。 +你可以通过传入 `:new_resource` 选项改变这一行为。 + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +如果你仍想使用 weak ETag,可以传入一个 `:kind` 选项: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### 发送文件 + +为了将文件的内容作为响应返回,可以使用 `send_file` 辅助方法: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +该辅助方法接受一些选项: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +可用的选项有: + +
    +
    filename
    +
    响应中使用的文件名,默认是真实的文件名。
    + +
    last_modified
    +
    Last-Modified 响应首部的值,默认是文件的 mtime (修改时间)。
    + +
    type
    +
    Content-Type 响应首部的值,如果未指定,会根据文件扩展名猜测。
    + +
    disposition
    +
    + Content-Disposition 响应首部的值, + 可选的值有: nil (默认)、:attachment 和 + :inline +
    + +
    length
    +
    Content-Length 响应首部的值,默认是文件的大小。
    + +
    status
    +
    + 将要返回的状态码。当以一个静态文件作为错误页面时,这很有用。 + + 如果 Rack 处理器支持的话,Ruby 进程也能使用除 streaming 以外的方法。 + 如果你使用这个辅助方法, Sinatra会自动处理 range 请求。 +
    +
    + +### 访问请求对象 + +传入的请求对象可以在请求层(过滤器、路由、错误处理器内部)通过 `request` 方法访问: + +```ruby +# 在 http://example.com/example 上运行的应用 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # 客户端设定的请求主体(见下文) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body 的长度 + request.media_type # request.body 的媒体类型 + request.host # "example.com" + request.get? # true (其它动词也具有类似方法) + request.form_data? # false + request["some_param"] # some_param 参数的值。[] 是访问 params hash 的捷径 + request.referrer # 客户端的 referrer 或者 '/' + request.user_agent # 用户代理 (:agent 条件使用该值) + request.cookies # 浏览器 cookies 哈希 + request.xhr? # 这是否是 ajax 请求? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 客户端 IP 地址 + request.secure? # false (如果是 ssl 则为 true) + request.forwarded? # true (如果是运行在反向代理之后) + request.env # Rack 中使用的未处理的 env hash +end +``` + +一些选项,例如 `script_name` 或者 `path_info` 也是可写的: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body` 是一个 IO 或者 StringIO 对象: + +```ruby +post "/api" do + request.body.rewind # 如果已经有人读了它 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### 附件 + +你可以使用 `attachment` 辅助方法来告诉浏览器响应应当被写入磁盘而不是在浏览器中显示。 + +```ruby +get '/' do + attachment + "store it!" +end +``` + +你也可以传递给该方法一个文件名: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### 处理日期和时间 + +Sinatra 提供了一个 `time_for` 辅助方法,其目的是根据给定的值生成 Time 对象。 +该方法也能够转换 `DateTime`、`Date` 和类似的类: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +`expires`、`last_modified` 和类似方法都在内部使用了该方法。 +因此,通过在应用中重写 `time_for` 方法,你可以轻松地扩展这些方法的行为: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### 查找模板文件 + +`find_template` 辅助方法用于在渲染时查找模板文件: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +这其实并不是很有用,除非你需要重载这个方法来实现你自己的查找机制。 +比如,如果你想使用不只一个视图目录: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +另一个例子是对不同的引擎使用不同的目录: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +你可以很容易地封装成一个扩展,然后与他人分享! + +请注意 `find_template` 并不会检查文件是否存在,而是为任何可能的路径调用传入的代码块。 +这并不会导致性能问题,因为 `render` 会在找到文件的时候马上使用 `break`。 +同样的,模板的路径(和内容)会在 development 以外的模式下被缓存。 +你应该时刻提醒自己这一点, 如果你真的想写一个非常疯狂的方法的话。 + +## 配置 + +在启动时运行一次,在任何环境下都是如此: + +```ruby +configure do + # 设置一个选项 + set :option, 'value' + + # 设置多个选项 + set :a => 1, :b => 2 + + # 等同于 `set :option, true` + enable :option + + # 等同于 `set :option, false` + disable :option + + # 也可以用代码块做动态设置 + set(:css_dir) { File.join(views, 'css') } +end +``` + +只有当环境 (`APP_ENV` 环境变量) 被设定为 `:production` 时才运行: + +```ruby +configure :production do + ... +end +``` + +当环境被设定为 `:production` 或者 `:test` 时运行: + +```ruby +configure :production, :test do + ... +end +``` + +你可以用 `settings` 访问这些配置项: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 配置攻击防护 + +Sinatra 使用 [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme) +来抵御常见的攻击。你可以轻易地禁用该行为(但这会大大增加应用被攻击的概率)。 + +```ruby +disable :protection +``` + +为了绕过某单层防护,可以设置 `protection` 为一个选项 hash: + +```ruby +set :protection, :except => :path_traversal +``` + +你可以传入一个数组,以禁用一系列防护措施: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +默认地,如果 `:sessions` 是启用的,Sinatra 只会使用基于会话的防护措施。 +当然,有时你可能想根据自己的需要设置会话。 +在这种情况下,你可以通过传入 `:session` 选项来开启基于会话的防护。 + +```ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 可选的设置 + +
    +
    absolute_redirects
    +
    + 如果被禁用,Sinatra 会允许使用相对路径重定向。 + 然而这样的话,Sinatra 就不再遵守 RFC 2616 (HTTP 1.1), 该协议只允许绝对路径重定向。 +
    +
    + 如果你的应用运行在一个未恰当设置的反向代理之后,你需要启用这个选项。 + 注意 url 辅助方法仍然会生成绝对 URL,除非你传入false 作为第二参数。 +
    +
    默认禁用。
    + +
    add_charset
    +
    + 设置 content_type 辅助方法会自动为媒体类型加上字符集信息。 + 你应该添加而不是覆盖这个选项: + settings.add_charset << "application/foobar" +
    + +
    app_file
    +
    + 主应用文件的路径,用来检测项目的根路径, views 和 public 文件夹和内联模板。 +
    + +
    bind
    +
    + 绑定的 IP 地址 (默认: 0.0.0.0,开发环境下为 localhost)。 + 仅对于内置的服务器有用。 +
    + +
    default_encoding
    +
    默认编码 (默认为 "utf-8")。
    + +
    dump_errors
    +
    在日志中显示错误。
    + +
    environment
    +
    + 当前环境,默认是 ENV['APP_ENV'], + 或者 "development" (如果 ENV['APP_ENV'] 不可用)。 +
    + +
    logging
    +
    使用 logger。
    + +
    lock
    +
    对每一个请求放置一个锁,只使用进程并发处理请求。
    +
    如果你的应用不是线程安全则需启动。默认禁用。
    + +
    method_override
    +
    + 使用 _method 魔法,以允许在不支持的浏览器中在使用 put/delete 方法提交表单。 +
    + +
    port
    +
    监听的端口号。只对内置服务器有用。
    + +
    prefixed_redirects
    +
    + 如果没有使用绝对路径,是否添加 request.script_name 到重定向请求。 + 如果添加,redirect '/foo' 会和 redirect to('/foo') 相同。 + 默认禁用。 +
    + +
    protection
    +
    是否启用网络攻击防护。参见上面的保护部分
    + +
    public_dir
    +
    public_folder 的别名。见下文。
    + +
    public_folder
    +
    + public 文件存放的路径。只有启用了静态文件服务(见下文的 static)才会使用。 + 如果未设置,默认从 app_file 推断。 +
    + +
    reload_templates
    +
    + 是否每个请求都重新载入模板。在开发模式下开启。 +
    + +
    root
    +
    到项目根目录的路径。默认从 app_file 设置推断。
    + +
    raise_errors
    +
    + 抛出异常(会停止应用)。 + 当 environment 设置为 "test" 时会默认开启,其它环境下默认禁用。 +
    + +
    run
    +
    如果启用,Sinatra 会负责 web 服务器的启动。若使用 rackup 或其他方式则不要启用。
    + +
    running
    +
    内置的服务器在运行吗? 不要修改这个设置!
    + +
    server
    +
    服务器,或用于内置服务器的服务器列表。顺序表明了优先级,默认顺序依赖 Ruby 实现。
    + +
    sessions
    +
    + 使用 Rack::Session::Cookie,启用基于 cookie 的会话。 + 查看“使用会话”部分以获得更多信息。 +
    + +
    show_exceptions
    +
    + 当有异常发生时,在浏览器中显示一个 stack trace。 + 当 environment 设置为 "development" 时,默认启用, + 否则默认禁用。 +
    +
    + 也可以设置为 :after_handler, + 这会在浏览器中显示 stack trace 之前触发应用级别的错误处理。 +
    + +
    static
    +
    决定 Sinatra 是否服务静态文件。
    +
    当服务器能够自行服务静态文件时,会禁用。
    +
    禁用会增强性能。
    +
    在经典风格中默认启用,在模块化应用中默认禁用。
    + +
    static_cache_control
    +
    + 当 Sinatra 提供静态文件服务时,设置此选项为响应添加 Cache-Control 首部。 + 使用 cache_control 辅助方法。默认禁用。 +
    +
    + 当设置多个值时使用数组: + set :static_cache_control, [:public, :max_age => 300] +
    + +
    threaded
    +
    + 若设置为 true,会告诉 Thin 使用 EventMachine.defer 处理请求。 +
    + +
    traps
    +
    Sinatra 是否应该处理系统信号。
    + +
    views
    +
    views 文件夹的路径。若未设置则会根据 app_file 推断。
    + +
    x_cascade
    +
    若没有路由匹配,是否设置 X-Cascade 首部。默认为 true
    +
    + +## 环境 + +Sinatra 中有三种预先定义的环境:"development"、"production" 和 "test"。 +环境可以通过 `APP_ENV` 环境变量设置。默认值为 "development"。 +在开发环境下,每次请求都会重新加载所有模板, +特殊的 `not_found` 和 `error` 错误处理器会在浏览器中显示 stack trace。 +在测试和生产环境下,模板默认会缓存。 + +在不同的环境下运行,设置 `APP_ENV` 环境变量: + +```shell +APP_ENV=production ruby my_app.rb +``` + +可以使用预定义的三种方法: `development?`、`test?` 和 `production?` 来检查当前环境: + +```ruby +get '/' do + if settings.development? + "development!" + else + "not development" + end +end +``` + +## 错误处理 + +错误处理器在与路由和 before 过滤器相同的上下文中运行, +这意味着你可以使用许多好东西,比如 `haml`, `erb`, `halt`,等等。 + +### 未找到 + +当一个 `Sinatra::NotFound` 错误被抛出时,或者当响应的状态码是 404 时, +会调用 `not_found` 处理器: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### 错误 + +在任何路由代码块或过滤器抛出异常时,会调用 `error` 处理器。 +但注意在开发环境下只有将 show exceptions 项设置为 `:after_handler` 时,才会生效。 + +```ruby +set :show_exceptions, :after_handler +``` + +可以用 Rack 变量 `sinatra.error` 访问异常对象: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +自定义错误: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +当下面的代码执行时: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +你会得到错误信息: + +``` +So what happened was... something bad +``` + +或者,你也可以为状态码设置错误处理器: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +或者为某个范围内的状态码统一设置错误处理器: + +```ruby +error 400..510 do + 'Boom' +end +``` + +在开发环境下,Sinatra会使用特殊的 `not_found` 和 `error` 处理器, +以便在浏览器中显示美观的 stack traces 和额外的调试信息。 + +## Rack 中间件 + +Sinatra 依赖 [Rack](http://rack.github.io/), 一个面向 Ruby 网络框架的最小化标准接口。 +Rack 最有趣的功能之一是支持“中间件”——位于服务器和你的应用之间的组件, +它们监控或操作 HTTP 请求/响应以提供多种常用功能。 + +Sinatra 通过顶层的 `use` 方法,让建立 Rack 中间件管道异常简单: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use` 的语义和在 [Rack::Builder](http://www.rubydoc.info/github/rack/rack/master/Rack/Builder) +DSL (在 rackup 文件中最频繁使用)中定义的完全一样。例如,`use` 方法接受 +多个/可变参数,以及代码块: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack 拥有有多种标准中间件,用于日志、调试、URL 路由、认证和会话处理。 +根据配置,Sinatra 可以自动使用这里面的许多组件, +所以你一般不需要显式地 `use` 它们。 + +你可以在 [rack](https://github.com/rack/rack/tree/master/lib/rack)、 +[rack-contrib](https://github.com/rack/rack-contrib#readm) 或 +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) +中找到有用的中间件。 + +## 测试 + +可以使用任何基于 Rack 的测试程序库或者框架来编写Sinatra的测试。 +推荐使用 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames): + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +注意:如果你使用 Sinatra 的模块化风格,应该用你应用的类名替代 `Sinatra::Application`。 + +## Sinatra::Base - 中间件、库和模块化应用 + +在顶层定义你的应用很适合微型项目, +但是在构建可复用的组件(如 Rack 中间件、Rails metal、带服务器组件的库或 Sinatra 扩展)时, +却有相当大的缺陷。 +顶层 DSL 认为你采用的是微型应用风格的配置 (例如:唯一应用文件、 +`./public` 和 `./views` 目录、日志、异常细节页面等)。 +如果你的项目不采用微型应用风格,应该使用 `Sinatra::Base`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +Sinatra::Base 的子类可以使用的方法实际上就是顶层 DSL 中可以使用的方法。 +大部分顶层应用可以通过两方面的改变转换为 Sinatra::Base 组件: + +* 你的文件应当引入 `sinatra/base` 而不是 `sinatra`; +否则,Sinatra 的所有 DSL 方法将会被导入主命名空间。 + +* 把应用的路由、错误处理器、过滤器和选项放在一个 Sinatra::Base 的子类中。 + +`Sinatra::Base` 是一个白板。大部分选项(包括内置的服务器)默认是禁用的。 +可以参考[配置](http://www.sinatrarb.com/configuration.html) +以查看可用选项的具体细节和它们的行为。如果你想让你的应用更像顶层定义的应用(即经典风格), +你可以继承 `Sinatra::Applicaiton`。 + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### 模块化风格 vs. 经典风格 + +与通常的认识相反,经典风格并没有任何错误。 +如果它适合你的应用,你不需要切换到模块化风格。 + +与模块化风格相比,经典风格的主要缺点在于,每个 Ruby 进程只能有一个 Sinatra 应用。 +如果你计划使用多个 Sinatra 应用,应该切换到模块化风格。 +你也完全可以混用模块化风格和经典风格。 + +如果从一种风格转换到另一种,你需要注意默认设置中的一些细微差别: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    设置经典风格模块化风格模块化风格
    app_file加载 sinatra 的文件继承 Sinatra::Base 的文件继承 Sinatra::Application 的文件
    run$0 == app_filefalsefalse
    loggingtruefalsetrue
    method_overridetruefalsetrue
    inline_templatestruefalsetrue
    statictrueFile.exist?(public_folder)true
    + +### 运行一个模块化应用 + +模块化应用的启动有两种常见方式,其中之一是使用 `run!` 方法主动启动: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... 这里是应用代码 ... + + # 如果直接执行该文件,那么启动服务器 + run! if app_file == $0 +end +``` + +执行该文件就会启动服务器: + +```shell +ruby my_app.rb +``` + +另一种方式是使用 `config.ru` 文件,这种方式允许你使用任何 Rack 处理器: + +```ruby +# config.ru (用 rackup 启动) +require './my_app' +run MyApp +``` + +运行: + +```shell +rackup -p 4567 +``` + +### 使用 config.ru 运行经典风格的应用 + +编写你的应用: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +添加相应的 `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### 何时使用 config.ru? + +下列情况,推荐使用 `config.ru`: + +* 部署时使用不同的 Rack 处理器 (Passenger、Unicorn、Heroku 等)。 +* 使用多个 `Sinatra::Base` 的子类。 +* 把 Sinatra 当作中间件使用,而非端点。 + +**你不必仅仅因为想使用模块化风格而切换到 `config.ru`,同样的, +你也不必仅仅因为要运行 `config.ru` 而切换到模块化风格。** + +### 把 Sinatra 当作中间件使用 + +Sinatra 可以使用其它 Rack 中间件, +反过来,任何 Sinatra 应用程序自身都可以被当作中间件,添加到任何 Rack 端点前面。 +此端点可以是任何 Sinatra 应用,或任何基于 Rack 的应用程序 (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 中间件的执行发生在 before 过滤器之前 + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 创建动态应用 + +有时你希望在运行时创建新应用,而不必把应用预先赋值给常量。这时可以使用 `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +`Sinatra.new` 接受一个可选的参数,表示要继承的应用: + +```ruby +# config.ru (用 rackup 启动) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +当你测试 Sinatra 扩展或在自己的类库中使用 Sinatra 时,这非常有用。 + +这也让把 Sinatra 当作中间件使用变得极其容易: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## 作用域和绑定 + +当前作用域决定了可以使用的方法和变量。 + +### 应用/类作用域 + +每个 Sinatra 应用都对应 `Sinatra::Base` 类的一个子类。 +如果你在使用顶层 DSL (`require 'sinatra'`),那么这个类就是 `Sinatra::Application`, +否则该类是你显式创建的子类。 +在类层面,你可以使用 `get` 或 `before` 这样的方法, +但不能访问 `request` 或 `session` 对象, 因为对于所有的请求,只有单一的应用类。 + +通过 `set` 创建的选项是类方法: + +```ruby +class MyApp < Sinatra::Base + # 嘿,我在应用作用域! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 嘿,我已经不在应用作用域了! + end +end +``` + +下列位置绑定的是应用作用域: + +* 应用类内部 +* 通过扩展定义的方法内部 +* 传递给 `helpers` 方法的代码块内部 +* 作为 `set` 值的 procs/blocks 内部 +* 传递给 `Sinatra.new` 的代码块内部 + +你可以这样访问变量域对象(应用类): +* 通过传递给 configure 代码块的对象 (`configure { |c| ... }`) +* 在请求作用域中使用 `settings` + +### 请求/实例作用域 + +对于每个请求,Sinatra 会创建应用类的一个新实例。所有的处理器代码块都在该实例对象的作用域中运行。 +在该作用域中, 你可以访问 `request` 和 `session` 对象, +或调用渲染方法(如 `erb`、`haml`)。你可以在请求作用域中通过 `settings` 辅助方法 +访问应用作用域: + +```ruby +class MyApp < Sinatra::Base + # 嘿,我在应用作用域! + get '/define_route/:name' do + # '/define_route/:name' 的请求作用域 + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}" 的请求作用域 + @value # => nil (并不是同一个请求) + end + + "Route defined!" + end +end +``` + +以下位置绑定的是请求作用域: + +* get、head、post、put、delete、options、patch、link 和 unlink 代码块内部 +* before 和 after 过滤器内部 +* 辅助方法内部 +* 模板/视图内部 + +### 代理作用域 + +代理作用域只是把方法转送到类作用域。 +然而,它与类作用域的行为并不完全相同, 因为你并不能在代理作用域获得类的绑定。 +只有显式地标记为供代理使用的方法才是可用的, +而且你不能和类作用域共享变量/状态。(解释:你有了一个不同的 `self`)。 +你可以通过调用 `Sinatra::Delegator.delegate :method_name` 显式地添加方法代理。 + +以下位置绑定的是代理变量域: +* 顶层绑定,如果你执行了 `require "sinatra"` +* 扩展了 `Sinatra::Delegator` 这一 mixin 的对象内部 + +自己在这里看一下源码:[Sinatra::Delegator +mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +已经 +[被扩展进了 main 对象](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。 + +## 命令行 + +可以直接运行 Sinatra 应用: + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +选项是: + +``` +-h # 显示帮助 +-p # 设置端口号 (默认是 4567) +-o # 设定主机名 (默认是 0.0.0.0) +-e # 设置环境 (默认是 development) +-s # 声明 rack 服务器/处理器 (默认是 thin) +-x # 打开互斥锁 (默认是 off) +``` + +### 多线程 + +_根据 Konstantin 的 [这个 StackOverflow 答案] [so-answer] 改写_ + +Sinatra 本身并不使用任何并发模型,而是将并发的任务留给底层的 +Rack 处理器(服务器),如 Thin、Puma 或 WEBrick。Sinatra 本身是线程安全的,所以 +Rack 处理器使用多线程并发模型并无任何问题。这意味着在启动服务器时,你必须指定特定 +Rack 处理器的正确调用方法。 +下面的例子展示了如何启动一个多线程的 Thin 服务器: + +```ruby +# app.rb + +require 'sinatra/base' + +class App < Sinatra::Base + get '/' do + "Hello, World" + end +end + +App.run! + +``` + +启动服务器的命令是: + +```shell +thin --threaded start +``` + + +[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) + +## 必要条件 + +以下 Ruby 版本受官方支持: +
    +
    Ruby 1.8.7
    +
    + Sinatra 完全支持 1.8.7,但是,除非必要,我们推荐你升级或者切换到 + JRuby 或 Rubinius。Sinatra 2.0 之前都不会取消对 1.8.7 + 的支持。Ruby 1.8.6 目前已不受支持。 +
    + +
    Ruby 1.9.2
    +
    + Sinatra 完全支持 1.9.2。 + 不要使用 1.9.2p0,它在运行 Sinatra 程序时会产生 segmentation faults 错误。 + 至少在 Sinatra 1.5 发布之前,官方对 1.9.2 的支持仍会继续。 +
    + +
    Ruby 1.9.3
    +
    + Sinatra 完全支持并推荐使用 1.9.3。请注意从更早的版本迁移到 1.9.3 会使所有的会话失效。 + 直到 Sinatra 2.0 发布之前,官方仍然会支持 1.9.3。 +
    + +
    Ruby 2.x
    +
    + Sinatra 完全支持并推荐使用 2.x。目前尚无停止支持 2.x 的计划。 +
    + +
    Rubinius
    +
    + Sinatra 官方支持 Rubinius (Rubinius >= 2.x)。推荐 gem install puma。 +
    + +
    JRuby
    +
    + Sinatra 官方支持 JRuby 的最新稳定版本,但不推荐在 JRuby 上使用 C 扩展。 + 推荐 gem install trinidad。 +
    +
    + +我们也在时刻关注新的 Ruby 版本。 + +以下 Ruby 实现不受 Sinatra 官方支持,但可以运行 Sinatra: + +* 老版本 JRuby 和 Rubinius +* Ruby 企业版 +* MacRuby、Maglev、IronRuby +* Ruby 1.9.0 和 1.9.1 (不推荐使用) + +不受官方支持的意思是,如果仅在不受支持的 Ruby 实现上发生错误,我们认为不是我们的问题,而是该实现的问题。 + +我们同时也针对 ruby-head (MRI 的未来版本)运行 CI,但由于 ruby-head 一直处在变化之中, +我们不能作任何保证。我们期望完全支持未来的 2.x 版本。 + +Sinatra 应该会运行在任何支持上述 Ruby 实现的操作系统上。 + +如果你使用 MacRuby,你应该 `gem install control_tower`。 + +Sinatra 目前不支持 Cardinal、SmallRuby、BlueRuby 或其它 1.8.7 之前的 Ruby 版本。 + +## 紧跟前沿 + +如果你想使用 Sinatra 的最新代码,请放心使用 master 分支来运行你的程序,它是相当稳定的。 + +我们也会不定期推出 prerelease gems,所以你也可以运行 + +```shell +gem install sinatra --pre +``` + +来获得最新的特性。 + +### 通过 Bundler 使用 Sinatra + +如果你想在应用中使用最新的 Sinatra,推荐使用 [Bundler](http://bundler.io)。 + +首先,安装 Bundler,如果你还没有安装的话: + +```shell +gem install bundler +``` + +然后,在你的项目目录下创建一个 `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 其它依赖 +gem 'haml' # 假如你使用 haml +gem 'activerecord', '~> 3.0' # 也许你还需要 ActiveRecord 3.x +``` + +请注意你必须在 `Gemfile` 中列出应用的所有依赖项。 +然而, Sinatra 的直接依赖项 (Rack 和 Tilt) 则会被 Bundler 自动获取和添加。 + +现在你可以这样运行你的应用: + +```shell +bundle exec ruby myapp.rb +``` + +### 使用自己本地的 Sinatra + +创建一个本地克隆,并通过 `$LOAD_PATH` 里的 `sinatra/lib` 目录运行你的应用: + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +为了在未来更新 Sinatra 源代码: + +```shell +cd myapp/sinatra +git pull +``` + +### 全局安装 + +你可以自行编译 Sinatra gem: + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +如果你以 root 身份安装 gems,最后一步应该是: + +```shell +sudo rake install +``` + +## 版本 + +Sinatra 遵循[语义化版本](http://semver.org),无论是 SemVer 还是 SemVerTag。 + +## 更多资料 + +* [项目官网](http://www.sinatrarb.com/) - 更多文档、新闻和其它资源的链接。 +* [贡献](http://www.sinatrarb.com/contributing) - 找到一个 bug?需要帮助?有了一个 patch? +* [问题追踪](https://github.com/sinatra/sinatra/issues) +* [Twitter](https://twitter.com/sinatra) +* [邮件列表](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net +* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack,点击 +[这里](https://sinatra-slack.herokuapp.com/) 获得邀请。 +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 教程 +* [Sinatra Recipes](http://recipes.sinatrarb.com/) 社区贡献的实用技巧 +* http://www.rubydoc.info/ 上[最新版本](http://www.rubydoc.info//gems/sinatra)或[当前 HEAD](http://www.rubydoc.info/github/sinatra/sinatra) 的 API 文档 +* [CI 服务器](https://travis-ci.org/sinatra/sinatra) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Rakefile b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Rakefile new file mode 100644 index 0000000000..3209d36450 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/Rakefile @@ -0,0 +1,225 @@ +require 'rake/clean' +require 'rake/testtask' +require 'fileutils' +require 'date' + +task :default => :test +task :spec => :test + +CLEAN.include "**/*.rbc" + +def source_version + @source_version ||= File.read(File.expand_path("../VERSION", __FILE__)).strip +end + +def prev_feature + source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s } +end + +def prev_version + return prev_feature + '.0' if source_version.end_with? '.0' + source_version.gsub(/\d+$/) { |s| s.to_i - 1 } +end + +# SPECS =============================================================== + +task :test do + ENV['LANG'] = 'C' + ENV.delete 'LC_CTYPE' +end + +Rake::TestTask.new(:test) do |t| + t.test_files = FileList['test/*_test.rb'] + t.ruby_opts = ['-r rubygems'] if defined? Gem + t.ruby_opts << '-I.' + t.warning = true +end + +Rake::TestTask.new(:"test:core") do |t| + core_tests = %w[base delegator encoding extensions filter + helpers mapped_error middleware radius rdoc + readme request response result route_added_hook + routing server settings sinatra static templates] + t.test_files = core_tests.map {|n| "test/#{n}_test.rb"} + t.ruby_opts = ["-r rubygems"] if defined? Gem + t.ruby_opts << "-I." + t.warning = true +end + +# Rcov ================================================================ + +namespace :test do + desc 'Measures test coverage' + task :coverage do + rm_f "coverage" + ENV['COVERAGE'] = '1' + Rake::Task['test'].invoke + end +end + +# Website ============================================================= + +desc 'Generate RDoc under doc/api' +task 'doc' => ['doc:api'] +task('doc:api') { sh "yardoc -o doc/api" } +CLEAN.include 'doc/api' + +# README =============================================================== + +task :add_template, [:name] do |t, args| + Dir.glob('README.*') do |file| + code = File.read(file) + if code =~ /^===.*#{args.name.capitalize}/ + puts "Already covered in #{file}" + else + template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] + if !template + puts "Liquid not found in #{file}" + else + puts "Adding section to #{file}" + template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) + code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" + File.open(file, "w") { |f| f << code } + end + end + end +end + +# Thanks in announcement =============================================== + +team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase", "Zachary Scott"] +desc "list of contributors" +task :thanks, ['release:all', :backports] do |t, a| + a.with_defaults :release => "#{prev_version}..HEAD", + :backports => "#{prev_feature}.0..#{prev_feature}.x" + included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } + excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } + commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } + authors = commits.keys.sort_by { |n| - commits[n].size } - team + puts authors[0..-2].join(', ') << " and " << authors.last, + "(based on commits included in #{a.release}, but not in #{a.backports})" +end + +desc "list of authors" +task :authors, [:commit_range, :format, :sep] do |t, a| + a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' + authors = Hash.new(0) + blake = "Blake Mizerany" + overall = 0 + mapping = { + "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, + "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", + "Wu Jiang (nouse)" => "Wu Jiang" } + `git shortlog -s #{a.commit_range}`.lines.map do |line| + line = line.force_encoding 'binary' if line.respond_to? :force_encoding + num, name = line.split("\t", 2).map(&:strip) + authors[mapping[name] || name] += num.to_i + overall += num.to_i + end + puts "#{overall} commits by #{authors.count} authors:" + puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) +end + +desc "generates TOC" +task :toc, [:readme] do |t, a| + a.with_defaults :readme => 'README.md' + + def self.link(title) + title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') + end + + puts "* [Sinatra](#sinatra)" + title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain + File.binread(a.readme).scan(/^##.*/) do |line| + puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } + end +end + +# PACKAGING ============================================================ + +if defined?(Gem) + GEMS_AND_ROOT_DIRECTORIES = { + "sinatra" => ".", + "sinatra-contrib" => "./sinatra-contrib", + "rack-protection" => "./rack-protection" + } + + def package(gem, ext='') + "pkg/#{gem}-#{source_version}" + ext + end + + directory 'pkg/' + CLOBBER.include('pkg') + + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + file package(gem, '.gem') => ["pkg/", "#{directory + '/' + gem}.gemspec"] do |f| + sh "cd #{directory} && gem build #{gem}.gemspec" + mv directory + "/" + File.basename(f.name), f.name + end + + file package(gem, '.tar.gz') => ["pkg/"] do |f| + sh <<-SH + git archive \ + --prefix=#{gem}-#{source_version}/ \ + --format=tar \ + HEAD -- #{directory} | gzip > #{f.name} + SH + end + end + + namespace :package do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Build #{gem} packages" + task gem => %w[.gem .tar.gz].map { |e| package(gem, e) } + end + + desc "Build all packages" + task :all => GEMS_AND_ROOT_DIRECTORIES.keys + end + + namespace :install do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Build and install #{gem} as local gem" + task gem => package(gem, '.gem') do + sh "gem install #{package(gem, '.gem')}" + end + end + + desc "Build and install all of the gems as local gems" + task :all => GEMS_AND_ROOT_DIRECTORIES.keys + end + + namespace :release do + GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory| + desc "Release #{gem} as a package" + task gem => "package:#{gem}" do + sh <<-SH + gem install #{package(gem, '.gem')} --local && + gem push #{package(gem, '.gem')} + SH + end + end + + desc "Commits the version to github repository" + task :commit_version do + %w[ + lib/sinatra + sinatra-contrib/lib/sinatra/contrib + rack-protection/lib/rack/protection + ].each do |path| + path = File.join(path, 'version.rb') + File.write(path, File.read(path).sub(/VERSION = '(.+?)'/, "VERSION = '#{source_version}'")) + end + + sh <<-SH + git commit --allow-empty -a -m '#{source_version} release' && + git tag -s v#{source_version} -m '#{source_version} release' && + git push && (git push origin || true) && + git push --tags && (git push origin --tags || true) + SH + end + + desc "Release all gems as packages" + task :all => [:test, :commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/SECURITY.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/SECURITY.md new file mode 100644 index 0000000000..e5affc29f2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/SECURITY.md @@ -0,0 +1,35 @@ +# Reporting a security bug + +All security bugs in Sinatra should be reported to the core team through our private mailing list [sinatra-security@googlegroups.com](https://groups.google.com/group/sinatra-security). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report. + +After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours. + +If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take: + +* Contact the current security coordinator [Zachary Scott](mailto:zzak@ruby-lang.org) directly + +## Disclosure Policy + +Sinatra has a 5 step disclosure policy, that is upheld to the best of our ability. + +1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process. +2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems. +3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement. +4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions. +5. On the embargo date, the [mailing list][mailing-list] and [security list][security-list] are sent a copy of the announcement. The changes are pushed to the public repository and new gems released to rubygems. + +Typically the embargo date will be set 72 hours from the time vendor-sec is first notified, however this may vary depending on the severity of the bug or difficulty in applying a fix. + +This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner. + +## Security Updates + +Security updates will be posted on the [mailing list][mailing-list] and [security list][security-list]. + +## Comments on this Policy + +If you have any suggestions to improve this policy, please send an email the core team at [sinatrarb@googlegroups.com](https://groups.google.com/group/sinatrarb). + + +[mailing-list]: http://groups.google.com/group/sinatrarb/topics +[security-list]: http://groups.google.com/group/sinatra-security/topics diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/VERSION b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/VERSION new file mode 100644 index 0000000000..530cdd91a2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/VERSION @@ -0,0 +1 @@ +2.2.4 diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/chat.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/chat.rb new file mode 100755 index 0000000000..4d03bd1c42 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/chat.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby -I ../lib -I lib +# coding: utf-8 +require_relative 'rainbows' +require 'sinatra' +set :server, :rainbows +connections = [] + +get '/' do + halt erb(:login) unless params[:user] + erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } +end + +get '/stream', :provides => 'text/event-stream' do + stream :keep_open do |out| + connections << out + out.callback { connections.delete(out) } + end +end + +post '/' do + connections.each { |out| out << "data: #{params[:msg]}\n\n" } + 204 # response without entity body +end + +__END__ + +@@ layout + + + Super Simple Chat with Sinatra + + + + <%= yield %> + + +@@ login +
    + + + +
    + +@@ chat +
    
    +
    + +
    + + + diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.conf b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.conf new file mode 100644 index 0000000000..31742e961b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.conf @@ -0,0 +1,3 @@ +Rainbows! do + use :EventMachine +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.rb new file mode 100644 index 0000000000..895e19a2be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/rainbows.rb @@ -0,0 +1,20 @@ +require 'rainbows' + +module Rack + module Handler + class Rainbows + def self.run(app, **options) + rainbows_options = { + listeners: ["#{options[:Host]}:#{options[:Port]}"], + worker_processes: 1, + timeout: 30, + config_file: ::File.expand_path('rainbows.conf', __dir__), + } + + ::Rainbows::HttpServer.new(app, rainbows_options).start.join + end + end + + register :rainbows, ::Rack::Handler::Rainbows + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/simple.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/simple.rb new file mode 100755 index 0000000000..2697f94bf2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/simple.rb @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby -I ../lib -I lib +require 'sinatra' +get('/') { 'this is a simple app' } diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/stream.ru b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/stream.ru new file mode 100644 index 0000000000..74af0a6148 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/examples/stream.ru @@ -0,0 +1,26 @@ +# this example does *not* work properly with WEBrick +# +# run *one* of these: +# +# rackup -s mongrel stream.ru # gem install mongrel +# unicorn stream.ru # gem install unicorn +# puma stream.ru # gem install puma +# rainbows -c rainbows.conf stream.ru # gem install rainbows eventmachine + +require 'sinatra/base' + +class Stream < Sinatra::Base + get '/' do + content_type :txt + + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end + end +end + +run Stream diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra.rb new file mode 100644 index 0000000000..68261380cb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra.rb @@ -0,0 +1,3 @@ +require 'sinatra/main' + +enable :inline_templates diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/base.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/base.rb new file mode 100644 index 0000000000..9db34dee76 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/base.rb @@ -0,0 +1,2033 @@ +# coding: utf-8 +# frozen_string_literal: true + +# external dependencies +require 'rack' +require 'tilt' +require 'rack/protection' +require 'mustermann' +require 'mustermann/sinatra' +require 'mustermann/regular' + +# stdlib dependencies +require 'thread' +require 'time' +require 'uri' + +# other files we need +require 'sinatra/indifferent_hash' +require 'sinatra/show_exceptions' +require 'sinatra/version' + +module Sinatra + # The request object. See Rack::Request for more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Request + class Request < Rack::Request + HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ + HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ + + # Returns an array of acceptable media types for the response + def accept + @env['sinatra.accept'] ||= begin + if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != '' + @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS). + map! { |e| AcceptEntry.new(e) }.sort + else + [AcceptEntry.new('*/*')] + end + end + end + + def accept?(type) + preferred_type(type).to_s.include?(type) + end + + def preferred_type(*types) + return accept.first if types.empty? + types.flatten! + return types.first if accept.empty? + accept.detect do |accept_header| + type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) } + return type if type + end + end + + alias secure? ssl? + + def forwarded? + @env.include? "HTTP_X_FORWARDED_HOST" + end + + def safe? + get? or head? or options? or trace? + end + + def idempotent? + safe? or put? or delete? or link? or unlink? + end + + def link? + request_method == "LINK" + end + + def unlink? + request_method == "UNLINK" + end + + def params + super + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}" + rescue EOFError => e + raise BadRequest, "Invalid multipart/form-data: #{Rack::Utils.escape_html(e.message)}" + end + + class AcceptEntry + attr_accessor :params + attr_reader :entry + + def initialize(entry) + params = entry.scan(HEADER_PARAM).map! do |s| + key, value = s.strip.split('=', 2) + value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') + [key, value] + end + + @entry = entry + @type = entry[/[^;]+/].delete(' ') + @params = Hash[params] + @q = @params.delete('q') { 1.0 }.to_f + end + + def <=>(other) + other.priority <=> self.priority + end + + def priority + # We sort in descending order; better matches should be higher. + [ @q, -@type.count('*'), @params.size ] + end + + def to_str + @type + end + + def to_s(full = false) + full ? entry : to_str + end + + def respond_to?(*args) + super or to_str.respond_to?(*args) + end + + def method_missing(*args, &block) + to_str.send(*args, &block) + end + end + + class MimeTypeEntry + attr_reader :params + + def initialize(entry) + params = entry.scan(HEADER_PARAM).map! do |s| + key, value = s.strip.split('=', 2) + value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') + [key, value] + end + + @type = entry[/[^;]+/].delete(' ') + @params = Hash[params] + end + + def accepts?(entry) + File.fnmatch(entry, self) && matches_params?(entry.params) + end + + def to_str + @type + end + + def matches_params?(params) + return true if @params.empty? + + params.all? { |k,v| !@params.has_key?(k) || @params[k] == v } + end + end + end + + # The response object. See Rack::Response and Rack::Response::Helpers for + # more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Response + # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers + class Response < Rack::Response + DROP_BODY_RESPONSES = [204, 304] + + def body=(value) + value = value.body while Rack::Response === value + @body = String === value ? [value.to_str] : value + end + + def each + block_given? ? super : enum_for(:each) + end + + def finish + result = body + + if drop_content_info? + headers.delete "Content-Length" + headers.delete "Content-Type" + end + + if drop_body? + close + result = [] + end + + if calculate_content_length? + # if some other code has already set Content-Length, don't muck with it + # currently, this would be the static file-handler + headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s + end + + [status, headers, result] + end + + private + + def calculate_content_length? + headers["Content-Type"] and not headers["Content-Length"] and Array === body + end + + def drop_content_info? + informational? or drop_body? + end + + def drop_body? + DROP_BODY_RESPONSES.include?(status) + end + end + + # Some Rack handlers (Rainbows!) implement an extended body object protocol, however, + # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. + # This middleware will detect an extended body object and will make sure it reaches the + # handler directly. We do this here, so our middleware and middleware set up by the app will + # still be able to run. + class ExtendedRack < Struct.new(:app) + def call(env) + result, callback = app.call(env), env['async.callback'] + return result unless callback and async?(*result) + after_response { callback.call result } + setup_close(env, *result) + throw :async + end + + private + + def setup_close(env, status, headers, body) + return unless body.respond_to? :close and env.include? 'async.close' + env['async.close'].callback { body.close } + env['async.close'].errback { body.close } + end + + def after_response(&block) + raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine + EventMachine.next_tick(&block) + end + + def async?(status, headers, body) + return true if status == -1 + body.respond_to? :callback and body.respond_to? :errback + end + end + + # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing, + # if another CommonLogger is already in the middleware chain. + class CommonLogger < Rack::CommonLogger + def call(env) + env['sinatra.commonlogger'] ? @app.call(env) : super + end + + superclass.class_eval do + alias call_without_check call unless method_defined? :call_without_check + def call(env) + env['sinatra.commonlogger'] = true + call_without_check(env) + end + end + end + + class BadRequest < TypeError #:nodoc: + def http_status; 400 end + end + + class NotFound < NameError #:nodoc: + def http_status; 404 end + end + + # Methods available to routes, before/after filters, and views. + module Helpers + # Set or retrieve the response status code. + def status(value = nil) + response.status = Rack::Utils.status_code(value) if value + response.status + end + + # Set or retrieve the response body. When a block is given, + # evaluation is deferred until the body is read with #each. + def body(value = nil, &block) + if block_given? + def block.each; yield(call) end + response.body = block + elsif value + # Rack 2.0 returns a Rack::File::Iterator here instead of + # Rack::File as it was in the previous API. + unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream) + headers.delete 'Content-Length' + end + response.body = value + else + response.body + end + end + + # Halt processing and redirect to the URI provided. + def redirect(uri, *args) + if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' + status 303 + else + status 302 + end + + # According to RFC 2616 section 14.30, "the field value consists of a + # single absolute URI" + response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) + halt(*args) + end + + # Generates the absolute URI for a given path in the app. + # Takes Rack routers and reverse proxies into account. + def uri(addr = nil, absolute = true, add_script_name = true) + return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i + uri = [host = String.new] + if absolute + host << "http#{'s' if request.secure?}://" + if request.forwarded? or request.port != (request.secure? ? 443 : 80) + host << request.host_with_port + else + host << request.host + end + end + uri << request.script_name.to_s if add_script_name + uri << (addr ? addr : request.path_info).to_s + File.join uri + end + + alias url uri + alias to uri + + # Halt processing and return the error status provided. + def error(code, body = nil) + code, body = 500, code.to_str if code.respond_to? :to_str + response.body = body unless body.nil? + halt code + end + + # Halt processing and return a 404 Not Found. + def not_found(body = nil) + error 404, body + end + + # Set multiple response headers with Hash. + def headers(hash = nil) + response.headers.merge! hash if hash + response.headers + end + + # Access the underlying Rack session. + def session + request.session + end + + # Access shared logger object. + def logger + request.logger + end + + # Look up a media type by file extension in Rack's mime registry. + def mime_type(type) + Base.mime_type(type) + end + + # Set the Content-Type of the response body given a media type or file + # extension. + def content_type(type = nil, params = {}) + return response['Content-Type'] unless type + default = params.delete :default + mime_type = mime_type(type) || default + fail "Unknown media type: %p" % type if mime_type.nil? + mime_type = mime_type.dup + unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } + params[:charset] = params.delete('charset') || settings.default_encoding + end + params.delete :charset if mime_type.include? 'charset' + unless params.empty? + mime_type << (mime_type.include?(';') ? ', ' : ';') + mime_type << params.map do |key, val| + val = val.inspect if val =~ /[";,]/ + "#{key}=#{val}" + end.join(', ') + end + response['Content-Type'] = mime_type + end + + # https://html.spec.whatwg.org/#multipart-form-data + MULTIPART_FORM_DATA_REPLACEMENT_TABLE = { + '"' => '%22', + "\r" => '%0D', + "\n" => '%0A' + }.freeze + + # Set the Content-Disposition to "attachment" with the specified filename, + # instructing the user agents to prompt to save. + def attachment(filename = nil, disposition = :attachment) + response['Content-Disposition'] = disposition.to_s.dup + return unless filename + + params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)) + response['Content-Disposition'] << params + ext = File.extname(filename) + content_type(ext) unless response['Content-Type'] || ext.empty? + end + + # Use the contents of the file at +path+ as the response body. + def send_file(path, opts = {}) + if opts[:type] or not response['Content-Type'] + content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' + end + + disposition = opts[:disposition] + filename = opts[:filename] + disposition = :attachment if disposition.nil? and filename + filename = path if filename.nil? + attachment(filename, disposition) if disposition + + last_modified opts[:last_modified] if opts[:last_modified] + + file = Rack::File.new(File.dirname(settings.app_file)) + result = file.serving(request, path) + + result[1].each { |k,v| headers[k] ||= v } + headers['Content-Length'] = result[1]['Content-Length'] + opts[:status] &&= Integer(opts[:status]) + halt (opts[:status] || result[0]), result[2] + rescue Errno::ENOENT + not_found + end + + # Class of the response body in case you use #stream. + # + # Three things really matter: The front and back block (back being the + # block generating content, front the one sending it to the client) and + # the scheduler, integrating with whatever concurrency feature the Rack + # handler is using. + # + # Scheduler has to respond to defer and schedule. + class Stream + def self.schedule(*) yield end + def self.defer(*) yield end + + def initialize(scheduler = self.class, keep_open = false, &back) + @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open + @callbacks, @closed = [], false + end + + def close + return if closed? + @closed = true + @scheduler.schedule { @callbacks.each { |c| c.call } } + end + + def each(&front) + @front = front + @scheduler.defer do + begin + @back.call(self) + rescue Exception => e + @scheduler.schedule { raise e } + end + close unless @keep_open + end + end + + def <<(data) + @scheduler.schedule { @front.call(data.to_s) } + self + end + + def callback(&block) + return yield if closed? + @callbacks << block + end + + alias errback callback + + def closed? + @closed + end + end + + # Allows to start sending data to the client even though later parts of + # the response body have not yet been generated. + # + # The close parameter specifies whether Stream#close should be called + # after the block has been executed. This is only relevant for evented + # servers like Rainbows. + def stream(keep_open = false) + scheduler = env['async.callback'] ? EventMachine : Stream + current = @params.dup + body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } } + end + + # Specify response freshness policy for HTTP caches (Cache-Control header). + # Any number of non-value directives (:public, :private, :no_cache, + # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with + # a Hash of value directives (:max_age, :s_maxage). + # + # cache_control :public, :must_revalidate, :max_age => 60 + # => Cache-Control: public, must-revalidate, max-age=60 + # + # See RFC 2616 / 14.9 for more on standard cache control directives: + # http://tools.ietf.org/html/rfc2616#section-14.9.1 + def cache_control(*values) + if values.last.kind_of?(Hash) + hash = values.pop + hash.reject! { |k, v| v == false } + hash.reject! { |k, v| values << k if v == true } + else + hash = {} + end + + values.map! { |value| value.to_s.tr('_','-') } + hash.each do |key, value| + key = key.to_s.tr('_', '-') + value = value.to_i if ['max-age', 's-maxage'].include? key + values << "#{key}=#{value}" + end + + response['Cache-Control'] = values.join(', ') if values.any? + end + + # Set the Expires header and Cache-Control/max-age directive. Amount + # can be an integer number of seconds in the future or a Time object + # indicating when the response should be considered "stale". The remaining + # "values" arguments are passed to the #cache_control helper: + # + # expires 500, :public, :must_revalidate + # => Cache-Control: public, must-revalidate, max-age=500 + # => Expires: Mon, 08 Jun 2009 08:50:17 GMT + # + def expires(amount, *values) + values << {} unless values.last.kind_of?(Hash) + + if amount.is_a? Integer + time = Time.now + amount.to_i + max_age = amount + else + time = time_for amount + max_age = time - Time.now + end + + values.last.merge!(:max_age => max_age) + cache_control(*values) + + response['Expires'] = time.httpdate + end + + # Set the last modified time of the resource (HTTP 'Last-Modified' header) + # and halt if conditional GET matches. The +time+ argument is a Time, + # DateTime, or other object that responds to +to_time+. + # + # When the current request includes an 'If-Modified-Since' header that is + # equal or later than the time specified, execution is immediately halted + # with a '304 Not Modified' response. + def last_modified(time) + return unless time + time = time_for time + response['Last-Modified'] = time.httpdate + return if env['HTTP_IF_NONE_MATCH'] + + if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i + halt 304 if since >= time.to_i + end + + if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i + halt 412 if since < time.to_i + end + rescue ArgumentError + end + + ETAG_KINDS = [:strong, :weak] + # Set the response entity tag (HTTP 'ETag' header) and halt if conditional + # GET matches. The +value+ argument is an identifier that uniquely + # identifies the current version of the resource. The +kind+ argument + # indicates whether the etag should be used as a :strong (default) or :weak + # cache validator. + # + # When the current request includes an 'If-None-Match' header with a + # matching etag, execution is immediately halted. If the request method is + # GET or HEAD, a '304 Not Modified' response is sent. + def etag(value, options = {}) + # Before touching this code, please double check RFC 2616 14.24 and 14.26. + options = {:kind => options} unless Hash === options + kind = options[:kind] || :strong + new_resource = options.fetch(:new_resource) { request.post? } + + unless ETAG_KINDS.include?(kind) + raise ArgumentError, ":strong or :weak expected" + end + + value = '"%s"' % value + value = "W/#{value}" if kind == :weak + response['ETag'] = value + + if success? or status == 304 + if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource + halt(request.safe? ? 304 : 412) + end + + if env['HTTP_IF_MATCH'] + halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource + end + end + end + + # Sugar for redirect (example: redirect back) + def back + request.referer + end + + # whether or not the status is set to 1xx + def informational? + status.between? 100, 199 + end + + # whether or not the status is set to 2xx + def success? + status.between? 200, 299 + end + + # whether or not the status is set to 3xx + def redirect? + status.between? 300, 399 + end + + # whether or not the status is set to 4xx + def client_error? + status.between? 400, 499 + end + + # whether or not the status is set to 5xx + def server_error? + status.between? 500, 599 + end + + # whether or not the status is set to 404 + def not_found? + status == 404 + end + + # whether or not the status is set to 400 + def bad_request? + status == 400 + end + + # Generates a Time object from the given value. + # Used by #expires and #last_modified. + def time_for(value) + if value.is_a? Numeric + Time.at value + elsif value.respond_to? :to_s + Time.parse value.to_s + else + value.to_time + end + rescue ArgumentError => boom + raise boom + rescue Exception + raise ArgumentError, "unable to convert #{value.inspect} to a Time object" + end + + private + + # Helper method checking if a ETag value list includes the current ETag. + def etag_matches?(list, new_resource = request.post?) + return !new_resource if list == '*' + list.to_s.split(/\s*,\s*/).include? response['ETag'] + end + + def with_params(temp_params) + original, @params = @params, temp_params + yield + ensure + @params = original if original + end + end + + # Template rendering methods. Each method takes the name of a template + # to render as a Symbol and returns a String with the rendered output, + # as well as an optional hash with additional options. + # + # `template` is either the name or path of the template as symbol + # (Use `:'subdir/myview'` for views in subdirectories), or a string + # that will be rendered. + # + # Possible options are: + # :content_type The content type to use, same arguments as content_type. + # :layout If set to something falsy, no layout is rendered, otherwise + # the specified layout is used (Ignored for `sass` and `less`) + # :layout_engine Engine to use for rendering the layout. + # :locals A hash with local variables that should be available + # in the template + # :scope If set, template is evaluate with the binding of the given + # object rather than the application instance. + # :views Views directory to use. + module Templates + module ContentTyped + attr_accessor :content_type + end + + def initialize + super + @default_layout = :layout + @preferred_extension = nil + end + + def erb(template, options = {}, locals = {}, &block) + render(:erb, template, options, locals, &block) + end + + def erubis(template, options = {}, locals = {}) + warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ + "If you have Erubis installed, it will be used automatically." + render :erubis, template, options, locals + end + + def haml(template, options = {}, locals = {}, &block) + render(:haml, template, options, locals, &block) + end + + def sass(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :sass, template, options, locals + end + + def scss(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :scss, template, options, locals + end + + def less(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :less, template, options, locals + end + + def stylus(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :styl, template, options, locals + end + + def builder(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:builder, template, options, locals, &block) + end + + def liquid(template, options = {}, locals = {}, &block) + render(:liquid, template, options, locals, &block) + end + + def markdown(template, options = {}, locals = {}) + options[:exclude_outvar] = true + render :markdown, template, options, locals + end + + def textile(template, options = {}, locals = {}) + render :textile, template, options, locals + end + + def rdoc(template, options = {}, locals = {}) + render :rdoc, template, options, locals + end + + def asciidoc(template, options = {}, locals = {}) + render :asciidoc, template, options, locals + end + + def radius(template, options = {}, locals = {}) + render :radius, template, options, locals + end + + def markaby(template = nil, options = {}, locals = {}, &block) + render_ruby(:mab, template, options, locals, &block) + end + + def coffee(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :js + render :coffee, template, options, locals + end + + def nokogiri(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:nokogiri, template, options, locals, &block) + end + + def slim(template, options = {}, locals = {}, &block) + render(:slim, template, options, locals, &block) + end + + def creole(template, options = {}, locals = {}) + render :creole, template, options, locals + end + + def mediawiki(template, options = {}, locals = {}) + render :mediawiki, template, options, locals + end + + def wlang(template, options = {}, locals = {}, &block) + render(:wlang, template, options, locals, &block) + end + + def yajl(template, options = {}, locals = {}) + options[:default_content_type] = :json + render :yajl, template, options, locals + end + + def rabl(template, options = {}, locals = {}) + Rabl.register! + render :rabl, template, options, locals + end + + # Calls the given block for every possible template file in views, + # named name.ext, where ext is registered on engine. + def find_template(views, name, engine) + yield ::File.join(views, "#{name}.#{@preferred_extension}") + + Tilt.default_mapping.extensions_for(engine).each do |ext| + yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension + end + end + + private + + # logic shared between builder and nokogiri + def render_ruby(engine, template, options = {}, locals = {}, &block) + options, template = template, nil if template.is_a?(Hash) + template = Proc.new { block } if template.nil? + render engine, template, options, locals + end + + def render(engine, data, options = {}, locals = {}, &block) + # merge app-level options + engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} + options.merge!(engine_options) { |key, v1, v2| v1 } + + # extract generic options + locals = options.delete(:locals) || locals || {} + views = options.delete(:views) || settings.views || "./views" + layout = options[:layout] + layout = false if layout.nil? && options.include?(:layout) + eat_errors = layout.nil? + layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false) + layout = @default_layout if layout.nil? or layout == true + layout_options = options.delete(:layout_options) || {} + content_type = options.delete(:default_content_type) + content_type = options.delete(:content_type) || content_type + layout_engine = options.delete(:layout_engine) || engine + scope = options.delete(:scope) || self + exclude_outvar = options.delete(:exclude_outvar) + options.delete(:layout) + + # set some defaults + options[:outvar] ||= '@_out_buf' unless exclude_outvar + options[:default_encoding] ||= settings.default_encoding + + # compile and render template + begin + layout_was = @default_layout + @default_layout = false + template = compile_template(engine, data, options, views) + output = template.render(scope, locals, &block) + ensure + @default_layout = layout_was + end + + # render layout + if layout + options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). + merge!(layout_options) + catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } + end + + output.extend(ContentTyped).content_type = content_type if content_type + output + end + + def compile_template(engine, data, options, views) + eat_errors = options.delete :eat_errors + template = Tilt[engine] + raise "Template engine not found: #{engine}" if template.nil? + + case data + when Symbol + template_cache.fetch engine, data, options, views do + body, path, line = settings.templates[data] + if body + body = body.call if body.respond_to?(:call) + template.new(path, line.to_i, options) { body } + else + found = false + @preferred_extension = engine.to_s + find_template(views, data, template) do |file| + path ||= file # keep the initial path rather than the last one + if found = File.exist?(file) + path = file + break + end + end + throw :layout_missing if eat_errors and not found + template.new(path, 1, options) + end + end + when Proc + compile_block_template(template, options, &data) + when String + template_cache.fetch engine, data, options, views do + compile_block_template(template, options) { data } + end + else + raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." + end + end + + def compile_block_template(template, options, &body) + caller = settings.caller_locations.first + path = options[:path] || caller[0] + line = options[:line] || caller[1] + template.new(path, line.to_i, options, &body) + end + end + + # Base class for all Sinatra applications and middleware. + class Base + include Rack::Utils + include Helpers + include Templates + + URI_INSTANCE = URI::Parser.new + + attr_accessor :app, :env, :request, :response, :params + attr_reader :template_cache + + def initialize(app = nil, **kwargs) + super() + @app = app + @template_cache = Tilt::Cache.new + @pinned_response = nil # whether a before! filter pinned the content-type + yield self if block_given? + end + + # Rack call interface. + def call(env) + dup.call!(env) + end + + def call!(env) # :nodoc: + @env = env + @params = IndifferentHash.new + @request = Request.new(env) + @response = Response.new + @pinned_response = nil + template_cache.clear if settings.reload_templates + + invoke { dispatch! } + invoke { error_block!(response.status) } unless @env['sinatra.error'] + + unless @response['Content-Type'] + if Array === body && body[0].respond_to?(:content_type) + content_type body[0].content_type + elsif default = settings.default_content_type + content_type default + end + end + + @response.finish + end + + # Access settings defined with Base.set. + def self.settings + self + end + + # Access settings defined with Base.set. + def settings + self.class.settings + end + + def options + warn "Sinatra::Base#options is deprecated and will be removed, " \ + "use #settings instead." + settings + end + + # Exit the current block, halts any further processing + # of the request, and returns the specified response. + def halt(*response) + response = response.first if response.length == 1 + throw :halt, response + end + + # Pass control to the next matching route. + # If there are no more matching routes, Sinatra will + # return a 404 response. + def pass(&block) + throw :pass, block + end + + # Forward the request to the downstream app -- middleware only. + def forward + fail "downstream app not set" unless @app.respond_to? :call + status, headers, body = @app.call env + @response.status = status + @response.body = body + @response.headers.merge! headers + nil + end + + private + + # Run filters defined on the class and all superclasses. + # Accepts an optional block to call after each filter is applied. + def filter!(type, base = settings, &block) + filter!(type, base.superclass, &block) if base.superclass.respond_to?(:filters) + base.filters[type].each do |args| + result = process_route(*args) + block.call(result) if block_given? + end + end + + # Run routes defined on the class and all superclasses. + def route!(base = settings, pass_block = nil) + if routes = base.routes[@request.request_method] + routes.each do |pattern, conditions, block| + response.delete_header('Content-Type') unless @pinned_response + + returned_pass_block = process_route(pattern, conditions) do |*args| + env['sinatra.route'] = "#{@request.request_method} #{pattern}" + route_eval { block[*args] } + end + + # don't wipe out pass_block in superclass + pass_block = returned_pass_block if returned_pass_block + end + end + + # Run routes defined in superclass. + if base.superclass.respond_to?(:routes) + return route!(base.superclass, pass_block) + end + + route_eval(&pass_block) if pass_block + route_missing + end + + # Run a route block and throw :halt with the result. + def route_eval + throw :halt, yield + end + + # If the current request matches pattern and conditions, fill params + # with keys and call the given block. + # Revert params afterwards. + # + # Returns pass block. + def process_route(pattern, conditions, block = nil, values = []) + route = @request.path_info + route = '/' if route.empty? and not settings.empty_path_info? + route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/') + return unless params = pattern.params(route) + + params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes + force_encoding(params) + @params = @params.merge(params) if params.any? + + regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} ) + if regexp_exists + captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c } + values += captures + @params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty? + else + values += params.values.flatten + end + + catch(:pass) do + conditions.each { |c| throw :pass if c.bind(self).call == false } + block ? block[self, values] : yield(self, values) + end + rescue + @env['sinatra.error.params'] = @params + raise + ensure + params ||= {} + params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params'] + end + + # No matching route was found or all routes passed. The default + # implementation is to forward the request downstream when running + # as middleware (@app is non-nil); when no downstream app is set, raise + # a NotFound exception. Subclasses can override this method to perform + # custom route miss logic. + def route_missing + if @app + forward + else + raise NotFound, "#{request.request_method} #{request.path_info}" + end + end + + # Attempt to serve static files from public directory. Throws :halt when + # a matching file is found, returns nil otherwise. + def static!(options = {}) + return if (public_dir = settings.public_folder).nil? + path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" + return unless valid_path?(path) + + path = File.expand_path(path) + return unless path.start_with?(File.expand_path(public_dir) + '/') + return unless File.file?(path) + + env['sinatra.static_file'] = path + cache_control(*settings.static_cache_control) if settings.static_cache_control? + send_file path, options.merge(:disposition => nil) + end + + # Run the block with 'throw :halt' support and apply result to the response. + def invoke + res = catch(:halt) { yield } + + res = [res] if Integer === res or String === res + if Array === res and Integer === res.first + res = res.dup + status(res.shift) + body(res.pop) + headers(*res) + elsif res.respond_to? :each + body res + end + nil # avoid double setting the same response tuple twice + end + + # Dispatch a request with error handling. + def dispatch! + # Avoid passing frozen string in force_encoding + @params.merge!(@request.params).each do |key, val| + next unless val.respond_to?(:force_encoding) + val = val.dup if val.frozen? + @params[key] = force_encoding(val) + end + + invoke do + static! if settings.static? && (request.get? || request.head?) + filter! :before do + @pinned_response = !response['Content-Type'].nil? + end + route! + end + rescue ::Exception => boom + invoke { handle_exception!(boom) } + ensure + begin + filter! :after unless env['sinatra.static_file'] + rescue ::Exception => boom + invoke { handle_exception!(boom) } unless @env['sinatra.error'] + end + end + + # Error handling during requests. + def handle_exception!(boom) + if error_params = @env['sinatra.error.params'] + @params = @params.merge(error_params) + end + @env['sinatra.error'] = boom + + if boom.respond_to? :http_status and boom.http_status.between? 400, 599 + status(boom.http_status) + elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 + status(boom.code) + else + status(500) + end + + if server_error? + dump_errors! boom if settings.dump_errors? + raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler + elsif not_found? + headers['X-Cascade'] = 'pass' if settings.x_cascade? + end + + if res = error_block!(boom.class, boom) || error_block!(status, boom) + return res + end + + if not_found? || bad_request? + if boom.message && boom.message != boom.class.name + body Rack::Utils.escape_html(boom.message) + else + content_type 'text/html' + body '

    ' + (not_found? ? 'Not Found' : 'Bad Request') + '

    ' + end + end + + return unless server_error? + raise boom if settings.raise_errors? or settings.show_exceptions? + error_block! Exception, boom + end + + # Find an custom error block for the key(s) specified. + def error_block!(key, *block_params) + base = settings + while base.respond_to?(:errors) + next base = base.superclass unless args_array = base.errors[key] + args_array.reverse_each do |args| + first = args == args_array.first + args += [block_params] + resp = process_route(*args) + return resp unless resp.nil? && !first + end + end + return false unless key.respond_to? :superclass and key.superclass < Exception + error_block!(key.superclass, *block_params) + end + + def dump_errors!(boom) + msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") + @env['rack.errors'].puts(msg) + end + + class << self + CALLERS_TO_IGNORE = [ # :nodoc: + /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code + /lib\/tilt.*\.rb$/, # all tilt code + /^\(.*\)$/, # generated code + /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks + /active_support/, # active_support require hacks + /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks + /= 1.9.2 + /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files + ] + + # contrary to what the comment said previously, rubinius never supported this + if defined?(RUBY_IGNORE_CALLERS) + warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" + CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) + end + + attr_reader :routes, :filters, :templates, :errors + + def callers_to_ignore + CALLERS_TO_IGNORE + end + + # Removes all routes, filters, middleware and extension hooks from the + # current class (not routes/filters/... defined by its superclass). + def reset! + @conditions = [] + @routes = {} + @filters = {:before => [], :after => []} + @errors = {} + @middleware = [] + @prototype = nil + @extensions = [] + + if superclass.respond_to?(:templates) + @templates = Hash.new { |hash, key| superclass.templates[key] } + else + @templates = {} + end + end + + # Extension modules registered on this class and all superclasses. + def extensions + if superclass.respond_to?(:extensions) + (@extensions + superclass.extensions).uniq + else + @extensions + end + end + + # Middleware used in this class and all superclasses. + def middleware + if superclass.respond_to?(:middleware) + superclass.middleware + @middleware + else + @middleware + end + end + + # Sets an option to the given value. If the value is a proc, + # the proc will be called every time the option is accessed. + def set(option, value = (not_set = true), ignore_setter = false, &block) + raise ArgumentError if block and !not_set + value, not_set = block, false if block + + if not_set + raise ArgumentError unless option.respond_to?(:each) + option.each { |k,v| set(k, v) } + return self + end + + if respond_to?("#{option}=") and not ignore_setter + return __send__("#{option}=", value) + end + + setter = proc { |val| set option, val, true } + getter = proc { value } + + case value + when Proc + getter = value + when Symbol, Integer, FalseClass, TrueClass, NilClass + getter = value.inspect + when Hash + setter = proc do |val| + val = value.merge val if Hash === val + set option, val, true + end + end + + define_singleton("#{option}=", setter) + define_singleton(option, getter) + define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" + self + end + + # Same as calling `set :option, true` for each of the given options. + def enable(*opts) + opts.each { |key| set(key, true) } + end + + # Same as calling `set :option, false` for each of the given options. + def disable(*opts) + opts.each { |key| set(key, false) } + end + + # Define a custom error handler. Optionally takes either an Exception + # class, or an HTTP status code to specify which errors should be + # handled. + def error(*codes, &block) + args = compile! "ERROR", /.*/, block + codes = codes.flat_map(&method(:Array)) + codes << Exception if codes.empty? + codes << Sinatra::NotFound if codes.include?(404) + codes.each { |c| (@errors[c] ||= []) << args } + end + + # Sugar for `error(404) { ... }` + def not_found(&block) + error(404, &block) + end + + # Define a named template. The block must return the template source. + def template(name, &block) + filename, line = caller_locations.first + templates[name] = [block, filename, line.to_i] + end + + # Define the layout template. The block must return the template source. + def layout(name = :layout, &block) + template name, &block + end + + # Load embedded templates from the file; uses the caller's __FILE__ + # when no file is specified. + def inline_templates=(file = nil) + file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file + + begin + io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) + app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) + rescue Errno::ENOENT + app, data = nil + end + + if data + if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m + encoding = $2 + else + encoding = settings.default_encoding + end + lines = app.count("\n") + 1 + template = nil + force_encoding data, encoding + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*\S)\s*$/ + template = force_encoding(String.new, encoding) + templates[$1.to_sym] = [template, file, lines] + elsif template + template << line + end + end + end + end + + # Lookup or register a mime type in Rack's mime registry. + def mime_type(type, value = nil) + return type if type.nil? + return type.to_s if type.to_s.include?('/') + type = ".#{type}" unless type.to_s[0] == ?. + return Rack::Mime.mime_type(type, nil) unless value + Rack::Mime::MIME_TYPES[type] = value + end + + # provides all mime types matching type, including deprecated types: + # mime_types :html # => ['text/html'] + # mime_types :js # => ['application/javascript', 'text/javascript'] + def mime_types(type) + type = mime_type type + type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] + end + + # Define a before filter; runs before all requests within the same + # context as route handlers and may access/modify the request and + # response. + def before(path = /.*/, **options, &block) + add_filter(:before, path, **options, &block) + end + + # Define an after filter; runs after all requests within the same + # context as route handlers and may access/modify the request and + # response. + def after(path = /.*/, **options, &block) + add_filter(:after, path, **options, &block) + end + + # add a filter + def add_filter(type, path = /.*/, **options, &block) + filters[type] << compile!(type, path, block, **options) + end + + # Add a route condition. The route is considered non-matching when the + # block returns false. + def condition(name = "#{caller.first[/`.*'/]} condition", &block) + @conditions << generate_method(name, &block) + end + + def public=(value) + warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead" + set(:public_folder, value) + end + + def public_dir=(value) + self.public_folder = value + end + + def public_dir + public_folder + end + + # Defining a `GET` handler also automatically defines + # a `HEAD` handler. + def get(path, opts = {}, &block) + conditions = @conditions.dup + route('GET', path, opts, &block) + + @conditions = conditions + route('HEAD', path, opts, &block) + end + + def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end + def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end + def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end + def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end + def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end + def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end + def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end + def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end + + # Makes the methods defined in the block and in the Modules given + # in `extensions` available to the handlers and templates + def helpers(*extensions, &block) + class_eval(&block) if block_given? + include(*extensions) if extensions.any? + end + + # Register an extension. Alternatively take a block from which an + # extension will be created and registered on the fly. + def register(*extensions, &block) + extensions << Module.new(&block) if block_given? + @extensions += extensions + extensions.each do |extension| + extend extension + extension.registered(self) if extension.respond_to?(:registered) + end + end + + def development?; environment == :development end + def production?; environment == :production end + def test?; environment == :test end + + # Set configuration options for Sinatra and/or the app. + # Allows scoping of settings for certain environments. + def configure(*envs) + yield self if envs.empty? || envs.include?(environment.to_sym) + end + + # Use the specified Rack middleware + def use(middleware, *args, &block) + @prototype = nil + @middleware << [middleware, args, block] + end + ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true) + + # Stop the self-hosted server if running. + def quit! + return unless running? + # Use Thin's hard #stop! if available, otherwise just #stop. + running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop + $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages? + set :running_server, nil + set :handler_name, nil + end + + alias_method :stop!, :quit! + + # Run the Sinatra app as a self-hosted server using + # Puma, Mongrel, or WEBrick (in that order). If given a block, will call + # with the constructed handler once we have taken the stage. + def run!(options = {}, &block) + return if running? + set options + handler = Rack::Handler.pick(server) + handler_name = handler.name.gsub(/.*::/, '') + server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} + server_settings.merge!(:Port => port, :Host => bind) + + begin + start_server(handler, server_settings, handler_name, &block) + rescue Errno::EADDRINUSE + $stderr.puts "== Someone is already performing on port #{port}!" + raise + ensure + quit! + end + end + + alias_method :start!, :run! + + # Check whether the self-hosted server is running or not. + def running? + running_server? + end + + # The prototype instance used to process requests. + def prototype + @prototype ||= new + end + + # Create a new instance without middleware in front of it. + alias new! new unless method_defined? :new! + + # Create a new instance of the class fronted by its middleware + # pipeline. The object is guaranteed to respond to #call but may not be + # an instance of the class new was called on. + def new(*args, &bk) + instance = new!(*args, &bk) + Wrapper.new(build(instance).to_app, instance) + end + ruby2_keywords :new if respond_to?(:ruby2_keywords, true) + + # Creates a Rack::Builder instance with all the middleware set up and + # the given +app+ as end point. + def build(app) + builder = Rack::Builder.new + setup_default_middleware builder + setup_middleware builder + builder.run app + builder + end + + def call(env) + synchronize { prototype.call(env) } + end + + # Like Kernel#caller but excluding certain magic entries and without + # line / method information; the resulting array contains filenames only. + def caller_files + cleaned_caller(1).flatten + end + + # Like caller_files, but containing Arrays rather than strings with the + # first element being the file, and the second being the line. + def caller_locations + cleaned_caller 2 + end + + private + + # Starts the server by running the Rack Handler. + def start_server(handler, server_settings, handler_name) + # Ensure we initialize middleware before startup, to match standard Rack + # behavior, by ensuring an instance exists: + prototype + # Run the instance we created: + handler.run(self, **server_settings) do |server| + unless suppress_messages? + $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" + end + + setup_traps + set :running_server, server + set :handler_name, handler_name + server.threaded = settings.threaded if server.respond_to? :threaded= + + yield server if block_given? + end + end + + def suppress_messages? + handler_name =~ /cgi/i || quiet + end + + def setup_traps + if traps? + at_exit { quit! } + + [:INT, :TERM].each do |signal| + old_handler = trap(signal) do + quit! + old_handler.call if old_handler.respond_to?(:call) + end + end + + set :traps, false + end + end + + # Dynamically defines a method on settings. + def define_singleton(name, content = Proc.new) + singleton_class.class_eval do + undef_method(name) if method_defined? name + String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) + end + end + + # Condition for matching host name. Parameter might be String or Regexp. + def host_name(pattern) + condition { pattern === request.host } + end + + # Condition for matching user agent. Parameter should be Regexp. + # Will set params[:agent]. + def user_agent(pattern) + condition do + if request.user_agent.to_s =~ pattern + @params[:agent] = $~[1..-1] + true + else + false + end + end + end + alias_method :agent, :user_agent + + # Condition for matching mimetypes. Accepts file extensions. + def provides(*types) + types.map! { |t| mime_types(t) } + types.flatten! + condition do + if type = response['Content-Type'] + types.include? type or types.include? type[/^[^;]+/] + elsif type = request.preferred_type(types) + params = (type.respond_to?(:params) ? type.params : {}) + content_type(type, params) + true + else + false + end + end + end + + def route(verb, path, options = {}, &block) + enable :empty_path_info if path == "" and empty_path_info.nil? + signature = compile!(verb, path, block, **options) + (@routes[verb] ||= []) << signature + invoke_hook(:route_added, verb, path, block) + signature + end + + def invoke_hook(name, *args) + extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } + end + + def generate_method(method_name, &block) + define_method(method_name, &block) + method = instance_method method_name + remove_method method_name + method + end + + def compile!(verb, path, block, **options) + # Because of self.options.host + host_name(options.delete(:host)) if options.key?(:host) + # Pass Mustermann opts to compile() + route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze + + options.each_pair { |option, args| send(option, *args) } + + pattern = compile(path, route_mustermann_opts) + method_name = "#{verb} #{path}" + unbound_method = generate_method(method_name, &block) + conditions, @conditions = @conditions, [] + wrapper = block.arity != 0 ? + proc { |a, p| unbound_method.bind(a).call(*p) } : + proc { |a, p| unbound_method.bind(a).call } + + [ pattern, conditions, wrapper ] + end + + def compile(path, route_mustermann_opts = {}) + Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts)) + end + + def setup_default_middleware(builder) + builder.use ExtendedRack + builder.use ShowExceptions if show_exceptions? + builder.use Rack::MethodOverride if method_override? + builder.use Rack::Head + setup_logging builder + setup_sessions builder + setup_protection builder + end + + def setup_middleware(builder) + middleware.each { |c,a,b| builder.use(c, *a, &b) } + end + + def setup_logging(builder) + if logging? + setup_common_logger(builder) + setup_custom_logger(builder) + elsif logging == false + setup_null_logger(builder) + end + end + + def setup_null_logger(builder) + builder.use Rack::NullLogger + end + + def setup_common_logger(builder) + builder.use Sinatra::CommonLogger + end + + def setup_custom_logger(builder) + if logging.respond_to? :to_int + builder.use Rack::Logger, logging + else + builder.use Rack::Logger + end + end + + def setup_protection(builder) + return unless protection? + options = Hash === protection ? protection.dup : {} + options = { + img_src: "'self' data:", + font_src: "'self'" + }.merge options + + protect_session = options.fetch(:session) { sessions? } + options[:without_session] = !protect_session + + options[:reaction] ||= :drop_session + + builder.use Rack::Protection, options + end + + def setup_sessions(builder) + return unless sessions? + options = {} + options[:secret] = session_secret if session_secret? + options.merge! sessions.to_hash if sessions.respond_to? :to_hash + builder.use session_store, options + end + + def inherited(subclass) + subclass.reset! + subclass.set :app_file, caller_files.first unless subclass.app_file? + super + end + + @@mutex = Mutex.new + def synchronize(&block) + if lock? + @@mutex.synchronize(&block) + else + yield + end + end + + # used for deprecation warnings + def warn(message) + super message + "\n\tfrom #{cleaned_caller.first.join(':')}" + end + + # Like Kernel#caller but excluding certain magic entries + def cleaned_caller(keep = 3) + caller(1). + map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. + reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } } + end + end + + # Force data to specified encoding. It defaults to settings.default_encoding + # which is UTF-8 by default + def self.force_encoding(data, encoding = default_encoding) + return if data == settings || data.is_a?(Tempfile) + if data.respond_to? :force_encoding + data.force_encoding(encoding).encode! + elsif data.respond_to? :each_value + data.each_value { |v| force_encoding(v, encoding) } + elsif data.respond_to? :each + data.each { |v| force_encoding(v, encoding) } + end + data + end + + def force_encoding(*args) settings.force_encoding(*args) end + + reset! + + set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym + set :raise_errors, Proc.new { test? } + set :dump_errors, Proc.new { !test? } + set :show_exceptions, Proc.new { development? } + set :sessions, false + set :session_store, Rack::Session::Cookie + set :logging, false + set :protection, true + set :method_override, false + set :use_code, false + set :default_encoding, "utf-8" + set :x_cascade, true + set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } + settings.add_charset << /^text\// + set :mustermann_opts, {} + set :default_content_type, 'text/html' + + # explicitly generating a session secret eagerly to play nice with preforking + begin + require 'securerandom' + set :session_secret, SecureRandom.hex(64) + rescue LoadError, NotImplementedError + # SecureRandom raises a NotImplementedError if no random device is available + set :session_secret, "%064x" % Kernel.rand(2**256-1) + end + + class << self + alias_method :methodoverride?, :method_override? + alias_method :methodoverride=, :method_override= + end + + set :run, false # start server via at-exit hook? + set :running_server, nil + set :handler_name, nil + set :traps, true + set :server, %w[HTTP webrick] + set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } + set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) + set :quiet, false + + ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + + if ruby_engine == 'macruby' + server.unshift 'control_tower' + else + server.unshift 'reel' + server.unshift 'puma' + server.unshift 'mongrel' if ruby_engine.nil? + server.unshift 'thin' if ruby_engine != 'jruby' + server.unshift 'trinidad' if ruby_engine == 'jruby' + end + + set :absolute_redirects, true + set :prefixed_redirects, false + set :empty_path_info, nil + set :strict_paths, true + + set :app_file, nil + set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } + set :views, Proc.new { root && File.join(root, 'views') } + set :reload_templates, Proc.new { development? } + set :lock, false + set :threaded, true + + set :public_folder, Proc.new { root && File.join(root, 'public') } + set :static, Proc.new { public_folder && File.exist?(public_folder) } + set :static_cache_control, false + + error ::Exception do + response.status = 500 + content_type 'text/html' + '

    Internal Server Error

    ' + end + + configure :development do + get '/__sinatra__/:image.png' do + filename = __dir__ + "/images/#{params[:image].to_i}.png" + content_type :png + send_file filename + end + + error NotFound do + content_type 'text/html' + + if self.class == Sinatra::Application + code = <<-RUBY.gsub(/^ {12}/, '') + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + RUBY + else + code = <<-RUBY.gsub(/^ {12}/, '') + class #{self.class} + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + end + RUBY + + file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') + code = "# in #{file}\n#{code}" unless file.empty? + end + + (<<-HTML).gsub(/^ {10}/, '') + + + + + + +

    Sinatra doesn’t know this ditty.

    + +
    + Try this: +
    #{Rack::Utils.escape_html(code)}
    +
    + + + HTML + end + end + end + + # Execution context for classic style (top-level) applications. All + # DSL methods executed on main are delegated to this class. + # + # The Application class should not be subclassed, unless you want to + # inherit all settings, routes, handlers, and error pages from the + # top-level. Subclassing Sinatra::Base is highly recommended for + # modular applications. + class Application < Base + set :logging, Proc.new { !test? } + set :method_override, true + set :run, Proc.new { !test? } + set :app_file, nil + + def self.register(*extensions, &block) #:nodoc: + added_methods = extensions.flat_map(&:public_instance_methods) + Delegator.delegate(*added_methods) + super(*extensions, &block) + end + end + + # Sinatra delegation mixin. Mixing this module into an object causes all + # methods to be delegated to the Sinatra::Application class. Used primarily + # at the top-level. + module Delegator #:nodoc: + def self.delegate(*methods) + methods.each do |method_name| + define_method(method_name) do |*args, &block| + return super(*args, &block) if respond_to? method_name + Delegator.target.send(method_name, *args, &block) + end + # ensure keyword argument passing is compatible with ruby >= 2.7 + ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true) + private method_name + end + end + + delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, + :template, :layout, :before, :after, :error, :not_found, :configure, + :set, :mime_type, :enable, :disable, :use, :development?, :test?, + :production?, :helpers, :settings, :register + + class << self + attr_accessor :target + end + + self.target = Application + end + + class Wrapper + def initialize(stack, instance) + @stack, @instance = stack, instance + end + + def settings + @instance.settings + end + + def helpers + @instance + end + + def call(env) + @stack.call(env) + end + + def inspect + "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" + end + end + + # Create a new Sinatra application; the block is evaluated in the class scope. + def self.new(base = Base, &block) + base = Class.new(base) + base.class_eval(&block) if block_given? + base + end + + # Extend the top-level DSL with the modules provided. + def self.register(*extensions, &block) + Delegator.target.register(*extensions, &block) + end + + # Include the helper modules provided in Sinatra's request context. + def self.helpers(*extensions, &block) + Delegator.target.helpers(*extensions, &block) + end + + # Use the middleware for classic applications. + def self.use(*args, &block) + Delegator.target.use(*args, &block) + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/404.png b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/404.png new file mode 100644 index 0000000000..f16a914ff2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/404.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/500.png b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/500.png new file mode 100644 index 0000000000..e08b17d9e6 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/images/500.png differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/indifferent_hash.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/indifferent_hash.rb new file mode 100644 index 0000000000..89b348fd86 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/indifferent_hash.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true +$stderr.puts <:foo and "foo" are + # considered to be the same. + # + # rgb = Sinatra::IndifferentHash.new + # + # rgb[:black] = '#000000' # symbol assignment + # rgb[:black] # => '#000000' # symbol retrieval + # rgb['black'] # => '#000000' # string retrieval + # + # rgb['white'] = '#FFFFFF' # string assignment + # rgb[:white] # => '#FFFFFF' # symbol retrieval + # rgb['white'] # => '#FFFFFF' # string retrieval + # + # Internally, symbols are mapped to strings when used as keys in the entire + # writing interface (calling e.g. []=, merge). This mapping + # belongs to the public interface. For example, given: + # + # hash = Sinatra::IndifferentHash.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 = Sinatra::IndifferentHash.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 Sinatra. + class IndifferentHash < Hash + def self.[](*args) + new.merge!(Hash[*args]) + end + + def initialize(*args) + args.map!(&method(:convert_value)) + + super(*args) + end + + def default(*args) + args.map!(&method(:convert_key)) + + super(*args) + end + + def default=(value) + super(convert_value(value)) + end + + def assoc(key) + super(convert_key(key)) + end + + def rassoc(value) + super(convert_value(value)) + end + + def fetch(key, *args) + args.map!(&method(:convert_value)) + + super(convert_key(key), *args) + end + + def [](key) + super(convert_key(key)) + end + + def []=(key, value) + super(convert_key(key), convert_value(value)) + end + + alias_method :store, :[]= + + def key(value) + super(convert_value(value)) + end + + def key?(key) + super(convert_key(key)) + end + + alias_method :has_key?, :key? + alias_method :include?, :key? + alias_method :member?, :key? + + def value?(value) + super(convert_value(value)) + end + + alias_method :has_value?, :value? + + def delete(key) + super(convert_key(key)) + end + + def dig(key, *other_keys) + super(convert_key(key), *other_keys) + end if method_defined?(:dig) # Added in Ruby 2.3 + + def fetch_values(*keys) + keys.map!(&method(:convert_key)) + + super(*keys) + end if method_defined?(:fetch_values) # Added in Ruby 2.3 + + def slice(*keys) + keys.map!(&method(:convert_key)) + + self.class[super(*keys)] + end if method_defined?(:slice) # Added in Ruby 2.5 + + def values_at(*keys) + keys.map!(&method(:convert_key)) + + super(*keys) + end + + def merge!(*other_hashes) + other_hashes.each do |other_hash| + if other_hash.is_a?(self.class) + super(other_hash) + else + other_hash.each_pair do |key, value| + key = convert_key(key) + value = yield(key, self[key], value) if block_given? && key?(key) + self[key] = convert_value(value) + end + end + end + + self + end + + alias_method :update, :merge! + + def merge(*other_hashes, &block) + dup.merge!(*other_hashes, &block) + end + + def replace(other_hash) + super(other_hash.is_a?(self.class) ? other_hash : self.class[other_hash]) + end + + if method_defined?(:transform_values!) # Added in Ruby 2.4 + def transform_values(&block) + dup.transform_values!(&block) + end + + def transform_values! + super + super(&method(:convert_value)) + end + end + + if method_defined?(:transform_keys!) # Added in Ruby 2.5 + def transform_keys(&block) + dup.transform_keys!(&block) + end + + def transform_keys! + super + super(&method(:convert_key)) + end + 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 compact + dup.tap(&:compact!) + end if method_defined?(:compact) # Added in Ruby 2.4 + + private + + def convert_key(key) + key.is_a?(Symbol) ? key.to_s : key + end + + def convert_value(value) + case value + when Hash + value.is_a?(self.class) ? value : self.class[value] + when Array + value.map(&method(:convert_value)) + else + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/main.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/main.rb new file mode 100644 index 0000000000..e4231c30f9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/main.rb @@ -0,0 +1,54 @@ +module Sinatra + ParamsConfig = {} + + if ARGV.any? + require 'optparse' + parser = OptionParser.new { |op| + op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) } + op.on('-s server', 'specify rack server/handler') { |val| ParamsConfig[:server] = val } + op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true } + op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true } + op.on('-e env', 'set the environment (default is development)') do |val| + ENV['RACK_ENV'] = val + ParamsConfig[:environment] = val.to_sym + end + op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val| + ParamsConfig[:bind] = val + end + } + begin + parser.parse!(ARGV.dup) + rescue => evar + ParamsConfig[:optparse_error] = evar + end + end + + require 'sinatra/base' + + class Application < Base + + # we assume that the first file that requires 'sinatra' is the + # app_file. all other path related options are calculated based + # on this path by default. + set :app_file, caller_files.first || $0 + + set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } + + if run? && ARGV.any? + error = ParamsConfig.delete(:optparse_error) + raise error if error + ParamsConfig.each { |k, v| set k, v } + end + end + + remove_const(:ParamsConfig) + at_exit { Application.run! if $!.nil? && Application.run? } +end + +# include would include the module in Object +# extend only extends the `main` object +extend Sinatra::Delegator + +class Rack::Builder + include Sinatra::Delegator +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/show_exceptions.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/show_exceptions.rb new file mode 100644 index 0000000000..de468c0a4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/show_exceptions.rb @@ -0,0 +1,362 @@ +# frozen_string_literal: true + +require 'rack/show_exceptions' + +module Sinatra + # Sinatra::ShowExceptions catches all exceptions raised from the app it + # wraps. It shows a useful backtrace with the sourcefile and clickable + # context, the whole Rack environment and the request data. + # + # Be careful when you use this on public-facing sites as it could reveal + # information helpful to attackers. + class ShowExceptions < Rack::ShowExceptions + @@eats_errors = Object.new + def @@eats_errors.flush(*) end + def @@eats_errors.puts(*) end + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + rescue Exception => e + errors, env["rack.errors"] = env["rack.errors"], @@eats_errors + + if prefers_plain_text?(env) + content_type = "text/plain" + body = dump_exception(e) + else + content_type = "text/html" + body = pretty(env, e) + end + + env["rack.errors"] = errors + + [ + 500, + { + "Content-Type" => content_type, + "Content-Length" => body.bytesize.to_s + }, + [body] + ] + end + + def template + TEMPLATE + end + + private + + def bad_request?(e) + Sinatra::BadRequest === e + end + + def prefers_plain_text?(env) + !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && + [/curl/].index { |item| item =~ env["HTTP_USER_AGENT"] } + end + + def frame_class(frame) + if frame.filename =~ %r{lib/sinatra.*\.rb} + "framework" + elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || + frame.filename =~ %r{/bin/(\w+)\z} + "system" + else + "app" + end + end + +TEMPLATE = ERB.new <<-HTML # :nodoc: + + + + + <%=h exception.class %> at <%=h path %> + + + + + + +
    + + +
    +

    BACKTRACE

    +

    (expand)

    + +
    + +
      + + <% id = 1 %> + <% frames.each do |frame| %> + <% if frame.context_line && frame.context_line != "#" %> + +
    • + <%=h frame.filename %> in + <%=h frame.function %> +
    • + +
    • + <% if frame.pre_context %> +
        + <% frame.pre_context.each do |line| %> +
      1. <%=h line %>
      2. + <% end %> +
      + <% end %> + +
        +
      1. <%= + h frame.context_line %>
      2. +
      + + <% if frame.post_context %> +
        + <% frame.post_context.each do |line| %> +
      1. <%=h line %>
      2. + <% end %> +
      + <% end %> +
      +
    • + + <% end %> + + <% id += 1 %> + <% end %> + +
    +
    + + <% unless bad_request?(exception) %> +
    +

    GET

    + <% if req.GET and not req.GET.empty? %> + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No GET data.

    + <% end %> +
    +
    + +
    +

    POST

    + <% if req.POST and not req.POST.empty? %> + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No POST data.

    + <% end %> +
    +
    + <% end %> + +
    + + <% unless req.cookies.empty? %> + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No cookie data.

    + <% end %> +
    +
    + +
    +

    Rack ENV

    + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> +
    VariableValue
    <%=h key %>
    <%=h val %>
    +
    +
    + +

    You're seeing this error because you have +enabled the show_exceptions setting.

    +
    + + +HTML + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/version.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/version.rb new file mode 100644 index 0000000000..d7b67c9d85 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/lib/sinatra/version.rb @@ -0,0 +1,3 @@ +module Sinatra + VERSION = '2.2.3' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/sinatra.gemspec b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/sinatra.gemspec new file mode 100644 index 0000000000..cfa4324c79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-2.2.4/sinatra.gemspec @@ -0,0 +1,49 @@ +version = File.read(File.expand_path("../VERSION", __FILE__)).strip + +Gem::Specification.new 'sinatra', version do |s| + s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." + s.summary = "Classy web-development dressed in a DSL" + s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] + s.email = "sinatrarb@googlegroups.com" + s.homepage = "http://sinatrarb.com/" + s.license = 'MIT' + s.files = Dir['README*.md', 'lib/**/*', 'examples/*'] + [ + ".yardopts", + "AUTHORS.md", + "CHANGELOG.md", + "CONTRIBUTING.md", + "Gemfile", + "LICENSE", + "MAINTENANCE.md", + "Rakefile", + "SECURITY.md", + "sinatra.gemspec", + "VERSION"] + s.extra_rdoc_files = %w[README.md LICENSE] + s.rdoc_options = %w[--line-numbers --title Sinatra --main README.rdoc --encoding=UTF-8] + + if s.respond_to?(:metadata) + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra', + 'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md', + 'homepage_uri' => 'http://sinatrarb.com/', + 'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues', + 'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb', + 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra' + } + else + raise <<-EOF +RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: + gem install rubygems-update + update_rubygems: + gem update --system +EOF + end + + s.required_ruby_version = '>= 2.3.0' + + s.add_dependency 'rack', '~> 2.2' + s.add_dependency 'tilt', '~> 2.0' + s.add_dependency 'rack-protection', version + s.add_dependency 'mustermann', '~> 2.0' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/LICENSE new file mode 100644 index 0000000000..87f78e3874 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Janko Marohnić + +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.0.0/gems/sinatra-activerecord-2.0.23/README.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/README.md new file mode 100644 index 0000000000..9a2b2fcb38 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/README.md @@ -0,0 +1,157 @@ +# Sinatra ActiveRecord Extension + +Extends [Sinatra](http://www.sinatrarb.com/) with extension methods and Rake +tasks for dealing with an SQL database using the +[ActiveRecord ORM](https://github.com/rails/rails/tree/master/activerecord). + +![test badge](https://github.com/sinatra-activerecord/sinatra-activerecord/workflows/rspec/badge.svg) + +## Requirement +ActiveRecord >= 4.1 + +## Setup + +Put it in your `Gemfile`, along with the adapter of your database. For +simplicity, let's assume you're using SQLite: + +```ruby +gem "sinatra-activerecord" +gem "sqlite3" +gem "rake" +``` + +Now require it in your Sinatra application, and establish the database +connection: + +```ruby +# app.rb +require "sinatra/activerecord" + +set :database, {adapter: "sqlite3", database: "foo.sqlite3"} +# or set :database_file, "path/to/database.yml" +``` + +If you have a `config/database.yml`, it will automatically be loaded, no need +to specify it. Also, in production, the `$DATABASE_URL` environment variable +will automatically be read as the database (if you haven't specified otherwise). + +If both `config/database.yml` and `$DATABASE_URL` are present, the database configuration of this two will be merged, with $DATABASE_URL's variable taking precedence over database.yml (for the same variable / key). + +Note that in **modular** Sinatra applications you will need to first register +the extension: + +```ruby +class YourApplication < Sinatra::Base + register Sinatra::ActiveRecordExtension +end +``` + +Now require the rake tasks and your app in your `Rakefile`: + +```ruby +# Rakefile +require "sinatra/activerecord/rake" + +namespace :db do + task :load_config do + require "./app" + end +end +``` + +In the Terminal test that it works: + +```sh +$ bundle exec rake -T +rake db:create # Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config) +rake db:create_migration # Create a migration (parameters: NAME, VERSION) +rake db:drop # Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases) +rake db:fixtures:load # Load fixtures into the current environment's database +rake db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false) +rake db:migrate:status # Display status of migrations +rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n) +rake db:schema:dump # Create a db/schema.rb file that can be portably used against any DB supported by AR +rake db:schema:load # Load a schema.rb file into the database +rake db:seed # Load the seed data from db/seeds.rb +rake db:setup # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first) +rake db:structure:dump # Dump the database structure to db/structure.sql +rake db:version # Retrieves the current schema version number +``` + +And that's it, you're all set :) + +## Usage + +You can create a migration: + +```sh +$ bundle exec rake db:create_migration NAME=create_users +``` + +This will create a migration file in your migrations directory (`./db/migrate` +by default), ready for editing. + +```ruby +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :name + end + end +end +``` + +Now migrate the database: + +```sh +$ bundle exec rake db:migrate +``` + +You can also write models: + +```ruby +class User < ActiveRecord::Base + validates_presence_of :name +end +``` + +You can put your models anywhere you want, only remember to require them if +they're in a separate file, and that they're loaded after `require "sinatra/activerecord"`. + +Now everything just works: + +```ruby +get '/users' do + @users = User.all + erb :index +end + +get '/users/:id' do + @user = User.find(params[:id]) + erb :show +end +``` + +A nice thing is that the `ActiveRecord::Base` class is available to +you through the `database` variable: + +```ruby +if database.table_exists?('users') + # Do stuff +else + raise "The table 'users' doesn't exist." +end +``` + +## History + +This gem was made in 2009 by Blake Mizerany, creator of Sinatra. + +## Social + +You can follow Janko on Twitter, [@jankomarohnic](http://twitter.com/jankomarohnic). +You can follow Axel on Twitter, [@soulchildpls](http://twitter.com/soulchildpls). + +## License + +[MIT](https://github.com/sinatra-activerecord/sinatra-activerecord/blob/master/LICENSE) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord.rb new file mode 100644 index 0000000000..4012f12781 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord.rb @@ -0,0 +1,99 @@ +require 'sinatra/base' +require 'active_record' +require 'active_support/core_ext/hash/keys' + +require 'logger' +require 'pathname' +require 'yaml' +require 'erb' + +require 'active_record/database_configurations/connection_url_resolver' if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.1') + +module Sinatra + module ActiveRecordHelper + def database + settings.database + end + end + + module ActiveRecordExtension + def self.registered(app) + if ENV['DATABASE_URL'] && File.exist?("#{Dir.pwd}/config/database.yml") + path = "#{Dir.pwd}/config/database.yml" + url = ENV['DATABASE_URL'] + file_path = File.join(root, path) if Pathname(path).relative? and root + file_spec = YAML.load(ERB.new(File.read(path)).result) || {} + + # ActiveRecord 6.1+ has moved the connection url resolver to another module + if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.1') + url_spec = ActiveRecord::DatabaseConfigurations::ConnectionUrlResolver.new(url).to_hash + else + url_spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash + end + + # if the configuration concerns only one database, and url_spec exist, url_spec will override the same key + # if the configuration has multiple databases (Rails 6.0+ feature), url_spec is discarded + # Following Active Record config convention + # https://github.com/rails/rails/blob/main/activerecord/lib/active_record/database_configurations.rb#L169 + final_spec = file_spec.keys.map do |env| + config = file_spec[env] + if config.is_a?(Hash) && config.all? { |_k, v| v.is_a?(Hash) } + [env, config] + else + [env, config.merge(url_spec)] + end + end.to_h + + app.set :database, final_spec + elsif ENV['DATABASE_URL'] + app.set :database, ENV['DATABASE_URL'] + elsif File.exist?("#{Dir.pwd}/config/database.yml") + app.set :database_file, "#{Dir.pwd}/config/database.yml" + end + + unless defined?(Rake) || [:test, :production].include?(app.settings.environment) + ActiveRecord::Base.logger = Logger.new(STDOUT) + end + + app.helpers ActiveRecordHelper + + # re-connect if database connection dropped (Rails 3 only) + app.before { ActiveRecord::Base.verify_active_connections! if ActiveRecord::Base.respond_to?(:verify_active_connections!) } + app.after { ActiveRecord::Base.clear_active_connections! } + end + + def database_file=(path) + path = File.join(root, path) if Pathname(path).relative? and root + spec = YAML.load(ERB.new(File.read(path)).result) || {} + set :database, spec + end + + def database=(spec) + if spec.is_a?(Hash) and spec.symbolize_keys[environment.to_sym] + ActiveRecord::Base.configurations = spec.stringify_keys + ActiveRecord::Base.establish_connection(environment.to_sym) + elsif spec.is_a?(Hash) + ActiveRecord::Base.configurations = { + environment.to_s => spec.stringify_keys + } + + ActiveRecord::Base.establish_connection(spec.stringify_keys) + else + if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.0') + ActiveRecord::Base.configurations ||= ActiveRecord::DatabaseConfigurations.new({}).resolve(spec) + else + ActiveRecord::Base.configurations ||= {} + ActiveRecord::Base.configurations[environment.to_s] = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(spec).to_hash + end + + ActiveRecord::Base.establish_connection(spec) + end + end + + def database + ActiveRecord::Base + end + end + + register ActiveRecordExtension +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake.rb new file mode 100644 index 0000000000..a0e1c76c0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake.rb @@ -0,0 +1,6 @@ +load "active_record/railties/databases.rake" +require "sinatra/activerecord/rake/activerecord_#{ActiveRecord::VERSION::MAJOR}" + +load "sinatra/activerecord/tasks.rake" + +ActiveRecord::Base.logger = nil diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_3.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_3.rb new file mode 100644 index 0000000000..33dd3bddc5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_3.rb @@ -0,0 +1,31 @@ +require "active_support/string_inquirer" + +module Rails + extend self + + def root + Pathname.new(Rake.application.original_dir) + end + + def env + ActiveSupport::StringInquirer.new(ENV["APP_ENV"] || ENV["RACK_ENV"] || "development") + end + + def application + seed_loader = Object.new + seed_loader.instance_eval do + def load_seed + load "db/seeds.rb" + end + end + seed_loader + end +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task.define_task("db:rails_env") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_4.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_4.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_4.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_5.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_5.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_5.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_6.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_6.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/rake/activerecord_6.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/tasks.rake b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/tasks.rake new file mode 100644 index 0000000000..73f66e3d92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.23/lib/sinatra/activerecord/tasks.rake @@ -0,0 +1,60 @@ +require "active_support/core_ext/string/strip" +require "pathname" +require "fileutils" + +namespace :db do + desc "Create a migration (parameters: NAME, VERSION)" + task :create_migration do + ARGV.each do |a| + # when we run 'rake db:create_migration create_users v1', + # rake will also run 'rake create_users' and 'rake v1' + # to avoid rake abort, we define an empty method for these (ie: "task :create_users do ; end") + next if a.nil? + task a.to_sym do ; end + end + + unless ENV["NAME"] || ARGV[1] + puts "No NAME specified. Example usage: `rake db:create_migration NAME=create_users`" + exit + end + + name = ENV["NAME"] || ARGV[1] + version = ENV["VERSION"] || ARGV[2] || Time.now.utc.strftime("%Y%m%d%H%M%S") + + ActiveRecord::Migrator.migrations_paths.each do |directory| + next unless File.exist?(directory) + migration_files = Pathname(directory).children + if duplicate = migration_files.find { |path| path.basename.to_s.include?(name) } + puts "Another migration is already named \"#{name}\": #{duplicate}." + exit + end + end + + filename = "#{version}_#{name}.rb" + dirname = ActiveRecord::Migrator.migrations_paths.first + path = File.join(dirname, filename) + ar_maj = ActiveRecord::VERSION::MAJOR + ar_min = ActiveRecord::VERSION::MINOR + base = "ActiveRecord::Migration" + base += "[#{ar_maj}.#{ar_min}]" if ar_maj >= 5 + + FileUtils.mkdir_p(dirname) + File.write path, <<-MIGRATION.strip_heredoc + class #{name.camelize} < #{base} + def change + end + end + MIGRATION + + puts path + end +end + +# The `db:create` and `db:drop` command won't work with a DATABASE_URL because +# the `db:load_config` command tries to connect to the DATABASE_URL, which either +# doesn't exist or isn't able to drop the database. Ignore loading the configs for +# these tasks if a `DATABASE_URL` is present. +if ENV.has_key? "DATABASE_URL" + Rake::Task["db:create"].prerequisites.delete("load_config") + Rake::Task["db:drop"].prerequisites.delete("load_config") +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/LICENSE new file mode 100644 index 0000000000..87f78e3874 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Janko Marohnić + +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.0.0/gems/sinatra-activerecord-2.0.26/README.md b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/README.md new file mode 100644 index 0000000000..e744d6f792 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/README.md @@ -0,0 +1,161 @@ +# Sinatra ActiveRecord Extension + +Extends [Sinatra](http://www.sinatrarb.com/) with extension methods and Rake +tasks for dealing with an SQL database using the +[ActiveRecord ORM](https://github.com/rails/rails/tree/master/activerecord). + +![test badge](https://github.com/sinatra-activerecord/sinatra-activerecord/workflows/rspec/badge.svg) + +## Requirement +ActiveRecord >= 4.1 + +## Setup + +Put it in your `Gemfile`, along with the adapter of your database. For +simplicity, let's assume you're using SQLite: + +```ruby +gem "sinatra-activerecord" +gem "sqlite3" +gem "rake" +``` + +Now require it in your Sinatra application, and establish the database +connection: + +```ruby +# app.rb +require "sinatra/activerecord" + +set :database, {adapter: "sqlite3", database: "foo.sqlite3"} +# or set :database_file, "path/to/database.yml" +``` + +If you have a `config/database.yml`, it will automatically be loaded, no need +to specify it. Also, in production, the `$DATABASE_URL` environment variable +will automatically be read as the database (if you haven't specified otherwise). + +If both `config/database.yml` and `$DATABASE_URL` are present, the database configuration of this two will be merged, with $DATABASE_URL's variable taking precedence over database.yml (for the same variable / key). + + +Note: If you are using ActiveRecord 6.0 and above, and have [defined multiple databases](https://guides.rubyonrails.org/active_record_multiple_databases.html#setting-up-your-application) for the database.yml, the $DATABASE_URL configuration will be discarded, following [Active Record convention here](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/database_configurations.rb#L169). + + +Note that in **modular** Sinatra applications you will need to first register +the extension: + +```ruby +class YourApplication < Sinatra::Base + register Sinatra::ActiveRecordExtension +end +``` + +Now require the rake tasks and your app in your `Rakefile`: + +```ruby +# Rakefile +require "sinatra/activerecord/rake" + +namespace :db do + task :load_config do + require "./app" + end +end +``` + +In the Terminal test that it works: + +```sh +$ bundle exec rake -T +rake db:create # Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config) +rake db:create_migration # Create a migration (parameters: NAME, VERSION) +rake db:drop # Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases) +rake db:fixtures:load # Load fixtures into the current environment's database +rake db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false) +rake db:migrate:status # Display status of migrations +rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n) +rake db:schema:dump # Create a db/schema.rb file that can be portably used against any DB supported by AR +rake db:schema:load # Load a schema.rb file into the database +rake db:seed # Load the seed data from db/seeds.rb +rake db:setup # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first) +rake db:structure:dump # Dump the database structure to db/structure.sql +rake db:version # Retrieves the current schema version number +``` + +And that's it, you're all set :) + +## Usage + +You can create a migration: + +```sh +$ bundle exec rake db:create_migration NAME=create_users +``` + +This will create a migration file in your migrations directory (`./db/migrate` +by default), ready for editing. + +```ruby +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :name + end + end +end +``` + +Now migrate the database: + +```sh +$ bundle exec rake db:migrate +``` + +You can also write models: + +```ruby +class User < ActiveRecord::Base + validates_presence_of :name +end +``` + +You can put your models anywhere you want, only remember to require them if +they're in a separate file, and that they're loaded after `require "sinatra/activerecord"`. + +Now everything just works: + +```ruby +get '/users' do + @users = User.all + erb :index +end + +get '/users/:id' do + @user = User.find(params[:id]) + erb :show +end +``` + +A nice thing is that the `ActiveRecord::Base` class is available to +you through the `database` variable: + +```ruby +if database.table_exists?('users') + # Do stuff +else + raise "The table 'users' doesn't exist." +end +``` + +## History + +This gem was made in 2009 by Blake Mizerany, creator of Sinatra. + +## Social + +You can follow Janko on Twitter, [@jankomarohnic](http://twitter.com/jankomarohnic). +You can follow Axel on Twitter, [@soulchildpls](http://twitter.com/soulchildpls). + +## License + +[MIT](https://github.com/sinatra-activerecord/sinatra-activerecord/blob/master/LICENSE) diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord.rb new file mode 100644 index 0000000000..a4658b7330 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord.rb @@ -0,0 +1,102 @@ +require 'sinatra/base' +require 'active_record' +require 'active_support/core_ext/hash/keys' + +require 'logger' +require 'pathname' +require 'yaml' +require 'erb' + +require 'active_record/database_configurations/connection_url_resolver' if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.1') + +module Sinatra + module ActiveRecordHelper + def database + settings.database + end + end + + module ActiveRecordExtension + def self.registered(app) + if ENV['DATABASE_URL'] && File.exist?("#{Dir.pwd}/config/database.yml") + path = "#{Dir.pwd}/config/database.yml" + url = ENV['DATABASE_URL'] + source = ERB.new(File.read(path)).result + file_spec = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(source) : YAML.load(source) + file_spec ||= {} + + # ActiveRecord 6.1+ has moved the connection url resolver to another module + if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.1') + url_spec = ActiveRecord::DatabaseConfigurations::ConnectionUrlResolver.new(url).to_hash + else + url_spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash + end + + # if the configuration concerns only one database, and url_spec exist, url_spec will override the same key + # if the configuration has multiple databases (Rails 6.0+ feature), url_spec is discarded + # Following Active Record config convention + # https://github.com/rails/rails/blob/main/activerecord/lib/active_record/database_configurations.rb#L169 + final_spec = file_spec.keys.map do |env| + config = file_spec[env] + if config.is_a?(Hash) && config.all? { |_k, v| v.is_a?(Hash) } + [env, config] + else + [env, config.merge(url_spec)] + end + end.to_h + + app.set :database, final_spec + elsif ENV['DATABASE_URL'] + app.set :database, ENV['DATABASE_URL'] + elsif File.exist?("#{Dir.pwd}/config/database.yml") + app.set :database_file, "#{Dir.pwd}/config/database.yml" + end + + unless defined?(Rake) || [:test, :production].include?(app.settings.environment) + ActiveRecord::Base.logger = Logger.new(STDOUT) + end + + app.helpers ActiveRecordHelper + + # re-connect if database connection dropped (Rails 3 only) + app.before { ActiveRecord::Base.verify_active_connections! if ActiveRecord::Base.respond_to?(:verify_active_connections!) } + app.after { ActiveRecord::Base.clear_active_connections! } + end + + def database_file=(path) + path = File.join(root, path) if Pathname(path).relative? and root + source = ERB.new(File.read(path)).result + spec = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(source) : YAML.load(source) + spec ||= {} + set :database, spec + end + + def database=(spec) + if spec.is_a?(Hash) and spec.symbolize_keys[environment.to_sym] + ActiveRecord::Base.configurations = spec.stringify_keys + ActiveRecord::Base.establish_connection(environment.to_sym) + elsif spec.is_a?(Hash) + ActiveRecord::Base.configurations = { + environment.to_s => spec.stringify_keys + } + + ActiveRecord::Base.establish_connection(spec.stringify_keys) + else + if Gem.loaded_specs["activerecord"].version >= Gem::Version.create('6.0') + ActiveRecord::Base.configurations ||= ActiveRecord::DatabaseConfigurations.new({}).resolve(spec) + else + ActiveRecord::Base.configurations ||= {} + ActiveRecord::Base.configurations[environment.to_s] = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(spec).to_hash + end + + ActiveRecord::Base.establish_connection(spec) + end + end + + def database + ActiveRecord::Base + end + end + + register ActiveRecordExtension +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake.rb new file mode 100644 index 0000000000..a0e1c76c0c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake.rb @@ -0,0 +1,6 @@ +load "active_record/railties/databases.rake" +require "sinatra/activerecord/rake/activerecord_#{ActiveRecord::VERSION::MAJOR}" + +load "sinatra/activerecord/tasks.rake" + +ActiveRecord::Base.logger = nil diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_3.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_3.rb new file mode 100644 index 0000000000..33dd3bddc5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_3.rb @@ -0,0 +1,31 @@ +require "active_support/string_inquirer" + +module Rails + extend self + + def root + Pathname.new(Rake.application.original_dir) + end + + def env + ActiveSupport::StringInquirer.new(ENV["APP_ENV"] || ENV["RACK_ENV"] || "development") + end + + def application + seed_loader = Object.new + seed_loader.instance_eval do + def load_seed + load "db/seeds.rb" + end + end + seed_loader + end +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task.define_task("db:rails_env") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_4.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_4.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_4.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_5.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_5.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_5.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_6.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_6.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_6.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_7.rb b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_7.rb new file mode 100644 index 0000000000..f09eb787fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/rake/activerecord_7.rb @@ -0,0 +1,23 @@ +seed_loader = Class.new do + def load_seed + load "#{ActiveRecord::Tasks::DatabaseTasks.db_dir}/seeds.rb" + end +end + +ActiveRecord::Tasks::DatabaseTasks.tap do |config| + config.root = Rake.application.original_dir + config.env = ENV["APP_ENV"] || ENV["RACK_ENV"] || "development" + config.db_dir = "db" + config.migrations_paths = ["db/migrate"] + config.fixtures_path = "test/fixtures" + config.seed_loader = seed_loader.new + config.database_configuration = ActiveRecord::Base.configurations +end + +# db:load_config can be overriden manually +Rake::Task["db:seed"].enhance(["db:load_config"]) +Rake::Task["db:load_config"].clear + +# define Rails' tasks as no-op +Rake::Task.define_task("db:environment") +Rake::Task["db:test:deprecated"].clear if Rake::Task.task_defined?("db:test:deprecated") diff --git a/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/tasks.rake b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/tasks.rake new file mode 100644 index 0000000000..73f66e3d92 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sinatra-activerecord-2.0.26/lib/sinatra/activerecord/tasks.rake @@ -0,0 +1,60 @@ +require "active_support/core_ext/string/strip" +require "pathname" +require "fileutils" + +namespace :db do + desc "Create a migration (parameters: NAME, VERSION)" + task :create_migration do + ARGV.each do |a| + # when we run 'rake db:create_migration create_users v1', + # rake will also run 'rake create_users' and 'rake v1' + # to avoid rake abort, we define an empty method for these (ie: "task :create_users do ; end") + next if a.nil? + task a.to_sym do ; end + end + + unless ENV["NAME"] || ARGV[1] + puts "No NAME specified. Example usage: `rake db:create_migration NAME=create_users`" + exit + end + + name = ENV["NAME"] || ARGV[1] + version = ENV["VERSION"] || ARGV[2] || Time.now.utc.strftime("%Y%m%d%H%M%S") + + ActiveRecord::Migrator.migrations_paths.each do |directory| + next unless File.exist?(directory) + migration_files = Pathname(directory).children + if duplicate = migration_files.find { |path| path.basename.to_s.include?(name) } + puts "Another migration is already named \"#{name}\": #{duplicate}." + exit + end + end + + filename = "#{version}_#{name}.rb" + dirname = ActiveRecord::Migrator.migrations_paths.first + path = File.join(dirname, filename) + ar_maj = ActiveRecord::VERSION::MAJOR + ar_min = ActiveRecord::VERSION::MINOR + base = "ActiveRecord::Migration" + base += "[#{ar_maj}.#{ar_min}]" if ar_maj >= 5 + + FileUtils.mkdir_p(dirname) + File.write path, <<-MIGRATION.strip_heredoc + class #{name.camelize} < #{base} + def change + end + end + MIGRATION + + puts path + end +end + +# The `db:create` and `db:drop` command won't work with a DATABASE_URL because +# the `db:load_config` command tries to connect to the DATABASE_URL, which either +# doesn't exist or isn't able to drop the database. Ignore loading the configs for +# these tasks if a `DATABASE_URL` is present. +if ENV.has_key? "DATABASE_URL" + Rake::Task["db:create"].prerequisites.delete("load_config") + Rake::Task["db:drop"].prerequisites.delete("load_config") +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/.gemtest b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/.gemtest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/.travis.yml b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/.travis.yml new file mode 100644 index 0000000000..4876968184 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/.travis.yml @@ -0,0 +1,33 @@ +language: ruby +cache: bundler +before_install: + - gem update --system 2.7.7 + - gem install bundler -v 1.16.2 +addons: + apt: + packages: + - libgmp-dev + +after_failure: + - "find . -name mkmf.log -exec cat {} \\;" + +after_success: + - "find . -name mkmf.log -exec cat {} \\;" + +env: + - USE_MINI_PORTILE=true + - USE_MINI_PORTILE=false +rvm: + - 1.9.3 + - 2.0.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - 2.7 + - ruby-head +matrix: + allow_failures: + - env: USE_MINI_PORTILE=false diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/API_CHANGES.rdoc b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/API_CHANGES.rdoc new file mode 100644 index 0000000000..7f77a02085 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/API_CHANGES.rdoc @@ -0,0 +1,50 @@ += API Changes + +* SQLite3::Database#execute only accepts an array for bind parameters. + +* SQLite3::ResultSet used to query the database for the first row, regardless + of whether the user asked for it or not. I have removed that so that rows + will not be returned until the user asks for them. This is a subtle but + sometimes important change in behavior. + + 83882d2208ed189361617d5ab8532a325aaf729d + +* SQLite3::Database#trace now takes either a block or an object that responds + to "call". The previous implementation passed around a VALUE that was cast + to a void *. This is dangerous because the value could get garbage collected + before the proc was called. If the user wants data passed around with the + block, they should use variables available to the closure or create an + object. + +* SQLite3::Statement#step automatically converts to ruby types, where before + all values were automatically yielded as strings. This will only be a + problem for people who were accessing information about the database that + wasn't previously passed through the pure ruby conversion code. + +* SQLite3::Database#errmsg no longer takes a parameter to return error + messages as UTF-16. Do people even use that? I opt for staying UTF-8 when + possible. See test_integration.rb test_errmsg_utf16 + +* SQLite3::Database#authorize same changes as trace + +* test/test_tc_database.rb was removed because we no longer use the Driver + design pattern. + += Garbage Collection Strategy + +All statements keep pointers back to their respective database connections. +The @connection instance variable on the Statement handle keeps the database +connection alive. Memory allocated for a statement handler will be freed in +two cases: + +* close is called on the statement +* The SQLite3::Database object gets garbage collected + +We can't free the memory for the statement in the garbage collection function +for the statement handler. The reason is because there exists a race +condition. We cannot guarantee the order in which objects will be garbage +collected. So, it is possible that a connection and a statement are up for +garbage collection. If the database connection were to be free'd before the +statement, then boom. Instead we'll be conservative and free unclosed +statements when the connection is terminated. + diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/CHANGELOG.rdoc b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/CHANGELOG.rdoc new file mode 100644 index 0000000000..00cfa99150 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/CHANGELOG.rdoc @@ -0,0 +1,318 @@ +=== 1.4.2 + +* Travis: Drop unused setting "sudo: false" +* The taint mechanism will be deprecated in Ruby 2.7 +* Fix Ruby 2.7 rb_check_safe_obj warnings +* Update travis config + +=== 1.4.1 + +* Don't mandate dl functions for the extention build +* bumping version + +=== 1.4.0 + +* Enhancements + * Better aggregator support + +* Bugfixes + * Various + +=== 1.3.13 + +* Enancements + * Support SQLite flags when defining functions + * Add definition for SQLITE_DETERMINISTIC flag + +=== 1.3.12 + +* Bugfixes: + * OS X install will default to homebrew if available. Fixes #195 + +=== 1.3.11 / 2015-10-10 + +* Enhancements: + * Windows: build against SQLite 3.8.11.1 + +* Internal: + * Use rake-compiler-dock to build Windows binaries. Pull #159 [larskanis] + * Expand Ruby versions being tested for Travis and AppVeyor + +=== 1.3.10 / 2014-10-30 + +* Enhancements: + * Windows: build against SQLite 3.8.6. Closes #135 [Hubro] + +=== 1.3.9 / 2014-02-25 + +* Bugfixes: + * Reset exception message. Closes #80 + * Reduce warnings due unused pointers. Closes #89 + * Add BSD-3 license reference to gemspec. Refs #99 and #106 + +=== 1.3.8 / 2013-08-17 + +* Enhancements: + * Windows: build against SQLite 3.7.17 + +* Bugfixes: + * Reset exception message. Closes #80 + * Correctly convert BLOB values to Ruby. Closes #65 + * Add MIT license reference to gemspec. Closes #99 + * Remove unused pointer. Closes #89 + +* Internal: + * Backport improvements in cross compilation for Windows + * Use of Minitest for internal tests + * Use Gemfile (generated by Hoe) to deal with dependencies + * Cleanup Travis CI + +=== 1.3.7 / 2013-01-11 + +* Bugfixes + * Closing a bad statement twice will not segv. + * Aggregate handlers are initialized on each query. Closes #44 + +* Internal + * Unset environment variables that could affect cross compilation. + +=== 1.3.6 / 2012-04-16 + +* Enhancements + * Windows: build against SQLite 3.7.11 + * Added SQLite3::ResultSet#each_hash for fetching each row as a hash. + * Added SQLite3::ResultSet#next_hash for fetching one row as a hash. + +* Bugfixes + * Support both UTF-16LE and UTF-16BE encoding modes on PPC. Closes #63 + * Protect parameters to custom functions from being garbage collected too + soon. Fixes #60. Thanks hirataya! + * Fix backwards compatibility with 1.2.5 with bind vars and `query` method. + Fixes #35. + * Fix double definition error caused by defining sqlite3_int64/uint64. + * Fix suspicious version regexp. + +* Deprecations + * ArrayWithTypesAndFields#types is deprecated and the class will be removed + in version 2.0.0. Please use the `types` method on the ResultSet class + that created this object. + * ArrayWithTypesAndFields#fields is deprecated and the class will be removed + in version 2.0.0. Please use the `columns` method on the ResultSet class + that created this object. + * The ArrayWithTypesAndFields class will be removed in 2.0.0 + * The ArrayWithTypes class will be removed in 2.0.0 + * HashWithTypesAndFields#types is deprecated and the class will be removed + in version 2.0.0. Please use the `types` method on the ResultSet class + that created this object. + * HashWithTypesAndFields#fields is deprecated and the class will be removed + in version 2.0.0. Please use the `columns` method on the ResultSet class + that created this object. + +=== 1.3.5 / 2011-12-03 - ZOMG Holidays are here Edition! + +* Enhancements + * Windows: build against SQLite 3.7.9 + * Static: enable SQLITE_ENABLE_COLUMN_METADATA + * Added Statement#clear_bindings! to set bindings back to nil + +* Bugfixes + * Fixed a segv on Database.new. Fixes #34 (thanks nobu!) + * Database error is not reset, so don't check it in Statement#reset! + * Remove conditional around Bignum statement bindings. + Fixes #52. Fixes #56. Thank you Evgeny Myasishchev. + +* Internal + * Use proper endianness when testing database connection with UTF-16. + Fixes #40. Fixes #51 + * Use -fPIC for static compilation when host is x86_64. + +=== 1.3.4 / 2011-07-25 + +* Enhancements: + * Windows: build against SQLite 3.7.7.1 + * Windows: build static binaries that do not depend on sqlite3.dll be + installed anymore + +* Bugfixes + * Backup API is conditionaly required so that older libsqlite3 can be used. + Thanks Hongli Lai. + * Fixed segmentation fault when nil is passed to SQLite3::Statement.new + * Fix extconf's hardcoded path that affected installation on certain systems. + +=== 1.3.3 / 2010-01-16 + +* Bugfixes + * Abort on installation if sqlite3_backup_init is missing. Fixes #19 + * Gem has been renamed to 'sqlite3'. Please use `gem install sqlite3` + +=== 1.3.2 / 2010-10-30 / RubyConf Uruguay Edition! + +* Enhancements: + * Windows: build against 3.7.3 version of SQLite3 + * SQLite3::Database can now be open as readonly + + db = SQLite3::Database.new('my.db', :readonly => true) + + * Added SQLite3::SQLITE_VERSION and SQLite3::SQLITE_VERSION_NUMBER [nurse] + +* Bugfixes + * type_translation= works along with Database#execute and a block + * defined functions are kept in a hash to prevent GC. #7 + * Removed GCC specific flags from extconf. + +* DEPRECATIONS + * SQLite3::Database#type_translation= will be deprecated in the future with + no replacement. + * SQlite3::Version will be deprecated in 2.0.0 with SQLite3::VERSION as the + replacement. + +=== 1.3.1 / 2010-07-09 + +* Enhancements + * Custom collations may be defined using SQLite3::Database#collation + +* Bugfixes + * Statements returning 0 columns are automatically stepped. [RF #28308] + * SQLite3::Database#encoding works on 1.8 and 1.9 + +=== 1.3.0 / 2010-06-06 + +* Enhancements + * Complete rewrite of C-based adapter from SWIG to hand-crafted one [tenderlove] + See API_CHANGES document for details. + This closes: Bug #27300, Bug #27241, Patch #16020 + * Improved UTF, Unicode, M17N, all that handling and proper BLOB handling [tenderlove, nurse] + * Added support for type translations [tenderlove] + + @db.translator.add_translator('sometime') do |type, thing| + 'output' # this will be returned as value for that column + end + +* Experimental + * Added API to access and load extensions. [kashif] + These functions maps directly into SQLite3 own enable_load_extension() + and load_extension() C-API functions. See SQLite3::Database API documentation for details. + This closes: Patches #9178 + +* Bugfixes + * Corrected gem dependencies (runtime and development) + * Fixed threaded tests [Alexey Borzenkov] + * Removed GitHub gemspec + * Fixed "No definition for" warnings from RDoc + * Generate zip and tgz files for releases + * Added Luis Lavena as gem Author (maintainer) + * Prevent mkmf interfere with Mighty Snow Leopard + * Allow extension compilation search for common lib paths [kashif] + (lookup /usr/local, /opt/local and /usr) + * Corrected extension compilation under MSVC [romuloceccon] + * Define load_extension functionality based on availability [tenderlove] + * Deprecation notices for Database#query. Fixes RF #28192 + +=== 1.3.0.beta.2 / 2010-05-15 + +* Enhancements + * Added support for type translations [tenderlove] + + @db.translator.add_translator('sometime') do |type, thing| + 'output' # this will be returned as value for that column + end + +* Bugfixes + * Allow extension compilation search for common lib paths [kashif] + (lookup /usr/local, /opt/local and /usr) + * Corrected extension compilation under MSVC [romuloceccon] + * Define load_extension functionality based on availability [tenderlove] + * Deprecation notices for Database#query. Fixes RF #28192 + +=== 1.3.0.beta.1 / 2010-05-10 + +* Enhancements + * Complete rewrite of C-based adapter from SWIG to hand-crafted one [tenderlove] + See API_CHANGES document for details. + This closes: Bug #27300, Bug #27241, Patch #16020 + * Improved UTF, Unicode, M17N, all that handling and proper BLOB handling [tenderlove, nurse] + +* Experimental + * Added API to access and load extensions. [kashif] + These functions maps directly into SQLite3 own enable_load_extension() + and load_extension() C-API functions. See SQLite3::Database API documentation for details. + This closes: Patches #9178 + +* Bugfixes + * Corrected gem dependencies (runtime and development) + * Fixed threaded tests [Alexey Borzenkov] + * Removed GitHub gemspec + * Fixed "No definition for" warnings from RDoc + * Generate zip and tgz files for releases + * Added Luis Lavena as gem Author (maintainer) + * Prevent mkmf interfere with Mighty Snow Leopard + +=== 1.2.5 / 25 Jul 2009 + +* Check for illegal nil before executing SQL [Erik Veenstra] +* Switch to Hoe for gem task management and packaging. +* Advertise rake-compiler as development dependency. +* Build gem binaries for Windows. +* Improved Ruby 1.9 support compatibility. +* Taint returned values. Patch #20325. +* Database.open and Database.new now take an optional block [Gerrit Kaiser] + + +=== 1.2.4.1 (internal) / 5 Jul 2009 + +* Check for illegal nil before executing SQL [Erik Veenstra] +* Switch to Hoe for gem task management and packaging. +* Advertise rake-compiler as development dependency. +* Build gem binaries for Windows. +* Improved Ruby 1.9 support compatibility. + + +=== 1.2.4 / 27 Aug 2008 + +* Package the updated C file for source builds. [Jamis Buck] + + +=== 1.2.3 / 26 Aug 2008 + +* Fix incorrect permissions on database.rb and translator.rb [Various] + +* Avoid using Object#extend for greater speedups [Erik Veenstra] + +* Ruby 1.9 compatibility tweaks for Array#zip [jimmy88@gmail.com] + +* Fix linking against Ruby 1.8.5 [Rob Holland ] + + +=== 1.2.2 / 31 May 2008 + +* Make the table_info method adjust the returned default value for the rows + so that the sqlite3 change in 3.3.8 and greater can be handled + transparently [Jamis Buck ] + +* Ruby 1.9 compatibility tweaks [Roman Le Negrate ] + +* Various performance enhancements [thanks Erik Veenstra] + +* Correct busy_handler documentation [Rob Holland ] + +* Use int_bind64 on Fixnum values larger than a 32bit C int can take. [Rob Holland ] + +* Work around a quirk in SQLite's error reporting by calling sqlite3_reset + to produce a more informative error code upon a failure from + sqlite3_step. [Rob Holland ] + +* Various documentation, test, and style tweaks [Rob Holland ] + +* Be more granular with time/data translation [Rob Holland ] + +* Use Date directly for parsing rather than going via Time [Rob Holland ] + +* Check for the rt library and fdatasync so we link against that when + needed [Rob Holland ] + +* Rename data structures to avoid collision on win32. based on patch + by: Luis Lavena [Rob Holland ] + +* Add test for defaults [Daniel Rodríguez Troitiño] + +* Correctly unquote double-quoted pragma defaults [Łukasz Dargiewicz ] diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ChangeLog.cvs b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ChangeLog.cvs new file mode 100644 index 0000000000..6e9dd51d82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ChangeLog.cvs @@ -0,0 +1,88 @@ +2005-01-05 09:40 minam + + * Rakefile, sqlite3-ruby-win32.gemspec, sqlite3-ruby.gemspec: Added + win32 gem. + +2005-01-05 07:31 minam + + * Rakefile, test/tc_integration.rb, test/tests.rb: Added + native-vs-dl benchmark to Rakefile. Added SQLITE3_DRIVERS + environment variable to integration test to specify which + driver(s) should be tested (defaults to "Native"). + +2005-01-04 14:26 minam + + * ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/driver/native/driver.rb, test/tc_database.rb, + test/tc_integration.rb, test/tests.rb: Unit tests: done. Bugs: + fixed. + +2005-01-03 23:13 minam + + * ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/driver/dl/driver.rb, + lib/sqlite3/driver/native/driver.rb, test/tc_integration.rb: + Custom functions (aggregate and otherwise) are supported by the + native driver now. Test cases for the same. + +2005-01-03 13:51 minam + + * ext/sqlite3_api/MANIFEST, ext/sqlite3_api/extconf.rb, + ext/sqlite3_api/post-clean.rb, ext/sqlite3_api/post-distclean.rb, + ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/resultset.rb, lib/sqlite3/version.rb, + lib/sqlite3/driver/dl/driver.rb, + lib/sqlite3/driver/native/driver.rb, test/native-vs-dl.rb, + test/tc_integration.rb: Added preliminary implementation of + native driver (swig-based), and integration tests. + +2004-12-29 19:37 minam + + * lib/sqlite3/driver/dl/driver.rb: Some fixes to allow the DL + driver to work with Ruby 1.8.1. + +2004-12-29 14:52 minam + + * lib/sqlite3/: database.rb, version.rb: Made #quote a class method + (again). Bumped version to 0.6. + +2004-12-25 22:59 minam + + * lib/sqlite3/driver/dl/api.rb: Added check for darwin in supported + platforms (thanks to bitsweat). + +2004-12-22 12:38 minam + + * Rakefile: Rakefile wasn't packaging the README file. + +2004-12-21 22:28 minam + + * Rakefile, sqlite3-ruby.gemspec, test/bm.rb: Packaging now works. + Added benchmarks. + +2004-12-21 21:45 minam + + * LICENSE, README, Rakefile, setup.rb, sqlite3-ruby.gemspec, + doc/faq/faq.rb, doc/faq/faq.yml, lib/sqlite3.rb, + lib/sqlite3/statement.rb, lib/sqlite3/constants.rb, + lib/sqlite3/database.rb, lib/sqlite3/resultset.rb, + lib/sqlite3/translator.rb, lib/sqlite3/value.rb, + lib/sqlite3/version.rb, lib/sqlite3/errors.rb, + lib/sqlite3/pragmas.rb, lib/sqlite3/driver/dl/api.rb, + lib/sqlite3/driver/dl/driver.rb, test/mocks.rb, + test/tc_database.rb, test/tests.rb, test/driver/dl/tc_driver.rb: + Initial import + +2004-12-21 21:45 minam + + * LICENSE, README, Rakefile, setup.rb, sqlite3-ruby.gemspec, + doc/faq/faq.rb, doc/faq/faq.yml, lib/sqlite3.rb, + lib/sqlite3/statement.rb, lib/sqlite3/constants.rb, + lib/sqlite3/database.rb, lib/sqlite3/resultset.rb, + lib/sqlite3/translator.rb, lib/sqlite3/value.rb, + lib/sqlite3/version.rb, lib/sqlite3/errors.rb, + lib/sqlite3/pragmas.rb, lib/sqlite3/driver/dl/api.rb, + lib/sqlite3/driver/dl/driver.rb, test/mocks.rb, + test/tc_database.rb, test/tests.rb, test/driver/dl/tc_driver.rb: + Initial revision + diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Gemfile b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Gemfile new file mode 100644 index 0000000000..866b4286f6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Gemfile @@ -0,0 +1,17 @@ +# -*- ruby -*- + +# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`. + +source "https://rubygems.org/" + + +gem "minitest", "~>5.11", :group => [:development, :test] +gem "rake-compiler", "~>1.0", :group => [:development, :test] +gem "rake-compiler-dock", "~>0.6.0", :group => [:development, :test] +gem "mini_portile", "~>0.6.2", :group => [:development, :test] +gem "hoe-bundler", "~>1.0", :group => [:development, :test] +gem "hoe-gemspec", "~>1.0", :group => [:development, :test] +gem "rdoc", ">=4.0", "<6", :group => [:development, :test] +gem "hoe", "~>3.17", :group => [:development, :test] + +# vim: syntax=ruby diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/LICENSE new file mode 100644 index 0000000000..75c06824ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2004, Jamis Buck (jamis@jamisbuck.org) +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. + + * The names of its contributors may not 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 COPYRIGHT OWNER 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.0.0/gems/sqlite3-1.4.2/Manifest.txt b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Manifest.txt new file mode 100644 index 0000000000..01da3677e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Manifest.txt @@ -0,0 +1,60 @@ +.gemtest +.travis.yml +API_CHANGES.rdoc +CHANGELOG.rdoc +ChangeLog.cvs +Gemfile +LICENSE +Manifest.txt +README.rdoc +Rakefile +appveyor.yml +ext/sqlite3/aggregator.c +ext/sqlite3/aggregator.h +ext/sqlite3/backup.c +ext/sqlite3/backup.h +ext/sqlite3/database.c +ext/sqlite3/database.h +ext/sqlite3/exception.c +ext/sqlite3/exception.h +ext/sqlite3/extconf.rb +ext/sqlite3/sqlite3.c +ext/sqlite3/sqlite3_ruby.h +ext/sqlite3/statement.c +ext/sqlite3/statement.h +faq/faq.rb +faq/faq.yml +lib/sqlite3.rb +lib/sqlite3/constants.rb +lib/sqlite3/database.rb +lib/sqlite3/errors.rb +lib/sqlite3/pragmas.rb +lib/sqlite3/resultset.rb +lib/sqlite3/statement.rb +lib/sqlite3/translator.rb +lib/sqlite3/value.rb +lib/sqlite3/version.rb +rakelib/faq.rake +rakelib/gem.rake +rakelib/native.rake +rakelib/vendor_sqlite3.rake +setup.rb +test/helper.rb +test/test_backup.rb +test/test_collation.rb +test/test_database.rb +test/test_database_flags.rb +test/test_database_readonly.rb +test/test_database_readwrite.rb +test/test_deprecated.rb +test/test_encoding.rb +test/test_integration.rb +test/test_integration_aggregate.rb +test/test_integration_open_close.rb +test/test_integration_pending.rb +test/test_integration_resultset.rb +test/test_integration_statement.rb +test/test_result_set.rb +test/test_sqlite3.rb +test/test_statement.rb +test/test_statement_execute.rb diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/README.rdoc b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/README.rdoc new file mode 100644 index 0000000000..9ef8576294 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/README.rdoc @@ -0,0 +1,118 @@ += SQLite3/Ruby Interface + +* https://github.com/sparklemotion/sqlite3-ruby +* http://groups.google.com/group/sqlite3-ruby +* http://rubygems.org/gems/sqlite3 +* http://www.rubydoc.info/gems/sqlite3/frames + +{Build Status}[https://travis-ci.org/sparklemotion/sqlite3-ruby] + +== DESCRIPTION + +This module allows Ruby programs to interface with the SQLite3 +database engine (http://www.sqlite.org). You must have the +SQLite engine installed in order to build this module. + +Note that this module is only compatible with SQLite 3.6.16 or newer. + +== SYNOPSIS + + require "sqlite3" + + # Open a database + db = SQLite3::Database.new "test.db" + + # Create a table + rows = db.execute <<-SQL + create table numbers ( + name varchar(30), + val int + ); + SQL + + # Execute a few inserts + { + "one" => 1, + "two" => 2, + }.each do |pair| + db.execute "insert into numbers values ( ?, ? )", pair + end + + # Find a few rows + db.execute( "select * from numbers" ) do |row| + p row + end + + # Create another table with multiple columns + + db.execute <<-SQL + create table students ( + name varchar(50), + email varchar(50), + grade varchar(5), + blog varchar(50) + ); + SQL + + # Execute inserts with parameter markers + db.execute("INSERT INTO students (name, email, grade, blog) + VALUES (?, ?, ?, ?)", ["Jane", "me@janedoe.com", "A", "http://blog.janedoe.com"]) + + db.execute( "select * from students" ) do |row| + p row + end + + +== Compilation and Installation + +Install SQLite3, enabling the option SQLITE_ENABLE_COLUMN_METADATA (see +www.sqlite.org/compile.html for details). + +Then do the following: + + ruby setup.rb config + ruby setup.rb setup + ruby setup.rb install + +Alternatively, you can download and install the RubyGem package for +SQLite3/Ruby (you must have RubyGems and SQLite3 installed, first): + + gem install sqlite3 + +If you have sqlite3 installed in a non-standard location, you can specify the location of the include and lib files by doing: + + gem install sqlite3 -- --with-sqlite3-include=/opt/local/include \ + --with-sqlite3-lib=/opt/local/lib + += SUPPORT!!! + +== OMG! Something has gone wrong! Where do I get help? + +The best place to get help is from the +{sqlite3-ruby mailing list}[http://groups.google.com/group/sqlite3-ruby] which +can be found here: + + * http://groups.google.com/group/sqlite3-ruby + +== I've found a bug! Where do I file it? + +Uh oh. After contacting the mailing list, you've found that you've actually +discovered a bug. You can file the bug at the +{github issues page}[https://github.com/sparklemotion/sqlite3-ruby/issues] +which can be found here: + + * https://github.com/sparklemotion/sqlite3-ruby/issues + +== Usage + +For help figuring out the SQLite3/Ruby interface, check out the +SYNOPSIS as well as the RDoc. It includes examples of +usage. If you have any questions that you feel should be addressed in the +FAQ, please send them to {the mailing list}[http://groups.google.com/group/sqlite3-ruby] + +== Source Code + +The source repository is accessible via git: + + git clone git://github.com/sparklemotion/sqlite3-ruby.git + diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Rakefile b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Rakefile new file mode 100644 index 0000000000..d947cf8e16 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/Rakefile @@ -0,0 +1,8 @@ +# +# NOTE: Keep this file clean. +# Add your customizations inside tasks directory. +# Thank You. +# + + +# vim: syntax=ruby diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/appveyor.yml b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/appveyor.yml new file mode 100644 index 0000000000..57725f0435 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/appveyor.yml @@ -0,0 +1,36 @@ +--- +version: "{build}" +branches: + only: + - master + - 1-3-stable +clone_depth: 10 +install: + - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% + - ruby --version + - gem --version + - gem install bundler --quiet --no-ri --no-rdoc + - bundler --version + - bundle install +build_script: + - rake native gem +test_script: + - rake test +artifacts: + - path: pkg\*.gem + +environment: + matrix: + - ruby_version: "193" + - ruby_version: "200" + - ruby_version: "200-x64" + - ruby_version: "21" + - ruby_version: "21-x64" + - ruby_version: "22" + - ruby_version: "22-x64" + - ruby_version: "23" + - ruby_version: "23-x64" + - ruby_version: "24" + - ruby_version: "24-x64" + - ruby_version: "25" + - ruby_version: "25-x64" diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/.sitearchdir.-.sqlite3.time b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/.sitearchdir.-.sqlite3.time new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/Makefile b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/Makefile new file mode 100644 index 0000000000..9795f81c05 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/Makefile @@ -0,0 +1,266 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +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.0.0 +hdrdir = $(topdir) +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.0.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)./.gem.20230605-4951-mj6ow +sitelibdir = $(DESTDIR)./.gem.20230605-4951-mj6ow +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 +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -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 +cppflags = +CCDLFLAGS = -fPIC +CFLAGS = $(CCDLFLAGS) -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DHAVE_RB_PROC_ARITY -DHAVE_RB_INTEGER_PACK -DHAVE_SQLITE3_INITIALIZE -DHAVE_SQLITE3_BACKUP_INIT -DHAVE_SQLITE3_COLUMN_DATABASE_NAME -DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION -DHAVE_SQLITE3_LOAD_EXTENSION -DHAVE_SQLITE3_OPEN_V2 -DHAVE_SQLITE3_PREPARE_V2 -DHAVE_TYPE_SQLITE3_INT64 -DHAVE_TYPE_SQLITE3_UINT64 -Wdate-time -D_FORTIFY_SOURCE=2 $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security $(ARCH_FLAG) +ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic +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.0 +RUBY_SO_NAME = ruby-3.0 +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.0.0 +ruby = $(bindir)/$(RUBY_BASE_NAME)3.0 +RUBY = $(ruby) +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 = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /usr/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 = /sqlite3 +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) -lsqlite3 -ldl -lpthread -lsqlite3 -lm -lc +ORIG_SRCS = aggregator.c backup.c database.c exception.c sqlite3.c statement.c +SRCS = $(ORIG_SRCS) +OBJS = aggregator.o backup.o database.o exception.o sqlite3.o statement.o +HDRS = $(srcdir)/aggregator.h $(srcdir)/backup.h $(srcdir)/database.h $(srcdir)/exception.h $(srcdir)/sqlite3_ruby.h $(srcdir)/statement.h +LOCAL_HDRS = +TARGET = sqlite3_native +TARGET_NAME = sqlite3_native +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) +CLEANOBJS = *.o *.bak + +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) $(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) $(TIMESTAMP_DIR)/.sitearchdir.-.sqlite3.time + $(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) +$(TIMESTAMP_DIR)/.sitearchdir.-.sqlite3.time: + $(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 sqlite3/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.c new file mode 100644 index 0000000000..df84ff350a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.c @@ -0,0 +1,273 @@ +#include +#include + +/* wraps a factory "handler" class. The "-aggregators" instance variable of + * the SQLite3::Database holds an array of all AggrogatorWrappers. + * + * An AggregatorWrapper holds the following instance variables: + * -handler_klass: the handler that creates the instances. + * -instances: array of all the cAggregatorInstance objects currently + * in-flight for this aggregator. */ +static VALUE cAggregatorWrapper; + +/* wraps a intance of the "handler" class. Loses its reference at the end of + * the xFinal callback. + * + * An AggregatorInstance holds the following instnace variables: + * -handler_instance: the instance to call `step` and `finalize` on. + * -exc_status: status returned by rb_protect. + * != 0 if an exception occurred. If an exception occured + * `step` and `finalize` won't be called any more. */ +static VALUE cAggregatorInstance; + +typedef struct rb_sqlite3_protected_funcall_args { + VALUE self; + ID method; + int argc; + VALUE *params; +} protected_funcall_args_t; + +/* why isn't there something like this in the ruby API? */ +static VALUE +rb_sqlite3_protected_funcall_body(VALUE protected_funcall_args_ptr) +{ + protected_funcall_args_t *args = + (protected_funcall_args_t*)protected_funcall_args_ptr; + + return rb_funcall2(args->self, args->method, args->argc, args->params); +} + +static VALUE +rb_sqlite3_protected_funcall(VALUE self, ID method, int argc, VALUE *params, + int* exc_status) +{ + protected_funcall_args_t args = { + .self = self, .method = method, .argc = argc, .params = params + }; + return rb_protect(rb_sqlite3_protected_funcall_body, (VALUE)(&args), exc_status); +} + +/* called in rb_sqlite3_aggregator_step and rb_sqlite3_aggregator_final. It + * checks if the exection context already has an associated instance. If it + * has one, it returns it. If there is no instance yet, it creates one and + * associates it with the context. */ +static VALUE +rb_sqlite3_aggregate_instance(sqlite3_context *ctx) +{ + VALUE aw = (VALUE) sqlite3_user_data(ctx); + VALUE handler_klass = rb_iv_get(aw, "-handler_klass"); + VALUE inst; + VALUE *inst_ptr = sqlite3_aggregate_context(ctx, (int)sizeof(VALUE)); + + if (!inst_ptr) { + rb_fatal("SQLite is out-of-merory"); + } + + inst = *inst_ptr; + + if (inst == Qfalse) { /* Qfalse == 0 */ + VALUE instances = rb_iv_get(aw, "-instances"); + int exc_status; + + inst = rb_class_new_instance(0, NULL, cAggregatorInstance); + rb_iv_set(inst, "-handler_instance", rb_sqlite3_protected_funcall( + handler_klass, rb_intern("new"), 0, NULL, &exc_status)); + rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); + + rb_ary_push(instances, inst); + + *inst_ptr = inst; + } + + if (inst == Qnil) { + rb_fatal("SQLite called us back on an already destroyed aggregate instance"); + } + + return inst; +} + +/* called by rb_sqlite3_aggregator_final. Unlinks and frees the + * aggregator_instance_t, so the handler_instance won't be marked any more + * and Ruby's GC may free it. */ +static void +rb_sqlite3_aggregate_instance_destroy(sqlite3_context *ctx) +{ + VALUE aw = (VALUE) sqlite3_user_data(ctx); + VALUE instances = rb_iv_get(aw, "-instances"); + VALUE *inst_ptr = sqlite3_aggregate_context(ctx, 0); + VALUE inst; + + if (!inst_ptr || (inst = *inst_ptr)) { + return; + } + + if (inst == Qnil) { + rb_fatal("attempt to destroy aggregate instance twice"); + } + + rb_iv_set(inst, "-handler_instance", Qnil); // may catch use-after-free + if (rb_ary_delete(instances, inst) == Qnil) { + rb_fatal("must be in instances at that point"); + } + + *inst_ptr = Qnil; +} + +static void +rb_sqlite3_aggregator_step(sqlite3_context * ctx, int argc, sqlite3_value **argv) +{ + VALUE inst = rb_sqlite3_aggregate_instance(ctx); + VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); + VALUE * params = NULL; + VALUE one_param; + int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); + int i; + + if (exc_status) { + return; + } + + if (argc == 1) { + one_param = sqlite3val2rb(argv[0]); + params = &one_param; + } + if (argc > 1) { + params = xcalloc((size_t)argc, sizeof(VALUE)); + for(i = 0; i < argc; i++) { + params[i] = sqlite3val2rb(argv[i]); + } + } + rb_sqlite3_protected_funcall( + handler_instance, rb_intern("step"), argc, params, &exc_status); + if (argc > 1) { + xfree(params); + } + + rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); +} + +/* we assume that this function is only called once per execution context */ +static void +rb_sqlite3_aggregator_final(sqlite3_context * ctx) +{ + VALUE inst = rb_sqlite3_aggregate_instance(ctx); + VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); + int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); + + if (!exc_status) { + VALUE result = rb_sqlite3_protected_funcall( + handler_instance, rb_intern("finalize"), 0, NULL, &exc_status); + if (!exc_status) { + set_sqlite3_func_result(ctx, result); + } + } + + if (exc_status) { + /* the user should never see this, as Statement.step() will pick up the + * outstanding exception and raise it instead of generating a new one + * for SQLITE_ERROR with message "Ruby Exception occured" */ + sqlite3_result_error(ctx, "Ruby Exception occured", -1); + } + + rb_sqlite3_aggregate_instance_destroy(ctx); +} + +/* call-seq: define_aggregator2(aggregator) + * + * Define an aggregrate function according to a factory object (the "handler") + * that knows how to obtain to all the information. The handler must provide + * the following class methods: + * + * +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This + * message is optional, and if the handler does not respond to it, + * the function will have an arity of -1. + * +name+:: this is the name of the function. The handler _must_ implement + * this message. + * +new+:: this must be implemented by the handler. It should return a new + * instance of the object that will handle a specific invocation of + * the function. + * + * The handler instance (the object returned by the +new+ message, described + * above), must respond to the following messages: + * + * +step+:: this is the method that will be called for each step of the + * aggregate function's evaluation. It should take parameters according + * to the *arity* definition. + * +finalize+:: this is the method that will be called to finalize the + * aggregate function's evaluation. It should not take arguments. + * + * Note the difference between this function and #create_aggregate_handler + * is that no FunctionProxy ("ctx") object is involved. This manifests in two + * ways: The return value of the aggregate function is the return value of + * +finalize+ and neither +step+ nor +finalize+ take an additional "ctx" + * parameter. + */ +VALUE +rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name) +{ + /* define_aggregator is added as a method to SQLite3::Database in database.c */ + sqlite3RubyPtr ctx; + int arity, status; + VALUE aw; + VALUE aggregators; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + if (!ctx->db) { + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); + } + + if (rb_respond_to(aggregator, rb_intern("arity"))) { + VALUE ruby_arity = rb_funcall(aggregator, rb_intern("arity"), 0); + arity = NUM2INT(ruby_arity); + } else { + arity = -1; + } + + if (arity < -1 || arity > 127) { +#ifdef PRIsVALUE + rb_raise(rb_eArgError, "%"PRIsVALUE" arity=%d out of range -1..127", + self, arity); +#else + rb_raise(rb_eArgError, "Aggregator arity=%d out of range -1..127", arity); +#endif + } + + if (!rb_ivar_defined(self, rb_intern("-aggregators"))) { + rb_iv_set(self, "-aggregators", rb_ary_new()); + } + aggregators = rb_iv_get(self, "-aggregators"); + + aw = rb_class_new_instance(0, NULL, cAggregatorWrapper); + rb_iv_set(aw, "-handler_klass", aggregator); + rb_iv_set(aw, "-instances", rb_ary_new()); + + status = sqlite3_create_function( + ctx->db, + StringValueCStr(ruby_name), + arity, + SQLITE_UTF8, + (void*)aw, + NULL, + rb_sqlite3_aggregator_step, + rb_sqlite3_aggregator_final + ); + + if (status != SQLITE_OK) { + rb_sqlite3_raise(ctx->db, status); + return self; // just in case rb_sqlite3_raise returns. + } + + rb_ary_push(aggregators, aw); + + return self; +} + +void +rb_sqlite3_aggregator_init(void) +{ + rb_gc_register_address(&cAggregatorWrapper); + rb_gc_register_address(&cAggregatorInstance); + /* rb_class_new generatos class with undefined allocator in ruby 1.9 */ + cAggregatorWrapper = rb_funcall(rb_cClass, rb_intern("new"), 0); + cAggregatorInstance = rb_funcall(rb_cClass, rb_intern("new"), 0); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.h new file mode 100644 index 0000000000..68968d15be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.h @@ -0,0 +1,12 @@ +#ifndef SQLITE3_AGGREGATOR_RUBY +#define SQLITE3_AGGREGATOR_RUBY + +#include + +VALUE +rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name); + +void +rb_sqlite3_aggregator_init(void); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.o new file mode 100644 index 0000000000..0627316830 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/aggregator.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.c new file mode 100644 index 0000000000..341df37687 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.c @@ -0,0 +1,168 @@ +#ifdef HAVE_SQLITE3_BACKUP_INIT + +#include + +#define REQUIRE_OPEN_BACKUP(_ctxt) \ + if(!_ctxt->p) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed backup"); + +VALUE cSqlite3Backup; + +static void deallocate(void * ctx) +{ + sqlite3BackupRubyPtr c = (sqlite3BackupRubyPtr)ctx; + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3BackupRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3BackupRuby)); + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +/* call-seq: SQLite3::Backup.new(dstdb, dstname, srcdb, srcname) + * + * Initialize backup the backup. + * + * dstdb: + * the destination SQLite3::Database object. + * dstname: + * the destination's database name. + * srcdb: + * the source SQLite3::Database object. + * srcname: + * the source's database name. + * + * The database name is "main", "temp", or the name specified in an + * ATTACH statement. + * + * This feature requires SQLite 3.6.11 or later. + * + * require 'sqlite3' + * sdb = SQLite3::Database.new('src.sqlite3') + * + * ddb = SQLite3::Database.new(':memory:') + * b = SQLite3::Backup.new(ddb, 'main', sdb, 'main') + * p [b.remaining, b.pagecount] # invalid value; for example [0, 0] + * begin + * p b.step(1) #=> OK or DONE + * p [b.remaining, b.pagecount] + * end while b.remaining > 0 + * b.finish + * + * ddb = SQLite3::Database.new(':memory:') + * b = SQLite3::Backup.new(ddb, 'main', sdb, 'main') + * b.step(-1) #=> DONE + * b.finish + * + */ +static VALUE initialize(VALUE self, VALUE dstdb, VALUE dstname, VALUE srcdb, VALUE srcname) +{ + sqlite3BackupRubyPtr ctx; + sqlite3RubyPtr ddb_ctx, sdb_ctx; + sqlite3_backup *pBackup; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + Data_Get_Struct(dstdb, sqlite3Ruby, ddb_ctx); + Data_Get_Struct(srcdb, sqlite3Ruby, sdb_ctx); + + if(!sdb_ctx->db) + rb_raise(rb_eArgError, "cannot backup from a closed database"); + if(!ddb_ctx->db) + rb_raise(rb_eArgError, "cannot backup to a closed database"); + + pBackup = sqlite3_backup_init(ddb_ctx->db, StringValuePtr(dstname), + sdb_ctx->db, StringValuePtr(srcname)); + if( pBackup ){ + ctx->p = pBackup; + } + else { + CHECK(ddb_ctx->db, sqlite3_errcode(ddb_ctx->db)); + } + + return self; +} + +/* call-seq: SQLite3::Backup#step(nPage) + * + * Copy database pages up to +nPage+. + * If negative, copy all remaining source pages. + * + * If all pages are copied, it returns SQLite3::Constants::ErrorCode::DONE. + * When coping is not done, it returns SQLite3::Constants::ErrorCode::OK. + * When some errors occur, it returns the error code. + */ +static VALUE step(VALUE self, VALUE nPage) +{ + sqlite3BackupRubyPtr ctx; + int status; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + status = sqlite3_backup_step(ctx->p, NUM2INT(nPage)); + return INT2NUM(status); +} + +/* call-seq: SQLite3::Backup#finish + * + * Destroy the backup object. + */ +static VALUE finish(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + (void)sqlite3_backup_finish(ctx->p); + ctx->p = NULL; + return Qnil; +} + +/* call-seq: SQLite3::Backup#remaining + * + * Returns the number of pages still to be backed up. + * + * Note that the value is only updated after step() is called, + * so before calling step() returned value is invalid. + */ +static VALUE remaining(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + return INT2NUM(sqlite3_backup_remaining(ctx->p)); +} + +/* call-seq: SQLite3::Backup#pagecount + * + * Returns the total number of pages in the source database file. + * + * Note that the value is only updated after step() is called, + * so before calling step() returned value is invalid. + */ +static VALUE pagecount(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + return INT2NUM(sqlite3_backup_pagecount(ctx->p)); +} + +void init_sqlite3_backup() +{ +#if 0 + VALUE mSqlite3 = rb_define_module("SQLite3"); +#endif + cSqlite3Backup = rb_define_class_under(mSqlite3, "Backup", rb_cObject); + + rb_define_alloc_func(cSqlite3Backup, allocate); + rb_define_method(cSqlite3Backup, "initialize", initialize, 4); + rb_define_method(cSqlite3Backup, "step", step, 1); + rb_define_method(cSqlite3Backup, "finish", finish, 0); + rb_define_method(cSqlite3Backup, "remaining", remaining, 0); + rb_define_method(cSqlite3Backup, "pagecount", pagecount, 0); +} + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.h new file mode 100644 index 0000000000..0c8c62022b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.h @@ -0,0 +1,15 @@ +#if !defined(SQLITE3_BACKUP_RUBY) && defined(HAVE_SQLITE3_BACKUP_INIT) +#define SQLITE3_BACKUP_RUBY + +#include + +struct _sqlite3BackupRuby { + sqlite3_backup *p; +}; + +typedef struct _sqlite3BackupRuby sqlite3BackupRuby; +typedef sqlite3BackupRuby * sqlite3BackupRubyPtr; + +void init_sqlite3_backup(); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.o new file mode 100644 index 0000000000..4181eb8270 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/backup.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.c new file mode 100644 index 0000000000..3fa2718b30 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.c @@ -0,0 +1,827 @@ +#include +#include + +#define REQUIRE_OPEN_DB(_ctxt) \ + if(!_ctxt->db) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); + +VALUE cSqlite3Database; + +static void deallocate(void * ctx) +{ + sqlite3RubyPtr c = (sqlite3RubyPtr)ctx; + sqlite3 * db = c->db; + + if(db) sqlite3_close(db); + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3RubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3Ruby)); + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +static char * +utf16_string_value_ptr(VALUE str) +{ + StringValue(str); + rb_str_buf_cat(str, "\x00", 1L); + return RSTRING_PTR(str); +} + +static VALUE sqlite3_rb_close(VALUE self); + +static VALUE rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs) +{ + sqlite3RubyPtr ctx; + VALUE flags; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + +#if defined TAINTING_SUPPORT +#if defined StringValueCStr + StringValuePtr(file); + rb_check_safe_obj(file); +#else + Check_SafeStr(file); +#endif +#endif + + status = sqlite3_open_v2( + StringValuePtr(file), + &ctx->db, + NUM2INT(mode), + NIL_P(zvfs) ? NULL : StringValuePtr(zvfs) + ); + + CHECK(ctx->db, status) + + return self; +} + +/* call-seq: db.close + * + * Closes this database. + */ +static VALUE sqlite3_rb_close(VALUE self) +{ + sqlite3RubyPtr ctx; + sqlite3 * db; + Data_Get_Struct(self, sqlite3Ruby, ctx); + + db = ctx->db; + CHECK(db, sqlite3_close(ctx->db)); + + ctx->db = NULL; + + rb_iv_set(self, "-aggregators", Qnil); + + return self; +} + +/* call-seq: db.closed? + * + * Returns +true+ if this database instance has been closed (see #close). + */ +static VALUE closed_p(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + + if(!ctx->db) return Qtrue; + + return Qfalse; +} + +/* call-seq: total_changes + * + * Returns the total number of changes made to this database instance + * since it was opened. + */ +static VALUE total_changes(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM((long)sqlite3_total_changes(ctx->db)); +} + +static void tracefunc(void * data, const char *sql) +{ + VALUE self = (VALUE)data; + VALUE thing = rb_iv_get(self, "@tracefunc"); + rb_funcall(thing, rb_intern("call"), 1, rb_str_new2(sql)); +} + +/* call-seq: + * trace { |sql| ... } + * trace(Class.new { def call sql; end }.new) + * + * Installs (or removes) a block that will be invoked for every SQL + * statement executed. The block receives one parameter: the SQL statement + * executed. If the block is +nil+, any existing tracer will be uninstalled. + */ +static VALUE trace(int argc, VALUE *argv, VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE block; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + rb_scan_args(argc, argv, "01", &block); + + if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); + + rb_iv_set(self, "@tracefunc", block); + + sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc, (void *)self); + + return self; +} + +static int rb_sqlite3_busy_handler(void * ctx, int count) +{ + VALUE self = (VALUE)(ctx); + VALUE handle = rb_iv_get(self, "@busy_handler"); + VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM((long)count)); + + if(Qfalse == result) return 0; + + return 1; +} + +/* call-seq: + * busy_handler { |count| ... } + * busy_handler(Class.new { def call count; end }.new) + * + * Register a busy handler with this database instance. When a requested + * resource is busy, this handler will be invoked. If the handler returns + * +false+, the operation will be aborted; otherwise, the resource will + * be requested again. + * + * The handler will be invoked with the name of the resource that was + * busy, and the number of times it has been retried. + * + * See also the mutually exclusive #busy_timeout. + */ +static VALUE busy_handler(int argc, VALUE *argv, VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE block; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + rb_scan_args(argc, argv, "01", &block); + + if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); + + rb_iv_set(self, "@busy_handler", block); + + status = sqlite3_busy_handler( + ctx->db, NIL_P(block) ? NULL : rb_sqlite3_busy_handler, (void *)self); + + CHECK(ctx->db, status); + + return self; +} + +/* call-seq: last_insert_row_id + * + * Obtains the unique row ID of the last row to be inserted by this Database + * instance. + */ +static VALUE last_insert_row_id(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return LL2NUM(sqlite3_last_insert_rowid(ctx->db)); +} + +VALUE sqlite3val2rb(sqlite3_value * val) +{ + switch(sqlite3_value_type(val)) { + case SQLITE_INTEGER: + return LL2NUM(sqlite3_value_int64(val)); + break; + case SQLITE_FLOAT: + return rb_float_new(sqlite3_value_double(val)); + break; + case SQLITE_TEXT: + return rb_str_new2((const char *)sqlite3_value_text(val)); + break; + case SQLITE_BLOB: { + /* Sqlite warns calling sqlite3_value_bytes may invalidate pointer from sqlite3_value_blob, + so we explicitly get the length before getting blob pointer. + Note that rb_str_new apparently create string with ASCII-8BIT (BINARY) encoding, + which is what we want, as blobs are binary + */ + int len = sqlite3_value_bytes(val); + return rb_str_new((const char *)sqlite3_value_blob(val), len); + break; + } + case SQLITE_NULL: + return Qnil; + break; + default: + rb_raise(rb_eRuntimeError, "bad type"); /* FIXME */ + } +} + +void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result) +{ + switch(TYPE(result)) { + case T_NIL: + sqlite3_result_null(ctx); + break; + case T_FIXNUM: + sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result)); + break; + case T_BIGNUM: { +#if SIZEOF_LONG < 8 + sqlite3_int64 num64; + + if (bignum_to_int64(result, &num64)) { + sqlite3_result_int64(ctx, num64); + break; + } +#endif + } + case T_FLOAT: + sqlite3_result_double(ctx, NUM2DBL(result)); + break; + case T_STRING: + if(CLASS_OF(result) == cSqlite3Blob + || rb_enc_get_index(result) == rb_ascii8bit_encindex() + ) { + sqlite3_result_blob( + ctx, + (const void *)StringValuePtr(result), + (int)RSTRING_LEN(result), + SQLITE_TRANSIENT + ); + } else { + sqlite3_result_text( + ctx, + (const char *)StringValuePtr(result), + (int)RSTRING_LEN(result), + SQLITE_TRANSIENT + ); + } + break; + default: + rb_raise(rb_eRuntimeError, "can't return %s", + rb_class2name(CLASS_OF(result))); + } +} + +static void rb_sqlite3_func(sqlite3_context * ctx, int argc, sqlite3_value **argv) +{ + VALUE callable = (VALUE)sqlite3_user_data(ctx); + VALUE params = rb_ary_new2(argc); + VALUE result; + int i; + + if (argc > 0) { + for(i = 0; i < argc; i++) { + VALUE param = sqlite3val2rb(argv[i]); + rb_ary_push(params, param); + } + } + + result = rb_apply(callable, rb_intern("call"), params); + + set_sqlite3_func_result(ctx, result); +} + +#ifndef HAVE_RB_PROC_ARITY +int rb_proc_arity(VALUE self) +{ + return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0)); +} +#endif + +/* call-seq: define_function_with_flags(name, flags) { |args,...| } + * + * Define a function named +name+ with +args+ using TextRep bitflags +flags+. The arity of the block + * will be used as the arity for the function defined. + */ +static VALUE define_function_with_flags(VALUE self, VALUE name, VALUE flags) +{ + sqlite3RubyPtr ctx; + VALUE block; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + block = rb_block_proc(); + + status = sqlite3_create_function( + ctx->db, + StringValuePtr(name), + rb_proc_arity(block), + NUM2INT(flags), + (void *)block, + rb_sqlite3_func, + NULL, + NULL + ); + + CHECK(ctx->db, status); + + rb_hash_aset(rb_iv_get(self, "@functions"), name, block); + + return self; +} + +/* call-seq: define_function(name) { |args,...| } + * + * Define a function named +name+ with +args+. The arity of the block + * will be used as the arity for the function defined. + */ +static VALUE define_function(VALUE self, VALUE name) +{ + return define_function_with_flags(self, name, INT2FIX(SQLITE_UTF8)); +} + +/* call-seq: interrupt + * + * Interrupts the currently executing operation, causing it to abort. + */ +static VALUE interrupt(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + sqlite3_interrupt(ctx->db); + + return self; +} + +/* call-seq: errmsg + * + * Return a string describing the last error to have occurred with this + * database. + */ +static VALUE errmsg(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return rb_str_new2(sqlite3_errmsg(ctx->db)); +} + +/* call-seq: errcode + * + * Return an integer representing the last error to have occurred with this + * database. + */ +static VALUE errcode_(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM((long)sqlite3_errcode(ctx->db)); +} + +/* call-seq: complete?(sql) + * + * Return +true+ if the string is a valid (ie, parsable) SQL statement, and + * +false+ otherwise. + */ +static VALUE complete_p(VALUE UNUSED(self), VALUE sql) +{ + if(sqlite3_complete(StringValuePtr(sql))) + return Qtrue; + + return Qfalse; +} + +/* call-seq: changes + * + * Returns the number of changes made to this database instance by the last + * operation performed. Note that a "delete from table" without a where + * clause will not affect this value. + */ +static VALUE changes(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM(sqlite3_changes(ctx->db)); +} + +static int rb_sqlite3_auth( + void *ctx, + int _action, + const char * _a, + const char * _b, + const char * _c, + const char * _d) +{ + VALUE self = (VALUE)ctx; + VALUE action = INT2NUM(_action); + VALUE a = _a ? rb_str_new2(_a) : Qnil; + VALUE b = _b ? rb_str_new2(_b) : Qnil; + VALUE c = _c ? rb_str_new2(_c) : Qnil; + VALUE d = _d ? rb_str_new2(_d) : Qnil; + VALUE callback = rb_iv_get(self, "@authorizer"); + VALUE result = rb_funcall(callback, rb_intern("call"), 5, action, a, b, c, d); + + if(T_FIXNUM == TYPE(result)) return (int)NUM2INT(result); + if(Qtrue == result) return SQLITE_OK; + if(Qfalse == result) return SQLITE_DENY; + + return SQLITE_IGNORE; +} + +/* call-seq: set_authorizer = auth + * + * Set the authorizer for this database. +auth+ must respond to +call+, and + * +call+ must take 5 arguments. + * + * Installs (or removes) a block that will be invoked for every access + * to the database. If the block returns 0 (or +true+), the statement + * is allowed to proceed. Returning 1 or false causes an authorization error to + * occur, and returning 2 or nil causes the access to be silently denied. + */ +static VALUE set_authorizer(VALUE self, VALUE authorizer) +{ + sqlite3RubyPtr ctx; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + status = sqlite3_set_authorizer( + ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth, (void *)self + ); + + CHECK(ctx->db, status); + + rb_iv_set(self, "@authorizer", authorizer); + + return self; +} + +/* call-seq: db.busy_timeout = ms + * + * Indicates that if a request for a resource terminates because that + * resource is busy, SQLite should sleep and retry for up to the indicated + * number of milliseconds. By default, SQLite does not retry + * busy resources. To restore the default behavior, send 0 as the + * +ms+ parameter. + * + * See also the mutually exclusive #busy_handler. + */ +static VALUE set_busy_timeout(VALUE self, VALUE timeout) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout))); + + return self; +} + +/* call-seq: db.extended_result_codes = true + * + * Enable extended result codes in SQLite. These result codes allow for more + * detailed exception reporting, such a which type of constraint is violated. + */ +static VALUE set_extended_result_codes(VALUE self, VALUE enable) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_extended_result_codes(ctx->db, RTEST(enable) ? 1 : 0)); + + return self; +} + +int rb_comparator_func(void * ctx, int a_len, const void * a, int b_len, const void * b) +{ + VALUE comparator; + VALUE a_str; + VALUE b_str; + VALUE comparison; + rb_encoding * internal_encoding; + + internal_encoding = rb_default_internal_encoding(); + + comparator = (VALUE)ctx; + a_str = rb_str_new((const char *)a, a_len); + b_str = rb_str_new((const char *)b, b_len); + + rb_enc_associate_index(a_str, rb_utf8_encindex()); + rb_enc_associate_index(b_str, rb_utf8_encindex()); + + if(internal_encoding) { + a_str = rb_str_export_to_enc(a_str, internal_encoding); + b_str = rb_str_export_to_enc(b_str, internal_encoding); + } + + comparison = rb_funcall(comparator, rb_intern("compare"), 2, a_str, b_str); + + return NUM2INT(comparison); +} + +/* call-seq: db.collation(name, comparator) + * + * Add a collation with name +name+, and a +comparator+ object. The + * +comparator+ object should implement a method called "compare" that takes + * two parameters and returns an integer less than, equal to, or greater than + * 0. + */ +static VALUE collation(VALUE self, VALUE name, VALUE comparator) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_create_collation( + ctx->db, + StringValuePtr(name), + SQLITE_UTF8, + (void *)comparator, + NIL_P(comparator) ? NULL : rb_comparator_func)); + + /* Make sure our comparator doesn't get garbage collected. */ + rb_hash_aset(rb_iv_get(self, "@collations"), name, comparator); + + return self; +} + +#ifdef HAVE_SQLITE3_LOAD_EXTENSION +/* call-seq: db.load_extension(file) + * + * Loads an SQLite extension library from the named file. Extension + * loading must be enabled using db.enable_load_extension(true) prior + * to calling this API. + */ +static VALUE load_extension(VALUE self, VALUE file) +{ + sqlite3RubyPtr ctx; + int status; + char *errMsg; + VALUE errexp; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + status = sqlite3_load_extension(ctx->db, RSTRING_PTR(file), 0, &errMsg); + if (status != SQLITE_OK) + { + errexp = rb_exc_new2(rb_eRuntimeError, errMsg); + sqlite3_free(errMsg); + rb_exc_raise(errexp); + } + + return self; +} +#endif + +#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION +/* call-seq: db.enable_load_extension(onoff) + * + * Enable or disable extension loading. + */ +static VALUE enable_load_extension(VALUE self, VALUE onoff) +{ + sqlite3RubyPtr ctx; + int onoffparam; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + if (Qtrue == onoff) { + onoffparam = 1; + } else if (Qfalse == onoff) { + onoffparam = 0; + } else { + onoffparam = (int)NUM2INT(onoff); + } + + CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, onoffparam)); + + return self; +} +#endif + +static int enc_cb(void * _self, int UNUSED(columns), char **data, char **UNUSED(names)) +{ + VALUE self = (VALUE)_self; + + int index = rb_enc_find_index(data[0]); + rb_encoding * e = rb_enc_from_index(index); + rb_iv_set(self, "@encoding", rb_enc_from_encoding(e)); + + return 0; +} + +/* call-seq: db.encoding + * + * Fetch the encoding set on this database + */ +static VALUE db_encoding(VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE enc; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + enc = rb_iv_get(self, "@encoding"); + + if(NIL_P(enc)) { + sqlite3_exec(ctx->db, "PRAGMA encoding", enc_cb, (void *)self, NULL); + } + + return rb_iv_get(self, "@encoding"); +} + +/* call-seq: db.transaction_active? + * + * Returns +true+ if there is a transaction active, and +false+ otherwise. + * + */ +static VALUE transaction_active_p(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return sqlite3_get_autocommit(ctx->db) ? Qfalse : Qtrue; +} + +static int hash_callback_function(VALUE callback_ary, int count, char **data, char **columns) +{ + VALUE new_hash = rb_hash_new(); + int i; + + for (i = 0; i < count; i++) { + if (data[i] == NULL) { + rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), Qnil); + } else { + rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), rb_str_new_cstr(data[i])); + } + } + + rb_ary_push(callback_ary, new_hash); + + return 0; +} + +static int regular_callback_function(VALUE callback_ary, int count, char **data, char **columns) +{ + VALUE new_ary = rb_ary_new(); + int i; + + for (i = 0; i < count; i++) { + if (data[i] == NULL) { + rb_ary_push(new_ary, Qnil); + } else { + rb_ary_push(new_ary, rb_str_new_cstr(data[i])); + } + } + + rb_ary_push(callback_ary, new_ary); + + return 0; +} + + +/* Is invoked by calling db.execute_batch2(sql, &block) + * + * Executes all statments in a given string separated by semicolons. + * If a query is made, all values returned are strings + * (except for 'NULL' values which return nil), + * so the user may parse values with a block. + * If no query is made, an empty array will be returned. + */ +static VALUE exec_batch(VALUE self, VALUE sql, VALUE results_as_hash) +{ + sqlite3RubyPtr ctx; + int status; + VALUE callback_ary = rb_ary_new(); + char *errMsg; + VALUE errexp; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + if(results_as_hash == Qtrue) { + status = sqlite3_exec(ctx->db, StringValuePtr(sql), hash_callback_function, callback_ary, &errMsg); + } else { + status = sqlite3_exec(ctx->db, StringValuePtr(sql), regular_callback_function, callback_ary, &errMsg); + } + + if (status != SQLITE_OK) + { + errexp = rb_exc_new2(rb_eRuntimeError, errMsg); + sqlite3_free(errMsg); + rb_exc_raise(errexp); + } + + return callback_ary; +} + +/* call-seq: db.db_filename(database_name) + * + * Returns the file associated with +database_name+. Can return nil or an + * empty string if the database is temporary, or in-memory. + */ +static VALUE db_filename(VALUE self, VALUE db_name) +{ + sqlite3RubyPtr ctx; + const char * fname; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + fname = sqlite3_db_filename(ctx->db, StringValueCStr(db_name)); + + if(fname) return SQLITE3_UTF8_STR_NEW2(fname); + return Qnil; +} + +static VALUE rb_sqlite3_open16(VALUE self, VALUE file) +{ + int status; + sqlite3RubyPtr ctx; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + +#if defined TAINTING_SUPPORT +#if defined StringValueCStr + StringValuePtr(file); + rb_check_safe_obj(file); +#else + Check_SafeStr(file); +#endif +#endif + + status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db); + + CHECK(ctx->db, status) + + return INT2NUM(status); +} + +void init_sqlite3_database() +{ +#if 0 + VALUE mSqlite3 = rb_define_module("SQLite3"); +#endif + cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject); + + rb_define_alloc_func(cSqlite3Database, allocate); + rb_define_private_method(cSqlite3Database, "open_v2", rb_sqlite3_open_v2, 3); + rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1); + rb_define_method(cSqlite3Database, "collation", collation, 2); + rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0); + rb_define_method(cSqlite3Database, "closed?", closed_p, 0); + rb_define_method(cSqlite3Database, "total_changes", total_changes, 0); + rb_define_method(cSqlite3Database, "trace", trace, -1); + rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0); + rb_define_method(cSqlite3Database, "define_function", define_function, 1); + rb_define_method(cSqlite3Database, "define_function_with_flags", define_function_with_flags, 2); + /* public "define_aggregator" is now a shim around define_aggregator2 + * implemented in Ruby */ + rb_define_private_method(cSqlite3Database, "define_aggregator2", rb_sqlite3_define_aggregator2, 2); + rb_define_method(cSqlite3Database, "interrupt", interrupt, 0); + rb_define_method(cSqlite3Database, "errmsg", errmsg, 0); + rb_define_method(cSqlite3Database, "errcode", errcode_, 0); + rb_define_method(cSqlite3Database, "complete?", complete_p, 1); + rb_define_method(cSqlite3Database, "changes", changes, 0); + rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1); + rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1); + rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1); + rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1); + rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0); + rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2); + rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1); + +#ifdef HAVE_SQLITE3_LOAD_EXTENSION + rb_define_method(cSqlite3Database, "load_extension", load_extension, 1); +#endif + +#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION + rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1); +#endif + + rb_define_method(cSqlite3Database, "encoding", db_encoding, 0); + + rb_sqlite3_aggregator_init(); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.h new file mode 100644 index 0000000000..e087789492 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.h @@ -0,0 +1,17 @@ +#ifndef SQLITE3_DATABASE_RUBY +#define SQLITE3_DATABASE_RUBY + +#include + +struct _sqlite3Ruby { + sqlite3 *db; +}; + +typedef struct _sqlite3Ruby sqlite3Ruby; +typedef sqlite3Ruby * sqlite3RubyPtr; + +void init_sqlite3_database(); +void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result); +VALUE sqlite3val2rb(sqlite3_value * val); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.o new file mode 100644 index 0000000000..3907d1e01e Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/database.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.c new file mode 100644 index 0000000000..1dcfe18d23 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.c @@ -0,0 +1,98 @@ +#include + +void rb_sqlite3_raise(sqlite3 * db, int status) +{ + VALUE klass = Qnil; + + /* Consider only lower 8 bits, to work correctly when + extended result codes are enabled. */ + switch(status & 0xff) { + case SQLITE_OK: + return; + break; + case SQLITE_ERROR: + klass = rb_path2class("SQLite3::SQLException"); + break; + case SQLITE_INTERNAL: + klass = rb_path2class("SQLite3::InternalException"); + break; + case SQLITE_PERM: + klass = rb_path2class("SQLite3::PermissionException"); + break; + case SQLITE_ABORT: + klass = rb_path2class("SQLite3::AbortException"); + break; + case SQLITE_BUSY: + klass = rb_path2class("SQLite3::BusyException"); + break; + case SQLITE_LOCKED: + klass = rb_path2class("SQLite3::LockedException"); + break; + case SQLITE_NOMEM: + klass = rb_path2class("SQLite3::MemoryException"); + break; + case SQLITE_READONLY: + klass = rb_path2class("SQLite3::ReadOnlyException"); + break; + case SQLITE_INTERRUPT: + klass = rb_path2class("SQLite3::InterruptException"); + break; + case SQLITE_IOERR: + klass = rb_path2class("SQLite3::IOException"); + break; + case SQLITE_CORRUPT: + klass = rb_path2class("SQLite3::CorruptException"); + break; + case SQLITE_NOTFOUND: + klass = rb_path2class("SQLite3::NotFoundException"); + break; + case SQLITE_FULL: + klass = rb_path2class("SQLite3::FullException"); + break; + case SQLITE_CANTOPEN: + klass = rb_path2class("SQLite3::CantOpenException"); + break; + case SQLITE_PROTOCOL: + klass = rb_path2class("SQLite3::ProtocolException"); + break; + case SQLITE_EMPTY: + klass = rb_path2class("SQLite3::EmptyException"); + break; + case SQLITE_SCHEMA: + klass = rb_path2class("SQLite3::SchemaChangedException"); + break; + case SQLITE_TOOBIG: + klass = rb_path2class("SQLite3::TooBigException"); + break; + case SQLITE_CONSTRAINT: + klass = rb_path2class("SQLite3::ConstraintException"); + break; + case SQLITE_MISMATCH: + klass = rb_path2class("SQLite3::MismatchException"); + break; + case SQLITE_MISUSE: + klass = rb_path2class("SQLite3::MisuseException"); + break; + case SQLITE_NOLFS: + klass = rb_path2class("SQLite3::UnsupportedException"); + break; + case SQLITE_AUTH: + klass = rb_path2class("SQLite3::AuthorizationException"); + break; + case SQLITE_FORMAT: + klass = rb_path2class("SQLite3::FormatException"); + break; + case SQLITE_RANGE: + klass = rb_path2class("SQLite3::RangeException"); + break; + case SQLITE_NOTADB: + klass = rb_path2class("SQLite3::NotADatabaseException"); + break; + default: + klass = rb_eRuntimeError; + } + + klass = rb_exc_new2(klass, sqlite3_errmsg(db)); + rb_iv_set(klass, "@code", INT2FIX(status)); + rb_exc_raise(klass); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.h new file mode 100644 index 0000000000..55b687e30b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.h @@ -0,0 +1,8 @@ +#ifndef SQLITE3_EXCEPTION_RUBY +#define SQLITE3_EXCEPTION_RUBY + +#define CHECK(_db, _status) rb_sqlite3_raise(_db, _status); + +void rb_sqlite3_raise(sqlite3 * db, int status); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.o new file mode 100644 index 0000000000..5495453145 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/exception.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/extconf.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/extconf.rb new file mode 100644 index 0000000000..5b958d0b35 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/extconf.rb @@ -0,0 +1,100 @@ +ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/ + +require 'mkmf' + +# :stopdoc: + +RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC'] + +ldflags = cppflags = nil +if RbConfig::CONFIG["host_os"] =~ /darwin/ + begin + if with_config('sqlcipher') + brew_prefix = `brew --prefix sqlcipher`.chomp + ldflags = "#{brew_prefix}/lib" + cppflags = "#{brew_prefix}/include/sqlcipher" + pkg_conf = "#{brew_prefix}/lib/pkgconfig" + else + brew_prefix = `brew --prefix sqlite3`.chomp + ldflags = "#{brew_prefix}/lib" + cppflags = "#{brew_prefix}/include" + pkg_conf = "#{brew_prefix}/lib/pkgconfig" + end + + # pkg_config should be less error prone than parsing compiler + # commandline options, but we need to set default ldflags and cpp flags + # in case the user doesn't have pkg-config installed + ENV['PKG_CONFIG_PATH'] ||= pkg_conf + rescue + end +end + +if with_config('sqlcipher') + pkg_config("sqlcipher") +else + pkg_config("sqlite3") +end + +# --with-sqlite3-{dir,include,lib} +if with_config('sqlcipher') + $CFLAGS << ' -DUSING_SQLCIPHER' + dir_config("sqlcipher", cppflags, ldflags) +else + dir_config("sqlite3", cppflags, ldflags) +end + +if RbConfig::CONFIG["host_os"] =~ /mswin/ + $CFLAGS << ' -W3' +end + +if RUBY_VERSION < '2.7' + $CFLAGS << ' -DTAINTING_SUPPORT' +end + +def asplode missing + if RUBY_PLATFORM =~ /mingw|mswin/ + abort "#{missing} is missing. Install SQLite3 from " + + "http://www.sqlite.org/ first." + else + abort <<-error +#{missing} is missing. Try 'brew install sqlite3', +'yum install sqlite-devel' or 'apt-get install libsqlite3-dev' +and check your shared library search path (the +location where your sqlite3 shared library is located). + error + end +end + +asplode('sqlite3.h') unless find_header 'sqlite3.h' +find_library 'pthread', 'pthread_create' # 1.8 support. *shrug* + +have_library 'dl' # for static builds + +if with_config('sqlcipher') + asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number' +else + asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number' +end + +# Functions defined in 1.9 but not 1.8 +have_func('rb_proc_arity') + +# Functions defined in 2.1 but not 2.0 +have_func('rb_integer_pack') + +# These functions may not be defined +have_func('sqlite3_initialize') +have_func('sqlite3_backup_init') +have_func('sqlite3_column_database_name') +have_func('sqlite3_enable_load_extension') +have_func('sqlite3_load_extension') + +unless have_func('sqlite3_open_v2') + abort "Please use a newer version of SQLite3" +end + +have_func('sqlite3_prepare_v2') +have_type('sqlite3_int64', 'sqlite3.h') +have_type('sqlite3_uint64', 'sqlite3.h') + +create_makefile('sqlite3/sqlite3_native') diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.c new file mode 100644 index 0000000000..ede81ea6ab --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.c @@ -0,0 +1,161 @@ +#include + +VALUE mSqlite3; +VALUE cSqlite3Blob; + +int bignum_to_int64(VALUE value, sqlite3_int64 *result) +{ +#ifdef HAVE_RB_INTEGER_PACK + const int nails = 0; + int t = rb_integer_pack(value, result, 1, sizeof(*result), nails, + INTEGER_PACK_NATIVE_BYTE_ORDER| + INTEGER_PACK_2COMP); + switch (t) { + case -2: case +2: + return 0; + case +1: + if (!nails) { + if (*result < 0) return 0; + } + break; + case -1: + if (!nails) { + if (*result >= 0) return 0; + } + else { + *result += INT64_MIN; + } + break; + } + return 1; +#else +# ifndef RBIGNUM_LEN +# define RBIGNUM_LEN(x) RBIGNUM(x)->len +# endif + const long len = RBIGNUM_LEN(value); + if (len == 0) { + *result = 0; + return 1; + } + if (len > 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) return 0; + if (len == 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) { + const BDIGIT *digits = RBIGNUM_DIGITS(value); + BDIGIT blast = digits[len-1]; + BDIGIT bmax = (BDIGIT)1UL << (63 % (CHAR_BIT * SIZEOF_BDIGITS)); + if (blast > bmax) return 0; + if (blast == bmax) { + if (RBIGNUM_POSITIVE_P(value)) { + return 0; + } + else { + long i = len-1; + while (i) { + if (digits[--i]) return 0; + } + } + } + } + *result = (sqlite3_int64)NUM2LL(value); + return 1; +#endif +} + +static VALUE libversion(VALUE UNUSED(klass)) +{ + return INT2NUM(sqlite3_libversion_number()); +} + +static VALUE using_sqlcipher(VALUE UNUSED(klass)) +{ +#ifdef USING_SQLCIPHER + return Qtrue; +#else + return Qfalse; +#endif +} + +/* Returns the compile time setting of the SQLITE_THREADSAFE flag. + * See: https://www.sqlite.org/c3ref/threadsafe.html + */ +static VALUE threadsafe_p(VALUE UNUSED(klass)) +{ + return INT2NUM(sqlite3_threadsafe()); +} + +void init_sqlite3_constants() +{ + VALUE mSqlite3Constants; + VALUE mSqlite3Open; + + mSqlite3Constants = rb_define_module_under(mSqlite3, "Constants"); + + /* sqlite3_open_v2 flags for Database::new */ + mSqlite3Open = rb_define_module_under(mSqlite3Constants, "Open"); + + /* symbols = IO.readlines('sqlite3.h').map { |n| /\A#define\s+(SQLITE_OPEN_\w+)\s/ =~ n && $1 }.compact + * pad = symbols.map(&:length).max - 9 + * symbols.each { |s| printf %Q{ rb_define_const(mSqlite3Open, %-#{pad}s INT2FIX(#{s}));\n}, '"' + s[12..-1] + '",' } + */ + rb_define_const(mSqlite3Open, "READONLY", INT2FIX(SQLITE_OPEN_READONLY)); + rb_define_const(mSqlite3Open, "READWRITE", INT2FIX(SQLITE_OPEN_READWRITE)); + rb_define_const(mSqlite3Open, "CREATE", INT2FIX(SQLITE_OPEN_CREATE)); + rb_define_const(mSqlite3Open, "DELETEONCLOSE", INT2FIX(SQLITE_OPEN_DELETEONCLOSE)); + rb_define_const(mSqlite3Open, "EXCLUSIVE", INT2FIX(SQLITE_OPEN_EXCLUSIVE)); + rb_define_const(mSqlite3Open, "MAIN_DB", INT2FIX(SQLITE_OPEN_MAIN_DB)); + rb_define_const(mSqlite3Open, "TEMP_DB", INT2FIX(SQLITE_OPEN_TEMP_DB)); + rb_define_const(mSqlite3Open, "TRANSIENT_DB", INT2FIX(SQLITE_OPEN_TRANSIENT_DB)); + rb_define_const(mSqlite3Open, "MAIN_JOURNAL", INT2FIX(SQLITE_OPEN_MAIN_JOURNAL)); + rb_define_const(mSqlite3Open, "TEMP_JOURNAL", INT2FIX(SQLITE_OPEN_TEMP_JOURNAL)); + rb_define_const(mSqlite3Open, "SUBJOURNAL", INT2FIX(SQLITE_OPEN_SUBJOURNAL)); + rb_define_const(mSqlite3Open, "MASTER_JOURNAL", INT2FIX(SQLITE_OPEN_MASTER_JOURNAL)); + rb_define_const(mSqlite3Open, "NOMUTEX", INT2FIX(SQLITE_OPEN_NOMUTEX)); + rb_define_const(mSqlite3Open, "FULLMUTEX", INT2FIX(SQLITE_OPEN_FULLMUTEX)); +#ifdef SQLITE_OPEN_AUTOPROXY + /* SQLITE_VERSION_NUMBER>=3007002 */ + rb_define_const(mSqlite3Open, "AUTOPROXY", INT2FIX(SQLITE_OPEN_AUTOPROXY)); + rb_define_const(mSqlite3Open, "SHAREDCACHE", INT2FIX(SQLITE_OPEN_SHAREDCACHE)); + rb_define_const(mSqlite3Open, "PRIVATECACHE", INT2FIX(SQLITE_OPEN_PRIVATECACHE)); + rb_define_const(mSqlite3Open, "WAL", INT2FIX(SQLITE_OPEN_WAL)); +#endif +#ifdef SQLITE_OPEN_URI + /* SQLITE_VERSION_NUMBER>=3007007 */ + rb_define_const(mSqlite3Open, "URI", INT2FIX(SQLITE_OPEN_URI)); +#endif +#ifdef SQLITE_OPEN_MEMORY + /* SQLITE_VERSION_NUMBER>=3007013 */ + rb_define_const(mSqlite3Open, "MEMORY", INT2FIX(SQLITE_OPEN_MEMORY)); +#endif +} + +void Init_sqlite3_native() +{ + /* + * SQLite3 is a wrapper around the popular database + * sqlite[http://sqlite.org]. + * + * For an example of usage, see SQLite3::Database. + */ + mSqlite3 = rb_define_module("SQLite3"); + + /* A class for differentiating between strings and blobs, when binding them + * into statements. + */ + cSqlite3Blob = rb_define_class_under(mSqlite3, "Blob", rb_cString); + + /* Initialize the sqlite3 library */ +#ifdef HAVE_SQLITE3_INITIALIZE + sqlite3_initialize(); +#endif + + init_sqlite3_constants(); + init_sqlite3_database(); + init_sqlite3_statement(); +#ifdef HAVE_SQLITE3_BACKUP_INIT + init_sqlite3_backup(); +#endif + rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); + rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); + rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); + rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); + rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.o new file mode 100644 index 0000000000..9a1acdfe47 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_native.so new file mode 100755 index 0000000000..569fb67ef2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_ruby.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_ruby.h new file mode 100644 index 0000000000..a647c4409e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/sqlite3_ruby.h @@ -0,0 +1,45 @@ +#ifndef SQLITE3_RUBY +#define SQLITE3_RUBY + +#include + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +#include + +#define USASCII_P(_obj) (rb_enc_get_index(_obj) == rb_usascii_encindex()) +#define UTF8_P(_obj) (rb_enc_get_index(_obj) == rb_utf8_encindex()) +#define UTF16_LE_P(_obj) (rb_enc_get_index(_obj) == rb_enc_find_index("UTF-16LE")) +#define UTF16_BE_P(_obj) (rb_enc_get_index(_obj) == rb_enc_find_index("UTF-16BE")) +#define SQLITE3_UTF8_STR_NEW2(_obj) \ + (rb_enc_associate_index(rb_str_new2(_obj), rb_utf8_encindex())) + + +#include + +#ifndef HAVE_TYPE_SQLITE3_INT64 +typedef sqlite_int64 sqlite3_int64; +#endif + +#ifndef HAVE_TYPE_SQLITE3_UINT64 +typedef sqlite_uint64 sqlite3_uint64; +#endif + +extern VALUE mSqlite3; +extern VALUE cSqlite3Blob; + +#include +#include +#include +#include + +int bignum_to_int64(VALUE big, sqlite3_int64 *result); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.c new file mode 100644 index 0000000000..44e1a5c975 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.c @@ -0,0 +1,442 @@ +#include + +#define REQUIRE_OPEN_STMT(_ctxt) \ + if(!_ctxt->st) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed statement"); + +VALUE cSqlite3Statement; + +static void deallocate(void * ctx) +{ + sqlite3StmtRubyPtr c = (sqlite3StmtRubyPtr)ctx; + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3StmtRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3StmtRuby)); + ctx->st = NULL; + ctx->done_p = 0; + + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +/* call-seq: SQLite3::Statement.new(db, sql) + * + * Create a new statement attached to the given Database instance, and which + * encapsulates the given SQL text. If the text contains more than one + * statement (i.e., separated by semicolons), then the #remainder property + * will be set to the trailing text. + */ +static VALUE initialize(VALUE self, VALUE db, VALUE sql) +{ + sqlite3RubyPtr db_ctx; + sqlite3StmtRubyPtr ctx; + const char *tail = NULL; + int status; + + StringValue(sql); + + Data_Get_Struct(db, sqlite3Ruby, db_ctx); + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(!db_ctx->db) + rb_raise(rb_eArgError, "prepare called on a closed database"); + + if(!UTF8_P(sql)) { + sql = rb_str_export_to_enc(sql, rb_utf8_encoding()); + } + +#ifdef HAVE_SQLITE3_PREPARE_V2 + status = sqlite3_prepare_v2( +#else + status = sqlite3_prepare( +#endif + db_ctx->db, + (const char *)StringValuePtr(sql), + (int)RSTRING_LEN(sql), + &ctx->st, + &tail + ); + + CHECK(db_ctx->db, status); + + rb_iv_set(self, "@connection", db); + rb_iv_set(self, "@remainder", rb_str_new2(tail)); + rb_iv_set(self, "@columns", Qnil); + rb_iv_set(self, "@types", Qnil); + + return self; +} + +/* call-seq: stmt.close + * + * Closes the statement by finalizing the underlying statement + * handle. The statement must not be used after being closed. + */ +static VALUE sqlite3_rb_close(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + REQUIRE_OPEN_STMT(ctx); + + sqlite3_finalize(ctx->st); + ctx->st = NULL; + + return self; +} + +/* call-seq: stmt.closed? + * + * Returns true if the statement has been closed. + */ +static VALUE closed_p(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(!ctx->st) return Qtrue; + + return Qfalse; +} + +static VALUE step(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + sqlite3_stmt *stmt; + int value, length; + VALUE list; + rb_encoding * internal_encoding; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + REQUIRE_OPEN_STMT(ctx); + + if(ctx->done_p) return Qnil; + + { + VALUE db = rb_iv_get(self, "@connection"); + rb_funcall(db, rb_intern("encoding"), 0); + internal_encoding = rb_default_internal_encoding(); + } + + stmt = ctx->st; + + value = sqlite3_step(stmt); + if (rb_errinfo() != Qnil) { + /* some user defined function was invoked as a callback during step and + * it raised an exception that has been suppressed until step returns. + * Now re-raise it. */ + VALUE exception = rb_errinfo(); + rb_set_errinfo(Qnil); + rb_exc_raise(exception); + } + + length = sqlite3_column_count(stmt); + list = rb_ary_new2((long)length); + + switch(value) { + case SQLITE_ROW: + { + int i; + for(i = 0; i < length; i++) { + switch(sqlite3_column_type(stmt, i)) { + case SQLITE_INTEGER: + rb_ary_push(list, LL2NUM(sqlite3_column_int64(stmt, i))); + break; + case SQLITE_FLOAT: + rb_ary_push(list, rb_float_new(sqlite3_column_double(stmt, i))); + break; + case SQLITE_TEXT: + { + VALUE str = rb_str_new( + (const char *)sqlite3_column_text(stmt, i), + (long)sqlite3_column_bytes(stmt, i) + ); + rb_enc_associate_index(str, rb_utf8_encindex()); + if(internal_encoding) + str = rb_str_export_to_enc(str, internal_encoding); + rb_ary_push(list, str); + } + break; + case SQLITE_BLOB: + { + VALUE str = rb_str_new( + (const char *)sqlite3_column_blob(stmt, i), + (long)sqlite3_column_bytes(stmt, i) + ); + rb_ary_push(list, str); + } + break; + case SQLITE_NULL: + rb_ary_push(list, Qnil); + break; + default: + rb_raise(rb_eRuntimeError, "bad type"); + } + } + } + break; + case SQLITE_DONE: + ctx->done_p = 1; + return Qnil; + break; + default: + sqlite3_reset(stmt); + ctx->done_p = 0; + CHECK(sqlite3_db_handle(ctx->st), value); + } + + return list; +} + +/* call-seq: stmt.bind_param(key, value) + * + * Binds value to the named (or positional) placeholder. If +param+ is a + * Fixnum, it is treated as an index for a positional placeholder. + * Otherwise it is used as the name of the placeholder to bind to. + * + * See also #bind_params. + */ +static VALUE bind_param(VALUE self, VALUE key, VALUE value) +{ + sqlite3StmtRubyPtr ctx; + int status; + int index; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + switch(TYPE(key)) { + case T_SYMBOL: + key = rb_funcall(key, rb_intern("to_s"), 0); + case T_STRING: + if(RSTRING_PTR(key)[0] != ':') key = rb_str_plus(rb_str_new2(":"), key); + index = sqlite3_bind_parameter_index(ctx->st, StringValuePtr(key)); + break; + default: + index = (int)NUM2INT(key); + } + + if(index == 0) + rb_raise(rb_path2class("SQLite3::Exception"), "no such bind parameter"); + + switch(TYPE(value)) { + case T_STRING: + if(CLASS_OF(value) == cSqlite3Blob + || rb_enc_get_index(value) == rb_ascii8bit_encindex() + ) { + status = sqlite3_bind_blob( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } else { + + + if (UTF16_LE_P(value) || UTF16_BE_P(value)) { + status = sqlite3_bind_text16( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } else { + if (!UTF8_P(value) || !USASCII_P(value)) { + value = rb_str_encode(value, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil); + } + status = sqlite3_bind_text( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } + } + break; + case T_BIGNUM: { + sqlite3_int64 num64; + if (bignum_to_int64(value, &num64)) { + status = sqlite3_bind_int64(ctx->st, index, num64); + break; + } + } + case T_FLOAT: + status = sqlite3_bind_double(ctx->st, index, NUM2DBL(value)); + break; + case T_FIXNUM: + status = sqlite3_bind_int64(ctx->st, index, (sqlite3_int64)FIX2LONG(value)); + break; + case T_NIL: + status = sqlite3_bind_null(ctx->st, index); + break; + default: + rb_raise(rb_eRuntimeError, "can't prepare %s", + rb_class2name(CLASS_OF(value))); + break; + } + + CHECK(sqlite3_db_handle(ctx->st), status); + + return self; +} + +/* call-seq: stmt.reset! + * + * Resets the statement. This is typically done internally, though it might + * occassionally be necessary to manually reset the statement. + */ +static VALUE reset_bang(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + sqlite3_reset(ctx->st); + + ctx->done_p = 0; + + return self; +} + +/* call-seq: stmt.clear_bindings! + * + * Resets the statement. This is typically done internally, though it might + * occassionally be necessary to manually reset the statement. + */ +static VALUE clear_bindings_bang(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + sqlite3_clear_bindings(ctx->st); + + ctx->done_p = 0; + + return self; +} + +/* call-seq: stmt.done? + * + * returns true if all rows have been returned. + */ +static VALUE done_p(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(ctx->done_p) return Qtrue; + return Qfalse; +} + +/* call-seq: stmt.column_count + * + * Returns the number of columns to be returned for this statement + */ +static VALUE column_count(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return INT2NUM((long)sqlite3_column_count(ctx->st)); +} + +/* call-seq: stmt.column_name(index) + * + * Get the column name at +index+. 0 based. + */ +static VALUE column_name(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + const char * name; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + name = sqlite3_column_name(ctx->st, (int)NUM2INT(index)); + + if(name) return SQLITE3_UTF8_STR_NEW2(name); + return Qnil; +} + +/* call-seq: stmt.column_decltype(index) + * + * Get the column type at +index+. 0 based. + */ +static VALUE column_decltype(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + const char * name; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index)); + + if(name) return rb_str_new2(name); + return Qnil; +} + +/* call-seq: stmt.bind_parameter_count + * + * Return the number of bind parameters + */ +static VALUE bind_parameter_count(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return INT2NUM((long)sqlite3_bind_parameter_count(ctx->st)); +} + +#ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME + +/* call-seq: stmt.database_name(column_index) + * + * Return the database name for the column at +column_index+ + */ +static VALUE database_name(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return SQLITE3_UTF8_STR_NEW2( + sqlite3_column_database_name(ctx->st, NUM2INT(index))); +} + +#endif + +void init_sqlite3_statement() +{ + cSqlite3Statement = rb_define_class_under(mSqlite3, "Statement", rb_cObject); + + rb_define_alloc_func(cSqlite3Statement, allocate); + rb_define_method(cSqlite3Statement, "initialize", initialize, 2); + rb_define_method(cSqlite3Statement, "close", sqlite3_rb_close, 0); + rb_define_method(cSqlite3Statement, "closed?", closed_p, 0); + rb_define_method(cSqlite3Statement, "bind_param", bind_param, 2); + rb_define_method(cSqlite3Statement, "reset!", reset_bang, 0); + rb_define_method(cSqlite3Statement, "clear_bindings!", clear_bindings_bang, 0); + rb_define_method(cSqlite3Statement, "step", step, 0); + rb_define_method(cSqlite3Statement, "done?", done_p, 0); + rb_define_method(cSqlite3Statement, "column_count", column_count, 0); + rb_define_method(cSqlite3Statement, "column_name", column_name, 1); + rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1); + rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0); + +#ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME + rb_define_method(cSqlite3Statement, "database_name", database_name, 1); +#endif +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.h new file mode 100644 index 0000000000..e7ef1f3a5e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.h @@ -0,0 +1,16 @@ +#ifndef SQLITE3_STATEMENT_RUBY +#define SQLITE3_STATEMENT_RUBY + +#include + +struct _sqlite3StmtRuby { + sqlite3_stmt *st; + int done_p; +}; + +typedef struct _sqlite3StmtRuby sqlite3StmtRuby; +typedef sqlite3StmtRuby * sqlite3StmtRubyPtr; + +void init_sqlite3_statement(); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.o b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.o new file mode 100644 index 0000000000..55348f7512 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/ext/sqlite3/statement.o differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.rb new file mode 100644 index 0000000000..61d7d01c49 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.rb @@ -0,0 +1,145 @@ +require 'yaml' +require 'redcloth' + +def process_faq_list( faqs ) + puts "
      " + faqs.each do |faq| + process_faq_list_item faq + end + puts "
    " +end + +def process_faq_list_item( faq ) + question = faq.keys.first + answer = faq.values.first + + print "
  • " + + question_text = RedCloth.new(question).to_html.gsub( %r{},"" ) + if answer.is_a?( Array ) + puts question_text + process_faq_list answer + else + print "#{question_text}" + end + + puts "
  • " +end + +def process_faq_descriptions( faqs, path=nil ) + faqs.each do |faq| + process_faq_description faq, path + end +end + +def process_faq_description( faq, path ) + question = faq.keys.first + path = ( path ? path + " " : "" ) + question + answer = faq.values.first + + if answer.is_a?( Array ) + process_faq_descriptions( answer, path ) + else + title = RedCloth.new( path ).to_html.gsub( %r{}, "" ) + answer = RedCloth.new( answer || "" ) + + puts "" + puts "
    #{title}
    " + puts "
    #{add_api_links(answer.to_html)}
    " + end +end + +API_OBJECTS = [ "Database", "Statement", "ResultSet", + "ParsedStatement", "Pragmas", "Translator" ].inject( "(" ) { |acc,name| + acc << "|" if acc.length > 1 + acc << name + acc + } + ")" + +def add_api_links( text ) + text.gsub( /#{API_OBJECTS}(#(\w+))?/ ) do + disp_obj = obj = $1 + + case obj + when "Pragmas"; disp_obj = "Database" + end + + method = $3 + s = "#{disp_obj}" + s << "##{method}" if method + s << "" + s + end +end + +faqs = YAML.load( File.read( "faq.yml" ) ) + +puts <<-EOF + + + SQLite3/Ruby FAQ + + + +

    SQLite/Ruby FAQ

    +
    +EOF + +process_faq_list( faqs ) +puts "
    " +process_faq_descriptions( faqs ) + +puts "" diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.yml b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.yml new file mode 100644 index 0000000000..155259fffb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/faq/faq.yml @@ -0,0 +1,426 @@ +--- +- "How do I do a database query?": + - "I just want an array of the rows...": >- + + Use the Database#execute method. If you don't give it a block, it will + return an array of all the rows: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        rows = db.execute( "select * from test" )
    +      
    + + - "I'd like to use a block to iterate through the rows...": >- + + Use the Database#execute method. If you give it a block, each row of the + result will be yielded to the block: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        db.execute( "select * from test" ) do |row|
    +          ...
    +        end
    +      
    + + - "I need to get the column names as well as the rows...": >- + + Use the Database#execute2 method. This works just like Database#execute; + if you don't give it a block, it returns an array of rows; otherwise, it + will yield each row to the block. _However_, the first row returned is + always an array of the column names from the query: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        columns, *rows = db.execute2( "select * from test" )
    +
    +        # or use a block:
    +
    +        columns = nil
    +        db.execute2( "select * from test" ) do |row|
    +          if columns.nil?
    +            columns = row
    +          else
    +            # process row
    +          end
    +        end
    +      
    + + - "I just want the first row of the result set...": >- + + Easy. Just call Database#get_first_row: + + +
    +        row = db.get_first_row( "select * from table" )
    +      
    + + + This also supports bind variables, just like Database#execute + and friends. + + - "I just want the first value of the first row of the result set...": >- + + Also easy. Just call Database#get_first_value: + + +
    +        count = db.get_first_value( "select count(*) from table" )
    +      
    + + + This also supports bind variables, just like Database#execute + and friends. + +- "How do I prepare a statement for repeated execution?": >- + If the same statement is going to be executed repeatedly, you can speed + things up a bit by _preparing_ the statement. You do this via the + Database#prepare method. It returns a Statement object, and you can + then invoke #execute on that to get the ResultSet: + + +
    +      stmt = db.prepare( "select * from person" )
    +
    +      1000.times do
    +        stmt.execute do |result|
    +          ...
    +        end
    +      end
    +
    +      stmt.close
    +
    +      # or, use a block
    +
    +      db.prepare( "select * from person" ) do |stmt|
    +        1000.times do
    +          stmt.execute do |result|
    +            ...
    +          end
    +        end
    +      end
    +    
    + + + This is made more useful by the ability to bind variables to placeholders + via the Statement#bind_param and Statement#bind_params methods. (See the + next FAQ for details.) + +- "How do I use placeholders in an SQL statement?": >- + Placeholders in an SQL statement take any of the following formats: + + + * @?@ + + * @?_nnn_@ + + * @:_word_@ + + + Where _n_ is an integer, and _word_ is an alpha-numeric identifier (or + number). When the placeholder is associated with a number, that number + identifies the index of the bind variable to replace it with. When it + is an identifier, it identifies the name of the correponding bind + variable. (In the instance of the first format--a single question + mark--the placeholder is assigned a number one greater than the last + index used, or 1 if it is the first.) + + + For example, here is a query using these placeholder formats: + + +
    +      select *
    +        from table
    +       where ( c = ?2 or c = ? )
    +         and d = :name
    +         and e = :1
    +    
    + + + This defines 5 different placeholders: 1, 2, 3, and "name". + + + You replace these placeholders by _binding_ them to values. This can be + accomplished in a variety of ways. + + + The Database#execute, and Database#execute2 methods all accept additional + arguments following the SQL statement. These arguments are assumed to be + bind parameters, and they are bound (positionally) to their corresponding + placeholders: + + +
    +      db.execute( "select * from table where a = ? and b = ?",
    +                  "hello",
    +                  "world" )
    +    
    + + + The above would replace the first question mark with 'hello' and the + second with 'world'. If the placeholders have an explicit index given, they + will be replaced with the bind parameter at that index (1-based). + + + If a Hash is given as a bind parameter, then its key/value pairs are bound + to the placeholders. This is how you bind by name: + + +
    +      db.execute( "select * from table where a = :name and b = :value",
    +                  "name" => "bob",
    +                  "value" => "priceless" )
    +    
    + + + You can also bind explicitly using the Statement object itself. Just pass + additional parameters to the Statement#execute statement: + + +
    +      db.prepare( "select * from table where a = :name and b = ?" ) do |stmt|
    +        stmt.execute "value", "name" => "bob"
    +      end
    +    
    + + + Or do a Database#prepare to get the Statement, and then use either + Statement#bind_param or Statement#bind_params: + + +
    +      stmt = db.prepare( "select * from table where a = :name and b = ?" )
    +
    +      stmt.bind_param( "name", "bob" )
    +      stmt.bind_param( 1, "value" )
    +
    +      # or
    +
    +      stmt.bind_params( "value", "name" => "bob" )
    +    
    + +- "How do I discover metadata about a query?": >- + + If you ever want to know the names or types of the columns in a result + set, you can do it in several ways. + + + The first way is to ask the row object itself. Each row will have a + property "fields" that returns an array of the column names. The row + will also have a property "types" that returns an array of the column + types: + + +
    +      rows = db.execute( "select * from table" )
    +      p rows[0].fields
    +      p rows[0].types
    +    
    + + + Obviously, this approach requires you to execute a statement that actually + returns data. If you don't know if the statement will return any rows, but + you still need the metadata, you can use Database#query and ask the + ResultSet object itself: + + +
    +      db.query( "select * from table" ) do |result|
    +        p result.columns
    +        p result.types
    +        ...
    +      end
    +    
    + + + Lastly, you can use Database#prepare and ask the Statement object what + the metadata are: + + +
    +      stmt = db.prepare( "select * from table" )
    +      p stmt.columns
    +      p stmt.types
    +    
    + +- "I'd like the rows to be indexible by column name.": >- + By default, each row from a query is returned as an Array of values. This + means that you can only obtain values by their index. Sometimes, however, + you would like to obtain values by their column name. + + + The first way to do this is to set the Database property "results_as_hash" + to true. If you do this, then all rows will be returned as Hash objects, + with the column names as the keys. (In this case, the "fields" property + is unavailable on the row, although the "types" property remains.) + + +
    +      db.results_as_hash = true
    +      db.execute( "select * from table" ) do |row|
    +        p row['column1']
    +        p row['column2']
    +      end
    +    
    + + + The other way is to use Ara Howard's + "ArrayFields":http://rubyforge.org/projects/arrayfields + module. Just require "arrayfields", and all of your rows will be indexable + by column name, even though they are still arrays! + + +
    +      require 'arrayfields'
    +
    +      ...
    +      db.execute( "select * from table" ) do |row|
    +        p row[0] == row['column1']
    +        p row[1] == row['column2']
    +      end
    +    
    + +- "I'd like the values from a query to be the correct types, instead of String.": >- + You can turn on "type translation" by setting Database#type_translation to + true: + + +
    +      db.type_translation = true
    +      db.execute( "select * from table" ) do |row|
    +        p row
    +      end
    +    
    + + + By doing this, each return value for each row will be translated to its + correct type, based on its declared column type. + + + You can even declare your own translation routines, if (for example) you are + using an SQL type that is not handled by default: + + +
    +      # assume "objects" table has the following schema:
    +      #   create table objects (
    +      #     name varchar2(20),
    +      #     thing object
    +      #   )
    +
    +      db.type_translation = true
    +      db.translator.add_translator( "object" ) do |type, value|
    +        db.decode( value )
    +      end
    +
    +      h = { :one=>:two, "three"=>"four", 5=>6 }
    +      dump = db.encode( h )
    +
    +      db.execute( "insert into objects values ( ?, ? )", "bob", dump )
    +
    +      obj = db.get_first_value( "select thing from objects where name='bob'" )
    +      p obj == h
    +    
    + +- "How do I insert binary data into the database?": >- + Use blobs. Blobs are new features of SQLite3. You have to use bind + variables to make it work: + + +
    +      db.execute( "insert into foo ( ?, ? )",
    +        SQLite3::Blob.new( "\0\1\2\3\4\5" ),
    +        SQLite3::Blob.new( "a\0b\0c\0d ) )
    +    
    + + + The blob values must be indicated explicitly by binding each parameter to + a value of type SQLite3::Blob. + +- "How do I do a DDL (insert, update, delete) statement?": >- + You can actually do inserts, updates, and deletes in exactly the same way + as selects, but in general the Database#execute method will be most + convenient: + + +
    +      db.execute( "insert into table values ( ?, ? )", *bind_vars )
    +    
    + +- "How do I execute multiple statements in a single string?": >- + The standard query methods (Database#execute, Database#execute2, + Database#query, and Statement#execute) will only execute the first + statement in the string that is given to them. Thus, if you have a + string with multiple SQL statements, each separated by a string, + you can't use those methods to execute them all at once. + + + Instead, use Database#execute_batch: + + +
    +      sql = <
    +
    +
    +    Unlike the other query methods, Database#execute_batch accepts no
    +    block. It will also only ever return +nil+. Thus, it is really only
    +    suitable for batch processing of DDL statements.
    +
    +- "How do I begin/end a transaction?":
    +    Use Database#transaction to start a transaction. If you give it a block,
    +    the block will be automatically committed at the end of the block,
    +    unless an exception was raised, in which case the transaction will be
    +    rolled back. (Never explicitly call Database#commit or Database#rollback
    +    inside of a transaction block--you'll get errors when the block
    +    terminates!)
    +
    +
    +    
    +      database.transaction do |db|
    +        db.execute( "insert into table values ( 'a', 'b', 'c' )" )
    +        ...
    +      end
    +    
    + + + Alternatively, if you don't give a block to Database#transaction, the + transaction remains open until you explicitly call Database#commit or + Database#rollback. + + +
    +      db.transaction
    +      db.execute( "insert into table values ( 'a', 'b', 'c' )" )
    +      db.commit
    +    
    + + + Note that SQLite does not allow nested transactions, so you'll get errors + if you try to open a new transaction while one is already active. Use + Database#transaction_active? to determine whether a transaction is + active or not. + +#- "How do I discover metadata about a table/index?": +# +#- "How do I do tweak database settings?": diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3.rb new file mode 100644 index 0000000000..f6110e1c0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3.rb @@ -0,0 +1,15 @@ +# support multiple ruby version (fat binaries under windows) +begin + RUBY_VERSION =~ /(\d+\.\d+)/ + require "sqlite3/#{$1}/sqlite3_native" +rescue LoadError + require 'sqlite3/sqlite3_native' +end + +require 'sqlite3/database' +require 'sqlite3/version' + +module SQLite3 + # Was sqlite3 compiled with thread safety on? + def self.threadsafe?; threadsafe > 0; end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/constants.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/constants.rb new file mode 100644 index 0000000000..d52a0012db --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/constants.rb @@ -0,0 +1,50 @@ +module SQLite3 ; module Constants + + module TextRep + UTF8 = 1 + UTF16LE = 2 + UTF16BE = 3 + UTF16 = 4 + ANY = 5 + DETERMINISTIC = 0x800 + end + + module ColumnType + INTEGER = 1 + FLOAT = 2 + TEXT = 3 + BLOB = 4 + NULL = 5 + end + + module ErrorCode + OK = 0 # Successful result + ERROR = 1 # SQL error or missing database + INTERNAL = 2 # An internal logic error in SQLite + PERM = 3 # Access permission denied + ABORT = 4 # Callback routine requested an abort + BUSY = 5 # The database file is locked + LOCKED = 6 # A table in the database is locked + NOMEM = 7 # A malloc() failed + READONLY = 8 # Attempt to write a readonly database + INTERRUPT = 9 # Operation terminated by sqlite_interrupt() + IOERR = 10 # Some kind of disk I/O error occurred + CORRUPT = 11 # The database disk image is malformed + NOTFOUND = 12 # (Internal Only) Table or record not found + FULL = 13 # Insertion failed because database is full + CANTOPEN = 14 # Unable to open the database file + PROTOCOL = 15 # Database lock protocol error + EMPTY = 16 # (Internal Only) Database table is empty + SCHEMA = 17 # The database schema changed + TOOBIG = 18 # Too much data for one row of a table + CONSTRAINT = 19 # Abort due to contraint violation + MISMATCH = 20 # Data type mismatch + MISUSE = 21 # Library used incorrectly + NOLFS = 22 # Uses OS features not supported on host + AUTH = 23 # Authorization denied + + ROW = 100 # sqlite_step() has another row ready + DONE = 101 # sqlite_step() has finished executing + end + +end ; end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb new file mode 100644 index 0000000000..643b6c7b8e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb @@ -0,0 +1,736 @@ +require 'sqlite3/constants' +require 'sqlite3/errors' +require 'sqlite3/pragmas' +require 'sqlite3/statement' +require 'sqlite3/translator' +require 'sqlite3/value' + +module SQLite3 + + # The Database class encapsulates a single connection to a SQLite3 database. + # Its usage is very straightforward: + # + # require 'sqlite3' + # + # SQLite3::Database.new( "data.db" ) do |db| + # db.execute( "select * from table" ) do |row| + # p row + # end + # end + # + # It wraps the lower-level methods provides by the selected driver, and + # includes the Pragmas module for access to various pragma convenience + # methods. + # + # The Database class provides type translation services as well, by which + # the SQLite3 data types (which are all represented as strings) may be + # converted into their corresponding types (as defined in the schemas + # for their tables). This translation only occurs when querying data from + # the database--insertions and updates are all still typeless. + # + # Furthermore, the Database class has been designed to work well with the + # ArrayFields module from Ara Howard. If you require the ArrayFields + # module before performing a query, and if you have not enabled results as + # hashes, then the results will all be indexible by field name. + class Database + attr_reader :collations + + include Pragmas + + class << self + + alias :open :new + + # Quotes the given string, making it safe to use in an SQL statement. + # It replaces all instances of the single-quote character with two + # single-quote characters. The modified string is returned. + def quote( string ) + string.gsub( /'/, "''" ) + end + + end + + # A boolean that indicates whether rows in result sets should be returned + # as hashes or not. By default, rows are returned as arrays. + attr_accessor :results_as_hash + + # call-seq: SQLite3::Database.new(file, options = {}) + # + # Create a new Database object that opens the given file. If utf16 + # is +true+, the filename is interpreted as a UTF-16 encoded string. + # + # By default, the new database will return result rows as arrays + # (#results_as_hash) and has type translation disabled (#type_translation=). + + def initialize file, options = {}, zvfs = nil + mode = Constants::Open::READWRITE | Constants::Open::CREATE + + if file.encoding == ::Encoding::UTF_16LE || file.encoding == ::Encoding::UTF_16BE || options[:utf16] + open16 file + else + # The three primary flag values for sqlite3_open_v2 are: + # SQLITE_OPEN_READONLY + # SQLITE_OPEN_READWRITE + # SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE -- always used for sqlite3_open and sqlite3_open16 + mode = Constants::Open::READONLY if options[:readonly] + + if options[:readwrite] + raise "conflicting options: readonly and readwrite" if options[:readonly] + mode = Constants::Open::READWRITE + end + + if options[:flags] + if options[:readonly] || options[:readwrite] + raise "conflicting options: flags with readonly and/or readwrite" + end + mode = options[:flags] + end + + open_v2 file.encode("utf-8"), mode, zvfs + end + + @tracefunc = nil + @authorizer = nil + @encoding = nil + @busy_handler = nil + @collations = {} + @functions = {} + @results_as_hash = options[:results_as_hash] + @type_translation = options[:type_translation] + @type_translator = make_type_translator @type_translation + @readonly = mode & Constants::Open::READONLY != 0 + + if block_given? + begin + yield self + ensure + close + end + end + end + + def type_translation= value # :nodoc: + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#type_translation= +SQLite3::Database#type_translation= is deprecated and will be removed +in version 2.0.0. + eowarn + @type_translator = make_type_translator value + @type_translation = value + end + attr_reader :type_translation # :nodoc: + + # Return the type translator employed by this database instance. Each + # database instance has its own type translator; this allows for different + # type handlers to be installed in each instance without affecting other + # instances. Furthermore, the translators are instantiated lazily, so that + # if a database does not use type translation, it will not be burdened by + # the overhead of a useless type translator. (See the Translator class.) + def translator + @translator ||= Translator.new + end + + # Installs (or removes) a block that will be invoked for every access + # to the database. If the block returns 0 (or +nil+), the statement + # is allowed to proceed. Returning 1 causes an authorization error to + # occur, and returning 2 causes the access to be silently denied. + def authorizer( &block ) + self.authorizer = block + end + + # Returns a Statement object representing the given SQL. This does not + # execute the statement; it merely prepares the statement for execution. + # + # The Statement can then be executed using Statement#execute. + # + def prepare sql + stmt = SQLite3::Statement.new( self, sql ) + return stmt unless block_given? + + begin + yield stmt + ensure + stmt.close unless stmt.closed? + end + end + + # Returns the filename for the database named +db_name+. +db_name+ defaults + # to "main". Main return `nil` or an empty string if the database is + # temporary or in-memory. + def filename db_name = 'main' + db_filename db_name + end + + # Executes the given SQL statement. If additional parameters are given, + # they are treated as bind variables, and are bound to the placeholders in + # the query. + # + # Note that if any of the values passed to this are hashes, then the + # key/value pairs are each bound separately, with the key being used as + # the name of the placeholder to bind the value to. + # + # The block is optional. If given, it will be invoked for each row returned + # by the query. Otherwise, any results are accumulated into an array and + # returned wholesale. + # + # See also #execute2, #query, and #execute_batch for additional ways of + # executing statements. + def execute sql, bind_vars = [], *args, &block + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [bind_vars] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for bind parameters as *args will be removed in 2.0.0. + eowarn + end + + prepare( sql ) do |stmt| + stmt.bind_params(bind_vars) + stmt = ResultSet.new self, stmt + + if block_given? + stmt.each do |row| + yield row + end + else + stmt.to_a + end + end + end + + # Executes the given SQL statement, exactly as with #execute. However, the + # first row returned (either via the block, or in the returned array) is + # always the names of the columns. Subsequent rows correspond to the data + # from the result set. + # + # Thus, even if the query itself returns no rows, this method will always + # return at least one row--the names of the columns. + # + # See also #execute, #query, and #execute_batch for additional ways of + # executing statements. + def execute2( sql, *bind_vars ) + prepare( sql ) do |stmt| + result = stmt.execute( *bind_vars ) + if block_given? + yield stmt.columns + result.each { |row| yield row } + else + return result.inject( [ stmt.columns ] ) { |arr,row| + arr << row; arr } + end + end + end + + # Executes all SQL statements in the given string. By contrast, the other + # means of executing queries will only execute the first statement in the + # string, ignoring all subsequent statements. This will execute each one + # in turn. The same bind parameters, if given, will be applied to each + # statement. + # + # This always returns +nil+, making it unsuitable for queries that return + # rows. + # + # See also #execute_batch2 for additional ways of + # executing statments. + def execute_batch( sql, bind_vars = [], *args ) + # FIXME: remove this stuff later + unless [Array, Hash].include?(bind_vars.class) + bind_vars = [bind_vars] + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute_batch with bind parameters +that are not a list of a hash. Please switch to passing bind parameters as an +array or hash. Support for this behavior will be removed in version 2.0.0. + eowarn + end + + # FIXME: remove this stuff later + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [nil] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute_batch with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for this behavior will be removed in version 2.0.0. + eowarn + end + + sql = sql.strip + until sql.empty? do + prepare( sql ) do |stmt| + unless stmt.closed? + # FIXME: this should probably use sqlite3's api for batch execution + # This implementation requires stepping over the results. + if bind_vars.length == stmt.bind_parameter_count + stmt.bind_params(bind_vars) + end + stmt.step + end + sql = stmt.remainder.strip + end + end + # FIXME: we should not return `nil` as a success return value + nil + end + + # Executes all SQL statements in the given string. By contrast, the other + # means of executing queries will only execute the first statement in the + # string, ignoring all subsequent statements. This will execute each one + # in turn. Bind parameters cannot be passed to #execute_batch2. + # + # If a query is made, all values will be returned as strings. + # If no query is made, an empty array will be returned. + # + # Because all values except for 'NULL' are returned as strings, + # a block can be passed to parse the values accordingly. + # + # See also #execute_batch for additional ways of + # executing statments. + def execute_batch2(sql, &block) + if block_given? + result = exec_batch(sql, @results_as_hash) + result.map do |val| + yield val + end + else + exec_batch(sql, @results_as_hash) + end + end + + # This is a convenience method for creating a statement, binding + # paramters to it, and calling execute: + # + # result = db.query( "select * from foo where a=?", [5]) + # # is the same as + # result = db.prepare( "select * from foo where a=?" ).execute( 5 ) + # + # You must be sure to call +close+ on the ResultSet instance that is + # returned, or you could have problems with locks on the table. If called + # with a block, +close+ will be invoked implicitly when the block + # terminates. + def query( sql, bind_vars = [], *args ) + + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [bind_vars] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#query with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for this will be removed in version 2.0.0. + eowarn + end + + result = prepare( sql ).execute( bind_vars ) + if block_given? + begin + yield result + ensure + result.close + end + else + return result + end + end + + # A convenience method for obtaining the first row of a result set, and + # discarding all others. It is otherwise identical to #execute. + # + # See also #get_first_value. + def get_first_row( sql, *bind_vars ) + execute( sql, *bind_vars ).first + end + + # A convenience method for obtaining the first value of the first row of a + # result set, and discarding all other values and rows. It is otherwise + # identical to #execute. + # + # See also #get_first_row. + def get_first_value( sql, *bind_vars ) + query( sql, bind_vars ) do |rs| + if (row = rs.next) + return @results_as_hash ? row[rs.columns[0]] : row[0] + end + end + nil + end + + alias :busy_timeout :busy_timeout= + + # Creates a new function for use in SQL statements. It will be added as + # +name+, with the given +arity+. (For variable arity functions, use + # -1 for the arity.) + # + # The block should accept at least one parameter--the FunctionProxy + # instance that wraps this function invocation--and any other + # arguments it needs (up to its arity). + # + # The block does not return a value directly. Instead, it will invoke + # the FunctionProxy#result= method on the +func+ parameter and + # indicate the return value that way. + # + # Example: + # + # db.create_function( "maim", 1 ) do |func, value| + # if value.nil? + # func.result = nil + # else + # func.result = value.split(//).sort.join + # end + # end + # + # puts db.get_first_value( "select maim(name) from table" ) + def create_function name, arity, text_rep=Constants::TextRep::UTF8, &block + define_function_with_flags(name, text_rep) do |*args| + fp = FunctionProxy.new + block.call(fp, *args) + fp.result + end + self + end + + # Creates a new aggregate function for use in SQL statements. Aggregate + # functions are functions that apply over every row in the result set, + # instead of over just a single row. (A very common aggregate function + # is the "count" function, for determining the number of rows that match + # a query.) + # + # The new function will be added as +name+, with the given +arity+. (For + # variable arity functions, use -1 for the arity.) + # + # The +step+ parameter must be a proc object that accepts as its first + # parameter a FunctionProxy instance (representing the function + # invocation), with any subsequent parameters (up to the function's arity). + # The +step+ callback will be invoked once for each row of the result set. + # + # The +finalize+ parameter must be a +proc+ object that accepts only a + # single parameter, the FunctionProxy instance representing the current + # function invocation. It should invoke FunctionProxy#result= to + # store the result of the function. + # + # Example: + # + # db.create_aggregate( "lengths", 1 ) do + # step do |func, value| + # func[ :total ] ||= 0 + # func[ :total ] += ( value ? value.length : 0 ) + # end + # + # finalize do |func| + # func.result = func[ :total ] || 0 + # end + # end + # + # puts db.get_first_value( "select lengths(name) from table" ) + # + # See also #create_aggregate_handler for a more object-oriented approach to + # aggregate functions. + def create_aggregate( name, arity, step=nil, finalize=nil, + text_rep=Constants::TextRep::ANY, &block ) + + proxy = Class.new do + def self.step( &block ) + define_method(:step_with_ctx, &block) + end + + def self.finalize( &block ) + define_method(:finalize_with_ctx, &block) + end + end + + if block_given? + proxy.instance_eval(&block) + else + proxy.class_eval do + define_method(:step_with_ctx, step) + define_method(:finalize_with_ctx, finalize) + end + end + + proxy.class_eval do + # class instance variables + @name = name + @arity = arity + + def self.name + @name + end + + def self.arity + @arity + end + + def initialize + @ctx = FunctionProxy.new + end + + def step( *args ) + step_with_ctx(@ctx, *args) + end + + def finalize + finalize_with_ctx(@ctx) + @ctx.result + end + end + define_aggregator2(proxy, name) + end + + # This is another approach to creating an aggregate function (see + # #create_aggregate). Instead of explicitly specifying the name, + # callbacks, arity, and type, you specify a factory object + # (the "handler") that knows how to obtain all of that information. The + # handler should respond to the following messages: + # + # +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This + # message is optional, and if the handler does not respond to it, + # the function will have an arity of -1. + # +name+:: this is the name of the function. The handler _must_ implement + # this message. + # +new+:: this must be implemented by the handler. It should return a new + # instance of the object that will handle a specific invocation of + # the function. + # + # The handler instance (the object returned by the +new+ message, described + # above), must respond to the following messages: + # + # +step+:: this is the method that will be called for each step of the + # aggregate function's evaluation. It should implement the same + # signature as the +step+ callback for #create_aggregate. + # +finalize+:: this is the method that will be called to finalize the + # aggregate function's evaluation. It should implement the + # same signature as the +finalize+ callback for + # #create_aggregate. + # + # Example: + # + # class LengthsAggregateHandler + # def self.arity; 1; end + # def self.name; 'lengths'; end + # + # def initialize + # @total = 0 + # end + # + # def step( ctx, name ) + # @total += ( name ? name.length : 0 ) + # end + # + # def finalize( ctx ) + # ctx.result = @total + # end + # end + # + # db.create_aggregate_handler( LengthsAggregateHandler ) + # puts db.get_first_value( "select lengths(name) from A" ) + def create_aggregate_handler( handler ) + # This is a compatiblity shim so the (basically pointless) FunctionProxy + # "ctx" object is passed as first argument to both step() and finalize(). + # Now its up to the library user whether he prefers to store his + # temporaries as instance varibales or fields in the FunctionProxy. + # The library user still must set the result value with + # FunctionProxy.result= as there is no backwards compatible way to + # change this. + proxy = Class.new(handler) do + def initialize + super + @fp = FunctionProxy.new + end + + def step( *args ) + super(@fp, *args) + end + + def finalize + super(@fp) + @fp.result + end + end + define_aggregator2(proxy, proxy.name) + self + end + + # Define an aggregate function named +name+ using a object template + # object +aggregator+. +aggregator+ must respond to +step+ and +finalize+. + # +step+ will be called with row information and +finalize+ must return the + # return value for the aggregator function. + # + # _API Change:_ +aggregator+ must also implement +clone+. The provided + # +aggregator+ object will serve as template that is cloned to provide the + # individual instances of the aggregate function. Regular ruby objects + # already provide a suitable +clone+. + # The functions arity is the arity of the +step+ method. + def define_aggregator( name, aggregator ) + # Previously, this has been implemented in C. Now this is just yet + # another compatiblity shim + proxy = Class.new do + @template = aggregator + @name = name + + def self.template + @template + end + + def self.name + @name + end + + def self.arity + # this is what sqlite3_obj_method_arity did before + @template.method(:step).arity + end + + def initialize + @klass = self.class.template.clone + end + + def step(*args) + @klass.step(*args) + end + + def finalize + @klass.finalize + end + end + define_aggregator2(proxy, name) + self + end + + # Begins a new transaction. Note that nested transactions are not allowed + # by SQLite, so attempting to nest a transaction will result in a runtime + # exception. + # + # The +mode+ parameter may be either :deferred (the default), + # :immediate, or :exclusive. + # + # If a block is given, the database instance is yielded to it, and the + # transaction is committed when the block terminates. If the block + # raises an exception, a rollback will be performed instead. Note that if + # a block is given, #commit and #rollback should never be called + # explicitly or you'll get an error when the block terminates. + # + # If a block is not given, it is the caller's responsibility to end the + # transaction explicitly, either by calling #commit, or by calling + # #rollback. + def transaction( mode = :deferred ) + execute "begin #{mode.to_s} transaction" + + if block_given? + abort = false + begin + yield self + rescue + abort = true + raise + ensure + abort and rollback or commit + end + end + + true + end + + # Commits the current transaction. If there is no current transaction, + # this will cause an error to be raised. This returns +true+, in order + # to allow it to be used in idioms like + # abort? and rollback or commit. + def commit + execute "commit transaction" + true + end + + # Rolls the current transaction back. If there is no current transaction, + # this will cause an error to be raised. This returns +true+, in order + # to allow it to be used in idioms like + # abort? and rollback or commit. + def rollback + execute "rollback transaction" + true + end + + # Returns +true+ if the database has been open in readonly mode + # A helper to check before performing any operation + def readonly? + @readonly + end + + # A helper class for dealing with custom functions (see #create_function, + # #create_aggregate, and #create_aggregate_handler). It encapsulates the + # opaque function object that represents the current invocation. It also + # provides more convenient access to the API functions that operate on + # the function object. + # + # This class will almost _always_ be instantiated indirectly, by working + # with the create methods mentioned above. + class FunctionProxy + attr_accessor :result + + # Create a new FunctionProxy that encapsulates the given +func+ object. + # If context is non-nil, the functions context will be set to that. If + # it is non-nil, it must quack like a Hash. If it is nil, then none of + # the context functions will be available. + def initialize + @result = nil + @context = {} + end + + # Set the result of the function to the given error message. + # The function will then return that error. + def set_error( error ) + @driver.result_error( @func, error.to_s, -1 ) + end + + # (Only available to aggregate functions.) Returns the number of rows + # that the aggregate has processed so far. This will include the current + # row, and so will always return at least 1. + def count + @driver.aggregate_count( @func ) + end + + # Returns the value with the given key from the context. This is only + # available to aggregate functions. + def []( key ) + @context[ key ] + end + + # Sets the value with the given key in the context. This is only + # available to aggregate functions. + def []=( key, value ) + @context[ key ] = value + end + end + + # Translates a +row+ of data from the database with the given +types+ + def translate_from_db types, row + @type_translator.call types, row + end + + private + + NULL_TRANSLATOR = lambda { |_, row| row } + + def make_type_translator should_translate + if should_translate + lambda { |types, row| + types.zip(row).map do |type, value| + translator.translate( type, value ) + end + } + else + NULL_TRANSLATOR + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/errors.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/errors.rb new file mode 100644 index 0000000000..87c7b4ee70 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/errors.rb @@ -0,0 +1,35 @@ +require 'sqlite3/constants' + +module SQLite3 + class Exception < ::StandardError + # A convenience for accessing the error code for this exception. + attr_reader :code + end + + class SQLException < Exception; end + class InternalException < Exception; end + class PermissionException < Exception; end + class AbortException < Exception; end + class BusyException < Exception; end + class LockedException < Exception; end + class MemoryException < Exception; end + class ReadOnlyException < Exception; end + class InterruptException < Exception; end + class IOException < Exception; end + class CorruptException < Exception; end + class NotFoundException < Exception; end + class FullException < Exception; end + class CantOpenException < Exception; end + class ProtocolException < Exception; end + class EmptyException < Exception; end + class SchemaChangedException < Exception; end + class TooBigException < Exception; end + class ConstraintException < Exception; end + class MismatchException < Exception; end + class MisuseException < Exception; end + class UnsupportedException < Exception; end + class AuthorizationException < Exception; end + class FormatException < Exception; end + class RangeException < Exception; end + class NotADatabaseException < Exception; end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/pragmas.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/pragmas.rb new file mode 100644 index 0000000000..6fd4ae11e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/pragmas.rb @@ -0,0 +1,588 @@ +require 'sqlite3/errors' + +module SQLite3 + + # This module is intended for inclusion solely by the Database class. It + # defines convenience methods for the various pragmas supported by SQLite3. + # + # For a detailed description of these pragmas, see the SQLite3 documentation + # at http://sqlite.org/pragma.html. + module Pragmas + + # Returns +true+ or +false+ depending on the value of the named pragma. + def get_boolean_pragma( name ) + get_first_value( "PRAGMA #{name}" ) != "0" + end + + # Sets the given pragma to the given boolean value. The value itself + # may be +true+ or +false+, or any other commonly used string or + # integer that represents truth. + def set_boolean_pragma( name, mode ) + case mode + when String + case mode.downcase + when "on", "yes", "true", "y", "t"; mode = "'ON'" + when "off", "no", "false", "n", "f"; mode = "'OFF'" + else + raise Exception, + "unrecognized pragma parameter #{mode.inspect}" + end + when true, 1 + mode = "ON" + when false, 0, nil + mode = "OFF" + else + raise Exception, + "unrecognized pragma parameter #{mode.inspect}" + end + + execute( "PRAGMA #{name}=#{mode}" ) + end + + # Requests the given pragma (and parameters), and if the block is given, + # each row of the result set will be yielded to it. Otherwise, the results + # are returned as an array. + def get_query_pragma( name, *parms, &block ) # :yields: row + if parms.empty? + execute( "PRAGMA #{name}", &block ) + else + args = "'" + parms.join("','") + "'" + execute( "PRAGMA #{name}( #{args} )", &block ) + end + end + + # Return the value of the given pragma. + def get_enum_pragma( name ) + get_first_value( "PRAGMA #{name}" ) + end + + # Set the value of the given pragma to +mode+. The +mode+ parameter must + # conform to one of the values in the given +enum+ array. Each entry in + # the array is another array comprised of elements in the enumeration that + # have duplicate values. See #synchronous, #default_synchronous, + # #temp_store, and #default_temp_store for usage examples. + def set_enum_pragma( name, mode, enums ) + match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } } + raise Exception, + "unrecognized #{name} #{mode.inspect}" unless match + execute( "PRAGMA #{name}='#{match.first.upcase}'" ) + end + + # Returns the value of the given pragma as an integer. + def get_int_pragma( name ) + get_first_value( "PRAGMA #{name}" ).to_i + end + + # Set the value of the given pragma to the integer value of the +value+ + # parameter. + def set_int_pragma( name, value ) + execute( "PRAGMA #{name}=#{value.to_i}" ) + end + + # The enumeration of valid synchronous modes. + SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ] + + # The enumeration of valid temp store modes. + TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ] + + # The enumeration of valid auto vacuum modes. + AUTO_VACUUM_MODES = [ [ 'none', 0 ], [ 'full', 1 ], [ 'incremental', 2 ] ] + + # The list of valid journaling modes. + JOURNAL_MODES = [ [ 'delete' ], [ 'truncate' ], [ 'persist' ], [ 'memory' ], + [ 'wal' ], [ 'off' ] ] + + # The list of valid locking modes. + LOCKING_MODES = [ [ 'normal' ], [ 'exclusive' ] ] + + # The list of valid encodings. + ENCODINGS = [ [ 'utf-8' ], [ 'utf-16' ], [ 'utf-16le' ], [ 'utf-16be ' ] ] + + # The list of valid WAL checkpoints. + WAL_CHECKPOINTS = [ [ 'passive' ], [ 'full' ], [ 'restart' ], [ 'truncate' ] ] + + def application_id + get_int_pragma "application_id" + end + + def application_id=( integer ) + set_int_pragma "application_id", integer + end + + def auto_vacuum + get_enum_pragma "auto_vacuum" + end + + def auto_vacuum=( mode ) + set_enum_pragma "auto_vacuum", mode, AUTO_VACUUM_MODES + end + + def automatic_index + get_boolean_pragma "automatic_index" + end + + def automatic_index=( mode ) + set_boolean_pragma "automatic_index", mode + end + + def busy_timeout + get_int_pragma "busy_timeout" + end + + def busy_timeout=( milliseconds ) + set_int_pragma "busy_timeout", milliseconds + end + + def cache_size + get_int_pragma "cache_size" + end + + def cache_size=( size ) + set_int_pragma "cache_size", size + end + + def cache_spill + get_boolean_pragma "cache_spill" + end + + def cache_spill=( mode ) + set_boolean_pragma "cache_spill", mode + end + + def case_sensitive_like=( mode ) + set_boolean_pragma "case_sensitive_like", mode + end + + def cell_size_check + get_boolean_pragma "cell_size_check" + end + + def cell_size_check=( mode ) + set_boolean_pragma "cell_size_check", mode + end + + def checkpoint_fullfsync + get_boolean_pragma "checkpoint_fullfsync" + end + + def checkpoint_fullfsync=( mode ) + set_boolean_pragma "checkpoint_fullfsync", mode + end + + def collation_list( &block ) # :yields: row + get_query_pragma "collation_list", &block + end + + def compile_options( &block ) # :yields: row + get_query_pragma "compile_options", &block + end + + def count_changes + get_boolean_pragma "count_changes" + end + + def count_changes=( mode ) + set_boolean_pragma "count_changes", mode + end + + def data_version + get_int_pragma "data_version" + end + + def database_list( &block ) # :yields: row + get_query_pragma "database_list", &block + end + + def default_cache_size + get_int_pragma "default_cache_size" + end + + def default_cache_size=( size ) + set_int_pragma "default_cache_size", size + end + + def default_synchronous + get_enum_pragma "default_synchronous" + end + + def default_synchronous=( mode ) + set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES + end + + def default_temp_store + get_enum_pragma "default_temp_store" + end + + def default_temp_store=( mode ) + set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES + end + + def defer_foreign_keys + get_boolean_pragma "defer_foreign_keys" + end + + def defer_foreign_keys=( mode ) + set_boolean_pragma "defer_foreign_keys", mode + end + + def encoding + get_enum_pragma "encoding" + end + + def encoding=( mode ) + set_enum_pragma "encoding", mode, ENCODINGS + end + + def foreign_key_check( *table, &block ) # :yields: row + get_query_pragma "foreign_key_check", *table, &block + end + + def foreign_key_list( table, &block ) # :yields: row + get_query_pragma "foreign_key_list", table, &block + end + + def foreign_keys + get_boolean_pragma "foreign_keys" + end + + def foreign_keys=( mode ) + set_boolean_pragma "foreign_keys", mode + end + + def freelist_count + get_int_pragma "freelist_count" + end + + def full_column_names + get_boolean_pragma "full_column_names" + end + + def full_column_names=( mode ) + set_boolean_pragma "full_column_names", mode + end + + def fullfsync + get_boolean_pragma "fullfsync" + end + + def fullfsync=( mode ) + set_boolean_pragma "fullfsync", mode + end + + def ignore_check_constraints=( mode ) + set_boolean_pragma "ignore_check_constraints", mode + end + + def incremental_vacuum( pages, &block ) # :yields: row + get_query_pragma "incremental_vacuum", pages, &block + end + + def index_info( index, &block ) # :yields: row + get_query_pragma "index_info", index, &block + end + + def index_list( table, &block ) # :yields: row + get_query_pragma "index_list", table, &block + end + + def index_xinfo( index, &block ) # :yields: row + get_query_pragma "index_xinfo", index, &block + end + + def integrity_check( *num_errors, &block ) # :yields: row + get_query_pragma "integrity_check", *num_errors, &block + end + + def journal_mode + get_enum_pragma "journal_mode" + end + + def journal_mode=( mode ) + set_enum_pragma "journal_mode", mode, JOURNAL_MODES + end + + def journal_size_limit + get_int_pragma "journal_size_limit" + end + + def journal_size_limit=( size ) + set_int_pragma "journal_size_limit", size + end + + def legacy_file_format + get_boolean_pragma "legacy_file_format" + end + + def legacy_file_format=( mode ) + set_boolean_pragma "legacy_file_format", mode + end + + def locking_mode + get_enum_pragma "locking_mode" + end + + def locking_mode=( mode ) + set_enum_pragma "locking_mode", mode, LOCKING_MODES + end + + def max_page_count + get_int_pragma "max_page_count" + end + + def max_page_count=( size ) + set_int_pragma "max_page_count", size + end + + def mmap_size + get_int_pragma "mmap_size" + end + + def mmap_size=( size ) + set_int_pragma "mmap_size", size + end + + def page_count + get_int_pragma "page_count" + end + + def page_size + get_int_pragma "page_size" + end + + def page_size=( size ) + set_int_pragma "page_size", size + end + + def parser_trace=( mode ) + set_boolean_pragma "parser_trace", mode + end + + def query_only + get_boolean_pragma "query_only" + end + + def query_only=( mode ) + set_boolean_pragma "query_only", mode + end + + def quick_check( *num_errors, &block ) # :yields: row + get_query_pragma "quick_check", *num_errors, &block + end + + def read_uncommitted + get_boolean_pragma "read_uncommitted" + end + + def read_uncommitted=( mode ) + set_boolean_pragma "read_uncommitted", mode + end + + def recursive_triggers + get_boolean_pragma "recursive_triggers" + end + + def recursive_triggers=( mode ) + set_boolean_pragma "recursive_triggers", mode + end + + def reverse_unordered_selects + get_boolean_pragma "reverse_unordered_selects" + end + + def reverse_unordered_selects=( mode ) + set_boolean_pragma "reverse_unordered_selects", mode + end + + def schema_cookie + get_int_pragma "schema_cookie" + end + + def schema_cookie=( cookie ) + set_int_pragma "schema_cookie", cookie + end + + def schema_version + get_int_pragma "schema_version" + end + + def schema_version=( version ) + set_int_pragma "schema_version", version + end + + def secure_delete + get_boolean_pragma "secure_delete" + end + + def secure_delete=( mode ) + set_boolean_pragma "secure_delete", mode + end + + def short_column_names + get_boolean_pragma "short_column_names" + end + + def short_column_names=( mode ) + set_boolean_pragma "short_column_names", mode + end + + def shrink_memory + execute( "PRAGMA shrink_memory" ) + end + + def soft_heap_limit + get_int_pragma "soft_heap_limit" + end + + def soft_heap_limit=( mode ) + set_int_pragma "soft_heap_limit", mode + end + + def stats( &block ) # :yields: row + get_query_pragma "stats", &block + end + + def synchronous + get_enum_pragma "synchronous" + end + + def synchronous=( mode ) + set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES + end + + def temp_store + get_enum_pragma "temp_store" + end + + def temp_store=( mode ) + set_enum_pragma "temp_store", mode, TEMP_STORE_MODES + end + + def threads + get_int_pragma "threads" + end + + def threads=( count ) + set_int_pragma "threads", count + end + + def user_cookie + get_int_pragma "user_cookie" + end + + def user_cookie=( cookie ) + set_int_pragma "user_cookie", cookie + end + + def user_version + get_int_pragma "user_version" + end + + def user_version=( version ) + set_int_pragma "user_version", version + end + + def vdbe_addoptrace=( mode ) + set_boolean_pragma "vdbe_addoptrace", mode + end + + def vdbe_debug=( mode ) + set_boolean_pragma "vdbe_debug", mode + end + + def vdbe_listing=( mode ) + set_boolean_pragma "vdbe_listing", mode + end + + def vdbe_trace + get_boolean_pragma "vdbe_trace" + end + + def vdbe_trace=( mode ) + set_boolean_pragma "vdbe_trace", mode + end + + def wal_autocheckpoint + get_int_pragma "wal_autocheckpoint" + end + + def wal_autocheckpoint=( mode ) + set_int_pragma "wal_autocheckpoint", mode + end + + def wal_checkpoint + get_enum_pragma "wal_checkpoint" + end + + def wal_checkpoint=( mode ) + set_enum_pragma "wal_checkpoint", mode, WAL_CHECKPOINTS + end + + def writable_schema=( mode ) + set_boolean_pragma "writable_schema", mode + end + + ### + # Returns information about +table+. Yields each row of table information + # if a block is provided. + def table_info table + stmt = prepare "PRAGMA table_info(#{table})" + columns = stmt.columns + + needs_tweak_default = + version_compare(SQLite3.libversion.to_s, "3.3.7") > 0 + + result = [] unless block_given? + stmt.each do |row| + new_row = Hash[columns.zip(row)] + + # FIXME: This should be removed but is required for older versions + # of rails + if(Object.const_defined?(:ActiveRecord)) + new_row['notnull'] = new_row['notnull'].to_s + end + + tweak_default(new_row) if needs_tweak_default + + if block_given? + yield new_row + else + result << new_row + end + end + stmt.close + + result + end + + private + + # Compares two version strings + def version_compare(v1, v2) + v1 = v1.split(".").map { |i| i.to_i } + v2 = v2.split(".").map { |i| i.to_i } + parts = [v1.length, v2.length].max + v1.push 0 while v1.length < parts + v2.push 0 while v2.length < parts + v1.zip(v2).each do |a,b| + return -1 if a < b + return 1 if a > b + end + return 0 + end + + # Since SQLite 3.3.8, the table_info pragma has returned the default + # value of the row as a quoted SQL value. This method essentially + # unquotes those values. + def tweak_default(hash) + case hash["dflt_value"] + when /^null$/i + hash["dflt_value"] = nil + when /^'(.*)'$/m + hash["dflt_value"] = $1.gsub(/''/, "'") + when /^"(.*)"$/m + hash["dflt_value"] = $1.gsub(/""/, '"') + end + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/resultset.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/resultset.rb new file mode 100644 index 0000000000..5c1cc19b79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/resultset.rb @@ -0,0 +1,187 @@ +require 'sqlite3/constants' +require 'sqlite3/errors' + +module SQLite3 + + # The ResultSet object encapsulates the enumerability of a query's output. + # It is a simple cursor over the data that the query returns. It will + # very rarely (if ever) be instantiated directly. Instead, clients should + # obtain a ResultSet instance via Statement#execute. + class ResultSet + include Enumerable + + class ArrayWithTypes < Array # :nodoc: + attr_accessor :types + end + + class ArrayWithTypesAndFields < Array # :nodoc: + attr_writer :types + attr_writer :fields + + def types + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#types. This method will be removed in +sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet +object that created this object + eowarn + @types + end + + def fields + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#fields. This method will be removed in +sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet +object that created this object + eowarn + @fields + end + end + + # The class of which we return an object in case we want a Hash as + # result. + class HashWithTypesAndFields < Hash # :nodoc: + attr_writer :types + attr_writer :fields + + def types + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#types. This method will be removed in +sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet +object that created this object + eowarn + @types + end + + def fields + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#fields. This method will be removed in +sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet +object that created this object + eowarn + @fields + end + + def [] key + key = fields[key] if key.is_a? Numeric + super key + end + end + + # Create a new ResultSet attached to the given database, using the + # given sql text. + def initialize db, stmt + @db = db + @stmt = stmt + end + + # Reset the cursor, so that a result set which has reached end-of-file + # can be rewound and reiterated. + def reset( *bind_params ) + @stmt.reset! + @stmt.bind_params( *bind_params ) + @eof = false + end + + # Query whether the cursor has reached the end of the result set or not. + def eof? + @stmt.done? + end + + # Obtain the next row from the cursor. If there are no more rows to be + # had, this will return +nil+. If type translation is active on the + # corresponding database, the values in the row will be translated + # according to their types. + # + # The returned value will be an array, unless Database#results_as_hash has + # been set to +true+, in which case the returned value will be a hash. + # + # For arrays, the column names are accessible via the +fields+ property, + # and the column types are accessible via the +types+ property. + # + # For hashes, the column names are the keys of the hash, and the column + # types are accessible via the +types+ property. + def next + if @db.results_as_hash + return next_hash + end + + row = @stmt.step + return nil if @stmt.done? + + row = @db.translate_from_db @stmt.types, row + + if row.respond_to?(:fields) + # FIXME: this can only happen if the translator returns something + # that responds to `fields`. Since we're removing the translator + # in 2.0, we can remove this branch in 2.0. + row = ArrayWithTypes.new(row) + else + # FIXME: the `fields` and `types` methods are deprecated on this + # object for version 2.0, so we can safely remove this branch + # as well. + row = ArrayWithTypesAndFields.new(row) + end + + row.fields = @stmt.columns + row.types = @stmt.types + row + end + + # Required by the Enumerable mixin. Provides an internal iterator over the + # rows of the result set. + def each + while node = self.next + yield node + end + end + + # Provides an internal iterator over the rows of the result set where + # each row is yielded as a hash. + def each_hash + while node = next_hash + yield node + end + end + + # Closes the statement that spawned this result set. + # Use with caution! Closing a result set will automatically + # close any other result sets that were spawned from the same statement. + def close + @stmt.close + end + + # Queries whether the underlying statement has been closed or not. + def closed? + @stmt.closed? + end + + # Returns the types of the columns returned by this result set. + def types + @stmt.types + end + + # Returns the names of the columns returned by this result set. + def columns + @stmt.columns + end + + # Return the next row as a hash + def next_hash + row = @stmt.step + return nil if @stmt.done? + + # FIXME: type translation is deprecated, so this can be removed + # in 2.0 + row = @db.translate_from_db @stmt.types, row + + # FIXME: this can be switched to a regular hash in 2.0 + row = HashWithTypesAndFields[*@stmt.columns.zip(row).flatten] + + # FIXME: these methods are deprecated for version 2.0, so we can remove + # this code in 2.0 + row.fields = @stmt.columns + row.types = @stmt.types + row + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/sqlite3_native.so new file mode 100755 index 0000000000..569fb67ef2 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/statement.rb new file mode 100644 index 0000000000..d3ae4dd5aa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/statement.rb @@ -0,0 +1,144 @@ +require 'sqlite3/errors' +require 'sqlite3/resultset' + +class String + def to_blob + SQLite3::Blob.new( self ) + end +end + +module SQLite3 + # A statement represents a prepared-but-unexecuted SQL query. It will rarely + # (if ever) be instantiated directly by a client, and is most often obtained + # via the Database#prepare method. + class Statement + include Enumerable + + # This is any text that followed the first valid SQL statement in the text + # with which the statement was initialized. If there was no trailing text, + # this will be the empty string. + attr_reader :remainder + + # Binds the given variables to the corresponding placeholders in the SQL + # text. + # + # See Database#execute for a description of the valid placeholder + # syntaxes. + # + # Example: + # + # stmt = db.prepare( "select * from table where a=? and b=?" ) + # stmt.bind_params( 15, "hello" ) + # + # See also #execute, #bind_param, Statement#bind_param, and + # Statement#bind_params. + def bind_params( *bind_vars ) + index = 1 + bind_vars.flatten.each do |var| + if Hash === var + var.each { |key, val| bind_param key, val } + else + bind_param index, var + index += 1 + end + end + end + + # Execute the statement. This creates a new ResultSet object for the + # statement's virtual machine. If a block was given, the new ResultSet will + # be yielded to it; otherwise, the ResultSet will be returned. + # + # Any parameters will be bound to the statement using #bind_params. + # + # Example: + # + # stmt = db.prepare( "select * from table" ) + # stmt.execute do |result| + # ... + # end + # + # See also #bind_params, #execute!. + def execute( *bind_vars ) + reset! if active? || done? + + bind_params(*bind_vars) unless bind_vars.empty? + @results = ResultSet.new(@connection, self) + + step if 0 == column_count + + yield @results if block_given? + @results + end + + # Execute the statement. If no block was given, this returns an array of + # rows returned by executing the statement. Otherwise, each row will be + # yielded to the block. + # + # Any parameters will be bound to the statement using #bind_params. + # + # Example: + # + # stmt = db.prepare( "select * from table" ) + # stmt.execute! do |row| + # ... + # end + # + # See also #bind_params, #execute. + def execute!( *bind_vars, &block ) + execute(*bind_vars) + block_given? ? each(&block) : to_a + end + + # Returns true if the statement is currently active, meaning it has an + # open result set. + def active? + !done? + end + + # Return an array of the column names for this statement. Note that this + # may execute the statement in order to obtain the metadata; this makes it + # a (potentially) expensive operation. + def columns + get_metadata unless @columns + return @columns + end + + def each + loop do + val = step + break self if done? + yield val + end + end + + # Return an array of the data types for each column in this statement. Note + # that this may execute the statement in order to obtain the metadata; this + # makes it a (potentially) expensive operation. + def types + must_be_open! + get_metadata unless @types + @types + end + + # Performs a sanity check to ensure that the statement is not + # closed. If it is, an exception is raised. + def must_be_open! # :nodoc: + if closed? + raise SQLite3::Exception, "cannot use a closed statement" + end + end + + private + # A convenience method for obtaining the metadata about the query. Note + # that this will actually execute the SQL, which means it can be a + # (potentially) expensive operation. + def get_metadata + @columns = Array.new(column_count) do |column| + column_name column + end + @types = Array.new(column_count) do |column| + column_decltype column + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/translator.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/translator.rb new file mode 100644 index 0000000000..69f623b072 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/translator.rb @@ -0,0 +1,118 @@ +require 'time' +require 'date' + +module SQLite3 + + # The Translator class encapsulates the logic and callbacks necessary for + # converting string data to a value of some specified type. Every Database + # instance may have a Translator instance, in order to assist in type + # translation (Database#type_translation). + # + # Further, applications may define their own custom type translation logic + # by registering translator blocks with the corresponding database's + # translator instance (Database#translator). + class Translator + + # Create a new Translator instance. It will be preinitialized with default + # translators for most SQL data types. + def initialize + @translators = Hash.new( proc { |type,value| value } ) + @type_name_cache = {} + register_default_translators + end + + # Add a new translator block, which will be invoked to process type + # translations to the given type. The type should be an SQL datatype, and + # may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical + # information is stripped off and discarded, so type translation decisions + # are made solely on the "base" type name. + # + # The translator block itself should accept two parameters, "type" and + # "value". In this case, the "type" is the full type name (including + # parentheses), so the block itself may include logic for changing how a + # type is translated based on the additional data. The "value" parameter + # is the (string) data to convert. + # + # The block should return the translated value. + def add_translator( type, &block ) # :yields: type, value + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling `add_translator`. +Built in translators are deprecated and will be removed in version 2.0.0 + eowarn + @translators[ type_name( type ) ] = block + end + + # Translate the given string value to a value of the given type. In the + # absense of an installed translator block for the given type, the value + # itself is always returned. Further, +nil+ values are never translated, + # and are always passed straight through regardless of the type parameter. + def translate( type, value ) + unless value.nil? + # FIXME: this is a hack to support Sequel + if type && %w{ datetime timestamp }.include?(type.downcase) + @translators[ type_name( type ) ].call( type, value.to_s ) + else + @translators[ type_name( type ) ].call( type, value ) + end + end + end + + # A convenience method for working with type names. This returns the "base" + # type name, without any parenthetical data. + def type_name( type ) + @type_name_cache[type] ||= begin + type = "" if type.nil? + type = $1 if type =~ /^(.*?)\(/ + type.upcase + end + end + private :type_name + + # Register the default translators for the current Translator instance. + # This includes translators for most major SQL data types. + def register_default_translators + [ "time", + "timestamp" ].each { |type| add_translator( type ) { |t, v| Time.parse( v ) } } + + add_translator( "date" ) { |t,v| Date.parse(v) } + add_translator( "datetime" ) { |t,v| DateTime.parse(v) } + + [ "decimal", + "float", + "numeric", + "double", + "real", + "dec", + "fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } } + + [ "integer", + "smallint", + "mediumint", + "int", + "bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } } + + [ "bit", + "bool", + "boolean" ].each do |type| + add_translator( type ) do |t,v| + !( v.strip.gsub(/00+/,"0") == "0" || + v.downcase == "false" || + v.downcase == "f" || + v.downcase == "no" || + v.downcase == "n" ) + end + end + + add_translator( "tinyint" ) do |type, value| + if type =~ /\(\s*1\s*\)/ + value.to_i == 1 + else + value.to_i + end + end + end + private :register_default_translators + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/value.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/value.rb new file mode 100644 index 0000000000..e5e5bf2c6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/value.rb @@ -0,0 +1,57 @@ +require 'sqlite3/constants' + +module SQLite3 + + class Value + attr_reader :handle + + def initialize( db, handle ) + @driver = db.driver + @handle = handle + end + + def null? + type == :null + end + + def to_blob + @driver.value_blob( @handle ) + end + + def length( utf16=false ) + if utf16 + @driver.value_bytes16( @handle ) + else + @driver.value_bytes( @handle ) + end + end + + def to_f + @driver.value_double( @handle ) + end + + def to_i + @driver.value_int( @handle ) + end + + def to_int64 + @driver.value_int64( @handle ) + end + + def to_s( utf16=false ) + @driver.value_text( @handle, utf16 ) + end + + def type + case @driver.value_type( @handle ) + when Constants::ColumnType::INTEGER then :int + when Constants::ColumnType::FLOAT then :float + when Constants::ColumnType::TEXT then :text + when Constants::ColumnType::BLOB then :blob + when Constants::ColumnType::NULL then :null + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/version.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/version.rb new file mode 100644 index 0000000000..efbd39603a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/lib/sqlite3/version.rb @@ -0,0 +1,25 @@ +module SQLite3 + + VERSION = '1.4.2' + + module VersionProxy + + MAJOR = 1 + MINOR = 4 + TINY = 2 + BUILD = nil + + STRING = [ MAJOR, MINOR, TINY, BUILD ].compact.join( "." ) + #:beta-tag: + + VERSION = ::SQLite3::VERSION + end + + def self.const_missing(name) + return super unless name == :Version + warn(<<-eowarn) if $VERBOSE +#{caller[0]}: SQLite::Version will be removed in sqlite3-ruby version 2.0.0 + eowarn + VersionProxy + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/faq.rake b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/faq.rake new file mode 100644 index 0000000000..9f8f652de2 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/faq.rake @@ -0,0 +1,9 @@ +# Generate FAQ +desc "Generate the FAQ document" +task :faq => ['faq/faq.html'] + +file 'faq/faq.html' => ['faq/faq.rb', 'faq/faq.yml'] do + cd 'faq' do + ruby "faq.rb > faq.html" + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/gem.rake b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/gem.rake new file mode 100644 index 0000000000..be41dff39c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/gem.rake @@ -0,0 +1,40 @@ +begin + require 'hoe' +rescue LoadError + # try with rubygems? + require 'rubygems' + require 'hoe' +end + +Hoe.plugin :debugging, :doofus, :git, :minitest, :bundler, :gemspec + +HOE = Hoe.spec 'sqlite3' do + developer 'Jamis Buck', 'jamis@37signals.com' + developer 'Luis Lavena', 'luislavena@gmail.com' + developer 'Aaron Patterson', 'aaron@tenderlovemaking.com' + + license "BSD-3-Clause" + + self.readme_file = 'README.rdoc' + self.history_file = 'CHANGELOG.rdoc' + self.extra_rdoc_files = FileList['*.rdoc', 'ext/**/*.c'] + + require_ruby_version ">= 1.8.7" + require_rubygems_version ">= 1.3.5" + + spec_extras[:extensions] = ["ext/sqlite3/extconf.rb"] + spec_extras[:metadata] = {'msys2_mingw_dependencies' => 'sqlite3'} + + extra_dev_deps << ['rake-compiler', "~> 1.0"] + extra_dev_deps << ['rake-compiler-dock', "~> 0.6.0"] + extra_dev_deps << ["mini_portile", "~> 0.6.2"] + extra_dev_deps << ["minitest", "~> 5.0"] + extra_dev_deps << ["hoe-bundler", "~> 1.0"] + extra_dev_deps << ["hoe-gemspec", "~> 1.0"] + + clean_globs.push('**/test.db') +end + +Hoe.add_include_dirs '.' + +# vim: syntax=ruby diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/native.rake b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/native.rake new file mode 100644 index 0000000000..c5aea6df2e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/native.rake @@ -0,0 +1,56 @@ +# use rake-compiler for building the extension +require 'rake/extensiontask' +require 'rake/extensioncompiler' + +# NOTE: version used by cross compilation of Windows native extension +# It do not affect compilation under other operating systems +# The version indicated is the minimum DLL suggested for correct functionality +BINARY_VERSION = "3.8.11.1" +URL_VERSION = "3081101" +URL_PATH = "/2015" + +task :devkit do + begin + require "devkit" + rescue LoadError => e + abort "Failed to activate RubyInstaller's DevKit required for compilation." + end +end + +# build sqlite3_native C extension +RUBY_EXTENSION = Rake::ExtensionTask.new('sqlite3_native', HOE.spec) do |ext| + # where to locate the extension + ext.ext_dir = 'ext/sqlite3' + + # where native extension will be copied (matches makefile) + ext.lib_dir = "lib/sqlite3" + + # clean binary folders always + CLEAN.include("#{ext.lib_dir}/?.?") + + # automatically add build options to avoid need of manual input + if RUBY_PLATFORM =~ /mswin|mingw/ then + # define target for extension (supporting fat binaries) + RUBY_VERSION =~ /(\d+\.\d+)/ + ext.lib_dir = "lib/sqlite3/#{$1}" + else + + # detect cross-compiler available + begin + Rake::ExtensionCompiler.mingw_host + ext.cross_compile = true + ext.cross_platform = ['i386-mswin32-60', 'i386-mingw32', 'x64-mingw32'] + ext.cross_compiling do |spec| + # The fat binary gem doesn't depend on the sqlite3 package, since it bundles the library. + spec.metadata.delete('msys2_mingw_dependencies') + end + rescue RuntimeError + # noop + end + end +end + +# ensure things are compiled prior testing +task :test => [:compile] + +# vim: syntax=ruby diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/vendor_sqlite3.rake b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/vendor_sqlite3.rake new file mode 100644 index 0000000000..bb0df797e3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/rakelib/vendor_sqlite3.rake @@ -0,0 +1,97 @@ +require "rake/clean" +require "rake/extensioncompiler" +require "mini_portile" + +CLOBBER.include("ports") + +directory "ports" + +def define_sqlite_task(platform, host) + recipe = MiniPortile.new "sqlite3", BINARY_VERSION + recipe.files << "http://sqlite.org#{URL_PATH}/sqlite-autoconf-#{URL_VERSION}.tar.gz" + recipe.host = host + + desc "Compile sqlite3 for #{platform} (#{host})" + task "ports:sqlite3:#{platform}" => ["ports"] do |t| + checkpoint = "ports/.#{recipe.name}-#{recipe.version}-#{recipe.host}.installed" + + unless File.exist?(checkpoint) + cflags = "-O2 -DSQLITE_ENABLE_COLUMN_METADATA" + cflags << " -fPIC" if recipe.host && recipe.host.include?("x86_64") + recipe.configure_options << "CFLAGS='#{cflags}'" + recipe.cook + touch checkpoint + end + end + + recipe +end + +# native sqlite3 compilation +recipe = define_sqlite_task(RUBY_PLATFORM, RbConfig::CONFIG["host"]) + +# force compilation of sqlite3 when working natively under MinGW +if RUBY_PLATFORM =~ /mingw/ + RUBY_EXTENSION.config_options << "--with-opt-dir=#{recipe.path}" + + # also prepend DevKit into compilation phase + Rake::Task["compile"].prerequisites.unshift "devkit", "ports:sqlite3:#{RUBY_PLATFORM}" + Rake::Task["native"].prerequisites.unshift "devkit", "ports:sqlite3:#{RUBY_PLATFORM}" +end + +# trick to test local compilation of sqlite3 +if ENV["USE_MINI_PORTILE"] == "true" + # fake recipe so we can build a directory to it + recipe = MiniPortile.new "sqlite3", BINARY_VERSION + recipe.host = RbConfig::CONFIG["host"] + + RUBY_EXTENSION.config_options << "--with-opt-dir=#{recipe.path}" + + # compile sqlite3 first + Rake::Task["compile"].prerequisites.unshift "ports:sqlite3:#{RUBY_PLATFORM}" +end + +# iterate over all cross-compilation platforms and define the proper +# sqlite3 recipe for it. +if RUBY_EXTENSION.cross_compile + config_path = File.expand_path("~/.rake-compiler/config.yml") + if File.exist?(config_path) + # obtains platforms from rake-compiler's config.yml + config_file = YAML.load_file(config_path) + + Array(RUBY_EXTENSION.cross_platform).each do |platform| + # obtain platform from rbconfig file + config_key = config_file.keys.sort.find { |key| + key.start_with?("rbconfig-#{platform}-") + } + rbfile = config_file[config_key] + + # skip if rbconfig cannot be read + next unless File.exist?(rbfile) + + host = IO.read(rbfile).match(/CONFIG\["CC"\] = "(.*)"/)[1].sub(/\-gcc/, '') + recipe = define_sqlite_task(platform, host) + + RUBY_EXTENSION.cross_config_options << { + platform => "--with-opt-dir=#{recipe.path}" + } + + # pre-compile sqlite3 port when cross-compiling + task :cross => "ports:sqlite3:#{platform}" + end + else + warn "rake-compiler configuration doesn't exist, but is required for ports" + end +end + +task :cross do + ["CC", "CXX", "LDFLAGS", "CPPFLAGS", "RUBYOPT"].each do |var| + ENV.delete(var) + end +end + +desc "Build windows binary gems per rake-compiler-dock." +task "gem:windows" do + require "rake_compiler_dock" + RakeCompilerDock.sh "bundle && rake cross native gem MAKE='nice make -j`nproc`'" +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/setup.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/setup.rb new file mode 100644 index 0000000000..bce201de3b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/setup.rb @@ -0,0 +1,1333 @@ +# +# setup.rb +# +# Copyright (c) 2000-2004 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +# +# For backward compatibility +# + +unless Enumerable.method_defined?(:map) + module Enumerable + alias map collect + end +end + +unless Enumerable.method_defined?(:detect) + module Enumerable + alias detect find + end +end + +unless Enumerable.method_defined?(:select) + module Enumerable + alias select find_all + end +end + +unless Enumerable.method_defined?(:reject) + module Enumerable + def reject + select {|i| not yield(i) } + end + end +end + +unless Enumerable.method_defined?(:inject) + module Enumerable + def inject(result) + each do |i| + result = yield(result, i) + end + result + end + end +end + +unless Enumerable.method_defined?(:any?) + module Enumerable + def any? + each do |i| + return true if yield(i) + end + false + end + end +end + +unless File.respond_to?(:read) + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +# +# Application independent utilities +# + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted windows stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + +# +# Config +# + +if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + require arg.split(/=/, 2)[1] + $".push 'rbconfig.rb' +else + require 'rbconfig' +end + +def multipackage_install? + FileTest.directory?(File.dirname($0) + '/packages') +end + + +class ConfigTable + + c = ::RbConfig::CONFIG + + rubypath = c['bindir'] + '/' + c['ruby_install_name'] + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + subprefix = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix') + } + + if c['rubylibdir'] + # V < 1.6.3 + stdruby = subprefix.call(c['rubylibdir']) + siteruby = subprefix.call(c['sitedir']) + versite = subprefix.call(c['sitelibdir']) + sodir = subprefix.call(c['sitearchdir']) + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + stdruby = "$prefix/lib/ruby/#{version}" + siteruby = subprefix.call(c['sitedir']) + versite = siteruby + '/' + version + sodir = "$site-ruby/#{c['arch']}" + else + # V < 1.4.4 + stdruby = "$prefix/lib/ruby/#{version}" + siteruby = "$prefix/lib/ruby/#{version}/site_ruby" + versite = siteruby + sodir = "$site-ruby/#{c['arch']}" + end + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + common_descripters = [ + [ 'prefix', [ c['prefix'], + 'path', + 'path prefix of target environment' ] ], + [ 'std-ruby', [ stdruby, + 'path', + 'the directory for standard ruby libraries' ] ], + [ 'site-ruby-common', [ siteruby, + 'path', + 'the directory for version-independent non-standard ruby libraries' ] ], + [ 'site-ruby', [ versite, + 'path', + 'the directory for non-standard ruby libraries' ] ], + [ 'bin-dir', [ '$prefix/bin', + 'path', + 'the directory for commands' ] ], + [ 'rb-dir', [ '$site-ruby', + 'path', + 'the directory for ruby scripts' ] ], + [ 'so-dir', [ sodir, + 'path', + 'the directory for ruby extentions' ] ], + [ 'data-dir', [ '$prefix/share', + 'path', + 'the directory for shared data' ] ], + [ 'ruby-path', [ rubypath, + 'path', + 'path to set to #! line' ] ], + [ 'ruby-prog', [ rubypath, + 'name', + 'the ruby program using for installation' ] ], + [ 'make-prog', [ makeprog, + 'name', + 'the make program to compile ruby extentions' ] ], + [ 'without-ext', [ 'no', + 'yes/no', + 'does not compile/install ruby extentions' ] ] + ] + multipackage_descripters = [ + [ 'with', [ '', + 'name,name...', + 'package names that you want to install', + 'ALL' ] ], + [ 'without', [ '', + 'name,name...', + 'package names that you do not want to install', + 'NONE' ] ] + ] + if multipackage_install? + DESCRIPTER = common_descripters + multipackage_descripters + else + DESCRIPTER = common_descripters + end + + SAVE_FILE = '.config' + + def ConfigTable.each_name(&block) + keys().each(&block) + end + + def ConfigTable.keys + DESCRIPTER.map {|name, *dummy| name } + end + + def ConfigTable.each_definition(&block) + DESCRIPTER.each(&block) + end + + def ConfigTable.get_entry(name) + name, ent = DESCRIPTER.assoc(name) + ent + end + + def ConfigTable.get_entry!(name) + get_entry(name) or raise ArgumentError, "no such config: #{name}" + end + + def ConfigTable.add_entry(name, vals) + ConfigTable::DESCRIPTER.push [name,vals] + end + + def ConfigTable.remove_entry(name) + get_entry(name) or raise ArgumentError, "no such config: #{name}" + DESCRIPTER.delete_if {|n, arr| n == name } + end + + def ConfigTable.config_key?(name) + get_entry(name) ? true : false + end + + def ConfigTable.bool_config?(name) + ent = get_entry(name) or return false + ent[1] == 'yes/no' + end + + def ConfigTable.value_config?(name) + ent = get_entry(name) or return false + ent[1] != 'yes/no' + end + + def ConfigTable.path_config?(name) + ent = get_entry(name) or return false + ent[1] == 'path' + end + + + class << self + alias newobj new + end + + def ConfigTable.new + c = newobj() + c.initialize_from_table + c + end + + def ConfigTable.load + c = newobj() + c.initialize_from_file + c + end + + def initialize_from_table + @table = {} + DESCRIPTER.each do |k, (default, vname, desc, default2)| + @table[k] = default + end + end + + def initialize_from_file + raise InstallError, "#{File.basename $0} config first"\ + unless File.file?(SAVE_FILE) + @table = {} + File.foreach(SAVE_FILE) do |line| + k, v = line.split(/=/, 2) + @table[k] = v.strip + end + end + + def save + File.open(SAVE_FILE, 'w') {|f| + @table.each do |k, v| + f.printf "%s=%s\n", k, v if v + end + } + end + + def []=(k, v) + raise InstallError, "unknown config option #{k}"\ + unless ConfigTable.config_key?(k) + @table[k] = v + end + + def [](key) + return nil unless @table[key] + @table[key].gsub(%r<\$([^/]+)>) { self[$1] } + end + + def set_raw(key, val) + @table[key] = val + end + + def get_raw(key) + @table[key] + end + +end + + +module MetaConfigAPI + + def eval_file_ifexist(fname) + instance_eval File.read(fname), fname, 1 if File.file?(fname) + end + + def config_names + ConfigTable.keys + end + + def config?(name) + ConfigTable.config_key?(name) + end + + def bool_config?(name) + ConfigTable.bool_config?(name) + end + + def value_config?(name) + ConfigTable.value_config?(name) + end + + def path_config?(name) + ConfigTable.path_config?(name) + end + + def add_config(name, argname, default, desc) + ConfigTable.add_entry name,[default,argname,desc] + end + + def add_path_config(name, default, desc) + add_config name, 'path', default, desc + end + + def add_bool_config(name, default, desc) + add_config name, 'yes/no', default ? 'yes' : 'no', desc + end + + def set_config_default(name, default) + if bool_config?(name) + ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no') + else + ConfigTable.get_entry!(name)[0] = default + end + end + + def remove_config(name) + ent = ConfigTable.get_entry(name) + ConfigTable.remove_entry name + ent + end + +end + +# +# File Operations +# + +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + dirname if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # does not check '/'... it's too abnormal case + dirs = dirname.split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(fname) + $stderr.puts "rm -f #{fname}" if verbose? + return if no_harm? + + if File.exist?(fname) or File.symlink?(fname) + File.chmod 0777, fname + File.unlink fname + end + end + + def rm_rf(dn) + $stderr.puts "rm -rf #{dn}" if verbose? + return if no_harm? + + Dir.chdir dn + Dir.foreach('.') do |fn| + next if fn == '.' + next if fn == '..' + if File.dir?(fn) + verbose_off { + rm_rf fn + } + else + verbose_off { + rm_f fn + } + end + end + Dir.chdir '..' + Dir.rmdir dn + end + + def move_file(src, dest) + File.unlink dest if File.exist?(dest) + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| f.write File.binread(src) } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + dest : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(str) + $stderr.puts str if verbose? + system str or raise RuntimeError, "'system #{str}' failed" + end + + def ruby(str) + command config('ruby-prog') + ' ' + str + end + + def make(task = '') + command config('make-prog') + ' ' + task + end + + def extdir?(dir) + File.exist?(dir + '/MANIFEST') or File.exist?("#{dir}/extconf.rb") + end + + def all_files_in(dirname) + Dir.open(dirname) {|d| + return d.select {|ent| File.file?("#{dirname}/#{ent}") } + } + end + + REJECT_DIRS = %w( + CVS SCCS RCS CVS.adm .svn + ) + + def all_dirs_in(dirname) + Dir.open(dirname) {|d| + return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS + } + end + +end + +# +# Main Installer +# + +class InstallError < StandardError; end + + +module HookUtils + + def run_hook(name) + try_run_hook "#{curr_srcdir()}/#{name}" or + try_run_hook "#{curr_srcdir()}/#{name}.rb" + end + + def try_run_hook(fname) + return false unless File.file?(fname) + begin + instance_eval File.read(fname), fname, 1 + rescue + raise InstallError, "hook #{fname} failed:\n" + $!.message + end + true + end + +end + + +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + #abstract srcdir_root + #abstract objdir_root + #abstract relpath + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file? srcfile(path) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.3.0' + Copyright = 'Copyright (c) 2000-2004 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + instance().invoke + end + + @singleton = nil + + def ToplevelInstaller.instance + @singleton ||= new(File.dirname($0)) + @singleton + end + + include MetaConfigAPI + + def initialize(ardir_root) + @config = nil + @options = { 'verbose' => true } + @ardir = File.expand_path(ardir_root) + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + @config = load_config('config') + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + @config = load_config(task) + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + eval_file_ifexist "#{@ardir}/metaconfig" + end + + def load_config(task) + case task + when 'config' + ConfigTable.new + when 'clean', 'distclean' + if File.exist?(ConfigTable::SAVE_FILE) + then ConfigTable.load + else ConfigTable.new + end + else + ConfigTable.load + end + end + + def init_installers + @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ + + while arg = ARGV.shift + case arg + when /\A\w+\z/ + raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg + return arg + + when '-q', '--quiet' + @options['verbose'] = false + + when '--verbose' + @options['verbose'] = true + + when '-h', '--help' + print_usage $stdout + exit 0 + + when '-v', '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + + when '--copyright' + puts Copyright + exit 0 + + else + raise InstallError, "unknown global option '#{arg}'" + end + end + + nil + end + + + def parsearg_no_options + raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\ + unless ARGV.empty? + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/ + @options['config-opt'] = [] + + while i = ARGV.shift + if /\A--?\z/ =~ i + @options['config-opt'] = ARGV.dup + break + end + m = re.match(i) or raise InstallError, "config: unknown option #{i}" + name, value = m.to_a[1,2] + if value + if ConfigTable.bool_config?(name) + raise InstallError, "config: --#{name} allows only yes/no for argument"\ + unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value + value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' + end + else + raise InstallError, "config: --#{name} requires argument"\ + unless ConfigTable.bool_config?(name) + value = 'yes' + end + @config[name] = value + end + end + + def parsearg_install + @options['no-harm'] = false + @options['install-prefix'] = '' + while a = ARGV.shift + case a + when /\A--no-harm\z/ + @options['no-harm'] = true + when /\A--prefix=(.*)\z/ + path = $1 + path = File.expand_path(path) unless path[0,1] == '/' + @options['install-prefix'] = path + else + raise InstallError, "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-20s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, '-h,--help', 'print this message' + out.printf fmt, '-v,--version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf " %-10s %s\n", name, desc + end + + out.puts + out.puts 'Options for CONFIG or ALL:' + ConfigTable.each_definition do |name, (default, arg, desc, default2)| + out.printf " %-20s %s [%s]\n", + '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg), + desc, + default2 || default + end + out.printf " %-20s %s [%s]\n", + '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's" + + out.puts + out.puts 'Options for INSTALL:' + out.printf " %-20s %s [%s]\n", + '--no-harm', 'only display what to do if given', 'off' + out.printf " %-20s %s [%s]\n", + '--prefix', 'install path prefix', '$prefix' + + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_show + ConfigTable.each_name do |k| + v = @config.get_raw(k) + if not v or v.empty? + v = '(not specified)' + end + printf "%-10s %s\n", k, v + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end + + +class ToplevelInstallerMulti < ToplevelInstaller + + include HookUtils + include HookScriptAPI + include FileOperations + + def initialize(ardir) + super + @packages = all_dirs_in("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + end + + def run_metaconfigs + eval_file_ifexist "#{@ardir}/metaconfig" + @packages.each do |name| + eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" + end + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, @options, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + raise InstallError, "no such package: #{name}" \ + unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # multi-package metaconfig API + # + + attr_reader :packages + + def declare_packages(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_clean + rm_f ConfigTable::SAVE_FILE + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f ConfigTable::SAVE_FILE + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def verbose? + @options['verbose'] + end + + def no_harm? + @options['no-harm'] + end + +end + + +class Installer + + FILETYPES = %w( bin lib ext data ) + + include HookScriptAPI + include HookUtils + include FileOperations + + def initialize(config, opt, srcroot, objroot) + @config = config + @options = opt + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + # + # Hook Script API bases + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # configs/options + # + + def no_harm? + @options['no-harm'] + end + + def verbose? + @options['verbose'] + end + + def verbose_off + begin + save, @options['verbose'] = @options['verbose'], false + yield + ensure + @options['verbose'] = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + def config_dir_bin(rel) + end + + def config_dir_lib(rel) + end + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + def extconf + opt = @options['config-opt'].join(' ') + command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}" + end + + def config_dir_data(rel) + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + all_files_in(curr_srcdir()).each do |fname| + adjust_shebang "#{curr_srcdir()}/#{fname}" + end + end + + def adjust_shebang(path) + return if no_harm? + tmpfile = File.basename(path) + '.tmp' + begin + File.open(path, 'rb') {|r| + File.open(tmpfile, 'wb') {|w| + first = r.gets + return unless should_modify_shebang?(first) + $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? + w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path')) + w.write r.read + } + } + move_file tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + def should_modify_shebang?(line) + File.basename(config('ruby-path')) == 'ruby' or + shebang_command(line) == 'ruby' + end + + def shebang_command(line) + cmd, arg = *line.sub(/\A\#!/, '').strip.split(/\s+/, 2) + cmd + end + + def setup_dir_lib(rel) + end + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + def setup_dir_data(rel) + end + + # + # TASK install + # + + def exec_install + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files ruby_extentions('.'), + "#{config('so-dir')}/#{rel}", + 0555 + end + + def install_dir_data(rel) + install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @options['install-prefix'] + list.each do |fname| + install fname, dest, mode, @options['install-prefix'] + end + end + + def ruby_scripts + collect_filenames_auto().select {|n| /\.rb\z/ =~ n } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + reject_patterns = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + mapping = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + REJECT_PATTERNS = Regexp.new('\A(?:' + + reject_patterns.map {|pat| + pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } + }.join('|') + + ')\z') + + def collect_filenames_auto + mapdir((existfiles() - hookfiles()).reject {|fname| + REJECT_PATTERNS =~ fname + }) + end + + def existfiles + all_files_in(curr_srcdir()) | all_files_in('.') + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def mapdir(filelist) + filelist.map {|fname| + if File.exist?(fname) # objdir + fname + else # srcdir + File.join(curr_srcdir(), fname) + end + } + end + + def ruby_extentions(dir) + _ruby_extentions(dir) or + raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first" + end + + DLEXT = /\.#{ ::RbConfig::CONFIG['DLEXT'] }\z/ + + def _ruby_extentions(dir) + Dir.open(dir) {|d| + return d.select {|fname| DLEXT =~ fname } + } + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f ConfigTable::SAVE_FILE + rm_f 'InstalledFiles' + end + + def clean_dir_bin(rel) + end + + def clean_dir_lib(rel) + end + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + def clean_dir_data(rel) + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f ConfigTable::SAVE_FILE + rm_f 'InstalledFiles' + end + + def distclean_dir_bin(rel) + end + + def distclean_dir_lib(rel) + end + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + # + # lib + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if config('without-ext') == 'yes' and type == 'ext' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + all_dirs_in(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + +end + + +if $0 == __FILE__ + begin + if multipackage_install? + ToplevelInstallerMulti.invoke + else + ToplevelInstaller.invoke + end + rescue + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/helper.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/helper.rb new file mode 100644 index 0000000000..efa4a39d79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/helper.rb @@ -0,0 +1,18 @@ +require 'sqlite3' +require 'minitest/autorun' + +unless RUBY_VERSION >= "1.9" + require 'iconv' +end + +module SQLite3 + class TestCase < Minitest::Test + alias :assert_not_equal :refute_equal + alias :assert_not_nil :refute_nil + alias :assert_raise :assert_raises + + def assert_nothing_raised + yield + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_backup.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_backup.rb new file mode 100644 index 0000000000..4e9570b91a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_backup.rb @@ -0,0 +1,33 @@ +require 'helper' + +module SQLite3 + class TestBackup < SQLite3::TestCase + def setup + @sdb = SQLite3::Database.new(':memory:') + @ddb = SQLite3::Database.new(':memory:') + @sdb.execute('CREATE TABLE foo (idx, val);'); + @data = ('A'..'Z').map{|x|x * 40} + @data.each_with_index do |v, i| + @sdb.execute('INSERT INTO foo (idx, val) VALUES (?, ?);', [i, v]) + end + end + + def test_backup_step + b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') + while b.step(1) == SQLite3::Constants::ErrorCode::OK + assert_not_equal(0, b.remaining) + end + assert_equal(0, b.remaining) + b.finish + assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) + end + + def test_backup_all + b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') + assert_equal(SQLite3::Constants::ErrorCode::DONE, b.step(-1)) + assert_equal(0, b.remaining) + b.finish + assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) + end + end if defined?(SQLite3::Backup) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_collation.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_collation.rb new file mode 100644 index 0000000000..360335e703 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_collation.rb @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +require 'helper' + +module SQLite3 + class TestCollation < SQLite3::TestCase + class Comparator + attr_reader :calls + def initialize + @calls = [] + end + + def compare left, right + @calls << [left, right] + left <=> right + end + end + + def setup + @db = SQLite3::Database.new(':memory:') + @create = "create table ex(id int, data string)" + @db.execute(@create); + [ [1, 'hello'], [2, 'world'] ].each do |vals| + @db.execute('insert into ex (id, data) VALUES (?, ?)', vals) + end + end + + def test_custom_collation + comparator = Comparator.new + + @db.collation 'foo', comparator + + assert_equal comparator, @db.collations['foo'] + @db.execute('select data from ex order by 1 collate foo') + assert_equal 1, comparator.calls.length + end + + def test_remove_collation + comparator = Comparator.new + + @db.collation 'foo', comparator + @db.collation 'foo', nil + + assert_nil @db.collations['foo'] + assert_raises(SQLite3::SQLException) do + @db.execute('select data from ex order by 1 collate foo') + end + end + + if RUBY_VERSION >= '1.9.1' + def test_encoding + comparator = Comparator.new + @db.collation 'foo', comparator + @db.execute('select data from ex order by 1 collate foo') + + a, b = *comparator.calls.first + + assert_equal Encoding.find('UTF-8'), a.encoding + assert_equal Encoding.find('UTF-8'), b.encoding + end + + def test_encoding_default_internal + warn_before = $-w + $-w = false + before_enc = Encoding.default_internal + + Encoding.default_internal = 'EUC-JP' + comparator = Comparator.new + @db.collation 'foo', comparator + @db.execute('select data from ex order by 1 collate foo') + + a, b = *comparator.calls.first + + assert_equal Encoding.find('EUC-JP'), a.encoding + assert_equal Encoding.find('EUC-JP'), b.encoding + ensure + Encoding.default_internal = before_enc + $-w = warn_before + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database.rb new file mode 100644 index 0000000000..c255ed8537 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database.rb @@ -0,0 +1,503 @@ +require 'helper' +require 'tempfile' +require 'pathname' + +module SQLite3 + class TestDatabase < SQLite3::TestCase + attr_reader :db + + def setup + @db = SQLite3::Database.new(':memory:') + super + end + + def test_segv + assert_raises { SQLite3::Database.new 1 } + end + + def test_db_filename + tf = nil + assert_equal '', @db.filename('main') + tf = Tempfile.new 'thing' + @db = SQLite3::Database.new tf.path + assert_equal File.expand_path(tf.path), File.expand_path(@db.filename('main')) + ensure + tf.unlink if tf + end + + def test_filename + tf = nil + assert_equal '', @db.filename + tf = Tempfile.new 'thing' + @db = SQLite3::Database.new tf.path + assert_equal File.expand_path(tf.path), File.expand_path(@db.filename) + ensure + tf.unlink if tf + end + + def test_filename_with_attachment + tf = nil + assert_equal '', @db.filename + tf = Tempfile.new 'thing' + @db.execute "ATTACH DATABASE '#{tf.path}' AS 'testing'" + assert_equal File.expand_path(tf.path), File.expand_path(@db.filename('testing')) + ensure + tf.unlink if tf + end + + def test_error_code + begin + db.execute 'SELECT' + rescue SQLite3::SQLException => e + end + assert_equal 1, e.code + end + + def test_extended_error_code + db.extended_result_codes = true + db.execute 'CREATE TABLE "employees" ("token" integer NOT NULL)' + begin + db.execute 'INSERT INTO employees (token) VALUES (NULL)' + rescue SQLite3::ConstraintException => e + end + assert_equal 1299, e.code + end + + def test_bignum + num = 4907021672125087844 + db.execute 'CREATE TABLE "employees" ("token" integer(8), "name" varchar(20) NOT NULL)' + db.execute "INSERT INTO employees(name, token) VALUES('employee-1', ?)", [num] + rows = db.execute 'select token from employees' + assert_equal num, rows.first.first + end + + def test_blob + @db.execute("CREATE TABLE blobs ( id INTEGER, hash BLOB(10) )") + blob = Blob.new("foo\0bar") + @db.execute("INSERT INTO blobs VALUES (0, ?)", [blob]) + assert_equal [[0, blob, blob.length, blob.length*2]], @db.execute("SELECT id, hash, length(hash), length(hex(hash)) FROM blobs") + end + + def test_get_first_row + assert_equal [1], @db.get_first_row('SELECT 1') + end + + def test_get_first_row_with_type_translation_and_hash_results + @db.results_as_hash = true + @db.type_translation = true + assert_equal({"1"=>1}, @db.get_first_row('SELECT 1')) + end + + def test_execute_with_type_translation_and_hash + @db.results_as_hash = true + @db.type_translation = true + rows = [] + @db.execute('SELECT 1') { |row| rows << row } + + assert_equal({"1"=>1}, rows.first) + end + + def test_encoding + assert @db.encoding, 'database has encoding' + end + + def test_changes + @db.execute("CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, number integer)") + assert_equal 0, @db.changes + @db.execute("INSERT INTO items (number) VALUES (10)") + assert_equal 1, @db.changes + @db.execute_batch( + "UPDATE items SET number = (number + :nn) WHERE (number = :n)", + {"nn" => 20, "n" => 10}) + assert_equal 1, @db.changes + assert_equal [[30]], @db.execute("select number from items") + end + + def test_batch_last_comment_is_processed + # FIXME: nil as a successful return value is kinda dumb + assert_nil @db.execute_batch <<-eosql + CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT); + -- omg + eosql + end + + def test_execute_batch2 + @db.results_as_hash = true + return_value = @db.execute_batch2 <<-eosql + CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, name string); + INSERT INTO items (name) VALUES ("foo"); + INSERT INTO items (name) VALUES ("bar"); + SELECT * FROM items; + eosql + assert_equal return_value, [{"id"=>"1","name"=>"foo"}, {"id"=>"2", "name"=>"bar"}] + + return_value = @db.execute_batch2('SELECT * FROM items;') do |result| + result["id"] = result["id"].to_i + result + end + assert_equal return_value, [{"id"=>1,"name"=>"foo"}, {"id"=>2, "name"=>"bar"}] + + return_value = @db.execute_batch2('INSERT INTO items (name) VALUES ("oof")') + assert_equal return_value, [] + + return_value = @db.execute_batch2( + 'CREATE TABLE employees (id integer PRIMARY KEY AUTOINCREMENT, name string, age integer(3)); + INSERT INTO employees (age) VALUES (30); + INSERT INTO employees (age) VALUES (40); + INSERT INTO employees (age) VALUES (20); + SELECT age FROM employees;') do |result| + result["age"] = result["age"].to_i + result + end + assert_equal return_value, [{"age"=>30}, {"age"=>40}, {"age"=>20}] + + return_value = @db.execute_batch2('SELECT name FROM employees'); + assert_equal return_value, [{"name"=>nil}, {"name"=>nil}, {"name"=>nil}] + + @db.results_as_hash = false + return_value = @db.execute_batch2( + 'CREATE TABLE managers (id integer PRIMARY KEY AUTOINCREMENT, age integer(3)); + INSERT INTO managers (age) VALUES (50); + INSERT INTO managers (age) VALUES (60); + SELECT id, age from managers;') do |result| + result = result.map do |res| + res.to_i + end + result + end + assert_equal return_value, [[1, 50], [2, 60]] + + assert_raises (RuntimeError) do + # "names" is not a valid column + @db.execute_batch2 'INSERT INTO items (names) VALUES ("bazz")' + end + + end + + def test_new + db = SQLite3::Database.new(':memory:') + assert db + end + + def test_new_yields_self + thing = nil + SQLite3::Database.new(':memory:') do |db| + thing = db + end + assert_instance_of(SQLite3::Database, thing) + end + + def test_new_with_options + # determine if Ruby is running on Big Endian platform + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + + if RUBY_VERSION >= "1.9" + db = SQLite3::Database.new(':memory:'.encode(utf16), :utf16 => true) + else + db = SQLite3::Database.new(Iconv.conv(utf16, 'UTF-8', ':memory:'), + :utf16 => true) + end + assert db + end + + def test_close + db = SQLite3::Database.new(':memory:') + db.close + assert db.closed? + end + + def test_block_closes_self + thing = nil + SQLite3::Database.new(':memory:') do |db| + thing = db + assert !thing.closed? + end + assert thing.closed? + end + + def test_block_closes_self_even_raised + thing = nil + begin + SQLite3::Database.new(':memory:') do |db| + thing = db + raise + end + rescue + end + assert thing.closed? + end + + def test_prepare + db = SQLite3::Database.new(':memory:') + stmt = db.prepare('select "hello world"') + assert_instance_of(SQLite3::Statement, stmt) + end + + def test_block_prepare_does_not_double_close + db = SQLite3::Database.new(':memory:') + r = db.prepare('select "hello world"') do |stmt| + stmt.close + :foo + end + assert_equal :foo, r + end + + def test_total_changes + db = SQLite3::Database.new(':memory:') + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + assert_equal 1, db.total_changes + end + + def test_execute_returns_list_of_hash + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + rows = db.execute("select * from foo") + assert_equal [{"a"=>1, "b"=>"hello"}], rows + end + + def test_execute_yields_hash + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + db.execute("select * from foo") do |row| + assert_equal({"a"=>1, "b"=>"hello"}, row) + end + end + + def test_table_info + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + info = [{ + "name" => "a", + "pk" => 1, + "notnull" => 0, + "type" => "integer", + "dflt_value" => nil, + "cid" => 0 + }, + { + "name" => "b", + "pk" => 0, + "notnull" => 0, + "type" => "text", + "dflt_value" => nil, + "cid" => 1 + }] + assert_equal info, db.table_info('foo') + end + + def test_total_changes_closed + db = SQLite3::Database.new(':memory:') + db.close + assert_raise(SQLite3::Exception) do + db.total_changes + end + end + + def test_trace_requires_opendb + @db.close + assert_raise(SQLite3::Exception) do + @db.trace { |x| } + end + end + + def test_trace_with_block + result = nil + @db.trace { |sql| result = sql } + @db.execute "select 'foo'" + assert_equal "select 'foo'", result + end + + def test_trace_with_object + obj = Class.new { + attr_accessor :result + def call sql; @result = sql end + }.new + + @db.trace(obj) + @db.execute "select 'foo'" + assert_equal "select 'foo'", obj.result + end + + def test_trace_takes_nil + @db.trace(nil) + @db.execute "select 'foo'" + end + + def test_last_insert_row_id_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.last_insert_row_id + end + end + + def test_define_function + called_with = nil + @db.define_function("hello") do |value| + called_with = value + end + @db.execute("select hello(10)") + assert_equal 10, called_with + end + + def test_call_func_arg_type + called_with = nil + @db.define_function("hello") do |b, c, d| + called_with = [b, c, d] + nil + end + @db.execute("select hello(2.2, 'foo', NULL)") + assert_equal [2.2, 'foo', nil], called_with + end + + def test_define_varargs + called_with = nil + @db.define_function("hello") do |*args| + called_with = args + nil + end + @db.execute("select hello(2.2, 'foo', NULL)") + assert_equal [2.2, 'foo', nil], called_with + end + + def test_call_func_blob + called_with = nil + @db.define_function("hello") do |a, b| + called_with = [a, b, a.length] + nil + end + blob = Blob.new("a\0fine\0kettle\0of\0fish") + @db.execute("select hello(?, length(?))", [blob, blob]) + assert_equal [blob, blob.length, 21], called_with + end + + def test_function_return + @db.define_function("hello") { |a| 10 } + assert_equal [10], @db.execute("select hello('world')").first + end + + def test_function_return_types + [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| + @db.define_function("hello") { |a| thing } + assert_equal [thing], @db.execute("select hello('world')").first + end + end + + def test_function_gc_segfault + @db.create_function("bug", -1) { |func, *values| func.result = values.join } + # With a lot of data and a lot of threads, try to induce a GC segfault. + params = Array.new(127, "?" * 28000) + proc = Proc.new { + db.execute("select bug(#{Array.new(params.length, "?").join(",")})", params) + } + m = Mutex.new + 30.times.map { Thread.new { m.synchronize { proc.call } } }.each(&:join) + end + + def test_function_return_type_round_trip + [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| + @db.define_function("hello") { |a| a } + assert_equal [thing], @db.execute("select hello(hello(?))", [thing]).first + end + end + + def test_define_function_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.define_function('foo') { } + end + end + + def test_inerrupt_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.interrupt + end + end + + def test_define_aggregate + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + + acc = Class.new { + attr_reader :sum + alias :finalize :sum + def initialize + @sum = 0 + end + + def step a + @sum += a + end + }.new + + @db.define_aggregator("accumulate", acc) + value = @db.get_first_value( "select accumulate(a) from foo" ) + assert_equal 6, value + end + + def test_authorizer_ok + @db.authorizer = Class.new { + def call action, a, b, c, d; true end + }.new + @db.prepare("select 'fooooo'") + + @db.authorizer = Class.new { + def call action, a, b, c, d; 0 end + }.new + @db.prepare("select 'fooooo'") + end + + def test_authorizer_ignore + @db.authorizer = Class.new { + def call action, a, b, c, d; nil end + }.new + stmt = @db.prepare("select 'fooooo'") + assert_nil stmt.step + end + + def test_authorizer_fail + @db.authorizer = Class.new { + def call action, a, b, c, d; false end + }.new + assert_raises(SQLite3::AuthorizationException) do + @db.prepare("select 'fooooo'") + end + end + + def test_remove_auth + @db.authorizer = Class.new { + def call action, a, b, c, d; false end + }.new + assert_raises(SQLite3::AuthorizationException) do + @db.prepare("select 'fooooo'") + end + + @db.authorizer = nil + @db.prepare("select 'fooooo'") + end + + def test_close_with_open_statements + @db.prepare("select 'foo'") + assert_raises(SQLite3::BusyException) do + @db.close + end + end + + def test_execute_with_empty_bind_params + assert_equal [['foo']], @db.execute("select 'foo'", []) + end + + def test_query_with_named_bind_params + assert_equal [['foo']], @db.query("select :n", {'n' => 'foo'}).to_a + end + + def test_execute_with_named_bind_params + assert_equal [['foo']], @db.execute("select :n", {'n' => 'foo'}) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_flags.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_flags.rb new file mode 100644 index 0000000000..9a1205e2ce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_flags.rb @@ -0,0 +1,95 @@ +require 'helper' + +module SQLite3 + class TestDatabaseFlags < SQLite3::TestCase + def setup + File.unlink 'test-flags.db' if File.exist?('test-flags.db') + @db = SQLite3::Database.new('test-flags.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-flags.db' if File.exist?('test-flags.db') + end + + def test_open_database_flags_constants + defined_to_date = [:READONLY, :READWRITE, :CREATE, :DELETEONCLOSE, + :EXCLUSIVE, :MAIN_DB, :TEMP_DB, :TRANSIENT_DB, + :MAIN_JOURNAL, :TEMP_JOURNAL, :SUBJOURNAL, + :MASTER_JOURNAL, :NOMUTEX, :FULLMUTEX] + if SQLite3::SQLITE_VERSION_NUMBER > 3007002 + defined_to_date += [:AUTOPROXY, :SHAREDCACHE, :PRIVATECACHE, :WAL] + end + if SQLite3::SQLITE_VERSION_NUMBER > 3007007 + defined_to_date += [:URI] + end + if SQLite3::SQLITE_VERSION_NUMBER > 3007013 + defined_to_date += [:MEMORY] + end + assert defined_to_date.sort == SQLite3::Constants::Open.constants.sort + end + + def test_open_database_flags_conflicts_with_readonly + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readonly => true) + end + end + + def test_open_database_flags_conflicts_with_readwrite + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readwrite => true) + end + end + + def test_open_database_readonly_flags + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) + assert @db.readonly? + end + + def test_open_database_readwrite_flags + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) + assert !@db.readonly? + end + + def test_open_database_readonly_flags_cant_open + File.unlink 'test-flags.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) + end + end + + def test_open_database_readwrite_flags_cant_open + File.unlink 'test-flags.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) + end + end + + def test_open_database_misuse_flags + assert_raise(SQLite3::MisuseException) do + flags = SQLite3::Constants::Open::READONLY | SQLite3::Constants::Open::READWRITE # <== incompatible flags + @db = SQLite3::Database.new('test-flags.db', :flags => flags) + end + end + + def test_open_database_create_flags + File.unlink 'test-flags.db' + flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE + @db = SQLite3::Database.new('test-flags.db', :flags => flags) do |db| + db.execute("CREATE TABLE foos (id integer)") + db.execute("INSERT INTO foos (id) VALUES (12)") + end + assert File.exist?('test-flags.db') + end + + def test_open_database_exotic_flags + flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE + exotic_flags = SQLite3::Constants::Open::NOMUTEX | SQLite3::Constants::Open::TEMP_DB + @db = SQLite3::Database.new('test-flags.db', :flags => flags | exotic_flags) + @db.execute("INSERT INTO foos (id) VALUES (12)") + assert @db.changes == 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readonly.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readonly.rb new file mode 100644 index 0000000000..def34b2235 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readonly.rb @@ -0,0 +1,36 @@ +require 'helper' + +module SQLite3 + class TestDatabaseReadonly < SQLite3::TestCase + def setup + File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') + @db = SQLite3::Database.new('test-readonly.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') + end + + def test_open_readonly_database + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + assert @db.readonly? + end + + def test_open_readonly_not_exists_database + File.unlink 'test-readonly.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + end + end + + def test_insert_readonly_database + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + assert_raise(SQLite3::ReadOnlyException) do + @db.execute("INSERT INTO foos (id) VALUES (12)") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readwrite.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readwrite.rb new file mode 100644 index 0000000000..f84857385d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_database_readwrite.rb @@ -0,0 +1,41 @@ +require 'helper' + +module SQLite3 + class TestDatabaseReadwrite < SQLite3::TestCase + def setup + File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') + @db = SQLite3::Database.new('test-readwrite.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') + end + + def test_open_readwrite_database + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) + assert !@db.readonly? + end + + def test_open_readwrite_readonly_database + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true, :readonly => true) + end + end + + def test_open_readwrite_not_exists_database + File.unlink 'test-readwrite.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-readwrite.db', :readonly => true) + end + end + + def test_insert_readwrite_database + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) + @db.execute("INSERT INTO foos (id) VALUES (12)") + assert @db.changes == 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_deprecated.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_deprecated.rb new file mode 100644 index 0000000000..4fa1dc4058 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_deprecated.rb @@ -0,0 +1,44 @@ +require 'helper' + +module SQLite3 + class TestDeprecated < SQLite3::TestCase + attr_reader :db + + def setup + super + @warn_before = $-w + $-w = false + @db = SQLite3::Database.new(':memory:') + @db.execute 'CREATE TABLE test_table (name text, age int)' + end + + def teardown + super + $-w = @warn_before + end + + def test_query_with_many_bind_params_not_nil + assert_equal [[1, 2]], db.query('select ?, ?', 1, 2).to_a + end + + def test_execute_with_many_bind_params_not_nil + assert_equal [[1, 2]], @db.execute("select ?, ?", 1, 2).to_a + end + + def test_query_with_many_bind_params + assert_equal [[nil, 1]], @db.query("select ?, ?", nil, 1).to_a + end + + def test_query_with_nil_bind_params + assert_equal [['foo']], @db.query("select 'foo'", nil).to_a + end + + def test_execute_with_many_bind_params + assert_equal [[nil, 1]], @db.execute("select ?, ?", nil, 1) + end + + def test_execute_with_nil_bind_params + assert_equal [['foo']], @db.execute("select 'foo'", nil) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_encoding.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_encoding.rb new file mode 100644 index 0000000000..32bf616909 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_encoding.rb @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +require 'helper' + +module SQLite3 + class TestEncoding < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @create = "create table ex(id int, data string)" + @insert = "insert into ex(id, data) values (?, ?)" + @db.execute(@create); + end + + def test_select_encoding_on_utf_16 + str = "foo" + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + db = SQLite3::Database.new(':memory:'.encode(utf16)) + db.execute @create + db.execute "insert into ex (id, data) values (1, \"#{str}\")" + + stmt = db.prepare 'select * from ex where data = ?' + ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each do |enc| + stmt.bind_param 1, str.encode(enc) + assert_equal 1, stmt.to_a.length + stmt.reset! + end + end + + def test_insert_encoding + str = "foo" + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + db = SQLite3::Database.new(':memory:'.encode(utf16)) + db.execute @create + stmt = db.prepare @insert + + ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each_with_index do |enc,i| + stmt.bind_param 1, i + stmt.bind_param 2, str.encode(enc) + stmt.to_a + stmt.reset! + end + + db.execute('select data from ex').flatten.each do |s| + assert_equal str, s + end + end + + def test_default_internal_is_honored + warn_before = $-w + $-w = false + + before_enc = Encoding.default_internal + + str = "壁に耳あり、障子に目あり" + stmt = @db.prepare('insert into ex(data) values (?)') + stmt.bind_param 1, str + stmt.step + + Encoding.default_internal = 'EUC-JP' + string = @db.execute('select data from ex').first.first + + assert_equal Encoding.default_internal, string.encoding + assert_equal str.encode('EUC-JP'), string + assert_equal str, string.encode(str.encoding) + ensure + Encoding.default_internal = before_enc + $-w = warn_before + end + + def test_blob_is_binary + str = "猫舌" + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, SQLite3::Blob.new(str)) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('UTF-8') + end + + def test_blob_is_ascii8bit + str = "猫舌" + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, str.dup.force_encoding("ASCII-8BIT")) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('UTF-8') + end + + def test_blob_with_eucjp + str = "猫舌".encode("EUC-JP") + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, SQLite3::Blob.new(str)) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('EUC-JP') + end + + def test_db_with_eucjp + db = SQLite3::Database.new(':memory:'.encode('EUC-JP')) + assert_equal(Encoding.find('UTF-8'), db.encoding) + end + + def test_db_with_utf16 + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + + db = SQLite3::Database.new(':memory:'.encode(utf16)) + assert_equal(Encoding.find(utf16), db.encoding) + end + + def test_statement_eucjp + str = "猫舌" + @db.execute("insert into ex(data) values ('#{str}')".encode('EUC-JP')) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_statement_utf8 + str = "猫舌" + @db.execute("insert into ex(data) values ('#{str}')") + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_encoding + assert_equal Encoding.find("UTF-8"), @db.encoding + end + + def test_utf_8 + str = "猫舌" + @db.execute(@insert, [10, str]) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_euc_jp + str = "猫舌".encode('EUC-JP') + @db.execute(@insert, [10, str]) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str.encode('UTF-8'), row.first.first + end + + end if RUBY_VERSION >= '1.9.1' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration.rb new file mode 100644 index 0000000000..d5ea3a533e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration.rb @@ -0,0 +1,507 @@ +require 'helper' + +class TC_Database_Integration < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + end + + def teardown + @db.close + end + + def test_table_info_with_type_translation_active + assert_nothing_raised { @db.table_info("foo") } + end + + def test_table_info_with_defaults_for_version_3_3_8_and_higher + @db.transaction do + @db.execute "create table defaults_test ( a string default NULL, b string default 'Hello', c string default '--- []\n' )" + data = @db.table_info( "defaults_test" ) + assert_equal({"name" => "a", "type" => "string", "dflt_value" => nil, "notnull" => 0, "cid" => 0, "pk" => 0}, + data[0]) + assert_equal({"name" => "b", "type" => "string", "dflt_value" => "Hello", "notnull" => 0, "cid" => 1, "pk" => 0}, + data[1]) + assert_equal({"name" => "c", "type" => "string", "dflt_value" => "--- []\n", "notnull" => 0, "cid" => 2, "pk" => 0}, + data[2]) + end + end + + def test_table_info_without_defaults_for_version_3_3_8_and_higher + @db.transaction do + @db.execute "create table no_defaults_test ( a integer default 1, b integer )" + data = @db.table_info( "no_defaults_test" ) + assert_equal({"name" => "a", "type" => "integer", "dflt_value" => "1", "notnull" => 0, "cid" => 0, "pk" => 0}, + data[0]) + assert_equal({"name" => "b", "type" => "integer", "dflt_value" => nil, "notnull" => 0, "cid" => 1, "pk" => 0}, + data[1]) + end + end + + def test_complete_fail + assert !@db.complete?( "select * from foo" ) + end + def test_complete_success + assert @db.complete?( "select * from foo;" ) + end + + # FIXME: do people really need UTF16 sql statements? + #def test_complete_fail_utf16 + # assert !@db.complete?( "select * from foo".to_utf16(false), true ) + #end + + # FIXME: do people really need UTF16 sql statements? + #def test_complete_success_utf16 + # assert @db.complete?( "select * from foo;".to_utf16(true), true ) + #end + + def test_errmsg + assert_equal "not an error", @db.errmsg + end + + # FIXME: do people really need UTF16 error messages? + #def test_errmsg_utf16 + # msg = Iconv.conv('UTF-16', 'UTF-8', 'not an error') + # assert_equal msg, @db.errmsg(true) + #end + + def test_errcode + assert_equal 0, @db.errcode + end + + def test_trace + result = nil + @db.trace { |sql| result = sql } + @db.execute "select * from foo" + assert_equal "select * from foo", result + end + + def test_authorizer_okay + @db.authorizer { |type,a,b,c,d| 0 } + rows = @db.execute "select * from foo" + assert_equal 3, rows.length + end + + def test_authorizer_error + @db.authorizer { |type,a,b,c,d| 1 } + assert_raise( SQLite3::AuthorizationException ) do + @db.execute "select * from foo" + end + end + + def test_authorizer_silent + @db.authorizer { |type,a,b,c,d| 2 } + rows = @db.execute "select * from foo" + assert rows.empty? + end + + def test_prepare_invalid_syntax + assert_raise( SQLite3::SQLException ) do + @db.prepare "select from foo" + end + end + + def test_prepare_invalid_column + assert_raise( SQLite3::SQLException ) do + @db.prepare "select k from foo" + end + end + + def test_prepare_invalid_table + assert_raise( SQLite3::SQLException ) do + @db.prepare "select * from barf" + end + end + + def test_prepare_no_block + stmt = @db.prepare "select * from foo" + assert stmt.respond_to?(:execute) + stmt.close + end + + def test_prepare_with_block + called = false + @db.prepare "select * from foo" do |stmt| + called = true + assert stmt.respond_to?(:execute) + end + assert called + end + + def test_execute_no_block_no_bind_no_match + rows = @db.execute( "select * from foo where a > 100" ) + assert rows.empty? + end + + def test_execute_with_block_no_bind_no_match + called = false + @db.execute( "select * from foo where a > 100" ) do |row| + called = true + end + assert !called + end + + def test_execute_no_block_with_bind_no_match + rows = @db.execute( "select * from foo where a > ?", 100 ) + assert rows.empty? + end + + def test_execute_with_block_with_bind_no_match + called = false + @db.execute( "select * from foo where a > ?", 100 ) do |row| + called = true + end + assert !called + end + + def test_execute_no_block_no_bind_with_match + rows = @db.execute( "select * from foo where a = 1" ) + assert_equal 1, rows.length + end + + def test_execute_with_block_no_bind_with_match + called = 0 + @db.execute( "select * from foo where a = 1" ) do |row| + called += 1 + end + assert_equal 1, called + end + + def test_execute_no_block_with_bind_with_match + rows = @db.execute( "select * from foo where a = ?", 1 ) + assert_equal 1, rows.length + end + + def test_execute_with_block_with_bind_with_match + called = 0 + @db.execute( "select * from foo where a = ?", 1 ) do |row| + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_no_bind_no_match + columns, *rows = @db.execute2( "select * from foo where a > 100" ) + assert rows.empty? + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_no_bind_no_match + called = 0 + @db.execute2( "select * from foo where a > 100" ) do |row| + assert [ "a", "b" ], row unless called == 0 + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_with_bind_no_match + columns, *rows = @db.execute2( "select * from foo where a > ?", 100 ) + assert rows.empty? + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_with_bind_no_match + called = 0 + @db.execute2( "select * from foo where a > ?", 100 ) do |row| + assert_equal [ "a", "b" ], row unless called == 0 + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_no_bind_with_match + columns, *rows = @db.execute2( "select * from foo where a = 1" ) + assert_equal 1, rows.length + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_no_bind_with_match + called = 0 + @db.execute2( "select * from foo where a = 1" ) do |row| + assert_equal [ 1, "foo" ], row unless called == 0 + called += 1 + end + assert_equal 2, called + end + + def test_execute2_no_block_with_bind_with_match + columns, *rows = @db.execute2( "select * from foo where a = ?", 1 ) + assert_equal 1, rows.length + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_with_bind_with_match + called = 0 + @db.execute2( "select * from foo where a = ?", 1 ) do + called += 1 + end + assert_equal 2, called + end + + def test_execute_batch_empty + assert_nothing_raised { @db.execute_batch "" } + end + + def test_execute_batch_no_bind + @db.transaction do + @db.execute_batch <<-SQL + create table bar ( a, b, c ); + insert into bar values ( 'one', 2, 'three' ); + insert into bar values ( 'four', 5, 'six' ); + insert into bar values ( 'seven', 8, 'nine' ); + SQL + end + rows = @db.execute( "select * from bar" ) + assert_equal 3, rows.length + end + + def test_execute_batch_with_bind + @db.execute_batch( <<-SQL, [1] ) + create table bar ( a, b, c ); + insert into bar values ( 'one', 2, ? ); + insert into bar values ( 'four', 5, ? ); + insert into bar values ( 'seven', 8, ? ); + SQL + rows = @db.execute( "select * from bar" ).map { |a,b,c| c } + assert_equal [1, 1, 1], rows + end + + def test_query_no_block_no_bind_no_match + result = @db.query( "select * from foo where a > 100" ) + assert_nil result.next + result.close + end + + def test_query_with_block_no_bind_no_match + r = nil + @db.query( "select * from foo where a > 100" ) do |result| + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_with_bind_no_match + result = @db.query( "select * from foo where a > ?", 100 ) + assert_nil result.next + result.close + end + + def test_query_with_block_with_bind_no_match + r = nil + @db.query( "select * from foo where a > ?", 100 ) do |result| + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_no_bind_with_match + result = @db.query( "select * from foo where a = 1" ) + assert_not_nil result.next + assert_nil result.next + result.close + end + + def test_query_with_block_no_bind_with_match + r = nil + @db.query( "select * from foo where a = 1" ) do |result| + assert_not_nil result.next + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_with_bind_with_match + result = @db.query( "select * from foo where a = ?", 1 ) + assert_not_nil result.next + assert_nil result.next + result.close + end + + def test_query_with_block_with_bind_with_match + r = nil + @db.query( "select * from foo where a = ?", 1 ) do |result| + assert_not_nil result.next + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_get_first_row_no_bind_no_match + result = @db.get_first_row( "select * from foo where a=100" ) + assert_nil result + end + + def test_get_first_row_no_bind_with_match + result = @db.get_first_row( "select * from foo where a=1" ) + assert_equal [ 1, "foo" ], result + end + + def test_get_first_row_with_bind_no_match + result = @db.get_first_row( "select * from foo where a=?", 100 ) + assert_nil result + end + + def test_get_first_row_with_bind_with_match + result = @db.get_first_row( "select * from foo where a=?", 1 ) + assert_equal [ 1, "foo" ], result + end + + def test_get_first_value_no_bind_no_match + result = @db.get_first_value( "select b, a from foo where a=100" ) + assert_nil result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=100" ) + assert_nil result + end + + def test_get_first_value_no_bind_with_match + result = @db.get_first_value( "select b, a from foo where a=1" ) + assert_equal "foo", result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=1" ) + assert_equal "foo", result + end + + def test_get_first_value_with_bind_no_match + result = @db.get_first_value( "select b, a from foo where a=?", 100 ) + assert_nil result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=?", 100 ) + assert_nil result + end + + def test_get_first_value_with_bind_with_match + result = @db.get_first_value( "select b, a from foo where a=?", 1 ) + assert_equal "foo", result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=?", 1 ) + assert_equal "foo", result + end + + def test_last_insert_row_id + @db.execute "insert into foo ( b ) values ( 'test' )" + assert_equal 4, @db.last_insert_row_id + @db.execute "insert into foo ( b ) values ( 'again' )" + assert_equal 5, @db.last_insert_row_id + end + + def test_changes + @db.execute "insert into foo ( b ) values ( 'test' )" + assert_equal 1, @db.changes + @db.execute "delete from foo where 1=1" + assert_equal 4, @db.changes + end + + def test_total_changes + assert_equal 3, @db.total_changes + @db.execute "insert into foo ( b ) values ( 'test' )" + @db.execute "delete from foo where 1=1" + assert_equal 8, @db.total_changes + end + + def test_transaction_nest + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.transaction do + end + end + end + end + + def test_transaction_rollback + @db.transaction + @db.execute_batch <<-SQL + insert into foo (b) values ( 'test1' ); + insert into foo (b) values ( 'test2' ); + insert into foo (b) values ( 'test3' ); + insert into foo (b) values ( 'test4' ); + SQL + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + @db.rollback + assert_equal 3, @db.get_first_value("select count(*) from foo").to_i + end + + def test_transaction_commit + @db.transaction + @db.execute_batch <<-SQL + insert into foo (b) values ( 'test1' ); + insert into foo (b) values ( 'test2' ); + insert into foo (b) values ( 'test3' ); + insert into foo (b) values ( 'test4' ); + SQL + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + @db.commit + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + end + + def test_transaction_rollback_in_block + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.rollback + end + end + end + + def test_transaction_commit_in_block + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.commit + end + end + end + + def test_transaction_active + assert !@db.transaction_active? + @db.transaction + assert @db.transaction_active? + @db.commit + assert !@db.transaction_active? + end + + def test_transaction_implicit_rollback + assert !@db.transaction_active? + @db.transaction + @db.execute('create table bar (x CHECK(1 = 0))') + assert @db.transaction_active? + assert_raises( SQLite3::ConstraintException ) do + @db.execute("insert or rollback into bar (x) VALUES ('x')") + end + assert !@db.transaction_active? + end + + def test_interrupt + @db.create_function( "abort", 1 ) do |func,x| + @db.interrupt + func.result = x + end + + assert_raise( SQLite3::InterruptException ) do + @db.execute "select abort(a) from foo" + end + end + + def test_create_function + @db.create_function( "munge", 1 ) do |func,x| + func.result = ">>>#{x}<<<" + end + + value = @db.get_first_value( "select munge(b) from foo where a=1" ) + assert_match( />>>.*<<= '1.9' + + busy = Mutex.new + busy.lock + handler_call_count = 0 + + t = Thread.new(busy) do |locker| + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + locker.lock + end + ensure + db2.close if db2 + end + end + + @db.busy_handler do |data,count| + handler_call_count += 1 + busy.unlock + true + end + + assert_nothing_raised do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + + t.join + + assert_equal 1, handler_call_count + end + + def test_busy_handler_impatient + busy = Mutex.new + busy.lock + handler_call_count = 0 + + t = Thread.new do + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + busy.lock + end + ensure + db2.close if db2 + end + end + sleep 1 + + @db.busy_handler do + handler_call_count += 1 + false + end + + assert_raise( SQLite3::BusyException ) do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + + busy.unlock + t.join + + assert_equal 1, handler_call_count + end + + def test_busy_timeout + @db.busy_timeout 1000 + busy = Mutex.new + busy.lock + + t = Thread.new do + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + busy.lock + end + ensure + db2.close if db2 + end + end + + sleep 1 + time = Benchmark.measure do + assert_raise( SQLite3::BusyException ) do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + end + + busy.unlock + t.join + + assert time.real*1000 >= 1000 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_resultset.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_resultset.rb new file mode 100644 index 0000000000..ad89c2e817 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_resultset.rb @@ -0,0 +1,142 @@ +require 'helper' + +class TC_ResultSet < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + @stmt = @db.prepare( "select * from foo where a in ( ?, ? )" ) + @result = @stmt.execute + end + + def teardown + @stmt.close + @db.close + end + + def test_reset_unused + assert_nothing_raised { @result.reset } + assert @result.to_a.empty? + end + + def test_reset_used + @result.to_a + assert_nothing_raised { @result.reset } + assert @result.to_a.empty? + end + + def test_reset_with_bind + @result.to_a + assert_nothing_raised { @result.reset( 1, 2 ) } + assert_equal 2, @result.to_a.length + end + + def test_eof_inner + @result.reset( 1 ) + assert !@result.eof? + end + + def test_eof_edge + @result.reset( 1 ) + @result.next # to first row + @result.next # to end of result set + assert @result.eof? + end + + def test_next_eof + @result.reset( 1 ) + assert_not_nil @result.next + assert_nil @result.next + end + + def test_next_no_type_translation_no_hash + @result.reset( 1 ) + assert_equal [ 1, "foo" ], @result.next + end + + def test_next_type_translation + @result.reset( 1 ) + assert_equal [ 1, "foo" ], @result.next + end + + def test_next_type_translation_with_untyped_column + @db.query( "select count(*) from foo" ) do |result| + assert_equal [3], result.next + end + end + + def test_type_translation_with_null_column + time = '1974-07-25 14:39:00' + + @db.execute "create table bar ( a integer, b time, c string )" + @db.execute "insert into bar (a, b, c) values (NULL, '#{time}', 'hello')" + @db.execute "insert into bar (a, b, c) values (1, NULL, 'hello')" + @db.execute "insert into bar (a, b, c) values (2, '#{time}', NULL)" + @db.query( "select * from bar" ) do |result| + assert_equal [nil, time, 'hello'], result.next + assert_equal [1, nil, 'hello'], result.next + assert_equal [2, time, nil], result.next + end + end + + def test_real_translation + @db.execute('create table foo_real(a real)') + @db.execute('insert into foo_real values (42)' ) + @db.query('select a, sum(a), typeof(a), typeof(sum(a)) from foo_real') do |result| + result = result.next + assert result[0].is_a?(Float) + assert result[1].is_a?(Float) + assert result[2].is_a?(String) + assert result[3].is_a?(String) + end + end + + def test_next_results_as_hash + @db.results_as_hash = true + @result.reset( 1 ) + hash = @result.next + assert_equal( { "a" => 1, "b" => "foo" }, + hash ) + assert_equal hash[0], 1 + assert_equal hash[1], "foo" + end + + def test_each + called = 0 + @result.reset( 1, 2 ) + @result.each { |row| called += 1 } + assert_equal 2, called + end + + def test_enumerable + @result.reset( 1, 2 ) + assert_equal 2, @result.to_a.length + end + + def test_types + assert_equal [ "integer", "text" ], @result.types + end + + def test_columns + assert_equal [ "a", "b" ], @result.columns + end + + def test_close + stmt = @db.prepare( "select * from foo" ) + result = stmt.execute + assert !result.closed? + result.close + assert result.closed? + assert stmt.closed? + assert_raise( SQLite3::Exception ) { result.reset } + assert_raise( SQLite3::Exception ) { result.next } + assert_raise( SQLite3::Exception ) { result.each } + assert_raise( SQLite3::Exception ) { result.close } + assert_raise( SQLite3::Exception ) { result.types } + assert_raise( SQLite3::Exception ) { result.columns } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_statement.rb new file mode 100644 index 0000000000..20dd6fcc1d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_integration_statement.rb @@ -0,0 +1,194 @@ +require 'helper' + +class TC_Statement < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + @stmt = @db.prepare( "select * from foo where a in ( ?, :named )" ) + end + + def teardown + @stmt.close + @db.close + end + + def test_remainder_empty + assert_equal "", @stmt.remainder + end + + def test_remainder_nonempty + called = false + @db.prepare( "select * from foo;\n blah" ) do |stmt| + called = true + assert_equal "\n blah", stmt.remainder + end + assert called + end + + def test_bind_params_empty + assert_nothing_raised { @stmt.bind_params } + assert @stmt.execute!.empty? + end + + def test_bind_params_array + @stmt.bind_params 1, 2 + assert_equal 2, @stmt.execute!.length + end + + def test_bind_params_hash + @stmt.bind_params ":named" => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_hash_without_colon + @stmt.bind_params "named" => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_hash_as_symbol + @stmt.bind_params :named => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_mixed + @stmt.bind_params( 1, ":named" => 2 ) + assert_equal 2, @stmt.execute!.length + end + + def test_bind_param_by_index + @stmt.bind_params( 1, 2 ) + assert_equal 2, @stmt.execute!.length + end + + def test_bind_param_by_name_bad + assert_raise( SQLite3::Exception ) { @stmt.bind_param( "@named", 2 ) } + end + + def test_bind_param_by_name_good + @stmt.bind_param( ":named", 2 ) + assert_equal 1, @stmt.execute!.length + end + + def test_bind_param_with_various_types + @db.transaction do + @db.execute "create table all_types ( a integer primary key, b float, c string, d integer )" + @db.execute "insert into all_types ( b, c, d ) values ( 1.4, 'hello', 68719476735 )" + end + + assert_equal 1, @db.execute( "select * from all_types where b = ?", 1.4 ).length + assert_equal 1, @db.execute( "select * from all_types where c = ?", 'hello').length + assert_equal 1, @db.execute( "select * from all_types where d = ?", 68719476735).length + end + + def test_execute_no_bind_no_block + assert_instance_of SQLite3::ResultSet, @stmt.execute + end + + def test_execute_with_bind_no_block + assert_instance_of SQLite3::ResultSet, @stmt.execute( 1, 2 ) + end + + def test_execute_no_bind_with_block + called = false + @stmt.execute { |row| called = true } + assert called + end + + def test_execute_with_bind_with_block + called = 0 + @stmt.execute( 1, 2 ) { |row| called += 1 } + assert_equal 1, called + end + + def test_reexecute + r = @stmt.execute( 1, 2 ) + assert_equal 2, r.to_a.length + assert_nothing_raised { r = @stmt.execute( 1, 2 ) } + assert_equal 2, r.to_a.length + end + + def test_execute_bang_no_bind_no_block + assert @stmt.execute!.empty? + end + + def test_execute_bang_with_bind_no_block + assert_equal 2, @stmt.execute!( 1, 2 ).length + end + + def test_execute_bang_no_bind_with_block + called = 0 + @stmt.execute! { |row| called += 1 } + assert_equal 0, called + end + + def test_execute_bang_with_bind_with_block + called = 0 + @stmt.execute!( 1, 2 ) { |row| called += 1 } + assert_equal 2, called + end + + def test_columns + c1 = @stmt.columns + c2 = @stmt.columns + assert_same c1, c2 + assert_equal 2, c1.length + end + + def test_columns_computed + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + assert_equal [ "count(*)" ], stmt.columns + end + assert called + end + + def test_types + t1 = @stmt.types + t2 = @stmt.types + assert_same t1, t2 + assert_equal 2, t1.length + end + + def test_types_computed + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + assert_equal [ nil ], stmt.types + end + assert called + end + + def test_close + stmt = @db.prepare( "select * from foo" ) + assert !stmt.closed? + stmt.close + assert stmt.closed? + assert_raise( SQLite3::Exception ) { stmt.execute } + assert_raise( SQLite3::Exception ) { stmt.execute! } + assert_raise( SQLite3::Exception ) { stmt.close } + assert_raise( SQLite3::Exception ) { stmt.bind_params 5 } + assert_raise( SQLite3::Exception ) { stmt.bind_param 1, 5 } + assert_raise( SQLite3::Exception ) { stmt.columns } + assert_raise( SQLite3::Exception ) { stmt.types } + end + + def test_committing_tx_with_statement_active + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + count = stmt.execute!.first.first.to_i + @db.transaction do + @db.execute "insert into foo ( b ) values ( 'hello' )" + end + new_count = stmt.execute!.first.first.to_i + assert_equal new_count, count+1 + end + assert called + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_result_set.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_result_set.rb new file mode 100644 index 0000000000..fa3df51660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_result_set.rb @@ -0,0 +1,37 @@ +require 'helper' + +module SQLite3 + class TestResultSet < SQLite3::TestCase + def test_each_hash + db = SQLite3::Database.new ':memory:' + db.execute "create table foo ( a integer primary key, b text )" + list = ('a'..'z').to_a + list.each do |t| + db.execute "insert into foo (b) values (\"#{t}\")" + end + + rs = db.prepare('select * from foo').execute + rs.each_hash do |hash| + assert_equal list[hash['a'] - 1], hash['b'] + end + end + + def test_next_hash + db = SQLite3::Database.new ':memory:' + db.execute "create table foo ( a integer primary key, b text )" + list = ('a'..'z').to_a + list.each do |t| + db.execute "insert into foo (b) values (\"#{t}\")" + end + + rs = db.prepare('select * from foo').execute + rows = [] + while row = rs.next_hash + rows << row + end + rows.each do |hash| + assert_equal list[hash['a'] - 1], hash['b'] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_sqlite3.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_sqlite3.rb new file mode 100644 index 0000000000..f5c7972378 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_sqlite3.rb @@ -0,0 +1,21 @@ +require 'helper' + +module SQLite3 + class TestSQLite3 < SQLite3::TestCase + def test_libversion + assert_not_nil SQLite3.libversion + end + + def test_threadsafe + assert_not_nil SQLite3.threadsafe + end + + def test_threadsafe? + if SQLite3.threadsafe > 0 + assert SQLite3.threadsafe? + else + refute SQLite3.threadsafe? + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement.rb new file mode 100644 index 0000000000..d78b35cd2d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement.rb @@ -0,0 +1,263 @@ +require 'helper' + +module SQLite3 + class TestStatement < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @stmt = SQLite3::Statement.new(@db, "select 'foo'") + end + + def test_double_close_does_not_segv + @db.execute 'CREATE TABLE "things" ("number" float NOT NULL)' + + stmt = @db.prepare 'INSERT INTO things (number) VALUES (?)' + assert_raises(SQLite3::ConstraintException) { stmt.execute(nil) } + + stmt.close + + assert_raises(SQLite3::Exception) { stmt.close } + end + + def test_raises_type_error + assert_raises(TypeError) do + SQLite3::Statement.new( @db, nil ) + end + end + + def test_insert_duplicate_records + @db.execute 'CREATE TABLE "things" ("name" varchar(20) CONSTRAINT "index_things_on_name" UNIQUE)' + stmt = @db.prepare("INSERT INTO things(name) VALUES(?)") + stmt.execute('ruby') + + exception = assert_raises(SQLite3::ConstraintException) { stmt.execute('ruby') } + # SQLite 3.8.2 returns new error message: + # UNIQUE constraint failed: *table_name*.*column_name* + # Older versions of SQLite return: + # column *column_name* is not unique + assert_match(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/, exception.message) + end + + ### + # This method may not exist depending on how sqlite3 was compiled + def test_database_name + @db.execute('create table foo(text BLOB)') + @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) + stmt = @db.prepare('select text from foo') + if stmt.respond_to?(:database_name) + assert_equal 'main', stmt.database_name(0) + end + end + + def test_prepare_blob + @db.execute('create table foo(text BLOB)') + stmt = @db.prepare('insert into foo(text) values (?)') + stmt.bind_param(1, SQLite3::Blob.new('hello')) + stmt.step + stmt.close + end + + def test_select_blob + @db.execute('create table foo(text BLOB)') + @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) + assert_equal 'hello', @db.execute('select * from foo').first.first + end + + def test_new + assert @stmt + end + + def test_new_closed_handle + @db = SQLite3::Database.new(':memory:') + @db.close + assert_raises(ArgumentError) do + SQLite3::Statement.new(@db, 'select "foo"') + end + end + + def test_new_with_remainder + stmt = SQLite3::Statement.new(@db, "select 'foo';bar") + assert_equal 'bar', stmt.remainder + end + + def test_empty_remainder + assert_equal '', @stmt.remainder + end + + def test_close + @stmt.close + assert @stmt.closed? + end + + def test_double_close + @stmt.close + assert_raises(SQLite3::Exception) do + @stmt.close + end + end + + def test_bind_param_string + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, "hello") + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_bind_param_int + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 10) + result = nil + stmt.each { |x| result = x } + assert_equal [10], result + end + + def test_bind_nil + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, nil) + result = nil + stmt.each { |x| result = x } + assert_equal [nil], result + end + + def test_bind_blob + @db.execute('create table foo(text BLOB)') + stmt = SQLite3::Statement.new(@db, 'insert into foo(text) values (?)') + stmt.bind_param(1, SQLite3::Blob.new('hello')) + stmt.execute + row = @db.execute('select * from foo') + + assert_equal ['hello'], row.first + assert_equal row.first.types, ['BLOB'] + end + + def test_bind_64 + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 2 ** 31) + result = nil + stmt.each { |x| result = x } + assert_equal [2 ** 31], result + end + + def test_bind_double + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 2.2) + result = nil + stmt.each { |x| result = x } + assert_equal [2.2], result + end + + def test_named_bind + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param(':foo', 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_no_colon + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param('foo', 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_symbol + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param(:foo, 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_not_found + stmt = SQLite3::Statement.new(@db, "select :foo") + assert_raises(SQLite3::Exception) do + stmt.bind_param('bar', 'hello') + end + end + + def test_each + r = nil + @stmt.each do |row| + r = row + end + assert_equal(['foo'], r) + end + + def test_reset! + r = [] + @stmt.each { |row| r << row } + @stmt.reset! + @stmt.each { |row| r << row } + assert_equal [['foo'], ['foo']], r + end + + def test_step + r = @stmt.step + assert_equal ['foo'], r + end + + def test_step_twice + assert_not_nil @stmt.step + assert !@stmt.done? + assert_nil @stmt.step + assert @stmt.done? + + @stmt.reset! + assert !@stmt.done? + end + + def test_step_never_moves_past_done + 10.times { @stmt.step } + @stmt.done? + end + + def test_column_count + assert_equal 1, @stmt.column_count + end + + def test_column_name + assert_equal "'foo'", @stmt.column_name(0) + assert_nil @stmt.column_name(10) + end + + def test_bind_parameter_count + stmt = SQLite3::Statement.new(@db, "select ?, ?, ?") + assert_equal 3, stmt.bind_parameter_count + end + + def test_execute_with_varargs + stmt = @db.prepare('select ?, ?') + assert_equal [[nil, nil]], stmt.execute(nil, nil).to_a + end + + def test_execute_with_hash + stmt = @db.prepare('select :n, :h') + assert_equal [[10, nil]], stmt.execute('n' => 10, 'h' => nil).to_a + end + + def test_with_error + @db.execute('CREATE TABLE "employees" ("name" varchar(20) NOT NULL CONSTRAINT "index_employees_on_name" UNIQUE)') + stmt = @db.prepare("INSERT INTO Employees(name) VALUES(?)") + stmt.execute('employee-1') + stmt.execute('employee-1') rescue SQLite3::ConstraintException + stmt.reset! + assert stmt.execute('employee-2') + end + + def test_clear_bindings! + stmt = @db.prepare('select ?, ?') + stmt.bind_param 1, "foo" + stmt.bind_param 2, "bar" + + # We can't fetch bound parameters back out of sqlite3, so just call + # the clear_bindings! method and assert that nil is returned + stmt.clear_bindings! + + while x = stmt.step + assert_equal [nil, nil], x + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement_execute.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement_execute.rb new file mode 100644 index 0000000000..63e022b2e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.4.2/test/test_statement_execute.rb @@ -0,0 +1,35 @@ +require 'helper' + +module SQLite3 + class TestStatementExecute < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @db.execute_batch( + "CREATE TABLE items (id integer PRIMARY KEY, number integer)") + end + + def test_execute_insert + ps = @db.prepare("INSERT INTO items (number) VALUES (:n)") + ps.execute('n'=>10) + assert_equal 1, @db.get_first_value("SELECT count(*) FROM items") + ps.close + end + + def test_execute_update + @db.execute("INSERT INTO items (number) VALUES (?)", [10]) + + ps = @db.prepare("UPDATE items SET number = :new WHERE number = :old") + ps.execute('old'=>10, 'new'=>20) + assert_equal 20, @db.get_first_value("SELECT number FROM items") + ps.close + end + + def test_execute_delete + @db.execute("INSERT INTO items (number) VALUES (?)", [20]) + ps = @db.prepare("DELETE FROM items WHERE number = :n") + ps.execute('n' => 20) + assert_equal 0, @db.get_first_value("SELECT count(*) FROM items") + ps.close + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/.gemtest b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/.gemtest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/API_CHANGES.md b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/API_CHANGES.md new file mode 100644 index 0000000000..790444806b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/API_CHANGES.md @@ -0,0 +1,49 @@ +# API Changes + +* SQLite3::Database#execute only accepts an array for bind parameters. + +* SQLite3::ResultSet used to query the database for the first row, regardless + of whether the user asked for it or not. I have removed that so that rows + will not be returned until the user asks for them. This is a subtle but + sometimes important change in behavior. + + 83882d2208ed189361617d5ab8532a325aaf729d + +* SQLite3::Database#trace now takes either a block or an object that responds + to "call". The previous implementation passed around a VALUE that was cast + to a void *. This is dangerous because the value could get garbage collected + before the proc was called. If the user wants data passed around with the + block, they should use variables available to the closure or create an + object. + +* SQLite3::Statement#step automatically converts to ruby types, where before + all values were automatically yielded as strings. This will only be a + problem for people who were accessing information about the database that + wasn't previously passed through the pure ruby conversion code. + +* SQLite3::Database#errmsg no longer takes a parameter to return error + messages as UTF-16. Do people even use that? I opt for staying UTF-8 when + possible. See test_integration.rb test_errmsg_utf16 + +* SQLite3::Database#authorize same changes as trace + +* test/test_tc_database.rb was removed because we no longer use the Driver + design pattern. + +# Garbage Collection Strategy + +All statements keep pointers back to their respective database connections. +The @connection instance variable on the Statement handle keeps the database +connection alive. Memory allocated for a statement handler will be freed in +two cases: + +* close is called on the statement +* The SQLite3::Database object gets garbage collected + +We can't free the memory for the statement in the garbage collection function +for the statement handler. The reason is because there exists a race +condition. We cannot guarantee the order in which objects will be garbage +collected. So, it is possible that a connection and a statement are up for +garbage collection. If the database connection were to be free'd before the +statement, then boom. Instead we'll be conservative and free unclosed +statements when the connection is terminated. diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CHANGELOG.md b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CHANGELOG.md new file mode 100644 index 0000000000..0dce5a6ecf --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CHANGELOG.md @@ -0,0 +1,495 @@ +# sqlite3-ruby Changelog + +## 1.6.1 / 2023-02-22 + +### Dependencies + +* Vendored sqlite is updated to [v3.41.0](https://sqlite.org/releaselog/3_41_0.html). + + +## 1.6.0 / 2023-01-13 + +### Ruby + +This release introduces native gem support for Ruby 3.2. + +This release ends native gem support for Ruby 2.6, for which [upstream support ended 2022-04-12](https://www.ruby-lang.org/en/downloads/branches/). + + +### Dependencies + +* Vendored sqlite3 is updated to [v3.40.1](https://sqlite.org/releaselog/3_40_1.html). + + +### Fixes + +* `get_boolean_pragma` now returns the correct value. Previously, it always returned true. [#275] (Thank you, @Edouard-chin!) + + +## 1.5.4 / 2022-11-18 + +### Dependencies + +* Vendored sqlite is updated to [v3.40.0](https://sqlite.org/releaselog/3_40_0.html). + + +## 1.5.3 / 2022-10-11 + +### Fixed + +* Fixed installation of the "ruby" platform gem when building from source on Fedora. In v1.5.0..v1.5.2, installation failed on some systems due to the behavior of Fedora's pkg-config implementation. [#355] + + +## 1.5.2 / 2022-10-01 + +### Packaging + +This version correctly vendors the tarball for sqlite v3.39.4 in the vanilla "ruby" platform gem package, so that users will not require network access at installation. + +v1.5.0 and v1.5.1 mistakenly packaged the tarball for sqlite v3.38.5 in the vanilla "ruby" platform gem, resulting in downloading the intended tarball over the network at installation time (or, if the network was not available, failure to install). Note that the precompiled native gems were not affected by this issue. [#352] + + +## 1.5.1 / 2022-09-29 + +### Dependencies + +* Vendored sqlite is updated to [v3.39.4](https://sqlite.org/releaselog/3_39_4.html). + +### Security + +The vendored version of sqlite, v3.39.4, should be considered to be a security release. From the release notes: + +> Version 3.39.4 is a minimal patch against the prior release that addresses issues found since the +> prior release. In particular, a potential vulnerability in the FTS3 extension has been fixed, so +> this should be considered a security update. +> +> In order to exploit the vulnerability, an attacker must have full SQL access and must be able to +> construct a corrupt database with over 2GB of FTS3 content. The problem arises from a 32-bit +> signed integer overflow. + +For more information please see [GHSA-mgvv-5mxp-xq67](https://github.com/sparklemotion/sqlite3-ruby/security/advisories/GHSA-mgvv-5mxp-xq67). + + +## 1.5.0 / 2022-09-08 + +### Packaging + +#### Faster, more reliable installation + +Native (precompiled) gems are available for Ruby 2.6, 2.7, 3.0, and 3.1 on all these platforms: + +- `aarch64-linux` +- `arm-linux` +- `arm64-darwin` +- `x64-mingw32` and `x64-mingw-ucrt` +- `x86-linux` +- `x86_64-darwin` +- `x86_64-linux` + +If you are using one of these Ruby versions on one of these platforms, the native gem is the recommended way to install sqlite3-ruby. + +See [the README](https://github.com/sparklemotion/sqlite3-ruby#native-gems-recommended) for more information. + + +#### More consistent developer experience + +Both the native (precompiled) gems and the vanilla "ruby platform" (source) gem include sqlite v3.39.3 by default. + +Defaulting to a consistent version of sqlite across all systems means that your development environment behaves exactly like your production environment, and you have access to the latest and greatest features of sqlite. + +You can opt-out of the packaged version of sqlite (and use your system-installed library as in versions < 1.5.0). See [the README](https://github.com/sparklemotion/sqlite3-ruby#avoiding-the-precompiled-native-gem) for more information. + +[Release notes for this version of sqlite](https://sqlite.org/releaselog/3_39_3.html) + + +### Rubies and Platforms + +* TruffleRuby is supported. +* Apple Silicon is supported (M1, arm64-darwin). +* vcpkg system libraries supported. [#332] (Thanks, @MSP-Greg!) + + +### Added + +* `SQLite3::SQLITE_LOADED_VERSION` contains the version string of the sqlite3 library that is dynamically loaded (compare to `SQLite3::SQLITE_VERSION` which is the version at compile-time). + + +### Fixed + +* `SQLite3::Database#load_extensions` now raises a `TypeError` unless a String is passed as the file path. Previously it was possible to pass a non-string and cause a segfault. [#339] + + +## 1.4.4 / 2022-06-14 + +### Fixes + +* Compilation no longer fails against SQLite3 versions < 3.29.0. This issue was introduced in v1.4.3. [#324] (Thank you, @r6e!) + + +## 1.4.3 / 2022-05-25 + +### Enhancements + +* Disable non-standard support for double-quoted string literals via the `:strict` option. [#317] (Thank you, @casperisfine!) +* Column type names are now explicitly downcased on platforms where they may have been in shoutcaps. [#315] (Thank you, @petergoldstein!) +* Support File or Pathname arguments to `Database.new`. [#283] (Thank you, @yb66!) +* Support building on MSVC. [#285] (Thank you, @jmarrec!) + + +## 1.4.2 / 2019-12-18 + +* Travis: Drop unused setting "sudo: false" +* The taint mechanism will be deprecated in Ruby 2.7 +* Fix Ruby 2.7 rb_check_safe_obj warnings +* Update travis config + + +## 1.4.1 + +* Don't mandate dl functions for the extention build +* bumping version + + +## 1.4.0 + +### Enhancements + +* Better aggregator support + +### Bugfixes + +* Various + + +## 1.3.13 + +### Enhancements + +* Support SQLite flags when defining functions +* Add definition for SQLITE_DETERMINISTIC flag + + +## 1.3.12 + +### Bugfixes + +* OS X install will default to homebrew if available. Fixes #195 + + +## 1.3.11 / 2015-10-10 + +### Enhancements + +* Windows: build against SQLite 3.8.11.1 + +### Internal + +* Use rake-compiler-dock to build Windows binaries. Pull #159 [larskanis] +* Expand Ruby versions being tested for Travis and AppVeyor + + +## 1.3.10 / 2014-10-30 + +### Enhancements + +* Windows: build against SQLite 3.8.6. Closes #135 [Hubro] + + +## 1.3.9 / 2014-02-25 + +### Bugfixes + +* Reset exception message. Closes #80 +* Reduce warnings due unused pointers. Closes #89 +* Add BSD-3 license reference to gemspec. Refs #99 and #106 + + +## 1.3.8 / 2013-08-17 + +### Enhancements + +* Windows: build against SQLite 3.7.17 + +### Bugfixes + +* Reset exception message. Closes #80 +* Correctly convert BLOB values to Ruby. Closes #65 +* Add MIT license reference to gemspec. Closes #99 +* Remove unused pointer. Closes #89 + +### Internal + +* Backport improvements in cross compilation for Windows +* Use of Minitest for internal tests +* Use Gemfile (generated by Hoe) to deal with dependencies +* Cleanup Travis CI + + +## 1.3.7 / 2013-01-11 + +### Bugfixes + +* Closing a bad statement twice will not segv. +* Aggregate handlers are initialized on each query. Closes #44 + +### Internal + +* Unset environment variables that could affect cross compilation. + + +## 1.3.6 / 2012-04-16 + +### Enhancements + +* Windows: build against SQLite 3.7.11 +* Added SQLite3::ResultSet#each_hash for fetching each row as a hash. +* Added SQLite3::ResultSet#next_hash for fetching one row as a hash. + +### Bugfixes + +* Support both UTF-16LE and UTF-16BE encoding modes on PPC. Closes #63 +* Protect parameters to custom functions from being garbage collected too + soon. Fixes #60. Thanks hirataya! +* Fix backwards compatibility with 1.2.5 with bind vars and `query` method. + Fixes #35. +* Fix double definition error caused by defining sqlite3_int64/uint64. +* Fix suspicious version regexp. + +### Deprecations + +* ArrayWithTypesAndFields#types is deprecated and the class will be removed + in version 2.0.0. Please use the `types` method on the ResultSet class + that created this object. +* ArrayWithTypesAndFields#fields is deprecated and the class will be removed + in version 2.0.0. Please use the `columns` method on the ResultSet class + that created this object. +* The ArrayWithTypesAndFields class will be removed in 2.0.0 +* The ArrayWithTypes class will be removed in 2.0.0 +* HashWithTypesAndFields#types is deprecated and the class will be removed + in version 2.0.0. Please use the `types` method on the ResultSet class + that created this object. +* HashWithTypesAndFields#fields is deprecated and the class will be removed + in version 2.0.0. Please use the `columns` method on the ResultSet class + that created this object. + + +## 1.3.5 / 2011-12-03 - ZOMG Holidays are here Edition! + +### Enhancements + +* Windows: build against SQLite 3.7.9 +* Static: enable SQLITE_ENABLE_COLUMN_METADATA +* Added Statement#clear_bindings! to set bindings back to nil + +### Bugfixes + +* Fixed a segv on Database.new. Fixes #34 (thanks nobu!) +* Database error is not reset, so don't check it in Statement#reset! +* Remove conditional around Bignum statement bindings. + Fixes #52. Fixes #56. Thank you Evgeny Myasishchev. + +### Internal + +* Use proper endianness when testing database connection with UTF-16. + Fixes #40. Fixes #51 +* Use -fPIC for static compilation when host is x86_64. + + +## 1.3.4 / 2011-07-25 + +### Enhancements + +* Windows: build against SQLite 3.7.7.1 +* Windows: build static binaries that do not depend on sqlite3.dll be + installed anymore + +### Bugfixes + +* Backup API is conditionally required so that older libsqlite3 can be used. + Thanks Hongli Lai. +* Fixed segmentation fault when nil is passed to SQLite3::Statement.new +* Fix extconf's hardcoded path that affected installation on certain systems. + + +## 1.3.3 / 2010-01-16 + +### Bugfixes + +* Abort on installation if sqlite3_backup_init is missing. Fixes #19 +* Gem has been renamed to 'sqlite3'. Please use `gem install sqlite3` + + +## 1.3.2 / 2010-10-30 / RubyConf Uruguay Edition! + +### Enhancements + +* Windows: build against 3.7.3 version of SQLite3 +* SQLite3::Database can now be open as readonly + + db = SQLite3::Database.new('my.db', :readonly => true) + +* Added SQLite3::SQLITE_VERSION and SQLite3::SQLITE_VERSION_NUMBER [nurse] + +### Bugfixes + +* type_translation= works along with Database#execute and a block +* defined functions are kept in a hash to prevent GC. #7 +* Removed GCC specific flags from extconf. + +### Deprecations + +* SQLite3::Database#type_translation= will be deprecated in the future with + no replacement. +* SQlite3::Version will be deprecated in 2.0.0 with SQLite3::VERSION as the + replacement. + + +## 1.3.1 / 2010-07-09 + +### Enhancements + +* Custom collations may be defined using SQLite3::Database#collation + +### Bugfixes + +* Statements returning 0 columns are automatically stepped. [RF #28308] +* SQLite3::Database#encoding works on 1.8 and 1.9 + + +## 1.3.0 / 2010-06-06 + +### Enhancements + +* Complete rewrite of C-based adapter from SWIG to hand-crafted one [tenderlove] + See API_CHANGES document for details. + This closes: Bug #27300, Bug #27241, Patch #16020 +* Improved UTF, Unicode, M17N, all that handling and proper BLOB handling [tenderlove, nurse] +* Added support for type translations [tenderlove] + + @db.translator.add_translator('sometime') do |type, thing| + 'output' # this will be returned as value for that column + end + +### Experimental + +* Added API to access and load extensions. [kashif] + These functions maps directly into SQLite3 own enable_load_extension() + and load_extension() C-API functions. See SQLite3::Database API documentation for details. + This closes: Patches #9178 + +### Bugfixes + +* Corrected gem dependencies (runtime and development) +* Fixed threaded tests [Alexey Borzenkov] +* Removed GitHub gemspec +* Fixed "No definition for" warnings from RDoc +* Generate zip and tgz files for releases +* Added Luis Lavena as gem Author (maintainer) +* Prevent mkmf interfere with Mighty Snow Leopard +* Allow extension compilation search for common lib paths [kashif] + (lookup /usr/local, /opt/local and /usr) +* Corrected extension compilation under MSVC [romuloceccon] +* Define load_extension functionality based on availability [tenderlove] +* Deprecation notices for Database#query. Fixes RF #28192 + + +## 1.3.0.beta.2 / 2010-05-15 + +### Enhancements + +* Added support for type translations [tenderlove] + + @db.translator.add_translator('sometime') do |type, thing| + 'output' # this will be returned as value for that column + end + +### Bugfixes + +* Allow extension compilation search for common lib paths [kashif] + (lookup /usr/local, /opt/local and /usr) +* Corrected extension compilation under MSVC [romuloceccon] +* Define load_extension functionality based on availability [tenderlove] +* Deprecation notices for Database#query. Fixes RF #28192 + + +## 1.3.0.beta.1 / 2010-05-10 + +### Enhancements + +* Complete rewrite of C-based adapter from SWIG to hand-crafted one [tenderlove] + See API_CHANGES document for details. + This closes: Bug #27300, Bug #27241, Patch #16020 +* Improved UTF, Unicode, M17N, all that handling and proper BLOB handling [tenderlove, nurse] + +### Experimental + +* Added API to access and load extensions. [kashif] + These functions maps directly into SQLite3 own enable_load_extension() + and load_extension() C-API functions. See SQLite3::Database API documentation for details. + This closes: Patches #9178 + +### Bugfixes + +* Corrected gem dependencies (runtime and development) +* Fixed threaded tests [Alexey Borzenkov] +* Removed GitHub gemspec +* Fixed "No definition for" warnings from RDoc +* Generate zip and tgz files for releases +* Added Luis Lavena as gem Author (maintainer) +* Prevent mkmf interfere with Mighty Snow Leopard + + +## 1.2.5 / 2009-07-25 + +* Check for illegal nil before executing SQL [Erik Veenstra] +* Switch to Hoe for gem task management and packaging. +* Advertise rake-compiler as development dependency. +* Build gem binaries for Windows. +* Improved Ruby 1.9 support compatibility. +* Taint returned values. Patch #20325. +* Database.open and Database.new now take an optional block [Gerrit Kaiser] + + +## 1.2.4.1 (internal) / 2009-07-05 + +* Check for illegal nil before executing SQL [Erik Veenstra] +* Switch to Hoe for gem task management and packaging. +* Advertise rake-compiler as development dependency. +* Build gem binaries for Windows. +* Improved Ruby 1.9 support compatibility. + + +## 1.2.4 / 2008-08-27 + +* Package the updated C file for source builds. [Jamis Buck] + + +## 1.2.3 / 2008-08-26 + +* Fix incorrect permissions on database.rb and translator.rb [Various] +* Avoid using Object#extend for greater speedups [Erik Veenstra] +* Ruby 1.9 compatibility tweaks for Array#zip [jimmy88@gmail.com] +* Fix linking against Ruby 1.8.5 [Rob Holland ] + + +## 1.2.2 / 2008-05-31 + +* Make the table_info method adjust the returned default value for the rows + so that the sqlite3 change in 3.3.8 and greater can be handled + transparently [Jamis Buck ] +* Ruby 1.9 compatibility tweaks [Roman Le Negrate ] +* Various performance enhancements [thanks Erik Veenstra] +* Correct busy_handler documentation [Rob Holland ] +* Use int_bind64 on Fixnum values larger than a 32bit C int can take. [Rob Holland ] +* Work around a quirk in SQLite's error reporting by calling sqlite3_reset + to produce a more informative error code upon a failure from + sqlite3_step. [Rob Holland ] +* Various documentation, test, and style tweaks [Rob Holland ] +* Be more granular with time/data translation [Rob Holland ] +* Use Date directly for parsing rather than going via Time [Rob Holland ] +* Check for the rt library and fdatasync so we link against that when + needed [Rob Holland ] +* Rename data structures to avoid collision on win32. based on patch + by: Luis Lavena [Rob Holland ] +* Add test for defaults [Daniel Rodríguez Troitiño] +* Correctly unquote double-quoted pragma defaults [Łukasz Dargiewicz ] diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CONTRIBUTING.md b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CONTRIBUTING.md new file mode 100644 index 0000000000..2472121132 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing to sqlite3-ruby + +**This document is a work-in-progress.** + +This doc is a short introduction on how to modify and maintain the sqlite3-ruby gem. + + +## Building gems + +As a prerequisite please make sure you have `docker` correctly installed, so that you're able to cross-compile the native gems. + +Run `bin/build-gems` which will package gems for all supported platforms, and run some basic sanity tests on those packages using `bin/test-gem-set` and `bin/test-gem-file-contents`. + + +## Updating the version of libsqlite3 + +Update `/dependencies.yml` to reflect: + +- the version of libsqlite3 +- the URL from which to download +- the checksum of the file, which will need to be verified manually (see comments in that file) + + +## Making a release + +A quick checklist: + +- [ ] make sure CI is green! +- [ ] update `CHANGELOG.md` and `lib/sqlite3/version.rb` including `VersionProxy::{MINOR,TINY}` +- [ ] create a git tag using a format that matches the pattern `v\d+\.\d+\.\d+`, e.g. `v1.3.13` +- [ ] run `bin/build-gems` and make sure it completes and all the tests pass +- [ ] `for g in gems/*.gem ; do gem push $g ; done` +- [ ] create a release at https://github.com/sparklemotion/sqlite3-ruby/releases and include sha2 checksums diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ChangeLog.cvs b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ChangeLog.cvs new file mode 100644 index 0000000000..6e9dd51d82 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ChangeLog.cvs @@ -0,0 +1,88 @@ +2005-01-05 09:40 minam + + * Rakefile, sqlite3-ruby-win32.gemspec, sqlite3-ruby.gemspec: Added + win32 gem. + +2005-01-05 07:31 minam + + * Rakefile, test/tc_integration.rb, test/tests.rb: Added + native-vs-dl benchmark to Rakefile. Added SQLITE3_DRIVERS + environment variable to integration test to specify which + driver(s) should be tested (defaults to "Native"). + +2005-01-04 14:26 minam + + * ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/driver/native/driver.rb, test/tc_database.rb, + test/tc_integration.rb, test/tests.rb: Unit tests: done. Bugs: + fixed. + +2005-01-03 23:13 minam + + * ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/driver/dl/driver.rb, + lib/sqlite3/driver/native/driver.rb, test/tc_integration.rb: + Custom functions (aggregate and otherwise) are supported by the + native driver now. Test cases for the same. + +2005-01-03 13:51 minam + + * ext/sqlite3_api/MANIFEST, ext/sqlite3_api/extconf.rb, + ext/sqlite3_api/post-clean.rb, ext/sqlite3_api/post-distclean.rb, + ext/sqlite3_api/sqlite3_api.i, lib/sqlite3/database.rb, + lib/sqlite3/resultset.rb, lib/sqlite3/version.rb, + lib/sqlite3/driver/dl/driver.rb, + lib/sqlite3/driver/native/driver.rb, test/native-vs-dl.rb, + test/tc_integration.rb: Added preliminary implementation of + native driver (swig-based), and integration tests. + +2004-12-29 19:37 minam + + * lib/sqlite3/driver/dl/driver.rb: Some fixes to allow the DL + driver to work with Ruby 1.8.1. + +2004-12-29 14:52 minam + + * lib/sqlite3/: database.rb, version.rb: Made #quote a class method + (again). Bumped version to 0.6. + +2004-12-25 22:59 minam + + * lib/sqlite3/driver/dl/api.rb: Added check for darwin in supported + platforms (thanks to bitsweat). + +2004-12-22 12:38 minam + + * Rakefile: Rakefile wasn't packaging the README file. + +2004-12-21 22:28 minam + + * Rakefile, sqlite3-ruby.gemspec, test/bm.rb: Packaging now works. + Added benchmarks. + +2004-12-21 21:45 minam + + * LICENSE, README, Rakefile, setup.rb, sqlite3-ruby.gemspec, + doc/faq/faq.rb, doc/faq/faq.yml, lib/sqlite3.rb, + lib/sqlite3/statement.rb, lib/sqlite3/constants.rb, + lib/sqlite3/database.rb, lib/sqlite3/resultset.rb, + lib/sqlite3/translator.rb, lib/sqlite3/value.rb, + lib/sqlite3/version.rb, lib/sqlite3/errors.rb, + lib/sqlite3/pragmas.rb, lib/sqlite3/driver/dl/api.rb, + lib/sqlite3/driver/dl/driver.rb, test/mocks.rb, + test/tc_database.rb, test/tests.rb, test/driver/dl/tc_driver.rb: + Initial import + +2004-12-21 21:45 minam + + * LICENSE, README, Rakefile, setup.rb, sqlite3-ruby.gemspec, + doc/faq/faq.rb, doc/faq/faq.yml, lib/sqlite3.rb, + lib/sqlite3/statement.rb, lib/sqlite3/constants.rb, + lib/sqlite3/database.rb, lib/sqlite3/resultset.rb, + lib/sqlite3/translator.rb, lib/sqlite3/value.rb, + lib/sqlite3/version.rb, lib/sqlite3/errors.rb, + lib/sqlite3/pragmas.rb, lib/sqlite3/driver/dl/api.rb, + lib/sqlite3/driver/dl/driver.rb, test/mocks.rb, + test/tc_database.rb, test/tests.rb, test/driver/dl/tc_driver.rb: + Initial revision + diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/Gemfile b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/Gemfile new file mode 100644 index 0000000000..b4e2a20bb6 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE new file mode 100644 index 0000000000..75c06824ad --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2004, Jamis Buck (jamis@jamisbuck.org) +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. + + * The names of its contributors may not 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 COPYRIGHT OWNER 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.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE-DEPENDENCIES b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE-DEPENDENCIES new file mode 100644 index 0000000000..a79a03fa59 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/LICENSE-DEPENDENCIES @@ -0,0 +1,20 @@ +# Vendored Dependency Licenses + +The library `sqlite3-ruby` (which lives at https://github.com/sparklemotion/sqlite3-ruby) may include the source code for `sqlite` (which lives at https://www.sqlite.org/) + +`sqlite` source code is licensed under the public domain: + +> https://www.sqlite.org/copyright.html + +The license terms shipped with `sqlite` are included here for your convenience: + +``` +The author disclaims copyright to this source code. In place of +a legal notice, here is a blessing: + + May you do good and not evil. + May you find forgiveness for yourself and forgive others. + May you share freely, never taking more than you give. +``` + +Note that these license terms do not apply to the `sqlite3-ruby` library itself. diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/README.md b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/README.md new file mode 100644 index 0000000000..19df67553c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/README.md @@ -0,0 +1,235 @@ +# Ruby Interface for SQLite3 + +* Source code: https://github.com/sparklemotion/sqlite3-ruby +* Mailing list: http://groups.google.com/group/sqlite3-ruby +* Download: http://rubygems.org/gems/sqlite3 +* Documentation: http://www.rubydoc.info/gems/sqlite3 + +[![Unit tests](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/sqlite3-ruby.yml/badge.svg)](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/sqlite3-ruby.yml) +[![Native packages](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/gem-install.yml/badge.svg)](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/gem-install.yml) + + +## Description + +This library allows Ruby programs to use the SQLite3 database engine (http://www.sqlite.org). + +Note that this module is only compatible with SQLite 3.6.16 or newer. + + +## Synopsis + +``` ruby +require "sqlite3" + +# Open a database +db = SQLite3::Database.new "test.db" + +# Create a table +rows = db.execute <<-SQL + create table numbers ( + name varchar(30), + val int + ); +SQL + +# Execute a few inserts +{ + "one" => 1, + "two" => 2, +}.each do |pair| + db.execute "insert into numbers values ( ?, ? )", pair +end + +# Find a few rows +db.execute( "select * from numbers" ) do |row| + p row +end +# => ["one", 1] +# ["two", 2] + +# Create another table with multiple columns +db.execute <<-SQL + create table students ( + name varchar(50), + email varchar(50), + grade varchar(5), + blog varchar(50) + ); +SQL + +# Execute inserts with parameter markers +db.execute("INSERT INTO students (name, email, grade, blog) + VALUES (?, ?, ?, ?)", ["Jane", "me@janedoe.com", "A", "http://blog.janedoe.com"]) + +db.execute( "select * from students" ) do |row| + p row +end +# => ["Jane", "me@janedoe.com", "A", "http://blog.janedoe.com"] +``` + +## Installation + +### Native Gems (recommended) + +In v1.5.0 and later, native (precompiled) gems are available for recent Ruby versions on these platforms: + +- `aarch64-linux` (requires: glibc >= 2.29) +- `arm-linux` (requires: glibc >= 2.29) +- `arm64-darwin` +- `x64-mingw32` / `x64-mingw-ucrt` +- `x86-linux` (requires: glibc >= 2.17) +- `x86_64-darwin` +- `x86_64-linux` (requires: glibc >= 2.17) + +If you are using one of these Ruby versions on one of these platforms, the native gem is the recommended way to install sqlite3-ruby. + +For example, on a linux system running Ruby 3.1: + +``` text +$ ruby -v +ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux] + +$ time gem install sqlite3 +Fetching sqlite3-1.5.0-x86_64-linux.gem +Successfully installed sqlite3-1.5.0-x86_64-linux +1 gem installed + +real 0m4.274s +user 0m0.734s +sys 0m0.165s +``` + +#### Avoiding the precompiled native gem + +The maintainers strongly urge you to use a native gem if at all possible. It will be a better experience for you and allow us to focus our efforts on improving functionality rather than diagnosing installation issues. + +If you're on a platform that supports a native gem but you want to avoid using it in your project, do one of the following: + +- If you're not using Bundler, then run `gem install sqlite3 --platform=ruby` +- If you are using Bundler + - version 2.3.18 or later, you can specify [`gem "sqlite3", force_ruby_platform: true`](https://bundler.io/v2.3/man/gemfile.5.html#FORCE_RUBY_PLATFORM) + - version 2.1 or later, then you'll need to run `bundle config set force_ruby_platform true` + - version 2.0 or earlier, then you'll need to run `bundle config force_ruby_platform true` + + +### Compiling the source gem + +If you are on a platform or version of Ruby that is not covered by the Native Gems, then the vanilla "ruby platform" (non-native) gem will be installed by the `gem install` or `bundle` commands. + + +#### Packaged libsqlite3 + +By default, as of v1.5.0 of this library, the latest available version of libsqlite3 is packaged with the gem and will be compiled and used automatically. This takes a bit longer than the native gem, but will provide a modern, well-supported version of libsqlite3. + +For example, on a linux system running Ruby 2.5: + +``` text +$ ruby -v +ruby 2.5.9p229 (2021-04-05 revision 67939) [x86_64-linux] + +$ time gem install sqlite3 +Building native extensions. This could take a while... +Successfully installed sqlite3-1.5.0 +1 gem installed + +real 0m20.620s +user 0m23.361s +sys 0m5.839s +``` + + +#### System libsqlite3 + +If you would prefer to build the sqlite3-ruby gem against your system libsqlite3, which requires that you install libsqlite3 and its development files yourself, you may do so by using the `--enable-system-libraries` flag at gem install time. + +PLEASE NOTE: + +- you must avoid installing a precompiled native gem (see [previous section](#avoiding-the-precompiled-native-gem)) +- only versions of libsqlite3 `>= 3.5.0` are supported, +- and some library features may depend on how your libsqlite3 was compiled. + +For example, on a linux system running Ruby 2.5: + +``` text +$ time gem install sqlite3 -- --enable-system-libraries +Building native extensions with: '--enable-system-libraries' +This could take a while... +Successfully installed sqlite3-1.5.0 +1 gem installed + +real 0m4.234s +user 0m3.809s +sys 0m0.912s +``` + +If you're using bundler, you can opt into system libraries like this: + +``` sh +bundle config build.sqlite3 --enable-system-libraries +``` + +If you have sqlite3 installed in a non-standard location, you may need to specify the location of the include and lib files by using `--with-sqlite-include` and `--with-sqlite-lib` options (or a `--with-sqlite-dir` option, see [MakeMakefile#dir_config](https://ruby-doc.org/stdlib-3.1.1/libdoc/mkmf/rdoc/MakeMakefile.html#method-i-dir_config)). If you have pkg-config installed and configured properly, this may not be necessary. + +``` sh +gem install sqlite3 -- \ + --enable-system-libraries \ + --with-sqlite3-include=/opt/local/include \ + --with-sqlite3-lib=/opt/local/lib +``` + + +#### System libsqlcipher + +If you'd like to link against a system-installed libsqlcipher, you may do so by using the `--with-sqlcipher` flag: + +``` text +$ time gem install sqlite3 -- --with-sqlcipher +Building native extensions with: '--with-sqlcipher' +This could take a while... +Successfully installed sqlite3-1.5.0 +1 gem installed + +real 0m4.772s +user 0m3.906s +sys 0m0.896s +``` + +If you have sqlcipher installed in a non-standard location, you may need to specify the location of the include and lib files by using `--with-sqlite-include` and `--with-sqlite-lib` options (or a `--with-sqlite-dir` option, see [MakeMakefile#dir_config](https://ruby-doc.org/stdlib-3.1.1/libdoc/mkmf/rdoc/MakeMakefile.html#method-i-dir_config)). If you have pkg-config installed and configured properly, this may not be necessary. + + +## Support + +### Something has gone wrong! Where do I get help? + +You can ask for help or support from the +[sqlite3-ruby mailing list](http://groups.google.com/group/sqlite3-ruby) which +can be found here: + +> http://groups.google.com/group/sqlite3-ruby + + +### I've found a bug! How do I report it? + +After contacting the mailing list, you've found that you've uncovered a bug. You can file the bug at the [github issues page](https://github.com/sparklemotion/sqlite3-ruby/issues) which can be found here: + +> https://github.com/sparklemotion/sqlite3-ruby/issues + + +## Usage + +For help figuring out the SQLite3/Ruby interface, check out the SYNOPSIS as well as the RDoc. It includes examples of usage. If you have any questions that you feel should be addressed in the FAQ, please send them to [the mailing list](http://groups.google.com/group/sqlite3-ruby). + + +## Contributing + +See [`CONTRIBUTING.md`](./CONTRIBUTING.md). + + +## License + +This library is licensed under `BSD-3-Clause`, see [`LICENSE`](./LICENSE). + + +### Dependencies + +The source code of `sqlite` is distributed in the "ruby platform" gem. This code is public domain, see [`LICENSE-DEPENDENCIES`](./LICENSE-DEPENDENCIES) for details. diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/dependencies.yml b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/dependencies.yml new file mode 100644 index 0000000000..0fc854da12 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/dependencies.yml @@ -0,0 +1,14 @@ +# TODO: stop using symbols here once we no longer support Ruby 2.7 and can rely on symbolize_names +:sqlite3: + # checksum verified by first checking the published sha3(256) checksum against https://sqlite.org/download.html: + # + # $ sha3sum -a 256 ports/archives/sqlite-autoconf-3410000.tar.gz + # d783ab44a2b44394331d392b8b8d4d2ea4964cbb2befc7c6c649bcfbdb3c9ffe ports/archives/sqlite-autoconf-3410000.tar.gz + # + # $ sha256sum ports/archives/sqlite-autoconf-3410000.tar.gz + # 49f77ac53fd9aa5d7395f2499cb816410e5621984a121b858ccca05310b05c70 ports/archives/sqlite-autoconf-3410000.tar.gz + # + :version: "3.41.0" + :files: + - :url: "https://sqlite.org/2023/sqlite-autoconf-3410000.tar.gz" + :sha256: "49f77ac53fd9aa5d7395f2499cb816410e5621984a121b858ccca05310b05c70" diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.c new file mode 100644 index 0000000000..42044bb39f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.c @@ -0,0 +1,274 @@ +#include +#include + +/* wraps a factory "handler" class. The "-aggregators" instance variable of + * the SQLite3::Database holds an array of all AggrogatorWrappers. + * + * An AggregatorWrapper holds the following instance variables: + * -handler_klass: the handler that creates the instances. + * -instances: array of all the cAggregatorInstance objects currently + * in-flight for this aggregator. */ +static VALUE cAggregatorWrapper; + +/* wraps a instance of the "handler" class. Loses its reference at the end of + * the xFinal callback. + * + * An AggregatorInstance holds the following instance variables: + * -handler_instance: the instance to call `step` and `finalize` on. + * -exc_status: status returned by rb_protect. + * != 0 if an exception occurred. If an exception occurred + * `step` and `finalize` won't be called any more. */ +static VALUE cAggregatorInstance; + +typedef struct rb_sqlite3_protected_funcall_args { + VALUE self; + ID method; + int argc; + VALUE *params; +} protected_funcall_args_t; + +/* why isn't there something like this in the ruby API? */ +static VALUE +rb_sqlite3_protected_funcall_body(VALUE protected_funcall_args_ptr) +{ + protected_funcall_args_t *args = + (protected_funcall_args_t*)protected_funcall_args_ptr; + + return rb_funcall2(args->self, args->method, args->argc, args->params); +} + +static VALUE +rb_sqlite3_protected_funcall(VALUE self, ID method, int argc, VALUE *params, + int* exc_status) +{ + protected_funcall_args_t args = { + .self = self, .method = method, .argc = argc, .params = params + }; + return rb_protect(rb_sqlite3_protected_funcall_body, (VALUE)(&args), exc_status); +} + +/* called in rb_sqlite3_aggregator_step and rb_sqlite3_aggregator_final. It + * checks if the execution context already has an associated instance. If it + * has one, it returns it. If there is no instance yet, it creates one and + * associates it with the context. */ +static VALUE +rb_sqlite3_aggregate_instance(sqlite3_context *ctx) +{ + VALUE aw = (VALUE) sqlite3_user_data(ctx); + VALUE handler_klass = rb_iv_get(aw, "-handler_klass"); + VALUE inst; + VALUE *inst_ptr = sqlite3_aggregate_context(ctx, (int)sizeof(VALUE)); + + if (!inst_ptr) { + rb_fatal("SQLite is out-of-merory"); + } + + inst = *inst_ptr; + + if (inst == Qfalse) { /* Qfalse == 0 */ + VALUE instances = rb_iv_get(aw, "-instances"); + int exc_status; + + inst = rb_class_new_instance(0, NULL, cAggregatorInstance); + rb_iv_set(inst, "-handler_instance", rb_sqlite3_protected_funcall( + handler_klass, rb_intern("new"), 0, NULL, &exc_status)); + rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); + + rb_ary_push(instances, inst); + + *inst_ptr = inst; + } + + if (inst == Qnil) { + rb_fatal("SQLite called us back on an already destroyed aggregate instance"); + } + + return inst; +} + +/* called by rb_sqlite3_aggregator_final. Unlinks and frees the + * aggregator_instance_t, so the handler_instance won't be marked any more + * and Ruby's GC may free it. */ +static void +rb_sqlite3_aggregate_instance_destroy(sqlite3_context *ctx) +{ + VALUE aw = (VALUE) sqlite3_user_data(ctx); + VALUE instances = rb_iv_get(aw, "-instances"); + VALUE *inst_ptr = sqlite3_aggregate_context(ctx, 0); + VALUE inst; + + if (!inst_ptr || (inst = *inst_ptr)) { + return; + } + + if (inst == Qnil) { + rb_fatal("attempt to destroy aggregate instance twice"); + } + + rb_iv_set(inst, "-handler_instance", Qnil); // may catch use-after-free + if (rb_ary_delete(instances, inst) == Qnil) { + rb_fatal("must be in instances at that point"); + } + + *inst_ptr = Qnil; +} + +static void +rb_sqlite3_aggregator_step(sqlite3_context * ctx, int argc, sqlite3_value **argv) +{ + VALUE inst = rb_sqlite3_aggregate_instance(ctx); + VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); + VALUE * params = NULL; + VALUE one_param; + int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); + int i; + + if (exc_status) { + return; + } + + if (argc == 1) { + one_param = sqlite3val2rb(argv[0]); + params = &one_param; + } + if (argc > 1) { + params = xcalloc((size_t)argc, sizeof(VALUE)); + for(i = 0; i < argc; i++) { + params[i] = sqlite3val2rb(argv[i]); + } + } + rb_sqlite3_protected_funcall( + handler_instance, rb_intern("step"), argc, params, &exc_status); + if (argc > 1) { + xfree(params); + } + + rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); +} + +/* we assume that this function is only called once per execution context */ +static void +rb_sqlite3_aggregator_final(sqlite3_context * ctx) +{ + VALUE inst = rb_sqlite3_aggregate_instance(ctx); + VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); + int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); + + if (!exc_status) { + VALUE result = rb_sqlite3_protected_funcall( + handler_instance, rb_intern("finalize"), 0, NULL, &exc_status); + if (!exc_status) { + set_sqlite3_func_result(ctx, result); + } + } + + if (exc_status) { + /* the user should never see this, as Statement.step() will pick up the + * outstanding exception and raise it instead of generating a new one + * for SQLITE_ERROR with message "Ruby Exception occurred" */ + sqlite3_result_error(ctx, "Ruby Exception occurred", -1); + } + + rb_sqlite3_aggregate_instance_destroy(ctx); +} + +/* call-seq: define_aggregator2(aggregator) + * + * Define an aggregrate function according to a factory object (the "handler") + * that knows how to obtain to all the information. The handler must provide + * the following class methods: + * + * +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This + * message is optional, and if the handler does not respond to it, + * the function will have an arity of -1. + * +name+:: this is the name of the function. The handler _must_ implement + * this message. + * +new+:: this must be implemented by the handler. It should return a new + * instance of the object that will handle a specific invocation of + * the function. + * + * The handler instance (the object returned by the +new+ message, described + * above), must respond to the following messages: + * + * +step+:: this is the method that will be called for each step of the + * aggregate function's evaluation. It should take parameters according + * to the *arity* definition. + * +finalize+:: this is the method that will be called to finalize the + * aggregate function's evaluation. It should not take arguments. + * + * Note the difference between this function and #create_aggregate_handler + * is that no FunctionProxy ("ctx") object is involved. This manifests in two + * ways: The return value of the aggregate function is the return value of + * +finalize+ and neither +step+ nor +finalize+ take an additional "ctx" + * parameter. + */ +VALUE +rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name) +{ + /* define_aggregator is added as a method to SQLite3::Database in database.c */ + sqlite3RubyPtr ctx; + int arity, status; + VALUE aw; + VALUE aggregators; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + if (!ctx->db) { + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); + } + + if (rb_respond_to(aggregator, rb_intern("arity"))) { + VALUE ruby_arity = rb_funcall(aggregator, rb_intern("arity"), 0); + arity = NUM2INT(ruby_arity); + } else { + arity = -1; + } + + if (arity < -1 || arity > 127) { +#ifdef PRIsVALUE + rb_raise(rb_eArgError, "%"PRIsVALUE" arity=%d out of range -1..127", + self, arity); +#else + rb_raise(rb_eArgError, "Aggregator arity=%d out of range -1..127", arity); +#endif + } + + if (!rb_ivar_defined(self, rb_intern("-aggregators"))) { + rb_iv_set(self, "-aggregators", rb_ary_new()); + } + aggregators = rb_iv_get(self, "-aggregators"); + + aw = rb_class_new_instance(0, NULL, cAggregatorWrapper); + rb_iv_set(aw, "-handler_klass", aggregator); + rb_iv_set(aw, "-instances", rb_ary_new()); + + status = sqlite3_create_function( + ctx->db, + StringValueCStr(ruby_name), + arity, + SQLITE_UTF8, + (void*)aw, + NULL, + rb_sqlite3_aggregator_step, + rb_sqlite3_aggregator_final + ); + + if (status != SQLITE_OK) { + rb_sqlite3_raise(ctx->db, status); + return self; // just in case rb_sqlite3_raise returns. + } + + rb_ary_push(aggregators, aw); + + return self; +} + +void +rb_sqlite3_aggregator_init(void) +{ + /* rb_class_new generatos class with undefined allocator in ruby 1.9 */ + cAggregatorWrapper = rb_funcall(rb_cClass, rb_intern("new"), 0); + rb_gc_register_mark_object(cAggregatorWrapper); + + cAggregatorInstance = rb_funcall(rb_cClass, rb_intern("new"), 0); + rb_gc_register_mark_object(cAggregatorInstance); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.h new file mode 100644 index 0000000000..68968d15be --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/aggregator.h @@ -0,0 +1,12 @@ +#ifndef SQLITE3_AGGREGATOR_RUBY +#define SQLITE3_AGGREGATOR_RUBY + +#include + +VALUE +rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name); + +void +rb_sqlite3_aggregator_init(void); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.c new file mode 100644 index 0000000000..135cb65f77 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.c @@ -0,0 +1,168 @@ +#ifdef HAVE_SQLITE3_BACKUP_INIT + +#include + +#define REQUIRE_OPEN_BACKUP(_ctxt) \ + if(!_ctxt->p) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed backup"); + +VALUE cSqlite3Backup; + +static void deallocate(void * ctx) +{ + sqlite3BackupRubyPtr c = (sqlite3BackupRubyPtr)ctx; + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3BackupRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3BackupRuby)); + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +/* call-seq: SQLite3::Backup.new(dstdb, dstname, srcdb, srcname) + * + * Initialize backup the backup. + * + * dstdb: + * the destination SQLite3::Database object. + * dstname: + * the destination's database name. + * srcdb: + * the source SQLite3::Database object. + * srcname: + * the source's database name. + * + * The database name is "main", "temp", or the name specified in an + * ATTACH statement. + * + * This feature requires SQLite 3.6.11 or later. + * + * require 'sqlite3' + * sdb = SQLite3::Database.new('src.sqlite3') + * + * ddb = SQLite3::Database.new(':memory:') + * b = SQLite3::Backup.new(ddb, 'main', sdb, 'main') + * p [b.remaining, b.pagecount] # invalid value; for example [0, 0] + * begin + * p b.step(1) #=> OK or DONE + * p [b.remaining, b.pagecount] + * end while b.remaining > 0 + * b.finish + * + * ddb = SQLite3::Database.new(':memory:') + * b = SQLite3::Backup.new(ddb, 'main', sdb, 'main') + * b.step(-1) #=> DONE + * b.finish + * + */ +static VALUE initialize(VALUE self, VALUE dstdb, VALUE dstname, VALUE srcdb, VALUE srcname) +{ + sqlite3BackupRubyPtr ctx; + sqlite3RubyPtr ddb_ctx, sdb_ctx; + sqlite3_backup *pBackup; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + Data_Get_Struct(dstdb, sqlite3Ruby, ddb_ctx); + Data_Get_Struct(srcdb, sqlite3Ruby, sdb_ctx); + + if(!sdb_ctx->db) + rb_raise(rb_eArgError, "cannot backup from a closed database"); + if(!ddb_ctx->db) + rb_raise(rb_eArgError, "cannot backup to a closed database"); + + pBackup = sqlite3_backup_init(ddb_ctx->db, StringValuePtr(dstname), + sdb_ctx->db, StringValuePtr(srcname)); + if( pBackup ){ + ctx->p = pBackup; + } + else { + CHECK(ddb_ctx->db, sqlite3_errcode(ddb_ctx->db)); + } + + return self; +} + +/* call-seq: SQLite3::Backup#step(nPage) + * + * Copy database pages up to +nPage+. + * If negative, copy all remaining source pages. + * + * If all pages are copied, it returns SQLite3::Constants::ErrorCode::DONE. + * When coping is not done, it returns SQLite3::Constants::ErrorCode::OK. + * When some errors occur, it returns the error code. + */ +static VALUE step(VALUE self, VALUE nPage) +{ + sqlite3BackupRubyPtr ctx; + int status; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + status = sqlite3_backup_step(ctx->p, NUM2INT(nPage)); + return INT2NUM(status); +} + +/* call-seq: SQLite3::Backup#finish + * + * Destroy the backup object. + */ +static VALUE finish(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + (void)sqlite3_backup_finish(ctx->p); + ctx->p = NULL; + return Qnil; +} + +/* call-seq: SQLite3::Backup#remaining + * + * Returns the number of pages still to be backed up. + * + * Note that the value is only updated after step() is called, + * so before calling step() returned value is invalid. + */ +static VALUE remaining(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + return INT2NUM(sqlite3_backup_remaining(ctx->p)); +} + +/* call-seq: SQLite3::Backup#pagecount + * + * Returns the total number of pages in the source database file. + * + * Note that the value is only updated after step() is called, + * so before calling step() returned value is invalid. + */ +static VALUE pagecount(VALUE self) +{ + sqlite3BackupRubyPtr ctx; + + Data_Get_Struct(self, sqlite3BackupRuby, ctx); + REQUIRE_OPEN_BACKUP(ctx); + return INT2NUM(sqlite3_backup_pagecount(ctx->p)); +} + +void init_sqlite3_backup(void) +{ +#if 0 + VALUE mSqlite3 = rb_define_module("SQLite3"); +#endif + cSqlite3Backup = rb_define_class_under(mSqlite3, "Backup", rb_cObject); + + rb_define_alloc_func(cSqlite3Backup, allocate); + rb_define_method(cSqlite3Backup, "initialize", initialize, 4); + rb_define_method(cSqlite3Backup, "step", step, 1); + rb_define_method(cSqlite3Backup, "finish", finish, 0); + rb_define_method(cSqlite3Backup, "remaining", remaining, 0); + rb_define_method(cSqlite3Backup, "pagecount", pagecount, 0); +} + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.h new file mode 100644 index 0000000000..0c8c62022b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/backup.h @@ -0,0 +1,15 @@ +#if !defined(SQLITE3_BACKUP_RUBY) && defined(HAVE_SQLITE3_BACKUP_INIT) +#define SQLITE3_BACKUP_RUBY + +#include + +struct _sqlite3BackupRuby { + sqlite3_backup *p; +}; + +typedef struct _sqlite3BackupRuby sqlite3BackupRuby; +typedef sqlite3BackupRuby * sqlite3BackupRubyPtr; + +void init_sqlite3_backup(); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.c new file mode 100644 index 0000000000..419cdaefb8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.c @@ -0,0 +1,853 @@ +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4028 ) +#endif + +#define REQUIRE_OPEN_DB(_ctxt) \ + if(!_ctxt->db) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); + +VALUE cSqlite3Database; + +static void deallocate(void * ctx) +{ + sqlite3RubyPtr c = (sqlite3RubyPtr)ctx; + sqlite3 * db = c->db; + + if(db) sqlite3_close(db); + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3RubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3Ruby)); + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +static char * +utf16_string_value_ptr(VALUE str) +{ + StringValue(str); + rb_str_buf_cat(str, "\x00\x00", 2L); + return RSTRING_PTR(str); +} + +static VALUE sqlite3_rb_close(VALUE self); + +static VALUE rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs) +{ + sqlite3RubyPtr ctx; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + +#if defined TAINTING_SUPPORT +# if defined StringValueCStr + StringValuePtr(file); + rb_check_safe_obj(file); +# else + Check_SafeStr(file); +# endif +#endif + + status = sqlite3_open_v2( + StringValuePtr(file), + &ctx->db, + NUM2INT(mode), + NIL_P(zvfs) ? NULL : StringValuePtr(zvfs) + ); + + CHECK(ctx->db, status) + + return self; +} + +static VALUE rb_sqlite3_disable_quirk_mode(VALUE self) +{ +#if defined SQLITE_DBCONFIG_DQS_DDL + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + + if(!ctx->db) return Qfalse; + + sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DDL, 0, (void*)0); + sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DML, 0, (void*)0); + + return Qtrue; +#else + return Qfalse; +#endif +} + +/* call-seq: db.close + * + * Closes this database. + */ +static VALUE sqlite3_rb_close(VALUE self) +{ + sqlite3RubyPtr ctx; + sqlite3 * db; + Data_Get_Struct(self, sqlite3Ruby, ctx); + + db = ctx->db; + CHECK(db, sqlite3_close(ctx->db)); + + ctx->db = NULL; + + rb_iv_set(self, "-aggregators", Qnil); + + return self; +} + +/* call-seq: db.closed? + * + * Returns +true+ if this database instance has been closed (see #close). + */ +static VALUE closed_p(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + + if(!ctx->db) return Qtrue; + + return Qfalse; +} + +/* call-seq: total_changes + * + * Returns the total number of changes made to this database instance + * since it was opened. + */ +static VALUE total_changes(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM((long)sqlite3_total_changes(ctx->db)); +} + +static void tracefunc(void * data, const char *sql) +{ + VALUE self = (VALUE)data; + VALUE thing = rb_iv_get(self, "@tracefunc"); + rb_funcall(thing, rb_intern("call"), 1, rb_str_new2(sql)); +} + +/* call-seq: + * trace { |sql| ... } + * trace(Class.new { def call sql; end }.new) + * + * Installs (or removes) a block that will be invoked for every SQL + * statement executed. The block receives one parameter: the SQL statement + * executed. If the block is +nil+, any existing tracer will be uninstalled. + */ +static VALUE trace(int argc, VALUE *argv, VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE block; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + rb_scan_args(argc, argv, "01", &block); + + if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); + + rb_iv_set(self, "@tracefunc", block); + + sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc, (void *)self); + + return self; +} + +static int rb_sqlite3_busy_handler(void * ctx, int count) +{ + VALUE self = (VALUE)(ctx); + VALUE handle = rb_iv_get(self, "@busy_handler"); + VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM((long)count)); + + if(Qfalse == result) return 0; + + return 1; +} + +/* call-seq: + * busy_handler { |count| ... } + * busy_handler(Class.new { def call count; end }.new) + * + * Register a busy handler with this database instance. When a requested + * resource is busy, this handler will be invoked. If the handler returns + * +false+, the operation will be aborted; otherwise, the resource will + * be requested again. + * + * The handler will be invoked with the name of the resource that was + * busy, and the number of times it has been retried. + * + * See also the mutually exclusive #busy_timeout. + */ +static VALUE busy_handler(int argc, VALUE *argv, VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE block; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + rb_scan_args(argc, argv, "01", &block); + + if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); + + rb_iv_set(self, "@busy_handler", block); + + status = sqlite3_busy_handler( + ctx->db, NIL_P(block) ? NULL : rb_sqlite3_busy_handler, (void *)self); + + CHECK(ctx->db, status); + + return self; +} + +/* call-seq: last_insert_row_id + * + * Obtains the unique row ID of the last row to be inserted by this Database + * instance. + */ +static VALUE last_insert_row_id(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return LL2NUM(sqlite3_last_insert_rowid(ctx->db)); +} + +VALUE sqlite3val2rb(sqlite3_value * val) +{ + switch(sqlite3_value_type(val)) { + case SQLITE_INTEGER: + return LL2NUM(sqlite3_value_int64(val)); + break; + case SQLITE_FLOAT: + return rb_float_new(sqlite3_value_double(val)); + break; + case SQLITE_TEXT: + return rb_str_new2((const char *)sqlite3_value_text(val)); + break; + case SQLITE_BLOB: { + /* Sqlite warns calling sqlite3_value_bytes may invalidate pointer from sqlite3_value_blob, + so we explicitly get the length before getting blob pointer. + Note that rb_str_new apparently create string with ASCII-8BIT (BINARY) encoding, + which is what we want, as blobs are binary + */ + int len = sqlite3_value_bytes(val); + return rb_str_new((const char *)sqlite3_value_blob(val), len); + break; + } + case SQLITE_NULL: + return Qnil; + break; + default: + rb_raise(rb_eRuntimeError, "bad type"); /* FIXME */ + } +} + +void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result) +{ + switch(TYPE(result)) { + case T_NIL: + sqlite3_result_null(ctx); + break; + case T_FIXNUM: + sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result)); + break; + case T_BIGNUM: { +#if SIZEOF_LONG < 8 + sqlite3_int64 num64; + + if (bignum_to_int64(result, &num64)) { + sqlite3_result_int64(ctx, num64); + break; + } +#endif + } + case T_FLOAT: + sqlite3_result_double(ctx, NUM2DBL(result)); + break; + case T_STRING: + if(CLASS_OF(result) == cSqlite3Blob + || rb_enc_get_index(result) == rb_ascii8bit_encindex() + ) { + sqlite3_result_blob( + ctx, + (const void *)StringValuePtr(result), + (int)RSTRING_LEN(result), + SQLITE_TRANSIENT + ); + } else { + sqlite3_result_text( + ctx, + (const char *)StringValuePtr(result), + (int)RSTRING_LEN(result), + SQLITE_TRANSIENT + ); + } + break; + default: + rb_raise(rb_eRuntimeError, "can't return %s", + rb_class2name(CLASS_OF(result))); + } +} + +static void rb_sqlite3_func(sqlite3_context * ctx, int argc, sqlite3_value **argv) +{ + VALUE callable = (VALUE)sqlite3_user_data(ctx); + VALUE params = rb_ary_new2(argc); + VALUE result; + int i; + + if (argc > 0) { + for(i = 0; i < argc; i++) { + VALUE param = sqlite3val2rb(argv[i]); + rb_ary_push(params, param); + } + } + + result = rb_apply(callable, rb_intern("call"), params); + + set_sqlite3_func_result(ctx, result); +} + +#ifndef HAVE_RB_PROC_ARITY +int rb_proc_arity(VALUE self) +{ + return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0)); +} +#endif + +/* call-seq: define_function_with_flags(name, flags) { |args,...| } + * + * Define a function named +name+ with +args+ using TextRep bitflags +flags+. The arity of the block + * will be used as the arity for the function defined. + */ +static VALUE define_function_with_flags(VALUE self, VALUE name, VALUE flags) +{ + sqlite3RubyPtr ctx; + VALUE block; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + block = rb_block_proc(); + + status = sqlite3_create_function( + ctx->db, + StringValuePtr(name), + rb_proc_arity(block), + NUM2INT(flags), + (void *)block, + rb_sqlite3_func, + NULL, + NULL + ); + + CHECK(ctx->db, status); + + rb_hash_aset(rb_iv_get(self, "@functions"), name, block); + + return self; +} + +/* call-seq: define_function(name) { |args,...| } + * + * Define a function named +name+ with +args+. The arity of the block + * will be used as the arity for the function defined. + */ +static VALUE define_function(VALUE self, VALUE name) +{ + return define_function_with_flags(self, name, INT2FIX(SQLITE_UTF8)); +} + +/* call-seq: interrupt + * + * Interrupts the currently executing operation, causing it to abort. + */ +static VALUE interrupt(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + sqlite3_interrupt(ctx->db); + + return self; +} + +/* call-seq: errmsg + * + * Return a string describing the last error to have occurred with this + * database. + */ +static VALUE errmsg(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return rb_str_new2(sqlite3_errmsg(ctx->db)); +} + +/* call-seq: errcode + * + * Return an integer representing the last error to have occurred with this + * database. + */ +static VALUE errcode_(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM((long)sqlite3_errcode(ctx->db)); +} + +/* call-seq: complete?(sql) + * + * Return +true+ if the string is a valid (ie, parsable) SQL statement, and + * +false+ otherwise. + */ +static VALUE complete_p(VALUE UNUSED(self), VALUE sql) +{ + if(sqlite3_complete(StringValuePtr(sql))) + return Qtrue; + + return Qfalse; +} + +/* call-seq: changes + * + * Returns the number of changes made to this database instance by the last + * operation performed. Note that a "delete from table" without a where + * clause will not affect this value. + */ +static VALUE changes(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return INT2NUM(sqlite3_changes(ctx->db)); +} + +static int rb_sqlite3_auth( + void *ctx, + int _action, + const char * _a, + const char * _b, + const char * _c, + const char * _d) +{ + VALUE self = (VALUE)ctx; + VALUE action = INT2NUM(_action); + VALUE a = _a ? rb_str_new2(_a) : Qnil; + VALUE b = _b ? rb_str_new2(_b) : Qnil; + VALUE c = _c ? rb_str_new2(_c) : Qnil; + VALUE d = _d ? rb_str_new2(_d) : Qnil; + VALUE callback = rb_iv_get(self, "@authorizer"); + VALUE result = rb_funcall(callback, rb_intern("call"), 5, action, a, b, c, d); + + if(T_FIXNUM == TYPE(result)) return (int)NUM2INT(result); + if(Qtrue == result) return SQLITE_OK; + if(Qfalse == result) return SQLITE_DENY; + + return SQLITE_IGNORE; +} + +/* call-seq: set_authorizer = auth + * + * Set the authorizer for this database. +auth+ must respond to +call+, and + * +call+ must take 5 arguments. + * + * Installs (or removes) a block that will be invoked for every access + * to the database. If the block returns 0 (or +true+), the statement + * is allowed to proceed. Returning 1 or false causes an authorization error to + * occur, and returning 2 or nil causes the access to be silently denied. + */ +static VALUE set_authorizer(VALUE self, VALUE authorizer) +{ + sqlite3RubyPtr ctx; + int status; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + status = sqlite3_set_authorizer( + ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth, (void *)self + ); + + CHECK(ctx->db, status); + + rb_iv_set(self, "@authorizer", authorizer); + + return self; +} + +/* call-seq: db.busy_timeout = ms + * + * Indicates that if a request for a resource terminates because that + * resource is busy, SQLite should sleep and retry for up to the indicated + * number of milliseconds. By default, SQLite does not retry + * busy resources. To restore the default behavior, send 0 as the + * +ms+ parameter. + * + * See also the mutually exclusive #busy_handler. + */ +static VALUE set_busy_timeout(VALUE self, VALUE timeout) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout))); + + return self; +} + +/* call-seq: db.extended_result_codes = true + * + * Enable extended result codes in SQLite. These result codes allow for more + * detailed exception reporting, such a which type of constraint is violated. + */ +static VALUE set_extended_result_codes(VALUE self, VALUE enable) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_extended_result_codes(ctx->db, RTEST(enable) ? 1 : 0)); + + return self; +} + +int rb_comparator_func(void * ctx, int a_len, const void * a, int b_len, const void * b) +{ + VALUE comparator; + VALUE a_str; + VALUE b_str; + VALUE comparison; + rb_encoding * internal_encoding; + + internal_encoding = rb_default_internal_encoding(); + + comparator = (VALUE)ctx; + a_str = rb_str_new((const char *)a, a_len); + b_str = rb_str_new((const char *)b, b_len); + + rb_enc_associate_index(a_str, rb_utf8_encindex()); + rb_enc_associate_index(b_str, rb_utf8_encindex()); + + if(internal_encoding) { + a_str = rb_str_export_to_enc(a_str, internal_encoding); + b_str = rb_str_export_to_enc(b_str, internal_encoding); + } + + comparison = rb_funcall(comparator, rb_intern("compare"), 2, a_str, b_str); + + return NUM2INT(comparison); +} + +/* call-seq: db.collation(name, comparator) + * + * Add a collation with name +name+, and a +comparator+ object. The + * +comparator+ object should implement a method called "compare" that takes + * two parameters and returns an integer less than, equal to, or greater than + * 0. + */ +static VALUE collation(VALUE self, VALUE name, VALUE comparator) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + CHECK(ctx->db, sqlite3_create_collation( + ctx->db, + StringValuePtr(name), + SQLITE_UTF8, + (void *)comparator, + NIL_P(comparator) ? NULL : rb_comparator_func)); + + /* Make sure our comparator doesn't get garbage collected. */ + rb_hash_aset(rb_iv_get(self, "@collations"), name, comparator); + + return self; +} + +#ifdef HAVE_SQLITE3_LOAD_EXTENSION +/* call-seq: db.load_extension(file) + * + * Loads an SQLite extension library from the named file. Extension + * loading must be enabled using db.enable_load_extension(true) prior + * to calling this API. + */ +static VALUE load_extension(VALUE self, VALUE file) +{ + sqlite3RubyPtr ctx; + int status; + char *errMsg; + VALUE errexp; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + status = sqlite3_load_extension(ctx->db, StringValuePtr(file), 0, &errMsg); + if (status != SQLITE_OK) + { + errexp = rb_exc_new2(rb_eRuntimeError, errMsg); + sqlite3_free(errMsg); + rb_exc_raise(errexp); + } + + return self; +} +#endif + +#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION +/* call-seq: db.enable_load_extension(onoff) + * + * Enable or disable extension loading. + */ +static VALUE enable_load_extension(VALUE self, VALUE onoff) +{ + sqlite3RubyPtr ctx; + int onoffparam; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + if (Qtrue == onoff) { + onoffparam = 1; + } else if (Qfalse == onoff) { + onoffparam = 0; + } else { + onoffparam = (int)NUM2INT(onoff); + } + + CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, onoffparam)); + + return self; +} +#endif + +static int enc_cb(void * _self, int UNUSED(columns), char **data, char **UNUSED(names)) +{ + VALUE self = (VALUE)_self; + + int index = rb_enc_find_index(data[0]); + rb_encoding * e = rb_enc_from_index(index); + rb_iv_set(self, "@encoding", rb_enc_from_encoding(e)); + + return 0; +} + +/* call-seq: db.encoding + * + * Fetch the encoding set on this database + */ +static VALUE db_encoding(VALUE self) +{ + sqlite3RubyPtr ctx; + VALUE enc; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + enc = rb_iv_get(self, "@encoding"); + + if(NIL_P(enc)) { + sqlite3_exec(ctx->db, "PRAGMA encoding", enc_cb, (void *)self, NULL); + } + + return rb_iv_get(self, "@encoding"); +} + +/* call-seq: db.transaction_active? + * + * Returns +true+ if there is a transaction active, and +false+ otherwise. + * + */ +static VALUE transaction_active_p(VALUE self) +{ + sqlite3RubyPtr ctx; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + return sqlite3_get_autocommit(ctx->db) ? Qfalse : Qtrue; +} + +static int hash_callback_function(VALUE callback_ary, int count, char **data, char **columns) +{ + VALUE new_hash = rb_hash_new(); + int i; + + for (i = 0; i < count; i++) { + if (data[i] == NULL) { + rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), Qnil); + } else { + rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), rb_str_new_cstr(data[i])); + } + } + + rb_ary_push(callback_ary, new_hash); + + return 0; +} + +static int regular_callback_function(VALUE callback_ary, int count, char **data, char **columns) +{ + VALUE new_ary = rb_ary_new(); + int i; + + for (i = 0; i < count; i++) { + if (data[i] == NULL) { + rb_ary_push(new_ary, Qnil); + } else { + rb_ary_push(new_ary, rb_str_new_cstr(data[i])); + } + } + + rb_ary_push(callback_ary, new_ary); + + return 0; +} + + +/* Is invoked by calling db.execute_batch2(sql, &block) + * + * Executes all statements in a given string separated by semicolons. + * If a query is made, all values returned are strings + * (except for 'NULL' values which return nil), + * so the user may parse values with a block. + * If no query is made, an empty array will be returned. + */ +static VALUE exec_batch(VALUE self, VALUE sql, VALUE results_as_hash) +{ + sqlite3RubyPtr ctx; + int status; + VALUE callback_ary = rb_ary_new(); + char *errMsg; + VALUE errexp; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + if(results_as_hash == Qtrue) { + status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)hash_callback_function, (void*)callback_ary, &errMsg); + } else { + status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)regular_callback_function, (void*)callback_ary, &errMsg); + } + + if (status != SQLITE_OK) + { + errexp = rb_exc_new2(rb_eRuntimeError, errMsg); + sqlite3_free(errMsg); + rb_exc_raise(errexp); + } + + return callback_ary; +} + +/* call-seq: db.db_filename(database_name) + * + * Returns the file associated with +database_name+. Can return nil or an + * empty string if the database is temporary, or in-memory. + */ +static VALUE db_filename(VALUE self, VALUE db_name) +{ + sqlite3RubyPtr ctx; + const char * fname; + Data_Get_Struct(self, sqlite3Ruby, ctx); + REQUIRE_OPEN_DB(ctx); + + fname = sqlite3_db_filename(ctx->db, StringValueCStr(db_name)); + + if(fname) return SQLITE3_UTF8_STR_NEW2(fname); + return Qnil; +} + +static VALUE rb_sqlite3_open16(VALUE self, VALUE file) +{ + int status; + sqlite3RubyPtr ctx; + + Data_Get_Struct(self, sqlite3Ruby, ctx); + +#if defined TAINTING_SUPPORT +#if defined StringValueCStr + StringValuePtr(file); + rb_check_safe_obj(file); +#else + Check_SafeStr(file); +#endif +#endif + + status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db); + + CHECK(ctx->db, status) + + return INT2NUM(status); +} + +void init_sqlite3_database(void) +{ +#if 0 + VALUE mSqlite3 = rb_define_module("SQLite3"); +#endif + cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject); + + rb_define_alloc_func(cSqlite3Database, allocate); + rb_define_private_method(cSqlite3Database, "open_v2", rb_sqlite3_open_v2, 3); + rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1); + rb_define_method(cSqlite3Database, "collation", collation, 2); + rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0); + rb_define_method(cSqlite3Database, "closed?", closed_p, 0); + rb_define_method(cSqlite3Database, "total_changes", total_changes, 0); + rb_define_method(cSqlite3Database, "trace", trace, -1); + rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0); + rb_define_method(cSqlite3Database, "define_function", define_function, 1); + rb_define_method(cSqlite3Database, "define_function_with_flags", define_function_with_flags, 2); + /* public "define_aggregator" is now a shim around define_aggregator2 + * implemented in Ruby */ + rb_define_private_method(cSqlite3Database, "define_aggregator2", rb_sqlite3_define_aggregator2, 2); + rb_define_private_method(cSqlite3Database, "disable_quirk_mode", rb_sqlite3_disable_quirk_mode, 0); + rb_define_method(cSqlite3Database, "interrupt", interrupt, 0); + rb_define_method(cSqlite3Database, "errmsg", errmsg, 0); + rb_define_method(cSqlite3Database, "errcode", errcode_, 0); + rb_define_method(cSqlite3Database, "complete?", complete_p, 1); + rb_define_method(cSqlite3Database, "changes", changes, 0); + rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1); + rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1); + rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1); + rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1); + rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0); + rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2); + rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1); + +#ifdef HAVE_SQLITE3_LOAD_EXTENSION + rb_define_method(cSqlite3Database, "load_extension", load_extension, 1); +#endif + +#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION + rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1); +#endif + + rb_define_method(cSqlite3Database, "encoding", db_encoding, 0); + + rb_sqlite3_aggregator_init(); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.h new file mode 100644 index 0000000000..e087789492 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/database.h @@ -0,0 +1,17 @@ +#ifndef SQLITE3_DATABASE_RUBY +#define SQLITE3_DATABASE_RUBY + +#include + +struct _sqlite3Ruby { + sqlite3 *db; +}; + +typedef struct _sqlite3Ruby sqlite3Ruby; +typedef sqlite3Ruby * sqlite3RubyPtr; + +void init_sqlite3_database(); +void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result); +VALUE sqlite3val2rb(sqlite3_value * val); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.c new file mode 100644 index 0000000000..1dcfe18d23 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.c @@ -0,0 +1,98 @@ +#include + +void rb_sqlite3_raise(sqlite3 * db, int status) +{ + VALUE klass = Qnil; + + /* Consider only lower 8 bits, to work correctly when + extended result codes are enabled. */ + switch(status & 0xff) { + case SQLITE_OK: + return; + break; + case SQLITE_ERROR: + klass = rb_path2class("SQLite3::SQLException"); + break; + case SQLITE_INTERNAL: + klass = rb_path2class("SQLite3::InternalException"); + break; + case SQLITE_PERM: + klass = rb_path2class("SQLite3::PermissionException"); + break; + case SQLITE_ABORT: + klass = rb_path2class("SQLite3::AbortException"); + break; + case SQLITE_BUSY: + klass = rb_path2class("SQLite3::BusyException"); + break; + case SQLITE_LOCKED: + klass = rb_path2class("SQLite3::LockedException"); + break; + case SQLITE_NOMEM: + klass = rb_path2class("SQLite3::MemoryException"); + break; + case SQLITE_READONLY: + klass = rb_path2class("SQLite3::ReadOnlyException"); + break; + case SQLITE_INTERRUPT: + klass = rb_path2class("SQLite3::InterruptException"); + break; + case SQLITE_IOERR: + klass = rb_path2class("SQLite3::IOException"); + break; + case SQLITE_CORRUPT: + klass = rb_path2class("SQLite3::CorruptException"); + break; + case SQLITE_NOTFOUND: + klass = rb_path2class("SQLite3::NotFoundException"); + break; + case SQLITE_FULL: + klass = rb_path2class("SQLite3::FullException"); + break; + case SQLITE_CANTOPEN: + klass = rb_path2class("SQLite3::CantOpenException"); + break; + case SQLITE_PROTOCOL: + klass = rb_path2class("SQLite3::ProtocolException"); + break; + case SQLITE_EMPTY: + klass = rb_path2class("SQLite3::EmptyException"); + break; + case SQLITE_SCHEMA: + klass = rb_path2class("SQLite3::SchemaChangedException"); + break; + case SQLITE_TOOBIG: + klass = rb_path2class("SQLite3::TooBigException"); + break; + case SQLITE_CONSTRAINT: + klass = rb_path2class("SQLite3::ConstraintException"); + break; + case SQLITE_MISMATCH: + klass = rb_path2class("SQLite3::MismatchException"); + break; + case SQLITE_MISUSE: + klass = rb_path2class("SQLite3::MisuseException"); + break; + case SQLITE_NOLFS: + klass = rb_path2class("SQLite3::UnsupportedException"); + break; + case SQLITE_AUTH: + klass = rb_path2class("SQLite3::AuthorizationException"); + break; + case SQLITE_FORMAT: + klass = rb_path2class("SQLite3::FormatException"); + break; + case SQLITE_RANGE: + klass = rb_path2class("SQLite3::RangeException"); + break; + case SQLITE_NOTADB: + klass = rb_path2class("SQLite3::NotADatabaseException"); + break; + default: + klass = rb_eRuntimeError; + } + + klass = rb_exc_new2(klass, sqlite3_errmsg(db)); + rb_iv_set(klass, "@code", INT2FIX(status)); + rb_exc_raise(klass); +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.h new file mode 100644 index 0000000000..55b687e30b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/exception.h @@ -0,0 +1,8 @@ +#ifndef SQLITE3_EXCEPTION_RUBY +#define SQLITE3_EXCEPTION_RUBY + +#define CHECK(_db, _status) rb_sqlite3_raise(_db, _status); + +void rb_sqlite3_raise(sqlite3 * db, int status); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/extconf.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/extconf.rb new file mode 100644 index 0000000000..e25b2e0fe0 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/extconf.rb @@ -0,0 +1,272 @@ +require "mkmf" +require "mini_portile2" +require "yaml" + +module Sqlite3 + module ExtConf + ENV_ALLOWLIST = ["CC", "CFLAGS", "LDFLAGS", "LIBS", "CPPFLAGS", "LT_SYS_LIBRARY_PATH", "CPP"] + + class << self + def configure + configure_cross_compiler + + if system_libraries? + message "Building sqlite3-ruby using system #{libname}.\n" + configure_system_libraries + else + message "Building sqlite3-ruby using packaged sqlite3.\n" + configure_packaged_libraries + end + + configure_extension + + create_makefile('sqlite3/sqlite3_native') + end + + def configure_cross_compiler + RbConfig::CONFIG["CC"] = RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"] if ENV["CC"] + ENV["CC"] = RbConfig::CONFIG["CC"] + end + + def system_libraries? + sqlcipher? || enable_config("system-libraries") + end + + def libname + sqlcipher? ? "sqlcipher" : "sqlite3" + end + + def sqlcipher? + with_config("sqlcipher") || + with_config("sqlcipher-dir") || + with_config("sqlcipher-include") || + with_config("sqlcipher-lib") + end + + def configure_system_libraries + pkg_config(libname) + append_cppflags("-DUSING_SQLCIPHER") if sqlcipher? + end + + def configure_packaged_libraries + minimal_recipe.tap do |recipe| + recipe.configure_options += ["--enable-shared=no", "--enable-static=yes"] + ENV.to_h.tap do |env| + additional_cflags = [ + "-fPIC", # needed for linking the static library into a shared library + "-O2", # see https://github.com/sparklemotion/sqlite3-ruby/issues/335 for some benchmarks + "-fvisibility=hidden", # see https://github.com/rake-compiler/rake-compiler-dock/issues/87 + ] + env["CFLAGS"] = [env["CFLAGS"], additional_cflags].flatten.join(" ") + recipe.configure_options += env.select { |k,v| ENV_ALLOWLIST.include?(k) } + .map { |key, value| "#{key}=#{value.strip}" } + end + + unless File.exist?(File.join(recipe.target, recipe.host, recipe.name, recipe.version)) + recipe.cook + end + recipe.activate + + # on macos, pkg-config will not return --cflags without this + ENV["PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"] = "t" + + # only needed for Ruby 3.1.3, see https://bugs.ruby-lang.org/issues/19233 + RbConfig::CONFIG["PKG_CONFIG"] = config_string("PKG_CONFIG") || "pkg-config" + + lib_path = File.join(recipe.path, "lib") + pcfile = File.join(lib_path, "pkgconfig", "sqlite3.pc") + abort_pkg_config("pkg_config") unless pkg_config(pcfile) + + # see https://bugs.ruby-lang.org/issues/18490 + ldflags = xpopen(["pkg-config", "--libs", "--static", pcfile], err: [:child, :out], &:read) + abort_pkg_config("xpopen") unless $?.success? + ldflags = ldflags.split + + # see https://github.com/flavorjones/mini_portile/issues/118 + "-L#{lib_path}".tap do |lib_path_flag| + ldflags.prepend(lib_path_flag) unless ldflags.include?(lib_path_flag) + end + + ldflags.each { |ldflag| append_ldflags(ldflag) } + end + end + + def configure_extension + if Gem::Requirement.new("< 2.7").satisfied_by?(Gem::Version.new(RUBY_VERSION)) + append_cppflags("-DTAINTING_SUPPORT") + end + + append_cflags("-fvisibility=hidden") # see https://github.com/rake-compiler/rake-compiler-dock/issues/87 + + if find_header("sqlite3.h") + # noop + elsif sqlcipher? && find_header("sqlcipher/sqlite3.h") + append_cppflags("-DUSING_SQLCIPHER_INC_SUBDIR") + else + abort_could_not_find("sqlite3.h") + end + + abort_could_not_find(libname) unless find_library(libname, "sqlite3_libversion_number", "sqlite3.h") + + # Functions defined in 1.9 but not 1.8 + have_func('rb_proc_arity') + + # Functions defined in 2.1 but not 2.0 + have_func('rb_integer_pack') + + # These functions may not be defined + have_func('sqlite3_initialize') + have_func('sqlite3_backup_init') + have_func('sqlite3_column_database_name') + have_func('sqlite3_enable_load_extension') + have_func('sqlite3_load_extension') + + unless have_func('sqlite3_open_v2') # https://www.sqlite.org/releaselog/3_5_0.html + abort("\nPlease use a version of SQLite3 >= 3.5.0\n\n") + end + + have_func('sqlite3_prepare_v2') + have_type('sqlite3_int64', 'sqlite3.h') + have_type('sqlite3_uint64', 'sqlite3.h') + end + + def minimal_recipe + MiniPortile.new(libname, sqlite3_config[:version]).tap do |recipe| + if sqlite_source_dir + recipe.source_directory = sqlite_source_dir + else + recipe.files = sqlite3_config[:files] + recipe.target = File.join(package_root_dir, "ports") + recipe.patch_files = Dir[File.join(package_root_dir, "patches", "*.patch")].sort + end + end + end + + def package_root_dir + File.expand_path(File.join(File.dirname(__FILE__), "..", "..")) + end + + def sqlite3_config + mini_portile_config[:sqlite3] + end + + def mini_portile_config + # TODO: once Ruby 2.7 is no longer supported, use symbolize_names: true + YAML.load_file(File.join(package_root_dir, "dependencies.yml")) + end + + def abort_could_not_find(missing) + abort("\nCould not find #{missing}.\nPlease visit https://github.com/sparklemotion/sqlite3-ruby for installation instructions.\n\n") + end + + def abort_pkg_config(id) + abort("\nCould not configure the build properly (#{id}). Please install either the `pkg-config` utility or the `pkg-config` rubygem.\n\n") + end + + def cross_build? + enable_config("cross-build") + end + + def sqlite_source_dir + arg_config("--with-sqlite-source-dir") + end + + def download + minimal_recipe.download + end + + def darwin? + RbConfig::CONFIG["target_os"].include?("darwin") + end + + def print_help + print(<<~TEXT) + USAGE: ruby #{$PROGRAM_NAME} [options] + + Flags that are always valid: + + --disable-system-libraries + Use the packaged libraries, and ignore the system libraries. + (This is the default behavior.) + + --enable-system-libraries + Use system libraries instead of building and using the packaged libraries. + + --with-sqlcipher + Use libsqlcipher instead of libsqlite3. + (Implies `--enable-system-libraries`.) + + --with-sqlite-source-dir=DIRECTORY + (dev only) Build sqlite from the source code in DIRECTORY + + --help + Display this message. + + + Flags only used when using system libraries: + + General (applying to all system libraries): + + --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 sqlcipher: + + --with-sqlcipher-dir=DIRECTORY + Look for sqlcipher headers and library in DIRECTORY. + (Implies `--with-sqlcipher` and `--enable-system-libraries`.) + + --with-sqlcipher-lib=DIRECTORY + Look for sqlcipher library in DIRECTORY. + (Implies `--with-sqlcipher` and `--enable-system-libraries`.) + + --with-sqlcipher-include=DIRECTORY + Look for sqlcipher headers in DIRECTORY. + (Implies `--with-sqlcipher` and `--enable-system-libraries`.) + + + Flags only used when building and using the packaged libraries: + + --enable-cross-build + Enable cross-build mode. (You probably do not want to set this manually.) + + + Environment variables used for compiling the C extension: + + CC + Use this path to invoke the compiler instead of `RbConfig::CONFIG['CC']` + + + Environment variables passed through to the compilation of packaged libraries: + + CC + CPPFLAGS + CFLAGS + LDFLAGS + LIBS + LT_SYS_LIBRARY_PATH + CPP + + TEXT + end + end + end +end + +if arg_config("--help") + Sqlite3::ExtConf.print_help + exit!(0) +end + +if arg_config("--download-dependencies") + Sqlite3::ExtConf.download + exit!(0) +end + +Sqlite3::ExtConf.configure diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3.c new file mode 100644 index 0000000000..5410293c4b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3.c @@ -0,0 +1,164 @@ +#include + +VALUE mSqlite3; +VALUE cSqlite3Blob; + +int bignum_to_int64(VALUE value, sqlite3_int64 *result) +{ +#ifdef HAVE_RB_INTEGER_PACK + const int nails = 0; + int t = rb_integer_pack(value, result, 1, sizeof(*result), nails, + INTEGER_PACK_NATIVE_BYTE_ORDER| + INTEGER_PACK_2COMP); + switch (t) { + case -2: case +2: + return 0; + case +1: + if (!nails) { + if (*result < 0) return 0; + } + break; + case -1: + if (!nails) { + if (*result >= 0) return 0; + } + else { + *result += INT64_MIN; + } + break; + } + return 1; +#else +# ifndef RBIGNUM_LEN +# define RBIGNUM_LEN(x) RBIGNUM(x)->len +# endif + const long len = RBIGNUM_LEN(value); + if (len == 0) { + *result = 0; + return 1; + } + if (len > 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) return 0; + if (len == 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) { + const BDIGIT *digits = RBIGNUM_DIGITS(value); + BDIGIT blast = digits[len-1]; + BDIGIT bmax = (BDIGIT)1UL << (63 % (CHAR_BIT * SIZEOF_BDIGITS)); + if (blast > bmax) return 0; + if (blast == bmax) { + if (RBIGNUM_POSITIVE_P(value)) { + return 0; + } + else { + long i = len-1; + while (i) { + if (digits[--i]) return 0; + } + } + } + } + *result = (sqlite3_int64)NUM2LL(value); + return 1; +#endif +} + +static VALUE libversion(VALUE UNUSED(klass)) +{ + return INT2NUM(sqlite3_libversion_number()); +} + +static VALUE using_sqlcipher(VALUE UNUSED(klass)) +{ +#ifdef USING_SQLCIPHER + return Qtrue; +#else + return Qfalse; +#endif +} + +/* Returns the compile time setting of the SQLITE_THREADSAFE flag. + * See: https://www.sqlite.org/c3ref/threadsafe.html + */ +static VALUE threadsafe_p(VALUE UNUSED(klass)) +{ + return INT2NUM(sqlite3_threadsafe()); +} + +void init_sqlite3_constants(void) +{ + VALUE mSqlite3Constants; + VALUE mSqlite3Open; + + mSqlite3Constants = rb_define_module_under(mSqlite3, "Constants"); + + /* sqlite3_open_v2 flags for Database::new */ + mSqlite3Open = rb_define_module_under(mSqlite3Constants, "Open"); + + /* symbols = IO.readlines('sqlite3.h').map { |n| /\A#define\s+(SQLITE_OPEN_\w+)\s/ =~ n && $1 }.compact + * pad = symbols.map(&:length).max - 9 + * symbols.each { |s| printf %Q{ rb_define_const(mSqlite3Open, %-#{pad}s INT2FIX(#{s}));\n}, '"' + s[12..-1] + '",' } + */ + rb_define_const(mSqlite3Open, "READONLY", INT2FIX(SQLITE_OPEN_READONLY)); + rb_define_const(mSqlite3Open, "READWRITE", INT2FIX(SQLITE_OPEN_READWRITE)); + rb_define_const(mSqlite3Open, "CREATE", INT2FIX(SQLITE_OPEN_CREATE)); + rb_define_const(mSqlite3Open, "DELETEONCLOSE", INT2FIX(SQLITE_OPEN_DELETEONCLOSE)); + rb_define_const(mSqlite3Open, "EXCLUSIVE", INT2FIX(SQLITE_OPEN_EXCLUSIVE)); + rb_define_const(mSqlite3Open, "MAIN_DB", INT2FIX(SQLITE_OPEN_MAIN_DB)); + rb_define_const(mSqlite3Open, "TEMP_DB", INT2FIX(SQLITE_OPEN_TEMP_DB)); + rb_define_const(mSqlite3Open, "TRANSIENT_DB", INT2FIX(SQLITE_OPEN_TRANSIENT_DB)); + rb_define_const(mSqlite3Open, "MAIN_JOURNAL", INT2FIX(SQLITE_OPEN_MAIN_JOURNAL)); + rb_define_const(mSqlite3Open, "TEMP_JOURNAL", INT2FIX(SQLITE_OPEN_TEMP_JOURNAL)); + rb_define_const(mSqlite3Open, "SUBJOURNAL", INT2FIX(SQLITE_OPEN_SUBJOURNAL)); + rb_define_const(mSqlite3Open, "MASTER_JOURNAL", INT2FIX(SQLITE_OPEN_MASTER_JOURNAL)); + rb_define_const(mSqlite3Open, "NOMUTEX", INT2FIX(SQLITE_OPEN_NOMUTEX)); + rb_define_const(mSqlite3Open, "FULLMUTEX", INT2FIX(SQLITE_OPEN_FULLMUTEX)); +#ifdef SQLITE_OPEN_AUTOPROXY + /* SQLITE_VERSION_NUMBER>=3007002 */ + rb_define_const(mSqlite3Open, "AUTOPROXY", INT2FIX(SQLITE_OPEN_AUTOPROXY)); + rb_define_const(mSqlite3Open, "SHAREDCACHE", INT2FIX(SQLITE_OPEN_SHAREDCACHE)); + rb_define_const(mSqlite3Open, "PRIVATECACHE", INT2FIX(SQLITE_OPEN_PRIVATECACHE)); + rb_define_const(mSqlite3Open, "WAL", INT2FIX(SQLITE_OPEN_WAL)); +#endif +#ifdef SQLITE_OPEN_URI + /* SQLITE_VERSION_NUMBER>=3007007 */ + rb_define_const(mSqlite3Open, "URI", INT2FIX(SQLITE_OPEN_URI)); +#endif +#ifdef SQLITE_OPEN_MEMORY + /* SQLITE_VERSION_NUMBER>=3007013 */ + rb_define_const(mSqlite3Open, "MEMORY", INT2FIX(SQLITE_OPEN_MEMORY)); +#endif +} + +RUBY_FUNC_EXPORTED +void Init_sqlite3_native(void) +{ + /* + * SQLite3 is a wrapper around the popular database + * sqlite[http://sqlite.org]. + * + * For an example of usage, see SQLite3::Database. + */ + mSqlite3 = rb_define_module("SQLite3"); + + /* A class for differentiating between strings and blobs, when binding them + * into statements. + */ + cSqlite3Blob = rb_define_class_under(mSqlite3, "Blob", rb_cString); + + /* Initialize the sqlite3 library */ +#ifdef HAVE_SQLITE3_INITIALIZE + sqlite3_initialize(); +#endif + + init_sqlite3_constants(); + init_sqlite3_database(); + init_sqlite3_statement(); +#ifdef HAVE_SQLITE3_BACKUP_INIT + init_sqlite3_backup(); +#endif + rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); + rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); + rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); + rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); + rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); + rb_define_const(mSqlite3, "SQLITE_LOADED_VERSION", rb_str_new2(sqlite3_libversion())); + +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3_ruby.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3_ruby.h new file mode 100644 index 0000000000..9cea90dc9a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/sqlite3_ruby.h @@ -0,0 +1,48 @@ +#ifndef SQLITE3_RUBY +#define SQLITE3_RUBY + +#include + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +#include + +#define USASCII_P(_obj) (rb_enc_get_index(_obj) == rb_usascii_encindex()) +#define UTF8_P(_obj) (rb_enc_get_index(_obj) == rb_utf8_encindex()) +#define UTF16_LE_P(_obj) (rb_enc_get_index(_obj) == rb_enc_find_index("UTF-16LE")) +#define UTF16_BE_P(_obj) (rb_enc_get_index(_obj) == rb_enc_find_index("UTF-16BE")) +#define SQLITE3_UTF8_STR_NEW2(_obj) \ + (rb_enc_associate_index(rb_str_new2(_obj), rb_utf8_encindex())) + +#ifdef USING_SQLCIPHER_INC_SUBDIR +# include +#else +# include +#endif + +#ifndef HAVE_TYPE_SQLITE3_INT64 +typedef sqlite_int64 sqlite3_int64; +#endif + +#ifndef HAVE_TYPE_SQLITE3_UINT64 +typedef sqlite_uint64 sqlite3_uint64; +#endif + +extern VALUE mSqlite3; +extern VALUE cSqlite3Blob; + +#include +#include +#include +#include + +int bignum_to_int64(VALUE big, sqlite3_int64 *result); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.c b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.c new file mode 100644 index 0000000000..ee2e03ecd3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.c @@ -0,0 +1,442 @@ +#include + +#define REQUIRE_OPEN_STMT(_ctxt) \ + if(!_ctxt->st) \ + rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed statement"); + +VALUE cSqlite3Statement; + +static void deallocate(void * ctx) +{ + sqlite3StmtRubyPtr c = (sqlite3StmtRubyPtr)ctx; + xfree(c); +} + +static VALUE allocate(VALUE klass) +{ + sqlite3StmtRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3StmtRuby)); + ctx->st = NULL; + ctx->done_p = 0; + + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); +} + +/* call-seq: SQLite3::Statement.new(db, sql) + * + * Create a new statement attached to the given Database instance, and which + * encapsulates the given SQL text. If the text contains more than one + * statement (i.e., separated by semicolons), then the #remainder property + * will be set to the trailing text. + */ +static VALUE initialize(VALUE self, VALUE db, VALUE sql) +{ + sqlite3RubyPtr db_ctx; + sqlite3StmtRubyPtr ctx; + const char *tail = NULL; + int status; + + StringValue(sql); + + Data_Get_Struct(db, sqlite3Ruby, db_ctx); + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(!db_ctx->db) + rb_raise(rb_eArgError, "prepare called on a closed database"); + + if(!UTF8_P(sql)) { + sql = rb_str_export_to_enc(sql, rb_utf8_encoding()); + } + +#ifdef HAVE_SQLITE3_PREPARE_V2 + status = sqlite3_prepare_v2( +#else + status = sqlite3_prepare( +#endif + db_ctx->db, + (const char *)StringValuePtr(sql), + (int)RSTRING_LEN(sql), + &ctx->st, + &tail + ); + + CHECK(db_ctx->db, status); + + rb_iv_set(self, "@connection", db); + rb_iv_set(self, "@remainder", rb_str_new2(tail)); + rb_iv_set(self, "@columns", Qnil); + rb_iv_set(self, "@types", Qnil); + + return self; +} + +/* call-seq: stmt.close + * + * Closes the statement by finalizing the underlying statement + * handle. The statement must not be used after being closed. + */ +static VALUE sqlite3_rb_close(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + REQUIRE_OPEN_STMT(ctx); + + sqlite3_finalize(ctx->st); + ctx->st = NULL; + + return self; +} + +/* call-seq: stmt.closed? + * + * Returns true if the statement has been closed. + */ +static VALUE closed_p(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(!ctx->st) return Qtrue; + + return Qfalse; +} + +static VALUE step(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + sqlite3_stmt *stmt; + int value, length; + VALUE list; + rb_encoding * internal_encoding; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + REQUIRE_OPEN_STMT(ctx); + + if(ctx->done_p) return Qnil; + + { + VALUE db = rb_iv_get(self, "@connection"); + rb_funcall(db, rb_intern("encoding"), 0); + internal_encoding = rb_default_internal_encoding(); + } + + stmt = ctx->st; + + value = sqlite3_step(stmt); + if (rb_errinfo() != Qnil) { + /* some user defined function was invoked as a callback during step and + * it raised an exception that has been suppressed until step returns. + * Now re-raise it. */ + VALUE exception = rb_errinfo(); + rb_set_errinfo(Qnil); + rb_exc_raise(exception); + } + + length = sqlite3_column_count(stmt); + list = rb_ary_new2((long)length); + + switch(value) { + case SQLITE_ROW: + { + int i; + for(i = 0; i < length; i++) { + switch(sqlite3_column_type(stmt, i)) { + case SQLITE_INTEGER: + rb_ary_push(list, LL2NUM(sqlite3_column_int64(stmt, i))); + break; + case SQLITE_FLOAT: + rb_ary_push(list, rb_float_new(sqlite3_column_double(stmt, i))); + break; + case SQLITE_TEXT: + { + VALUE str = rb_str_new( + (const char *)sqlite3_column_text(stmt, i), + (long)sqlite3_column_bytes(stmt, i) + ); + rb_enc_associate_index(str, rb_utf8_encindex()); + if(internal_encoding) + str = rb_str_export_to_enc(str, internal_encoding); + rb_ary_push(list, str); + } + break; + case SQLITE_BLOB: + { + VALUE str = rb_str_new( + (const char *)sqlite3_column_blob(stmt, i), + (long)sqlite3_column_bytes(stmt, i) + ); + rb_ary_push(list, str); + } + break; + case SQLITE_NULL: + rb_ary_push(list, Qnil); + break; + default: + rb_raise(rb_eRuntimeError, "bad type"); + } + } + } + break; + case SQLITE_DONE: + ctx->done_p = 1; + return Qnil; + break; + default: + sqlite3_reset(stmt); + ctx->done_p = 0; + CHECK(sqlite3_db_handle(ctx->st), value); + } + + return list; +} + +/* call-seq: stmt.bind_param(key, value) + * + * Binds value to the named (or positional) placeholder. If +param+ is a + * Fixnum, it is treated as an index for a positional placeholder. + * Otherwise it is used as the name of the placeholder to bind to. + * + * See also #bind_params. + */ +static VALUE bind_param(VALUE self, VALUE key, VALUE value) +{ + sqlite3StmtRubyPtr ctx; + int status; + int index; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + switch(TYPE(key)) { + case T_SYMBOL: + key = rb_funcall(key, rb_intern("to_s"), 0); + case T_STRING: + if(RSTRING_PTR(key)[0] != ':') key = rb_str_plus(rb_str_new2(":"), key); + index = sqlite3_bind_parameter_index(ctx->st, StringValuePtr(key)); + break; + default: + index = (int)NUM2INT(key); + } + + if(index == 0) + rb_raise(rb_path2class("SQLite3::Exception"), "no such bind parameter"); + + switch(TYPE(value)) { + case T_STRING: + if(CLASS_OF(value) == cSqlite3Blob + || rb_enc_get_index(value) == rb_ascii8bit_encindex() + ) { + status = sqlite3_bind_blob( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } else { + + + if (UTF16_LE_P(value) || UTF16_BE_P(value)) { + status = sqlite3_bind_text16( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } else { + if (!UTF8_P(value) || !USASCII_P(value)) { + value = rb_str_encode(value, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil); + } + status = sqlite3_bind_text( + ctx->st, + index, + (const char *)StringValuePtr(value), + (int)RSTRING_LEN(value), + SQLITE_TRANSIENT + ); + } + } + break; + case T_BIGNUM: { + sqlite3_int64 num64; + if (bignum_to_int64(value, &num64)) { + status = sqlite3_bind_int64(ctx->st, index, num64); + break; + } + } + case T_FLOAT: + status = sqlite3_bind_double(ctx->st, index, NUM2DBL(value)); + break; + case T_FIXNUM: + status = sqlite3_bind_int64(ctx->st, index, (sqlite3_int64)FIX2LONG(value)); + break; + case T_NIL: + status = sqlite3_bind_null(ctx->st, index); + break; + default: + rb_raise(rb_eRuntimeError, "can't prepare %s", + rb_class2name(CLASS_OF(value))); + break; + } + + CHECK(sqlite3_db_handle(ctx->st), status); + + return self; +} + +/* call-seq: stmt.reset! + * + * Resets the statement. This is typically done internally, though it might + * occasionally be necessary to manually reset the statement. + */ +static VALUE reset_bang(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + sqlite3_reset(ctx->st); + + ctx->done_p = 0; + + return self; +} + +/* call-seq: stmt.clear_bindings! + * + * Resets the statement. This is typically done internally, though it might + * occasionally be necessary to manually reset the statement. + */ +static VALUE clear_bindings_bang(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + sqlite3_clear_bindings(ctx->st); + + ctx->done_p = 0; + + return self; +} + +/* call-seq: stmt.done? + * + * returns true if all rows have been returned. + */ +static VALUE done_p(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + + if(ctx->done_p) return Qtrue; + return Qfalse; +} + +/* call-seq: stmt.column_count + * + * Returns the number of columns to be returned for this statement + */ +static VALUE column_count(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return INT2NUM((long)sqlite3_column_count(ctx->st)); +} + +/* call-seq: stmt.column_name(index) + * + * Get the column name at +index+. 0 based. + */ +static VALUE column_name(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + const char * name; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + name = sqlite3_column_name(ctx->st, (int)NUM2INT(index)); + + if(name) return SQLITE3_UTF8_STR_NEW2(name); + return Qnil; +} + +/* call-seq: stmt.column_decltype(index) + * + * Get the column type at +index+. 0 based. + */ +static VALUE column_decltype(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + const char * name; + + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index)); + + if(name) return rb_str_new2(name); + return Qnil; +} + +/* call-seq: stmt.bind_parameter_count + * + * Return the number of bind parameters + */ +static VALUE bind_parameter_count(VALUE self) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return INT2NUM((long)sqlite3_bind_parameter_count(ctx->st)); +} + +#ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME + +/* call-seq: stmt.database_name(column_index) + * + * Return the database name for the column at +column_index+ + */ +static VALUE database_name(VALUE self, VALUE index) +{ + sqlite3StmtRubyPtr ctx; + Data_Get_Struct(self, sqlite3StmtRuby, ctx); + REQUIRE_OPEN_STMT(ctx); + + return SQLITE3_UTF8_STR_NEW2( + sqlite3_column_database_name(ctx->st, NUM2INT(index))); +} + +#endif + +void init_sqlite3_statement(void) +{ + cSqlite3Statement = rb_define_class_under(mSqlite3, "Statement", rb_cObject); + + rb_define_alloc_func(cSqlite3Statement, allocate); + rb_define_method(cSqlite3Statement, "initialize", initialize, 2); + rb_define_method(cSqlite3Statement, "close", sqlite3_rb_close, 0); + rb_define_method(cSqlite3Statement, "closed?", closed_p, 0); + rb_define_method(cSqlite3Statement, "bind_param", bind_param, 2); + rb_define_method(cSqlite3Statement, "reset!", reset_bang, 0); + rb_define_method(cSqlite3Statement, "clear_bindings!", clear_bindings_bang, 0); + rb_define_method(cSqlite3Statement, "step", step, 0); + rb_define_method(cSqlite3Statement, "done?", done_p, 0); + rb_define_method(cSqlite3Statement, "column_count", column_count, 0); + rb_define_method(cSqlite3Statement, "column_name", column_name, 1); + rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1); + rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0); + +#ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME + rb_define_method(cSqlite3Statement, "database_name", database_name, 1); +#endif +} diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.h b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.h new file mode 100644 index 0000000000..e7ef1f3a5e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/ext/sqlite3/statement.h @@ -0,0 +1,16 @@ +#ifndef SQLITE3_STATEMENT_RUBY +#define SQLITE3_STATEMENT_RUBY + +#include + +struct _sqlite3StmtRuby { + sqlite3_stmt *st; + int done_p; +}; + +typedef struct _sqlite3StmtRuby sqlite3StmtRuby; +typedef sqlite3StmtRuby * sqlite3StmtRubyPtr; + +void init_sqlite3_statement(); + +#endif diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.md b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.md new file mode 100644 index 0000000000..6f2e24685a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.md @@ -0,0 +1,431 @@ + +## How do I do a database query? +### I just want an array of the rows... + +Use the `Database#execute` method. If you don't give it a block, it will +return an array of all the rows: + +```ruby + require 'sqlite3' + + db = SQLite3::Database.new( "test.db" ) + rows = db.execute( "select * from test" ) +``` + +### I'd like to use a block to iterate through the rows... + +Use the `Database#execute` method. If you give it a block, each row of the +result will be yielded to the block: + + +```ruby + require 'sqlite3' + + db = SQLite3::Database.new( "test.db" ) + db.execute( "select * from test" ) do |row| + ... + end +``` + +### I need to get the column names as well as the rows... + +Use the `Database#execute2` method. This works just like `Database#execute`; +if you don't give it a block, it returns an array of rows; otherwise, it +will yield each row to the block. _However_, the first row returned is +always an array of the column names from the query: + + +```ruby + require 'sqlite3' + + db = SQLite3::Database.new( "test.db" ) + columns, *rows = db.execute2( "select * from test" ) + + # or use a block: + + columns = nil + db.execute2( "select * from test" ) do |row| + if columns.nil? + columns = row + else + # process row + end + end +``` + +### I just want the first row of the result set... + +Easy. Just call `Database#get_first_row`: + + +```ruby + row = db.get_first_row( "select * from table" ) +``` + + +This also supports bind variables, just like `Database#execute` +and friends. + +### I just want the first value of the first row of the result set... + +Also easy. Just call `Database#get_first_value`: + + +```ruby + count = db.get_first_value( "select count(*) from table" ) +``` + + +This also supports bind variables, just like `Database#execute` +and friends. + +## How do I prepare a statement for repeated execution? + +If the same statement is going to be executed repeatedly, you can speed +things up a bit by _preparing_ the statement. You do this via the +`Database#prepare` method. It returns a `Statement` object, and you can +then invoke `#execute` on that to get the `ResultSet`: + + +```ruby + stmt = db.prepare( "select * from person" ) + + 1000.times do + stmt.execute do |result| + ... + end + end + + stmt.close + + # or, use a block + + db.prepare( "select * from person" ) do |stmt| + 1000.times do + stmt.execute do |result| + ... + end + end + end +``` + + +This is made more useful by the ability to bind variables to placeholders +via the `Statement#bind_param` and `Statement#bind_params` methods. (See the +next FAQ for details.) + +## How do I use placeholders in an SQL statement? + +Placeholders in an SQL statement take any of the following formats: + + +* `?` +* `?_nnn_` +* `:_word_` + + +Where _n_ is an integer, and _word_ is an alpha-numeric identifier (or +number). When the placeholder is associated with a number, that number +identifies the index of the bind variable to replace it with. When it +is an identifier, it identifies the name of the corresponding bind +variable. (In the instance of the first format--a single question +mark--the placeholder is assigned a number one greater than the last +index used, or 1 if it is the first.) + + +For example, here is a query using these placeholder formats: + + +```sql + select * + from table + where ( c = ?2 or c = ? ) + and d = :name + and e = :1 +``` + + +This defines 5 different placeholders: 1, 2, 3, and "name". + + +You replace these placeholders by _binding_ them to values. This can be +accomplished in a variety of ways. + + +The `Database#execute`, and `Database#execute2` methods all accept additional +arguments following the SQL statement. These arguments are assumed to be +bind parameters, and they are bound (positionally) to their corresponding +placeholders: + + +```ruby + db.execute( "select * from table where a = ? and b = ?", + "hello", + "world" ) +``` + + +The above would replace the first question mark with 'hello' and the +second with 'world'. If the placeholders have an explicit index given, they +will be replaced with the bind parameter at that index (1-based). + + +If a Hash is given as a bind parameter, then its key/value pairs are bound +to the placeholders. This is how you bind by name: + + +```ruby + db.execute( "select * from table where a = :name and b = :value", + "name" => "bob", + "value" => "priceless" ) +``` + + +You can also bind explicitly using the `Statement` object itself. Just pass +additional parameters to the `Statement#execute` statement: + + +```ruby + db.prepare( "select * from table where a = :name and b = ?" ) do |stmt| + stmt.execute "value", "name" => "bob" + end +``` + + +Or do a `Database#prepare` to get the `Statement`, and then use either +`Statement#bind_param` or `Statement#bind_params`: + + +```ruby + stmt = db.prepare( "select * from table where a = :name and b = ?" ) + + stmt.bind_param( "name", "bob" ) + stmt.bind_param( 1, "value" ) + + # or + + stmt.bind_params( "value", "name" => "bob" ) +``` + +## How do I discover metadata about a query? + +If you ever want to know the names or types of the columns in a result +set, you can do it in several ways. + + +The first way is to ask the row object itself. Each row will have a +property "fields" that returns an array of the column names. The row +will also have a property "types" that returns an array of the column +types: + + +```ruby + rows = db.execute( "select * from table" ) + p rows[0].fields + p rows[0].types +``` + + +Obviously, this approach requires you to execute a statement that actually +returns data. If you don't know if the statement will return any rows, but +you still need the metadata, you can use `Database#query` and ask the +`ResultSet` object itself: + + +```ruby + db.query( "select * from table" ) do |result| + p result.columns + p result.types + ... + end +``` + + +Lastly, you can use `Database#prepare` and ask the `Statement` object what +the metadata are: + + +```ruby + stmt = db.prepare( "select * from table" ) + p stmt.columns + p stmt.types +``` + +## I'd like the rows to be indexible by column name. + +By default, each row from a query is returned as an `Array` of values. This +means that you can only obtain values by their index. Sometimes, however, +you would like to obtain values by their column name. + + +The first way to do this is to set the Database property `results_as_hash` +to true. If you do this, then all rows will be returned as Hash objects, +with the column names as the keys. (In this case, the `fields` property +is unavailable on the row, although the "types" property remains.) + + +```ruby + db.results_as_hash = true + db.execute( "select * from table" ) do |row| + p row['column1'] + p row['column2'] + end +``` + + +The other way is to use Ara Howard's +[`ArrayFields`](http://rubyforge.org/projects/arrayfields) +module. Just `require "arrayfields"`, and all of your rows will be indexable +by column name, even though they are still arrays! + + +```ruby + require 'arrayfields' + + ... + db.execute( "select * from table" ) do |row| + p row[0] == row['column1'] + p row[1] == row['column2'] + end +``` + +## I'd like the values from a query to be the correct types, instead of String. + +You can turn on "type translation" by setting `Database#type_translation` to +true: + + +```ruby + db.type_translation = true + db.execute( "select * from table" ) do |row| + p row + end +``` + + +By doing this, each return value for each row will be translated to its +correct type, based on its declared column type. + + +You can even declare your own translation routines, if (for example) you are +using an SQL type that is not handled by default: + + +```ruby + # assume "objects" table has the following schema: + # create table objects ( + # name varchar2(20), + # thing object + # ) + + db.type_translation = true + db.translator.add_translator( "object" ) do |type, value| + db.decode( value ) + end + + h = { :one=>:two, "three"=>"four", 5=>6 } + dump = db.encode( h ) + + db.execute( "insert into objects values ( ?, ? )", "bob", dump ) + + obj = db.get_first_value( "select thing from objects where name='bob'" ) + p obj == h +``` + +## How do I insert binary data into the database? + +Use blobs. Blobs are new features of SQLite3. You have to use bind +variables to make it work: + + +```ruby + db.execute( "insert into foo ( ?, ? )", + SQLite3::Blob.new( "\0\1\2\3\4\5" ), + SQLite3::Blob.new( "a\0b\0c\0d ) ) +``` + + +The blob values must be indicated explicitly by binding each parameter to +a value of type `SQLite3::Blob`. + +## How do I do a DDL (insert, update, delete) statement? + +You can actually do inserts, updates, and deletes in exactly the same way +as selects, but in general the `Database#execute` method will be most +convenient: + + +```ruby + db.execute( "insert into table values ( ?, ? )", *bind_vars ) +``` + +## How do I execute multiple statements in a single string? + +The standard query methods (`Database#execute`, `Database#execute2`, +`Database#query`, and `Statement#execute`) will only execute the first +statement in the string that is given to them. Thus, if you have a +string with multiple SQL statements, each separated by a string, +you can't use those methods to execute them all at once. + + +Instead, use `Database#execute_batch`: + + +```ruby + sql = <" + faqs.each do |faq| + process_faq_list_item faq + end + puts "" +end + +def process_faq_list_item( faq ) + question = faq.keys.first + answer = faq.values.first + + print "
  • " + + question_text = RedCloth.new(question).to_html.gsub( %r{},"" ) + if answer.is_a?( Array ) + puts question_text + process_faq_list answer + else + print "#{question_text}" + end + + puts "
  • " +end + +def process_faq_descriptions( faqs, path=nil ) + faqs.each do |faq| + process_faq_description faq, path + end +end + +def process_faq_description( faq, path ) + question = faq.keys.first + path = ( path ? path + " " : "" ) + question + answer = faq.values.first + + if answer.is_a?( Array ) + process_faq_descriptions( answer, path ) + else + title = RedCloth.new( path ).to_html.gsub( %r{}, "" ) + answer = RedCloth.new( answer || "" ) + + puts "" + puts "
    #{title}
    " + puts "
    #{add_api_links(answer.to_html)}
    " + end +end + +API_OBJECTS = [ "Database", "Statement", "ResultSet", + "ParsedStatement", "Pragmas", "Translator" ].inject( "(" ) { |acc,name| + acc << "|" if acc.length > 1 + acc << name + acc + } + ")" + +def add_api_links( text ) + text.gsub( /#{API_OBJECTS}(#(\w+))?/ ) do + disp_obj = obj = $1 + + case obj + when "Pragmas"; disp_obj = "Database" + end + + method = $3 + s = "#{disp_obj}" + s << "##{method}" if method + s << "" + s + end +end + +faqs = YAML.load( File.read( "faq.yml" ) ) + +puts <<-EOF + + + SQLite3/Ruby FAQ + + + +

    SQLite/Ruby FAQ

    +
    +EOF + +process_faq_list( faqs ) +puts "
    " +process_faq_descriptions( faqs ) + +puts "" diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.yml b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.yml new file mode 100644 index 0000000000..2360ae734b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/faq/faq.yml @@ -0,0 +1,426 @@ +--- +- "How do I do a database query?": + - "I just want an array of the rows...": >- + + Use the Database#execute method. If you don't give it a block, it will + return an array of all the rows: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        rows = db.execute( "select * from test" )
    +      
    + + - "I'd like to use a block to iterate through the rows...": >- + + Use the Database#execute method. If you give it a block, each row of the + result will be yielded to the block: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        db.execute( "select * from test" ) do |row|
    +          ...
    +        end
    +      
    + + - "I need to get the column names as well as the rows...": >- + + Use the Database#execute2 method. This works just like Database#execute; + if you don't give it a block, it returns an array of rows; otherwise, it + will yield each row to the block. _However_, the first row returned is + always an array of the column names from the query: + + +
    +        require 'sqlite3'
    +
    +        db = SQLite3::Database.new( "test.db" )
    +        columns, *rows = db.execute2( "select * from test" )
    +
    +        # or use a block:
    +
    +        columns = nil
    +        db.execute2( "select * from test" ) do |row|
    +          if columns.nil?
    +            columns = row
    +          else
    +            # process row
    +          end
    +        end
    +      
    + + - "I just want the first row of the result set...": >- + + Easy. Just call Database#get_first_row: + + +
    +        row = db.get_first_row( "select * from table" )
    +      
    + + + This also supports bind variables, just like Database#execute + and friends. + + - "I just want the first value of the first row of the result set...": >- + + Also easy. Just call Database#get_first_value: + + +
    +        count = db.get_first_value( "select count(*) from table" )
    +      
    + + + This also supports bind variables, just like Database#execute + and friends. + +- "How do I prepare a statement for repeated execution?": >- + If the same statement is going to be executed repeatedly, you can speed + things up a bit by _preparing_ the statement. You do this via the + Database#prepare method. It returns a Statement object, and you can + then invoke #execute on that to get the ResultSet: + + +
    +      stmt = db.prepare( "select * from person" )
    +
    +      1000.times do
    +        stmt.execute do |result|
    +          ...
    +        end
    +      end
    +
    +      stmt.close
    +
    +      # or, use a block
    +
    +      db.prepare( "select * from person" ) do |stmt|
    +        1000.times do
    +          stmt.execute do |result|
    +            ...
    +          end
    +        end
    +      end
    +    
    + + + This is made more useful by the ability to bind variables to placeholders + via the Statement#bind_param and Statement#bind_params methods. (See the + next FAQ for details.) + +- "How do I use placeholders in an SQL statement?": >- + Placeholders in an SQL statement take any of the following formats: + + + * @?@ + + * @?_nnn_@ + + * @:_word_@ + + + Where _n_ is an integer, and _word_ is an alpha-numeric identifier (or + number). When the placeholder is associated with a number, that number + identifies the index of the bind variable to replace it with. When it + is an identifier, it identifies the name of the corresponding bind + variable. (In the instance of the first format--a single question + mark--the placeholder is assigned a number one greater than the last + index used, or 1 if it is the first.) + + + For example, here is a query using these placeholder formats: + + +
    +      select *
    +        from table
    +       where ( c = ?2 or c = ? )
    +         and d = :name
    +         and e = :1
    +    
    + + + This defines 5 different placeholders: 1, 2, 3, and "name". + + + You replace these placeholders by _binding_ them to values. This can be + accomplished in a variety of ways. + + + The Database#execute, and Database#execute2 methods all accept additional + arguments following the SQL statement. These arguments are assumed to be + bind parameters, and they are bound (positionally) to their corresponding + placeholders: + + +
    +      db.execute( "select * from table where a = ? and b = ?",
    +                  "hello",
    +                  "world" )
    +    
    + + + The above would replace the first question mark with 'hello' and the + second with 'world'. If the placeholders have an explicit index given, they + will be replaced with the bind parameter at that index (1-based). + + + If a Hash is given as a bind parameter, then its key/value pairs are bound + to the placeholders. This is how you bind by name: + + +
    +      db.execute( "select * from table where a = :name and b = :value",
    +                  "name" => "bob",
    +                  "value" => "priceless" )
    +    
    + + + You can also bind explicitly using the Statement object itself. Just pass + additional parameters to the Statement#execute statement: + + +
    +      db.prepare( "select * from table where a = :name and b = ?" ) do |stmt|
    +        stmt.execute "value", "name" => "bob"
    +      end
    +    
    + + + Or do a Database#prepare to get the Statement, and then use either + Statement#bind_param or Statement#bind_params: + + +
    +      stmt = db.prepare( "select * from table where a = :name and b = ?" )
    +
    +      stmt.bind_param( "name", "bob" )
    +      stmt.bind_param( 1, "value" )
    +
    +      # or
    +
    +      stmt.bind_params( "value", "name" => "bob" )
    +    
    + +- "How do I discover metadata about a query?": >- + + If you ever want to know the names or types of the columns in a result + set, you can do it in several ways. + + + The first way is to ask the row object itself. Each row will have a + property "fields" that returns an array of the column names. The row + will also have a property "types" that returns an array of the column + types: + + +
    +      rows = db.execute( "select * from table" )
    +      p rows[0].fields
    +      p rows[0].types
    +    
    + + + Obviously, this approach requires you to execute a statement that actually + returns data. If you don't know if the statement will return any rows, but + you still need the metadata, you can use Database#query and ask the + ResultSet object itself: + + +
    +      db.query( "select * from table" ) do |result|
    +        p result.columns
    +        p result.types
    +        ...
    +      end
    +    
    + + + Lastly, you can use Database#prepare and ask the Statement object what + the metadata are: + + +
    +      stmt = db.prepare( "select * from table" )
    +      p stmt.columns
    +      p stmt.types
    +    
    + +- "I'd like the rows to be indexible by column name.": >- + By default, each row from a query is returned as an Array of values. This + means that you can only obtain values by their index. Sometimes, however, + you would like to obtain values by their column name. + + + The first way to do this is to set the Database property "results_as_hash" + to true. If you do this, then all rows will be returned as Hash objects, + with the column names as the keys. (In this case, the "fields" property + is unavailable on the row, although the "types" property remains.) + + +
    +      db.results_as_hash = true
    +      db.execute( "select * from table" ) do |row|
    +        p row['column1']
    +        p row['column2']
    +      end
    +    
    + + + The other way is to use Ara Howard's + "ArrayFields":http://rubyforge.org/projects/arrayfields + module. Just require "arrayfields", and all of your rows will be indexable + by column name, even though they are still arrays! + + +
    +      require 'arrayfields'
    +
    +      ...
    +      db.execute( "select * from table" ) do |row|
    +        p row[0] == row['column1']
    +        p row[1] == row['column2']
    +      end
    +    
    + +- "I'd like the values from a query to be the correct types, instead of String.": >- + You can turn on "type translation" by setting Database#type_translation to + true: + + +
    +      db.type_translation = true
    +      db.execute( "select * from table" ) do |row|
    +        p row
    +      end
    +    
    + + + By doing this, each return value for each row will be translated to its + correct type, based on its declared column type. + + + You can even declare your own translation routines, if (for example) you are + using an SQL type that is not handled by default: + + +
    +      # assume "objects" table has the following schema:
    +      #   create table objects (
    +      #     name varchar2(20),
    +      #     thing object
    +      #   )
    +
    +      db.type_translation = true
    +      db.translator.add_translator( "object" ) do |type, value|
    +        db.decode( value )
    +      end
    +
    +      h = { :one=>:two, "three"=>"four", 5=>6 }
    +      dump = db.encode( h )
    +
    +      db.execute( "insert into objects values ( ?, ? )", "bob", dump )
    +
    +      obj = db.get_first_value( "select thing from objects where name='bob'" )
    +      p obj == h
    +    
    + +- "How do I insert binary data into the database?": >- + Use blobs. Blobs are new features of SQLite3. You have to use bind + variables to make it work: + + +
    +      db.execute( "insert into foo ( ?, ? )",
    +        SQLite3::Blob.new( "\0\1\2\3\4\5" ),
    +        SQLite3::Blob.new( "a\0b\0c\0d ) )
    +    
    + + + The blob values must be indicated explicitly by binding each parameter to + a value of type SQLite3::Blob. + +- "How do I do a DDL (insert, update, delete) statement?": >- + You can actually do inserts, updates, and deletes in exactly the same way + as selects, but in general the Database#execute method will be most + convenient: + + +
    +      db.execute( "insert into table values ( ?, ? )", *bind_vars )
    +    
    + +- "How do I execute multiple statements in a single string?": >- + The standard query methods (Database#execute, Database#execute2, + Database#query, and Statement#execute) will only execute the first + statement in the string that is given to them. Thus, if you have a + string with multiple SQL statements, each separated by a string, + you can't use those methods to execute them all at once. + + + Instead, use Database#execute_batch: + + +
    +      sql = <
    +
    +
    +    Unlike the other query methods, Database#execute_batch accepts no
    +    block. It will also only ever return +nil+. Thus, it is really only
    +    suitable for batch processing of DDL statements.
    +
    +- "How do I begin/end a transaction?":
    +    Use Database#transaction to start a transaction. If you give it a block,
    +    the block will be automatically committed at the end of the block,
    +    unless an exception was raised, in which case the transaction will be
    +    rolled back. (Never explicitly call Database#commit or Database#rollback
    +    inside of a transaction block--you'll get errors when the block
    +    terminates!)
    +
    +
    +    
    +      database.transaction do |db|
    +        db.execute( "insert into table values ( 'a', 'b', 'c' )" )
    +        ...
    +      end
    +    
    + + + Alternatively, if you don't give a block to Database#transaction, the + transaction remains open until you explicitly call Database#commit or + Database#rollback. + + +
    +      db.transaction
    +      db.execute( "insert into table values ( 'a', 'b', 'c' )" )
    +      db.commit
    +    
    + + + Note that SQLite does not allow nested transactions, so you'll get errors + if you try to open a new transaction while one is already active. Use + Database#transaction_active? to determine whether a transaction is + active or not. + +#- "How do I discover metadata about a table/index?": +# +#- "How do I do tweak database settings?": diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3.rb new file mode 100644 index 0000000000..f6110e1c0f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3.rb @@ -0,0 +1,15 @@ +# support multiple ruby version (fat binaries under windows) +begin + RUBY_VERSION =~ /(\d+\.\d+)/ + require "sqlite3/#{$1}/sqlite3_native" +rescue LoadError + require 'sqlite3/sqlite3_native' +end + +require 'sqlite3/database' +require 'sqlite3/version' + +module SQLite3 + # Was sqlite3 compiled with thread safety on? + def self.threadsafe?; threadsafe > 0; end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/2.7/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/2.7/sqlite3_native.so new file mode 100755 index 0000000000..7cd7fb8a9b Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/2.7/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.0/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.0/sqlite3_native.so new file mode 100755 index 0000000000..a0bea1c8a1 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.0/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.1/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.1/sqlite3_native.so new file mode 100755 index 0000000000..bbceb65b31 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.1/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.2/sqlite3_native.so b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.2/sqlite3_native.so new file mode 100755 index 0000000000..260825b888 Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/3.2/sqlite3_native.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/constants.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/constants.rb new file mode 100644 index 0000000000..29f2a5a085 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/constants.rb @@ -0,0 +1,50 @@ +module SQLite3 ; module Constants + + module TextRep + UTF8 = 1 + UTF16LE = 2 + UTF16BE = 3 + UTF16 = 4 + ANY = 5 + DETERMINISTIC = 0x800 + end + + module ColumnType + INTEGER = 1 + FLOAT = 2 + TEXT = 3 + BLOB = 4 + NULL = 5 + end + + module ErrorCode + OK = 0 # Successful result + ERROR = 1 # SQL error or missing database + INTERNAL = 2 # An internal logic error in SQLite + PERM = 3 # Access permission denied + ABORT = 4 # Callback routine requested an abort + BUSY = 5 # The database file is locked + LOCKED = 6 # A table in the database is locked + NOMEM = 7 # A malloc() failed + READONLY = 8 # Attempt to write a readonly database + INTERRUPT = 9 # Operation terminated by sqlite_interrupt() + IOERR = 10 # Some kind of disk I/O error occurred + CORRUPT = 11 # The database disk image is malformed + NOTFOUND = 12 # (Internal Only) Table or record not found + FULL = 13 # Insertion failed because database is full + CANTOPEN = 14 # Unable to open the database file + PROTOCOL = 15 # Database lock protocol error + EMPTY = 16 # (Internal Only) Database table is empty + SCHEMA = 17 # The database schema changed + TOOBIG = 18 # Too much data for one row of a table + CONSTRAINT = 19 # Abort due to constraint violation + MISMATCH = 20 # Data type mismatch + MISUSE = 21 # Library used incorrectly + NOLFS = 22 # Uses OS features not supported on host + AUTH = 23 # Authorization denied + + ROW = 100 # sqlite_step() has another row ready + DONE = 101 # sqlite_step() has finished executing + end + +end ; end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/database.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/database.rb new file mode 100644 index 0000000000..433cc6422d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/database.rb @@ -0,0 +1,741 @@ +require 'sqlite3/constants' +require 'sqlite3/errors' +require 'sqlite3/pragmas' +require 'sqlite3/statement' +require 'sqlite3/translator' +require 'sqlite3/value' + +module SQLite3 + + # The Database class encapsulates a single connection to a SQLite3 database. + # Its usage is very straightforward: + # + # require 'sqlite3' + # + # SQLite3::Database.new( "data.db" ) do |db| + # db.execute( "select * from table" ) do |row| + # p row + # end + # end + # + # It wraps the lower-level methods provides by the selected driver, and + # includes the Pragmas module for access to various pragma convenience + # methods. + # + # The Database class provides type translation services as well, by which + # the SQLite3 data types (which are all represented as strings) may be + # converted into their corresponding types (as defined in the schemas + # for their tables). This translation only occurs when querying data from + # the database--insertions and updates are all still typeless. + # + # Furthermore, the Database class has been designed to work well with the + # ArrayFields module from Ara Howard. If you require the ArrayFields + # module before performing a query, and if you have not enabled results as + # hashes, then the results will all be indexible by field name. + class Database + attr_reader :collations + + include Pragmas + + class << self + + alias :open :new + + # Quotes the given string, making it safe to use in an SQL statement. + # It replaces all instances of the single-quote character with two + # single-quote characters. The modified string is returned. + def quote( string ) + string.gsub( /'/, "''" ) + end + + end + + # A boolean that indicates whether rows in result sets should be returned + # as hashes or not. By default, rows are returned as arrays. + attr_accessor :results_as_hash + + # call-seq: SQLite3::Database.new(file, options = {}) + # + # Create a new Database object that opens the given file. If utf16 + # is +true+, the filename is interpreted as a UTF-16 encoded string. + # + # By default, the new database will return result rows as arrays + # (#results_as_hash) and has type translation disabled (#type_translation=). + + def initialize file, options = {}, zvfs = nil + mode = Constants::Open::READWRITE | Constants::Open::CREATE + + file = file.to_path if file.respond_to? :to_path + if file.encoding == ::Encoding::UTF_16LE || file.encoding == ::Encoding::UTF_16BE || options[:utf16] + open16 file + else + # The three primary flag values for sqlite3_open_v2 are: + # SQLITE_OPEN_READONLY + # SQLITE_OPEN_READWRITE + # SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE -- always used for sqlite3_open and sqlite3_open16 + mode = Constants::Open::READONLY if options[:readonly] + + if options[:readwrite] + raise "conflicting options: readonly and readwrite" if options[:readonly] + mode = Constants::Open::READWRITE + end + + if options[:flags] + if options[:readonly] || options[:readwrite] + raise "conflicting options: flags with readonly and/or readwrite" + end + mode = options[:flags] + end + + open_v2 file.encode("utf-8"), mode, zvfs + + if options[:strict] + disable_quirk_mode + end + end + + @tracefunc = nil + @authorizer = nil + @encoding = nil + @busy_handler = nil + @collations = {} + @functions = {} + @results_as_hash = options[:results_as_hash] + @type_translation = options[:type_translation] + @type_translator = make_type_translator @type_translation + @readonly = mode & Constants::Open::READONLY != 0 + + if block_given? + begin + yield self + ensure + close + end + end + end + + def type_translation= value # :nodoc: + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#type_translation= +SQLite3::Database#type_translation= is deprecated and will be removed +in version 2.0.0. + eowarn + @type_translator = make_type_translator value + @type_translation = value + end + attr_reader :type_translation # :nodoc: + + # Return the type translator employed by this database instance. Each + # database instance has its own type translator; this allows for different + # type handlers to be installed in each instance without affecting other + # instances. Furthermore, the translators are instantiated lazily, so that + # if a database does not use type translation, it will not be burdened by + # the overhead of a useless type translator. (See the Translator class.) + def translator + @translator ||= Translator.new + end + + # Installs (or removes) a block that will be invoked for every access + # to the database. If the block returns 0 (or +nil+), the statement + # is allowed to proceed. Returning 1 causes an authorization error to + # occur, and returning 2 causes the access to be silently denied. + def authorizer( &block ) + self.authorizer = block + end + + # Returns a Statement object representing the given SQL. This does not + # execute the statement; it merely prepares the statement for execution. + # + # The Statement can then be executed using Statement#execute. + # + def prepare sql + stmt = SQLite3::Statement.new( self, sql ) + return stmt unless block_given? + + begin + yield stmt + ensure + stmt.close unless stmt.closed? + end + end + + # Returns the filename for the database named +db_name+. +db_name+ defaults + # to "main". Main return `nil` or an empty string if the database is + # temporary or in-memory. + def filename db_name = 'main' + db_filename db_name + end + + # Executes the given SQL statement. If additional parameters are given, + # they are treated as bind variables, and are bound to the placeholders in + # the query. + # + # Note that if any of the values passed to this are hashes, then the + # key/value pairs are each bound separately, with the key being used as + # the name of the placeholder to bind the value to. + # + # The block is optional. If given, it will be invoked for each row returned + # by the query. Otherwise, any results are accumulated into an array and + # returned wholesale. + # + # See also #execute2, #query, and #execute_batch for additional ways of + # executing statements. + def execute sql, bind_vars = [], *args, &block + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [bind_vars] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for bind parameters as *args will be removed in 2.0.0. + eowarn + end + + prepare( sql ) do |stmt| + stmt.bind_params(bind_vars) + stmt = ResultSet.new self, stmt + + if block_given? + stmt.each do |row| + yield row + end + else + stmt.to_a + end + end + end + + # Executes the given SQL statement, exactly as with #execute. However, the + # first row returned (either via the block, or in the returned array) is + # always the names of the columns. Subsequent rows correspond to the data + # from the result set. + # + # Thus, even if the query itself returns no rows, this method will always + # return at least one row--the names of the columns. + # + # See also #execute, #query, and #execute_batch for additional ways of + # executing statements. + def execute2( sql, *bind_vars ) + prepare( sql ) do |stmt| + result = stmt.execute( *bind_vars ) + if block_given? + yield stmt.columns + result.each { |row| yield row } + else + return result.inject( [ stmt.columns ] ) { |arr,row| + arr << row; arr } + end + end + end + + # Executes all SQL statements in the given string. By contrast, the other + # means of executing queries will only execute the first statement in the + # string, ignoring all subsequent statements. This will execute each one + # in turn. The same bind parameters, if given, will be applied to each + # statement. + # + # This always returns +nil+, making it unsuitable for queries that return + # rows. + # + # See also #execute_batch2 for additional ways of + # executing statements. + def execute_batch( sql, bind_vars = [], *args ) + # FIXME: remove this stuff later + unless [Array, Hash].include?(bind_vars.class) + bind_vars = [bind_vars] + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute_batch with bind parameters +that are not a list of a hash. Please switch to passing bind parameters as an +array or hash. Support for this behavior will be removed in version 2.0.0. + eowarn + end + + # FIXME: remove this stuff later + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [nil] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#execute_batch with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for this behavior will be removed in version 2.0.0. + eowarn + end + + sql = sql.strip + until sql.empty? do + prepare( sql ) do |stmt| + unless stmt.closed? + # FIXME: this should probably use sqlite3's api for batch execution + # This implementation requires stepping over the results. + if bind_vars.length == stmt.bind_parameter_count + stmt.bind_params(bind_vars) + end + stmt.step + end + sql = stmt.remainder.strip + end + end + # FIXME: we should not return `nil` as a success return value + nil + end + + # Executes all SQL statements in the given string. By contrast, the other + # means of executing queries will only execute the first statement in the + # string, ignoring all subsequent statements. This will execute each one + # in turn. Bind parameters cannot be passed to #execute_batch2. + # + # If a query is made, all values will be returned as strings. + # If no query is made, an empty array will be returned. + # + # Because all values except for 'NULL' are returned as strings, + # a block can be passed to parse the values accordingly. + # + # See also #execute_batch for additional ways of + # executing statements. + def execute_batch2(sql, &block) + if block_given? + result = exec_batch(sql, @results_as_hash) + result.map do |val| + yield val + end + else + exec_batch(sql, @results_as_hash) + end + end + + # This is a convenience method for creating a statement, binding + # parameters to it, and calling execute: + # + # result = db.query( "select * from foo where a=?", [5]) + # # is the same as + # result = db.prepare( "select * from foo where a=?" ).execute( 5 ) + # + # You must be sure to call +close+ on the ResultSet instance that is + # returned, or you could have problems with locks on the table. If called + # with a block, +close+ will be invoked implicitly when the block + # terminates. + def query( sql, bind_vars = [], *args ) + + if bind_vars.nil? || !args.empty? + if args.empty? + bind_vars = [] + else + bind_vars = [bind_vars] + args + end + + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling SQLite3::Database#query with nil or multiple bind params +without using an array. Please switch to passing bind parameters as an array. +Support for this will be removed in version 2.0.0. + eowarn + end + + result = prepare( sql ).execute( bind_vars ) + if block_given? + begin + yield result + ensure + result.close + end + else + return result + end + end + + # A convenience method for obtaining the first row of a result set, and + # discarding all others. It is otherwise identical to #execute. + # + # See also #get_first_value. + def get_first_row( sql, *bind_vars ) + execute( sql, *bind_vars ).first + end + + # A convenience method for obtaining the first value of the first row of a + # result set, and discarding all other values and rows. It is otherwise + # identical to #execute. + # + # See also #get_first_row. + def get_first_value( sql, *bind_vars ) + query( sql, bind_vars ) do |rs| + if (row = rs.next) + return @results_as_hash ? row[rs.columns[0]] : row[0] + end + end + nil + end + + alias :busy_timeout :busy_timeout= + + # Creates a new function for use in SQL statements. It will be added as + # +name+, with the given +arity+. (For variable arity functions, use + # -1 for the arity.) + # + # The block should accept at least one parameter--the FunctionProxy + # instance that wraps this function invocation--and any other + # arguments it needs (up to its arity). + # + # The block does not return a value directly. Instead, it will invoke + # the FunctionProxy#result= method on the +func+ parameter and + # indicate the return value that way. + # + # Example: + # + # db.create_function( "maim", 1 ) do |func, value| + # if value.nil? + # func.result = nil + # else + # func.result = value.split(//).sort.join + # end + # end + # + # puts db.get_first_value( "select maim(name) from table" ) + def create_function name, arity, text_rep=Constants::TextRep::UTF8, &block + define_function_with_flags(name, text_rep) do |*args| + fp = FunctionProxy.new + block.call(fp, *args) + fp.result + end + self + end + + # Creates a new aggregate function for use in SQL statements. Aggregate + # functions are functions that apply over every row in the result set, + # instead of over just a single row. (A very common aggregate function + # is the "count" function, for determining the number of rows that match + # a query.) + # + # The new function will be added as +name+, with the given +arity+. (For + # variable arity functions, use -1 for the arity.) + # + # The +step+ parameter must be a proc object that accepts as its first + # parameter a FunctionProxy instance (representing the function + # invocation), with any subsequent parameters (up to the function's arity). + # The +step+ callback will be invoked once for each row of the result set. + # + # The +finalize+ parameter must be a +proc+ object that accepts only a + # single parameter, the FunctionProxy instance representing the current + # function invocation. It should invoke FunctionProxy#result= to + # store the result of the function. + # + # Example: + # + # db.create_aggregate( "lengths", 1 ) do + # step do |func, value| + # func[ :total ] ||= 0 + # func[ :total ] += ( value ? value.length : 0 ) + # end + # + # finalize do |func| + # func.result = func[ :total ] || 0 + # end + # end + # + # puts db.get_first_value( "select lengths(name) from table" ) + # + # See also #create_aggregate_handler for a more object-oriented approach to + # aggregate functions. + def create_aggregate( name, arity, step=nil, finalize=nil, + text_rep=Constants::TextRep::ANY, &block ) + + proxy = Class.new do + def self.step( &block ) + define_method(:step_with_ctx, &block) + end + + def self.finalize( &block ) + define_method(:finalize_with_ctx, &block) + end + end + + if block_given? + proxy.instance_eval(&block) + else + proxy.class_eval do + define_method(:step_with_ctx, step) + define_method(:finalize_with_ctx, finalize) + end + end + + proxy.class_eval do + # class instance variables + @name = name + @arity = arity + + def self.name + @name + end + + def self.arity + @arity + end + + def initialize + @ctx = FunctionProxy.new + end + + def step( *args ) + step_with_ctx(@ctx, *args) + end + + def finalize + finalize_with_ctx(@ctx) + @ctx.result + end + end + define_aggregator2(proxy, name) + end + + # This is another approach to creating an aggregate function (see + # #create_aggregate). Instead of explicitly specifying the name, + # callbacks, arity, and type, you specify a factory object + # (the "handler") that knows how to obtain all of that information. The + # handler should respond to the following messages: + # + # +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This + # message is optional, and if the handler does not respond to it, + # the function will have an arity of -1. + # +name+:: this is the name of the function. The handler _must_ implement + # this message. + # +new+:: this must be implemented by the handler. It should return a new + # instance of the object that will handle a specific invocation of + # the function. + # + # The handler instance (the object returned by the +new+ message, described + # above), must respond to the following messages: + # + # +step+:: this is the method that will be called for each step of the + # aggregate function's evaluation. It should implement the same + # signature as the +step+ callback for #create_aggregate. + # +finalize+:: this is the method that will be called to finalize the + # aggregate function's evaluation. It should implement the + # same signature as the +finalize+ callback for + # #create_aggregate. + # + # Example: + # + # class LengthsAggregateHandler + # def self.arity; 1; end + # def self.name; 'lengths'; end + # + # def initialize + # @total = 0 + # end + # + # def step( ctx, name ) + # @total += ( name ? name.length : 0 ) + # end + # + # def finalize( ctx ) + # ctx.result = @total + # end + # end + # + # db.create_aggregate_handler( LengthsAggregateHandler ) + # puts db.get_first_value( "select lengths(name) from A" ) + def create_aggregate_handler( handler ) + # This is a compatibility shim so the (basically pointless) FunctionProxy + # "ctx" object is passed as first argument to both step() and finalize(). + # Now its up to the library user whether he prefers to store his + # temporaries as instance variables or fields in the FunctionProxy. + # The library user still must set the result value with + # FunctionProxy.result= as there is no backwards compatible way to + # change this. + proxy = Class.new(handler) do + def initialize + super + @fp = FunctionProxy.new + end + + def step( *args ) + super(@fp, *args) + end + + def finalize + super(@fp) + @fp.result + end + end + define_aggregator2(proxy, proxy.name) + self + end + + # Define an aggregate function named +name+ using a object template + # object +aggregator+. +aggregator+ must respond to +step+ and +finalize+. + # +step+ will be called with row information and +finalize+ must return the + # return value for the aggregator function. + # + # _API Change:_ +aggregator+ must also implement +clone+. The provided + # +aggregator+ object will serve as template that is cloned to provide the + # individual instances of the aggregate function. Regular ruby objects + # already provide a suitable +clone+. + # The functions arity is the arity of the +step+ method. + def define_aggregator( name, aggregator ) + # Previously, this has been implemented in C. Now this is just yet + # another compatibility shim + proxy = Class.new do + @template = aggregator + @name = name + + def self.template + @template + end + + def self.name + @name + end + + def self.arity + # this is what sqlite3_obj_method_arity did before + @template.method(:step).arity + end + + def initialize + @klass = self.class.template.clone + end + + def step(*args) + @klass.step(*args) + end + + def finalize + @klass.finalize + end + end + define_aggregator2(proxy, name) + self + end + + # Begins a new transaction. Note that nested transactions are not allowed + # by SQLite, so attempting to nest a transaction will result in a runtime + # exception. + # + # The +mode+ parameter may be either :deferred (the default), + # :immediate, or :exclusive. + # + # If a block is given, the database instance is yielded to it, and the + # transaction is committed when the block terminates. If the block + # raises an exception, a rollback will be performed instead. Note that if + # a block is given, #commit and #rollback should never be called + # explicitly or you'll get an error when the block terminates. + # + # If a block is not given, it is the caller's responsibility to end the + # transaction explicitly, either by calling #commit, or by calling + # #rollback. + def transaction( mode = :deferred ) + execute "begin #{mode.to_s} transaction" + + if block_given? + abort = false + begin + yield self + rescue + abort = true + raise + ensure + abort and rollback or commit + end + end + + true + end + + # Commits the current transaction. If there is no current transaction, + # this will cause an error to be raised. This returns +true+, in order + # to allow it to be used in idioms like + # abort? and rollback or commit. + def commit + execute "commit transaction" + true + end + + # Rolls the current transaction back. If there is no current transaction, + # this will cause an error to be raised. This returns +true+, in order + # to allow it to be used in idioms like + # abort? and rollback or commit. + def rollback + execute "rollback transaction" + true + end + + # Returns +true+ if the database has been open in readonly mode + # A helper to check before performing any operation + def readonly? + @readonly + end + + # A helper class for dealing with custom functions (see #create_function, + # #create_aggregate, and #create_aggregate_handler). It encapsulates the + # opaque function object that represents the current invocation. It also + # provides more convenient access to the API functions that operate on + # the function object. + # + # This class will almost _always_ be instantiated indirectly, by working + # with the create methods mentioned above. + class FunctionProxy + attr_accessor :result + + # Create a new FunctionProxy that encapsulates the given +func+ object. + # If context is non-nil, the functions context will be set to that. If + # it is non-nil, it must quack like a Hash. If it is nil, then none of + # the context functions will be available. + def initialize + @result = nil + @context = {} + end + + # Set the result of the function to the given error message. + # The function will then return that error. + def set_error( error ) + @driver.result_error( @func, error.to_s, -1 ) + end + + # (Only available to aggregate functions.) Returns the number of rows + # that the aggregate has processed so far. This will include the current + # row, and so will always return at least 1. + def count + @driver.aggregate_count( @func ) + end + + # Returns the value with the given key from the context. This is only + # available to aggregate functions. + def []( key ) + @context[ key ] + end + + # Sets the value with the given key in the context. This is only + # available to aggregate functions. + def []=( key, value ) + @context[ key ] = value + end + end + + # Translates a +row+ of data from the database with the given +types+ + def translate_from_db types, row + @type_translator.call types, row + end + + private + + NULL_TRANSLATOR = lambda { |_, row| row } + + def make_type_translator should_translate + if should_translate + lambda { |types, row| + types.zip(row).map do |type, value| + translator.translate( type, value ) + end + } + else + NULL_TRANSLATOR + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/errors.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/errors.rb new file mode 100644 index 0000000000..87c7b4ee70 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/errors.rb @@ -0,0 +1,35 @@ +require 'sqlite3/constants' + +module SQLite3 + class Exception < ::StandardError + # A convenience for accessing the error code for this exception. + attr_reader :code + end + + class SQLException < Exception; end + class InternalException < Exception; end + class PermissionException < Exception; end + class AbortException < Exception; end + class BusyException < Exception; end + class LockedException < Exception; end + class MemoryException < Exception; end + class ReadOnlyException < Exception; end + class InterruptException < Exception; end + class IOException < Exception; end + class CorruptException < Exception; end + class NotFoundException < Exception; end + class FullException < Exception; end + class CantOpenException < Exception; end + class ProtocolException < Exception; end + class EmptyException < Exception; end + class SchemaChangedException < Exception; end + class TooBigException < Exception; end + class ConstraintException < Exception; end + class MismatchException < Exception; end + class MisuseException < Exception; end + class UnsupportedException < Exception; end + class AuthorizationException < Exception; end + class FormatException < Exception; end + class RangeException < Exception; end + class NotADatabaseException < Exception; end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/pragmas.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/pragmas.rb new file mode 100644 index 0000000000..39ef8b3ada --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/pragmas.rb @@ -0,0 +1,595 @@ +require 'sqlite3/errors' + +module SQLite3 + + # This module is intended for inclusion solely by the Database class. It + # defines convenience methods for the various pragmas supported by SQLite3. + # + # For a detailed description of these pragmas, see the SQLite3 documentation + # at http://sqlite.org/pragma.html. + module Pragmas + + # Returns +true+ or +false+ depending on the value of the named pragma. + def get_boolean_pragma( name ) + get_first_value( "PRAGMA #{name}" ) != 0 + end + + # Sets the given pragma to the given boolean value. The value itself + # may be +true+ or +false+, or any other commonly used string or + # integer that represents truth. + def set_boolean_pragma( name, mode ) + case mode + when String + case mode.downcase + when "on", "yes", "true", "y", "t"; mode = "'ON'" + when "off", "no", "false", "n", "f"; mode = "'OFF'" + else + raise Exception, + "unrecognized pragma parameter #{mode.inspect}" + end + when true, 1 + mode = "ON" + when false, 0, nil + mode = "OFF" + else + raise Exception, + "unrecognized pragma parameter #{mode.inspect}" + end + + execute( "PRAGMA #{name}=#{mode}" ) + end + + # Requests the given pragma (and parameters), and if the block is given, + # each row of the result set will be yielded to it. Otherwise, the results + # are returned as an array. + def get_query_pragma( name, *params, &block ) # :yields: row + if params.empty? + execute( "PRAGMA #{name}", &block ) + else + args = "'" + params.join("','") + "'" + execute( "PRAGMA #{name}( #{args} )", &block ) + end + end + + # Return the value of the given pragma. + def get_enum_pragma( name ) + get_first_value( "PRAGMA #{name}" ) + end + + # Set the value of the given pragma to +mode+. The +mode+ parameter must + # conform to one of the values in the given +enum+ array. Each entry in + # the array is another array comprised of elements in the enumeration that + # have duplicate values. See #synchronous, #default_synchronous, + # #temp_store, and #default_temp_store for usage examples. + def set_enum_pragma( name, mode, enums ) + match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } } + raise Exception, + "unrecognized #{name} #{mode.inspect}" unless match + execute( "PRAGMA #{name}='#{match.first.upcase}'" ) + end + + # Returns the value of the given pragma as an integer. + def get_int_pragma( name ) + get_first_value( "PRAGMA #{name}" ).to_i + end + + # Set the value of the given pragma to the integer value of the +value+ + # parameter. + def set_int_pragma( name, value ) + execute( "PRAGMA #{name}=#{value.to_i}" ) + end + + # The enumeration of valid synchronous modes. + SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ] + + # The enumeration of valid temp store modes. + TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ] + + # The enumeration of valid auto vacuum modes. + AUTO_VACUUM_MODES = [ [ 'none', 0 ], [ 'full', 1 ], [ 'incremental', 2 ] ] + + # The list of valid journaling modes. + JOURNAL_MODES = [ [ 'delete' ], [ 'truncate' ], [ 'persist' ], [ 'memory' ], + [ 'wal' ], [ 'off' ] ] + + # The list of valid locking modes. + LOCKING_MODES = [ [ 'normal' ], [ 'exclusive' ] ] + + # The list of valid encodings. + ENCODINGS = [ [ 'utf-8' ], [ 'utf-16' ], [ 'utf-16le' ], [ 'utf-16be ' ] ] + + # The list of valid WAL checkpoints. + WAL_CHECKPOINTS = [ [ 'passive' ], [ 'full' ], [ 'restart' ], [ 'truncate' ] ] + + def application_id + get_int_pragma "application_id" + end + + def application_id=( integer ) + set_int_pragma "application_id", integer + end + + def auto_vacuum + get_enum_pragma "auto_vacuum" + end + + def auto_vacuum=( mode ) + set_enum_pragma "auto_vacuum", mode, AUTO_VACUUM_MODES + end + + def automatic_index + get_boolean_pragma "automatic_index" + end + + def automatic_index=( mode ) + set_boolean_pragma "automatic_index", mode + end + + def busy_timeout + get_int_pragma "busy_timeout" + end + + def busy_timeout=( milliseconds ) + set_int_pragma "busy_timeout", milliseconds + end + + def cache_size + get_int_pragma "cache_size" + end + + def cache_size=( size ) + set_int_pragma "cache_size", size + end + + def cache_spill + get_boolean_pragma "cache_spill" + end + + def cache_spill=( mode ) + set_boolean_pragma "cache_spill", mode + end + + def case_sensitive_like=( mode ) + set_boolean_pragma "case_sensitive_like", mode + end + + def cell_size_check + get_boolean_pragma "cell_size_check" + end + + def cell_size_check=( mode ) + set_boolean_pragma "cell_size_check", mode + end + + def checkpoint_fullfsync + get_boolean_pragma "checkpoint_fullfsync" + end + + def checkpoint_fullfsync=( mode ) + set_boolean_pragma "checkpoint_fullfsync", mode + end + + def collation_list( &block ) # :yields: row + get_query_pragma "collation_list", &block + end + + def compile_options( &block ) # :yields: row + get_query_pragma "compile_options", &block + end + + def count_changes + get_boolean_pragma "count_changes" + end + + def count_changes=( mode ) + set_boolean_pragma "count_changes", mode + end + + def data_version + get_int_pragma "data_version" + end + + def database_list( &block ) # :yields: row + get_query_pragma "database_list", &block + end + + def default_cache_size + get_int_pragma "default_cache_size" + end + + def default_cache_size=( size ) + set_int_pragma "default_cache_size", size + end + + def default_synchronous + get_enum_pragma "default_synchronous" + end + + def default_synchronous=( mode ) + set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES + end + + def default_temp_store + get_enum_pragma "default_temp_store" + end + + def default_temp_store=( mode ) + set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES + end + + def defer_foreign_keys + get_boolean_pragma "defer_foreign_keys" + end + + def defer_foreign_keys=( mode ) + set_boolean_pragma "defer_foreign_keys", mode + end + + def encoding + get_enum_pragma "encoding" + end + + def encoding=( mode ) + set_enum_pragma "encoding", mode, ENCODINGS + end + + def foreign_key_check( *table, &block ) # :yields: row + get_query_pragma "foreign_key_check", *table, &block + end + + def foreign_key_list( table, &block ) # :yields: row + get_query_pragma "foreign_key_list", table, &block + end + + def foreign_keys + get_boolean_pragma "foreign_keys" + end + + def foreign_keys=( mode ) + set_boolean_pragma "foreign_keys", mode + end + + def freelist_count + get_int_pragma "freelist_count" + end + + def full_column_names + get_boolean_pragma "full_column_names" + end + + def full_column_names=( mode ) + set_boolean_pragma "full_column_names", mode + end + + def fullfsync + get_boolean_pragma "fullfsync" + end + + def fullfsync=( mode ) + set_boolean_pragma "fullfsync", mode + end + + def ignore_check_constraints=( mode ) + set_boolean_pragma "ignore_check_constraints", mode + end + + def incremental_vacuum( pages, &block ) # :yields: row + get_query_pragma "incremental_vacuum", pages, &block + end + + def index_info( index, &block ) # :yields: row + get_query_pragma "index_info", index, &block + end + + def index_list( table, &block ) # :yields: row + get_query_pragma "index_list", table, &block + end + + def index_xinfo( index, &block ) # :yields: row + get_query_pragma "index_xinfo", index, &block + end + + def integrity_check( *num_errors, &block ) # :yields: row + get_query_pragma "integrity_check", *num_errors, &block + end + + def journal_mode + get_enum_pragma "journal_mode" + end + + def journal_mode=( mode ) + set_enum_pragma "journal_mode", mode, JOURNAL_MODES + end + + def journal_size_limit + get_int_pragma "journal_size_limit" + end + + def journal_size_limit=( size ) + set_int_pragma "journal_size_limit", size + end + + def legacy_file_format + get_boolean_pragma "legacy_file_format" + end + + def legacy_file_format=( mode ) + set_boolean_pragma "legacy_file_format", mode + end + + def locking_mode + get_enum_pragma "locking_mode" + end + + def locking_mode=( mode ) + set_enum_pragma "locking_mode", mode, LOCKING_MODES + end + + def max_page_count + get_int_pragma "max_page_count" + end + + def max_page_count=( size ) + set_int_pragma "max_page_count", size + end + + def mmap_size + get_int_pragma "mmap_size" + end + + def mmap_size=( size ) + set_int_pragma "mmap_size", size + end + + def page_count + get_int_pragma "page_count" + end + + def page_size + get_int_pragma "page_size" + end + + def page_size=( size ) + set_int_pragma "page_size", size + end + + def parser_trace=( mode ) + set_boolean_pragma "parser_trace", mode + end + + def query_only + get_boolean_pragma "query_only" + end + + def query_only=( mode ) + set_boolean_pragma "query_only", mode + end + + def quick_check( *num_errors, &block ) # :yields: row + get_query_pragma "quick_check", *num_errors, &block + end + + def read_uncommitted + get_boolean_pragma "read_uncommitted" + end + + def read_uncommitted=( mode ) + set_boolean_pragma "read_uncommitted", mode + end + + def recursive_triggers + get_boolean_pragma "recursive_triggers" + end + + def recursive_triggers=( mode ) + set_boolean_pragma "recursive_triggers", mode + end + + def reverse_unordered_selects + get_boolean_pragma "reverse_unordered_selects" + end + + def reverse_unordered_selects=( mode ) + set_boolean_pragma "reverse_unordered_selects", mode + end + + def schema_cookie + get_int_pragma "schema_cookie" + end + + def schema_cookie=( cookie ) + set_int_pragma "schema_cookie", cookie + end + + def schema_version + get_int_pragma "schema_version" + end + + def schema_version=( version ) + set_int_pragma "schema_version", version + end + + def secure_delete + get_boolean_pragma "secure_delete" + end + + def secure_delete=( mode ) + set_boolean_pragma "secure_delete", mode + end + + def short_column_names + get_boolean_pragma "short_column_names" + end + + def short_column_names=( mode ) + set_boolean_pragma "short_column_names", mode + end + + def shrink_memory + execute( "PRAGMA shrink_memory" ) + end + + def soft_heap_limit + get_int_pragma "soft_heap_limit" + end + + def soft_heap_limit=( mode ) + set_int_pragma "soft_heap_limit", mode + end + + def stats( &block ) # :yields: row + get_query_pragma "stats", &block + end + + def synchronous + get_enum_pragma "synchronous" + end + + def synchronous=( mode ) + set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES + end + + def temp_store + get_enum_pragma "temp_store" + end + + def temp_store=( mode ) + set_enum_pragma "temp_store", mode, TEMP_STORE_MODES + end + + def threads + get_int_pragma "threads" + end + + def threads=( count ) + set_int_pragma "threads", count + end + + def user_cookie + get_int_pragma "user_cookie" + end + + def user_cookie=( cookie ) + set_int_pragma "user_cookie", cookie + end + + def user_version + get_int_pragma "user_version" + end + + def user_version=( version ) + set_int_pragma "user_version", version + end + + def vdbe_addoptrace=( mode ) + set_boolean_pragma "vdbe_addoptrace", mode + end + + def vdbe_debug=( mode ) + set_boolean_pragma "vdbe_debug", mode + end + + def vdbe_listing=( mode ) + set_boolean_pragma "vdbe_listing", mode + end + + def vdbe_trace + get_boolean_pragma "vdbe_trace" + end + + def vdbe_trace=( mode ) + set_boolean_pragma "vdbe_trace", mode + end + + def wal_autocheckpoint + get_int_pragma "wal_autocheckpoint" + end + + def wal_autocheckpoint=( mode ) + set_int_pragma "wal_autocheckpoint", mode + end + + def wal_checkpoint + get_enum_pragma "wal_checkpoint" + end + + def wal_checkpoint=( mode ) + set_enum_pragma "wal_checkpoint", mode, WAL_CHECKPOINTS + end + + def writable_schema=( mode ) + set_boolean_pragma "writable_schema", mode + end + + ### + # Returns information about +table+. Yields each row of table information + # if a block is provided. + def table_info table + stmt = prepare "PRAGMA table_info(#{table})" + columns = stmt.columns + + needs_tweak_default = + version_compare(SQLite3.libversion.to_s, "3.3.7") > 0 + + result = [] unless block_given? + stmt.each do |row| + new_row = Hash[columns.zip(row)] + + # FIXME: This should be removed but is required for older versions + # of rails + if(Object.const_defined?(:ActiveRecord)) + new_row['notnull'] = new_row['notnull'].to_s + end + + tweak_default(new_row) if needs_tweak_default + + # Ensure the type value is downcased. On Mac and Windows + # platforms this value is now being returned as all upper + # case. + if new_row['type'] + new_row['type'] = new_row['type'].downcase + end + + if block_given? + yield new_row + else + result << new_row + end + end + stmt.close + + result + end + + private + + # Compares two version strings + def version_compare(v1, v2) + v1 = v1.split(".").map { |i| i.to_i } + v2 = v2.split(".").map { |i| i.to_i } + parts = [v1.length, v2.length].max + v1.push 0 while v1.length < parts + v2.push 0 while v2.length < parts + v1.zip(v2).each do |a,b| + return -1 if a < b + return 1 if a > b + end + return 0 + end + + # Since SQLite 3.3.8, the table_info pragma has returned the default + # value of the row as a quoted SQL value. This method essentially + # unquotes those values. + def tweak_default(hash) + case hash["dflt_value"] + when /^null$/i + hash["dflt_value"] = nil + when /^'(.*)'$/m + hash["dflt_value"] = $1.gsub(/''/, "'") + when /^"(.*)"$/m + hash["dflt_value"] = $1.gsub(/""/, '"') + end + end + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/resultset.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/resultset.rb new file mode 100644 index 0000000000..5c1cc19b79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/resultset.rb @@ -0,0 +1,187 @@ +require 'sqlite3/constants' +require 'sqlite3/errors' + +module SQLite3 + + # The ResultSet object encapsulates the enumerability of a query's output. + # It is a simple cursor over the data that the query returns. It will + # very rarely (if ever) be instantiated directly. Instead, clients should + # obtain a ResultSet instance via Statement#execute. + class ResultSet + include Enumerable + + class ArrayWithTypes < Array # :nodoc: + attr_accessor :types + end + + class ArrayWithTypesAndFields < Array # :nodoc: + attr_writer :types + attr_writer :fields + + def types + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#types. This method will be removed in +sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet +object that created this object + eowarn + @types + end + + def fields + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#fields. This method will be removed in +sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet +object that created this object + eowarn + @fields + end + end + + # The class of which we return an object in case we want a Hash as + # result. + class HashWithTypesAndFields < Hash # :nodoc: + attr_writer :types + attr_writer :fields + + def types + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#types. This method will be removed in +sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet +object that created this object + eowarn + @types + end + + def fields + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling #{self.class}#fields. This method will be removed in +sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet +object that created this object + eowarn + @fields + end + + def [] key + key = fields[key] if key.is_a? Numeric + super key + end + end + + # Create a new ResultSet attached to the given database, using the + # given sql text. + def initialize db, stmt + @db = db + @stmt = stmt + end + + # Reset the cursor, so that a result set which has reached end-of-file + # can be rewound and reiterated. + def reset( *bind_params ) + @stmt.reset! + @stmt.bind_params( *bind_params ) + @eof = false + end + + # Query whether the cursor has reached the end of the result set or not. + def eof? + @stmt.done? + end + + # Obtain the next row from the cursor. If there are no more rows to be + # had, this will return +nil+. If type translation is active on the + # corresponding database, the values in the row will be translated + # according to their types. + # + # The returned value will be an array, unless Database#results_as_hash has + # been set to +true+, in which case the returned value will be a hash. + # + # For arrays, the column names are accessible via the +fields+ property, + # and the column types are accessible via the +types+ property. + # + # For hashes, the column names are the keys of the hash, and the column + # types are accessible via the +types+ property. + def next + if @db.results_as_hash + return next_hash + end + + row = @stmt.step + return nil if @stmt.done? + + row = @db.translate_from_db @stmt.types, row + + if row.respond_to?(:fields) + # FIXME: this can only happen if the translator returns something + # that responds to `fields`. Since we're removing the translator + # in 2.0, we can remove this branch in 2.0. + row = ArrayWithTypes.new(row) + else + # FIXME: the `fields` and `types` methods are deprecated on this + # object for version 2.0, so we can safely remove this branch + # as well. + row = ArrayWithTypesAndFields.new(row) + end + + row.fields = @stmt.columns + row.types = @stmt.types + row + end + + # Required by the Enumerable mixin. Provides an internal iterator over the + # rows of the result set. + def each + while node = self.next + yield node + end + end + + # Provides an internal iterator over the rows of the result set where + # each row is yielded as a hash. + def each_hash + while node = next_hash + yield node + end + end + + # Closes the statement that spawned this result set. + # Use with caution! Closing a result set will automatically + # close any other result sets that were spawned from the same statement. + def close + @stmt.close + end + + # Queries whether the underlying statement has been closed or not. + def closed? + @stmt.closed? + end + + # Returns the types of the columns returned by this result set. + def types + @stmt.types + end + + # Returns the names of the columns returned by this result set. + def columns + @stmt.columns + end + + # Return the next row as a hash + def next_hash + row = @stmt.step + return nil if @stmt.done? + + # FIXME: type translation is deprecated, so this can be removed + # in 2.0 + row = @db.translate_from_db @stmt.types, row + + # FIXME: this can be switched to a regular hash in 2.0 + row = HashWithTypesAndFields[*@stmt.columns.zip(row).flatten] + + # FIXME: these methods are deprecated for version 2.0, so we can remove + # this code in 2.0 + row.fields = @stmt.columns + row.types = @stmt.types + row + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/statement.rb new file mode 100644 index 0000000000..6e77f2e3ba --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/statement.rb @@ -0,0 +1,145 @@ +require 'sqlite3/errors' +require 'sqlite3/resultset' + +class String + def to_blob + SQLite3::Blob.new( self ) + end +end + +module SQLite3 + # A statement represents a prepared-but-unexecuted SQL query. It will rarely + # (if ever) be instantiated directly by a client, and is most often obtained + # via the Database#prepare method. + class Statement + include Enumerable + + # This is any text that followed the first valid SQL statement in the text + # with which the statement was initialized. If there was no trailing text, + # this will be the empty string. + attr_reader :remainder + + # Binds the given variables to the corresponding placeholders in the SQL + # text. + # + # See Database#execute for a description of the valid placeholder + # syntaxes. + # + # Example: + # + # stmt = db.prepare( "select * from table where a=? and b=?" ) + # stmt.bind_params( 15, "hello" ) + # + # See also #execute, #bind_param, Statement#bind_param, and + # Statement#bind_params. + def bind_params( *bind_vars ) + index = 1 + bind_vars.flatten.each do |var| + if Hash === var + var.each { |key, val| bind_param key, val } + else + bind_param index, var + index += 1 + end + end + end + + # Execute the statement. This creates a new ResultSet object for the + # statement's virtual machine. If a block was given, the new ResultSet will + # be yielded to it; otherwise, the ResultSet will be returned. + # + # Any parameters will be bound to the statement using #bind_params. + # + # Example: + # + # stmt = db.prepare( "select * from table" ) + # stmt.execute do |result| + # ... + # end + # + # See also #bind_params, #execute!. + def execute( *bind_vars ) + reset! if active? || done? + + bind_params(*bind_vars) unless bind_vars.empty? + @results = ResultSet.new(@connection, self) + + step if 0 == column_count + + yield @results if block_given? + @results + end + + # Execute the statement. If no block was given, this returns an array of + # rows returned by executing the statement. Otherwise, each row will be + # yielded to the block. + # + # Any parameters will be bound to the statement using #bind_params. + # + # Example: + # + # stmt = db.prepare( "select * from table" ) + # stmt.execute! do |row| + # ... + # end + # + # See also #bind_params, #execute. + def execute!( *bind_vars, &block ) + execute(*bind_vars) + block_given? ? each(&block) : to_a + end + + # Returns true if the statement is currently active, meaning it has an + # open result set. + def active? + !done? + end + + # Return an array of the column names for this statement. Note that this + # may execute the statement in order to obtain the metadata; this makes it + # a (potentially) expensive operation. + def columns + get_metadata unless @columns + return @columns + end + + def each + loop do + val = step + break self if done? + yield val + end + end + + # Return an array of the data types for each column in this statement. Note + # that this may execute the statement in order to obtain the metadata; this + # makes it a (potentially) expensive operation. + def types + must_be_open! + get_metadata unless @types + @types + end + + # Performs a sanity check to ensure that the statement is not + # closed. If it is, an exception is raised. + def must_be_open! # :nodoc: + if closed? + raise SQLite3::Exception, "cannot use a closed statement" + end + end + + private + # A convenience method for obtaining the metadata about the query. Note + # that this will actually execute the SQL, which means it can be a + # (potentially) expensive operation. + def get_metadata + @columns = Array.new(column_count) do |column| + column_name column + end + @types = Array.new(column_count) do |column| + val = column_decltype(column) + val.nil? ? nil : val.downcase + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/translator.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/translator.rb new file mode 100644 index 0000000000..0809c0bb38 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/translator.rb @@ -0,0 +1,118 @@ +require 'time' +require 'date' + +module SQLite3 + + # The Translator class encapsulates the logic and callbacks necessary for + # converting string data to a value of some specified type. Every Database + # instance may have a Translator instance, in order to assist in type + # translation (Database#type_translation). + # + # Further, applications may define their own custom type translation logic + # by registering translator blocks with the corresponding database's + # translator instance (Database#translator). + class Translator + + # Create a new Translator instance. It will be preinitialized with default + # translators for most SQL data types. + def initialize + @translators = Hash.new( proc { |type,value| value } ) + @type_name_cache = {} + register_default_translators + end + + # Add a new translator block, which will be invoked to process type + # translations to the given type. The type should be an SQL datatype, and + # may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical + # information is stripped off and discarded, so type translation decisions + # are made solely on the "base" type name. + # + # The translator block itself should accept two parameters, "type" and + # "value". In this case, the "type" is the full type name (including + # parentheses), so the block itself may include logic for changing how a + # type is translated based on the additional data. The "value" parameter + # is the (string) data to convert. + # + # The block should return the translated value. + def add_translator( type, &block ) # :yields: type, value + warn(<<-eowarn) if $VERBOSE +#{caller[0]} is calling `add_translator`. +Built in translators are deprecated and will be removed in version 2.0.0 + eowarn + @translators[ type_name( type ) ] = block + end + + # Translate the given string value to a value of the given type. In the + # absence of an installed translator block for the given type, the value + # itself is always returned. Further, +nil+ values are never translated, + # and are always passed straight through regardless of the type parameter. + def translate( type, value ) + unless value.nil? + # FIXME: this is a hack to support Sequel + if type && %w{ datetime timestamp }.include?(type.downcase) + @translators[ type_name( type ) ].call( type, value.to_s ) + else + @translators[ type_name( type ) ].call( type, value ) + end + end + end + + # A convenience method for working with type names. This returns the "base" + # type name, without any parenthetical data. + def type_name( type ) + @type_name_cache[type] ||= begin + type = "" if type.nil? + type = $1 if type =~ /^(.*?)\(/ + type.upcase + end + end + private :type_name + + # Register the default translators for the current Translator instance. + # This includes translators for most major SQL data types. + def register_default_translators + [ "time", + "timestamp" ].each { |type| add_translator( type ) { |t, v| Time.parse( v ) } } + + add_translator( "date" ) { |t,v| Date.parse(v) } + add_translator( "datetime" ) { |t,v| DateTime.parse(v) } + + [ "decimal", + "float", + "numeric", + "double", + "real", + "dec", + "fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } } + + [ "integer", + "smallint", + "mediumint", + "int", + "bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } } + + [ "bit", + "bool", + "boolean" ].each do |type| + add_translator( type ) do |t,v| + !( v.strip.gsub(/00+/,"0") == "0" || + v.downcase == "false" || + v.downcase == "f" || + v.downcase == "no" || + v.downcase == "n" ) + end + end + + add_translator( "tinyint" ) do |type, value| + if type =~ /\(\s*1\s*\)/ + value.to_i == 1 + else + value.to_i + end + end + end + private :register_default_translators + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/value.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/value.rb new file mode 100644 index 0000000000..e5e5bf2c6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/value.rb @@ -0,0 +1,57 @@ +require 'sqlite3/constants' + +module SQLite3 + + class Value + attr_reader :handle + + def initialize( db, handle ) + @driver = db.driver + @handle = handle + end + + def null? + type == :null + end + + def to_blob + @driver.value_blob( @handle ) + end + + def length( utf16=false ) + if utf16 + @driver.value_bytes16( @handle ) + else + @driver.value_bytes( @handle ) + end + end + + def to_f + @driver.value_double( @handle ) + end + + def to_i + @driver.value_int( @handle ) + end + + def to_int64 + @driver.value_int64( @handle ) + end + + def to_s( utf16=false ) + @driver.value_text( @handle, utf16 ) + end + + def type + case @driver.value_type( @handle ) + when Constants::ColumnType::INTEGER then :int + when Constants::ColumnType::FLOAT then :float + when Constants::ColumnType::TEXT then :text + when Constants::ColumnType::BLOB then :blob + when Constants::ColumnType::NULL then :null + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/version.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/version.rb new file mode 100644 index 0000000000..d1ff20cfa1 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/lib/sqlite3/version.rb @@ -0,0 +1,23 @@ +module SQLite3 + + VERSION = "1.6.1" + + module VersionProxy + MAJOR = 1 + MINOR = 6 + TINY = 1 + BUILD = nil + + STRING = [ MAJOR, MINOR, TINY, BUILD ].compact.join( "." ) + + VERSION = ::SQLite3::VERSION + end + + def self.const_missing(name) + return super unless name == :Version + warn(<<-eowarn) if $VERBOSE +#{caller[0]}: SQLite::Version will be removed in sqlite3-ruby version 2.0.0 + eowarn + VersionProxy + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/helper.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/helper.rb new file mode 100644 index 0000000000..8f7d1b35c3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/helper.rb @@ -0,0 +1,27 @@ +require 'sqlite3' +require 'minitest/autorun' + +if ENV['GITHUB_ACTIONS'] == 'true' || ENV['CI'] + $VERBOSE = nil +end + +puts "info: sqlite3-ruby version: #{SQLite3::VERSION}/#{SQLite3::VersionProxy::STRING}" +puts "info: sqlite3 version: #{SQLite3::SQLITE_VERSION}/#{SQLite3::SQLITE_LOADED_VERSION}" +puts "info: sqlcipher?: #{SQLite3.sqlcipher?}" +puts "info: threadsafe?: #{SQLite3.threadsafe?}" + +unless RUBY_VERSION >= "1.9" + require 'iconv' +end + +module SQLite3 + class TestCase < Minitest::Test + alias :assert_not_equal :refute_equal + alias :assert_not_nil :refute_nil + alias :assert_raise :assert_raises + + def assert_nothing_raised + yield + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_backup.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_backup.rb new file mode 100644 index 0000000000..4e9570b91a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_backup.rb @@ -0,0 +1,33 @@ +require 'helper' + +module SQLite3 + class TestBackup < SQLite3::TestCase + def setup + @sdb = SQLite3::Database.new(':memory:') + @ddb = SQLite3::Database.new(':memory:') + @sdb.execute('CREATE TABLE foo (idx, val);'); + @data = ('A'..'Z').map{|x|x * 40} + @data.each_with_index do |v, i| + @sdb.execute('INSERT INTO foo (idx, val) VALUES (?, ?);', [i, v]) + end + end + + def test_backup_step + b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') + while b.step(1) == SQLite3::Constants::ErrorCode::OK + assert_not_equal(0, b.remaining) + end + assert_equal(0, b.remaining) + b.finish + assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) + end + + def test_backup_all + b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') + assert_equal(SQLite3::Constants::ErrorCode::DONE, b.step(-1)) + assert_equal(0, b.remaining) + b.finish + assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) + end + end if defined?(SQLite3::Backup) +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_collation.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_collation.rb new file mode 100644 index 0000000000..360335e703 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_collation.rb @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +require 'helper' + +module SQLite3 + class TestCollation < SQLite3::TestCase + class Comparator + attr_reader :calls + def initialize + @calls = [] + end + + def compare left, right + @calls << [left, right] + left <=> right + end + end + + def setup + @db = SQLite3::Database.new(':memory:') + @create = "create table ex(id int, data string)" + @db.execute(@create); + [ [1, 'hello'], [2, 'world'] ].each do |vals| + @db.execute('insert into ex (id, data) VALUES (?, ?)', vals) + end + end + + def test_custom_collation + comparator = Comparator.new + + @db.collation 'foo', comparator + + assert_equal comparator, @db.collations['foo'] + @db.execute('select data from ex order by 1 collate foo') + assert_equal 1, comparator.calls.length + end + + def test_remove_collation + comparator = Comparator.new + + @db.collation 'foo', comparator + @db.collation 'foo', nil + + assert_nil @db.collations['foo'] + assert_raises(SQLite3::SQLException) do + @db.execute('select data from ex order by 1 collate foo') + end + end + + if RUBY_VERSION >= '1.9.1' + def test_encoding + comparator = Comparator.new + @db.collation 'foo', comparator + @db.execute('select data from ex order by 1 collate foo') + + a, b = *comparator.calls.first + + assert_equal Encoding.find('UTF-8'), a.encoding + assert_equal Encoding.find('UTF-8'), b.encoding + end + + def test_encoding_default_internal + warn_before = $-w + $-w = false + before_enc = Encoding.default_internal + + Encoding.default_internal = 'EUC-JP' + comparator = Comparator.new + @db.collation 'foo', comparator + @db.execute('select data from ex order by 1 collate foo') + + a, b = *comparator.calls.first + + assert_equal Encoding.find('EUC-JP'), a.encoding + assert_equal Encoding.find('EUC-JP'), b.encoding + ensure + Encoding.default_internal = before_enc + $-w = warn_before + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database.rb new file mode 100644 index 0000000000..2ac409ff67 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database.rb @@ -0,0 +1,545 @@ +require 'helper' +require 'tempfile' +require 'pathname' + +module SQLite3 + class TestDatabase < SQLite3::TestCase + attr_reader :db + + def setup + @db = SQLite3::Database.new(':memory:') + super + end + + def test_segv + assert_raises { SQLite3::Database.new 1 } + end + + def test_db_filename + tf = nil + assert_equal '', @db.filename('main') + tf = Tempfile.new 'thing' + @db = SQLite3::Database.new tf.path + assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename('main')) + ensure + tf.unlink if tf + end + + def test_filename + tf = nil + assert_equal '', @db.filename + tf = Tempfile.new 'thing' + @db = SQLite3::Database.new tf.path + assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename) + ensure + tf.unlink if tf + end + + def test_filename_with_attachment + tf = nil + assert_equal '', @db.filename + tf = Tempfile.new 'thing' + @db.execute "ATTACH DATABASE '#{tf.path}' AS 'testing'" + + assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename('testing')) + ensure + tf.unlink if tf + end + + + def test_filename_to_path + tf = Tempfile.new 'thing' + pn = Pathname tf.path + db = SQLite3::Database.new pn + assert_equal pn.realdirpath.to_s, File.realdirpath(db.filename) + ensure + tf.close! if tf + end + + + def test_error_code + begin + db.execute 'SELECT' + rescue SQLite3::SQLException => e + end + assert_equal 1, e.code + end + + def test_extended_error_code + db.extended_result_codes = true + db.execute 'CREATE TABLE "employees" ("token" integer NOT NULL)' + begin + db.execute 'INSERT INTO employees (token) VALUES (NULL)' + rescue SQLite3::ConstraintException => e + end + assert_equal 1299, e.code + end + + def test_bignum + num = 4907021672125087844 + db.execute 'CREATE TABLE "employees" ("token" integer(8), "name" varchar(20) NOT NULL)' + db.execute "INSERT INTO employees(name, token) VALUES('employee-1', ?)", [num] + rows = db.execute 'select token from employees' + assert_equal num, rows.first.first + end + + def test_blob + @db.execute("CREATE TABLE blobs ( id INTEGER, hash BLOB(10) )") + blob = Blob.new("foo\0bar") + @db.execute("INSERT INTO blobs VALUES (0, ?)", [blob]) + assert_equal [[0, blob, blob.length, blob.length*2]], @db.execute("SELECT id, hash, length(hash), length(hex(hash)) FROM blobs") + end + + def test_get_first_row + assert_equal [1], @db.get_first_row('SELECT 1') + end + + def test_get_first_row_with_type_translation_and_hash_results + @db.results_as_hash = true + @db.type_translation = true + assert_equal({"1"=>1}, @db.get_first_row('SELECT 1')) + end + + def test_execute_with_type_translation_and_hash + @db.results_as_hash = true + @db.type_translation = true + rows = [] + @db.execute('SELECT 1') { |row| rows << row } + + assert_equal({"1"=>1}, rows.first) + end + + def test_encoding + assert @db.encoding, 'database has encoding' + end + + def test_changes + @db.execute("CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, number integer)") + assert_equal 0, @db.changes + @db.execute("INSERT INTO items (number) VALUES (10)") + assert_equal 1, @db.changes + @db.execute_batch( + "UPDATE items SET number = (number + :nn) WHERE (number = :n)", + {"nn" => 20, "n" => 10}) + assert_equal 1, @db.changes + assert_equal [[30]], @db.execute("select number from items") + end + + def test_batch_last_comment_is_processed + # FIXME: nil as a successful return value is kinda dumb + assert_nil @db.execute_batch <<-eosql + CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT); + -- omg + eosql + end + + def test_execute_batch2 + @db.results_as_hash = true + return_value = @db.execute_batch2 <<-eosql + CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, name string); + INSERT INTO items (name) VALUES ("foo"); + INSERT INTO items (name) VALUES ("bar"); + SELECT * FROM items; + eosql + assert_equal return_value, [{"id"=>"1","name"=>"foo"}, {"id"=>"2", "name"=>"bar"}] + + return_value = @db.execute_batch2('SELECT * FROM items;') do |result| + result["id"] = result["id"].to_i + result + end + assert_equal return_value, [{"id"=>1,"name"=>"foo"}, {"id"=>2, "name"=>"bar"}] + + return_value = @db.execute_batch2('INSERT INTO items (name) VALUES ("oof")') + assert_equal return_value, [] + + return_value = @db.execute_batch2( + 'CREATE TABLE employees (id integer PRIMARY KEY AUTOINCREMENT, name string, age integer(3)); + INSERT INTO employees (age) VALUES (30); + INSERT INTO employees (age) VALUES (40); + INSERT INTO employees (age) VALUES (20); + SELECT age FROM employees;') do |result| + result["age"] = result["age"].to_i + result + end + assert_equal return_value, [{"age"=>30}, {"age"=>40}, {"age"=>20}] + + return_value = @db.execute_batch2('SELECT name FROM employees'); + assert_equal return_value, [{"name"=>nil}, {"name"=>nil}, {"name"=>nil}] + + @db.results_as_hash = false + return_value = @db.execute_batch2( + 'CREATE TABLE managers (id integer PRIMARY KEY AUTOINCREMENT, age integer(3)); + INSERT INTO managers (age) VALUES (50); + INSERT INTO managers (age) VALUES (60); + SELECT id, age from managers;') do |result| + result = result.map do |res| + res.to_i + end + result + end + assert_equal return_value, [[1, 50], [2, 60]] + + assert_raises (RuntimeError) do + # "names" is not a valid column + @db.execute_batch2 'INSERT INTO items (names) VALUES ("bazz")' + end + + end + + def test_new + db = SQLite3::Database.new(':memory:') + assert db + end + + def test_new_yields_self + thing = nil + SQLite3::Database.new(':memory:') do |db| + thing = db + end + assert_instance_of(SQLite3::Database, thing) + end + + def test_new_with_options + # determine if Ruby is running on Big Endian platform + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + + if RUBY_VERSION >= "1.9" + db = SQLite3::Database.new(':memory:'.encode(utf16), :utf16 => true) + else + db = SQLite3::Database.new(Iconv.conv(utf16, 'UTF-8', ':memory:'), + :utf16 => true) + end + assert db + end + + def test_close + db = SQLite3::Database.new(':memory:') + db.close + assert db.closed? + end + + def test_block_closes_self + thing = nil + SQLite3::Database.new(':memory:') do |db| + thing = db + assert !thing.closed? + end + assert thing.closed? + end + + def test_block_closes_self_even_raised + thing = nil + begin + SQLite3::Database.new(':memory:') do |db| + thing = db + raise + end + rescue + end + assert thing.closed? + end + + def test_prepare + db = SQLite3::Database.new(':memory:') + stmt = db.prepare('select "hello world"') + assert_instance_of(SQLite3::Statement, stmt) + end + + def test_block_prepare_does_not_double_close + db = SQLite3::Database.new(':memory:') + r = db.prepare('select "hello world"') do |stmt| + stmt.close + :foo + end + assert_equal :foo, r + end + + def test_total_changes + db = SQLite3::Database.new(':memory:') + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + assert_equal 1, db.total_changes + end + + def test_execute_returns_list_of_hash + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + rows = db.execute("select * from foo") + assert_equal [{"a"=>1, "b"=>"hello"}], rows + end + + def test_execute_yields_hash + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + db.execute("insert into foo (b) values ('hello')") + db.execute("select * from foo") do |row| + assert_equal({"a"=>1, "b"=>"hello"}, row) + end + end + + def test_table_info + db = SQLite3::Database.new(':memory:', :results_as_hash => true) + db.execute("create table foo ( a integer primary key, b text )") + info = [{ + "name" => "a", + "pk" => 1, + "notnull" => 0, + "type" => "integer", + "dflt_value" => nil, + "cid" => 0 + }, + { + "name" => "b", + "pk" => 0, + "notnull" => 0, + "type" => "text", + "dflt_value" => nil, + "cid" => 1 + }] + assert_equal info, db.table_info('foo') + end + + def test_total_changes_closed + db = SQLite3::Database.new(':memory:') + db.close + assert_raise(SQLite3::Exception) do + db.total_changes + end + end + + def test_trace_requires_opendb + @db.close + assert_raise(SQLite3::Exception) do + @db.trace { |x| } + end + end + + def test_trace_with_block + result = nil + @db.trace { |sql| result = sql } + @db.execute "select 'foo'" + assert_equal "select 'foo'", result + end + + def test_trace_with_object + obj = Class.new { + attr_accessor :result + def call sql; @result = sql end + }.new + + @db.trace(obj) + @db.execute "select 'foo'" + assert_equal "select 'foo'", obj.result + end + + def test_trace_takes_nil + @db.trace(nil) + @db.execute "select 'foo'" + end + + def test_last_insert_row_id_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.last_insert_row_id + end + end + + def test_define_function + called_with = nil + @db.define_function("hello") do |value| + called_with = value + end + @db.execute("select hello(10)") + assert_equal 10, called_with + end + + def test_call_func_arg_type + called_with = nil + @db.define_function("hello") do |b, c, d| + called_with = [b, c, d] + nil + end + @db.execute("select hello(2.2, 'foo', NULL)") + + assert_in_delta(2.2, called_with[0], 0.0001) + assert_equal("foo", called_with[1]) + assert_nil(called_with[2]) + end + + def test_define_varargs + called_with = nil + @db.define_function("hello") do |*args| + called_with = args + nil + end + @db.execute("select hello(2.2, 'foo', NULL)") + + assert_in_delta(2.2, called_with[0], 0.0001) + assert_equal("foo", called_with[1]) + assert_nil(called_with[2]) + end + + def test_call_func_blob + called_with = nil + @db.define_function("hello") do |a, b| + called_with = [a, b, a.length] + nil + end + blob = Blob.new("a\0fine\0kettle\0of\0fish") + @db.execute("select hello(?, length(?))", [blob, blob]) + assert_equal [blob, blob.length, 21], called_with + end + + def test_function_return + @db.define_function("hello") { |a| 10 } + assert_equal [10], @db.execute("select hello('world')").first + end + + def test_function_return_types + [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| + @db.define_function("hello") { |a| thing } + assert_equal [thing], @db.execute("select hello('world')").first + end + end + + def test_function_gc_segfault + @db.create_function("bug", -1) { |func, *values| func.result = values.join } + # With a lot of data and a lot of threads, try to induce a GC segfault. + params = Array.new(127, "?" * 28000) + proc = Proc.new { + db.execute("select bug(#{Array.new(params.length, "?").join(",")})", params) + } + m = Mutex.new + 30.times.map { Thread.new { m.synchronize { proc.call } } }.each(&:join) + end + + def test_function_return_type_round_trip + [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| + @db.define_function("hello") { |a| a } + assert_equal [thing], @db.execute("select hello(hello(?))", [thing]).first + end + end + + def test_define_function_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.define_function('foo') { } + end + end + + def test_inerrupt_closed + @db.close + assert_raise(SQLite3::Exception) do + @db.interrupt + end + end + + def test_define_aggregate + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + + acc = Class.new { + attr_reader :sum + alias :finalize :sum + def initialize + @sum = 0 + end + + def step a + @sum += a + end + }.new + + @db.define_aggregator("accumulate", acc) + value = @db.get_first_value( "select accumulate(a) from foo" ) + assert_equal 6, value + end + + def test_authorizer_ok + @db.authorizer = Class.new { + def call action, a, b, c, d; true end + }.new + @db.prepare("select 'fooooo'") + + @db.authorizer = Class.new { + def call action, a, b, c, d; 0 end + }.new + @db.prepare("select 'fooooo'") + end + + def test_authorizer_ignore + @db.authorizer = Class.new { + def call action, a, b, c, d; nil end + }.new + stmt = @db.prepare("select 'fooooo'") + assert_nil stmt.step + end + + def test_authorizer_fail + @db.authorizer = Class.new { + def call action, a, b, c, d; false end + }.new + assert_raises(SQLite3::AuthorizationException) do + @db.prepare("select 'fooooo'") + end + end + + def test_remove_auth + @db.authorizer = Class.new { + def call action, a, b, c, d; false end + }.new + assert_raises(SQLite3::AuthorizationException) do + @db.prepare("select 'fooooo'") + end + + @db.authorizer = nil + @db.prepare("select 'fooooo'") + end + + def test_close_with_open_statements + @db.prepare("select 'foo'") + assert_raises(SQLite3::BusyException) do + @db.close + end + end + + def test_execute_with_empty_bind_params + assert_equal [['foo']], @db.execute("select 'foo'", []) + end + + def test_query_with_named_bind_params + assert_equal [['foo']], @db.query("select :n", {'n' => 'foo'}).to_a + end + + def test_execute_with_named_bind_params + assert_equal [['foo']], @db.execute("select :n", {'n' => 'foo'}) + end + + def test_strict_mode + unless Gem::Requirement.new(">= 3.29.0").satisfied_by?(Gem::Version.new(SQLite3::SQLITE_VERSION)) + skip("strict mode feature not available in #{SQLite3::SQLITE_VERSION}") + end + + db = SQLite3::Database.new(':memory:') + db.execute('create table numbers (val int);') + db.execute('create index index_numbers_nope ON numbers ("nope");') # nothing raised + + db = SQLite3::Database.new(':memory:', :strict => true) + db.execute('create table numbers (val int);') + error = assert_raises SQLite3::SQLException do + db.execute('create index index_numbers_nope ON numbers ("nope");') + end + assert_includes error.message, "no such column: nope" + end + + def test_load_extension_with_nonstring_argument + db = SQLite3::Database.new(':memory:') + skip("extensions are not enabled") unless db.respond_to?(:load_extension) + assert_raises(TypeError) { db.load_extension(1) } + assert_raises(TypeError) { db.load_extension(Pathname.new("foo.so")) } + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_flags.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_flags.rb new file mode 100644 index 0000000000..9a1205e2ce --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_flags.rb @@ -0,0 +1,95 @@ +require 'helper' + +module SQLite3 + class TestDatabaseFlags < SQLite3::TestCase + def setup + File.unlink 'test-flags.db' if File.exist?('test-flags.db') + @db = SQLite3::Database.new('test-flags.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-flags.db' if File.exist?('test-flags.db') + end + + def test_open_database_flags_constants + defined_to_date = [:READONLY, :READWRITE, :CREATE, :DELETEONCLOSE, + :EXCLUSIVE, :MAIN_DB, :TEMP_DB, :TRANSIENT_DB, + :MAIN_JOURNAL, :TEMP_JOURNAL, :SUBJOURNAL, + :MASTER_JOURNAL, :NOMUTEX, :FULLMUTEX] + if SQLite3::SQLITE_VERSION_NUMBER > 3007002 + defined_to_date += [:AUTOPROXY, :SHAREDCACHE, :PRIVATECACHE, :WAL] + end + if SQLite3::SQLITE_VERSION_NUMBER > 3007007 + defined_to_date += [:URI] + end + if SQLite3::SQLITE_VERSION_NUMBER > 3007013 + defined_to_date += [:MEMORY] + end + assert defined_to_date.sort == SQLite3::Constants::Open.constants.sort + end + + def test_open_database_flags_conflicts_with_readonly + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readonly => true) + end + end + + def test_open_database_flags_conflicts_with_readwrite + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readwrite => true) + end + end + + def test_open_database_readonly_flags + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) + assert @db.readonly? + end + + def test_open_database_readwrite_flags + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) + assert !@db.readonly? + end + + def test_open_database_readonly_flags_cant_open + File.unlink 'test-flags.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) + end + end + + def test_open_database_readwrite_flags_cant_open + File.unlink 'test-flags.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) + end + end + + def test_open_database_misuse_flags + assert_raise(SQLite3::MisuseException) do + flags = SQLite3::Constants::Open::READONLY | SQLite3::Constants::Open::READWRITE # <== incompatible flags + @db = SQLite3::Database.new('test-flags.db', :flags => flags) + end + end + + def test_open_database_create_flags + File.unlink 'test-flags.db' + flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE + @db = SQLite3::Database.new('test-flags.db', :flags => flags) do |db| + db.execute("CREATE TABLE foos (id integer)") + db.execute("INSERT INTO foos (id) VALUES (12)") + end + assert File.exist?('test-flags.db') + end + + def test_open_database_exotic_flags + flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE + exotic_flags = SQLite3::Constants::Open::NOMUTEX | SQLite3::Constants::Open::TEMP_DB + @db = SQLite3::Database.new('test-flags.db', :flags => flags | exotic_flags) + @db.execute("INSERT INTO foos (id) VALUES (12)") + assert @db.changes == 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readonly.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readonly.rb new file mode 100644 index 0000000000..def34b2235 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readonly.rb @@ -0,0 +1,36 @@ +require 'helper' + +module SQLite3 + class TestDatabaseReadonly < SQLite3::TestCase + def setup + File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') + @db = SQLite3::Database.new('test-readonly.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') + end + + def test_open_readonly_database + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + assert @db.readonly? + end + + def test_open_readonly_not_exists_database + File.unlink 'test-readonly.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + end + end + + def test_insert_readonly_database + @db = SQLite3::Database.new('test-readonly.db', :readonly => true) + assert_raise(SQLite3::ReadOnlyException) do + @db.execute("INSERT INTO foos (id) VALUES (12)") + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readwrite.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readwrite.rb new file mode 100644 index 0000000000..f84857385d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_database_readwrite.rb @@ -0,0 +1,41 @@ +require 'helper' + +module SQLite3 + class TestDatabaseReadwrite < SQLite3::TestCase + def setup + File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') + @db = SQLite3::Database.new('test-readwrite.db') + @db.execute("CREATE TABLE foos (id integer)") + @db.close + end + + def teardown + @db.close unless @db.closed? + File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') + end + + def test_open_readwrite_database + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) + assert !@db.readonly? + end + + def test_open_readwrite_readonly_database + assert_raise(RuntimeError) do + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true, :readonly => true) + end + end + + def test_open_readwrite_not_exists_database + File.unlink 'test-readwrite.db' + assert_raise(SQLite3::CantOpenException) do + @db = SQLite3::Database.new('test-readwrite.db', :readonly => true) + end + end + + def test_insert_readwrite_database + @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) + @db.execute("INSERT INTO foos (id) VALUES (12)") + assert @db.changes == 1 + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_deprecated.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_deprecated.rb new file mode 100644 index 0000000000..4fa1dc4058 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_deprecated.rb @@ -0,0 +1,44 @@ +require 'helper' + +module SQLite3 + class TestDeprecated < SQLite3::TestCase + attr_reader :db + + def setup + super + @warn_before = $-w + $-w = false + @db = SQLite3::Database.new(':memory:') + @db.execute 'CREATE TABLE test_table (name text, age int)' + end + + def teardown + super + $-w = @warn_before + end + + def test_query_with_many_bind_params_not_nil + assert_equal [[1, 2]], db.query('select ?, ?', 1, 2).to_a + end + + def test_execute_with_many_bind_params_not_nil + assert_equal [[1, 2]], @db.execute("select ?, ?", 1, 2).to_a + end + + def test_query_with_many_bind_params + assert_equal [[nil, 1]], @db.query("select ?, ?", nil, 1).to_a + end + + def test_query_with_nil_bind_params + assert_equal [['foo']], @db.query("select 'foo'", nil).to_a + end + + def test_execute_with_many_bind_params + assert_equal [[nil, 1]], @db.execute("select ?, ?", nil, 1) + end + + def test_execute_with_nil_bind_params + assert_equal [['foo']], @db.execute("select 'foo'", nil) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_encoding.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_encoding.rb new file mode 100644 index 0000000000..32bf616909 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_encoding.rb @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +require 'helper' + +module SQLite3 + class TestEncoding < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @create = "create table ex(id int, data string)" + @insert = "insert into ex(id, data) values (?, ?)" + @db.execute(@create); + end + + def test_select_encoding_on_utf_16 + str = "foo" + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + db = SQLite3::Database.new(':memory:'.encode(utf16)) + db.execute @create + db.execute "insert into ex (id, data) values (1, \"#{str}\")" + + stmt = db.prepare 'select * from ex where data = ?' + ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each do |enc| + stmt.bind_param 1, str.encode(enc) + assert_equal 1, stmt.to_a.length + stmt.reset! + end + end + + def test_insert_encoding + str = "foo" + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + db = SQLite3::Database.new(':memory:'.encode(utf16)) + db.execute @create + stmt = db.prepare @insert + + ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each_with_index do |enc,i| + stmt.bind_param 1, i + stmt.bind_param 2, str.encode(enc) + stmt.to_a + stmt.reset! + end + + db.execute('select data from ex').flatten.each do |s| + assert_equal str, s + end + end + + def test_default_internal_is_honored + warn_before = $-w + $-w = false + + before_enc = Encoding.default_internal + + str = "壁に耳あり、障子に目あり" + stmt = @db.prepare('insert into ex(data) values (?)') + stmt.bind_param 1, str + stmt.step + + Encoding.default_internal = 'EUC-JP' + string = @db.execute('select data from ex').first.first + + assert_equal Encoding.default_internal, string.encoding + assert_equal str.encode('EUC-JP'), string + assert_equal str, string.encode(str.encoding) + ensure + Encoding.default_internal = before_enc + $-w = warn_before + end + + def test_blob_is_binary + str = "猫舌" + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, SQLite3::Blob.new(str)) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('UTF-8') + end + + def test_blob_is_ascii8bit + str = "猫舌" + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, str.dup.force_encoding("ASCII-8BIT")) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('UTF-8') + end + + def test_blob_with_eucjp + str = "猫舌".encode("EUC-JP") + @db.execute('create table foo(data text)') + stmt = @db.prepare('insert into foo(data) values (?)') + stmt.bind_param(1, SQLite3::Blob.new(str)) + stmt.step + + string = @db.execute('select data from foo').first.first + assert_equal Encoding.find('ASCII-8BIT'), string.encoding + assert_equal str, string.force_encoding('EUC-JP') + end + + def test_db_with_eucjp + db = SQLite3::Database.new(':memory:'.encode('EUC-JP')) + assert_equal(Encoding.find('UTF-8'), db.encoding) + end + + def test_db_with_utf16 + utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" + + db = SQLite3::Database.new(':memory:'.encode(utf16)) + assert_equal(Encoding.find(utf16), db.encoding) + end + + def test_statement_eucjp + str = "猫舌" + @db.execute("insert into ex(data) values ('#{str}')".encode('EUC-JP')) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_statement_utf8 + str = "猫舌" + @db.execute("insert into ex(data) values ('#{str}')") + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_encoding + assert_equal Encoding.find("UTF-8"), @db.encoding + end + + def test_utf_8 + str = "猫舌" + @db.execute(@insert, [10, str]) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str, row.first.first + end + + def test_euc_jp + str = "猫舌".encode('EUC-JP') + @db.execute(@insert, [10, str]) + row = @db.execute("select data from ex") + assert_equal @db.encoding, row.first.first.encoding + assert_equal str.encode('UTF-8'), row.first.first + end + + end if RUBY_VERSION >= '1.9.1' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration.rb new file mode 100644 index 0000000000..d5ea3a533e --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration.rb @@ -0,0 +1,507 @@ +require 'helper' + +class TC_Database_Integration < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + end + + def teardown + @db.close + end + + def test_table_info_with_type_translation_active + assert_nothing_raised { @db.table_info("foo") } + end + + def test_table_info_with_defaults_for_version_3_3_8_and_higher + @db.transaction do + @db.execute "create table defaults_test ( a string default NULL, b string default 'Hello', c string default '--- []\n' )" + data = @db.table_info( "defaults_test" ) + assert_equal({"name" => "a", "type" => "string", "dflt_value" => nil, "notnull" => 0, "cid" => 0, "pk" => 0}, + data[0]) + assert_equal({"name" => "b", "type" => "string", "dflt_value" => "Hello", "notnull" => 0, "cid" => 1, "pk" => 0}, + data[1]) + assert_equal({"name" => "c", "type" => "string", "dflt_value" => "--- []\n", "notnull" => 0, "cid" => 2, "pk" => 0}, + data[2]) + end + end + + def test_table_info_without_defaults_for_version_3_3_8_and_higher + @db.transaction do + @db.execute "create table no_defaults_test ( a integer default 1, b integer )" + data = @db.table_info( "no_defaults_test" ) + assert_equal({"name" => "a", "type" => "integer", "dflt_value" => "1", "notnull" => 0, "cid" => 0, "pk" => 0}, + data[0]) + assert_equal({"name" => "b", "type" => "integer", "dflt_value" => nil, "notnull" => 0, "cid" => 1, "pk" => 0}, + data[1]) + end + end + + def test_complete_fail + assert !@db.complete?( "select * from foo" ) + end + def test_complete_success + assert @db.complete?( "select * from foo;" ) + end + + # FIXME: do people really need UTF16 sql statements? + #def test_complete_fail_utf16 + # assert !@db.complete?( "select * from foo".to_utf16(false), true ) + #end + + # FIXME: do people really need UTF16 sql statements? + #def test_complete_success_utf16 + # assert @db.complete?( "select * from foo;".to_utf16(true), true ) + #end + + def test_errmsg + assert_equal "not an error", @db.errmsg + end + + # FIXME: do people really need UTF16 error messages? + #def test_errmsg_utf16 + # msg = Iconv.conv('UTF-16', 'UTF-8', 'not an error') + # assert_equal msg, @db.errmsg(true) + #end + + def test_errcode + assert_equal 0, @db.errcode + end + + def test_trace + result = nil + @db.trace { |sql| result = sql } + @db.execute "select * from foo" + assert_equal "select * from foo", result + end + + def test_authorizer_okay + @db.authorizer { |type,a,b,c,d| 0 } + rows = @db.execute "select * from foo" + assert_equal 3, rows.length + end + + def test_authorizer_error + @db.authorizer { |type,a,b,c,d| 1 } + assert_raise( SQLite3::AuthorizationException ) do + @db.execute "select * from foo" + end + end + + def test_authorizer_silent + @db.authorizer { |type,a,b,c,d| 2 } + rows = @db.execute "select * from foo" + assert rows.empty? + end + + def test_prepare_invalid_syntax + assert_raise( SQLite3::SQLException ) do + @db.prepare "select from foo" + end + end + + def test_prepare_invalid_column + assert_raise( SQLite3::SQLException ) do + @db.prepare "select k from foo" + end + end + + def test_prepare_invalid_table + assert_raise( SQLite3::SQLException ) do + @db.prepare "select * from barf" + end + end + + def test_prepare_no_block + stmt = @db.prepare "select * from foo" + assert stmt.respond_to?(:execute) + stmt.close + end + + def test_prepare_with_block + called = false + @db.prepare "select * from foo" do |stmt| + called = true + assert stmt.respond_to?(:execute) + end + assert called + end + + def test_execute_no_block_no_bind_no_match + rows = @db.execute( "select * from foo where a > 100" ) + assert rows.empty? + end + + def test_execute_with_block_no_bind_no_match + called = false + @db.execute( "select * from foo where a > 100" ) do |row| + called = true + end + assert !called + end + + def test_execute_no_block_with_bind_no_match + rows = @db.execute( "select * from foo where a > ?", 100 ) + assert rows.empty? + end + + def test_execute_with_block_with_bind_no_match + called = false + @db.execute( "select * from foo where a > ?", 100 ) do |row| + called = true + end + assert !called + end + + def test_execute_no_block_no_bind_with_match + rows = @db.execute( "select * from foo where a = 1" ) + assert_equal 1, rows.length + end + + def test_execute_with_block_no_bind_with_match + called = 0 + @db.execute( "select * from foo where a = 1" ) do |row| + called += 1 + end + assert_equal 1, called + end + + def test_execute_no_block_with_bind_with_match + rows = @db.execute( "select * from foo where a = ?", 1 ) + assert_equal 1, rows.length + end + + def test_execute_with_block_with_bind_with_match + called = 0 + @db.execute( "select * from foo where a = ?", 1 ) do |row| + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_no_bind_no_match + columns, *rows = @db.execute2( "select * from foo where a > 100" ) + assert rows.empty? + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_no_bind_no_match + called = 0 + @db.execute2( "select * from foo where a > 100" ) do |row| + assert [ "a", "b" ], row unless called == 0 + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_with_bind_no_match + columns, *rows = @db.execute2( "select * from foo where a > ?", 100 ) + assert rows.empty? + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_with_bind_no_match + called = 0 + @db.execute2( "select * from foo where a > ?", 100 ) do |row| + assert_equal [ "a", "b" ], row unless called == 0 + called += 1 + end + assert_equal 1, called + end + + def test_execute2_no_block_no_bind_with_match + columns, *rows = @db.execute2( "select * from foo where a = 1" ) + assert_equal 1, rows.length + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_no_bind_with_match + called = 0 + @db.execute2( "select * from foo where a = 1" ) do |row| + assert_equal [ 1, "foo" ], row unless called == 0 + called += 1 + end + assert_equal 2, called + end + + def test_execute2_no_block_with_bind_with_match + columns, *rows = @db.execute2( "select * from foo where a = ?", 1 ) + assert_equal 1, rows.length + assert_equal [ "a", "b" ], columns + end + + def test_execute2_with_block_with_bind_with_match + called = 0 + @db.execute2( "select * from foo where a = ?", 1 ) do + called += 1 + end + assert_equal 2, called + end + + def test_execute_batch_empty + assert_nothing_raised { @db.execute_batch "" } + end + + def test_execute_batch_no_bind + @db.transaction do + @db.execute_batch <<-SQL + create table bar ( a, b, c ); + insert into bar values ( 'one', 2, 'three' ); + insert into bar values ( 'four', 5, 'six' ); + insert into bar values ( 'seven', 8, 'nine' ); + SQL + end + rows = @db.execute( "select * from bar" ) + assert_equal 3, rows.length + end + + def test_execute_batch_with_bind + @db.execute_batch( <<-SQL, [1] ) + create table bar ( a, b, c ); + insert into bar values ( 'one', 2, ? ); + insert into bar values ( 'four', 5, ? ); + insert into bar values ( 'seven', 8, ? ); + SQL + rows = @db.execute( "select * from bar" ).map { |a,b,c| c } + assert_equal [1, 1, 1], rows + end + + def test_query_no_block_no_bind_no_match + result = @db.query( "select * from foo where a > 100" ) + assert_nil result.next + result.close + end + + def test_query_with_block_no_bind_no_match + r = nil + @db.query( "select * from foo where a > 100" ) do |result| + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_with_bind_no_match + result = @db.query( "select * from foo where a > ?", 100 ) + assert_nil result.next + result.close + end + + def test_query_with_block_with_bind_no_match + r = nil + @db.query( "select * from foo where a > ?", 100 ) do |result| + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_no_bind_with_match + result = @db.query( "select * from foo where a = 1" ) + assert_not_nil result.next + assert_nil result.next + result.close + end + + def test_query_with_block_no_bind_with_match + r = nil + @db.query( "select * from foo where a = 1" ) do |result| + assert_not_nil result.next + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_query_no_block_with_bind_with_match + result = @db.query( "select * from foo where a = ?", 1 ) + assert_not_nil result.next + assert_nil result.next + result.close + end + + def test_query_with_block_with_bind_with_match + r = nil + @db.query( "select * from foo where a = ?", 1 ) do |result| + assert_not_nil result.next + assert_nil result.next + r = result + end + assert r.closed? + end + + def test_get_first_row_no_bind_no_match + result = @db.get_first_row( "select * from foo where a=100" ) + assert_nil result + end + + def test_get_first_row_no_bind_with_match + result = @db.get_first_row( "select * from foo where a=1" ) + assert_equal [ 1, "foo" ], result + end + + def test_get_first_row_with_bind_no_match + result = @db.get_first_row( "select * from foo where a=?", 100 ) + assert_nil result + end + + def test_get_first_row_with_bind_with_match + result = @db.get_first_row( "select * from foo where a=?", 1 ) + assert_equal [ 1, "foo" ], result + end + + def test_get_first_value_no_bind_no_match + result = @db.get_first_value( "select b, a from foo where a=100" ) + assert_nil result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=100" ) + assert_nil result + end + + def test_get_first_value_no_bind_with_match + result = @db.get_first_value( "select b, a from foo where a=1" ) + assert_equal "foo", result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=1" ) + assert_equal "foo", result + end + + def test_get_first_value_with_bind_no_match + result = @db.get_first_value( "select b, a from foo where a=?", 100 ) + assert_nil result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=?", 100 ) + assert_nil result + end + + def test_get_first_value_with_bind_with_match + result = @db.get_first_value( "select b, a from foo where a=?", 1 ) + assert_equal "foo", result + @db.results_as_hash = true + result = @db.get_first_value( "select b, a from foo where a=?", 1 ) + assert_equal "foo", result + end + + def test_last_insert_row_id + @db.execute "insert into foo ( b ) values ( 'test' )" + assert_equal 4, @db.last_insert_row_id + @db.execute "insert into foo ( b ) values ( 'again' )" + assert_equal 5, @db.last_insert_row_id + end + + def test_changes + @db.execute "insert into foo ( b ) values ( 'test' )" + assert_equal 1, @db.changes + @db.execute "delete from foo where 1=1" + assert_equal 4, @db.changes + end + + def test_total_changes + assert_equal 3, @db.total_changes + @db.execute "insert into foo ( b ) values ( 'test' )" + @db.execute "delete from foo where 1=1" + assert_equal 8, @db.total_changes + end + + def test_transaction_nest + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.transaction do + end + end + end + end + + def test_transaction_rollback + @db.transaction + @db.execute_batch <<-SQL + insert into foo (b) values ( 'test1' ); + insert into foo (b) values ( 'test2' ); + insert into foo (b) values ( 'test3' ); + insert into foo (b) values ( 'test4' ); + SQL + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + @db.rollback + assert_equal 3, @db.get_first_value("select count(*) from foo").to_i + end + + def test_transaction_commit + @db.transaction + @db.execute_batch <<-SQL + insert into foo (b) values ( 'test1' ); + insert into foo (b) values ( 'test2' ); + insert into foo (b) values ( 'test3' ); + insert into foo (b) values ( 'test4' ); + SQL + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + @db.commit + assert_equal 7, @db.get_first_value("select count(*) from foo").to_i + end + + def test_transaction_rollback_in_block + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.rollback + end + end + end + + def test_transaction_commit_in_block + assert_raise( SQLite3::SQLException ) do + @db.transaction do + @db.commit + end + end + end + + def test_transaction_active + assert !@db.transaction_active? + @db.transaction + assert @db.transaction_active? + @db.commit + assert !@db.transaction_active? + end + + def test_transaction_implicit_rollback + assert !@db.transaction_active? + @db.transaction + @db.execute('create table bar (x CHECK(1 = 0))') + assert @db.transaction_active? + assert_raises( SQLite3::ConstraintException ) do + @db.execute("insert or rollback into bar (x) VALUES ('x')") + end + assert !@db.transaction_active? + end + + def test_interrupt + @db.create_function( "abort", 1 ) do |func,x| + @db.interrupt + func.result = x + end + + assert_raise( SQLite3::InterruptException ) do + @db.execute "select abort(a) from foo" + end + end + + def test_create_function + @db.create_function( "munge", 1 ) do |func,x| + func.result = ">>>#{x}<<<" + end + + value = @db.get_first_value( "select munge(b) from foo where a=1" ) + assert_match( />>>.*<<= '1.9' + + busy = Mutex.new + busy.lock + handler_call_count = 0 + + t = Thread.new(busy) do |locker| + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + locker.lock + end + ensure + db2.close if db2 + end + end + + @db.busy_handler do |data,count| + handler_call_count += 1 + busy.unlock + true + end + + assert_nothing_raised do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + + t.join + + assert_equal 1, handler_call_count + end + + def test_busy_handler_impatient + busy = Mutex.new + busy.lock + handler_call_count = 0 + + t = Thread.new do + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + busy.lock + end + ensure + db2.close if db2 + end + end + sleep 1 + + @db.busy_handler do + handler_call_count += 1 + false + end + + assert_raise( SQLite3::BusyException ) do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + + busy.unlock + t.join + + assert_equal 1, handler_call_count + end + + def test_busy_timeout + @db.busy_timeout 1000 + busy = Mutex.new + busy.lock + + t = Thread.new do + begin + db2 = SQLite3::Database.open( "test.db" ) + db2.transaction( :exclusive ) do + busy.lock + end + ensure + db2.close if db2 + end + end + + sleep 1 + time = Benchmark.measure do + assert_raise( SQLite3::BusyException ) do + @db.execute "insert into foo (b) values ( 'from 2' )" + end + end + + busy.unlock + t.join + + assert time.real*1000 >= 1000 + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_resultset.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_resultset.rb new file mode 100644 index 0000000000..ad89c2e817 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_resultset.rb @@ -0,0 +1,142 @@ +require 'helper' + +class TC_ResultSet < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + @stmt = @db.prepare( "select * from foo where a in ( ?, ? )" ) + @result = @stmt.execute + end + + def teardown + @stmt.close + @db.close + end + + def test_reset_unused + assert_nothing_raised { @result.reset } + assert @result.to_a.empty? + end + + def test_reset_used + @result.to_a + assert_nothing_raised { @result.reset } + assert @result.to_a.empty? + end + + def test_reset_with_bind + @result.to_a + assert_nothing_raised { @result.reset( 1, 2 ) } + assert_equal 2, @result.to_a.length + end + + def test_eof_inner + @result.reset( 1 ) + assert !@result.eof? + end + + def test_eof_edge + @result.reset( 1 ) + @result.next # to first row + @result.next # to end of result set + assert @result.eof? + end + + def test_next_eof + @result.reset( 1 ) + assert_not_nil @result.next + assert_nil @result.next + end + + def test_next_no_type_translation_no_hash + @result.reset( 1 ) + assert_equal [ 1, "foo" ], @result.next + end + + def test_next_type_translation + @result.reset( 1 ) + assert_equal [ 1, "foo" ], @result.next + end + + def test_next_type_translation_with_untyped_column + @db.query( "select count(*) from foo" ) do |result| + assert_equal [3], result.next + end + end + + def test_type_translation_with_null_column + time = '1974-07-25 14:39:00' + + @db.execute "create table bar ( a integer, b time, c string )" + @db.execute "insert into bar (a, b, c) values (NULL, '#{time}', 'hello')" + @db.execute "insert into bar (a, b, c) values (1, NULL, 'hello')" + @db.execute "insert into bar (a, b, c) values (2, '#{time}', NULL)" + @db.query( "select * from bar" ) do |result| + assert_equal [nil, time, 'hello'], result.next + assert_equal [1, nil, 'hello'], result.next + assert_equal [2, time, nil], result.next + end + end + + def test_real_translation + @db.execute('create table foo_real(a real)') + @db.execute('insert into foo_real values (42)' ) + @db.query('select a, sum(a), typeof(a), typeof(sum(a)) from foo_real') do |result| + result = result.next + assert result[0].is_a?(Float) + assert result[1].is_a?(Float) + assert result[2].is_a?(String) + assert result[3].is_a?(String) + end + end + + def test_next_results_as_hash + @db.results_as_hash = true + @result.reset( 1 ) + hash = @result.next + assert_equal( { "a" => 1, "b" => "foo" }, + hash ) + assert_equal hash[0], 1 + assert_equal hash[1], "foo" + end + + def test_each + called = 0 + @result.reset( 1, 2 ) + @result.each { |row| called += 1 } + assert_equal 2, called + end + + def test_enumerable + @result.reset( 1, 2 ) + assert_equal 2, @result.to_a.length + end + + def test_types + assert_equal [ "integer", "text" ], @result.types + end + + def test_columns + assert_equal [ "a", "b" ], @result.columns + end + + def test_close + stmt = @db.prepare( "select * from foo" ) + result = stmt.execute + assert !result.closed? + result.close + assert result.closed? + assert stmt.closed? + assert_raise( SQLite3::Exception ) { result.reset } + assert_raise( SQLite3::Exception ) { result.next } + assert_raise( SQLite3::Exception ) { result.each } + assert_raise( SQLite3::Exception ) { result.close } + assert_raise( SQLite3::Exception ) { result.types } + assert_raise( SQLite3::Exception ) { result.columns } + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_statement.rb new file mode 100644 index 0000000000..20dd6fcc1d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_integration_statement.rb @@ -0,0 +1,194 @@ +require 'helper' + +class TC_Statement < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(":memory:") + @db.transaction do + @db.execute "create table foo ( a integer primary key, b text )" + @db.execute "insert into foo ( b ) values ( 'foo' )" + @db.execute "insert into foo ( b ) values ( 'bar' )" + @db.execute "insert into foo ( b ) values ( 'baz' )" + end + @stmt = @db.prepare( "select * from foo where a in ( ?, :named )" ) + end + + def teardown + @stmt.close + @db.close + end + + def test_remainder_empty + assert_equal "", @stmt.remainder + end + + def test_remainder_nonempty + called = false + @db.prepare( "select * from foo;\n blah" ) do |stmt| + called = true + assert_equal "\n blah", stmt.remainder + end + assert called + end + + def test_bind_params_empty + assert_nothing_raised { @stmt.bind_params } + assert @stmt.execute!.empty? + end + + def test_bind_params_array + @stmt.bind_params 1, 2 + assert_equal 2, @stmt.execute!.length + end + + def test_bind_params_hash + @stmt.bind_params ":named" => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_hash_without_colon + @stmt.bind_params "named" => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_hash_as_symbol + @stmt.bind_params :named => 2 + assert_equal 1, @stmt.execute!.length + end + + def test_bind_params_mixed + @stmt.bind_params( 1, ":named" => 2 ) + assert_equal 2, @stmt.execute!.length + end + + def test_bind_param_by_index + @stmt.bind_params( 1, 2 ) + assert_equal 2, @stmt.execute!.length + end + + def test_bind_param_by_name_bad + assert_raise( SQLite3::Exception ) { @stmt.bind_param( "@named", 2 ) } + end + + def test_bind_param_by_name_good + @stmt.bind_param( ":named", 2 ) + assert_equal 1, @stmt.execute!.length + end + + def test_bind_param_with_various_types + @db.transaction do + @db.execute "create table all_types ( a integer primary key, b float, c string, d integer )" + @db.execute "insert into all_types ( b, c, d ) values ( 1.4, 'hello', 68719476735 )" + end + + assert_equal 1, @db.execute( "select * from all_types where b = ?", 1.4 ).length + assert_equal 1, @db.execute( "select * from all_types where c = ?", 'hello').length + assert_equal 1, @db.execute( "select * from all_types where d = ?", 68719476735).length + end + + def test_execute_no_bind_no_block + assert_instance_of SQLite3::ResultSet, @stmt.execute + end + + def test_execute_with_bind_no_block + assert_instance_of SQLite3::ResultSet, @stmt.execute( 1, 2 ) + end + + def test_execute_no_bind_with_block + called = false + @stmt.execute { |row| called = true } + assert called + end + + def test_execute_with_bind_with_block + called = 0 + @stmt.execute( 1, 2 ) { |row| called += 1 } + assert_equal 1, called + end + + def test_reexecute + r = @stmt.execute( 1, 2 ) + assert_equal 2, r.to_a.length + assert_nothing_raised { r = @stmt.execute( 1, 2 ) } + assert_equal 2, r.to_a.length + end + + def test_execute_bang_no_bind_no_block + assert @stmt.execute!.empty? + end + + def test_execute_bang_with_bind_no_block + assert_equal 2, @stmt.execute!( 1, 2 ).length + end + + def test_execute_bang_no_bind_with_block + called = 0 + @stmt.execute! { |row| called += 1 } + assert_equal 0, called + end + + def test_execute_bang_with_bind_with_block + called = 0 + @stmt.execute!( 1, 2 ) { |row| called += 1 } + assert_equal 2, called + end + + def test_columns + c1 = @stmt.columns + c2 = @stmt.columns + assert_same c1, c2 + assert_equal 2, c1.length + end + + def test_columns_computed + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + assert_equal [ "count(*)" ], stmt.columns + end + assert called + end + + def test_types + t1 = @stmt.types + t2 = @stmt.types + assert_same t1, t2 + assert_equal 2, t1.length + end + + def test_types_computed + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + assert_equal [ nil ], stmt.types + end + assert called + end + + def test_close + stmt = @db.prepare( "select * from foo" ) + assert !stmt.closed? + stmt.close + assert stmt.closed? + assert_raise( SQLite3::Exception ) { stmt.execute } + assert_raise( SQLite3::Exception ) { stmt.execute! } + assert_raise( SQLite3::Exception ) { stmt.close } + assert_raise( SQLite3::Exception ) { stmt.bind_params 5 } + assert_raise( SQLite3::Exception ) { stmt.bind_param 1, 5 } + assert_raise( SQLite3::Exception ) { stmt.columns } + assert_raise( SQLite3::Exception ) { stmt.types } + end + + def test_committing_tx_with_statement_active + called = false + @db.prepare( "select count(*) from foo" ) do |stmt| + called = true + count = stmt.execute!.first.first.to_i + @db.transaction do + @db.execute "insert into foo ( b ) values ( 'hello' )" + end + new_count = stmt.execute!.first.first.to_i + assert_equal new_count, count+1 + end + assert called + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_pragmas.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_pragmas.rb new file mode 100644 index 0000000000..504e709153 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_pragmas.rb @@ -0,0 +1,22 @@ +require 'helper' + +module SQLite3 + class TestPragmas < SQLite3::TestCase + def setup + super + @db = SQLite3::Database.new(":memory:") + end + + def test_get_boolean_pragma + refute(@db.get_boolean_pragma("read_uncommitted")) + end + + def test_set_boolean_pragma + @db.set_boolean_pragma("read_uncommitted", 1) + + assert(@db.get_boolean_pragma("read_uncommitted")) + ensure + @db.set_boolean_pragma("read_uncommitted", 0) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_result_set.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_result_set.rb new file mode 100644 index 0000000000..fa3df51660 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_result_set.rb @@ -0,0 +1,37 @@ +require 'helper' + +module SQLite3 + class TestResultSet < SQLite3::TestCase + def test_each_hash + db = SQLite3::Database.new ':memory:' + db.execute "create table foo ( a integer primary key, b text )" + list = ('a'..'z').to_a + list.each do |t| + db.execute "insert into foo (b) values (\"#{t}\")" + end + + rs = db.prepare('select * from foo').execute + rs.each_hash do |hash| + assert_equal list[hash['a'] - 1], hash['b'] + end + end + + def test_next_hash + db = SQLite3::Database.new ':memory:' + db.execute "create table foo ( a integer primary key, b text )" + list = ('a'..'z').to_a + list.each do |t| + db.execute "insert into foo (b) values (\"#{t}\")" + end + + rs = db.prepare('select * from foo').execute + rows = [] + while row = rs.next_hash + rows << row + end + rows.each do |hash| + assert_equal list[hash['a'] - 1], hash['b'] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_sqlite3.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_sqlite3.rb new file mode 100644 index 0000000000..4bd1c5df06 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_sqlite3.rb @@ -0,0 +1,30 @@ +require 'helper' + +module SQLite3 + class TestSQLite3 < SQLite3::TestCase + def test_libversion + assert_not_nil SQLite3.libversion + end + + def test_threadsafe + assert_not_nil SQLite3.threadsafe + end + + def test_threadsafe? + if SQLite3.threadsafe > 0 + assert SQLite3.threadsafe? + else + refute SQLite3.threadsafe? + end + end + + def test_version_strings + skip if SQLite3::VERSION.include?("test") # see set-version-to-timestamp rake task + assert_equal(SQLite3::VERSION, SQLite3::VersionProxy::STRING) + end + + def test_compiled_version_and_loaded_version + assert_equal(SQLite3::SQLITE_VERSION, SQLite3::SQLITE_LOADED_VERSION) + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement.rb new file mode 100644 index 0000000000..734d1bddc8 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement.rb @@ -0,0 +1,263 @@ +require 'helper' + +module SQLite3 + class TestStatement < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @stmt = SQLite3::Statement.new(@db, "select 'foo'") + end + + def test_double_close_does_not_segv + @db.execute 'CREATE TABLE "things" ("number" float NOT NULL)' + + stmt = @db.prepare 'INSERT INTO things (number) VALUES (?)' + assert_raises(SQLite3::ConstraintException) { stmt.execute(nil) } + + stmt.close + + assert_raises(SQLite3::Exception) { stmt.close } + end + + def test_raises_type_error + assert_raises(TypeError) do + SQLite3::Statement.new( @db, nil ) + end + end + + def test_insert_duplicate_records + @db.execute 'CREATE TABLE "things" ("name" varchar(20) CONSTRAINT "index_things_on_name" UNIQUE)' + stmt = @db.prepare("INSERT INTO things(name) VALUES(?)") + stmt.execute('ruby') + + exception = assert_raises(SQLite3::ConstraintException) { stmt.execute('ruby') } + # SQLite 3.8.2 returns new error message: + # UNIQUE constraint failed: *table_name*.*column_name* + # Older versions of SQLite return: + # column *column_name* is not unique + assert_match(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/, exception.message) + end + + ### + # This method may not exist depending on how sqlite3 was compiled + def test_database_name + @db.execute('create table foo(text BLOB)') + @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) + stmt = @db.prepare('select text from foo') + if stmt.respond_to?(:database_name) + assert_equal 'main', stmt.database_name(0) + end + end + + def test_prepare_blob + @db.execute('create table foo(text BLOB)') + stmt = @db.prepare('insert into foo(text) values (?)') + stmt.bind_param(1, SQLite3::Blob.new('hello')) + stmt.step + stmt.close + end + + def test_select_blob + @db.execute('create table foo(text BLOB)') + @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) + assert_equal 'hello', @db.execute('select * from foo').first.first + end + + def test_new + assert @stmt + end + + def test_new_closed_handle + @db = SQLite3::Database.new(':memory:') + @db.close + assert_raises(ArgumentError) do + SQLite3::Statement.new(@db, 'select "foo"') + end + end + + def test_new_with_remainder + stmt = SQLite3::Statement.new(@db, "select 'foo';bar") + assert_equal 'bar', stmt.remainder + end + + def test_empty_remainder + assert_equal '', @stmt.remainder + end + + def test_close + @stmt.close + assert @stmt.closed? + end + + def test_double_close + @stmt.close + assert_raises(SQLite3::Exception) do + @stmt.close + end + end + + def test_bind_param_string + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, "hello") + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_bind_param_int + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 10) + result = nil + stmt.each { |x| result = x } + assert_equal [10], result + end + + def test_bind_nil + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, nil) + result = nil + stmt.each { |x| result = x } + assert_equal [nil], result + end + + def test_bind_blob + @db.execute('create table foo(text BLOB)') + stmt = SQLite3::Statement.new(@db, 'insert into foo(text) values (?)') + stmt.bind_param(1, SQLite3::Blob.new('hello')) + stmt.execute + row = @db.execute('select * from foo') + + assert_equal ['hello'], row.first + assert_equal ['blob'], row.first.types + end + + def test_bind_64 + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 2 ** 31) + result = nil + stmt.each { |x| result = x } + assert_equal [2 ** 31], result + end + + def test_bind_double + stmt = SQLite3::Statement.new(@db, "select ?") + stmt.bind_param(1, 2.2) + result = nil + stmt.each { |x| result = x } + assert_equal [2.2], result + end + + def test_named_bind + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param(':foo', 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_no_colon + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param('foo', 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_symbol + stmt = SQLite3::Statement.new(@db, "select :foo") + stmt.bind_param(:foo, 'hello') + result = nil + stmt.each { |x| result = x } + assert_equal ['hello'], result + end + + def test_named_bind_not_found + stmt = SQLite3::Statement.new(@db, "select :foo") + assert_raises(SQLite3::Exception) do + stmt.bind_param('bar', 'hello') + end + end + + def test_each + r = nil + @stmt.each do |row| + r = row + end + assert_equal(['foo'], r) + end + + def test_reset! + r = [] + @stmt.each { |row| r << row } + @stmt.reset! + @stmt.each { |row| r << row } + assert_equal [['foo'], ['foo']], r + end + + def test_step + r = @stmt.step + assert_equal ['foo'], r + end + + def test_step_twice + assert_not_nil @stmt.step + assert !@stmt.done? + assert_nil @stmt.step + assert @stmt.done? + + @stmt.reset! + assert !@stmt.done? + end + + def test_step_never_moves_past_done + 10.times { @stmt.step } + @stmt.done? + end + + def test_column_count + assert_equal 1, @stmt.column_count + end + + def test_column_name + assert_equal "'foo'", @stmt.column_name(0) + assert_nil @stmt.column_name(10) + end + + def test_bind_parameter_count + stmt = SQLite3::Statement.new(@db, "select ?, ?, ?") + assert_equal 3, stmt.bind_parameter_count + end + + def test_execute_with_varargs + stmt = @db.prepare('select ?, ?') + assert_equal [[nil, nil]], stmt.execute(nil, nil).to_a + end + + def test_execute_with_hash + stmt = @db.prepare('select :n, :h') + assert_equal [[10, nil]], stmt.execute('n' => 10, 'h' => nil).to_a + end + + def test_with_error + @db.execute('CREATE TABLE "employees" ("name" varchar(20) NOT NULL CONSTRAINT "index_employees_on_name" UNIQUE)') + stmt = @db.prepare("INSERT INTO Employees(name) VALUES(?)") + stmt.execute('employee-1') + stmt.execute('employee-1') rescue SQLite3::ConstraintException + stmt.reset! + assert stmt.execute('employee-2') + end + + def test_clear_bindings! + stmt = @db.prepare('select ?, ?') + stmt.bind_param 1, "foo" + stmt.bind_param 2, "bar" + + # We can't fetch bound parameters back out of sqlite3, so just call + # the clear_bindings! method and assert that nil is returned + stmt.clear_bindings! + + while x = stmt.step + assert_equal [nil, nil], x + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement_execute.rb b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement_execute.rb new file mode 100644 index 0000000000..63e022b2e5 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/sqlite3-1.6.1-x86_64-linux/test/test_statement_execute.rb @@ -0,0 +1,35 @@ +require 'helper' + +module SQLite3 + class TestStatementExecute < SQLite3::TestCase + def setup + @db = SQLite3::Database.new(':memory:') + @db.execute_batch( + "CREATE TABLE items (id integer PRIMARY KEY, number integer)") + end + + def test_execute_insert + ps = @db.prepare("INSERT INTO items (number) VALUES (:n)") + ps.execute('n'=>10) + assert_equal 1, @db.get_first_value("SELECT count(*) FROM items") + ps.close + end + + def test_execute_update + @db.execute("INSERT INTO items (number) VALUES (?)", [10]) + + ps = @db.prepare("UPDATE items SET number = :new WHERE number = :old") + ps.execute('old'=>10, 'new'=>20) + assert_equal 20, @db.get_first_value("SELECT number FROM items") + ps.close + end + + def test_execute_delete + @db.execute("INSERT INTO items (number) VALUES (?)", [20]) + ps = @db.prepare("DELETE FROM items WHERE number = :n") + ps.execute('n' => 20) + assert_equal 0, @db.get_first_value("SELECT count(*) FROM items") + ps.close + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/CHANGELOG b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/CHANGELOG new file mode 100644 index 0000000000..95c80111de --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/CHANGELOG @@ -0,0 +1,416 @@ +== 1.8.1 Infinite Smoothie + * Fix possible HTTP Response Splitting + +== 1.8.0 Possessed Pickle + * Many things + +== 1.7.2 Bachmanity + * Add config support for ssl_version and ssl_cipher_list [frameworked] + +== 1.7.1 Muffin Mode + * Ruby 2.4 support (Fixnum deprecation) [nimish-mehta] + * Allow ERB templates in config files [markets] + +== 1.7.0 Dunder Mifflin + * Rack 2 support + * Ensure Response body.close is called in the same thread + Fixes issues with ActiveRecord connection management [#307] + * Fix TCP/IP Backend reports incorrect port when asked to bind to 0 [meschbach] + * Work with ruby 2.3's --enable-frozen-string-literal [jeremyevans] + +== 1.6.4 Gob Bluth + * Increase REQUEST_PATH to 2048 symbols [X2rdas] + * Fix warning in logger [tenderlove] + * Add :timeout option for Rack::Server.new [sugitak] + * When restarting, exit on a next tick so we can send response back to a client [rsamoilov] + * Check for empty PID files [z1dane] + * Update Event Machine version to 1.0.4, Ruby 2.2.0 fix [freemanoid] + +== 1.6.3 Protein Powder + * Add HTTP 422 status code [rajcybage] + * Add warning about EM reactor still running when stopping. + * Remove version number from "Server" HTTP header. [benbasson] + * Adding `--ssl-disable-verify` to allow disabling of client cert requests when SSL enabled [brucek] + * Ensure Tempfiles created by a large request are closed and deleted. [Tonkpils] + +== 1.6.2 Doc Brown + * No longer replace response's body on HEAD request. Ensuring body.close will be called. + * Remove `---ssl-verify` option as EventMachine doesn't verify the certificate. + * Fix env['rack.peer_cert'] to return SSL certifcate. + +== 1.6.1 Death Proof + * Regression: Default logger to STDOUT when using outside of CLI. + * Regression: Downgrade Rack required version back to 1.0 to work w/ prior Rails versions. + +== 1.6.0 Greek Yogurt + * Accept absolute URL in request line, eg.: 'GET http://site.com/he/lo HTTP/1.1'. + * HEAD request no longer return a body in the response. + * No longer stop EventMachine's reactor loop unless it was started by Thin. + * Make request env keys upcasing locale-agnostic. + * Use Ruby's `Logger` for logging. [Akshay Moghe]. + The logger can now be set using `Thin::Logging.logger=`. + Tracing of request is handled by a second logger, `Thin::Logging.trace_logger=`. + * Add --threadpool-size option to configure EM's thread pool size (default: 20). + * Pipelining is no longer supported. + +== 1.5.1 Straight Razor + * Fix issue when running as another user/group without a PID file. + * Allow overriding Connection & Server response headers. + * Update vlad example [Mathieu Lemoine] + * Keep connections in a Hash to speedup deletion [slivu] + * Force kill using already known pid. Prevents "thin stop" from leaving a process that removed its + pid file, but is still running (e.g. hung on some at_exit callback) [Michal Kwiatkowski] + +== 1.5.0 Knife + * Fix compilation under Ubuntu 12.04 with -Werror=format-security option. + * Raise an error when no PID file. + * Prevent duplicate response headers. + * Make proper response on exception [MasterLambaster]. + * Automatically close idling pipeline connections on server stop [MasterLambaster]. + +== 1.4.1 Chromeo Fix + * Fix error when sending USR1 signal and no log file is supplied. + +== 1.4.0 Chromeo + * kill -USR1 $PID for log rotation [catwell]. + * Fix HUP signal being reseted after deamonization [atotic]. + * Fix error with nil addresses in Connection#socket_address. + +== 1.3.2 Low-bar Squat + * Remove mack and halcyon Rack adapters from automatic detection. + +== 1.3.1 Triple Espresso + * Fix service not working pre 1.9. + +== 1.3.0 Double Espresso + * BREAKING CHANGE: Thin no longer ships with fat Windows binaries. + From now on, to install on Windows, install https://github.com/oneclick/rubyinstaller/wiki/Development-Kit. + * BREAKING CHANGE: Remove automatic Content-Length setting. + It is now the responsibility of the app (or a middleware) to set the Content-Length. + * Log errors to STDERR [textgoeshere] + * Shut down gracefully when receiving SIGTERM [ddollar] + + Processes are allowed a chance to shut down gracefully when receiving + SIGTERM (http://en.wikipedia.org/wiki/SIGTERM). + + On Heroku, when shutting down a process, we send a SIGTERM followed 10 + seconds later with a SIGKILL, similar to the behavior of the init daemon + on most Unix systems. This patch will allow Heroku apps to shut down + gracefully when they need to be terminated / moved. + +== 1.2.11 Bat-Shit Crazy + * Fix pure Ruby gem to not include binary. + +== 1.2.10 I'm dumb (BAD RELEASE, DON'T USE) + * I really am (bad release fix) + +== 1.2.9 Black Keys Extra Plus Wow (BAD RELEASE, DON'T USE) + * Improve fat binary loading. + +== 1.2.8 Black Keys + * Allow the connection to remain open for 1xx statuses [timshadel] + + Both the 100 and 101 status codes require that the connection to the + server remain open. The 100 status code is used to tell the client that + the server is still receiving its request, and will continue to read + request input on the connection. The 101 status code is used to upgrade + the existing connection to another protocol, and specifically is NOT + used to upgrade a separate connection. Therefore, the connection must + remain open after this response in order to facilitate that. + + * Accept IE7 badly encoded URL (eg.: %uEEEE) + * Fix gemspec to work w/ Bundler [smparkes] + * Add SSL support [tmm1] + * Catch Errno::EPERM in Process.running? [Tony Kemp] + On some systems (e.g. OpenBSD) you receive an EPERM exception if + you try to Process.getpgid on a process you do not own (even if you + are root). But it does mean that the process exists, so return true. + * Fix Rails version check that select which Rack adapter to use. Was using CGI adapter in Rails 3. + * Ignore SIGHUP when no restart block is given + * Add SSL options to thin command line tool [goldmann] + + --ssl Enables SSL + --ssl-key-file PATH Path to private key + --ssl-cert-file PATH Path to certificate + --ssl-verify Enables SSL certificate verification + + * Expose peer SSL certificate in env (rack.peer_cert) [fd] + * Adjusting unix socket permissions to be more open [mbj] + +== 1.2.7 No Hup + * Support multiple Ruby version (fat binaries under windows) + * Do not trap unsupported HUP signal on Windows + +== 1.2.6 Crazy Delicious + * Make work with Rails 3 out-of-the-box. + * Auto-detect and load config.ru files on start. Makes Rails 3 work. + * Fix signals being ignored under 1.9 when daemonized. + +== 1.2.5 This Is Not A Web Server + * Add rolling restart support (--onebyone option) [sikachu] + * Force external_encoding of request's body to ASCII_8BIT [jeremyz] + * Ensure Rack base API is used in Rails adapter only if version >= 2.3.2 [#111 state:resolved] + +== 1.2.4 Flaming Astroboy + * Fix a few issues in thin to make it a better "gem citizen" [josh] + * Fix test for rack based Rails in adapter under Ruby >= 1.8.7 [#109 state:resolved] + * Fix Remote address spoofing vulnerability in Connection#remote_address [Alexey Borzenkov] + * Fix uninitialized constant ActionController::Dispatcher error with Rails 1.2.3 [Chris Anderton] [#103 state:resolved] + +== 1.2.2 I Find Your Lack of Sauce Disturbing release + * Fix force kill under 1.9 [Alexey Chebotar] + * Fix regression when --only option is used w/ --socket. + * Add process name 'tag' functionality. Easier to distinguish thin daemons + from eachother in process listing [ctcherry] + +== 1.2.1 Asynctilicious Ultra Supreme release + * Require Rack 1.0.0 + * Require EventMachine 0.12.6 + * Use Rails Rack based dispatcher when available + * Allow String for response body + * Require openssl before eventmachine to prevent crash in 1.9 + +== 1.2.0 Asynctilicious Supreme release + * Add support for Windows mingw Ruby distro [Juan C. Rodriguez] + * Add async response support, see example/async_*.ru [raggi] + +== 1.1.1 Super Disco Power Plus release + * Fix bug when running with only options [hasimo] + +== 1.1.0 Super Disco Power release + * Require EventMachine 0.12.4 + * Remove Thin handler, now part of Rack 0.9.1 + * Fix Rack protocol version to 0.1 in environment hash. + * Fix error when passing no_epoll option to a cluster. + * Omit parsing #defined strings [Jérémy Zurcher] + * Defaults SERVER_NAME to localhost like webrick does [#87 state:resolved] + * Namespace parser to prevent error when mongrel is required [cliffmoon] + * Set RACK_ENV based on environment option when loading rackup file [Curtis Summers] [#83 state:resolved] + * Fixes a warning RE relative_url_root when using a prefix with Rails 2.1.1 [seriph] [#85 state:resolved] + * --only can work as a sequence number (if < 80) or a port number (if >= 80) [jmay] [#81 state:resolved] + +== 1.0.0 That's What She Said release + * Fixed vlad.rake to allow TCP or socket [hellekin] + * Updated Mack adapter to handle both <0.8.0 and >0.8.0 [Mark Bates] + * rails rack adapter uses File.readable_real? so it recognizes ACL permissions [Ricardo Chimal] + * Log a warning if Rack application returns nil body [Michael S. Klishin] + * Handle nil and Time header values correctly [#76 state:resolved] [tmm1] + * Add Content-Length header to response automatically when possible [#74 state:resolved] [dkubb] + * Runner now remembers -r, -D and -V parameters so that clustered servers inherit those and + `restart` keep your parameters. + * Make Set-Cookie header, in Rails adapter, compatible with current Rack spec [Pedro Belo] + [#73, state:resolved] + * Add --no-epoll option to disable epoll usage on Linux [#61 state:resolved] + * Add --force (-f) option to force stopping of a daemonized server [#72 state:resolved] + * Update halycon adapter loader [mtodd] + +== 0.8.2 Double Margarita release + * Require EventMachine 0.12.0 + * [bug] Fix timeout handling when running command + * [bug] Fix hanging when restarting and no process is running in single server move, fixes #67 + * Added Mack adapter [markbates] + * Allow rackup .rb files by getting a conventionally named constant as the app [bmizerany] + +== 0.8.1 Rebel Porpoise release + * [bug] Rescue all types of errors when processing request, fixes #62 + * [bug] Use Swiftiply backend when -y option is specified, fixes #63 and #64 + * Allow passing port as a string in Server.new + * Define deferred?(env) in your Rack application to set if a request is handled in a + thread (return true) or not (return false). + +== 0.8.0 Dodgy Dentist release + * [bug] Fix server crash when header too large. + * Add --require (-r) option to require a library, before executing your script. + * Rename --rackup short option to -R, warn and load as rackup when file ends with .ru. + * List supported adapters in command usage. + * Add file adapter to built-in adapter, serve static files in current directory. + * Allow disabling signal handling in Server with :signals => false + * Make Server.new arguments more flexible, can now specify any of host, port, app or hash options. + * Add --backend option to specified which backend to use, closes #55 + * [bug] Serve static file only on GET and HEAD requests in Rails adapter, fixes #58 + * Add threaded option to run server in threaded mode, calling the application in a + thread allowing for concurrency in the Rack adapter, closes #46 + * Guess which adapter to use from directory (chdir option) + or use specified one in 'adapter' option, re #47. + +== 0.7.1 Fancy Pants release + * Clean stale PID files when starting as daemon, fixes #53 [Chu Yeow] + * Require EventMachine 0.11.0 for UNIX domain sockets. Until it's released, install from: + gem install eventmachine --source http://code.macournoyer.com + * Ruby 1.8.5 compatibility, closes #49 [Wincent Colaiuta] + * Move all EventMachine stuff out of Server, you can now create a Thin Backend that + does not depend on EventMachine. + * Rename Connector to Backend. Extend Thin::Backends::Base to implement your own. + * Fix high memory usage with big POST body, fixes #48 + +== 0.7.0 Spherical Cow release + * Add --max-persistent-conns option to sets the maximum number of persistent connections. + Set to 0 to disable Keep-Alive. + * INT signal now force stop and QUIT signal gracefully stops. + * Warn when descriptors table size can't be set as high as expected. + * Eval Rackup config file using top level bindings. + * Remove daemons gem dependency on Windows plateform, fixes #45. + * Change default timeout from 60 to 30 seconds. + * Add --max-conns option to sets the maximum number of file or socket descriptors that + your process may open, defaults to 1024. + * Tail logfile when stopping and restarting a demonized server, fixes #26. + * Wrap application in a Rack::CommonLogger adapter in debug mode. + * --debug (-D) option no longer set $DEBUG so logging will be less verbose + and Ruby won't be too strict, fixes #36. + * Deprecate Server#silent in favour of Logging.silent. + * Persistent connection (keep-alive) support. + * Fix -s option not being included in generated config file, fixes #37. + * Add Swiftiply support. Use w/ the --swiftiply (-y) option in the thin script, + closes #28 [Alex MacCaw] + +== 0.6.4 Sexy Lobster release + * Fix error when stopping server on UNIX domain socket, fixes #42 + * Rescue errors in Connection#get_peername more gracefully, setting REMOTE_ADDR to nil, fixes #43 + +== 0.6.3 Ninja Cookie release + * Add tasks for Vlad the Deployer in example/vlad.rake [cnantais] + * Add Ramaze Rackup config file in example dir [tmm1] + Use like this from you Ramaze app dir: + + thin start -r /path/to/thin/example/ramaze.ru + + * Add the --rackup option to load a Rack config file instead of the Rails adapter. + So you can use any framework with the thin script and start cluster and stuff like that. + A Rack config file is one that is usable through the rackup command and looks like this: + + use Rack::CommonLogger + run MyCrazyRackAdapter.new(:uterly, 'cool') + + Then use it with thin like this: + + thin start --rackup config.ru + + * thin config --chrdir ... -C thin/yml do not change current directory anymore, fixes #33. + * Add a better sample god config file in example/thin.god that loads all info from config + files in /etc/thin. Drop-in replacement for the thin runlevel service [Gump]. + * Add support for specifying a custom Connector to the server and add more doc about Server + configuration. + * Add a script to run thin as a runlevel service that can start at startup, closes #31 [Gump] + Setup the service like this: + + sudo thin install /etc/thin + + This will install the boot script under /etc/init.d/thin. Then copy your config files to + /etc/thin. Works only under Linux. + * Set process name to 'thin server (0.0.0.0:3000)' when running as a daemon, closes #32. + * Make sure chdir option from config file is used when present. + * Raise an error when starting a server as a daemon and pid file already exist, fixes #27. + +== 0.6.2 Rambo release + * Server now let current connections finish before stopping, fixes #18 + * Fix uploading hanging bug when body is moved to a tempfile, + also delete the tempfile properly upon completion, fixes #25 + * 'thin restart' now sends HUP signals rather then stopping & starting, closes #17 + * HUP signal now launches a new process with the same options. + * Add PID and more info from the last request to the Stats adapter + mostly taken from Rack::ShowException. + * pid and log files in cluster are no longer required to be relative to the + app directory (chdir option), fixes #24 + * Revert to using #each when building response headers under Ruby 1.8, + solves an issue w/ Camping adapter, fixes #22 + * Restructure thin script options in 3 sections: server, daemon and cluster + * Add --only (-o) option to control only one server of a cluster. + * Stylize stats page and make the url configurable from the thin script. + * Raise error if attempting to use unix sockets on windows. + * Add example config files for http://www.tildeslash.com/monit usage. + Include the example file using "include /path/to/thin/monit/file" in your monitrc file. + The group settings let you do this to manage your clusters: + + sudo monit -g blog restart all + + There are examples of thin listening on sockets and thin listening on unix sockets. + +== 0.6.1 Cheesecake release + * Remove socket file when server stops. + * Set back cluster to use 'thin' command to launch servers. + +== 0.6.0 Big Pony release + * Add support for connection through UNIX domain socket. + Use the --socket (-S) option w/ the thin script to configure the socket filename. + Nginx support binding to a UNIX socket like this: + + upstream backend { + server unix:/tmp/thin.0.sock; + server unix:/tmp/thin.1.sock; + server unix:/tmp/thin.2.sock; + } + + Start your servers like this: + + thin start -s3 -S/tmp/thin.sock + + * Remove Server#listen! method. Use Server#start instead. + * Server can now yield a Rack::Builder to allow building an app in one call: + + Server.start '0.0.0.0', 3000 do + use Rack::CommonLogger + use Rack::ShowExceptions + map "/lobster" do + use Rack::Lint + run Rack::Lobster.new + end + end + + * Add a very basic stats page through Stats adapter, load w/ --stats and browse to /stats. + * Add --trace (-V) option to trace request/response and get backtrace w/out all Ruby debug stuff. + * Add --config (-C) option to load options from a config file in thin script [Matt Todd]. + * Alter response headers to output directly to a string. + * Improve specs stability. + * Move request body to a Tempfile if too big (> 112 KB) + * Remove useless check for max header size in Request (already done in the parser) + +== 0.5.4 Flying Mustard release + * Don't read the full body, use direct streaming when sending response. + See: Response#each + As a result, the Content-Length can not be calculated anymore. + You have to do set this in your adapter. All frameworks do it anyway. + It improve memory usage and boost speed for low concurrency. + Thanks to Kent Sibilev and Ezra for their help on that one. + * Add 'Server' response header + * Fix --user and --group option not changing daemon process privileges + +== 0.5.3 Purple Yogurt release + * win32 pre-compiled gem now available + * change rake task configuration to allow win32 gem build + * Add prefix option to thin script to mount app under a given path. + +== 0.5.2 Cheezburger release + * Add cluster support through the -s option in the thin script, start 3 thins like this: + thin start -s3 -p3000 + 3 thin servers will be started on port 3000, 3001, 3002, also the port number will be + injected in the pid and log filenames. + * Fix IOError when writing to logger when starting server as a daemon. + * Really change directory when the -c option is specified. + * Add restart command to thin script. + * Fix typos in thin script usage message and expand chdir path. + * Rename thin script options to be the same as mongrel_rails script [thronedrk]: + -o --host => -a --address + --log-file => --log + --pid-file => --pid + --env => --environment + +== 0.5.1 LOLCAT release + * Add URL rewriting to Rails adapter so that page caching works and / fetches index.html if present. + * Fix bug in multiline response header parsing. + * Add specs for the Rails adapter. + * Fix Set-Cookie headers in Rails adapter to handle multiple values correctly. + * Fix Ruby 1.9 incompatibility in Response#headers= and Rakefile. + * Fix parser to be Ruby 1.9 compatible [Aman Gupta] + * Set gemspec to use EventMachine version 0.8.1 as it's the latest one having precompiled windows binaries. + [Francis Cianfrocca]. + * Add -D option to thin script to set debugging on. + * Output incoming data and response when debugging is on. + +== 0.5.0 + * Full rewrite to use EventMachine, Rack and Mongrel parser + +== 0.4.1 + * Fix Rails environment option not being used in thin script. + +== 0.4.0 + * First alphaish release as a gem. diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/README.md b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/README.md new file mode 100644 index 0000000000..d5c2acdf4f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/README.md @@ -0,0 +1,102 @@ +# Thin + +A small and fast Ruby web server + +## Installation + +``` +gem install thin +``` + +Or add `thin` to your `Gemfile`: + +```ruby +gem 'thin' +``` + +## Usage + +A +thin+ script offers an easy way to start your Rack application: + +``` +thin start +``` + +Browse the `example` directory for sample applications. + +## Usage with Rails Action Cable + +To use Thin with Action Cable, add the following to your `Gemfile`: + +```ruby +gem 'faye-websocket' +gem 'thin' # If not already done +``` + +Create a `config/initializers/thin_action_cable.rb`: + +```ruby +Rails.application.config.action_cable.use_faye = true +Faye::WebSocket.load_adapter 'thin' +``` + +### CLI + +Use a rackup (config.ru) file and bind to localhost port 8080: + +``` +thin -R config.ru -a 127.0.0.1 -p 8080 start +``` + +Store the server process ID, log to a file and daemonize: + +``` +thin -p 9292 -P tmp/pids/thin.pid -l logs/thin.log -d start +``` + +Thin is quite flexible in that many options can be specified at the command line (see `thin -h` for more). + +### Configuration files + +You can create a configuration file using `thin config -C config/thin.yml`. + +You can then use it with all commands, such as: `thin start -C config/thin.yml`. + +Here is an example config file: + +```yaml +--- +user: www-data +group: www-data +pid: tmp/pids/thin.pid +timeout: 30 +wait: 30 +log: log/thin.log +max_conns: 1024 +require: [] +environment: production +max_persistent_conns: 512 +servers: 1 +threaded: true +no-epoll: true +daemonize: true +socket: tmp/sockets/thin.sock +chdir: /path/to/your/apps/root +tag: a-name-to-show-up-in-ps aux +``` + +## License + +Ruby License, http://www.ruby-lang.org/en/LICENSE.txt. + +## Credits + +The parser was originally from Mongrel http://mongrel.rubyforge.org by Zed Shaw. +Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed under +the Ruby license and the GPL2. + +Thin is copyright Marc-Andre Cournoyer + +Get help at http://groups.google.com/group/thin-ruby/ +Report bugs at https://github.com/macournoyer/thin/issues +and major security issues directly to me at macournoyer@gmail.com. diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/Rakefile b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/Rakefile new file mode 100644 index 0000000000..c5f1f967fa --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/Rakefile @@ -0,0 +1,25 @@ +require 'rake' +require 'rake/clean' +load 'thin.gemspec' + +# Load tasks in tasks/ +Dir['tasks/**/*.rake'].each { |rake| load rake } + +task :default => :spec + +desc "Build gem packages" +task :build do + sh "gem build thin.gemspec" +end + +desc "Push gem packages" +task :push => :build do + sh "gem push thin-*.gem" +end + +task :install => :build do + sh "gem install thin-*.gem" +end + +desc "Release version #{Thin::VERSION::STRING}" +task :release => [:tag, :push] diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/bin/thin b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/bin/thin new file mode 100755 index 0000000000..860107d9ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/bin/thin @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# Thin command line interface script. +# Run thin -h to get more usage. + +require 'thin' +Thin::Runner.new(ARGV).run! diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/adapter.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/adapter.rb new file mode 100644 index 0000000000..77375fdf8d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/adapter.rb @@ -0,0 +1,32 @@ +# Run with: ruby adapter.rb +# Then browse to http://localhost:3000/test +# and http://localhost:3000/files/adapter.rb +require 'thin' + +class SimpleAdapter + def call(env) + body = ["hello!"] + [ + 200, + { 'Content-Type' => 'text/plain' }, + body + ] + end +end + +Thin::Server.start('0.0.0.0', 3000) do + use Rack::CommonLogger + map '/test' do + run SimpleAdapter.new + end + map '/files' do + run Rack::File.new('.') + end +end + +# You could also start the server like this: +# +# app = Rack::URLMap.new('/test' => SimpleAdapter.new, +# '/files' => Rack::File.new('.')) +# Thin::Server.start('0.0.0.0', 3000, app) +# diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_app.ru b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_app.ru new file mode 100755 index 0000000000..4e9be17bfb --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_app.ru @@ -0,0 +1,126 @@ +#!/usr/bin/env rackup -s thin +# +# async_app.ru +# raggi/thin +# +# A second demo app for async rack + thin app processing! +# Now using http status code 100 instead. +# +# Created by James Tucker on 2008-06-17. +# Copyright 2008 James Tucker . +# +#-- +# Benchmark Results: +# +# raggi@mbk:~$ ab -c 100 -n 500 http://127.0.0.1:3000/ +# This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0 +# Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +# Copyright 2006 The Apache Software Foundation, http://www.apache.org/ +# +# Benchmarking 127.0.0.1 (be patient) +# Completed 100 requests +# Completed 200 requests +# Completed 300 requests +# Completed 400 requests +# Finished 500 requests +# +# +# Server Software: thin +# Server Hostname: 127.0.0.1 +# Server Port: 3000 +# +# Document Path: / +# Document Length: 12 bytes +# +# Concurrency Level: 100 +# Time taken for tests: 5.263089 seconds +# Complete requests: 500 +# Failed requests: 0 +# Write errors: 0 +# Total transferred: 47000 bytes +# HTML transferred: 6000 bytes +# Requests per second: 95.00 [#/sec] (mean) +# Time per request: 1052.618 [ms] (mean) +# Time per request: 10.526 [ms] (mean, across all concurrent requests) +# Transfer rate: 8.55 [Kbytes/sec] received +# +# Connection Times (ms) +# min mean[+/-sd] median max +# Connect: 0 3 2.2 3 8 +# Processing: 1042 1046 3.1 1046 1053 +# Waiting: 1037 1042 3.6 1041 1050 +# Total: 1045 1049 3.1 1049 1057 +# +# Percentage of the requests served within a certain time (ms) +# 50% 1049 +# 66% 1051 +# 75% 1053 +# 80% 1053 +# 90% 1054 +# 95% 1054 +# 98% 1056 +# 99% 1057 +# 100% 1057 (longest request) + +class DeferrableBody + include EventMachine::Deferrable + + def call(body) + body.each do |chunk| + @body_callback.call(chunk) + end + end + + def each &blk + @body_callback = blk + end + +end + +class AsyncApp + + # This is a template async response. N.B. Can't use string for body on 1.9 + AsyncResponse = [-1, {}, []].freeze + + def call(env) + + body = DeferrableBody.new + + # Get the headers out there asap, let the client know we're alive... + EventMachine::next_tick { env['async.callback'].call [200, {'Content-Type' => 'text/plain'}, body] } + + # Semi-emulate a long db request, instead of a timer, in reality we'd be + # waiting for the response data. Whilst this happens, other connections + # can be serviced. + # This could be any callback based thing though, a deferrable waiting on + # IO data, a db request, an http request, an smtp send, whatever. + EventMachine::add_timer(1) { + body.call ["Woah, async!\n"] + + EventMachine::next_tick { + # This could actually happen any time, you could spawn off to new + # threads, pause as a good looking lady walks by, whatever. + # Just shows off how we can defer chunks of data in the body, you can + # even call this many times. + body.call ["Cheers then!"] + body.succeed + } + } + + # throw :async # Still works for supporting non-async frameworks... + + AsyncResponse # May end up in Rack :-) + end + +end + +# The additions to env for async.connection and async.callback absolutely +# destroy the speed of the request if Lint is doing it's checks on env. +# It is also important to note that an async response will not pass through +# any further middleware, as the async response notification has been passed +# right up to the webserver, and the callback goes directly there too. +# Middleware could possibly catch :async, and also provide a different +# async.connection and async.callback. + +# use Rack::Lint +run AsyncApp.new diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_chat.ru b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_chat.ru new file mode 100755 index 0000000000..e9e3ddf83a --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_chat.ru @@ -0,0 +1,247 @@ +#!/usr/bin/env rackup -s thin +# +# async_chat.ru +# raggi/thin +# +# Created by James Tucker on 2008-06-19. +# Copyright 2008 James Tucker . + +# Uncomment if appropriate for you.. +EM.epoll +# EM.kqueue # bug on OS X in 0.12? + +class DeferrableBody + include EventMachine::Deferrable + + def initialize + @queue = [] + end + + def schedule_dequeue + return unless @body_callback + EventMachine::next_tick do + next unless body = @queue.shift + body.each do |chunk| + @body_callback.call(chunk) + end + schedule_dequeue unless @queue.empty? + end + end + + def call(body) + @queue << body + schedule_dequeue + end + + def each &blk + @body_callback = blk + schedule_dequeue + end + +end + +class Chat + + module UserBody + attr_accessor :username + end + + def initialize + @users = {} + end + + def render_page + [] << <<-EOPAGE + + + + + + + Async Chat + + + +
    + Your first message will become your nickname! + Users: #{@users.map{|k,u|u.username}.join(', ')} +
    +
    + +
    + + + + EOPAGE + end + + def register_user(user_id, renderer) + body = create_user(user_id) + body.call render_page + body.errback { delete_user user_id } + body.callback { delete_user user_id } + + EventMachine::next_tick do + renderer.call [200, {'Content-Type' => 'text/html'}, body] + end + end + + def new_message(user_id, message) + return unless @users[user_id] + if @users[user_id].username == :anonymous + username = unique_username(message) + log "User: #{user_id} is #{username}" + @users[user_id].username = message + message = "-> #{username} signed on." + end + username ||= @users[user_id].username + log "User: #{username} sent: #{message}" + @users.each do |id, body| + EventMachine::next_tick { body.call [js_message(username, message)] } + end + end + + private + def unique_username(name) + name.concat('_') while @users.any? { |id,u| name == u.username } + name + end + + def log(str) + print str, "\n" + end + + def add_user(id, body) + @users[id] = body + end + + def delete_user(id) + message = "User: #{id} - #{@users[id].username if @users[id]} disconnected." + log message + new_message(id, message) + @users.delete id + end + + def js_message(username, message) + %() + end + + def create_user(id) + message = "User: #{id} connected." + log message + new_message(id, message) + body = DeferrableBody.new + body.extend UserBody + body.username = :anonymous + add_user(id, body) + body + end + +end + +class AsyncChat + + AsyncResponse = [-1, {}, []].freeze + AjaxResponse = [200, {}, []].freeze + + def initialize + @chat = Chat.new + end + + def call(env) + request = Rack::Request.new(env) + # TODO - cookie me, baby + user_id = request.env['REMOTE_ADDR'] + if request.xhr? + message = request['message'] + @chat.new_message(user_id, Rack::Utils.escape_html(message)) + AjaxResponse + else + renderer = request.env['async.callback'] + @chat.register_user(user_id, renderer) + AsyncResponse + end + end + +end + +run AsyncChat.new diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_tailer.ru b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_tailer.ru new file mode 100755 index 0000000000..467f0df610 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/async_tailer.ru @@ -0,0 +1,100 @@ +#!/usr/bin/env rackup -s thin +# +# async_tailer.ru +# raggi/thin +# +# Tested with 150 spawned tails on OS X +# +# Created by James Tucker on 2008-06-18. +# Copyright 2008 James Tucker . + +# Uncomment if appropriate for you.. +# EM.epoll +# EM.kqueue + +class DeferrableBody + include EventMachine::Deferrable + + def initialize + @queue = [] + # make sure to flush out the queue before closing the connection + callback{ + until @queue.empty? + @queue.shift.each{|chunk| @body_callback.call(chunk) } + end + } + end + + def schedule_dequeue + return unless @body_callback + EventMachine::next_tick do + next unless body = @queue.shift + body.each do |chunk| + @body_callback.call(chunk) + end + schedule_dequeue unless @queue.empty? + end + end + + def call(body) + @queue << body + schedule_dequeue + end + + def each &blk + @body_callback = blk + schedule_dequeue + end + +end + +module TailRenderer + attr_accessor :callback + + def receive_data(data) + @callback.call([data]) + end + + def unbind + @callback.succeed + end +end + +class AsyncTailer + + AsyncResponse = [-1, {}, []].freeze + + def call(env) + + body = DeferrableBody.new + + EventMachine::next_tick do + + env['async.callback'].call [200, {'Content-Type' => 'text/html'}, body] + + body.call ["

    Async Tailer

    "]
    +      
    +    end
    +    
    +    EventMachine::popen('tail -f /var/log/system.log', TailRenderer) do |t|
    +      
    +      t.callback = body
    +      
    +      # If for some reason we 'complete' body, close the tail.
    +      body.callback do
    +        t.close_connection
    +      end
    +      
    +      # If for some reason the client disconnects, close the tail.
    +      body.errback do
    +        t.close_connection
    +      end
    +      
    +    end
    +    
    +    AsyncResponse
    +  end
    +  
    +end
    +
    +run AsyncTailer.new
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/config.ru b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/config.ru
    new file mode 100644
    index 0000000000..bc655f85d8
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/config.ru
    @@ -0,0 +1,22 @@
    +# Run with: rackup -s thin
    +# then browse to http://localhost:9292
    +# Or with: thin start -R config.ru
    +# then browse to http://localhost:3000
    +# 
    +# Check Rack::Builder doc for more details on this file format:
    +#  http://rack.rubyforge.org/doc/classes/Rack/Builder.html
    +require 'thin'
    +
    +app = proc do |env|
    +  # Response body has to respond to each and yield strings
    +  # See Rack specs for more info: http://rack.rubyforge.org/doc/files/SPEC.html
    +  body = ['hi!']
    +  
    +  [
    +    200,                                        # Status code
    +    { 'Content-Type' => 'text/html' },          # Reponse headers
    +    body                                        # Body of the response
    +  ]
    +end
    +
    +run app
    \ No newline at end of file
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_sockets b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_sockets
    new file mode 100644
    index 0000000000..15e9c436f7
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_sockets
    @@ -0,0 +1,20 @@
    +check process blog1
    +    with pidfile /u/apps/blog/shared/pids/thin.14000.pid
    +    start program = "ruby thin start -d -e production -u nobody -g nobody -p 14000 -a 127.0.0.1 -P tmp/pids/thin.14000.pid -c /u/apps/blog/current"
    +    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.14000.pid"
    +    if totalmem > 90.0 MB for 5 cycles then restart
    +    if failed port 14000 then restart
    +    if cpu usage > 95% for 3 cycles then restart
    +    if 5 restarts within 5 cycles then timeout
    +		group blog
    +
    +check process blog2
    +    with pidfile /u/apps/blog/shared/pids/thin.14001.pid
    +    start program = "ruby thin start -d -e production -u nobody -g nobody -p 14001 -a 127.0.0.1 -P tmp/pids/thin.14001.pid -c /u/apps/blog/current"
    +    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.14001.pid"
    +    if totalmem > 90.0 MB for 5 cycles then restart
    +    if failed port 14001 then restart
    +    if cpu usage > 95% for 3 cycles then restart
    +    if 5 restarts within 5 cycles then timeout
    +		group blog
    +
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_unixsock b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_unixsock
    new file mode 100644
    index 0000000000..e5e0e33a86
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/monit_unixsock
    @@ -0,0 +1,20 @@
    +check process blog1
    +    with pidfile /u/apps/blog/shared/pids/thin.1.pid
    +    start program = "ruby thin start -d -e production -S /u/apps/blog/shared/pids/thin.1.sock -P tmp/pids/thin.1.pid -c /u/apps/blog/current"
    +    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.1.pid"
    +    if totalmem > 90.0 MB for 5 cycles then restart
    +		if failed unixsocket /u/apps/blog/shared/pids/thin.1.sock then restart
    +    if cpu usage > 95% for 3 cycles then restart
    +    if 5 restarts within 5 cycles then timeout
    +		group blog
    +
    +check process blog2
    +    with pidfile /u/apps/blog/shared/pids/thin.2.pid
    +    start program = "ruby thin start -d -e production -S /u/apps/blog/shared/pids/thin.2.sock -P tmp/pids/thin.2.pid -c /u/apps/blog/current"
    +    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.2.pid"
    +    if totalmem > 90.0 MB for 5 cycles then restart
    +		if failed unixsocket /u/apps/blog/shared/pids/thin.2.sock then restart
    +    if cpu usage > 95% for 3 cycles then restart
    +    if 5 restarts within 5 cycles then timeout
    +		group blog
    +
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/myapp.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/myapp.rb
    new file mode 100644
    index 0000000000..02e617f343
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/myapp.rb
    @@ -0,0 +1 @@
    +Myapp = lambda { |env| [200, {}, 'this is my app!'] }
    \ No newline at end of file
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/ramaze.ru b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/ramaze.ru
    new file mode 100644
    index 0000000000..2a19ffbc83
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/ramaze.ru
    @@ -0,0 +1,12 @@
    +# Ramaze Rackup config file.
    +# by tmm1
    +# Use with --rackup option:
    +# 
    +#   thin start -r ramaze.ru
    +# 
    +require 'start'
    +
    +Ramaze.trait[:essentials].delete Ramaze::Adapter
    +Ramaze.start :force => true
    +
    +run Ramaze::Adapter::Base
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin.god b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin.god
    new file mode 100644
    index 0000000000..4bbb6657fb
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin.god
    @@ -0,0 +1,80 @@
    +# == God config file
    +# http://god.rubyforge.org/
    +# Authors: Gump and michael@glauche.de
    +#
    +# Config file for god that configures watches for each instance of a thin server for
    +# each thin configuration file found in /etc/thin.
    +# In order to get it working on Ubuntu, I had to make a change to god as noted at
    +# the following blog:
    +# http://blog.alexgirard.com/ruby-one-line-to-save-god/
    +#
    +require 'yaml'
    +
    +config_path = "/etc/thin"
    +
    +Dir[config_path + "/*.yml"].each do |file|
    +  config = YAML.load_file(file)
    +  num_servers = config["servers"] ||= 1
    +
    +  (0...num_servers).each do |i|
    +    # UNIX socket cluster use number 0 to 2 (for 3 servers)
    +    # and tcp cluster use port number 3000 to 3002.
    +    number = config['socket'] ? i : (config['port'] + i)
    +    
    +    God.watch do |w|
    +      w.group = "thin-" + File.basename(file, ".yml")
    +      w.name = w.group + "-#{number}"
    +      
    +      w.interval = 30.seconds
    +      
    +      w.uid = config["user"]
    +      w.gid = config["group"]
    +      
    +      w.start = "thin start -C #{file} -o #{number}"
    +      w.start_grace = 10.seconds
    +      
    +      w.stop = "thin stop -C #{file} -o #{number}"
    +      w.stop_grace = 10.seconds
    +      
    +      w.restart = "thin restart -C #{file} -o #{number}"
    +
    +      pid_path = config["chdir"] + "/" + config["pid"]
    +      ext = File.extname(pid_path)
    +
    +      w.pid_file = pid_path.gsub(/#{ext}$/, ".#{number}#{ext}")
    +      
    +      w.behavior(:clean_pid_file)
    +
    +      w.start_if do |start|
    +        start.condition(:process_running) do |c|
    +          c.interval = 5.seconds
    +          c.running = false
    +        end
    +      end
    +
    +      w.restart_if do |restart|
    +        restart.condition(:memory_usage) do |c|
    +          c.above = 150.megabytes
    +          c.times = [3,5] # 3 out of 5 intervals
    +        end
    +
    +        restart.condition(:cpu_usage) do |c|
    +          c.above = 50.percent
    +          c.times = 5
    +        end
    +      end
    +
    +      w.lifecycle do |on|
    +        on.condition(:flapping) do |c|
    +          c.to_state = [:start, :restart]
    +          c.times = 5
    +          c.within = 5.minutes
    +          c.transition = :unmonitored
    +          c.retry_in = 10.minutes
    +          c.retry_times = 5
    +          c.retry_within = 2.hours
    +        end
    +      end
    +    end
    +  end
    +end
    \ No newline at end of file
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.erb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.erb
    new file mode 100644
    index 0000000000..c96ac88e26
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.erb
    @@ -0,0 +1,36 @@
    +
    +
    +
    +  
    +    
    +    
    +      
    +    
    +    
    +      
    +      
    +      
    +    
    +    <% 0.upto(thin_max_instances - 1) do |instance| %>
    +    
    +    
    +      
    +      
    +        
    +      
    +      
    +        
    +          
    +          
    +            
    +          
    +        
    +      
    +      
    +    
    +    <% end %>
    +  
    +
    +
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.readme.txt b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.readme.txt
    new file mode 100644
    index 0000000000..aea4e9386f
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/thin_solaris_smf.readme.txt
    @@ -0,0 +1,150 @@
    +Using Thin with Solaris' SMF Monitoring Framework
    +- - - - - - - - - - - - - - - - - - - - - - - - -
    +
    +Solaris uses the Service Management Framework (SMF) at the OS level to manage, monitor, and restart long running processes.  This replaces init scripts, and tools like monit and god.
    +
    +The sample XML file (thin_solaris_smf.erb) is an example SMF manifest which I use on a Joyent accelerator which runs on OpenSolaris.
    +
    +This setup will:
    +
    +- ensure the right dependencies are loaded
    +- start n instances of Thin, and monitor each individually.  If any single one dies it will be restarted instantly (test it by killing a single thin instance and it will be back alive before you can type 'ps -ef').
    +
    +This is better than using clustering since if you start the cluster with SMF it will only notice a problem and restart individual Thin's if ALL of them are dead, at which point it will restart the whole cluster.  This approach makes sure that all of your Thins start together and are monitored and managed independant of each other.  This problem likely exists if you are using god or monit to monitor only the start of the master cluster, and don't then monitor the individual processes started.
    +
    +This example is in .erb format instead of plain XML since I dynamically generate this file as part of a Capistrano deployment.  In my deploy.rb file I define the variables found in this erb.  Of course you don't need to use this with Capistrano.  Just replace the few ERB variables from the xml file, change its extension, and load that directly in Solaris if you prefer.
    +
    +Here are some examples for usage to get you started with Capistrano, and Thin:
    +
    +FILE : config/deploy.rb
    +--
    +
    +  require 'config/accelerator/accelerator_tasks'
    +
    +  set :application, "yourapp"
    +  set :svcadm_bin, "/usr/sbin/svcadm"
    +  set :svccfg_bin, "/usr/sbin/svccfg"
    +  set :svcs_bin, "/usr/bin/svcs"
    +
    +  # gets the list of remote service SMF names that we need to start
    +  # like (depending on thin_max_instances settings):
    +  # svc:/network/thin/yourapp-production:i_0
    +  # svc:/network/thin/yourapp-production:i_1
    +  # svc:/network/thin/yourapp-production:i_2
    +  set :service_list, "`svcs -H -o FMRI svc:network/thin/#{application}-production`"
    +
    +  # how many Thin instances should be setup to run?
    +  # this affects the generated thin smf file, and the nginx vhost conf
    +  # need to re-run setup for thin smf and nginx vhost conf when changed
    +  set :thin_max_instances, 3
    +
    +  # OVERRIDE STANDARD TASKS
    +  desc "Restart the entire application"
    +  deploy.task :restart do
    +    accelerator.thin.restart
    +    accelerator.nginx.restart
    +  end
    +
    +  desc "Start the entire application"
    +  deploy.task :start do
    +    accelerator.thin.restart
    +    accelerator.nginx.restart
    +  end
    +
    +  desc "Stop the entire application"
    +  deploy.task :stop do
    +    accelerator.thin.disable
    +    accelerator.nginx.disable
    +  end
    +
    +
    +FILE : config/accelerator/accelerator_tasks.rb
    +--
    +
    +    desc "Create and deploy Thin SMF config"
    +    task :create_thin_smf, :roles => :app do
    +      service_name = application
    +      working_directory = current_path
    +      template = File.read("config/accelerator/thin_solaris_smf.erb")
    +      buffer = ERB.new(template).result(binding)
    +      put buffer, "#{shared_path}/#{application}-thin-smf.xml"
    +      sudo "#{svccfg_bin} import #{shared_path}/#{application}-thin-smf.xml"
    +    end
    +
    +    desc "Delete Thin SMF config"
    +    task :delete_thin_smf, :roles => :app do
    +      accelerator.thin.disable
    +      sudo "#{svccfg_bin} delete /network/thin/#{application}-production"
    +    end
    +
    +    desc "Show all SMF services"
    +    task :svcs do
    +      run "#{svcs_bin} -a" do |ch, st, data|
    +        puts data
    +      end
    +    end
    +
    +    desc "Shows all non-functional SMF services"
    +    task :svcs_broken do
    +      run "#{svcs_bin} -vx" do |ch, st, data|
    +        puts data
    +      end
    +    end
    +
    +
    +    namespace :thin do
    +
    +      desc "Disable all Thin servers"
    +      task :disable, :roles => :app do
    +        # temporarily disable, until next reboot (-t)
    +        sudo "#{svcadm_bin} disable -t #{service_list}"
    +      end
    +
    +      desc "Enable all Thin servers"
    +      task :enable, :roles => :app do
    +        # start the app with all recursive dependencies
    +        sudo "#{svcadm_bin} enable -r #{service_list}"
    +      end
    +
    +      desc "Restart all Thin servers"
    +      task :restart, :roles => :app do
    +        # svcadm restart doesn't seem to work right, so we'll brute force it
    +        disable
    +        enable
    +      end
    +
    +    end # namespace thin
    +
    +
    +FILE : config/thin.yml
    +--
    +
    +---
    +pid: tmp/pids/thin.pid
    +socket: /tmp/thin.sock
    +log: log/thin.log
    +max_conns: 1024
    +timeout: 30
    +chdir: /your/app/dir/rails/root
    +environment: production
    +max_persistent_conns: 512
    +daemonize: true
    +servers: 3
    +
    +
    +FILE : config/accelerator/thin_solaris_smf.erb
    +--
    +This is of course an example.  It works for me, but YMMV
    +
    +You may need to change this line to match your environment and config:
    +  exec='/opt/csw/bin/thin -C config/thin.yml --only <%= instance.to_s %> start'
    +
    +
    +CONTRIBUTE:
    +
    +If you see problems or enhancements for this approach please send me an email at glenn [at] rempe dot us.  Sadly, I won't be able to provide support for this example as time and my limited Solaris admin skills won't allow.
    +
    +Cheers,
    +
    +Glenn Rempe
    +2008/03/20
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/vlad.rake b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/vlad.rake
    new file mode 100644
    index 0000000000..af97a8193a
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/example/vlad.rake
    @@ -0,0 +1,72 @@
    +# $GEM_HOME/gems/vlad-1.2.0/lib/vlad/thin.rb
    +# Thin tasks for Vlad the Deployer
    +# By cnantais
    +require 'vlad'
    +
    +namespace :vlad do
    +  ##
    +  # Thin app server
    +
    +  set :thin_address,       nil
    +  set :thin_command,       "thin"
    +  set(:thin_conf)          { "#{shared_path}/thin_cluster.conf" }
    +  set :thin_environment,   "production"
    +  set :thin_group,         nil
    +  set :thin_log_file,      nil
    +  set :thin_pid_file,      nil
    +  set :thin_port,          nil
    +  set :thin_socket,        nil
    +  set :thin_prefix,        nil
    +  set :thin_servers,       2
    +  set :thin_user,          nil
    +  
    +  set :thin_uses_bundler,  true
    +
    +  desc "Prepares application servers for deployment. thin
    +configuration is set via the thin_* variables.".cleanup
    +
    +  remote_task :setup_app, :roles => :app do
    +  
    +    raise(ArgumentError, "Please provide either thin_socket or thin_port") if thin_port.nil? && thin_socket.nil?
    +  
    +    cmd = [
    +           "config",
    +           (%(-s "#{thin_servers}") if thin_servers),
    +           (%(-S "#{thin_socket}") if thin_socket),
    +           (%(-e "#{thin_environment}") if thin_environment),
    +           (%(-a "#{thin_address}") if thin_address),
    +           %(-c "#{current_path}"),
    +           (%(-C "#{thin_conf}") if thin_conf),
    +           (%(-P "#{thin_pid_file}") if thin_pid_file),
    +           (%(-l "#{thin_log_file}") if thin_log_file),
    +           (%(--user "#{thin_user}") if thin_user),
    +           (%(--group "#{thin_group}") if thin_group),
    +           (%(--prefix "#{thin_prefix}") if thin_prefix),
    +           (%(-p "#{thin_port}") if thin_port),
    +          ].compact.join ' '
    +
    +    thin(cmd)
    +  end
    +
    +  def thin(cmd) # :nodoc:
    +    command = if thin_uses_bundler
    +      %(BUNDLE_GEMFILE="#{current_path}/Gemfile" bundle exec #{thin_command} #{cmd} -C "#{thin_conf}")
    +    else
    +      %(#{thin_command} #{cmd} -C "#{thin_conf}")
    +    end
    +
    +    %(cd "#{current_path}" && #{command})
    +  end
    +
    +  desc "Restart the app servers"
    +
    +  remote_task :start_app, :roles => :app do
    +    run thin(%(restart -O -s "#{thin_servers}"))
    +  end
    +
    +  desc "Stop the app servers"
    +
    +  remote_task :stop_app, :roles => :app do
    +    run thin(%(stop -s "#{thin_servers}"))
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/.sitearchdir.time b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/.sitearchdir.time
    new file mode 100644
    index 0000000000..e69de29bb2
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/Makefile b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/Makefile
    new file mode 100644
    index 0000000000..da0eb46ccb
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/Makefile
    @@ -0,0 +1,266 @@
    +
    +SHELL = /bin/sh
    +
    +# V=0 quiet, V=1 verbose.  other values don't work.
    +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.0.0
    +hdrdir = $(topdir)
    +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.0.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)./.gem.20230605-4951-y08t1w
    +sitelibdir = $(DESTDIR)./.gem.20230605-4951-y08t1w
    +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
    +debugflags = -ggdb3
    +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -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
    +cppflags = 
    +CCDLFLAGS = -fPIC
    +CFLAGS   = $(CCDLFLAGS) -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC $(ARCH_FLAG)
    +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
    +DEFS     = 
    +CPPFLAGS =  -Wdate-time -D_FORTIFY_SOURCE=2 $(DEFS) $(cppflags)
    +CXXFLAGS = $(CCDLFLAGS) -g -O2 -ffile-prefix-map=/build/ruby3.0-p8XSIY/ruby3.0-3.0.2=. -fstack-protector-strong -Wformat -Werror=format-security $(ARCH_FLAG)
    +ldflags  = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic
    +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.0
    +RUBY_SO_NAME = ruby-3.0
    +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.0.0
    +ruby = $(bindir)/$(RUBY_BASE_NAME)3.0
    +RUBY = $(ruby)
    +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 = $(RUBY) -run -e rm -- -rf
    +RMDIRS = rmdir --ignore-fail-on-non-empty -p
    +MAKEDIRS = /usr/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) -lc  -lm   -lc
    +ORIG_SRCS = parser.c thin.c
    +SRCS = $(ORIG_SRCS) 
    +OBJS = parser.o thin.o
    +HDRS = $(srcdir)/ext_help.h $(srcdir)/parser.h
    +LOCAL_HDRS = 
    +TARGET = thin_parser
    +TARGET_NAME = thin_parser
    +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) 
    +CLEANOBJS     = *.o  *.bak
    +
    +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) $(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) $(TIMESTAMP_DIR)/.sitearchdir.time
    +	$(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)
    +$(TIMESTAMP_DIR)/.sitearchdir.time:
    +	$(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.0.0/gems/thin-1.8.1/ext/thin_parser/common.rl b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/common.rl
    new file mode 100644
    index 0000000000..0887703606
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/common.rl
    @@ -0,0 +1,59 @@
    +%%{
    +  
    +  machine http_parser_common;
    +
    +#### HTTP PROTOCOL GRAMMAR
    +# line endings
    +  CRLF = "\r\n";
    +
    +# character types
    +  CTL = (cntrl | 127);
    +  safe = ("$" | "-" | "_" | ".");
    +  extra = ("!" | "*" | "'" | "(" | ")" | ",");
    +  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
    +  sorta_safe = ("\"" | "<" | ">");
    +  unsafe = (CTL | " " | "#" | "%" | sorta_safe);
    +  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
    +  unreserved = (alpha | digit | safe | extra | national);
    +  escape = ("%" "u"? xdigit xdigit);
    +  uchar = (unreserved | escape | sorta_safe);
    +  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
    +  tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
    +
    +# elements
    +  token = (ascii -- (CTL | tspecials));
    +
    +# URI schemes and absolute paths
    +  scheme = ( "http"i ("s"i)? );
    +  hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]"));
    +  host_with_port = (hostname (":" digit*)?);
    +  userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*;
    +
    +  path = ( pchar+ ( "/" pchar* )* ) ;
    +  query = ( uchar | reserved )* %query_string ;
    +  param = ( pchar | "/" )* ;
    +  params = ( param ( ";" param )* ) ;
    +  rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?;
    +  absolute_path = ( "/"+ rel_path );
    +  path_uri = absolute_path > mark %request_uri;
    +  Absolute_URI = (scheme "://" userinfo host_with_port path_uri);
    +
    +  Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
    +  Fragment = ( uchar | reserved )* >mark %fragment;
    +  Method = ( upper | digit | safe ){1,20} >mark %request_method;
    +
    +  http_number = ( digit+ "." digit+ ) ;
    +  HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
    +  Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
    +
    +  field_name = ( token -- ":" )+ >start_field %write_field;
    +
    +  field_value = any* >start_value %write_value;
    +
    +  message_header = field_name ":" " "* field_value :> CRLF;
    +
    +  Request = Request_Line ( message_header )* ( CRLF @done );
    +
    +main := Request;
    +
    +}%%
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/ext_help.h b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/ext_help.h
    new file mode 100644
    index 0000000000..8b4d754c76
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/ext_help.h
    @@ -0,0 +1,14 @@
    +#ifndef ext_help_h
    +#define ext_help_h
    +
    +#define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be.");
    +#define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name);
    +#define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T);
    +
    +#ifdef DEBUG
    +#define TRACE()  fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
    +#else
    +#define TRACE() 
    +#endif
    +
    +#endif
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/extconf.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/extconf.rb
    new file mode 100644
    index 0000000000..f83a75ac45
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/extconf.rb
    @@ -0,0 +1,6 @@
    +require 'mkmf'
    +
    +dir_config("thin_parser")
    +have_library("c", "main")
    +
    +create_makefile("thin_parser")
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.c b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.c
    new file mode 100644
    index 0000000000..42832de4e1
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.c
    @@ -0,0 +1,1447 @@
    +
    +#line 1 "parser.rl"
    +/**
    + * Copyright (c) 2005 Zed A. Shaw
    + * You can redistribute it and/or modify it under the same terms as Ruby.
    + */
    +#include "parser.h"
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#define LEN(AT, FPC) (FPC - buffer - parser->AT)
    +#define MARK(M,FPC) (parser->M = (FPC) - buffer)
    +#define PTR_TO(F) (buffer + parser->F)
    +
    +/** Machine **/
    +
    +
    +#line 81 "parser.rl"
    +
    +
    +/** Data **/
    +
    +#line 27 "parser.c"
    +static const int http_parser_start = 1;
    +static const int http_parser_first_final = 70;
    +static const int http_parser_error = 0;
    +
    +static const int http_parser_en_main = 1;
    +
    +
    +#line 85 "parser.rl"
    +
    +int thin_http_parser_init(http_parser *parser)  {
    +  int cs = 0;
    +  
    +#line 40 "parser.c"
    +	{
    +	cs = http_parser_start;
    +	}
    +
    +#line 89 "parser.rl"
    +  parser->cs = cs;
    +  parser->body_start = 0;
    +  parser->content_len = 0;
    +  parser->mark = 0;
    +  parser->nread = 0;
    +  parser->field_len = 0;
    +  parser->field_start = 0;    
    +
    +  return(1);
    +}
    +
    +
    +/** exec **/
    +size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
    +  const char *p, *pe;
    +  int cs = parser->cs;
    +
    +  assert(off <= len && "offset past end of buffer");
    +
    +  p = buffer+off;
    +  pe = buffer+len;
    +
    +  assert(*pe == '\0' && "pointer does not end on NUL");
    +  assert(pe - p == (long)(len - off) && "pointers aren't same distance");
    +
    +
    +  
    +#line 73 "parser.c"
    +	{
    +	if ( p == pe )
    +		goto _test_eof;
    +	switch ( cs )
    +	{
    +case 1:
    +	switch( (*p) ) {
    +		case 36: goto tr0;
    +		case 95: goto tr0;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto tr0;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto tr0;
    +	} else
    +		goto tr0;
    +	goto st0;
    +st0:
    +cs = 0;
    +	goto _out;
    +tr0:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st2;
    +st2:
    +	if ( ++p == pe )
    +		goto _test_eof2;
    +case 2:
    +#line 104 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st51;
    +		case 95: goto st51;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st51;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st51;
    +	} else
    +		goto st51;
    +	goto st0;
    +tr2:
    +#line 36 "parser.rl"
    +	{ 
    +    if (parser->request_method != NULL) {
    +      parser->request_method(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st3;
    +st3:
    +	if ( ++p == pe )
    +		goto _test_eof3;
    +case 3:
    +#line 131 "parser.c"
    +	switch( (*p) ) {
    +		case 42: goto tr4;
    +		case 47: goto tr5;
    +		case 72: goto st34;
    +		case 104: goto st34;
    +	}
    +	goto st0;
    +tr4:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st4;
    +st4:
    +	if ( ++p == pe )
    +		goto _test_eof4;
    +case 4:
    +#line 147 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr7;
    +		case 35: goto tr8;
    +	}
    +	goto st0;
    +tr7:
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +tr30:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +#line 46 "parser.rl"
    +	{ 
    +    if (parser->fragment != NULL) {
    +      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +tr33:
    +#line 46 "parser.rl"
    +	{ 
    +    if (parser->fragment != NULL) {
    +      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +tr38:
    +#line 65 "parser.rl"
    +	{
    +    if (parser->request_path != NULL) {
    +      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +tr45:
    +#line 52 "parser.rl"
    +	{MARK(query_start, p); }
    +#line 53 "parser.rl"
    +	{ 
    +    if (parser->query_string != NULL) {
    +      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +tr49:
    +#line 53 "parser.rl"
    +	{ 
    +    if (parser->query_string != NULL) {
    +      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st5;
    +st5:
    +	if ( ++p == pe )
    +		goto _test_eof5;
    +case 5:
    +#line 227 "parser.c"
    +	if ( (*p) == 72 )
    +		goto tr9;
    +	goto st0;
    +tr9:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st6;
    +st6:
    +	if ( ++p == pe )
    +		goto _test_eof6;
    +case 6:
    +#line 239 "parser.c"
    +	if ( (*p) == 84 )
    +		goto st7;
    +	goto st0;
    +st7:
    +	if ( ++p == pe )
    +		goto _test_eof7;
    +case 7:
    +	if ( (*p) == 84 )
    +		goto st8;
    +	goto st0;
    +st8:
    +	if ( ++p == pe )
    +		goto _test_eof8;
    +case 8:
    +	if ( (*p) == 80 )
    +		goto st9;
    +	goto st0;
    +st9:
    +	if ( ++p == pe )
    +		goto _test_eof9;
    +case 9:
    +	if ( (*p) == 47 )
    +		goto st10;
    +	goto st0;
    +st10:
    +	if ( ++p == pe )
    +		goto _test_eof10;
    +case 10:
    +	if ( 48 <= (*p) && (*p) <= 57 )
    +		goto st11;
    +	goto st0;
    +st11:
    +	if ( ++p == pe )
    +		goto _test_eof11;
    +case 11:
    +	if ( (*p) == 46 )
    +		goto st12;
    +	if ( 48 <= (*p) && (*p) <= 57 )
    +		goto st11;
    +	goto st0;
    +st12:
    +	if ( ++p == pe )
    +		goto _test_eof12;
    +case 12:
    +	if ( 48 <= (*p) && (*p) <= 57 )
    +		goto st13;
    +	goto st0;
    +st13:
    +	if ( ++p == pe )
    +		goto _test_eof13;
    +case 13:
    +	if ( (*p) == 13 )
    +		goto tr17;
    +	if ( 48 <= (*p) && (*p) <= 57 )
    +		goto st13;
    +	goto st0;
    +tr17:
    +#line 59 "parser.rl"
    +	{	
    +    if (parser->http_version != NULL) {
    +      parser->http_version(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st14;
    +tr25:
    +#line 30 "parser.rl"
    +	{ MARK(mark, p); }
    +#line 31 "parser.rl"
    +	{ 
    +    if (parser->http_field != NULL) {
    +      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st14;
    +tr28:
    +#line 31 "parser.rl"
    +	{ 
    +    if (parser->http_field != NULL) {
    +      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st14;
    +st14:
    +	if ( ++p == pe )
    +		goto _test_eof14;
    +case 14:
    +#line 326 "parser.c"
    +	if ( (*p) == 10 )
    +		goto st15;
    +	goto st0;
    +st15:
    +	if ( ++p == pe )
    +		goto _test_eof15;
    +case 15:
    +	switch( (*p) ) {
    +		case 13: goto st16;
    +		case 33: goto tr20;
    +		case 124: goto tr20;
    +		case 126: goto tr20;
    +	}
    +	if ( (*p) < 45 ) {
    +		if ( (*p) > 39 ) {
    +			if ( 42 <= (*p) && (*p) <= 43 )
    +				goto tr20;
    +		} else if ( (*p) >= 35 )
    +			goto tr20;
    +	} else if ( (*p) > 46 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 48 <= (*p) && (*p) <= 57 )
    +				goto tr20;
    +		} else if ( (*p) > 90 ) {
    +			if ( 94 <= (*p) && (*p) <= 122 )
    +				goto tr20;
    +		} else
    +			goto tr20;
    +	} else
    +		goto tr20;
    +	goto st0;
    +st16:
    +	if ( ++p == pe )
    +		goto _test_eof16;
    +case 16:
    +	if ( (*p) == 10 )
    +		goto tr21;
    +	goto st0;
    +tr21:
    +#line 71 "parser.rl"
    +	{ 
    +    parser->body_start = p - buffer + 1; 
    +    if (parser->header_done != NULL) {
    +      parser->header_done(parser->data, p + 1, pe - p - 1);
    +    }
    +    {p++; cs = 70; goto _out;}
    +  }
    +	goto st70;
    +st70:
    +	if ( ++p == pe )
    +		goto _test_eof70;
    +case 70:
    +#line 379 "parser.c"
    +	goto st0;
    +tr20:
    +#line 25 "parser.rl"
    +	{ MARK(field_start, p); }
    +	goto st17;
    +st17:
    +	if ( ++p == pe )
    +		goto _test_eof17;
    +case 17:
    +#line 389 "parser.c"
    +	switch( (*p) ) {
    +		case 33: goto st17;
    +		case 58: goto tr23;
    +		case 124: goto st17;
    +		case 126: goto st17;
    +	}
    +	if ( (*p) < 45 ) {
    +		if ( (*p) > 39 ) {
    +			if ( 42 <= (*p) && (*p) <= 43 )
    +				goto st17;
    +		} else if ( (*p) >= 35 )
    +			goto st17;
    +	} else if ( (*p) > 46 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 48 <= (*p) && (*p) <= 57 )
    +				goto st17;
    +		} else if ( (*p) > 90 ) {
    +			if ( 94 <= (*p) && (*p) <= 122 )
    +				goto st17;
    +		} else
    +			goto st17;
    +	} else
    +		goto st17;
    +	goto st0;
    +tr23:
    +#line 26 "parser.rl"
    +	{ 
    +    parser->field_len = LEN(field_start, p);
    +  }
    +	goto st18;
    +tr26:
    +#line 30 "parser.rl"
    +	{ MARK(mark, p); }
    +	goto st18;
    +st18:
    +	if ( ++p == pe )
    +		goto _test_eof18;
    +case 18:
    +#line 428 "parser.c"
    +	switch( (*p) ) {
    +		case 13: goto tr25;
    +		case 32: goto tr26;
    +	}
    +	goto tr24;
    +tr24:
    +#line 30 "parser.rl"
    +	{ MARK(mark, p); }
    +	goto st19;
    +st19:
    +	if ( ++p == pe )
    +		goto _test_eof19;
    +case 19:
    +#line 442 "parser.c"
    +	if ( (*p) == 13 )
    +		goto tr28;
    +	goto st19;
    +tr8:
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st20;
    +tr39:
    +#line 65 "parser.rl"
    +	{
    +    if (parser->request_path != NULL) {
    +      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st20;
    +tr46:
    +#line 52 "parser.rl"
    +	{MARK(query_start, p); }
    +#line 53 "parser.rl"
    +	{ 
    +    if (parser->query_string != NULL) {
    +      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st20;
    +tr50:
    +#line 53 "parser.rl"
    +	{ 
    +    if (parser->query_string != NULL) {
    +      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    +    }
    +  }
    +#line 41 "parser.rl"
    +	{
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    +    }
    +  }
    +	goto st20;
    +st20:
    +	if ( ++p == pe )
    +		goto _test_eof20;
    +case 20:
    +#line 502 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr30;
    +		case 35: goto st0;
    +		case 37: goto tr31;
    +		case 127: goto st0;
    +	}
    +	if ( 0 <= (*p) && (*p) <= 31 )
    +		goto st0;
    +	goto tr29;
    +tr29:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st21;
    +st21:
    +	if ( ++p == pe )
    +		goto _test_eof21;
    +case 21:
    +#line 520 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr33;
    +		case 35: goto st0;
    +		case 37: goto st22;
    +		case 127: goto st0;
    +	}
    +	if ( 0 <= (*p) && (*p) <= 31 )
    +		goto st0;
    +	goto st21;
    +tr31:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st22;
    +st22:
    +	if ( ++p == pe )
    +		goto _test_eof22;
    +case 22:
    +#line 538 "parser.c"
    +	if ( (*p) == 117 )
    +		goto st24;
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st23;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st23;
    +	} else
    +		goto st23;
    +	goto st0;
    +st23:
    +	if ( ++p == pe )
    +		goto _test_eof23;
    +case 23:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st21;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st21;
    +	} else
    +		goto st21;
    +	goto st0;
    +st24:
    +	if ( ++p == pe )
    +		goto _test_eof24;
    +case 24:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st23;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st23;
    +	} else
    +		goto st23;
    +	goto st0;
    +tr5:
    +#line 22 "parser.rl"
    +	{MARK(mark, p); }
    +	goto st25;
    +st25:
    +	if ( ++p == pe )
    +		goto _test_eof25;
    +case 25:
    +#line 584 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr38;
    +		case 35: goto tr39;
    +		case 37: goto st26;
    +		case 63: goto tr41;
    +		case 127: goto st0;
    +	}
    +	if ( 0 <= (*p) && (*p) <= 31 )
    +		goto st0;
    +	goto st25;
    +st26:
    +	if ( ++p == pe )
    +		goto _test_eof26;
    +case 26:
    +	if ( (*p) == 117 )
    +		goto st28;
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st27;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st27;
    +	} else
    +		goto st27;
    +	goto st0;
    +st27:
    +	if ( ++p == pe )
    +		goto _test_eof27;
    +case 27:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st25;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st25;
    +	} else
    +		goto st25;
    +	goto st0;
    +st28:
    +	if ( ++p == pe )
    +		goto _test_eof28;
    +case 28:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st27;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st27;
    +	} else
    +		goto st27;
    +	goto st0;
    +tr41:
    +#line 65 "parser.rl"
    +	{
    +    if (parser->request_path != NULL) {
    +      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    +    }
    +  }
    +	goto st29;
    +st29:
    +	if ( ++p == pe )
    +		goto _test_eof29;
    +case 29:
    +#line 648 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr45;
    +		case 35: goto tr46;
    +		case 37: goto tr47;
    +		case 127: goto st0;
    +	}
    +	if ( 0 <= (*p) && (*p) <= 31 )
    +		goto st0;
    +	goto tr44;
    +tr44:
    +#line 52 "parser.rl"
    +	{MARK(query_start, p); }
    +	goto st30;
    +st30:
    +	if ( ++p == pe )
    +		goto _test_eof30;
    +case 30:
    +#line 666 "parser.c"
    +	switch( (*p) ) {
    +		case 32: goto tr49;
    +		case 35: goto tr50;
    +		case 37: goto st31;
    +		case 127: goto st0;
    +	}
    +	if ( 0 <= (*p) && (*p) <= 31 )
    +		goto st0;
    +	goto st30;
    +tr47:
    +#line 52 "parser.rl"
    +	{MARK(query_start, p); }
    +	goto st31;
    +st31:
    +	if ( ++p == pe )
    +		goto _test_eof31;
    +case 31:
    +#line 684 "parser.c"
    +	if ( (*p) == 117 )
    +		goto st33;
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st32;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st32;
    +	} else
    +		goto st32;
    +	goto st0;
    +st32:
    +	if ( ++p == pe )
    +		goto _test_eof32;
    +case 32:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st30;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st30;
    +	} else
    +		goto st30;
    +	goto st0;
    +st33:
    +	if ( ++p == pe )
    +		goto _test_eof33;
    +case 33:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st32;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st32;
    +	} else
    +		goto st32;
    +	goto st0;
    +st34:
    +	if ( ++p == pe )
    +		goto _test_eof34;
    +case 34:
    +	switch( (*p) ) {
    +		case 84: goto st35;
    +		case 116: goto st35;
    +	}
    +	goto st0;
    +st35:
    +	if ( ++p == pe )
    +		goto _test_eof35;
    +case 35:
    +	switch( (*p) ) {
    +		case 84: goto st36;
    +		case 116: goto st36;
    +	}
    +	goto st0;
    +st36:
    +	if ( ++p == pe )
    +		goto _test_eof36;
    +case 36:
    +	switch( (*p) ) {
    +		case 80: goto st37;
    +		case 112: goto st37;
    +	}
    +	goto st0;
    +st37:
    +	if ( ++p == pe )
    +		goto _test_eof37;
    +case 37:
    +	switch( (*p) ) {
    +		case 58: goto st38;
    +		case 83: goto st50;
    +		case 115: goto st50;
    +	}
    +	goto st0;
    +st38:
    +	if ( ++p == pe )
    +		goto _test_eof38;
    +case 38:
    +	if ( (*p) == 47 )
    +		goto st39;
    +	goto st0;
    +st39:
    +	if ( ++p == pe )
    +		goto _test_eof39;
    +case 39:
    +	if ( (*p) == 47 )
    +		goto st40;
    +	goto st0;
    +st40:
    +	if ( ++p == pe )
    +		goto _test_eof40;
    +case 40:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto st0;
    +		case 60: goto st0;
    +		case 91: goto st47;
    +		case 95: goto st45;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 45 ) {
    +		if ( (*p) > 32 ) {
    +			if ( 34 <= (*p) && (*p) <= 35 )
    +				goto st0;
    +		} else if ( (*p) >= 0 )
    +			goto st0;
    +	} else if ( (*p) > 57 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 62 <= (*p) && (*p) <= 64 )
    +				goto st0;
    +		} else if ( (*p) > 90 ) {
    +			if ( 97 <= (*p) && (*p) <= 122 )
    +				goto st45;
    +		} else
    +			goto st45;
    +	} else
    +		goto st45;
    +	goto st41;
    +st41:
    +	if ( ++p == pe )
    +		goto _test_eof41;
    +case 41:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto st0;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 34 ) {
    +		if ( 0 <= (*p) && (*p) <= 32 )
    +			goto st0;
    +	} else if ( (*p) > 35 ) {
    +		if ( 62 <= (*p) && (*p) <= 63 )
    +			goto st0;
    +	} else
    +		goto st0;
    +	goto st41;
    +st42:
    +	if ( ++p == pe )
    +		goto _test_eof42;
    +case 42:
    +	if ( (*p) == 117 )
    +		goto st44;
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st43;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st43;
    +	} else
    +		goto st43;
    +	goto st0;
    +st43:
    +	if ( ++p == pe )
    +		goto _test_eof43;
    +case 43:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st41;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st41;
    +	} else
    +		goto st41;
    +	goto st0;
    +st44:
    +	if ( ++p == pe )
    +		goto _test_eof44;
    +case 44:
    +	if ( (*p) < 65 ) {
    +		if ( 48 <= (*p) && (*p) <= 57 )
    +			goto st43;
    +	} else if ( (*p) > 70 ) {
    +		if ( 97 <= (*p) && (*p) <= 102 )
    +			goto st43;
    +	} else
    +		goto st43;
    +	goto st0;
    +st45:
    +	if ( ++p == pe )
    +		goto _test_eof45;
    +case 45:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto tr5;
    +		case 58: goto st46;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 95: goto st45;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 45 ) {
    +		if ( (*p) > 32 ) {
    +			if ( 34 <= (*p) && (*p) <= 35 )
    +				goto st0;
    +		} else if ( (*p) >= 0 )
    +			goto st0;
    +	} else if ( (*p) > 57 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 62 <= (*p) && (*p) <= 63 )
    +				goto st0;
    +		} else if ( (*p) > 90 ) {
    +			if ( 97 <= (*p) && (*p) <= 122 )
    +				goto st45;
    +		} else
    +			goto st45;
    +	} else
    +		goto st45;
    +	goto st41;
    +st46:
    +	if ( ++p == pe )
    +		goto _test_eof46;
    +case 46:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto tr5;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 34 ) {
    +		if ( 0 <= (*p) && (*p) <= 32 )
    +			goto st0;
    +	} else if ( (*p) > 35 ) {
    +		if ( (*p) > 57 ) {
    +			if ( 62 <= (*p) && (*p) <= 63 )
    +				goto st0;
    +		} else if ( (*p) >= 48 )
    +			goto st46;
    +	} else
    +		goto st0;
    +	goto st41;
    +st47:
    +	if ( ++p == pe )
    +		goto _test_eof47;
    +case 47:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto st0;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( (*p) > 32 ) {
    +			if ( 34 <= (*p) && (*p) <= 35 )
    +				goto st0;
    +		} else if ( (*p) >= 0 )
    +			goto st0;
    +	} else if ( (*p) > 58 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 62 <= (*p) && (*p) <= 63 )
    +				goto st0;
    +		} else if ( (*p) > 70 ) {
    +			if ( 97 <= (*p) && (*p) <= 102 )
    +				goto st48;
    +		} else
    +			goto st48;
    +	} else
    +		goto st48;
    +	goto st41;
    +st48:
    +	if ( ++p == pe )
    +		goto _test_eof48;
    +case 48:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto st0;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 93: goto st49;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( (*p) > 32 ) {
    +			if ( 34 <= (*p) && (*p) <= 35 )
    +				goto st0;
    +		} else if ( (*p) >= 0 )
    +			goto st0;
    +	} else if ( (*p) > 58 ) {
    +		if ( (*p) < 65 ) {
    +			if ( 62 <= (*p) && (*p) <= 63 )
    +				goto st0;
    +		} else if ( (*p) > 70 ) {
    +			if ( 97 <= (*p) && (*p) <= 102 )
    +				goto st48;
    +		} else
    +			goto st48;
    +	} else
    +		goto st48;
    +	goto st41;
    +st49:
    +	if ( ++p == pe )
    +		goto _test_eof49;
    +case 49:
    +	switch( (*p) ) {
    +		case 37: goto st42;
    +		case 47: goto tr5;
    +		case 58: goto st46;
    +		case 60: goto st0;
    +		case 64: goto st40;
    +		case 127: goto st0;
    +	}
    +	if ( (*p) < 34 ) {
    +		if ( 0 <= (*p) && (*p) <= 32 )
    +			goto st0;
    +	} else if ( (*p) > 35 ) {
    +		if ( 62 <= (*p) && (*p) <= 63 )
    +			goto st0;
    +	} else
    +		goto st0;
    +	goto st41;
    +st50:
    +	if ( ++p == pe )
    +		goto _test_eof50;
    +case 50:
    +	if ( (*p) == 58 )
    +		goto st38;
    +	goto st0;
    +st51:
    +	if ( ++p == pe )
    +		goto _test_eof51;
    +case 51:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st52;
    +		case 95: goto st52;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st52;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st52;
    +	} else
    +		goto st52;
    +	goto st0;
    +st52:
    +	if ( ++p == pe )
    +		goto _test_eof52;
    +case 52:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st53;
    +		case 95: goto st53;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st53;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st53;
    +	} else
    +		goto st53;
    +	goto st0;
    +st53:
    +	if ( ++p == pe )
    +		goto _test_eof53;
    +case 53:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st54;
    +		case 95: goto st54;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st54;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st54;
    +	} else
    +		goto st54;
    +	goto st0;
    +st54:
    +	if ( ++p == pe )
    +		goto _test_eof54;
    +case 54:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st55;
    +		case 95: goto st55;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st55;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st55;
    +	} else
    +		goto st55;
    +	goto st0;
    +st55:
    +	if ( ++p == pe )
    +		goto _test_eof55;
    +case 55:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st56;
    +		case 95: goto st56;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st56;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st56;
    +	} else
    +		goto st56;
    +	goto st0;
    +st56:
    +	if ( ++p == pe )
    +		goto _test_eof56;
    +case 56:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st57;
    +		case 95: goto st57;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st57;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st57;
    +	} else
    +		goto st57;
    +	goto st0;
    +st57:
    +	if ( ++p == pe )
    +		goto _test_eof57;
    +case 57:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st58;
    +		case 95: goto st58;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st58;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st58;
    +	} else
    +		goto st58;
    +	goto st0;
    +st58:
    +	if ( ++p == pe )
    +		goto _test_eof58;
    +case 58:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st59;
    +		case 95: goto st59;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st59;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st59;
    +	} else
    +		goto st59;
    +	goto st0;
    +st59:
    +	if ( ++p == pe )
    +		goto _test_eof59;
    +case 59:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st60;
    +		case 95: goto st60;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st60;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st60;
    +	} else
    +		goto st60;
    +	goto st0;
    +st60:
    +	if ( ++p == pe )
    +		goto _test_eof60;
    +case 60:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st61;
    +		case 95: goto st61;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st61;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st61;
    +	} else
    +		goto st61;
    +	goto st0;
    +st61:
    +	if ( ++p == pe )
    +		goto _test_eof61;
    +case 61:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st62;
    +		case 95: goto st62;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st62;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st62;
    +	} else
    +		goto st62;
    +	goto st0;
    +st62:
    +	if ( ++p == pe )
    +		goto _test_eof62;
    +case 62:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st63;
    +		case 95: goto st63;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st63;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st63;
    +	} else
    +		goto st63;
    +	goto st0;
    +st63:
    +	if ( ++p == pe )
    +		goto _test_eof63;
    +case 63:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st64;
    +		case 95: goto st64;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st64;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st64;
    +	} else
    +		goto st64;
    +	goto st0;
    +st64:
    +	if ( ++p == pe )
    +		goto _test_eof64;
    +case 64:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st65;
    +		case 95: goto st65;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st65;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st65;
    +	} else
    +		goto st65;
    +	goto st0;
    +st65:
    +	if ( ++p == pe )
    +		goto _test_eof65;
    +case 65:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st66;
    +		case 95: goto st66;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st66;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st66;
    +	} else
    +		goto st66;
    +	goto st0;
    +st66:
    +	if ( ++p == pe )
    +		goto _test_eof66;
    +case 66:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st67;
    +		case 95: goto st67;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st67;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st67;
    +	} else
    +		goto st67;
    +	goto st0;
    +st67:
    +	if ( ++p == pe )
    +		goto _test_eof67;
    +case 67:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st68;
    +		case 95: goto st68;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st68;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st68;
    +	} else
    +		goto st68;
    +	goto st0;
    +st68:
    +	if ( ++p == pe )
    +		goto _test_eof68;
    +case 68:
    +	switch( (*p) ) {
    +		case 32: goto tr2;
    +		case 36: goto st69;
    +		case 95: goto st69;
    +	}
    +	if ( (*p) < 48 ) {
    +		if ( 45 <= (*p) && (*p) <= 46 )
    +			goto st69;
    +	} else if ( (*p) > 57 ) {
    +		if ( 65 <= (*p) && (*p) <= 90 )
    +			goto st69;
    +	} else
    +		goto st69;
    +	goto st0;
    +st69:
    +	if ( ++p == pe )
    +		goto _test_eof69;
    +case 69:
    +	if ( (*p) == 32 )
    +		goto tr2;
    +	goto st0;
    +	}
    +	_test_eof2: cs = 2; goto _test_eof; 
    +	_test_eof3: cs = 3; goto _test_eof; 
    +	_test_eof4: cs = 4; goto _test_eof; 
    +	_test_eof5: cs = 5; goto _test_eof; 
    +	_test_eof6: cs = 6; goto _test_eof; 
    +	_test_eof7: cs = 7; goto _test_eof; 
    +	_test_eof8: cs = 8; goto _test_eof; 
    +	_test_eof9: cs = 9; goto _test_eof; 
    +	_test_eof10: cs = 10; goto _test_eof; 
    +	_test_eof11: cs = 11; goto _test_eof; 
    +	_test_eof12: cs = 12; goto _test_eof; 
    +	_test_eof13: cs = 13; goto _test_eof; 
    +	_test_eof14: cs = 14; goto _test_eof; 
    +	_test_eof15: cs = 15; goto _test_eof; 
    +	_test_eof16: cs = 16; goto _test_eof; 
    +	_test_eof70: cs = 70; goto _test_eof; 
    +	_test_eof17: cs = 17; goto _test_eof; 
    +	_test_eof18: cs = 18; goto _test_eof; 
    +	_test_eof19: cs = 19; goto _test_eof; 
    +	_test_eof20: cs = 20; goto _test_eof; 
    +	_test_eof21: cs = 21; goto _test_eof; 
    +	_test_eof22: cs = 22; goto _test_eof; 
    +	_test_eof23: cs = 23; goto _test_eof; 
    +	_test_eof24: cs = 24; goto _test_eof; 
    +	_test_eof25: cs = 25; goto _test_eof; 
    +	_test_eof26: cs = 26; goto _test_eof; 
    +	_test_eof27: cs = 27; goto _test_eof; 
    +	_test_eof28: cs = 28; goto _test_eof; 
    +	_test_eof29: cs = 29; goto _test_eof; 
    +	_test_eof30: cs = 30; goto _test_eof; 
    +	_test_eof31: cs = 31; goto _test_eof; 
    +	_test_eof32: cs = 32; goto _test_eof; 
    +	_test_eof33: cs = 33; goto _test_eof; 
    +	_test_eof34: cs = 34; goto _test_eof; 
    +	_test_eof35: cs = 35; goto _test_eof; 
    +	_test_eof36: cs = 36; goto _test_eof; 
    +	_test_eof37: cs = 37; goto _test_eof; 
    +	_test_eof38: cs = 38; goto _test_eof; 
    +	_test_eof39: cs = 39; goto _test_eof; 
    +	_test_eof40: cs = 40; goto _test_eof; 
    +	_test_eof41: cs = 41; goto _test_eof; 
    +	_test_eof42: cs = 42; goto _test_eof; 
    +	_test_eof43: cs = 43; goto _test_eof; 
    +	_test_eof44: cs = 44; goto _test_eof; 
    +	_test_eof45: cs = 45; goto _test_eof; 
    +	_test_eof46: cs = 46; goto _test_eof; 
    +	_test_eof47: cs = 47; goto _test_eof; 
    +	_test_eof48: cs = 48; goto _test_eof; 
    +	_test_eof49: cs = 49; goto _test_eof; 
    +	_test_eof50: cs = 50; goto _test_eof; 
    +	_test_eof51: cs = 51; goto _test_eof; 
    +	_test_eof52: cs = 52; goto _test_eof; 
    +	_test_eof53: cs = 53; goto _test_eof; 
    +	_test_eof54: cs = 54; goto _test_eof; 
    +	_test_eof55: cs = 55; goto _test_eof; 
    +	_test_eof56: cs = 56; goto _test_eof; 
    +	_test_eof57: cs = 57; goto _test_eof; 
    +	_test_eof58: cs = 58; goto _test_eof; 
    +	_test_eof59: cs = 59; goto _test_eof; 
    +	_test_eof60: cs = 60; goto _test_eof; 
    +	_test_eof61: cs = 61; goto _test_eof; 
    +	_test_eof62: cs = 62; goto _test_eof; 
    +	_test_eof63: cs = 63; goto _test_eof; 
    +	_test_eof64: cs = 64; goto _test_eof; 
    +	_test_eof65: cs = 65; goto _test_eof; 
    +	_test_eof66: cs = 66; goto _test_eof; 
    +	_test_eof67: cs = 67; goto _test_eof; 
    +	_test_eof68: cs = 68; goto _test_eof; 
    +	_test_eof69: cs = 69; goto _test_eof; 
    +
    +	_test_eof: {}
    +	_out: {}
    +	}
    +
    +#line 116 "parser.rl"
    +
    +  parser->cs = cs;
    +  parser->nread += p - (buffer + off);
    +
    +  assert(p <= pe && "buffer overflow after parsing execute");
    +  assert(parser->nread <= len && "nread longer than length");
    +  assert(parser->body_start <= len && "body starts after buffer end");
    +  assert(parser->mark < len && "mark is after buffer end");
    +  assert(parser->field_len <= len && "field has length longer than whole buffer");
    +  assert(parser->field_start < len && "field starts after buffer end");
    +
    +  if(parser->body_start) {
    +    /* final \r\n combo encountered so stop right here */
    +    parser->nread++;
    +  }
    +
    +  return(parser->nread);
    +}
    +
    +int thin_http_parser_has_error(http_parser *parser) {
    +  return parser->cs == http_parser_error;
    +}
    +
    +int thin_http_parser_is_finished(http_parser *parser) {
    +  return parser->cs == http_parser_first_final;
    +}
    +
    +int thin_http_parser_finish(http_parser *parser)
    +{
    +  if (thin_http_parser_has_error(parser) ) {
    +    return -1;
    +  } else if (thin_http_parser_is_finished(parser) ) {
    +    return 1;
    +  } else {
    +    return 0;
    +  }
    +}
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.h b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.h
    new file mode 100644
    index 0000000000..f80d40f6b0
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.h
    @@ -0,0 +1,49 @@
    +/**
    + * Copyright (c) 2005 Zed A. Shaw
    + * You can redistribute it and/or modify it under the same terms as Ruby.
    + */
    +
    +#ifndef http11_parser_h
    +#define http11_parser_h
    +
    +#include 
    +
    +#if defined(_WIN32)
    +#include 
    +#endif
    +
    +typedef void (*element_cb)(void *data, const char *at, size_t length);
    +typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
    +
    +typedef struct http_parser { 
    +  int cs;
    +  size_t body_start;
    +  int content_len;
    +  size_t nread;
    +  size_t mark;
    +  size_t field_start;
    +  size_t field_len;
    +  size_t query_start;
    +
    +  void *data;
    +
    +  field_cb http_field;
    +  element_cb request_method;
    +  element_cb request_uri;
    +  element_cb fragment;
    +  element_cb request_path;
    +  element_cb query_string;
    +  element_cb http_version;
    +  element_cb header_done;
    +  
    +} http_parser;
    +
    +int thin_http_parser_init(http_parser *parser);
    +int thin_http_parser_finish(http_parser *parser);
    +size_t thin_http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
    +int thin_http_parser_has_error(http_parser *parser);
    +int thin_http_parser_is_finished(http_parser *parser);
    +
    +#define http_parser_nread(parser) (parser)->nread 
    +
    +#endif
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.o b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.o
    new file mode 100644
    index 0000000000..970a5dbfde
    Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.o differ
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.rl b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.rl
    new file mode 100644
    index 0000000000..05c8eb62a7
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/parser.rl
    @@ -0,0 +1,152 @@
    +/**
    + * Copyright (c) 2005 Zed A. Shaw
    + * You can redistribute it and/or modify it under the same terms as Ruby.
    + */
    +#include "parser.h"
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#define LEN(AT, FPC) (FPC - buffer - parser->AT)
    +#define MARK(M,FPC) (parser->M = (FPC) - buffer)
    +#define PTR_TO(F) (buffer + parser->F)
    +
    +/** Machine **/
    +
    +%%{
    +  
    +  machine http_parser;
    +
    +  action mark {MARK(mark, fpc); }
    +
    +
    +  action start_field { MARK(field_start, fpc); }
    +  action write_field { 
    +    parser->field_len = LEN(field_start, fpc);
    +  }
    +
    +  action start_value { MARK(mark, fpc); }
    +  action write_value { 
    +    if (parser->http_field != NULL) {
    +      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
    +    }
    +  }
    +  action request_method { 
    +    if (parser->request_method != NULL) {
    +      parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
    +    }
    +  }
    +  action request_uri {
    +    if (parser->request_uri != NULL) {
    +      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
    +    }
    +  }
    +  action fragment { 
    +    if (parser->fragment != NULL) {
    +      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
    +    }
    +  }
    +
    +  action start_query {MARK(query_start, fpc); }
    +  action query_string { 
    +    if (parser->query_string != NULL) {
    +      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
    +    }
    +  }
    +
    +  action http_version {	
    +    if (parser->http_version != NULL) {
    +      parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
    +    }
    +  }
    +
    +  action request_path {
    +    if (parser->request_path != NULL) {
    +      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
    +    }
    +  }
    +
    +  action done { 
    +    parser->body_start = fpc - buffer + 1; 
    +    if (parser->header_done != NULL) {
    +      parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
    +    }
    +    fbreak;
    +  }
    +
    +  include http_parser_common "common.rl";
    +
    +}%%
    +
    +/** Data **/
    +%% write data;
    +
    +int thin_http_parser_init(http_parser *parser)  {
    +  int cs = 0;
    +  %% write init;
    +  parser->cs = cs;
    +  parser->body_start = 0;
    +  parser->content_len = 0;
    +  parser->mark = 0;
    +  parser->nread = 0;
    +  parser->field_len = 0;
    +  parser->field_start = 0;    
    +
    +  return(1);
    +}
    +
    +
    +/** exec **/
    +size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
    +  const char *p, *pe;
    +  int cs = parser->cs;
    +
    +  assert(off <= len && "offset past end of buffer");
    +
    +  p = buffer+off;
    +  pe = buffer+len;
    +
    +  assert(*pe == '\0' && "pointer does not end on NUL");
    +  assert(pe - p == (long)(len - off) && "pointers aren't same distance");
    +
    +
    +  %% write exec;
    +
    +  parser->cs = cs;
    +  parser->nread += p - (buffer + off);
    +
    +  assert(p <= pe && "buffer overflow after parsing execute");
    +  assert(parser->nread <= len && "nread longer than length");
    +  assert(parser->body_start <= len && "body starts after buffer end");
    +  assert(parser->mark < len && "mark is after buffer end");
    +  assert(parser->field_len <= len && "field has length longer than whole buffer");
    +  assert(parser->field_start < len && "field starts after buffer end");
    +
    +  if(parser->body_start) {
    +    /* final \r\n combo encountered so stop right here */
    +    parser->nread++;
    +  }
    +
    +  return(parser->nread);
    +}
    +
    +int thin_http_parser_has_error(http_parser *parser) {
    +  return parser->cs == http_parser_error;
    +}
    +
    +int thin_http_parser_is_finished(http_parser *parser) {
    +  return parser->cs == http_parser_first_final;
    +}
    +
    +int thin_http_parser_finish(http_parser *parser)
    +{
    +  if (thin_http_parser_has_error(parser) ) {
    +    return -1;
    +  } else if (thin_http_parser_is_finished(parser) ) {
    +    return 1;
    +  } else {
    +    return 0;
    +  }
    +}
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.c b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.c
    new file mode 100644
    index 0000000000..b3e777cc33
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.c
    @@ -0,0 +1,435 @@
    +/**
    + * Mongrel Parser adpated to Thin and to play more nicely with Rack specs.
    + * 
    + * Orignal version Copyright (c) 2005 Zed A. Shaw
    + * You can redistribute it and/or modify it under the same terms as Ruby.
    + */
    +#include "ruby.h"
    +#include "ext_help.h"
    +#include 
    +#include 
    +#include "parser.h"
    +#include 
    +
    +static VALUE mThin;
    +static VALUE cHttpParser;
    +static VALUE eHttpParserError;
    +
    +static VALUE global_empty;
    +static VALUE global_http_prefix;
    +static VALUE global_request_method;
    +static VALUE global_request_uri;
    +static VALUE global_fragment;
    +static VALUE global_query_string;
    +static VALUE global_http_version;
    +static VALUE global_content_length;
    +static VALUE global_http_content_length;
    +static VALUE global_request_path;
    +static VALUE global_content_type;
    +static VALUE global_http_content_type;
    +static VALUE global_gateway_interface;
    +static VALUE global_gateway_interface_value;
    +static VALUE global_server_name;
    +static VALUE global_server_port;
    +static VALUE global_server_protocol;
    +static VALUE global_server_protocol_value;
    +static VALUE global_http_host;
    +static VALUE global_port_80;
    +static VALUE global_http_body;
    +static VALUE global_url_scheme;
    +static VALUE global_url_scheme_value;
    +static VALUE global_script_name;
    +static VALUE global_path_info;
    +
    +#define TRIE_INCREASE 30
    +
    +/** Defines common length and error messages for input length validation. */
    +#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N  " is longer than the " # length " allowed length."
    +
    +/** Validates the max length of given input and throws an HttpParserError exception if over. */
    +#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, "%s", MAX_##N##_LENGTH_ERR); }
    +
    +/** Defines global strings in the init method. */
    +#define DEF_GLOBAL(N, val)   global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
    +
    +/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */
    +#ifndef RSTRING_PTR
    +#define RSTRING_PTR(s) (RSTRING(s)->ptr)
    +#endif
    +
    +/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */
    +#ifndef RSTRING_LEN
    +#define RSTRING_LEN(s) (RSTRING(s)->len)
    +#endif
    +
    +/* Defines the maximum allowed lengths for various input elements.*/
    +DEF_MAX_LENGTH(FIELD_NAME, 256);
    +DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
    +DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
    +DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
    +DEF_MAX_LENGTH(REQUEST_PATH, 2048);
    +DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
    +DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
    +
    +static void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
    +{
    +  char *ch, *end;
    +  VALUE req = (VALUE)data;
    +  VALUE v = Qnil;
    +  VALUE f = Qnil;
    +
    +  VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
    +  VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
    +
    +  v = rb_str_new(value, vlen);
    +  f = rb_str_dup(global_http_prefix);
    +  f = rb_str_buf_cat(f, field, flen); 
    +
    +  for(ch = RSTRING_PTR(f) + RSTRING_LEN(global_http_prefix), end = RSTRING_PTR(f) + RSTRING_LEN(f); ch < end; ch++) {
    +    if (*ch >= 'a' && *ch <= 'z') {
    +      *ch &= ~0x20; // upcase
    +    } else if (*ch == '-') {
    +      *ch = '_';
    +    }
    +  }
    +
    +  rb_hash_aset(req, f, v);
    +}
    +
    +static void request_method(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = Qnil;
    +
    +  val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_request_method, val);
    +}
    +
    +static void request_uri(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = Qnil;
    +
    +  VALIDATE_MAX_LENGTH(length, REQUEST_URI);
    +
    +  val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_request_uri, val);
    +}
    +
    +static void fragment(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = Qnil;
    +
    +  VALIDATE_MAX_LENGTH(length, FRAGMENT);
    +
    +  val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_fragment, val);
    +}
    +
    +static void request_path(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = Qnil;
    +
    +  VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
    +
    +  val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_request_path, val);
    +  rb_hash_aset(req, global_path_info, val);
    +}
    +
    +static void query_string(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = Qnil;
    +
    +  VALIDATE_MAX_LENGTH(length, QUERY_STRING);
    +
    +  val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_query_string, val);
    +}
    +
    +static void http_version(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE val = rb_str_new(at, length);
    +  rb_hash_aset(req, global_http_version, val);
    +}
    +
    +/** Finalizes the request header to have a bunch of stuff that's
    +  needed. */
    +
    +static void header_done(void *data, const char *at, size_t length)
    +{
    +  VALUE req = (VALUE)data;
    +  VALUE temp = Qnil;
    +  VALUE ctype = Qnil;
    +  VALUE clen = Qnil;
    +  VALUE body = Qnil;
    +  char *colon = NULL;
    +
    +  clen = rb_hash_aref(req, global_http_content_length);
    +  if(clen != Qnil) {
    +    rb_hash_aset(req, global_content_length, clen);
    +    rb_hash_delete(req, global_http_content_length);
    +  }
    +
    +  ctype = rb_hash_aref(req, global_http_content_type);
    +  if(ctype != Qnil) {
    +    rb_hash_aset(req, global_content_type, ctype);
    +    rb_hash_delete(req, global_http_content_type);
    +  }
    +
    +  rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
    +  if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
    +    /* ruby better close strings off with a '\0' dammit */
    +    colon = strchr(RSTRING_PTR(temp), ':');
    +    if(colon != NULL) {
    +      rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING_PTR(temp)));
    +      rb_hash_aset(req, global_server_port, 
    +          rb_str_substr(temp, colon - RSTRING_PTR(temp)+1, 
    +            RSTRING_LEN(temp)));
    +    } else {
    +      rb_hash_aset(req, global_server_name, temp);
    +      rb_hash_aset(req, global_server_port, global_port_80);
    +    }
    +  }
    +
    +  /* grab the initial body and stuff it into the hash */
    +  if(length > 0) {
    +    body = rb_hash_aref(req, global_http_body);
    +    rb_io_write(body, rb_str_new(at, length));
    +  }
    +  
    +  /* according to Rack specs, query string must be empty string if none */
    +  if (rb_hash_aref(req, global_query_string) == Qnil) {
    +    rb_hash_aset(req, global_query_string, global_empty);
    +  }
    +  if (rb_hash_aref(req, global_path_info) == Qnil) {
    +    rb_hash_aset(req, global_path_info, global_empty);
    +  }
    +  
    +  /* set some constants */
    +  rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
    +  rb_hash_aset(req, global_url_scheme, global_url_scheme_value);
    +  rb_hash_aset(req, global_script_name, global_empty);
    +}
    +
    +
    +void Thin_HttpParser_free(void *data) {
    +  TRACE();
    +
    +  if(data) {
    +    free(data);
    +  }
    +}
    +
    +
    +VALUE Thin_HttpParser_alloc(VALUE klass)
    +{
    +  VALUE obj;
    +  http_parser *hp = ALLOC_N(http_parser, 1);
    +  TRACE();
    +  hp->http_field = http_field;
    +  hp->request_method = request_method;
    +  hp->request_uri = request_uri;
    +  hp->fragment = fragment;
    +  hp->request_path = request_path;
    +  hp->query_string = query_string;
    +  hp->http_version = http_version;
    +  hp->header_done = header_done;
    +  thin_http_parser_init(hp);
    +
    +  obj = Data_Wrap_Struct(klass, NULL, Thin_HttpParser_free, hp);
    +
    +  return obj;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.new -> parser
    + *
    + * Creates a new parser.
    + */
    +VALUE Thin_HttpParser_init(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +  thin_http_parser_init(http);
    +
    +  return self;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.reset -> nil
    + *
    + * Resets the parser to it's initial state so that you can reuse it
    + * rather than making new ones.
    + */
    +VALUE Thin_HttpParser_reset(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +  thin_http_parser_init(http);
    +
    +  return Qnil;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.finish -> true/false
    + *
    + * Finishes a parser early which could put in a "good" or bad state.
    + * You should call reset after finish it or bad things will happen.
    + */
    +VALUE Thin_HttpParser_finish(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +  thin_http_parser_finish(http);
    +
    +  return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.execute(req_hash, data, start) -> Integer
    + *
    + * Takes a Hash and a String of data, parses the String of data filling in the Hash
    + * returning an Integer to indicate how much of the data has been read.  No matter
    + * what the return value, you should call HttpParser#finished? and HttpParser#error?
    + * to figure out if it's done parsing or there was an error.
    + * 
    + * This function now throws an exception when there is a parsing error.  This makes 
    + * the logic for working with the parser much easier.  You can still test for an 
    + * error, but now you need to wrap the parser with an exception handling block.
    + *
    + * The third argument allows for parsing a partial request and then continuing
    + * the parsing from that position.  It needs all of the original data as well 
    + * so you have to append to the data buffer as you read.
    + */
    +VALUE Thin_HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
    +{
    +  http_parser *http = NULL;
    +  int from = 0;
    +  char *dptr = NULL;
    +  long dlen = 0;
    +
    +  DATA_GET(self, http_parser, http);
    +
    +  from = FIX2INT(start);
    +  dptr = RSTRING_PTR(data);
    +  dlen = RSTRING_LEN(data);
    +
    +  if(from >= dlen) {
    +    rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
    +  } else {
    +    http->data = (void *)req_hash;
    +    thin_http_parser_execute(http, dptr, dlen, from);
    +
    +    VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
    +
    +    if(thin_http_parser_has_error(http)) {
    +      rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
    +    } else {
    +      return INT2FIX(http_parser_nread(http));
    +    }
    +  }
    +}
    +
    +
    +
    +/**
    + * call-seq:
    + *    parser.error? -> true/false
    + *
    + * Tells you whether the parser is in an error state.
    + */
    +VALUE Thin_HttpParser_has_error(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +
    +  return thin_http_parser_has_error(http) ? Qtrue : Qfalse;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.finished? -> true/false
    + *
    + * Tells you whether the parser is finished or not and in a good state.
    + */
    +VALUE Thin_HttpParser_is_finished(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +
    +  return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
    +}
    +
    +
    +/**
    + * call-seq:
    + *    parser.nread -> Integer
    + *
    + * Returns the amount of data processed so far during this processing cycle.  It is
    + * set to 0 on initialize or reset calls and is incremented each time execute is called.
    + */
    +VALUE Thin_HttpParser_nread(VALUE self)
    +{
    +  http_parser *http = NULL;
    +  DATA_GET(self, http_parser, http);
    +
    +  return INT2FIX(http->nread);
    +}
    +
    +void Init_thin_parser()
    +{
    +
    +  mThin = rb_define_module("Thin");
    +
    +  DEF_GLOBAL(empty, "");
    +  DEF_GLOBAL(http_prefix, "HTTP_");
    +  DEF_GLOBAL(request_method, "REQUEST_METHOD");
    +  DEF_GLOBAL(request_uri, "REQUEST_URI");
    +  DEF_GLOBAL(fragment, "FRAGMENT");
    +  DEF_GLOBAL(query_string, "QUERY_STRING");
    +  DEF_GLOBAL(http_version, "HTTP_VERSION");
    +  DEF_GLOBAL(request_path, "REQUEST_PATH");
    +  DEF_GLOBAL(content_length, "CONTENT_LENGTH");
    +  DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
    +  DEF_GLOBAL(content_type, "CONTENT_TYPE");
    +  DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
    +  DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
    +  DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
    +  DEF_GLOBAL(server_name, "SERVER_NAME");
    +  DEF_GLOBAL(server_port, "SERVER_PORT");
    +  DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
    +  DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
    +  DEF_GLOBAL(http_host, "HTTP_HOST");
    +  DEF_GLOBAL(port_80, "80");
    +  DEF_GLOBAL(http_body, "rack.input");
    +  DEF_GLOBAL(url_scheme, "rack.url_scheme");
    +  DEF_GLOBAL(url_scheme_value, "http");
    +  DEF_GLOBAL(script_name, "SCRIPT_NAME");
    +  DEF_GLOBAL(path_info, "PATH_INFO");
    +
    +  eHttpParserError = rb_define_class_under(mThin, "InvalidRequest", rb_eIOError);
    +
    +  cHttpParser = rb_define_class_under(mThin, "HttpParser", rb_cObject);
    +  rb_define_alloc_func(cHttpParser, Thin_HttpParser_alloc);
    +  rb_define_method(cHttpParser, "initialize", Thin_HttpParser_init,0);
    +  rb_define_method(cHttpParser, "reset", Thin_HttpParser_reset,0);
    +  rb_define_method(cHttpParser, "finish", Thin_HttpParser_finish,0);
    +  rb_define_method(cHttpParser, "execute", Thin_HttpParser_execute,3);
    +  rb_define_method(cHttpParser, "error?", Thin_HttpParser_has_error,0);
    +  rb_define_method(cHttpParser, "finished?", Thin_HttpParser_is_finished,0);
    +  rb_define_method(cHttpParser, "nread", Thin_HttpParser_nread,0);
    +}
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.o b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.o
    new file mode 100644
    index 0000000000..bb9a9cd89d
    Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin.o differ
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin_parser.so b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin_parser.so
    new file mode 100755
    index 0000000000..74ca3142ee
    Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/ext/thin_parser/thin_parser.so differ
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/loader.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/loader.rb
    new file mode 100644
    index 0000000000..4574f28404
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/loader.rb
    @@ -0,0 +1,75 @@
    +module Rack
    +  class AdapterNotFound < RuntimeError; end
    +  
    +  # Mapping used to guess which adapter to use in Adapter.for.
    +  # Framework  =>  in order they will
    +  # be tested.
    +  # +nil+ for value to never guess.
    +  # NOTE: If a framework has a file that is not unique, make sure to place
    +  # it at the end.
    +  ADAPTERS = [
    +    [:rack,    'config.ru'],
    +    [:rails,   'config/environment.rb'],
    +    [:ramaze,  'start.rb'],
    +    [:merb,    'config/init.rb'],
    +    [:file,    nil]
    +  ]
    +  
    +  module Adapter
    +    # Guess which adapter to use based on the directory structure
    +    # or file content.
    +    # Returns a symbol representing the name of the adapter to use
    +    # to load the application under dir/.
    +    def self.guess(dir)
    +      ADAPTERS.each do |adapter, file|
    +        return adapter if file && ::File.exist?(::File.join(dir, file))
    +      end
    +      raise AdapterNotFound, "No adapter found for #{dir}"
    +    end
    +    
    +    # Load a Rack application from a Rack config file (.ru).
    +    def self.load(config)
    +      rackup_code = ::File.read(config)
    +      eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, config)
    +    end
    +    
    +    # Loads an adapter identified by +name+ using +options+ hash.
    +    def self.for(name, options={})
    +      ENV['RACK_ENV'] = options[:environment]
    +      
    +      case name.to_sym
    +      when :rack
    +        return load(::File.join(options[:chdir], "config.ru"))
    +        
    +      when :rails
    +        return Rails.new(options.merge(:root => options[:chdir]))
    +      
    +      when :ramaze
    +        require "#{options[:chdir]}/start"
    +        
    +        Ramaze.trait[:essentials].delete Ramaze::Adapter
    +        Ramaze.start :force => true
    +        
    +        return Ramaze::Adapter::Base
    +        
    +      when :merb
    +        require 'merb-core'
    +        
    +        Merb::Config.setup(:merb_root   => options[:chdir],
    +                           :environment => options[:environment])
    +        Merb.environment = Merb::Config[:environment]
    +        Merb.root = Merb::Config[:merb_root]
    +        Merb::BootLoader.run
    +        
    +        return Merb::Rack::Application.new
    +        
    +      when :file
    +        return Rack::File.new(options[:chdir])
    +        
    +      else
    +        raise AdapterNotFound, "Adapter not found: #{name}"
    +        
    +      end
    +    end
    +  end
    +end
    \ No newline at end of file
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/rails.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/rails.rb
    new file mode 100644
    index 0000000000..0da1b98b4e
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/adapter/rails.rb
    @@ -0,0 +1,178 @@
    +require 'cgi'
    +
    +# Adapter to run a Rails app with any supported Rack handler.
    +# By default it will try to load the Rails application in the
    +# current directory in the development environment.
    +#
    +# Options:
    +#  root: Root directory of the Rails app
    +#  environment: Rails environment to run in (development [default], production or test)
    +#  prefix: Set the relative URL root.
    +#
    +# Based on http://fuzed.rubyforge.org/ Rails adapter
    +module Rack
    +  module Adapter
    +    class Rails
    +      FILE_METHODS = %w(GET HEAD).freeze
    +
    +      def initialize(options = {})
    +        @root   = options[:root]        || Dir.pwd
    +        @env    = options[:environment] || 'development'
    +        @prefix = options[:prefix]
    +
    +        load_application
    +
    +        @rails_app = self.class.rack_based? ? ActionController::Dispatcher.new : CgiApp.new
    +        @file_app  = Rack::File.new(::File.join(RAILS_ROOT, "public"))
    +      end
    +
    +      def load_application
    +        ENV['RAILS_ENV'] = @env
    +
    +        require "#{@root}/config/environment"
    +        require 'dispatcher'
    +
    +        if @prefix
    +          if ActionController::Base.respond_to?(:relative_url_root=)
    +            ActionController::Base.relative_url_root = @prefix # Rails 2.1.1
    +          else
    +            ActionController::AbstractRequest.relative_url_root = @prefix
    +          end
    +        end
    +      end
    +
    +      def file_exist?(path)
    +        full_path = ::File.join(@file_app.root, Utils.unescape(path))
    +        ::File.file?(full_path) && ::File.readable_real?(full_path)
    +      end
    +
    +      def call(env)
    +        path        = env['PATH_INFO'].chomp('/')
    +        method      = env['REQUEST_METHOD']
    +        cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
    +
    +        if FILE_METHODS.include?(method)
    +          if file_exist?(path)              # Serve the file if it's there
    +            return @file_app.call(env)
    +          elsif file_exist?(cached_path)    # Serve the page cache if it's there
    +            env['PATH_INFO'] = cached_path
    +            return @file_app.call(env)
    +          end
    +        end
    +
    +        # No static file, let Rails handle it
    +        @rails_app.call(env)
    +      end
    +
    +      def self.rack_based?
    +        rails_version = ::Rails::VERSION
    +        return false if rails_version::MAJOR < 2
    +        return false if rails_version::MAJOR == 2 && rails_version::MINOR < 2
    +        return false if rails_version::MAJOR == 2 && rails_version::MINOR == 2 && rails_version::TINY < 3
    +        true # >= 2.2.3
    +      end
    +
    +      protected
    +        # For Rails pre Rack (2.3)
    +        class CgiApp
    +          def call(env)
    +            request         = Request.new(env)
    +            response        = Response.new
    +            session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
    +            cgi             = CGIWrapper.new(request, response)
    +
    +            Dispatcher.dispatch(cgi, session_options, response)
    +
    +            response.finish
    +          end
    +        end
    +
    +        class CGIWrapper < ::CGI
    +          def initialize(request, response, *args)
    +            @request  = request
    +            @response = response
    +            @args     = *args
    +            @input    = request.body
    +
    +            super *args
    +          end
    +
    +          def header(options = 'text/html')
    +            if options.is_a?(String)
    +              @response['Content-Type']     = options unless @response['Content-Type']
    +            else
    +              @response['Content-Length']   = options.delete('Content-Length').to_s if options['Content-Length']
    +
    +              @response['Content-Type']     = options.delete('type') || "text/html"
    +              @response['Content-Type']    += '; charset=' + options.delete('charset') if options['charset']
    +
    +              @response['Content-Language'] = options.delete('language') if options['language']
    +              @response['Expires']          = options.delete('expires') if options['expires']
    +
    +              @response.status              = options.delete('Status') if options['Status']
    +
    +              # Convert 'cookie' header to 'Set-Cookie' headers.
    +              # Because Set-Cookie header can appear more the once in the response body,
    +              # we store it in a line break seperated string that will be translated to
    +              # multiple Set-Cookie header by the handler.
    +              if cookie = options.delete('cookie')
    +                cookies = []
    +
    +                case cookie
    +                  when Array then cookie.each { |c| cookies << c.to_s }
    +                  when Hash  then cookie.each { |_, c| cookies << c.to_s }
    +                  else            cookies << cookie.to_s
    +                end
    +
    +                @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
    +
    +                @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
    +                # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
    +                if Thin.ruby_18?
    +                  @response['Set-Cookie'].flatten!
    +                else
    +                  @response['Set-Cookie'] = @response['Set-Cookie'].join("\n")
    +                end
    +              end
    +
    +              options.each { |k, v| @response[k] = v }
    +            end
    +
    +            ''
    +          end
    +
    +          def params
    +            @params ||= @request.params
    +          end
    +
    +          def cookies
    +            @request.cookies
    +          end
    +
    +          def query_string
    +            @request.query_string
    +          end
    +
    +          # Used to wrap the normal args variable used inside CGI.
    +          def args
    +            @args
    +          end
    +
    +          # Used to wrap the normal env_table variable used inside CGI.
    +          def env_table
    +            @request.env
    +          end
    +
    +          # Used to wrap the normal stdinput variable used inside CGI.
    +          def stdinput
    +            @input
    +          end
    +
    +          def stdoutput
    +            STDERR.puts 'stdoutput should not be used.'
    +            @response.body
    +          end
    +      end
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/handler/thin.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/handler/thin.rb
    new file mode 100644
    index 0000000000..bfa03cd543
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/rack/handler/thin.rb
    @@ -0,0 +1,38 @@
    +# frozen_string_literal: true
    +
    +require "thin"
    +require "thin/server"
    +require "thin/logging"
    +require "thin/backends/tcp_server"
    +
    +module Rack
    +  module Handler
    +    class Thin
    +      def self.run(app, **options)
    +        environment  = ENV['RACK_ENV'] || 'development'
    +        default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
    +
    +        host = options.delete(:Host) || default_host
    +        port = options.delete(:Port) || 8080
    +        args = [host, port, app, options]
    +
    +        server = ::Thin::Server.new(*args)
    +        yield server if block_given?
    +
    +        server.start
    +      end
    +
    +      def self.valid_options
    +        environment  = ENV['RACK_ENV'] || 'development'
    +        default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
    +
    +        {
    +          "Host=HOST" => "Hostname to listen on (default: #{default_host})",
    +          "Port=PORT" => "Port to listen on (default: 8080)",
    +        }
    +      end
    +    end
    +
    +    register :thin, ::Rack::Handler::Thin
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin.rb
    new file mode 100644
    index 0000000000..9075e9e270
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin.rb
    @@ -0,0 +1,45 @@
    +require 'fileutils'
    +require 'timeout'
    +require 'stringio'
    +require 'time'
    +require 'forwardable'
    +require 'openssl'
    +require 'eventmachine'
    +require 'rack'
    +
    +module Thin
    +  autoload :Command,            "thin/command"
    +  autoload :Connection,         "thin/connection"
    +  autoload :Daemonizable,       "thin/daemonizing"
    +  autoload :Logging,            "thin/logging"
    +  autoload :Headers,            "thin/headers"
    +  autoload :Request,            "thin/request"
    +  autoload :Response,           "thin/response"
    +  autoload :Runner,             "thin/runner"
    +  autoload :Server,             "thin/server"
    +  autoload :Stats,              "thin/stats"
    +  
    +  module Backends
    +    autoload :Base,             "thin/backends/base"
    +    autoload :SwiftiplyClient,  "thin/backends/swiftiply_client"
    +    autoload :TcpServer,        "thin/backends/tcp_server"
    +    autoload :UnixServer,       "thin/backends/unix_server"
    +  end
    +  
    +  module Controllers
    +    autoload :Cluster,          "thin/controllers/cluster"
    +    autoload :Controller,       "thin/controllers/controller"
    +    autoload :Service,          "thin/controllers/service"
    +  end
    +end
    +
    +require "thin/version"
    +require "thin/statuses"
    +require "rack/adapter/loader"
    +require "thin_parser"
    +
    +module Rack
    +  module Adapter
    +    autoload :Rails, "rack/adapter/rails"
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/base.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/base.rb
    new file mode 100644
    index 0000000000..ad2d63cfd8
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/base.rb
    @@ -0,0 +1,169 @@
    +module Thin
    +  module Backends
    +    # A Backend connects the server to the client. It handles:
    +    # * connection/disconnection to the server
    +    # * initialization of the connections
    +    # * monitoring of the active connections.
    +    #
    +    # == Implementing your own backend
    +    # You can create your own minimal backend by inheriting this class and
    +    # defining the +connect+ and +disconnect+ method.
    +    # If your backend is not based on EventMachine you also need to redefine
    +    # the +start+, +stop+, stop! and +config+ methods.
    +    class Base
    +      # Server serving the connections throught the backend
    +      attr_accessor :server
    +      
    +      # Maximum time for incoming data to arrive
    +      attr_accessor :timeout
    +      
    +      # Maximum number of file or socket descriptors that the server may open.
    +      attr_accessor :maximum_connections
    +      
    +      # Maximum number of connections that can be persistent
    +      attr_accessor :maximum_persistent_connections
    +
    +      #allows setting of the eventmachine threadpool size
    +      attr_reader :threadpool_size
    +      def threadpool_size=(size)
    +        @threadpool_size = size
    +        EventMachine.threadpool_size = size
    +      end
    +
    +      # Allow using threads in the backend.
    +      attr_writer :threaded
    +      def threaded?; @threaded end
    +            
    +      # Allow using SSL in the backend.
    +      attr_writer :ssl, :ssl_options
    +      def ssl?; @ssl end
    +      
    +      # Number of persistent connections currently opened
    +      attr_accessor :persistent_connection_count
    +      
    +      # Disable the use of epoll under Linux
    +      attr_accessor :no_epoll
    +      
    +      def initialize
    +        @connections                    = {}
    +        @timeout                        = Server::DEFAULT_TIMEOUT
    +        @persistent_connection_count    = 0
    +        @maximum_connections            = Server::DEFAULT_MAXIMUM_CONNECTIONS
    +        @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
    +        @no_epoll                       = false
    +        @running                        = false
    +        @ssl                            = nil
    +        @started_reactor                = false
    +        @stopping                       = false
    +        @threaded                       = nil
    +      end
    +      
    +      # Start the backend and connect it.
    +      def start
    +        @stopping = false
    +        starter   = proc do
    +          connect
    +          yield if block_given?
    +          @running = true
    +        end
    +        
    +        # Allow for early run up of eventmachine.
    +        if EventMachine.reactor_running?
    +          starter.call
    +        else
    +          @started_reactor = true
    +          EventMachine.run(&starter)
    +        end
    +      end
    +      
    +      # Stop of the backend from accepting new connections.
    +      def stop
    +        @running  = false
    +        @stopping = true
    +        
    +        # Do not accept anymore connection
    +        disconnect
    +        # Close idle persistent connections
    +        @connections.each_value { |connection| connection.close_connection if connection.idle? }
    +        stop! if @connections.empty?
    +      end
    +      
    +      # Force stop of the backend NOW, too bad for the current connections.
    +      def stop!
    +        @running  = false
    +        @stopping = false
    +        
    +        EventMachine.stop if @started_reactor && EventMachine.reactor_running?
    +        @connections.each_value { |connection| connection.close_connection }
    +        close
    +      end
    +      
    +      # Configure the backend. This method will be called before droping superuser privileges,
    +      # so you can do crazy stuff that require godlike powers here.
    +      def config
    +        # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
    +        EventMachine.epoll unless @no_epoll
    +        
    +        # Set the maximum number of socket descriptors that the server may open.
    +        # The process needs to have required privilege to set it higher the 1024 on
    +        # some systems.
    +        @maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
    +      end
    +      
    +      # Free up resources used by the backend.
    +      def close
    +      end
    +      
    +      # Returns +true+ if the backend is connected and running.
    +      def running?
    +        @running
    +      end
    +
    +      def started_reactor?
    +        @started_reactor
    +      end
    +            
    +      # Called by a connection when it's unbinded.
    +      def connection_finished(connection)
    +        @persistent_connection_count -= 1 if connection.can_persist?
    +        @connections.delete(connection.__id__)
    +        
    +        # Finalize gracefull stop if there's no more active connection.
    +        stop! if @stopping && @connections.empty?
    +      end
    +      
    +      # Returns +true+ if no active connection.
    +      def empty?
    +        @connections.empty?
    +      end
    +      
    +      # Number of active connections.
    +      def size
    +        @connections.size
    +      end
    +      
    +      protected
    +        # Initialize a new connection to a client.
    +        def initialize_connection(connection)
    +          connection.backend                 = self
    +          connection.app                     = @server.app
    +          connection.comm_inactivity_timeout = @timeout
    +          connection.threaded                = @threaded
    +          
    +          if @ssl
    +            connection.start_tls(@ssl_options)
    +          end
    +
    +          # We control the number of persistent connections by keeping
    +          # a count of the total one allowed yet.
    +          if @persistent_connection_count < @maximum_persistent_connections
    +            connection.can_persist!
    +            @persistent_connection_count += 1
    +          end
    +
    +          @connections[connection.__id__] = connection
    +        end
    +      
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/swiftiply_client.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/swiftiply_client.rb
    new file mode 100644
    index 0000000000..b10d017359
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/swiftiply_client.rb
    @@ -0,0 +1,56 @@
    +module Thin
    +  module Backends
    +    # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
    +    class SwiftiplyClient < Base
    +      attr_accessor :key
    +
    +      attr_accessor :host, :port
    +
    +      def initialize(host, port, options = {})
    +        @host = host
    +        @port = port.to_i
    +        @key  = options[:swiftiply].to_s
    +        super()
    +      end
    +
    +      # Connect the server
    +      def connect
    +        EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
    +      end
    +
    +      # Stops the server
    +      def disconnect
    +        EventMachine.stop
    +      end
    +
    +      def to_s
    +        "#{@host}:#{@port} swiftiply"
    +      end
    +    end
    +  end
    +
    +  class SwiftiplyConnection < Connection
    +    def connection_completed
    +      send_data swiftiply_handshake(@backend.key)
    +    end
    +
    +    def persistent?
    +      true
    +    end
    +
    +    def unbind
    +      super
    +      EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
    +    end
    +
    +    protected
    +      def swiftiply_handshake(key)
    +        'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i) }.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
    +      end
    +
    +      # For some reason Swiftiply request the current host
    +      def host_ip
    +        Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0, 0, 0, 0]
    +      end
    +  end
    +end
    \ No newline at end of file
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/tcp_server.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/tcp_server.rb
    new file mode 100644
    index 0000000000..5a70dfcc32
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/tcp_server.rb
    @@ -0,0 +1,34 @@
    +module Thin
    +  module Backends
    +    # Backend to act as a TCP socket server.
    +    class TcpServer < Base
    +      # Address and port on which the server is listening for connections.
    +      attr_accessor :host, :port
    +
    +      def initialize(host, port)
    +        @host = host
    +        @port = port
    +        super()
    +      end
    +
    +      # Connect the server
    +      def connect
    +        @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
    +        binary_name = EventMachine.get_sockname( @signature )
    +        port_name = Socket.unpack_sockaddr_in( binary_name )
    +        @port = port_name[0]
    +        @host = port_name[1]
    +        @signature
    +      end
    +
    +      # Stops the server
    +      def disconnect
    +        EventMachine.stop_server(@signature)
    +      end
    +
    +      def to_s
    +        "#{@host}:#{@port}"
    +      end
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/unix_server.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/unix_server.rb
    new file mode 100644
    index 0000000000..1f5e4910b8
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/backends/unix_server.rb
    @@ -0,0 +1,56 @@
    +module Thin
    +  module Backends
    +    # Backend to act as a UNIX domain socket server.
    +    class UnixServer < Base
    +      # UNIX domain socket on which the server is listening for connections.
    +      attr_accessor :socket
    +      
    +      def initialize(socket)
    +        raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
    +        @socket = socket
    +        super()
    +      end
    +      
    +      # Connect the server
    +      def connect
    +        at_exit { remove_socket_file } # In case it crashes
    +        old_umask = File.umask(0)
    +        begin
    +          EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
    +          # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
    +          #      so we have to go in the internal stuff to find it.
    +        @signature = EventMachine.instance_eval{@acceptors.keys.first}
    +        ensure
    +          File.umask(old_umask)
    +        end
    +      end
    +      
    +      # Stops the server
    +      def disconnect
    +        EventMachine.stop_server(@signature)
    +      end
    +      
    +      # Free up resources used by the backend.
    +      def close
    +        remove_socket_file
    +      end
    +      
    +      def to_s
    +        @socket
    +      end
    +      
    +      protected
    +        def remove_socket_file
    +          File.delete(@socket) if @socket && File.exist?(@socket)
    +        end
    +    end    
    +  end
    +
    +  # Connection through a UNIX domain socket.
    +  class UnixConnection < Connection
    +    protected
    +      def socket_address        
    +        '127.0.0.1' # Unix domain sockets can only be local
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/command.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/command.rb
    new file mode 100644
    index 0000000000..da2ca02fb0
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/command.rb
    @@ -0,0 +1,53 @@
    +require 'open3'
    +
    +module Thin
    +  # Run a command through the +thin+ command-line script.
    +  class Command
    +    include Logging
    +    
    +    class << self
    +      # Path to the +thin+ script used to control the servers.
    +      # Leave this to default to use the one in the path.
    +      attr_accessor :script
    +    end
    +    
    +    def initialize(name, options={})
    +      @name    = name
    +      @options = options
    +    end
    +    
    +    def self.run(*args)
    +      new(*args).run
    +    end
    +    
    +    # Send the command to the +thin+ script
    +    def run
    +      shell_cmd = shellify
    +      trace shell_cmd
    +      trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
    +      Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
    +        log_info stdout.gets until stdout.eof?
    +        log_info stderr.gets until stderr.eof?
    +      end
    +    end
    +    
    +    # Turn into a runnable shell command
    +    def shellify
    +      shellified_options = @options.inject([]) do |args, (name, value)|
    +        option_name = name.to_s.tr("_", "-")
    +        case value
    +        when NilClass,
    +             TrueClass then args << "--#{option_name}"
    +        when FalseClass
    +        when Array     then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
    +        else                args << "--#{option_name}=#{value.inspect}"
    +        end
    +        args
    +      end
    +      
    +      raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
    +      
    +      "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/connection.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/connection.rb
    new file mode 100644
    index 0000000000..ec105b8e17
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/connection.rb
    @@ -0,0 +1,215 @@
    +require 'socket'
    +
    +module Thin
    +  # Connection between the server and client.
    +  # This class is instanciated by EventMachine on each new connection
    +  # that is opened.
    +  class Connection < EventMachine::Connection
    +    include Logging
    +
    +    # This is a template async response. N.B. Can't use string for body on 1.9
    +    AsyncResponse = [-1, {}, []].freeze
    +
    +    # Rack application (adapter) served by this connection.
    +    attr_accessor :app
    +
    +    # Backend to the server
    +    attr_accessor :backend
    +
    +    # Current request served by the connection
    +    attr_accessor :request
    +
    +    # Next response sent through the connection
    +    attr_accessor :response
    +
    +    # Calling the application in a threaded allowing
    +    # concurrent processing of requests.
    +    attr_writer :threaded
    +
    +    # Get the connection ready to process a request.
    +    def post_init
    +      @request  = Request.new
    +      @response = Response.new
    +    end
    +
    +    # Called when data is received from the client.
    +    def receive_data(data)
    +      @idle = false
    +      trace data
    +      process if @request.parse(data)
    +    rescue InvalidRequest => e
    +      log_error("Invalid request", e)
    +      post_process Response::BAD_REQUEST
    +    end
    +
    +    # Called when all data was received and the request
    +    # is ready to be processed.
    +    def process
    +      if threaded?
    +        @request.threaded = true
    +        EventMachine.defer { post_process(pre_process) }
    +      else
    +        @request.threaded = false
    +        post_process(pre_process)
    +      end
    +    end
    +
    +    def ssl_verify_peer(cert)
    +      # In order to make the cert available later we have to have made at least
    +      # a show of verifying it.
    +      true
    +    end
    +
    +    def pre_process
    +      # Add client info to the request env
    +      @request.remote_address = remote_address
    +
    +      # Connection may be closed unless the App#call response was a [-1, ...]
    +      # It should be noted that connection objects will linger until this
    +      # callback is no longer referenced, so be tidy!
    +      @request.async_callback = method(:post_process)
    +
    +      if @backend.ssl?
    +        @request.env["rack.url_scheme"] = "https"
    +
    +        if cert = get_peer_cert
    +          @request.env['rack.peer_cert'] = cert
    +        end
    +      end
    +
    +      # When we're under a non-async framework like rails, we can still spawn
    +      # off async responses using the callback info, so there's little point
    +      # in removing this.
    +      response = AsyncResponse
    +      catch(:async) do
    +        # Process the request calling the Rack adapter
    +        response = @app.call(@request.env)
    +      end
    +      response
    +    rescue Exception => e
    +      unexpected_error(e)
    +      # Pass through error response
    +      can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
    +    end
    +
    +    def post_process(result)
    +      return unless result
    +      result = result.to_a
    +
    +      # Status code -1 indicates that we're going to respond later (async).
    +      return if result.first == AsyncResponse.first
    +
    +      @response.status, @response.headers, @response.body = *result
    +
    +      log_error("Rack application returned nil body. " \
    +                "Probably you wanted it to be an empty string?") if @response.body.nil?
    +
    +      # HEAD requests should not return a body.
    +      @response.skip_body! if @request.head?
    +
    +      # Make the response persistent if requested by the client
    +      @response.persistent! if @request.persistent?
    +
    +      # Send the response
    +      @response.each do |chunk|
    +        trace chunk
    +        send_data chunk
    +      end
    +
    +    rescue Exception => e
    +      unexpected_error(e)
    +      # Close connection since we can't handle response gracefully
    +      close_connection
    +    ensure
    +      # If the body is being deferred, then terminate afterward.
    +      if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
    +        @response.body.callback { terminate_request }
    +        @response.body.errback  { terminate_request }
    +      else
    +        # Don't terminate the response if we're going async.
    +        terminate_request unless result && result.first == AsyncResponse.first
    +      end
    +    end
    +
    +    # Logs information about an unexpected exceptional condition
    +    def unexpected_error(e)
    +      log_error("Unexpected error while processing request", e)
    +    end
    +
    +    def close_request_response
    +      @request.async_close.succeed if @request.async_close
    +      @request.close  rescue nil
    +      @response.close rescue nil
    +    end
    +
    +    # Does request and response cleanup (closes open IO streams and
    +    # deletes created temporary files).
    +    # Re-initializes response and request if client supports persistent
    +    # connection.
    +    def terminate_request
    +      unless persistent?
    +        close_connection_after_writing rescue nil
    +        close_request_response
    +      else
    +        close_request_response
    +        # Connection become idle but it's still open
    +        @idle = true
    +        # Prepare the connection for another request if the client
    +        # supports HTTP pipelining (persistent connection).
    +        post_init
    +      end
    +    end
    +
    +    # Called when the connection is unbinded from the socket
    +    # and can no longer be used to process requests.
    +    def unbind
    +      @request.async_close.succeed if @request.async_close
    +      @response.body.fail if @response.body.respond_to?(:fail)
    +      @backend.connection_finished(self)
    +    end
    +
    +    # Allows this connection to be persistent.
    +    def can_persist!
    +      @can_persist = true
    +    end
    +
    +    # Return +true+ if this connection is allowed to stay open and be persistent.
    +    def can_persist?
    +      @can_persist
    +    end
    +
    +    # Return +true+ if the connection must be left open
    +    # and ready to be reused for another request.
    +    def persistent?
    +      @can_persist && @response.persistent?
    +    end
    +
    +    # Return +true+ if the connection is open but is not
    +    # processing any user requests
    +    def idle?
    +      @idle
    +    end
    +
    +    # +true+ if app.call will be called inside a thread.
    +    # You can set all requests as threaded setting Connection#threaded=true
    +    # or on a per-request case returning +true+ in app.deferred?.
    +    def threaded?
    +      @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
    +    end
    +
    +    # IP Address of the remote client.
    +    def remote_address
    +      socket_address
    +    rescue Exception => e
    +      log_error('Could not infer remote address', e)
    +      nil
    +    end
    +
    +    protected
    +      # Returns IP address of peer as a string.
    +      def socket_address
    +        peer = get_peername
    +        Socket.unpack_sockaddr_in(peer)[1] if peer
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/cluster.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/cluster.rb
    new file mode 100644
    index 0000000000..33692c11c0
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/cluster.rb
    @@ -0,0 +1,178 @@
    +require 'socket'
    +
    +module Thin
    +  # An exception class to handle the event that server didn't start on time
    +  class RestartTimeout < RuntimeError; end
    +  
    +  module Controllers
    +    # Control a set of servers.
    +    # * Generate start and stop commands and run them.
    +    # * Inject the port or socket number in the pid and log filenames.
    +    # Servers are started throught the +thin+ command-line script.
    +    class Cluster < Controller
    +      # Cluster only options that should not be passed in the command sent
    +      # to the indiviual servers.
    +      CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
    +      
    +      # Maximum wait time for the server to be restarted
    +      DEFAULT_WAIT_TIME = 30    # seconds
    +      
    +      # Create a new cluster of servers launched using +options+.
    +      def initialize(options)
    +        super
    +        # Cluster can only contain daemonized servers
    +        @options.merge!(:daemonize => true)
    +      end
    +      
    +      def first_port; @options[:port]     end
    +      def address;    @options[:address]  end
    +      def socket;     @options[:socket]   end
    +      def pid_file;   @options[:pid]      end
    +      def log_file;   @options[:log]      end
    +      def size;       @options[:servers]  end
    +      def only;       @options[:only]     end
    +      def onebyone;   @options[:onebyone] end
    +      def wait;       @options[:wait]     end
    +      
    +      def swiftiply?
    +        @options.has_key?(:swiftiply)
    +      end
    +    
    +      # Start the servers
    +      def start
    +        with_each_server { |n| start_server n }
    +      end
    +    
    +      # Start a single server
    +      def start_server(number)
    +        log_info "Starting server on #{server_id(number)} ... "
    +      
    +        run :start, number
    +      end
    +  
    +      # Stop the servers
    +      def stop
    +        with_each_server { |n| stop_server n }
    +      end
    +    
    +      # Stop a single server
    +      def stop_server(number)
    +        log_info "Stopping server on #{server_id(number)} ... "
    +      
    +        run :stop, number
    +      end
    +    
    +      # Stop and start the servers.
    +      def restart
    +        unless onebyone
    +          # Let's do a normal restart by defaults
    +          stop
    +          sleep 0.1 # Let's breath a bit shall we ?
    +          start
    +        else
    +          with_each_server do |n| 
    +            stop_server(n)
    +            sleep 0.1 # Let's breath a bit shall we ?
    +            start_server(n)
    +            wait_until_server_started(n)
    +          end
    +        end
    +      end
    +      
    +      def test_socket(number)
    +        if socket
    +          UNIXSocket.new(socket_for(number))
    +        else
    +          TCPSocket.new(address, number)
    +        end
    +      rescue
    +        nil
    +      end
    +      
    +      # Make sure the server is running before moving on to the next one.
    +      def wait_until_server_started(number)
    +        log_info "Waiting for server to start ..."
    +        STDOUT.flush # Need this to make sure user got the message
    +        
    +        tries = 0
    +        loop do
    +          if test_socket = test_socket(number)
    +            test_socket.close
    +            break
    +          elsif tries < wait
    +            sleep 1
    +            tries += 1
    +          else
    +            raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
    +                                  "for more information, or set the value of 'wait' in your config " +
    +                                  "file to be higher (defaults: 30)."
    +          end
    +        end
    +      end
    +    
    +      def server_id(number)
    +        if socket
    +          socket_for(number)
    +        elsif swiftiply?
    +          [address, first_port, number].join(':')
    +        else
    +          [address, number].join(':')
    +        end
    +      end
    +    
    +      def log_file_for(number)
    +        include_server_number log_file, number
    +      end
    +    
    +      def pid_file_for(number)
    +        include_server_number pid_file, number
    +      end
    +    
    +      def socket_for(number)
    +        include_server_number socket, number
    +      end
    +    
    +      def pid_for(number)
    +        File.read(pid_file_for(number)).chomp.to_i
    +      end
    +      
    +      private
    +        # Send the command to the +thin+ script
    +        def run(cmd, number)
    +          cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
    +          cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
    +          if socket
    +            cmd_options.merge!(:socket => socket_for(number))
    +          elsif swiftiply?
    +            cmd_options.merge!(:port => first_port)
    +          else
    +            cmd_options.merge!(:port => number)
    +          end
    +          Command.run(cmd, cmd_options)
    +        end
    +      
    +        def with_each_server
    +          if only
    +            if first_port && only < 80
    +              # interpret +only+ as a sequence number
    +              yield first_port + only
    +            else
    +              # interpret +only+ as an absolute port number
    +              yield only
    +            end
    +          elsif socket || swiftiply?
    +            size.times { |n| yield n }
    +          else
    +            size.times { |n| yield first_port + n }
    +          end
    +        end
    +      
    +        # Add the server port or number in the filename
    +        # so each instance get its own file
    +        def include_server_number(path, number)
    +          ext = File.extname(path)
    +          path.gsub(/#{ext}$/, ".#{number}#{ext}")
    +        end
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/controller.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/controller.rb
    new file mode 100644
    index 0000000000..bb590265df
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/controller.rb
    @@ -0,0 +1,189 @@
    +require 'yaml'
    +
    +module Thin
    +  # Error raised that will abort the process and print not backtrace.
    +  class RunnerError < RuntimeError; end
    +  
    +  # Raised when a mandatory option is missing to run a command.
    +  class OptionRequired < RunnerError
    +    def initialize(option)
    +      super("#{option} option required")
    +    end
    +  end
    +  
    +  # Raised when an option is not valid.
    +  class InvalidOption < RunnerError; end
    +  
    +  # Build and control Thin servers.
    +  # Hey Controller pattern is not only for web apps yo!
    +  module Controllers  
    +    # Controls one Thin server.
    +    # Allow to start, stop, restart and configure a single thin server.
    +    class Controller
    +      include Logging
    +    
    +      # Command line options passed to the thin script
    +      attr_accessor :options
    +    
    +      def initialize(options)
    +        @options = options
    +        
    +        if @options[:socket]
    +          @options.delete(:address)
    +          @options.delete(:port)
    +        end
    +      end
    +    
    +      def start
    +        # Constantize backend class
    +        @options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
    +
    +        server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
    +                            @options[:port],                         # Port ignored on UNIX socket
    +                            @options)
    +        
    +        # Set options
    +        server.pid_file                       = @options[:pid]
    +        server.log_file                       = @options[:log]
    +        server.timeout                        = @options[:timeout]
    +        server.maximum_connections            = @options[:max_conns]
    +        server.maximum_persistent_connections = @options[:max_persistent_conns]
    +        server.threaded                       = @options[:threaded]
    +        server.no_epoll                       = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
    +        server.threadpool_size                = @options[:threadpool_size] if server.threaded?
    +
    +        # ssl support
    +        if @options[:ssl]
    +          server.ssl = true
    +          server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
    +        end
    +
    +        # Detach the process, after this line the current process returns
    +        server.daemonize if @options[:daemonize]
    +
    +        # +config+ must be called before changing privileges since it might require superuser power.
    +        server.config
    +        
    +        server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
    +
    +        # If a Rack config file is specified we eval it inside a Rack::Builder block to create
    +        # a Rack adapter from it. Or else we guess which adapter to use and load it.
    +        if @options[:rackup]
    +          server.app = load_rackup_config
    +        else
    +          server.app = load_adapter
    +        end
    +
    +        # If a prefix is required, wrap in Rack URL mapper
    +        server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
    +
    +        # If a stats URL is specified, wrap in Stats adapter
    +        server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
    +
    +        # Register restart procedure which just start another process with same options,
    +        # so that's why this is done here.
    +        server.on_restart { Command.run(:start, @options) }
    +
    +        server.start
    +      end
    +    
    +      def stop
    +        raise OptionRequired, :pid unless @options[:pid]
    +      
    +        tail_log(@options[:log]) do
    +          if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
    +            wait_for_file :deletion, @options[:pid]
    +          end
    +        end
    +      end
    +    
    +      def restart
    +        raise OptionRequired, :pid unless @options[:pid]
    +        
    +        tail_log(@options[:log]) do
    +          if Server.restart(@options[:pid])
    +            wait_for_file :creation, @options[:pid]
    +          end
    +        end
    +      end
    +    
    +      def config
    +        config_file = @options.delete(:config) || raise(OptionRequired, :config)
    +
    +        # Stringify keys
    +        @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
    +
    +        File.open(config_file, 'w') { |f| f << @options.to_yaml }
    +        log_info "Wrote configuration to #{config_file}"
    +      end
    +      
    +      protected
    +        # Wait for a pid file to either be created or deleted.
    +        def wait_for_file(state, file)
    +          Timeout.timeout(@options[:timeout] || 30) do
    +            case state
    +            when :creation then sleep 0.1 until File.exist?(file)
    +            when :deletion then sleep 0.1 while File.exist?(file)
    +            end
    +          end
    +        end
    +        
    +        # Tail the log file of server +number+ during the execution of the block.        
    +        def tail_log(log_file)
    +          if log_file
    +            tail_thread = tail(log_file)
    +            yield
    +            tail_thread.kill
    +          else
    +            yield
    +          end
    +        end
    +        
    +        # Acts like GNU tail command. Taken from Rails.
    +        def tail(file)
    +          cursor = File.exist?(file) ? File.size(file) : 0
    +          last_checked = Time.now
    +          tail_thread = Thread.new do
    +            Thread.pass until File.exist?(file)
    +            File.open(file, 'r') do |f|
    +              loop do
    +                f.seek cursor
    +                if f.mtime > last_checked
    +                  last_checked = f.mtime
    +                  contents = f.read
    +                  cursor += contents.length
    +                  print contents
    +                  STDOUT.flush
    +                end
    +                sleep 0.1
    +              end
    +            end
    +          end
    +          sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
    +          tail_thread
    +        end
    +
    +      private
    +        def load_adapter
    +          adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
    +          log_info "Using #{adapter} adapter"
    +          Rack::Adapter.for(adapter, @options)
    +        rescue Rack::AdapterNotFound => e
    +          raise InvalidOption, e.message
    +        end
    +        
    +        def load_rackup_config
    +          ENV['RACK_ENV'] = @options[:environment]
    +          case @options[:rackup]
    +          when /\.rb$/
    +            Kernel.load(@options[:rackup])
    +            Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
    +          when /\.ru$/
    +            Rack::Adapter.load(@options[:rackup])
    +          else
    +            raise "Invalid rackup file.  please specify either a .ru or .rb file"
    +          end
    +        end
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.rb
    new file mode 100644
    index 0000000000..ea2de8f3e7
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.rb
    @@ -0,0 +1,76 @@
    +require 'erb'
    +
    +module Thin
    +  module Controllers
    +    # System service controller to launch all servers which
    +    # config files are in a directory.
    +    class Service < Controller
    +      INITD_PATH          = File.directory?('/etc/rc.d') ? '/etc/rc.d/thin' : '/etc/init.d/thin'
    +      DEFAULT_CONFIG_PATH = '/etc/thin'
    +      TEMPLATE            = File.dirname(__FILE__) + '/service.sh.erb'
    +    
    +      def initialize(options)
    +        super
    +      
    +        raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
    +      end
    +    
    +      def config_path
    +        @options[:all] || DEFAULT_CONFIG_PATH
    +      end
    +    
    +      def start
    +        run :start
    +      end
    +    
    +      def stop
    +        run :stop
    +      end
    +    
    +      def restart
    +        run :restart
    +      end
    +    
    +      def install(config_files_path=DEFAULT_CONFIG_PATH)
    +        if File.exist?(INITD_PATH)
    +          log_info "Thin service already installed at #{INITD_PATH}"
    +        else
    +          log_info "Installing thin service at #{INITD_PATH} ..."
    +          sh "mkdir -p #{File.dirname(INITD_PATH)}"
    +          log_info "writing #{INITD_PATH}"        
    +          File.open(INITD_PATH, 'w') do |f|
    +            f << ERB.new(File.read(TEMPLATE)).result(binding)
    +          end
    +          sh "chmod +x #{INITD_PATH}" # Make executable
    +        end
    +      
    +        sh "mkdir -p #{config_files_path}"
    +
    +        log_info ''
    +        log_info "To configure thin to start at system boot:"
    +        log_info "on RedHat like systems:"
    +        log_info "  sudo /sbin/chkconfig --level 345 #{NAME} on"
    +        log_info "on Debian-like systems (Ubuntu):"
    +        log_info "  sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
    +        log_info "on Gentoo:"
    +        log_info "  sudo rc-update add #{NAME} default"
    +        log_info ''
    +        log_info "Then put your config files in #{config_files_path}"
    +      end
    +    
    +      private
    +        def run(command)
    +          Dir[config_path + '/*'].each do |config|
    +            next if config.end_with?("~")
    +            log_info "[#{command}] #{config} ..."
    +            Command.run(command, :config => config, :daemonize => true)
    +          end
    +        end
    +      
    +        def sh(cmd)
    +          log_info cmd
    +          system(cmd)
    +        end
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.sh.erb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.sh.erb
    new file mode 100644
    index 0000000000..5b96548caa
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/controllers/service.sh.erb
    @@ -0,0 +1,39 @@
    +#!/bin/sh
    +### BEGIN INIT INFO
    +# Provides:          thin
    +# Required-Start:    $local_fs $remote_fs
    +# Required-Stop:     $local_fs $remote_fs
    +# Default-Start:     2 3 4 5
    +# Default-Stop:      S 0 1 6
    +# Short-Description: thin initscript
    +# Description:       thin
    +### END INIT INFO
    +
    +# Original author: Forrest Robertson
    +
    +# Do NOT "set -e"
    +
    +DAEMON=<%= Command.script %>
    +SCRIPT_NAME=<%= INITD_PATH %>
    +CONFIG_PATH=<%= config_files_path %>
    +
    +# Exit if the package is not installed
    +[ -x "$DAEMON" ] || exit 0
    +
    +case "$1" in
    +  start)
    +	$DAEMON start --all $CONFIG_PATH
    +	;;
    +  stop)
    +	$DAEMON stop --all $CONFIG_PATH
    +	;;
    +  restart)
    +	$DAEMON restart --all $CONFIG_PATH
    +	;;
    +  *)
    +	echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
    +	exit 3
    +	;;
    +esac
    +
    +:
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/daemonizing.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/daemonizing.rb
    new file mode 100644
    index 0000000000..82387439cf
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/daemonizing.rb
    @@ -0,0 +1,199 @@
    +require 'etc'
    +require 'daemons' unless Thin.win?
    +
    +module Process
    +  # Returns +true+ the process identied by +pid+ is running.
    +  def running?(pid)
    +    Process.getpgid(pid) != -1
    +  rescue Errno::EPERM
    +    true
    +  rescue Errno::ESRCH
    +    false
    +  end
    +  module_function :running?
    +end
    +
    +module Thin
    +  # Raised when the pid file already exist starting as a daemon.
    +  class PidFileExist < RuntimeError; end
    +  class PidFileNotFound < RuntimeError; end
    +  
    +  # Module included in classes that can be turned into a daemon.
    +  # Handle stuff like:
    +  # * storing the PID in a file
    +  # * redirecting output to the log file
    +  # * changing process privileges
    +  # * killing the process gracefully
    +  module Daemonizable
    +    attr_accessor :pid_file, :log_file
    +    
    +    def self.included(base)
    +      base.extend ClassMethods
    +    end
    +
    +    def pid
    +      File.exist?(pid_file) && !File.zero?(pid_file) ? open(pid_file).read.to_i : nil
    +    end
    +
    +    def kill(timeout = 60)
    +      if File.exist?(@pid_file)
    +        self.class.kill(@pid_file, timeout)
    +      end
    +    end
    +
    +    # Turns the current script into a daemon process that detaches from the console.
    +    def daemonize
    +      raise PlatformNotSupported, 'Daemonizing is not supported on Windows'     if Thin.win?
    +      raise ArgumentError,        'You must specify a pid_file to daemonize' unless @pid_file
    +      
    +      remove_stale_pid_file
    +      
    +      pwd = Dir.pwd # Current directory is changed during daemonization, so store it
    +      
    +      # HACK we need to create the directory before daemonization to prevent a bug under 1.9
    +      #      ignoring all signals when the directory is created after daemonization.
    +      FileUtils.mkdir_p File.dirname(@pid_file)
    +      FileUtils.mkdir_p File.dirname(@log_file)
    +      
    +      Daemonize.daemonize(File.expand_path(@log_file), name)
    +      
    +      Dir.chdir(pwd)
    +      
    +      write_pid_file
    +
    +      at_exit do
    +        log_info "Exiting!"
    +        remove_pid_file
    +      end
    +    end
    +
    +    # Change privileges of the process
    +    # to the specified user and group.
    +    def change_privilege(user, group=user)
    +      log_info "Changing process privilege to #{user}:#{group}"
    +      
    +      uid, gid = Process.euid, Process.egid
    +      target_uid = Etc.getpwnam(user).uid
    +      target_gid = Etc.getgrnam(group).gid
    +
    +      if uid != target_uid || gid != target_gid
    +        # Change PID file ownership
    +        File.chown(target_uid, target_gid, @pid_file) if File.exists?(@pid_file)
    +
    +        # Change process ownership
    +        Process.initgroups(user, target_gid)
    +        Process::GID.change_privilege(target_gid)
    +        Process::UID.change_privilege(target_uid)
    +
    +        # Correct environment variables
    +        ENV.store('USER', user)
    +        ENV.store('HOME', File.expand_path("~#{user}"))
    +      end
    +    rescue Errno::EPERM => e
    +      log_info "Couldn't change user and group to #{user}:#{group}: #{e}"
    +    end
    +    
    +    # Register a proc to be called to restart the server.
    +    def on_restart(&block)
    +      @on_restart = block
    +    end
    +    
    +    # Restart the server.
    +    def restart
    +      if @on_restart
    +        log_info 'Restarting ...'
    +        stop
    +        remove_pid_file
    +        @on_restart.call
    +        EM.next_tick { exit! }
    +      end
    +    end
    +    
    +    module ClassMethods
    +      # Send a QUIT or INT (if timeout is +0+) signal the process which
    +      # PID is stored in +pid_file+.
    +      # If the process is still running after +timeout+, KILL signal is
    +      # sent.
    +      def kill(pid_file, timeout=60)
    +        if timeout == 0
    +          send_signal('INT', pid_file, timeout)
    +        else
    +          send_signal('QUIT', pid_file, timeout)
    +        end
    +      end
    +      
    +      # Restart the server by sending HUP signal.
    +      def restart(pid_file)
    +        send_signal('HUP', pid_file)
    +      end
    +
    +      def monotonic_time
    +        Process.clock_gettime(Process::CLOCK_MONOTONIC)
    +      end
    +
    +      # Send a +signal+ to the process which PID is stored in +pid_file+.
    +      def send_signal(signal, pid_file, timeout=60)
    +        if pid = read_pid_file(pid_file)
    +          Logging.log_info "Sending #{signal} signal to process #{pid} ... "
    +
    +          Process.kill(signal, pid)
    +
    +          # This loop seems kind of racy to me...
    +          started_at = monotonic_time
    +          while Process.running?(pid)
    +            sleep 0.1
    +            raise Timeout::Error if (monotonic_time - started_at) > timeout
    +          end
    +        else
    +          raise PidFileNotFound, "Can't stop process, no PID found in #{pid_file}"
    +        end
    +      rescue Timeout::Error
    +        Logging.log_info "Timeout!"
    +        force_kill(pid, pid_file)
    +      rescue Interrupt
    +        force_kill(pid, pid_file)
    +      rescue Errno::ESRCH # No such process
    +        Logging.log_info "process not found!"
    +        force_kill(pid, pid_file)
    +      end
    +      
    +      def force_kill(pid, pid_file)
    +        Logging.log_info "Sending KILL signal to process #{pid} ... "
    +        Process.kill("KILL", pid)
    +        File.delete(pid_file) if File.exist?(pid_file)
    +      end
    +      
    +      def read_pid_file(file)
    +        if File.file?(file) && pid = File.read(file)
    +          pid.to_i
    +        else
    +          nil
    +        end
    +      end
    +    end
    +    
    +    protected
    +      def remove_pid_file
    +        File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
    +      end
    +    
    +      def write_pid_file
    +        log_info "Writing PID to #{@pid_file}"
    +        open(@pid_file,"w") { |f| f.write(Process.pid) }
    +        File.chmod(0644, @pid_file)
    +      end
    +      
    +      # If PID file is stale, remove it.
    +      def remove_stale_pid_file
    +        if File.exist?(@pid_file)
    +          if pid && Process.running?(pid)
    +            raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
    +                                "Stop the process or delete #{@pid_file}."
    +          else
    +            log_info "Deleting stale PID file #{@pid_file}"
    +            remove_pid_file
    +          end
    +        end
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/headers.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/headers.rb
    new file mode 100644
    index 0000000000..4f773b44e9
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/headers.rb
    @@ -0,0 +1,47 @@
    +module Thin
    +  # Raised when an header is not valid
    +  # and the server can not process it.
    +  class InvalidHeader < StandardError; end
    +
    +  # Store HTTP header name-value pairs direcly to a string
    +  # and allow duplicated entries on some names.
    +  class Headers
    +    HEADER_FORMAT      = "%s: %s\r\n".freeze
    +    ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
    +    CR_OR_LF           = /[\r\n]/.freeze
    +    
    +    def initialize
    +      @sent = {}
    +      @out = []
    +    end
    +    
    +    # Add key: value pair to the headers.
    +    # Ignore if already sent and no duplicates are allowed
    +    # for this +key+.
    +    def []=(key, value)
    +      downcase_key = key.downcase
    +      if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
    +        @sent[downcase_key] = true
    +        value = case value
    +                when Time
    +                  value.httpdate
    +                when NilClass
    +                  return
    +                when CR_OR_LF
    +                  raise InvalidHeader, "Header contains CR or LF"
    +                else
    +                  value.to_s
    +                end
    +        @out << HEADER_FORMAT % [key, value]
    +      end
    +    end
    +    
    +    def has_key?(key)
    +      @sent[key.downcase]
    +    end
    +    
    +    def to_s
    +      @out.join
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/logging.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/logging.rb
    new file mode 100644
    index 0000000000..cf927a9a76
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/logging.rb
    @@ -0,0 +1,174 @@
    +require 'logger'
    +
    +module Thin
    +  # To be included in classes to allow some basic logging
    +  # that can be silenced (Logging.silent=) or made
    +  # more verbose.
    +  # Logging.trace=:  log all raw request and response and
    +  #                           messages logged with +trace+.
    +  # Logging.silent=: silence all log all log messages
    +  #                           altogether.
    +  module Logging
    +    # Simple formatter which only displays the message.
    +    # Taken from ActiveSupport
    +    class SimpleFormatter < Logger::Formatter
    +      def call(severity, timestamp, progname, msg)
    +        "#{timestamp} #{String === msg ? msg : msg.inspect}\n"
    +      end
    +    end
    +
    +    @trace_logger = nil
    +
    +    class << self
    +      attr_reader :logger
    +      attr_reader :trace_logger
    +
    +      def trace=(enabled)
    +        if enabled
    +          @trace_logger ||= Logger.new(STDOUT)
    +        else
    +          @trace_logger = nil
    +        end
    +      end
    +
    +      def trace?
    +        !@trace_logger.nil?
    +      end
    +
    +      def silent=(shh)
    +        if shh
    +          @logger = nil
    +        else
    +          @logger ||= Logger.new(STDOUT)
    +        end
    +      end
    +
    +      def silent?
    +        !@logger.nil?
    +      end
    +
    +      def level
    +        @logger ? @logger.level : nil # or 'silent'
    +      end
    +
    +      def level=(value)
    +        # If logging has been silenced, then re-enable logging
    +        @logger = Logger.new(STDOUT) if @logger.nil?
    +        @logger.level = value
    +      end
    +
    +      # Allow user to specify a custom logger to use.
    +      # This object must respond to:
    +      # +level+, +level=+ and +debug+, +info+, +warn+, +error+, +fatal+
    +      def logger=(custom_logger)
    +        [ :level   ,
    +          :level=  ,
    +          :debug   ,
    +          :info    ,
    +          :warn    ,
    +          :error   ,
    +          :fatal   ,
    +          :unknown ,
    +        ].each do |method|
    +          if not custom_logger.respond_to?(method)
    +            raise ArgumentError, "logger must respond to #{method}"
    +          end
    +        end
    +
    +        @logger = custom_logger
    +      end
    +
    +      def trace_logger=(custom_tracer)
    +        [ :level   ,
    +          :level=  ,
    +          :debug   ,
    +          :info    ,
    +          :warn    ,
    +          :error   ,
    +          :fatal   ,
    +          :unknown ,
    +        ].each do |method|
    +          if not custom_tracer.respond_to?(method)
    +            raise ArgumentError, "trace logger must respond to #{method}"
    +          end
    +        end
    +
    +        @trace_logger = custom_tracer
    +      end
    +
    +      def log_msg(msg, level=Logger::INFO)
    +        return unless @logger
    +        @logger.add(level, msg)
    +      end
    +
    +      def trace_msg(msg)
    +        return unless @trace_logger
    +        @trace_logger.info(msg)
    +      end
    +
    +      # Provided for backwards compatibility.
    +      # Callers should be using the +level+ (on the +Logging+ module
    +      # or on the instance) to figure out what the log level is.
    +      def debug?
    +        self.level == Logger::DEBUG
    +      end
    +      def debug=(val)
    +        self.level = (val ? Logger::DEBUG : Logger::INFO)
    +      end
    +
    +    end # module methods
    +
    +    # Default logger to stdout.
    +    self.logger           = Logger.new(STDOUT)
    +    self.logger.level     = Logger::INFO
    +    self.logger.formatter = Logging::SimpleFormatter.new
    +
    +    def silent
    +      Logging.silent?
    +    end
    +
    +    def silent=(value)
    +      Logging.silent = value
    +    end
    +
    +    # Log a message if tracing is activated
    +    def trace(msg=nil)
    +      Logging.trace_msg(msg) if msg
    +    end
    +    module_function :trace
    +    public :trace
    +
    +    # Log a message at DEBUG level
    +    def log_debug(msg=nil)
    +      Logging.log_msg(msg || yield, Logger::DEBUG)
    +    end
    +    module_function :log_debug
    +    public :log_debug
    +
    +    # Log a message at INFO level
    +    def log_info(msg)
    +      Logging.log_msg(msg || yield, Logger::INFO)
    +    end
    +    module_function :log_info
    +    public :log_info
    +
    +    # Log a message at ERROR level (and maybe a backtrace)
    +    def log_error(msg, e=nil)
    +      log_msg = msg
    +      if e
    +        log_msg += ": #{e}\n\t" + e.backtrace.join("\n\t") + "\n"
    +      end
    +      Logging.log_msg(log_msg, Logger::ERROR)
    +    end
    +    module_function :log_error
    +    public :log_error
    +
    +    # For backwards compatibility
    +    def log msg
    +      STDERR.puts('#log has been deprecated, please use the ' \
    +                  'log_level function instead (e.g. - log_info).')
    +      log_info(msg)
    +    end
    +
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/request.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/request.rb
    new file mode 100644
    index 0000000000..b5efe24bbb
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/request.rb
    @@ -0,0 +1,164 @@
    +require 'tempfile'
    +
    +module Thin
    +  # Raised when an incoming request is not valid
    +  # and the server can not process it.
    +  class InvalidRequest < IOError; end
    +
    +  # A request sent by the client to the server.
    +  class Request
    +    # Maximum request body size before it is moved out of memory
    +    # and into a tempfile for reading.
    +    MAX_BODY          = 1024 * (80 + 32)
    +    BODY_TMPFILE      = 'thin-body'.freeze
    +    MAX_HEADER        = 1024 * (80 + 32)
    +
    +    INITIAL_BODY      = String.new
    +    # Force external_encoding of request's body to ASCII_8BIT
    +    INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!) && defined?(Encoding::ASCII_8BIT)
    +
    +    # Freeze some HTTP header names & values
    +    SERVER_SOFTWARE   = 'SERVER_SOFTWARE'.freeze
    +    SERVER_NAME       = 'SERVER_NAME'.freeze
    +    REQUEST_METHOD    = 'REQUEST_METHOD'.freeze
    +    LOCALHOST         = 'localhost'.freeze
    +    HTTP_VERSION      = 'HTTP_VERSION'.freeze
    +    HTTP_1_0          = 'HTTP/1.0'.freeze
    +    REMOTE_ADDR       = 'REMOTE_ADDR'.freeze
    +    CONTENT_LENGTH    = 'CONTENT_LENGTH'.freeze
    +    CONNECTION        = 'HTTP_CONNECTION'.freeze
    +    KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
    +    CLOSE_REGEXP      = /\bclose\b/i.freeze
    +    HEAD              = 'HEAD'.freeze
    +
    +    # Freeze some Rack header names
    +    RACK_INPUT        = 'rack.input'.freeze
    +    RACK_VERSION      = 'rack.version'.freeze
    +    RACK_ERRORS       = 'rack.errors'.freeze
    +    RACK_MULTITHREAD  = 'rack.multithread'.freeze
    +    RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
    +    RACK_RUN_ONCE     = 'rack.run_once'.freeze
    +    ASYNC_CALLBACK    = 'async.callback'.freeze
    +    ASYNC_CLOSE       = 'async.close'.freeze
    +
    +    # CGI-like request environment variables
    +    attr_reader :env
    +
    +    # Unparsed data of the request
    +    attr_reader :data
    +
    +    # Request body
    +    attr_reader :body
    +
    +    def initialize
    +      @parser   = Thin::HttpParser.new
    +      @data     = String.new
    +      @nparsed  = 0
    +      @body     = StringIO.new(INITIAL_BODY.dup)
    +      @env      = {
    +        SERVER_SOFTWARE   => SERVER,
    +        SERVER_NAME       => LOCALHOST,
    +
    +        # Rack stuff
    +        RACK_INPUT        => @body,
    +
    +        RACK_VERSION      => VERSION::RACK,
    +        RACK_ERRORS       => STDERR,
    +
    +        RACK_MULTITHREAD  => false,
    +        RACK_MULTIPROCESS => false,
    +        RACK_RUN_ONCE     => false
    +      }
    +    end
    +
    +    # Parse a chunk of data into the request environment
    +    # Raises an +InvalidRequest+ if invalid.
    +    # Returns +true+ if the parsing is complete.
    +    def parse(data)
    +      if data.size > 0 && finished? # headers and body already fully satisfied. more data is erroneous.
    +        raise InvalidRequest, 'Content longer than specified'
    +      elsif @parser.finished?  # Header finished, can only be some more body
    +        @body << data
    +      else                  # Parse more header using the super parser
    +        @data << data
    +        raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
    +
    +        @nparsed = @parser.execute(@env, @data, @nparsed)
    +
    +        # Transfer to a tempfile if body is very big
    +        move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
    +      end
    +
    +
    +      if finished?   # Check if header and body are complete
    +        @data = nil
    +        @body.rewind
    +        true         # Request is fully parsed
    +      else
    +        false        # Not finished, need more data
    +      end
    +    end
    +
    +    # +true+ if headers and body are finished parsing
    +    def finished?
    +      @parser.finished? && @body.size >= content_length
    +    end
    +
    +    # Expected size of the body
    +    def content_length
    +      @env[CONTENT_LENGTH].to_i
    +    end
    +
    +    # Returns +true+ if the client expects the connection to be persistent.
    +    def persistent?
    +      # Clients and servers SHOULD NOT assume that a persistent connection
    +      # is maintained for HTTP versions less than 1.1 unless it is explicitly
    +      # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
    +      if @env[HTTP_VERSION] == HTTP_1_0
    +        @env[CONNECTION] =~ KEEP_ALIVE_REGEXP
    +
    +      # HTTP/1.1 client intends to maintain a persistent connection unless
    +      # a Connection header including the connection-token "close" was sent
    +      # in the request
    +      else
    +        @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
    +      end
    +    end
    +
    +    def remote_address=(address)
    +      @env[REMOTE_ADDR] = address
    +    end
    +
    +    def threaded=(value)
    +      @env[RACK_MULTITHREAD] = value
    +    end
    +
    +    def async_callback=(callback)
    +      @env[ASYNC_CALLBACK] = callback
    +      @env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
    +    end
    +
    +    def async_close
    +      @async_close ||= @env[ASYNC_CLOSE]
    +    end
    +
    +    def head?
    +      @env[REQUEST_METHOD] == HEAD
    +    end
    +
    +    # Close any resource used by the request
    +    def close
    +      @body.close! if @body.class == Tempfile
    +    end
    +
    +    private
    +      def move_body_to_tempfile
    +        current_body = @body
    +        current_body.rewind
    +        @body = Tempfile.new(BODY_TMPFILE)
    +        @body.binmode
    +        @body << current_body.read
    +        @env[RACK_INPUT] = @body
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/response.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/response.rb
    new file mode 100644
    index 0000000000..7a5ce93ca3
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/response.rb
    @@ -0,0 +1,117 @@
    +module Thin
    +  # A response sent to the client.
    +  class Response
    +    CONNECTION     = 'Connection'.freeze
    +    CLOSE          = 'close'.freeze
    +    KEEP_ALIVE     = 'keep-alive'.freeze
    +    SERVER         = 'Server'.freeze
    +    CONTENT_LENGTH = 'Content-Length'.freeze
    +
    +    PERSISTENT_STATUSES  = [100, 101].freeze
    +
    +    #Error Responses
    +    ERROR            = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
    +    PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
    +    BAD_REQUEST      = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze
    +
    +    # Status code
    +    attr_accessor :status
    +
    +    # Response body, must respond to +each+.
    +    attr_accessor :body
    +
    +    # Headers key-value hash
    +    attr_reader   :headers
    +
    +    def initialize
    +      @headers    = Headers.new
    +      @status     = 200
    +      @persistent = false
    +      @skip_body  = false
    +    end
    +
    +    # String representation of the headers
    +    # to be sent in the response.
    +    def headers_output
    +      # Set default headers
    +      @headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
    +      @headers[SERVER]     = Thin::NAME unless @headers.has_key?(SERVER)
    +
    +      @headers.to_s
    +    end
    +
    +    # Top header of the response,
    +    # containing the status code and response headers.
    +    def head
    +      "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
    +    end
    +
    +    if Thin.ruby_18?
    +
    +      # Ruby 1.8 implementation.
    +      # Respects Rack specs.
    +      #
    +      # See http://rack.rubyforge.org/doc/files/SPEC.html
    +      def headers=(key_value_pairs)
    +        key_value_pairs.each do |k, vs|
    +          vs.each { |v| @headers[k] = v.chomp } if vs
    +        end if key_value_pairs
    +      end
    +
    +    else
    +
    +      # Ruby 1.9 doesn't have a String#each anymore.
    +      # Rack spec doesn't take care of that yet, for now we just use
    +      # +each+ but fallback to +each_line+ on strings.
    +      # I wish we could remove that condition.
    +      # To be reviewed when a new Rack spec comes out.
    +      def headers=(key_value_pairs)
    +        key_value_pairs.each do |k, vs|
    +          next unless vs
    +          if vs.is_a?(String)
    +            vs.each_line { |v| @headers[k] = v.chomp }
    +          else
    +            vs.each { |v| @headers[k] = v.chomp }
    +          end
    +        end if key_value_pairs
    +      end
    +
    +    end
    +
    +    # Close any resource used by the response
    +    def close
    +      @body.close if @body.respond_to?(:close)
    +    end
    +
    +    # Yields each chunk of the response.
    +    # To control the size of each chunk
    +    # define your own +each+ method on +body+.
    +    def each
    +      yield head
    +
    +      unless @skip_body
    +        if @body.is_a?(String)
    +          yield @body
    +        else
    +          @body.each { |chunk| yield chunk }
    +        end
    +      end
    +    end
    +
    +    # Tell the client the connection should stay open
    +    def persistent!
    +      @persistent = true
    +    end
    +
    +    # Persistent connection must be requested as keep-alive
    +    # from the server and have a Content-Length, or the response
    +    # status must require that the connection remain open.
    +    def persistent?
    +      (@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
    +    end
    +
    +    def skip_body!
    +      @skip_body = true
    +    end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/runner.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/runner.rb
    new file mode 100644
    index 0000000000..be654626e3
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/runner.rb
    @@ -0,0 +1,238 @@
    +require 'logger'
    +require 'optparse'
    +require 'yaml'
    +require 'erb'
    +
    +module Thin
    +  # CLI runner.
    +  # Parse options and send command to the correct Controller.
    +  class Runner
    +    COMMANDS            = %w(start stop restart config)
    +    LINUX_ONLY_COMMANDS = %w(install)
    +
    +    # Commands that wont load options from the config file
    +    CONFIGLESS_COMMANDS = %w(config install)
    +
    +    # Parsed options
    +    attr_accessor :options
    +
    +    # Name of the command to be runned.
    +    attr_accessor :command
    +
    +    # Arguments to be passed to the command.
    +    attr_accessor :arguments
    +
    +    # Return all available commands
    +    def self.commands
    +      commands  = COMMANDS
    +      commands += LINUX_ONLY_COMMANDS if Thin.linux?
    +      commands
    +    end
    +
    +    def initialize(argv)
    +      @argv = argv
    +
    +      # Default options values
    +      @options = {
    +        :chdir                => Dir.pwd,
    +        :environment          => ENV['RACK_ENV'] || 'development',
    +        :address              => '0.0.0.0',
    +        :port                 => Server::DEFAULT_PORT,
    +        :timeout              => Server::DEFAULT_TIMEOUT,
    +        :log                  => File.join(Dir.pwd, 'log/thin.log'),
    +        :pid                  => 'tmp/pids/thin.pid',
    +        :max_conns            => Server::DEFAULT_MAXIMUM_CONNECTIONS,
    +        :max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
    +        :require              => [],
    +        :wait                 => Controllers::Cluster::DEFAULT_WAIT_TIME,
    +        :threadpool_size      => 20
    +      }
    +
    +      parse!
    +    end
    +
    +    def parser
    +      # NOTE: If you add an option here make sure the key in the +options+ hash is the
    +      # same as the name of the command line option.
    +      # +option+ keys are used to build the command line to launch other processes,
    +      # see lib/thin/command.rb.
    +      @parser ||= OptionParser.new do |opts|
    +        opts.banner = "Usage: thin [options] #{self.class.commands.join('|')}"
    +
    +        opts.separator ""
    +        opts.separator "Server options:"
    +
    +        opts.on("-a", "--address HOST", "bind to HOST address " +
    +                                        "(default: #{@options[:address]})")             { |host| @options[:address] = host }
    +        opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})")          { |port| @options[:port] = port.to_i }
    +        opts.on("-S", "--socket FILE", "bind to unix domain socket")                    { |file| @options[:socket] = file }
    +        opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply")                       { |key| @options[:swiftiply] = key }
    +        opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
    +                                        "(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
    +        opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
    +                                       "Rack adapter")                                  { |file| @options[:rackup] = file }
    +        opts.on("-c", "--chdir DIR", "Change to dir before starting")                   { |dir| @options[:chdir] = File.expand_path(dir) }
    +        opts.on(      "--stats PATH", "Mount the Stats adapter under PATH")             { |path| @options[:stats] = path }
    +
    +        opts.separator ""
    +        opts.separator "SSL options:"
    +
    +        opts.on(      "--ssl", "Enables SSL")                                           { @options[:ssl] = true }
    +        opts.on(      "--ssl-key-file PATH", "Path to private key")                     { |path| @options[:ssl_key_file] = path }
    +        opts.on(      "--ssl-cert-file PATH", "Path to certificate")                    { |path| @options[:ssl_cert_file] = path }
    +        opts.on(      "--ssl-disable-verify", "Disables (optional) client cert requests") { @options[:ssl_disable_verify] = true }
    +        opts.on(      "--ssl-version VERSION", "TLSv1, TLSv1_1, TLSv1_2")               { |version| @options[:ssl_version] = version }
    +        opts.on(      "--ssl-cipher-list STRING", "Example: HIGH:!ADH:!RC4:-MEDIUM:-LOW:-EXP:-CAMELLIA") { |cipher| @options[:ssl_cipher_list] = cipher }
    +
    +        opts.separator ""
    +        opts.separator "Adapter options:"
    +        opts.on("-e", "--environment ENV", "Framework environment " +
    +                                           "(default: #{@options[:environment]})")      { |env| @options[:environment] = env }
    +        opts.on(      "--prefix PATH", "Mount the app under PATH (start with /)")       { |path| @options[:prefix] = path }
    +
    +        unless Thin.win? # Daemonizing not supported on Windows
    +          opts.separator ""
    +          opts.separator "Daemon options:"
    +
    +          opts.on("-d", "--daemonize", "Run daemonized in the background")              { @options[:daemonize] = true }
    +          opts.on("-l", "--log FILE", "File to redirect output " +
    +                                      "(default: #{@options[:log]})")                   { |file| @options[:log] = file }
    +          opts.on("-P", "--pid FILE", "File to store PID " +
    +                                      "(default: #{@options[:pid]})")                   { |file| @options[:pid] = file }
    +          opts.on("-u", "--user NAME", "User to run daemon as (use with -g)")           { |user| @options[:user] = user }
    +          opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)")         { |group| @options[:group] = group }
    +          opts.on(      "--tag NAME", "Additional text to display in process listing")  { |tag| @options[:tag] = tag }
    +
    +          opts.separator ""
    +          opts.separator "Cluster options:"
    +
    +          opts.on("-s", "--servers NUM", "Number of servers to start")                  { |num| @options[:servers] = num.to_i }
    +          opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
    +          opts.on("-C", "--config FILE", "Load options from config file")               { |file| @options[:config] = file }
    +          opts.on(      "--all [DIR]", "Send command to each config files in DIR")      { |dir| @options[:all] = dir } if Thin.linux?
    +          opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
    +          opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
    +        end
    +
    +        opts.separator ""
    +        opts.separator "Tuning options:"
    +
    +        opts.on("-b", "--backend CLASS", "Backend to use, full classname")              { |name| @options[:backend] = name }
    +        opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
    +                                       "(default: #{@options[:timeout]})")              { |sec| @options[:timeout] = sec.to_i }
    +        opts.on("-f", "--force", "Force the execution of the command")                  { @options[:force] = true }
    +        opts.on(      "--max-conns NUM", "Maximum number of open file descriptors " +
    +                                         "(default: #{@options[:max_conns]})",
    +                                         "Might require sudo to set higher than 1024")  { |num| @options[:max_conns] = num.to_i } unless Thin.win?
    +        opts.on(      "--max-persistent-conns NUM",
    +                                       "Maximum number of persistent connections",
    +                                       "(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
    +        opts.on(      "--threaded", "Call the Rack application in threads " +
    +                                    "[experimental]")                                   { @options[:threaded] = true }
    +        opts.on(      "--threadpool-size NUM", "Sets the size of the EventMachine threadpool.",
    +                                       "(default: #{@options[:threadpool_size]})") { |num| @options[:threadpool_size] = num.to_i }
    +        opts.on(      "--no-epoll", "Disable the use of epoll")                         { @options[:no_epoll] = true } if Thin.linux?
    +
    +        opts.separator ""
    +        opts.separator "Common options:"
    +
    +        opts.on_tail("-r", "--require FILE", "require the library")                     { |file| @options[:require] << file }
    +        opts.on_tail("-q", "--quiet", "Silence all logging")                            { @options[:quiet] = true }
    +        opts.on_tail("-D", "--debug", "Enable debug logging")                           { @options[:debug] = true }
    +        opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)")      { @options[:trace] = true }
    +        opts.on_tail("-h", "--help", "Show this message")                               { puts opts; exit }
    +        opts.on_tail('-v', '--version', "Show version")                                 { puts Thin::SERVER; exit }
    +      end
    +    end
    +
    +    # Parse the options.
    +    def parse!
    +      parser.parse! @argv
    +      @command   = @argv.shift
    +      @arguments = @argv
    +    end
    +
    +    # Parse the current shell arguments and run the command.
    +    # Exits on error.
    +    def run!
    +      if self.class.commands.include?(@command)
    +        run_command
    +      elsif @command.nil?
    +        puts "Command required"
    +        puts @parser
    +        exit 1
    +      else
    +        abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
    +      end
    +    end
    +
    +    # Send the command to the controller: single instance or cluster.
    +    def run_command
    +      load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
    +
    +      # PROGRAM_NAME is relative to the current directory, so make sure
    +      # we store and expand it before changing directory.
    +      Command.script = File.expand_path($PROGRAM_NAME)
    +
    +      # Change the current directory ASAP so that all relative paths are
    +      # relative to this one.
    +      Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
    +
    +      @options[:require].each { |r| ruby_require r }
    +
    +      # Setup the logger
    +      if @options[:quiet]
    +        Logging.silent = true
    +      else
    +        Logging.level = Logger::DEBUG if @options[:debug]
    +      end
    +
    +      if @options[:trace]
    +        # Trace raw requests/responses
    +        Logging.trace_logger = Logging.logger
    +      end
    +
    +      controller = case
    +      when cluster? then Controllers::Cluster.new(@options)
    +      when service? then Controllers::Service.new(@options)
    +      else               Controllers::Controller.new(@options)
    +      end
    +
    +      if controller.respond_to?(@command)
    +        begin
    +          controller.send(@command, *@arguments)
    +        rescue RunnerError => e
    +          abort e.message
    +        end
    +      else
    +        abort "Invalid options for command: #{@command}"
    +      end
    +    end
    +
    +    # +true+ if we're controlling a cluster.
    +    def cluster?
    +      @options[:only] || @options[:servers] || @options[:config]
    +    end
    +
    +    # +true+ if we're acting a as system service.
    +    def service?
    +      @options.has_key?(:all) || @command == 'install'
    +    end
    +
    +    private
    +      def load_options_from_config_file!
    +        if file = @options.delete(:config)
    +          YAML.load(ERB.new(File.read(file)).result).each { |key, value| @options[key.to_sym] = value }
    +        end
    +      end
    +
    +      def ruby_require(file)
    +        if File.extname(file) == '.ru'
    +          warn 'WARNING: Use the -R option to load a Rack config file'
    +          @options[:rackup] = file
    +        else
    +          require file
    +        end
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/server.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/server.rb
    new file mode 100644
    index 0000000000..295c7f41be
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/server.rb
    @@ -0,0 +1,290 @@
    +module Thin
    +  # The utterly famous Thin HTTP server.
    +  # It listens for incoming requests through a given +backend+
    +  # and forwards all requests to +app+.
    +  #
    +  # == TCP server
    +  # Create a new TCP server bound to host:port by specifiying +host+
    +  # and +port+ as the first 2 arguments.
    +  #
    +  #   Thin::Server.start('0.0.0.0', 3000, app)
    +  #
    +  # == UNIX domain server
    +  # Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
    +  # as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a /
    +  # it will be assumed to be a UNIX socket. 
    +  #
    +  #   Thin::Server.start('/tmp/thin.sock', app)
    +  #
    +  # == Using a custom backend
    +  # You can implement your own way to connect the server to its client by creating your
    +  # own Backend class and passing it as the :backend option.
    +  #
    +  #   Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
    +  #
    +  # == Rack application (+app+)
    +  # All requests will be processed through +app+, which must be a valid Rack adapter.
    +  # A valid Rack adapter (application) must respond to call(env#Hash) and
    +  # return an array of [status, headers, body].
    +  #
    +  # == Building an app in place
    +  # If a block is passed, a Rack::Builder instance
    +  # will be passed to build the +app+. So you can do cool stuff like this:
    +  # 
    +  #   Thin::Server.start('0.0.0.0', 3000) do
    +  #     use Rack::CommonLogger
    +  #     use Rack::ShowExceptions
    +  #     map "/lobster" do
    +  #       use Rack::Lint
    +  #       run Rack::Lobster.new
    +  #     end
    +  #   end
    +  #
    +  # == Controlling with signals
    +  # * INT and TERM: Force shutdown (see Server#stop!)
    +  # * TERM & QUIT calls +stop+ to shutdown gracefully.
    +  # * HUP calls +restart+ to ... surprise, restart!
    +  # * USR1 reopen log files.
    +  # Signals are processed at one second intervals.
    +  # Disable signals by passing :signals => false.
    +  # 
    +  class Server
    +    include Logging
    +    include Daemonizable
    +    extend  Forwardable
    +    
    +    # Default values
    +    DEFAULT_TIMEOUT                        = 30 #sec
    +    DEFAULT_HOST                           = '0.0.0.0'
    +    DEFAULT_PORT                           = 3000
    +    DEFAULT_MAXIMUM_CONNECTIONS            = 1024
    +    DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 100
    +        
    +    # Application (Rack adapter) called with the request that produces the response.
    +    attr_accessor :app
    +
    +    # A tag that will show in the process listing
    +    attr_accessor :tag
    +
    +    # Backend handling the connections to the clients.
    +    attr_accessor :backend
    +    
    +    # Maximum number of seconds for incoming data to arrive before the connection
    +    # is dropped.
    +    def_delegators :backend, :timeout, :timeout=
    +    
    +    # Maximum number of file or socket descriptors that the server may open.
    +    def_delegators :backend, :maximum_connections, :maximum_connections=
    +    
    +    # Maximum number of connections that can be persistent at the same time.
    +    # Most browsers never close the connection so most of the time they are closed
    +    # when the timeout occurs. If we don't control the number of persistent connections,
    +    # it would be very easy to overflow the server for a DoS attack.
    +    def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
    +    
    +    # Allow using threads in the backend.
    +    def_delegators :backend, :threaded?, :threaded=, :threadpool_size, :threadpool_size=
    +    
    +    # Allow using SSL in the backend.
    +    def_delegators :backend, :ssl?, :ssl=, :ssl_options=
    +    
    +    # Address and port on which the server is listening for connections.
    +    def_delegators :backend, :host, :port
    +    
    +    # UNIX domain socket on which the server is listening for connections.
    +    def_delegator :backend, :socket
    +    
    +    # Disable the use of epoll under Linux
    +    def_delegators :backend, :no_epoll, :no_epoll=
    +    
    +    def initialize(*args, &block)
    +      host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
    +      
    +      # Guess each parameter by its type so they can be
    +      # received in any order.
    +      args.each do |arg|
    +        case arg
    +        when 0.class, /^\d+$/ then port    = arg.to_i
    +        when String           then host    = arg
    +        when Hash             then options = arg
    +        else
    +          @app = arg if arg.respond_to?(:call)
    +        end
    +      end
    +      
    +      # Set tag if needed
    +      self.tag = options[:tag]
    +
    +      # Try to intelligently select which backend to use.
    +      @backend = select_backend(host, port, options)
    +      
    +      load_cgi_multipart_eof_fix
    +      
    +      @backend.server = self
    +      
    +      # Set defaults
    +      @backend.maximum_connections            = DEFAULT_MAXIMUM_CONNECTIONS
    +      @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
    +      @backend.timeout                        = options[:timeout] || DEFAULT_TIMEOUT
    +      
    +      # Allow using Rack builder as a block
    +      @app = Rack::Builder.new(&block).to_app if block
    +      
    +      # If in debug mode, wrap in logger adapter
    +      @app = Rack::CommonLogger.new(@app) if Logging.debug?
    +      
    +      @setup_signals = options[:signals] != false
    +    end
    +    
    +    # Lil' shortcut to turn this:
    +    # 
    +    #   Server.new(...).start
    +    # 
    +    # into this:
    +    # 
    +    #   Server.start(...)
    +    # 
    +    def self.start(*args, &block)
    +      new(*args, &block).start!
    +    end
    +        
    +    # Start the server and listen for connections.
    +    def start
    +      raise ArgumentError, 'app required' unless @app
    +      
    +      log_info  "Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
    +      log_debug "Debugging ON"
    +      trace     "Tracing ON"
    +      
    +      log_info "Maximum connections set to #{@backend.maximum_connections}"
    +      log_info "Listening on #{@backend}, CTRL+C to stop"
    +
    +      @backend.start { setup_signals if @setup_signals }
    +    end
    +    alias :start! :start
    +    
    +    # == Gracefull shutdown
    +    # Stops the server after processing all current connections.
    +    # As soon as this method is called, the server stops accepting
    +    # new requests and waits for all current connections to finish.
    +    # Calling twice is the equivalent of calling stop!.
    +    def stop
    +      if running?
    +        @backend.stop
    +        unless @backend.empty?
    +          log_info "Waiting for #{@backend.size} connection(s) to finish, "\
    +                   "can take up to #{timeout} sec, CTRL+C to stop now"
    +        end
    +      else
    +        stop!
    +      end
    +    end
    +    
    +    # == Force shutdown
    +    # Stops the server closing all current connections right away.
    +    # This doesn't wait for connection to finish their work and send data.
    +    # All current requests will be dropped.
    +    def stop!
    +      if @backend.started_reactor?
    +        log_info "Stopping ..."
    +      else
    +        log_info "Stopping Thin ..."
    +        log_info "Thin was started inside an existing EventMachine.run block."
    +        log_info "Call `EventMachine.stop` to stop the reactor and quit the process."
    +      end
    +
    +      @backend.stop!
    +    end
    +    
    +    # == Reopen log file.
    +    # Reopen the log file and redirect STDOUT and STDERR to it.
    +    def reopen_log
    +      return unless log_file
    +      file = File.expand_path(log_file)
    +      log_info "Reopening log file: #{file}"
    +      Daemonize.redirect_io(file)
    +    end
    +    
    +    # == Configure the server
    +    # The process might need to have superuser privilege to configure
    +    # server with optimal options.
    +    def config
    +      @backend.config
    +    end
    +    
    +    # Name of the server and type of backend used.
    +    # This is also the name of the process in which Thin is running as a daemon.
    +    def name
    +      "thin server (#{@backend})" + (tag ? " [#{tag}]" : "")
    +    end
    +    alias :to_s :name
    +    
    +    # Return +true+ if the server is running and ready to receive requests.
    +    # Note that the server might still be running and return +false+ when
    +    # shuting down and waiting for active connections to complete.
    +    def running?
    +      @backend.running?
    +    end
    +    
    +    protected
    +      def setup_signals
    +        # Queue up signals so they are processed in non-trap context
    +        # using a EM timer.
    +        @signal_queue ||= []
    +
    +        %w( INT TERM ).each do |signal|
    +          trap(signal) { @signal_queue.push signal }
    +        end
    +        # *nix only signals
    +        %w( QUIT HUP USR1 ).each do |signal|
    +          trap(signal) { @signal_queue.push signal }
    +        end unless Thin.win?
    +
    +        # Signals are processed at one second intervals.
    +        @signal_timer ||= EM.add_periodic_timer(1) { handle_signals }
    +      end
    +
    +      def handle_signals
    +        case @signal_queue.shift
    +        when 'INT'
    +          stop!
    +        when 'TERM', 'QUIT'
    +          stop
    +        when 'HUP'
    +          restart
    +        when 'USR1'
    +          reopen_log
    +        end
    +        EM.next_tick { handle_signals } unless @signal_queue.empty?
    +      end
    +      
    +      def select_backend(host, port, options)
    +        case
    +        when options.has_key?(:backend)
    +          raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
    +          options[:backend].new(host, port, options)
    +        when options.has_key?(:swiftiply)
    +          Backends::SwiftiplyClient.new(host, port, options)
    +        when host.include?('/')
    +          Backends::UnixServer.new(host)
    +        else
    +          Backends::TcpServer.new(host, port)
    +        end
    +      end
    +      
    +      # Taken from Mongrel cgi_multipart_eof_fix
    +      # Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
    +      def load_cgi_multipart_eof_fix
    +        version = RUBY_VERSION.split('.').map { |i| i.to_i }
    +        
    +        if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
    +          begin
    +            require 'cgi_multipart_eof_fix'
    +          rescue LoadError
    +            log_error "Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
    +            log_error "gem install cgi_multipart_eof_fix"
    +          end
    +        end
    +      end
    +  end
    +end
    diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.html.erb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.html.erb
    new file mode 100644
    index 0000000000..14338bf15e
    --- /dev/null
    +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.html.erb
    @@ -0,0 +1,216 @@
    +<%#
    +# Taken from Rack::ShowException
    +# adapted from Django 
    +# Copyright (c) 2005, the Lawrence Journal-World
    +# Used under the modified BSD license:
    +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
    +%>
    +
    +
    +
    +  
    +  
    +  Thin Stats
    +  
    +
    +
    +
    +
    +

    Server stats

    +

    <%= Thin::SERVER %>

    + + + + + + + + + +
    Uptime<%= Time.now - @start_time %> sec
    PID<%=h Process.pid %>
    + + <% if @last_request %> +

    Jump to:

    + + <% end %> +
    + +
    +

    Requests

    +

    Stats

    + + + + + + + + + + + + + + + + + +
    Requests<%= @requests %>
    Finished<%= @requests_finished %>
    Errors<%= @requests - @requests_finished %>
    Last request<%= @last_request_time %> sec
    +
    + +<% if @last_request %> +
    +

    Last Request information

    + +

    GET

    + <% unless @last_request.GET.empty? %> + + + + + + + + + <% @last_request.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No GET data.

    + <% end %> + +

    POST

    + <% unless @last_request.POST.empty? %> + + + + + + + + + <% @last_request.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No POST data.

    + <% end %> + + + + <% unless @last_request.cookies.empty? %> + + + + + + + + + <% @last_request.cookies.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No cookie data.

    + <% end %> + +

    Rack ENV

    + + + + + + + + + <% @last_request.env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val %>
    + +
    +<% end %> + +
    +

    + You're seeing this page because you use Thin::Stats. +

    +
    + + + \ No newline at end of file diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.rb new file mode 100644 index 0000000000..572523631d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/stats.rb @@ -0,0 +1,52 @@ +require 'erb' + +module Thin + module Stats + # Rack adapter to log stats about a Rack application. + class Adapter + include ERB::Util + + def initialize(app, path='/stats') + @app = app + @path = path + + @template = ERB.new(File.read(File.dirname(__FILE__) + '/stats.html.erb')) + + @requests = 0 + @requests_finished = 0 + @start_time = Time.now + end + + def call(env) + if env['PATH_INFO'].index(@path) == 0 + serve(env) + else + log(env) { @app.call(env) } + end + end + + def log(env) + @requests += 1 + @last_request = Rack::Request.new(env) + request_started_at = Time.now + + response = yield + + @requests_finished += 1 + @last_request_time = Time.now - request_started_at + + response + end + + def serve(env) + body = @template.result(binding) + + [ + 200, + { 'Content-Type' => 'text/html' }, + [body] + ] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/statuses.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/statuses.rb new file mode 100644 index 0000000000..2548e7a6fd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/statuses.rb @@ -0,0 +1,48 @@ +module Thin + # Every standard HTTP code mapped to the appropriate message. + # Stolent from Mongrel. + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 422 => 'Unprocessable Entity', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 511 => 'Network Authentication Required' + } +end diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/version.rb b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/version.rb new file mode 100644 index 0000000000..44de854524 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin/version.rb @@ -0,0 +1,32 @@ +module Thin + # Raised when a feature is not supported on the + # current platform. + class PlatformNotSupported < RuntimeError; end + + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 8 + TINY = 1 + + STRING = [MAJOR, MINOR, TINY].join('.') + + CODENAME = "Infinite Smoothie".freeze + + RACK = [1, 0].freeze # Rack protocol version + end + + NAME = 'thin'.freeze + SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze + + def self.win? + RUBY_PLATFORM =~ /mswin|mingw/ + end + + def self.linux? + RUBY_PLATFORM =~ /linux/ + end + + def self.ruby_18? + RUBY_VERSION =~ /^1\.8/ + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin_parser.so b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin_parser.so new file mode 100755 index 0000000000..74ca3142ee Binary files /dev/null and b/vendor/bundle/ruby/3.0.0/gems/thin-1.8.1/lib/thin_parser.so differ diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/COPYING b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/COPYING new file mode 100644 index 0000000000..7ef2588d6d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/COPYING @@ -0,0 +1,18 @@ +Copyright (c) 2010-2016 Ryan Tomayko + +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 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.0.0/gems/tilt-2.0.10/bin/tilt b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/bin/tilt new file mode 100755 index 0000000000..97d6366d66 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/bin/tilt @@ -0,0 +1,122 @@ +#!/usr/bin/env ruby +require 'ostruct' +require 'optparse' +require 'tilt' + +usage = < +Process template and write output to stdout. With no or +when is '-', read template from stdin and use the --type option +to determine the template's type. + +Options + -l, --list List template engines + file patterns and exit + -t, --type= Use this template engine; required if no + -y, --layout= Use as a layout template + + -D= Define variable as + -d, --define-file= Load YAML from and use for variables + --vars= Evaluate to Hash and use for variables + + -h, --help Show this help message + +Convert markdown to HTML: + $ tilt foo.markdown > foo.html + +Process ERB template: + $ echo "Answer: <%= 2 + 2 %>" | tilt -t erb + Answer: 4 + +Define variables: + $ echo "Answer: <%= 2 + n %>" | tilt -t erb --vars="{:n=>40}" + Answer: 42 + $ echo "Answer: <%= 2 + n.to_i %>" | tilt -t erb -Dn=40 + Answer: 42 +USAGE + +script_name = File.basename($0) +pattern = nil +layout = nil +locals = {} + +ARGV.options do |o| + o.program_name = script_name + + # list all available template engines + o.on("-l", "--list") do + groups = {} + Tilt.lazy_map.each do |pattern,engines| + engines.each do |engine| + key = engine[0].split('::').last.sub(/Template$/, '') + (groups[key] ||= []) << pattern + end + end + groups.sort { |(k1,v1),(k2,v2)| k1 <=> k2 }.each do |engine,files| + printf "%-15s %s\n", engine, files.sort.join(', ') + end + exit + end + + # the template type / pattern + o.on("-t", "--type=PATTERN", String) do |val| + abort "unknown template type: #{val}" if Tilt[val].nil? + pattern = val + end + + # pass template output into the specified layout template + o.on("-y", "--layout=FILE", String) do |file| + paths = [file, "~/.tilt/#{file}", "/etc/tilt/#{file}"] + layout = paths. + map { |p| File.expand_path(p) }. + find { |p| File.exist?(p) } + abort "no such layout: #{file}" if layout.nil? + end + + # define a local variable + o.on("-D", "--define=PAIR", String) do |pair| + key, value = pair.split(/[=:]/, 2) + locals[key.to_sym] = value + end + + # define local variables from YAML or JSON + o.on("-d", "--define-file=FILE", String) do |file| + require 'yaml' + abort "no such define file: #{file}" unless File.exist? file + hash = File.open(file, 'r:bom|utf-8') { |f| YAML.load f, file } + abort "vars must be a Hash, not #{hash.inspect}" if !hash.is_a?(Hash) + hash.each { |key, value| locals[key.to_sym] = value } + end + + # define local variables using a Ruby hash + o.on("--vars=RUBY") do |ruby| + hash = eval(ruby) + abort "vars must be a Hash, not #{hash.inspect}" if !hash.is_a?(Hash) + hash.each { |key, value| locals[key.to_sym] = value } + end + + o.on_tail("-h", "--help") { puts usage; exit } + + o.parse! +end + +file = ARGV.first || '-' +pattern = file if pattern.nil? +abort "template type not given. see: #{$0} --help" if ['-', ''].include?(pattern) + +engine = Tilt[pattern] +abort "template engine not found for: #{pattern}" if engine.nil? + +template = + engine.new(file) { + if file == '-' + $stdin.read + else + File.read(file) + end + } +output = template.render(self, locals) + +# process layout +output = Tilt.new(layout).render(self, locals) { output } if layout + +$stdout.write(output) diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt.rb new file mode 100644 index 0000000000..35d93f0345 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt.rb @@ -0,0 +1,166 @@ +require 'tilt/mapping' +require 'tilt/template' + +# Namespace for Tilt. This module is not intended to be included anywhere. +module Tilt + # Current version. + VERSION = '2.0.10' + + @default_mapping = Mapping.new + + # @return [Tilt::Mapping] the main mapping object + def self.default_mapping + @default_mapping + end + + # @private + def self.lazy_map + default_mapping.lazy_map + end + + # @see Tilt::Mapping#register + def self.register(template_class, *extensions) + default_mapping.register(template_class, *extensions) + end + + # @see Tilt::Mapping#register_lazy + def self.register_lazy(class_name, file, *extensions) + default_mapping.register_lazy(class_name, file, *extensions) + end + + # @deprecated Use {register} instead. + def self.prefer(template_class, *extensions) + register(template_class, *extensions) + end + + # @see Tilt::Mapping#registered? + def self.registered?(ext) + default_mapping.registered?(ext) + end + + # @see Tilt::Mapping#new + def self.new(file, line=nil, options={}, &block) + default_mapping.new(file, line, options, &block) + end + + # @see Tilt::Mapping#[] + def self.[](file) + default_mapping[file] + end + + # @see Tilt::Mapping#template_for + def self.template_for(file) + default_mapping.template_for(file) + end + + # @see Tilt::Mapping#templates_for + def self.templates_for(file) + default_mapping.templates_for(file) + end + + # @return the template object that is currently rendering. + # + # @example + # tmpl = Tilt['index.erb'].new { '<%= Tilt.current_template %>' } + # tmpl.render == tmpl.to_s + # + # @note This is currently an experimental feature and might return nil + # in the future. + def self.current_template + Thread.current[:tilt_current_template] + end + + # Extremely simple template cache implementation. Calling applications + # create a Tilt::Cache instance and use #fetch with any set of hashable + # arguments (such as those to Tilt.new): + # + # cache = Tilt::Cache.new + # cache.fetch(path, line, options) { Tilt.new(path, line, options) } + # + # Subsequent invocations return the already loaded template object. + # + # @note + # Tilt::Cache is a thin wrapper around Hash. It has the following + # limitations: + # * Not thread-safe. + # * Size is unbounded. + # * Keys are not copied defensively, and should not be modified after + # being passed to #fetch. More specifically, the values returned by + # key#hash and key#eql? should not change. + # If this is too limiting for you, use a different cache implementation. + class Cache + def initialize + @cache = {} + end + + # Caches a value for key, or returns the previously cached value. + # If a value has been previously cached for key then it is + # returned. Otherwise, block is yielded to and its return value + # which may be nil, is cached under key and returned. + # @yield + # @yieldreturn the value to cache for key + def fetch(*key) + @cache.fetch(key) do + @cache[key] = yield + end + end + + # Clears the cache. + def clear + @cache = {} + end + end + + + # Template Implementations ================================================ + + # ERB + register_lazy :ERBTemplate, 'tilt/erb', 'erb', 'rhtml' + register_lazy :ErubisTemplate, 'tilt/erubis', 'erb', 'rhtml', 'erubis' + register_lazy :ErubiTemplate, 'tilt/erubi', 'erb', 'rhtml', 'erubi' + + # Markdown + register_lazy :BlueClothTemplate, 'tilt/bluecloth', 'markdown', 'mkd', 'md' + register_lazy :MarukuTemplate, 'tilt/maruku', 'markdown', 'mkd', 'md' + register_lazy :KramdownTemplate, 'tilt/kramdown', 'markdown', 'mkd', 'md' + register_lazy :RDiscountTemplate, 'tilt/rdiscount', 'markdown', 'mkd', 'md' + register_lazy :RedcarpetTemplate, 'tilt/redcarpet', 'markdown', 'mkd', 'md' + register_lazy :CommonMarkerTemplate, 'tilt/commonmarker', 'markdown', 'mkd', 'md' + register_lazy :PandocTemplate, 'tilt/pandoc', 'markdown', 'mkd', 'md' + + # Rest (sorted by name) + register_lazy :AsciidoctorTemplate, 'tilt/asciidoc', 'ad', 'adoc', 'asciidoc' + register_lazy :BabelTemplate, 'tilt/babel', 'es6', 'babel', 'jsx' + register_lazy :BuilderTemplate, 'tilt/builder', 'builder' + register_lazy :CSVTemplate, 'tilt/csv', 'rcsv' + register_lazy :CoffeeScriptTemplate, 'tilt/coffee', 'coffee' + register_lazy :CoffeeScriptLiterateTemplate, 'tilt/coffee', 'litcoffee' + register_lazy :CreoleTemplate, 'tilt/creole', 'wiki', 'creole' + register_lazy :EtanniTemplate, 'tilt/etanni', 'etn', 'etanni' + register_lazy :HamlTemplate, 'tilt/haml', 'haml' + register_lazy :LessTemplate, 'tilt/less', 'less' + register_lazy :LiquidTemplate, 'tilt/liquid', 'liquid' + register_lazy :LiveScriptTemplate, 'tilt/livescript','ls' + register_lazy :MarkabyTemplate, 'tilt/markaby', 'mab' + register_lazy :NokogiriTemplate, 'tilt/nokogiri', 'nokogiri' + register_lazy :PlainTemplate, 'tilt/plain', 'html' + register_lazy :PrawnTemplate, 'tilt/prawn', 'prawn' + register_lazy :RDocTemplate, 'tilt/rdoc', 'rdoc' + register_lazy :RadiusTemplate, 'tilt/radius', 'radius' + register_lazy :RedClothTemplate, 'tilt/redcloth', 'textile' + register_lazy :RstPandocTemplate, 'tilt/rst-pandoc', 'rst' + register_lazy :SassTemplate, 'tilt/sass', 'sass' + register_lazy :ScssTemplate, 'tilt/sass', 'scss' + register_lazy :SigilTemplate, 'tilt/sigil', 'sigil' + register_lazy :StringTemplate, 'tilt/string', 'str' + register_lazy :TypeScriptTemplate, 'tilt/typescript', 'ts', 'tsx' + register_lazy :WikiClothTemplate, 'tilt/wikicloth', 'wiki', 'mediawiki', 'mw' + register_lazy :YajlTemplate, 'tilt/yajl', 'yajl' + + # External template engines + register_lazy 'Slim::Template', 'slim', 'slim' + register_lazy 'Tilt::HandlebarsTemplate', 'tilt/handlebars', 'handlebars', 'hbs' + register_lazy 'Tilt::OrgTemplate', 'org-ruby', 'org' + register_lazy 'Opal::Processor', 'opal', 'opal', 'rb' + register_lazy 'Tilt::JbuilderTemplate', 'tilt/jbuilder', 'jbuilder' +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/asciidoc.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/asciidoc.rb new file mode 100644 index 0000000000..fb1ccc4024 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/asciidoc.rb @@ -0,0 +1,27 @@ +require 'tilt/template' +require 'asciidoctor' + +# AsciiDoc see: http://asciidoc.org/ +module Tilt + # Asciidoctor implementation for AsciiDoc see: + # http://asciidoctor.github.com/ + # + # Asciidoctor is an open source, pure-Ruby processor for + # converting AsciiDoc documents or strings into HTML 5, + # DocBook 4.5 and other formats. + class AsciidoctorTemplate < Template + self.default_mime_type = 'text/html' + + def prepare + options[:header_footer] = false if options[:header_footer].nil? + end + + def evaluate(scope, locals, &block) + @output ||= Asciidoctor.render(data, options, &block) + end + + def allows_script? + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/babel.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/babel.rb new file mode 100644 index 0000000000..a498ec9a2c --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/babel.rb @@ -0,0 +1,16 @@ +require 'tilt/template' +require 'babel/transpiler' + +module Tilt + class BabelTemplate < Template + self.default_mime_type = 'application/javascript' + + def prepare + options[:filename] ||= file + end + + def evaluate(scope, locals, &block) + @output ||= Babel::Transpiler.transform(data)["code"] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/bluecloth.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/bluecloth.rb new file mode 100644 index 0000000000..7ed59c71b3 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/bluecloth.rb @@ -0,0 +1,24 @@ +require 'tilt/template' +require 'bluecloth' + +module Tilt + # BlueCloth Markdown implementation. See: + # http://deveiate.org/projects/BlueCloth/ + class BlueClothTemplate < Template + self.default_mime_type = 'text/html' + + def prepare + @engine = BlueCloth.new(data, options) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/builder.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/builder.rb new file mode 100644 index 0000000000..adde14bd1b --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/builder.rb @@ -0,0 +1,37 @@ +require 'tilt/template' +require 'builder' + +module Tilt + # Builder template implementation. See: + # http://builder.rubyforge.org/ + class BuilderTemplate < Template + self.default_mime_type = 'text/xml' + + def prepare + options[:indent] ||= 2 + end + + def evaluate(scope, locals, &block) + xml = (locals[:xml] || ::Builder::XmlMarkup.new(options)) + + if data.respond_to?(:to_str) + if !locals[:xml] + locals = locals.merge(:xml => xml) + end + return super(scope, locals, &block) + end + + data.call(xml) + xml.target! + end + + def precompiled_postamble(locals) + "xml.target!" + end + + def precompiled_template(locals) + data.to_str + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/coffee.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/coffee.rb new file mode 100644 index 0000000000..08feb502fe --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/coffee.rb @@ -0,0 +1,58 @@ +require 'tilt/template' +require 'coffee_script' + +module Tilt + # CoffeeScript template implementation. See: + # http://coffeescript.org/ + # + # CoffeeScript templates do not support object scopes, locals, or yield. + class CoffeeScriptTemplate < Template + self.default_mime_type = 'application/javascript' + + @@default_bare = false + + def self.default_bare + @@default_bare + end + + def self.default_bare=(value) + @@default_bare = value + end + + # DEPRECATED + def self.default_no_wrap + @@default_bare + end + + # DEPRECATED + def self.default_no_wrap=(value) + @@default_bare = value + end + + def self.literate? + false + end + + def prepare + if !options.key?(:bare) and !options.key?(:no_wrap) + options[:bare] = self.class.default_bare + end + options[:literate] ||= self.class.literate? + end + + def evaluate(scope, locals, &block) + @output ||= CoffeeScript.compile(data, options) + end + + def allows_script? + false + end + end + + class CoffeeScriptLiterateTemplate < CoffeeScriptTemplate + def self.literate? + true + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/commonmarker.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/commonmarker.rb new file mode 100644 index 0000000000..dd331e4b22 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/commonmarker.rb @@ -0,0 +1,78 @@ +require 'tilt/template' +require 'commonmarker' + +module Tilt + class CommonMarkerTemplate < Template + self.default_mime_type = 'text/html' + + OPTION_ALIAS = { + :smartypants => :SMART + } + PARSE_OPTIONS = [ + :SMART, + :smartypants, + ].freeze + RENDER_OPTIONS = [ + :GITHUB_PRE_LANG, + :HARDBREAKS, + :NOBREAKS, + :SAFE, + :SOURCEPOS, + ].freeze + EXTENSIONS = [ + :autolink, + :strikethrough, + :table, + :tagfilter, + ].freeze + + def extensions + EXTENSIONS.select do |extension| + options[extension] + end + end + + def parse_options + raw_options = PARSE_OPTIONS.select do |option| + options[option] + end + actual_options = raw_options.map do |option| + OPTION_ALIAS[option] || option + end + + if actual_options.any? + actual_options + else + :DEFAULT + end + end + + def render_options + raw_options = RENDER_OPTIONS.select do |option| + options[option] + end + actual_options = raw_options.map do |option| + OPTION_ALIAS[option] || option + end + if actual_options.any? + actual_options + else + :DEFAULT + end + end + + def prepare + @engine = nil + @output = nil + end + + def evaluate(scope, locals, &block) + doc = CommonMarker.render_doc(data, parse_options, extensions) + doc.to_html(render_options, extensions) + end + + def allows_script? + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/creole.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/creole.rb new file mode 100644 index 0000000000..04cff16f79 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/creole.rb @@ -0,0 +1,25 @@ +require 'tilt/template' +require 'creole' + +module Tilt + # Creole implementation. See: + # http://www.wikicreole.org/ + class CreoleTemplate < Template + def prepare + opts = {} + [:allowed_schemes, :extensions, :no_escape].each do |k| + opts[k] = options[k] if options[k] + end + @engine = Creole::Parser.new(data, opts) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/csv.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/csv.rb new file mode 100644 index 0000000000..fd0e602a3f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/csv.rb @@ -0,0 +1,65 @@ +require 'tilt/template' + +if RUBY_VERSION >= '1.9.0' + require 'csv' +else + require 'fastercsv' +end + +module Tilt + + # CSV Template implementation. See: + # http://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html + # + # == Example + # + # # Example of csv template + # tpl = <<-EOS + # # header + # csv << ['NAME', 'ID'] + # + # # data rows + # @people.each do |person| + # csv << [person[:name], person[:id]] + # end + # EOS + # + # @people = [ + # {:name => "Joshua Peek", :id => 1}, + # {:name => "Ryan Tomayko", :id => 2}, + # {:name => "Simone Carletti", :id => 3} + # ] + # + # template = Tilt::CSVTemplate.new { tpl } + # template.render(self) + # + class CSVTemplate < Template + self.default_mime_type = 'text/csv' + + def self.engine + if RUBY_VERSION >= '1.9.0' && defined? ::CSV + ::CSV + elsif defined? ::FasterCSV + ::FasterCSV + end + end + + def prepare + @outvar = options.delete(:outvar) || '_csvout' + end + + def precompiled_template(locals) + <<-RUBY + #{@outvar} = #{self.class.engine}.generate(#{options}) do |csv| + #{data} + end + RUBY + end + + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/dummy.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/dummy.rb new file mode 100644 index 0000000000..437a1d978d --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/dummy.rb @@ -0,0 +1,3 @@ +# Used for detecting autoloading bug in JRuby +class Tilt::Dummy; end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erb.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erb.rb new file mode 100644 index 0000000000..cbf4e0cd04 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erb.rb @@ -0,0 +1,63 @@ +require 'tilt/template' +require 'erb' + +module Tilt + # ERB template implementation. See: + # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html + class ERBTemplate < Template + @@default_output_variable = '_erbout' + + SUPPORTS_KVARGS = ::ERB.instance_method(:initialize).parameters.assoc(:key) rescue false + + def self.default_output_variable + @@default_output_variable + end + + def self.default_output_variable=(name) + warn "#{self}.default_output_variable= has been replaced with the :outvar-option" + @@default_output_variable = name + end + + def prepare + @outvar = options[:outvar] || self.class.default_output_variable + options[:trim] = '<>' if !(options[:trim] == false) && (options[:trim].nil? || options[:trim] == true) + @engine = if SUPPORTS_KVARGS + ::ERB.new(data, trim_mode: options[:trim], eoutvar: @outvar) + else + ::ERB.new(data, options[:safe], options[:trim], @outvar) + end + end + + def precompiled_template(locals) + source = @engine.src + source + end + + def precompiled_preamble(locals) + <<-RUBY + begin + __original_outvar = #{@outvar} if defined?(#{@outvar}) + #{super} + RUBY + end + + def precompiled_postamble(locals) + <<-RUBY + #{super} + ensure + #{@outvar} = __original_outvar + end + RUBY + end + + # ERB generates a line to specify the character coding of the generated + # source in 1.9. Account for this in the line offset. + if RUBY_VERSION >= '1.9.0' + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubi.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubi.rb new file mode 100644 index 0000000000..ea146e43d7 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubi.rb @@ -0,0 +1,32 @@ +require 'tilt/template' +require 'erubi' + +module Tilt + # Erubi (a simplified version of Erubis) template implementation. + # See https://github.com/jeremyevans/erubi + # + # ErubiTemplate supports the following additional options, in addition + # to the options supported by the Erubi engine: + # + # :engine_class :: allows you to specify a custom engine class to use + # instead of the default (which is ::Erubi::Engine). + class ErubiTemplate < Template + def prepare + @options.merge!(:preamble => false, :postamble => false, :ensure=>true) + + engine_class = @options[:engine_class] || Erubi::Engine + + @engine = engine_class.new(data, @options) + @outvar = @engine.bufvar + + # Remove dup after tilt supports frozen source. + @src = @engine.src.dup + + @engine + end + + def precompiled_template(locals) + @src + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubis.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubis.rb new file mode 100644 index 0000000000..0f18078ffd --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/erubis.rb @@ -0,0 +1,43 @@ +require 'tilt/erb' +require 'erubis' + +module Tilt + # Erubis template implementation. See: + # http://www.kuwata-lab.com/erubis/ + # + # ErubisTemplate supports the following additional options, which are not + # passed down to the Erubis engine: + # + # :engine_class allows you to specify a custom engine class to use + # instead of the default (which is ::Erubis::Eruby). + # + # :escape_html when true, ::Erubis::EscapedEruby will be used as + # the engine class instead of the default. All content + # within <%= %> blocks will be automatically html escaped. + class ErubisTemplate < ERBTemplate + def prepare + @outvar = options.delete(:outvar) || self.class.default_output_variable + @options.merge!(:preamble => false, :postamble => false, :bufvar => @outvar) + engine_class = options.delete(:engine_class) + engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html) + @engine = (engine_class || ::Erubis::Eruby).new(data, options) + end + + def precompiled_preamble(locals) + [super, "#{@outvar} = _buf = String.new"].join("\n") + end + + def precompiled_postamble(locals) + [@outvar, super].join("\n") + end + + # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. + # Override and adjust back. + if RUBY_VERSION >= '1.9.0' + def precompiled(locals) + source, offset = super + [source, offset - 1] + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/etanni.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/etanni.rb new file mode 100644 index 0000000000..e598ceddc9 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/etanni.rb @@ -0,0 +1,27 @@ +require 'tilt/template' + +module Tilt + class EtanniTemplate < Template + def prepare + separator = data.hash.abs + chomp = "<<#{separator}.chomp!" + start = "\n_out_ << #{chomp}\n" + stop = "\n#{separator}\n" + replacement = "#{stop}\\1#{start}" + + temp = data.strip + temp.gsub!(/<\?r\s+(.*?)\s+\?>/m, replacement) + + @code = "_out_ = [<<#{separator}.chomp!]\n#{temp}#{stop}_out_.join" + end + + def precompiled_template(locals) + @code + end + + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/haml.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/haml.rb new file mode 100644 index 0000000000..fc7f205e08 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/haml.rb @@ -0,0 +1,86 @@ +require 'tilt/template' +require 'haml' + +module Tilt + # Haml template implementation. See: + # http://haml.hamptoncatlin.com/ + class HamlTemplate < Template + self.default_mime_type = 'text/html' + + # `Gem::Version.correct?` may return false because of Haml::VERSION #=> "3.1.8 (Separated Sally)". After Haml 4, it's always correct. + if Gem::Version.correct?(Haml::VERSION) && Gem::Version.new(Haml::VERSION) >= Gem::Version.new('5.0.0.beta.2') + def prepare + options = {}.update(@options).update(filename: eval_file, line: line) + if options.include?(:outvar) + options[:buffer] = options.delete(:outvar) + options[:save_buffer] = true + end + @engine = ::Haml::TempleEngine.new(options) + @engine.compile(data) + end + + def evaluate(scope, locals, &block) + raise ArgumentError, 'invalid scope: must not be frozen' if scope.frozen? + super + end + + def precompiled_template(locals) + @engine.precompiled_with_ambles( + [], + after_preamble: <<-RUBY + __in_erb_template = true + _haml_locals = locals + RUBY + ) + end + else # Following definitions are for Haml <= 4 and deprecated. + def prepare + options = @options.merge(:filename => eval_file, :line => line) + @engine = ::Haml::Engine.new(data, options) + end + + def evaluate(scope, locals, &block) + raise ArgumentError, 'invalid scope: must not be frozen' if scope.frozen? + + if @engine.respond_to?(:precompiled_method_return_value, true) + super + else + @engine.render(scope, locals, &block) + end + end + + # Precompiled Haml source. Taken from the precompiled_with_ambles + # method in Haml::Precompiler: + # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126 + def precompiled_template(locals) + @engine.precompiled + end + + def precompiled_preamble(locals) + local_assigns = super + @engine.instance_eval do + <<-RUBY + begin + extend Haml::Helpers + _hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, #{options_for_buffer.inspect}) + _erbout = _hamlout.buffer + __in_erb_template = true + _haml_locals = locals + #{local_assigns} + RUBY + end + end + + def precompiled_postamble(locals) + @engine.instance_eval do + <<-RUBY + #{precompiled_method_return_value} + ensure + @haml_buffer = @haml_buffer.upper if haml_buffer + end + RUBY + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/kramdown.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/kramdown.rb new file mode 100644 index 0000000000..c69a07396f --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/kramdown.rb @@ -0,0 +1,25 @@ +require 'tilt/template' +require 'kramdown' + +module Tilt + # Kramdown Markdown implementation. See: + # http://kramdown.rubyforge.org/ + class KramdownTemplate < Template + DUMB_QUOTES = [39, 39, 34, 34] + + def prepare + options[:smart_quotes] = DUMB_QUOTES unless options[:smartypants] + @engine = Kramdown::Document.new(data, options) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/less.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/less.rb new file mode 100644 index 0000000000..81318a62ca --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/less.rb @@ -0,0 +1,30 @@ +require 'tilt/template' +require 'less' + +module Tilt + # Lessscss template implementation. See: + # http://lesscss.org/ + # + # Less templates do not support object scopes, locals, or yield. + class LessTemplate < Template + self.default_mime_type = 'text/css' + + def prepare + if ::Less.const_defined? :Engine + @engine = ::Less::Engine.new(data) + else + parser = ::Less::Parser.new(options.merge :filename => eval_file, :line => line) + @engine = parser.parse(data) + end + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_css(options) + end + + def allows_script? + false + end + end +end + diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/liquid.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/liquid.rb new file mode 100644 index 0000000000..f2b47b5939 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/liquid.rb @@ -0,0 +1,44 @@ +require 'tilt/template' +require 'liquid' + +module Tilt + # Liquid template implementation. See: + # http://liquidmarkup.org/ + # + # Liquid is designed to be a *safe* template system and threfore + # does not provide direct access to execuatable scopes. In order to + # support a +scope+, the +scope+ must be able to represent itself + # as a hash by responding to #to_h. If the +scope+ does not respond + # to #to_h it will be ignored. + # + # LiquidTemplate does not support yield blocks. + # + # It's suggested that your program require 'liquid' at load + # time when using this template engine. + class LiquidTemplate < Template + def prepare + @engine = ::Liquid::Template.parse(data, liquid_options) + end + + def evaluate(scope, locals, &block) + locals = locals.inject({}){ |h,(k,v)| h[k.to_s] = v ; h } + if scope.respond_to?(:to_h) + scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h } + locals = scope.merge(locals) + end + locals['yield'] = block.nil? ? '' : yield + locals['content'] = locals['yield'] + @engine.render(locals) + end + + def allows_script? + false + end + + private + + def liquid_options + { line_numbers: true }.merge options + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/livescript.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/livescript.rb new file mode 100644 index 0000000000..6c3cabd318 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/livescript.rb @@ -0,0 +1,23 @@ +require 'tilt/template' +require 'livescript' + +module Tilt + # LiveScript template implementation. See: + # http://livescript.net/ + # + # LiveScript templates do not support object scopes, locals, or yield. + class LiveScriptTemplate < Template + self.default_mime_type = 'application/javascript' + + def prepare + end + + def evaluate(scope, locals, &block) + @output ||= LiveScript.compile(data, options) + end + + def allows_script? + false + end + end +end diff --git a/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/mapping.rb b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/mapping.rb new file mode 100644 index 0000000000..de78cd2552 --- /dev/null +++ b/vendor/bundle/ruby/3.0.0/gems/tilt-2.0.10/lib/tilt/mapping.rb @@ -0,0 +1,293 @@ +require 'monitor' + +module Tilt + # Tilt::Mapping associates file extensions with template implementations. + # + # mapping = Tilt::Mapping.new + # mapping.register(Tilt::RDocTemplate, 'rdoc') + # mapping['index.rdoc'] # => Tilt::RDocTemplate + # mapping.new('index.rdoc').render + # + # You can use {#register} to register a template class by file + # extension, {#registered?} to see if a file extension is mapped, + # {#[]} to lookup template classes, and {#new} to instantiate template + # objects. + # + # Mapping also supports *lazy* template implementations. Note that regularly + # registered template implementations *always* have preference over lazily + # registered template implementations. You should use {#register} if you + # depend on a specific template implementation and {#register_lazy} if there + # are multiple alternatives. + # + # mapping = Tilt::Mapping.new + # mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') + # mapping['index.md'] + # # => RDiscount::Template + # + # {#register_lazy} takes a class name, a filename, and a list of file + # extensions. When you try to lookup a template name that matches the + # file extension, Tilt will automatically try to require the filename and + # constantize the class name. + # + # Unlike {#register}, there can be multiple template implementations + # registered lazily to the same file extension. Tilt will attempt to load the + # template implementations in order (registered *last* would be tried first), + # returning the first which doesn't raise LoadError. + # + # If all of the registered template implementations fails, Tilt will raise + # the exception of the first, since that was the most preferred one. + # + # mapping = Tilt::Mapping.new + # mapping.register_lazy('Bluecloth::Template', 'bluecloth/template', 'md') + # mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') + # mapping['index.md'] + # # => RDiscount::Template + # + # In the previous example we say that RDiscount has a *higher priority* than + # BlueCloth. Tilt will first try to `require "rdiscount/template"`, falling + # back to `require "bluecloth/template"`. If none of these are successful, + # the first error will be raised. + class Mapping + # @private + attr_reader :lazy_map, :template_map + + def initialize + @template_map = Hash.new + @lazy_map = Hash.new { |h, k| h[k] = [] } + end + + # @private + def initialize_copy(other) + @template_map = other.template_map.dup + @lazy_map = other.lazy_map.dup + end + + # Registers a lazy template implementation by file extension. You + # can have multiple lazy template implementations defined on the + # same file extension, in which case the template implementation + # defined *last* will be attempted loaded *first*. + # + # @param class_name [String] Class name of a template class. + # @param file [String] Filename where the template class is defined. + # @param extensions [Array] List of extensions. + # @return [void] + # + # @example + # mapping.register_lazy 'MyEngine::Template', 'my_engine/template', 'mt' + # + # defined?(MyEngine::Template) # => false + # mapping['index.mt'] # => MyEngine::Template + # defined?(MyEngine::Template) # => true + def register_lazy(class_name, file, *extensions) + # Internal API + if class_name.is_a?(Symbol) + Tilt.autoload class_name, file + class_name = "Tilt::#{class_name}" + end + + extensions.each do |ext| + @lazy_map[ext].unshift([class_name, file]) + end + end + + # Registers a template implementation by file extension. There can only be + # one template implementation per file extension, and this method will + # override any existing mapping. + # + # @param template_class + # @param extensions [Array] List of extensions. + # @return [void] + # + # @example + # mapping.register MyEngine::Template, 'mt' + # mapping['index.mt'] # => MyEngine::Template + def register(template_class, *extensions) + if template_class.respond_to?(:to_str) + # Support register(ext, template_class) too + extensions, template_class = [template_class], extensions[0] + end + + extensions.each do |ext| + @template_map[ext.to_s] = template_class + end + end + + # Checks if a file extension is registered (either eagerly or + # lazily) in this mapping. + # + # @param ext [String] File extension. + # + # @example + # mapping.registered?('erb') # => true + # mapping.registered?('nope') # => false + def registered?(ext) + @template_map.has_key?(ext.downcase) or lazy?(ext) + end + + # Instantiates a new template class based on the file. + # + # @raise [RuntimeError] if there is no template class registered for the + # file name. + # + # @example + # mapping.new('index.mt') # => instance of MyEngine::Template + # + # @see Tilt::Template.new + def new(file, line=nil, options={}, &block) + if template_class = self[file] + template_class.new(file, line, options, &block) + else + fail "No template engine registered for #{File.basename(file)}" + end + end + + # Looks up a template class based on file name and/or extension. + # + # @example + # mapping['views/hello.erb'] # => Tilt::ERBTemplate + # mapping['hello.erb'] # => Tilt::ERBTemplate + # mapping['erb'] # => Tilt::ERBTemplate + # + # @return [template class] + def [](file) + _, ext = split(file) + ext && lookup(ext) + end + + alias template_for [] + + # Looks up a list of template classes based on file name. If the file name + # has multiple extensions, it will return all template classes matching the + # extensions from the end. + # + # @example + # mapping.templates_for('views/index.haml.erb') + # # => [Tilt::ERBTemplate, Tilt::HamlTemplate] + # + # @return [Array